1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/web/ synced 2025-03-09 00:00:01 +01:00

Merge branch 'develop' into '120-add-full-screen-support-for-visualizations'

# Conflicts:
#   src/styles/app.css
This commit is contained in:
Markus Grigull 2017-09-18 18:32:51 +02:00
commit 2eec87e0d3
63 changed files with 11839 additions and 357 deletions

View file

@ -4,10 +4,17 @@ FROM node:8.2
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# use changes to package.json to force Docker not to use the cache
# when we change our application's nodejs dependencies:
ADD package.json /usr/src/app
RUN npm install
# Install app dependencies
COPY . /usr/src/app
RUN npm install && npm run build
RUN npm run build
VOLUME /usr/src/app/build
# Run the app in a local webserver
RUN npm install -g serve
EXPOSE 5000
CMD [ "true" ]
CMD [ "serve", "-s", "build" ]

9364
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -12,6 +12,7 @@
"d3-shape": "^1.2.0",
"d3-time-format": "^2.0.5",
"es6-promise": "^4.0.5",
"file-saver": "^1.3.3",
"flux": "^3.1.2",
"gaugeJS": "^1.3.2",
"immutable": "^3.8.1",
@ -28,11 +29,11 @@
"react-router": "^4.1.2",
"react-router-dom": "^4.1.2",
"react-sortable-tree": "^0.1.19",
"superagent": "^3.5.0"
"superagent": "^3.5.0",
"react-scripts": "1.0.10"
},
"devDependencies": {
"chai": "^4.1.0",
"react-scripts": "1.0.10"
"chai": "^4.1.0"
},
"scripts": {
"start": "react-scripts start",

View file

@ -12,6 +12,7 @@ import EditWidgetSignalsControl from '../../../components/dialog/edit-widget-sig
import EditWidgetOrientation from '../../../components/dialog/edit-widget-orientation';
import EditWidgetTextSizeControl from '../../../components/dialog/edit-widget-text-size-control';
import EditWidgetAspectControl from '../../../components/dialog/edit-widget-aspect-control';
import EditWidgetCheckboxControl from '../../../components/dialog/edit-widget-checkbox-control';
describe('edit widget control creator', () => {
it('should not return null', () => {
@ -20,7 +21,7 @@ describe('edit widget control creator', () => {
});
var runs = [
{ args: { widgetType: 'Value' }, result: { controlNumber: 4, controlTypes: [EditWidgetTextControl, EditWidgetSimulatorControl, EditWidgetSignalControl, EditWidgetTextSizeControl] } },
{ args: { widgetType: 'Value' }, result: { controlNumber: 5, controlTypes: [EditWidgetTextControl, EditWidgetSimulatorControl, EditWidgetSignalControl, EditWidgetTextSizeControl, EditWidgetCheckboxControl] } },
{ args: { widgetType: 'Plot' }, result: { controlNumber: 4, controlTypes: [EditWidgetTimeControl, EditWidgetSimulatorControl, EditWidgetSignalsControl, EditWidgetTextControl] } },
{ args: { widgetType: 'Table' }, result: { controlNumber: 1, controlTypes: [EditWidgetSimulatorControl] } },
{ args: { widgetType: 'Image' }, result: { controlNumber: 2, controlTypes: [EditImageWidgetControl, EditWidgetAspectControl] } },

View file

@ -128,9 +128,9 @@ class RestAPI {
});
}
upload(url, data, token) {
upload(url, data, token, progressCallback) {
return new Promise(function (resolve, reject) {
var req = request.post(url).send(data);
const req = request.post(url).send(data).on('progress', progressCallback);
if (token != null) {
req.set('x-access-token', token);

View file

@ -23,17 +23,26 @@ import React from 'react';
import { Modal, Button } from 'react-bootstrap';
class Dialog extends React.Component {
closeModal() {
closeModal = (event) => {
this.props.onClose(false);
}
cancelModal() {
cancelModal = (event) => {
this.props.onClose(true);
}
onKeyPress = (event) => {
/*if (event.key === 'Enter') {
// prevent input from submitting
event.preventDefault();
this.closeModal(false);
}*/
}
render() {
return (
<Modal show={this.props.show} onEnter={this.props.onReset}>
<Modal keyboard show={this.props.show} onEnter={this.props.onReset} onHide={this.cancelModal} onKeyPress={this.onKeyPress}>
<Modal.Header>
<Modal.Title>{this.props.title}</Modal.Title>
</Modal.Header>
@ -43,8 +52,8 @@ class Dialog extends React.Component {
</Modal.Body>
<Modal.Footer>
<Button onClick={() => this.cancelModal()}>Cancel</Button>
<Button bsStyle="primary" type="submit" onClick={() => this.closeModal()} disabled={!this.props.valid}>{this.props.buttonTitle}</Button>
<Button onClick={this.cancelModal}>Cancel</Button>
<Button onClick={this.closeModal} disabled={!this.props.valid}>{this.props.buttonTitle}</Button>
</Modal.Footer>
</Modal>
);

View file

@ -41,7 +41,9 @@ class NewNodeDialog extends React.Component {
onClose(canceled) {
if (canceled === false) {
this.props.onClose(this.state);
if (this.valid) {
this.props.onClose(this.state);
}
} else {
this.props.onClose();
}

View file

@ -39,7 +39,9 @@ class EditProjectDialog extends React.Component {
onClose(canceled) {
if (canceled === false) {
this.props.onClose(this.state);
if (this.valid) {
this.props.onClose(this.state);
}
} else {
this.props.onClose();
}

View file

@ -42,7 +42,9 @@ class EditSimulationModelDialog extends React.Component {
onClose(canceled) {
if (canceled === false) {
this.props.onClose(this.state);
if (this.valid) {
this.props.onClose(this.state);
}
} else {
this.props.onClose();
}

View file

@ -38,7 +38,9 @@ class EditSimulationDialog extends React.Component {
onClose(canceled) {
if (canceled === false) {
this.props.onClose(this.state);
if (this.valid) {
this.props.onClose(this.state);
}
} else {
this.props.onClose();
}

View file

@ -37,7 +37,9 @@ class EditSimulatorDialog extends React.Component {
onClose(canceled) {
if (canceled === false) {
this.props.onClose(this.state);
if (this.valid) {
this.props.onClose(this.state);
}
} else {
this.props.onClose();
}

View file

@ -40,7 +40,9 @@ class EditUserDialog extends React.Component {
onClose(canceled) {
if (canceled === false) {
this.props.onClose(this.state);
if (this.valid) {
this.props.onClose(this.state);
}
} else {
this.props.onClose();
}

View file

@ -38,7 +38,9 @@ class EditVisualizationDialog extends React.Component {
onClose(canceled) {
if (canceled === false) {
this.props.onClose(this.state);
if (this.valid) {
this.props.onClose(this.state);
}
} else {
this.props.onClose();
}

View file

@ -0,0 +1,45 @@
/**
* File: edit-widget-checkbox-control.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 19.08.2017
*
* This file is part of VILLASweb.
*
* VILLASweb is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VILLASweb is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import React from 'react';
import { FormGroup, Checkbox } from 'react-bootstrap';
class EditWidgetCheckboxControl extends React.Component {
constructor(props) {
super(props);
this.state = {
widget: {}
};
}
componentWillReceiveProps(nextProps) {
this.setState({ widget: nextProps.widget });
}
render() {
return <FormGroup>
<Checkbox id={this.props.controlId} checked={this.state.widget[this.props.controlId] || ''} onChange={e => this.props.handleChange(e)}>{this.props.text}</Checkbox>
</FormGroup>;
}
}
export default EditWidgetCheckboxControl;

View file

@ -0,0 +1,131 @@
/**
* File: edit-widget-color-zones-control.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 20.08.2017
*
* This file is part of VILLASweb.
*
* VILLASweb is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VILLASweb is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import React from 'react';
import { FormGroup, ControlLabel, Button, Glyphicon } from 'react-bootstrap';
import Table from '../table';
import TableColumn from '../table-column';
class EditWidgetColorZonesControl extends React.Component {
constructor(props) {
super(props);
this.state = {
widget: {
zones: []
},
selectedZones: []
};
}
componentWillReceiveProps(nextProps) {
this.setState({ widget: nextProps.widget });
}
addZone = () => {
// add row
const widget = this.state.widget;
widget.zones.push({ strokeStyle: 'ffffff', min: 0, max: 100 });
this.setState({ widget });
this.sendEvent(widget);
}
removeZones = () => {
// remove zones
const widget = this.state.widget;
this.state.selectedZones.forEach(row => {
widget.zones.splice(row, 1);
});
this.setState({ selectedZones: [], widget });
this.sendEvent(widget);
}
changeCell = (event, row, column) => {
// change row
const widget = this.state.widget;
if (column === 1) {
widget.zones[row].strokeStyle = event.target.value;
} else if (column === 2) {
widget.zones[row].min = event.target.value;
} else if (column === 3) {
widget.zones[row].max = event.target.value;
}
this.setState({ widget });
this.sendEvent(widget);
}
sendEvent(widget) {
// create event
const event = {
target: {
id: 'zones',
value: widget.zones
}
};
this.props.handleChange(event);
}
checkedCell = (row, event) => {
// update selected rows
const selectedZones = this.state.selectedZones;
if (event.target.checked) {
if (selectedZones.indexOf(row) === -1) {
selectedZones.push(row);
}
} else {
let index = selectedZones.indexOf(row);
if (row > -1) {
selectedZones.splice(index, 1);
}
}
this.setState({ selectedZones });
}
render() {
return <FormGroup>
<ControlLabel>Color zones</ControlLabel>
<Table data={this.state.widget.zones}>
<TableColumn width="20" checkbox onChecked={this.checkedCell} />
<TableColumn title="Color" dataKey="strokeStyle" inlineEditable onInlineChange={this.changeCell} />
<TableColumn title="Minimum" dataKey="min" inlineEditable onInlineChange={this.changeCell} />
<TableColumn title="Maximum" dataKey="max" inlineEditable onInlineChange={this.changeCell} />
</Table>
<Button onClick={this.addZone} disabled={!this.props.widget.colorZones}><Glyphicon glyph="plus" /> Add</Button>
<Button onClick={this.removeZones} disabled={!this.props.widget.colorZones}><Glyphicon glyph="minus" /> Remove</Button>
</FormGroup>;
}
}
export default EditWidgetColorZonesControl;

View file

@ -31,6 +31,10 @@ import EditWidgetSignalsControl from './edit-widget-signals-control';
import EditWidgetOrientation from './edit-widget-orientation';
import EditWidgetAspectControl from './edit-widget-aspect-control';
import EditWidgetTextSizeControl from './edit-widget-text-size-control';
import EditWidgetCheckboxControl from './edit-widget-checkbox-control';
import EditWidgetColorZonesControl from './edit-widget-color-zones-control';
import EditWidgetMinMaxControl from './edit-widget-min-max-control';
import EditWidgetHTMLContent from './edit-widget-html-content';
export default function createControls(widgetType = null, widget = null, sessionToken = null, files = null, validateForm, simulation, handleChange) {
// Use a list to concatenate the controls according to the widget type
@ -42,10 +46,11 @@ export default function createControls(widgetType = null, widget = null, session
handleChange([e, {target: {id: 'signal', value: 0}}]);
}
dialogControls.push(
<EditWidgetTextControl key={1} widget={widget} controlId={'name'} label={'Text'} placeholder={'Enter text'} validate={id => validateForm(id)} handleChange={e => handleChange(e)} />,
<EditWidgetSimulatorControl key={2} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => valueBoundOnChange(e)} />,
<EditWidgetSignalControl key={3} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetTextSizeControl key={4} widget={widget} handleChange={e => handleChange(e)} />
<EditWidgetTextControl key={0} widget={widget} controlId={'name'} label={'Text'} placeholder={'Enter text'} validate={id => validateForm(id)} handleChange={e => handleChange(e)} />,
<EditWidgetSimulatorControl key={1} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => valueBoundOnChange(e)} />,
<EditWidgetSignalControl key={2} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetTextSizeControl key={3} widget={widget} handleChange={e => handleChange(e)} />,
<EditWidgetCheckboxControl key={4} widget={widget} controlId={'showUnit'} text="Show unit" handleChange={e => handleChange(e)} />
);
break;
case 'Plot':
@ -53,21 +58,22 @@ export default function createControls(widgetType = null, widget = null, session
handleChange([e, {target: {id: 'signals', value: []}}]);
}
dialogControls.push(
<EditWidgetTimeControl key={1} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetSimulatorControl key={2} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => plotBoundOnChange(e)} />,
<EditWidgetSignalsControl key={3} controlId={'signals'} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetTextControl key={4} controlId={'ylabel'} label={'Y-Axis name'} placeholder={'Enter a name for the y-axis'} widget={widget} handleChange={(e) => handleChange(e)} />
<EditWidgetTimeControl key={0} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetSimulatorControl key={1} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => plotBoundOnChange(e)} />,
<EditWidgetSignalsControl key={2} controlId={'signals'} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetTextControl key={3} controlId={'ylabel'} label={'Y-Axis name'} placeholder={'Enter a name for the y-axis'} widget={widget} handleChange={(e) => handleChange(e)} />,
<EditWidgetMinMaxControl key={4} widget={widget} controlId="y" handleChange={e => handleChange(e)} />
);
break;
case 'Table':
dialogControls.push(
<EditWidgetSimulatorControl key={1} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />
<EditWidgetSimulatorControl key={0} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />
);
break;
case 'Image':
dialogControls.push(
<EditImageWidgetControl key={1} sessionToken={sessionToken} widget={widget} files={files} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetAspectControl key={2} widget={widget} handleChange={e => handleChange(e)} />
<EditImageWidgetControl key={0} sessionToken={sessionToken} widget={widget} files={files} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetAspectControl key={1} widget={widget} handleChange={e => handleChange(e)} />
);
break;
case 'Gauge':
@ -75,9 +81,12 @@ export default function createControls(widgetType = null, widget = null, session
handleChange([e, {target: {id: 'signal', value: ''}}]);
}
dialogControls.push(
<EditWidgetTextControl key={1} widget={widget} controlId={'name'} label={'Text'} placeholder={'Enter text'} validate={id => validateForm(id)} handleChange={e => handleChange(e)} />,
<EditWidgetSimulatorControl key={2} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => gaugeBoundOnChange(e) } />,
<EditWidgetSignalControl key={3} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />
<EditWidgetTextControl key={0} widget={widget} controlId={'name'} label={'Text'} placeholder={'Enter text'} validate={id => validateForm(id)} handleChange={e => handleChange(e)} />,
<EditWidgetSimulatorControl key={1} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => gaugeBoundOnChange(e) } />,
<EditWidgetSignalControl key={2} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetCheckboxControl key={3} widget={widget} controlId="colorZones" text="Show color zones" handleChange={e => handleChange(e)} />,
<EditWidgetColorZonesControl key={4} widget={widget} handleChange={e => handleChange(e)} />,
<EditWidgetMinMaxControl key={5} widget={widget} controlId="value" handleChange={e => handleChange(e)} />
);
break;
case 'PlotTable':
@ -85,25 +94,27 @@ export default function createControls(widgetType = null, widget = null, session
handleChange([e, {target: {id: 'preselectedSignals', value: []}}]);
}
dialogControls.push(
<EditWidgetSimulatorControl key={1} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => plotTableBoundOnChange(e)} />,
<EditWidgetSignalsControl key={2} controlId={'preselectedSignals'} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetTextControl key={3} controlId={'ylabel'} label={'Y-Axis'} placeholder={'Enter a name for the Y-axis'} widget={widget} handleChange={(e) => handleChange(e)} />
<EditWidgetSimulatorControl key={0} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => plotTableBoundOnChange(e)} />,
<EditWidgetSignalsControl key={1} controlId={'preselectedSignals'} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetTextControl key={2} controlId={'ylabel'} label={'Y-Axis'} placeholder={'Enter a name for the Y-axis'} widget={widget} handleChange={(e) => handleChange(e)} />,
<EditWidgetTimeControl key={3} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetMinMaxControl key={4} widget={widget} controlId="y" handleChange={e => handleChange(e)} />
);
break;
case 'Slider':
dialogControls.push(
<EditWidgetOrientation key={1} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />
<EditWidgetOrientation key={0} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />
);
break;
case 'Button':
dialogControls.push(
<EditWidgetColorControl key={1} widget={widget} controlId={'background_color'} label={'Background'} validate={(id) => validateForm(id)} handleChange={(e) => handleChange(e)} />,
<EditWidgetColorControl key={2} widget={widget} controlId={'font_color'} label={'Font color'} validate={(id) => validateForm(id)} handleChange={(e) => handleChange(e)} />
<EditWidgetColorControl key={0} widget={widget} controlId={'background_color'} label={'Background'} validate={(id) => validateForm(id)} handleChange={(e) => handleChange(e)} />,
<EditWidgetColorControl key={1} widget={widget} controlId={'font_color'} label={'Font color'} validate={(id) => validateForm(id)} handleChange={(e) => handleChange(e)} />
);
break;
case 'Box':
dialogControls.push(
<EditWidgetColorControl key={1} widget={widget} controlId={'border_color'} label={'Border color'} validate={(id) => validateForm(id)} handleChange={(e) => handleChange(e)} />
<EditWidgetColorControl key={0} widget={widget} controlId={'border_color'} label={'Border color'} validate={(id) => validateForm(id)} handleChange={(e) => handleChange(e)} />
);
break;
case 'Label':
@ -113,6 +124,13 @@ export default function createControls(widgetType = null, widget = null, session
<EditWidgetColorControl key={2} widget={widget} controlId={'fontColor'} label={'Text color'} handleChange={e => handleChange(e)} />
);
break;
case 'HTML':
dialogControls.push(
<EditWidgetHTMLContent key={0} widget={widget} placeholder='HTML Code' controlId='content' handleChange={e => handleChange(e)} />
);
break;
default:
console.log('Non-valid widget type: ' + widgetType);
}

View file

@ -0,0 +1,47 @@
/**
* File: edit-widget-html-content.js
* Author: Ricardo Hernandez-Montoya <rhernandez@gridhound.de>
* Date: 03.09.2017
*
* This file is part of VILLASweb.
*
* VILLASweb is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VILLASweb is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
**********************************************************************************/
import React from 'react';
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
class EditWidgetHTMLContent extends React.Component {
constructor(props) {
super(props);
this.state = {
widget: {}
};
}
componentWillReceiveProps(nextProps) {
// Update state's widget with props
this.setState({ widget: nextProps.widget });
}
render() {
return <FormGroup controlId={this.props.controlId}>
<ControlLabel>HTML Content</ControlLabel>
<FormControl componentClass="textarea" style={{ height: 200 }} placeholder={this.props.placeholder} value={this.state.widget[this.props.controlId] || ''} onChange={e => this.props.handleChange(e)} />
</FormGroup>;
}
}
export default EditWidgetHTMLContent;

View file

@ -19,12 +19,12 @@
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import React, { Component } from 'react';
import { FormGroup, FormControl, ControlLabel, Button } from 'react-bootstrap';
import React from 'react';
import { FormGroup, FormControl, ControlLabel, Button, ProgressBar } from 'react-bootstrap';
import AppDispatcher from '../../app-dispatcher';
class EditImageWidgetControl extends Component {
class EditImageWidgetControl extends React.Component {
constructor(props) {
super(props);
@ -32,7 +32,8 @@ class EditImageWidgetControl extends Component {
widget: {
file: ''
},
fileList: null
fileList: null,
progress: 0
};
}
@ -40,11 +41,11 @@ class EditImageWidgetControl extends Component {
this.setState({ widget: nextProps.widget });
}
startFileUpload() {
startFileUpload = () => {
// get selected file
var formData = new FormData();
let formData = new FormData();
for (var key in this.state.fileList) {
for (let key in this.state.fileList) {
if (this.state.fileList.hasOwnProperty(key) && this.state.fileList[key] instanceof File) {
formData.append(key, this.state.fileList[key]);
}
@ -54,39 +55,46 @@ class EditImageWidgetControl extends Component {
AppDispatcher.dispatch({
type: 'files/start-upload',
data: formData,
token: this.props.sessionToken
token: this.props.sessionToken,
progressCallback: this.uploadProgress,
finishedCallback: this.clearProgress
});
}
uploadProgress = (e) => {
this.setState({ progress: Math.round(e.percent) });
}
clearProgress = () => {
this.setState({ progress: 0 });
}
render() {
return (
<div>
<FormGroup controlId="file">
<ControlLabel>Image</ControlLabel>
<FormControl componentClass="select" value={this.state.widget.file} onChange={(e) => this.props.handleChange(e)}>
{
this.props.files.length === 0? (
<option disabled value style={{ display: 'none' }}>No images found, please upload one first.</option>
) : (
this.props.files.reduce( (entries, file, index) => {
entries.push(<option key={++index} value={file._id}>{file.name}</option>);
return entries;
}, [
<option key={0} value=''>Please select one image</option>
])
)
}
</FormControl>
</FormGroup>
return <div>
<FormGroup controlId="file">
<ControlLabel>Image</ControlLabel>
<FormControl componentClass="select" value={this.state.widget.file} onChange={(e) => this.props.handleChange(e)}>
{this.props.files.length === 0 ? (
<option disabled value style={{ display: 'none' }}>No images found, please upload one first.</option>
) : (
this.props.files.reduce((entries, file, index) => {
entries.push(<option key={++index} value={file._id}>{file.name}</option>);
return entries;
}, [
<option key={0} value=''>Please select one image</option>
])
)}
</FormControl>
</FormGroup>
<FormGroup controlId="upload">
<ControlLabel>Upload</ControlLabel>
<FormControl type="file" onChange={(e) => this.setState({ fileList: e.target.files }) } />
</FormGroup>
<FormGroup controlId="upload">
<ControlLabel>Upload</ControlLabel>
<FormControl type="file" onChange={(e) => this.setState({ fileList: e.target.files }) } />
</FormGroup>
<Button bsSize="small" onClick={() => this.startFileUpload() }>Upload</Button>
</div>
);
<ProgressBar striped active now={this.state.progress} label={`${this.state.progress}%`} />
<Button bsSize="small" onClick={this.startFileUpload}>Upload</Button>
</div>;
}
}

View file

@ -0,0 +1,64 @@
/**
* File: edit-widget-min-max-control.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 30.08.2017
*
* This file is part of VILLASweb.
*
* VILLASweb is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VILLASweb is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import React from 'react';
import { FormGroup, FormControl, ControlLabel, Checkbox, Table } from 'react-bootstrap';
class EditWidgetMinMaxControl extends React.Component {
constructor(props) {
super(props);
const widget = {};
widget[props.controlID + "UseMinMax"] = false;
widget[props.controlId + "Min"] = 0;
widget[props.controlId + "Max"] = 1;
this.state = {
widget
};
}
componentWillReceiveProps(nextProps) {
this.setState({ widget: nextProps.widget });
}
render() {
return <FormGroup>
<ControlLabel>{this.props.label}</ControlLabel>
<Checkbox id={this.props.controlId + "UseMinMax"} checked={this.state.widget[this.props.controlId + "UseMinMax"] || ''} onChange={e => this.props.handleChange(e)}>Enable min-max</Checkbox>
<Table>
<tbody>
<tr>
<td>
Min: <FormControl type="number" id={this.props.controlId + "Min"} disabled={!this.state.widget[this.props.controlId + "UseMinMax"]} placeholder="Minimum value" value={this.state.widget[this.props.controlId + 'Min']} onChange={e => this.props.handleChange(e)} />
</td>
<td>
Max: <FormControl type="number" id={this.props.controlId + "Max"} disabled={!this.state.widget[this.props.controlId + "UseMinMax"]} placeholder="Maximum value" value={this.state.widget[this.props.controlId + 'Max']} onChange={e => this.props.handleChange(e)} />
</td>
</tr>
</tbody>
</Table>
</FormGroup>;
}
}
export default EditWidgetMinMaxControl;

View file

@ -43,7 +43,9 @@ class EditWidgetDialog extends React.Component {
onClose(canceled) {
if (canceled === false) {
this.props.onClose(this.state.temporal);
if (this.valid) {
this.props.onClose(this.state.temporal);
}
} else {
this.props.onClose();
}
@ -51,7 +53,7 @@ class EditWidgetDialog extends React.Component {
assignAspectRatio(changeObject, fileId) {
// get aspect ratio of file
let file = this.props.files.find(element => element._id === fileId);
const file = this.props.files.find(element => element._id === fileId);
// scale width to match aspect
const aspectRatio = file.dimensions.width / file.dimensions.height;
@ -90,6 +92,10 @@ class EditWidgetDialog extends React.Component {
// get file and update size
changeObject = this.assignAspectRatio(changeObject, e.target.value);
} else if (e.target.type === 'checkbox') {
changeObject[e.target.id] = e.target.checked;
} else if (e.target.type === 'number') {
changeObject[e.target.id] = Number(e.target.value);
} else {
changeObject[e.target.id] = e.target.value;
}
@ -134,11 +140,6 @@ class EditWidgetDialog extends React.Component {
return (
<Dialog show={this.props.show} title="Edit Widget" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
<form encType='multipart/form-data'>
{/*<FormGroup controlId="name" validationState={this.validateForm('name')}>
<ControlLabel>Name</ControlLabel>
<FormControl type="text" placeholder="Enter name" value={this.state.temporal.name} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>*/}
{ controls || '' }
</form>
</Dialog>

View file

@ -0,0 +1,125 @@
/**
* File: import-node.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 03.09.2017
*
* This file is part of VILLASweb.
*
* VILLASweb is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VILLASweb is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import React from 'react';
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
import Dialog from './dialog';
class ImportNodeDialog extends React.Component {
valid = false;
imported = false;
constructor(props) {
super(props);
this.state = {
name: '',
endpoint: '',
simulators: []
};
}
onClose(canceled) {
if (canceled === false) {
this.props.onClose(this.state);
} else {
this.props.onClose();
}
}
handleChange(e) {
this.setState({ [e.target.id]: e.target.value });
}
resetState() {
this.setState({ name: '', endpoint: '' });
this.imported = false;
}
loadFile(fileList) {
// get file
const file = fileList[0];
if (!file.type.match('application/json')) {
return;
}
// create file reader
var reader = new FileReader();
var self = this;
reader.onload = function(event) {
// read simulator
const node = JSON.parse(event.target.result);
self.imported = true;
self.setState({ name: node.name, endpoint: node.endpoint, simulators: node.simulators });
};
reader.readAsText(file);
}
validateForm(target) {
// check all controls
let endpoint = true;
let name = true;
if (this.state.name === '' || this.props.nodes.find(node => node.name === this.state.name) !== undefined) {
name = false;
}
if (this.state.endpoint === '' || this.props.nodes.find(node => node.endpoint === this.state.endpoint) !== undefined) {
endpoint = false;
}
this.valid = endpoint && name;
// return state to control
if (target === 'name') return name ? "success" : "error";
else return endpoint ? "success" : "error";
}
render() {
return (
<Dialog show={this.props.show} title="Import Simulator" buttonTitle="Import" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
<form>
<FormGroup controlId="file">
<ControlLabel>Simulator File</ControlLabel>
<FormControl type="file" onChange={(e) => this.loadFile(e.target.files)} />
</FormGroup>
<FormGroup controlId="name" validationState={this.validateForm('name')}>
<ControlLabel>Name</ControlLabel>
<FormControl readOnly={!this.imported} type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
<FormGroup controlId="endpoint" validationState={this.validateForm('endpoint')}>
<ControlLabel>Endpoint</ControlLabel>
<FormControl readOnly={!this.imported} type="text" placeholder="Enter endpoint" value={this.state.endpoint} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
</form>
</Dialog>
);
}
}
export default ImportNodeDialog;

View file

@ -0,0 +1,189 @@
/**
* File: import-simulation-model.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 03.09.2017
*
* This file is part of VILLASweb.
*
* VILLASweb is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VILLASweb is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import React from 'react';
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
import Table from '../table';
import TableColumn from '../table-column';
import Dialog from './dialog';
class ImportSimulationModelDialog extends React.Component {
valid = false;
imported = false;
constructor(props) {
super(props);
this.state = {
name: '',
simulator: { node: '', simulator: '' },
length: '1',
mapping: [ { name: 'Signal', type: 'Type' } ]
};
}
onClose(canceled) {
if (canceled === false) {
this.props.onClose(this.state);
} else {
this.props.onClose();
}
}
resetState() {
this.setState({
name: '',
simulator: { node: this.props.nodes[0] ? this.props.nodes[0]._id : '', simulator: this.props.nodes[0].simulators[0] ? 0 : '' },
length: '1',
mapping: [ { name: 'Signal', type: 'Type' } ]
});
this.imported = false;
}
handleChange(e) {
if (e.target.id === 'length') {
// change mapping size
if (e.target.value > this.state.mapping.length) {
// add missing signals
while (this.state.mapping.length < e.target.value) {
this.state.mapping.push({ name: 'Signal', type: 'Type' });
}
} else {
// remove signals
this.state.mapping.splice(e.target.value, this.state.mapping.length - e.target.value);
}
}
if (e.target.id === 'simulator') {
this.setState({ simulator: JSON.parse(e.target.value) });
} else {
this.setState({ [e.target.id]: e.target.value });
}
}
handleMappingChange(event, row, column) {
var mapping = this.state.mapping;
if (column === 1) {
mapping[row].name = event.target.value;
} else if (column === 2) {
mapping[row].type = event.target.value;
}
this.setState({ mapping: mapping });
}
loadFile(fileList) {
// get file
const file = fileList[0];
if (!file.type.match('application/json')) {
return;
}
// create file reader
var reader = new FileReader();
var self = this;
reader.onload = function(event) {
// read simulator
const model = JSON.parse(event.target.result);
self.imported = true;
self.valid = true;
self.setState({ name: model.name, mapping: model.mapping, length: model.length, simulator: { node: self.props.nodes[0]._id, simulator: 0 } });
};
reader.readAsText(file);
}
validateForm(target) {
// check all controls
var name = true;
var length = true;
var simulator = true;
if (this.state.name === '') {
name = false;
}
if (this.state.simulator === '') {
simulator = false;
}
// test if simulatorid is a number (in a string, not type of number)
if (!/^\d+$/.test(this.state.length)) {
length = false;
}
this.valid = name && length && simulator;
// return state to control
if (target === 'name') return name ? "success" : "error";
else if (target === 'length') return length ? "success" : "error";
else if (target === 'simulator') return simulator ? "success" : "error";
}
render() {
return (
<Dialog show={this.props.show} title="Import Simulation Model" buttonTitle="Import" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
<form>
<FormGroup controlId="file">
<ControlLabel>Simulation Model File</ControlLabel>
<FormControl type="file" onChange={(e) => this.loadFile(e.target.files)} />
</FormGroup>
<FormGroup controlId="name" validationState={this.validateForm('name')}>
<ControlLabel>Name</ControlLabel>
<FormControl readOnly={!this.imported} type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
<FormGroup controlId="simulator">
<ControlLabel>Simulator</ControlLabel>
<FormControl readOnly={!this.imported} componentClass="select" placeholder="Select simulator" value={JSON.stringify({ node: this.state.simulator.node, simulator: this.state.simulator.simulator})} onChange={(e) => this.handleChange(e)}>
{this.props.nodes.map(node => (
node.simulators.map((simulator, index) => (
<option key={node._id + index} value={JSON.stringify({ node: node.name, simulator: simulator.name })}>{node.name}/{simulator.name}</option>
))
))}
</FormControl>
</FormGroup>
<FormGroup controlId="length" validationState={this.validateForm('length')}>
<ControlLabel>Length</ControlLabel>
<FormControl readOnly={!this.imported} type="number" placeholder="Enter length" min="1" value={this.state.length} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
<FormGroup controlId="mapping">
<ControlLabel>Mapping</ControlLabel>
<Table data={this.state.mapping}>
<TableColumn title='ID' width='60' dataIndex />
<TableColumn title='Name' dataKey='name' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange(event, row, column)} />
<TableColumn title='Type' dataKey='type' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange(event, row, column)} />
</Table>
</FormGroup>
</form>
</Dialog>
);
}
}
export default ImportSimulationModelDialog;

View file

@ -0,0 +1,140 @@
/**
* File: import-simulation.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 03.09.2017
*
* This file is part of VILLASweb.
*
* VILLASweb is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VILLASweb is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import React from 'react';
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
import Dialog from './dialog';
class ImportSimulationDialog extends React.Component {
valid = false;
imported = false;
constructor(props) {
super(props);
this.state = {
name: '',
models: [],
};
}
onClose(canceled) {
if (canceled === false) {
this.props.onClose(this.state);
} else {
this.props.onClose();
}
}
handleChange(e, index) {
if (e.target.id === 'simulator') {
const models = this.state.models;
models[index].simulator = JSON.parse(e.target.value);
this.setState({ models });
} else {
this.setState({ [e.target.id]: e.target.value });
}
}
resetState() {
this.setState({ name: '', models: [] });
this.imported = false;
}
loadFile(fileList) {
// get file
const file = fileList[0];
if (!file.type.match('application/json')) {
return;
}
// create file reader
var reader = new FileReader();
var self = this;
reader.onload = function(event) {
// read simulator
const simulation = JSON.parse(event.target.result);
simulation.models.forEach(model => {
model.simulator = {
node: self.props.nodes[0]._id,
simulator: 0
}
});
self.imported = true;
self.setState({ name: simulation.name, models: simulation.models });
};
reader.readAsText(file);
}
validateForm(target) {
// check all controls
let name = true;
if (this.state.name === '') {
name = false;
}
this.valid = name;
// return state to control
if (target === 'name') return name ? "success" : "error";
}
render() {
return (
<Dialog show={this.props.show} title="Import Simulation" buttonTitle="Import" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
<form>
<FormGroup controlId="file">
<ControlLabel>Simulation File</ControlLabel>
<FormControl type="file" onChange={(e) => this.loadFile(e.target.files)} />
</FormGroup>
<FormGroup controlId="name" validationState={this.validateForm('name')}>
<ControlLabel>Name</ControlLabel>
<FormControl readOnly={!this.imported} type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
{this.state.models.map((model, index) => (
<FormGroup controlId="simulator" key={index}>
<ControlLabel>{model.name} - Simulator</ControlLabel>
<FormControl componentClass="select" placeholder="Select simulator" value={JSON.stringify({ node: model.simulator.node, simulator: model.simulator.simulator})} onChange={(e) => this.handleChange(e, index)}>
{this.props.nodes.map(node => (
node.simulators.map((simulator, index) => (
<option key={node._id + index} value={JSON.stringify({ node: node._id, simulator: index })}>{node.name}/{simulator.name}</option>
))
))}
</FormControl>
</FormGroup>
))}
</form>
</Dialog>
);
}
}
export default ImportSimulationDialog;

View file

@ -0,0 +1,133 @@
/**
* File: import-simulator.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 04.04.2017
*
* This file is part of VILLASweb.
*
* VILLASweb is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VILLASweb is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import React from 'react';
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
import Dialog from './dialog';
class ImportVisualizationDialog extends React.Component {
valid = false;
imported = false;
constructor(props) {
super(props);
this.state = {
name: '',
widgets: [],
grid: 0
};
}
onClose(canceled) {
if (canceled === false) {
this.props.onClose(this.state);
} else {
this.props.onClose();
}
}
handleChange(e, index) {
this.setState({ [e.target.id]: e.target.value });
}
resetState() {
this.setState({ name: '', widgets: [], grid: 0 });
this.imported = false;
}
loadFile(fileList) {
// get file
const file = fileList[0];
if (!file.type.match('application/json')) {
return;
}
// create file reader
var reader = new FileReader();
var self = this;
reader.onload = function(event) {
// read simulator
const visualization = JSON.parse(event.target.result);
let defaultSimulator = "";
if (self.props.simulation.models != null) {
defaultSimulator = self.props.simulation.models[0].simulator;
}
visualization.widgets.forEach(widget => {
switch (widget.type) {
case 'Value':
case 'Plot':
case 'Table':
case 'PlotTable':
case 'Gauge':
widget.simulator = defaultSimulator;
break;
}
});
self.imported = true;
self.valid = true;
self.setState({ name: visualization.name, widgets: visualization.widgets, grid: visualization.grid });
};
reader.readAsText(file);
}
validateForm(target) {
// check all controls
let name = true;
if (this.state.name === '') {
name = false;
}
this.valid = name;
// return state to control
if (target === 'name') return name ? "success" : "error";
}
render() {
return (
<Dialog show={this.props.show} title="Import Visualization" buttonTitle="Import" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
<form>
<FormGroup controlId="file">
<ControlLabel>Visualization File</ControlLabel>
<FormControl type="file" onChange={(e) => this.loadFile(e.target.files)} />
</FormGroup>
<FormGroup controlId="name" validationState={this.validateForm('name')}>
<ControlLabel>Name</ControlLabel>
<FormControl readOnly={!this.imported} type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
</form>
</Dialog>
);
}
}
export default ImportVisualizationDialog;

View file

@ -25,7 +25,7 @@ import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
import Dialog from './dialog';
class NewNodeDialog extends React.Component {
valid: false;
valid = false;
constructor(props) {
super(props);
@ -40,7 +40,9 @@ class NewNodeDialog extends React.Component {
onClose(canceled) {
if (canceled === false) {
this.props.onClose(this.state);
if (this.valid) {
this.props.onClose(this.state);
}
} else {
this.props.onClose();
}

View file

@ -38,7 +38,9 @@ class NewProjectDialog extends React.Component {
onClose(canceled) {
if (canceled === false) {
this.props.onClose(this.state);
if (this.valid) {
this.props.onClose(this.state);
}
} else {
this.props.onClose();
}

View file

@ -27,7 +27,7 @@ import TableColumn from '../table-column';
import Dialog from './dialog';
class NewSimulationModelDialog extends React.Component {
valid: false;
valid = false;
constructor(props) {
super(props);
@ -42,7 +42,9 @@ class NewSimulationModelDialog extends React.Component {
onClose(canceled) {
if (canceled === false) {
this.props.onClose(this.state);
if (this.valid) {
this.props.onClose(this.state);
}
} else {
this.props.onClose();
}

View file

@ -37,7 +37,9 @@ class NewSimulationDialog extends React.Component {
onClose(canceled) {
if (canceled === false) {
this.props.onClose(this.state);
if (this.valid) {
this.props.onClose(this.state);
}
} else {
this.props.onClose();
}

View file

@ -37,7 +37,9 @@ class NewSimulatorDialog extends React.Component {
onClose(canceled) {
if (canceled === false) {
this.props.onClose(this.state);
if (this.valid) {
this.props.onClose(this.state);
}
} else {
this.props.onClose();
}

View file

@ -40,7 +40,9 @@ class NewUserDialog extends React.Component {
onClose(canceled) {
if (canceled === false) {
this.props.onClose(this.state);
if (this.valid) {
this.props.onClose(this.state);
}
} else {
this.props.onClose();
}

View file

@ -37,7 +37,9 @@ class NewVisualzationDialog extends React.Component {
onClose(canceled) {
if (canceled === false) {
this.props.onClose(this.state);
if (this.valid) {
this.props.onClose(this.state);
}
} else {
this.props.onClose();
}

View file

@ -25,7 +25,7 @@ class Footer extends Component {
render() {
return (
<footer className="app-footer">
Copyright &copy; {new Date().getFullYear()}
Copyright &copy; {new Date().getFullYear()} - <a href="https://acs.eonerc.rwth-aachen.de">Institute for Automation of Complex Power Systems</a> - <a href="https://www.rwth-aachen.de">RWTH Aachen University</a>
</footer>
);
}

View file

@ -34,7 +34,7 @@ class SidebarMenu extends React.Component {
<li><NavLink to="/simulations" activeClassName="active" title="Simulations">Simulations</NavLink></li>
<li><NavLink to="/simulators" activeClassName="active" title="Simulators">Simulators</NavLink></li>
{ this.props.currentRole === 'admin' ?
<li><NavLink to="/users" activeClassName="active" title="User Management">User Management</NavLink></li> : ''
<li><NavLink to="/users" activeClassName="active" title="User Management">Users</NavLink></li> : ''
}
<li><NavLink to="/logout" title="Logout">Logout</NavLink></li>
</ul>

View file

@ -47,6 +47,7 @@ class NodeTree extends React.Component {
buttons.push(<Button bsSize="small" onClick={() => this.props.onNodeAdd(rowInfo.node)}><Glyphicon glyph="plus" /></Button>);
buttons.push(<Button bsSize="small" onClick={() => this.props.onNodeEdit(rowInfo.node)}><Glyphicon glyph="pencil" /></Button>);
buttons.push(<Button bsSize="small" onClick={() => this.props.onNodeDelete(rowInfo.node)}><Glyphicon glyph="trash" /></Button>);
buttons.push(<Button bsSize="small" onClick={() => this.props.onNodeExport(rowInfo.node)}><Glyphicon glyph="export" /></Button>);
} else {
// get child index
var index = rowInfo.path[1] - rowInfo.path[0] - 1;

View file

@ -28,12 +28,15 @@ class TableColumn extends Component {
width: null,
editButton: false,
deleteButton: false,
exportButton: false,
link: '/',
linkKey: '',
dataIndex: false,
inlineEditable: false,
clickable: false,
labelKey: null
labelKey: null,
checkbox: false,
checkboxKey: ''
};
render() {

View file

@ -20,7 +20,7 @@
******************************************************************************/
import React, { Component } from 'react';
import { Table, Button, Glyphicon, FormControl, Label } from 'react-bootstrap';
import { Table, Button, Glyphicon, FormControl, Label, Checkbox } from 'react-bootstrap';
import { Link } from 'react-router-dom';
//import TableColumn from './table-column';
@ -96,6 +96,16 @@ class CustomTable extends Component {
cell.push(<Button bsClass='table-control-button' onClick={() => child.props.onDelete(index)}><Glyphicon glyph='remove' /></Button>);
}
if (child.props.checkbox) {
const checkboxKey = this.props.checkboxKey;
cell.push(<Checkbox className="table-control-checkbox" inline checked={checkboxKey ? data[checkboxKey] : null} onChange={e => child.props.onChecked(index, e)}></Checkbox>);
}
if (child.props.exportButton) {
cell.push(<Button bsClass='table-control-button' onClick={() => child.props.onExport(index)}><Glyphicon glyph='export' /></Button>);
}
return cell;
}

View file

@ -36,6 +36,7 @@ class WidgetFactory {
widget.height = 30;
widget.textSize = 16;
widget.name = 'Value';
widget.showUnit = false;
break;
case 'Plot':
widget.simulator = defaultSimulator;
@ -46,6 +47,9 @@ class WidgetFactory {
widget.minHeight = 200;
widget.width = 400;
widget.height = 200;
widget.yMin = 0;
widget.yMax = 10;
widget.yUseMinMax = false;
break;
case 'Table':
widget.simulator = defaultSimulator;
@ -67,11 +71,14 @@ class WidgetFactory {
widget.preselectedSignals = [];
widget.signals = []; // initialize selected signals
widget.ylabel = '';
widget.minWidth = 400;
widget.minHeight = 300;
widget.width = 500;
widget.height = 500;
widget.minWidth = 200;
widget.minHeight = 100;
widget.width = 600;
widget.height = 300;
widget.time = 60;
widget.yMin = 0;
widget.yMax = 10;
widget.yUseMinMax = false;
break;
case 'Image':
widget.minWidth = 20;
@ -104,10 +111,15 @@ class WidgetFactory {
case 'Gauge':
widget.simulator = defaultSimulator;
widget.signal = 0;
widget.minWidth = 200;
widget.minWidth = 100;
widget.minHeight = 150;
widget.width = 200;
widget.width = 150;
widget.height = 150;
widget.colorZones = false;
widget.zones = [];
widget.valueMin = 0;
widget.valueMax = 1;
widget.valueUseMinMax = false;
break;
case 'Box':
widget.minWidth = 50;
@ -117,6 +129,10 @@ class WidgetFactory {
widget.border_color = 0;
widget.z = 0;
break;
case 'HTML':
widget.content = '<i>Hello World</i>';
break;
default:
widget.width = 100;
widget.height = 100;

View file

@ -18,62 +18,31 @@ class WidgetGauge extends Component {
this.gauge = null;
this.state = {
value: 0
value: 0,
minValue: 0,
maxValue: 1
};
}
staticLabels(widget_height) {
let label_font_size = Math.floor(widget_height * 0.055); // font scaling factor, integer for performance
return {
font: label_font_size + 'px "Helvetica Neue"',
labels: [0.0, 0.1, 0.5, 0.9, 1.0],
color: "#000000",
fractionDigits: 1
}
}
computeGaugeOptions(widget_height) {
return {
angle: -0.25,
lineWidth: 0.2,
pointer: {
length: 0.6,
strokeWidth: 0.035
},
radiusScale: 0.9,
colorStart: '#6EA2B0',
colorStop: '#6EA2B0',
strokeColor: '#E0E0E0',
highDpiSupport: true,
staticLabels: this.staticLabels(widget_height)
};
}
componentDidMount() {
const opts = this.computeGaugeOptions(this.props.widget.height);
this.gauge = new Gauge(this.gaugeCanvas).setOptions(opts);
this.gauge.maxValue = 1;
this.gauge.setMinValue(0);
this.gauge = new Gauge(this.gaugeCanvas).setOptions(this.computeGaugeOptions(this.props.widget));
this.gauge.maxValue = this.state.maxValue;
this.gauge.setMinValue(this.state.minValue);
this.gauge.animationSpeed = 30;
this.gauge.set(this.state.value);
}
shouldComponentUpdate(nextProps, nextState) {
// Check if size changed, resize labels if it did (the canvas itself is scaled with css)
if (this.props.widget.height !== nextProps.widget.height) {
this.updateAfterResize(nextProps.widget.height);
}
// signal component update only if the value changed
return this.state.value !== nextState.value;
this.updateLabels(this.state.minValue, this.state.maxValue);
}
componentWillReceiveProps(nextProps) {
// update value
const simulator = nextProps.widget.simulator;
if (nextProps.data == null || nextProps.data[simulator.node][simulator.simulator] == null || nextProps.data[simulator.node][simulator.simulator].values == null) {
if (nextProps.data == null || nextProps.data[simulator.node] == null
|| nextProps.data[simulator.node][simulator.simulator] == null
|| nextProps.data[simulator.node][simulator.simulator].length === 0
|| nextProps.data[simulator.node][simulator.simulator].values.length === 0
|| nextProps.data[simulator.node][simulator.simulator].values[0].length === 0) {
this.setState({ value: 0 });
return;
}
@ -83,36 +52,127 @@ class WidgetGauge extends Component {
// Take just 3 decimal positions
// Note: Favor this method over Number.toFixed(n) in order to avoid a type conversion, since it returns a String
if (signal != null) {
const new_value = Math.round( signal[signal.length - 1].y * 1e3 ) / 1e3;
if (this.state.value !== new_value) {
this.setState({ value: new_value });
const value = Math.round( signal[signal.length - 1].y * 1e3 ) / 1e3;
if (this.state.value !== value && value != null) {
this.setState({ value });
// update min-max if needed
let updateLabels = false;
let minValue = this.state.minValue;
let maxValue = this.state.maxValue;
if (nextProps.widget.valueUseMinMax) {
if (this.state.minValue > nextProps.widget.valueMin) {
minValue = nextProps.widget.valueMin;
this.setState({ minValue });
this.gauge.setMinValue(minValue);
updateLabels = true;
}
if (this.state.maxValue < nextProps.widget.valueMax) {
maxValue = nextProps.widget.valueMax;
this.setState({ maxValue });
this.gauge.maxValue = maxValue;
updateLabels = true;
}
}
if (updateLabels === false) {
// check if min/max changed
if (minValue > this.gauge.minValue) {
minValue = this.gauge.minValue;
updateLabels = true;
this.setState({ minValue });
}
if (maxValue < this.gauge.maxValue) {
maxValue = this.gauge.maxValue;
updateLabels = true;
this.setState({ maxValue });
}
}
if (updateLabels) {
this.updateLabels(minValue, maxValue);
}
// update gauge's value
this.gauge.set(new_value);
this.gauge.set(value);
}
}
}
updateAfterResize(newHeight) {
// Update labels after resize
this.gauge.setOptions({ staticLabels: this.staticLabels(newHeight) });
updateLabels(minValue, maxValue, force) {
// calculate labels
const labels = [];
const labelCount = 5;
const labelStep = (maxValue - minValue) / (labelCount - 1);
for (let i = 0; i < labelCount; i++) {
labels.push(minValue + labelStep * i);
}
// calculate zones
let zones = this.props.widget.colorZones ? this.props.widget.zones : null;
if (zones != null) {
// adapt range 0-100 to actual min-max
const step = (maxValue - minValue) / 100;
zones = zones.map(zone => {
return Object.assign({}, zone, { min: (zone.min * step) + +minValue, max: zone.max * step + +minValue, strokeStyle: '#' + zone.strokeStyle });
});
}
this.gauge.setOptions({
staticLabels: {
font: '10px "Helvetica Neue"',
labels,
color: "#000000",
fractionDigits: 1
},
staticZones: zones
});
}
computeGaugeOptions(widget) {
return {
angle: -0.25,
lineWidth: 0.2,
pointer: {
length: 0.6,
strokeWidth: 0.035
},
radiusScale: 0.8,
colorStart: '#6EA2B0',
colorStop: '#6EA2B0',
strokeColor: '#E0E0E0',
highDpiSupport: true,
limitMax: false,
limitMin: false
};
}
render() {
var componentClass = this.props.editing ? "gauge-widget editing" : "gauge-widget";
var signalType = null;
const componentClass = this.props.editing ? "gauge-widget editing" : "gauge-widget";
let signalType = null;
if (this.props.simulation) {
var simulationModel = this.props.simulation.models.filter((model) => model.simulator.node === this.props.widget.simulator.node && model.simulator.simulator === this.props.widget.simulator.simulator)[0];
const simulationModel = this.props.simulation.models.filter((model) => model.simulator.node === this.props.widget.simulator.node && model.simulator.simulator === this.props.widget.simulator.simulator)[0];
signalType = (simulationModel != null && simulationModel.length > 0) ? simulationModel.mapping[this.props.widget.signal].type : '';
}
return (
<div className={ componentClass }>
<div className="gauge-name">{ this.props.widget.name }</div>
<canvas ref={ (node) => this.gaugeCanvas = node } />
<div className="gauge-unit">{ signalType }</div>
<div className="gauge-value">{ this.state.value }</div>
<div className={componentClass}>
<div className="gauge-name">{this.props.widget.name}</div>
<canvas ref={node => this.gaugeCanvas = node} />
<div className="gauge-unit">{signalType}</div>
<div className="gauge-value">{this.state.value}</div>
</div>
);
}

View file

@ -1,7 +1,7 @@
/**
* File: villas-store.js
* File: widget-html.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 02.03.2017
* Date: 29.08.2017
*
* This file is part of VILLASweb.
*
@ -19,25 +19,12 @@
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import { ReduceStore } from 'flux/utils';
import React from 'react';
import AppDispatcher from '../app-dispatcher';
class VillasStore extends ReduceStore {
constructor() {
super(AppDispatcher);
}
getInitialState() {
return {};
}
reduce(state, action) {
switch (action.type) {
default:
return state;
}
class WidgetHTML extends React.Component {
render() {
return <div dangerouslySetInnerHTML={{__html: this.props.widget.content }} />
}
}
export default new VillasStore();
export default WidgetHTML;

View file

@ -38,13 +38,15 @@ class WidgetImage extends React.Component {
}
render() {
let file = this.props.files.find(file => file._id === this.props.widget.file);
const file = this.props.files.find(file => file._id === this.props.widget.file);
return (
<div className="full">
{file &&
{file ? (
<img className="full" alt={file.name} src={'/' + config.publicPathBase + file.path} onDragStart={e => e.preventDefault()} />
}
) : (
<img className="full" alt="questionmark" src={'/' + config.publicPathBase + 'missing-image.png'} onDragStart={e => e.preventDefault()} />
)}
</div>
);
}

View file

@ -37,7 +37,6 @@ class WidgetPlotTable extends Component {
}
componentWillReceiveProps(nextProps) {
// Update internal selected signals state with props (different array objects)
if (this.props.widget.signals !== nextProps.widget.signals) {
this.setState( {signals: nextProps.widget.signals});
@ -158,6 +157,9 @@ class WidgetPlotTable extends Component {
time={this.props.widget.time}
width={this.props.widget.width - 100}
height={this.props.widget.height - 55}
yMin={this.props.widget.yMin}
yMax={this.props.widget.yMax}
yUseMinMax={this.props.widget.yUseMinMax}
/>
</div>
</div>

View file

@ -25,45 +25,59 @@ import Plot from './widget-plot/plot';
import PlotLegend from './widget-plot/plot-legend';
class WidgetPlot extends React.Component {
render() {
const simulator = this.props.widget.simulator;
const simulation = this.props.simulation;
let legendSignals = [];
let simulatorData = [];
constructor(props) {
super(props);
this.state = {
data: [],
legend: []
};
}
componentWillReceiveProps(nextProps) {
const simulator = nextProps.widget.simulator;
const simulation = nextProps.simulation;
// Proceed if a simulation with models and a simulator are available
if (simulator && this.props.data[simulator.node] != null && this.props.data[simulator.node][simulator.simulator] != null && simulation && simulation.models.length > 0) {
if (simulator && nextProps.data[simulator.node] != null && nextProps.data[simulator.node][simulator.simulator] != null && simulation && simulation.models.length > 0) {
const model = simulation.models.find(model => model.simulator.node === simulator.node && model.simulator.simulator === simulator.simulator);
const chosenSignals = this.props.widget.signals;
const chosenSignals = nextProps.widget.signals;
simulatorData = this.props.data[simulator.node][simulator.simulator].values.filter((values, index) => (
this.props.widget.signals.findIndex(value => value === index) !== -1
const data = nextProps.data[simulator.node][simulator.simulator].values.filter((values, index) => (
nextProps.widget.signals.findIndex(value => value === index) !== -1
));
// Query the signals that will be displayed in the legend
legendSignals = model.mapping.reduce( (accum, model_signal, signal_index) => {
const legend = model.mapping.reduce( (accum, model_signal, signal_index) => {
if (chosenSignals.includes(signal_index)) {
accum.push({ index: signal_index, name: model_signal.name });
}
return accum;
}, []);
this.setState({ data, legend });
} else {
this.setState({ data: [], legend: [] });
}
}
return (
<div className="plot-widget" ref="wrapper">
<div className="widget-plot">
<Plot
data={simulatorData}
height={this.props.widget.height - 55}
width={this.props.widget.width - 20}
time={this.props.widget.time}
/>
</div>
<PlotLegend signals={legendSignals} />
render() {
return <div className="plot-widget" ref="wrapper">
<div className="widget-plot">
<Plot
data={this.state.data}
height={this.props.widget.height - 55}
width={this.props.widget.width - 20}
time={this.props.widget.time}
yMin={this.props.widget.yMin}
yMax={this.props.widget.yMax}
yUseMinMax={this.props.widget.yUseMinMax}
/>
</div>
);
<PlotLegend signals={this.state.legend} />
</div>;
}
}

View file

@ -7,25 +7,21 @@
* Unauthorized copying of this file, via any medium is strictly prohibited.
**********************************************************************************/
import React, { Component } from 'react';
import React from 'react';
import { scaleOrdinal, schemeCategory10 } from 'd3-scale';
class PlotLegend extends Component {
// constructor(props) {
// super(props);
// }
class PlotLegend extends React.Component {
render() {
var colorScale = scaleOrdinal(schemeCategory10);
const colorScale = scaleOrdinal(schemeCategory10);
return (
<div className="plot-legend">
{ this.props.signals.map( (signal) =>
<div key={signal.index} className="signal-legend"><span className="legend-color" style={{ background: colorScale(signal.index) }}>&nbsp;&nbsp;</span> {signal.name} </div>)
}
</div>
);
return <div className="plot-legend">
{this.props.signals.map(signal =>
<div key={signal.index} className="signal-legend">
<span className="legend-color" style={{ background: colorScale(signal.index) }}>&nbsp;&nbsp;</span>{signal.name}
</div>
)}
</div>;
}
}
export default PlotLegend;
export default PlotLegend;

View file

@ -22,22 +22,38 @@ class Plot extends React.Component {
constructor(props) {
super(props);
// create dummy axes
const xScale = scaleTime().domain([Date.now(), Date.now() + 5 * 1000]).range([leftMargin, props.width]);
const yScale = scaleLinear().domain([0, 10]).range([props.height, bottomMargin]);
const xAxis = axisBottom().scale(xScale).ticks(5).tickFormat(date => timeFormat("%M:%S")(date));
const yAxis = axisLeft().scale(yScale).ticks(5);
this.state = {
data: null
data: null,
xAxis,
yAxis
};
}
componentWillReceiveProps(nextProps) {
// check if data is valid
if (nextProps.data == null || nextProps.data.length === 0 || nextProps.data[0].length === 0) {
this.setState({ data: null });
// create empty plot axes
const xScale = scaleTime().domain([Date.now(), Date.now() + 5 * 1000]).range([leftMargin, nextProps.width]);
const yScale = scaleLinear().domain([0, 10]).range([nextProps.height, bottomMargin]);
const xAxis = axisBottom().scale(xScale).ticks(5).tickFormat(date => timeFormat("%M:%S")(date));
const yAxis = axisLeft().scale(yScale).ticks(5);
this.setState({ data: null, xAxis, yAxis });
return;
}
// only show data in requested time
let data = nextProps.data;
const firstTimestamp = data[0][data[0].length - 1].x - this.props.time * 1000;
const firstTimestamp = data[0][data[0].length - 1].x - nextProps.time * 1000;
if (data[0][0].x < firstTimestamp) {
// only show data in range (+100 ms)
const index = data[0].findIndex(value => value.x >= firstTimestamp - 100);
@ -50,20 +66,26 @@ class Plot extends React.Component {
xRange[0] = xRange[1] - nextProps.time * 1000;
}
let yRange = [0, 0];
let yRange;
data.map(values => {
const range = extent(values, p => p.y);
if (range[0] < yRange[0]) yRange[0] = range[0];
if (range[1] > yRange[1]) yRange[1] = range[1];
if (nextProps.yUseMinMax) {
yRange = [nextProps.yMin, nextProps.yMax];
} else {
yRange = [0, 0];
return values;
});
data.map(values => {
const range = extent(values, p => p.y);
if (range[0] < yRange[0]) yRange[0] = range[0];
if (range[1] > yRange[1]) yRange[1] = range[1];
return values;
});
}
// create scale functions for both axes
const xScale = scaleTime().domain(xRange).range([leftMargin, nextProps.width]);
const yScale = scaleLinear().domain(yRange).range([nextProps.height, bottomMargin]);
const xAxis = axisBottom().scale(xScale).ticks(5).tickFormat(date => timeFormat("%M:%S")(date));
const yAxis = axisLeft().scale(yScale).ticks(5);
@ -77,18 +99,14 @@ class Plot extends React.Component {
}
render() {
if (this.state.data == null) return false;
return <svg width={this.props.width + leftMargin} height={this.props.height + bottomMargin}>
<g ref={node => select(node).call(this.state.xAxis)} style={{ transform: `translateY(${this.props.height}px)` }} />
<g ref={node => select(node).call(this.state.yAxis)} style={{ transform: `translateX(${leftMargin}px)`}} />
return(
<svg width={this.props.width + leftMargin} height={this.props.height + bottomMargin}>
<g ref={node => select(node).call(this.state.xAxis)} style={{ transform: `translateY(${this.props.height}px)` }} />
<g ref={node => select(node).call(this.state.yAxis)} style={{ transform: `translateX(${leftMargin}px)`}} />
<g>
{this.state.data}
</g>
</svg>
);
<g>
{this.state.data}
</g>
</svg>;
}
}

View file

@ -26,7 +26,8 @@ class WidgetValue extends Component {
super(props);
this.state = {
value: ''
value: '',
unit: ''
};
}
@ -35,17 +36,23 @@ class WidgetValue extends Component {
const simulator = nextProps.widget.simulator.simulator;
const node = nextProps.widget.simulator.node;
//console.log(nextProps.widget.simulator);
if (nextProps.data == null || nextProps.data[node] == null || nextProps.data[node][simulator] == null || nextProps.data[node][simulator].values == null) {
this.setState({ value: '' });
return;
}
// get unit from simulation model
let unit = '';
if (nextProps.simulation) {
const simulationModel = nextProps.simulation.models.find(model => model.simulator.node === node && model.simulator.simulator === simulator);
unit = simulationModel.mapping[nextProps.widget.signal].type;
}
// check if value has changed
const signal = nextProps.data[node][simulator].values[nextProps.widget.signal];
if (signal != null && this.state.value !== signal[signal.length - 1].y) {
this.setState({ value: signal[signal.length - 1].y });
this.setState({ value: signal[signal.length - 1].y, unit });
}
}
@ -53,7 +60,11 @@ class WidgetValue extends Component {
var value_to_render = Number(this.state.value);
return (
<div className="single-value-widget">
<strong style={{ fontSize: this.props.widget.textSize + 'px' }}>{this.props.widget.name}</strong> <span style={{ fontSize: this.props.widget.textSize + 'px' }}>{ Number.isNaN(value_to_render)? NaN : value_to_render.toFixed(3) } </span>
<strong style={{ fontSize: this.props.widget.textSize + 'px' }}>{this.props.widget.name}</strong>
<span style={{ fontSize: this.props.widget.textSize + 'px' }}>{Number.isNaN(value_to_render) ? NaN : value_to_render.toFixed(3)}</span>
{this.props.widget.showUnit &&
<span style={{ fontSize: this.props.widget.textSize + 'px' }}>[{this.state.unit}]</span>
}
</div>
);
}

View file

@ -1,6 +1,11 @@
const config = {
publicPathBase: 'public/'
publicPathBase: 'public/',
instance: 'frontend of the Global RT-SuperLab Demonstration',
admin: {
name: 'Steffen Vogel',
mail: 'stvogel@eonerc.rwth-aachen.de'
}
}
export default config
export default config

View file

@ -22,23 +22,74 @@
import React, { Component } from 'react';
import { Container } from 'flux/utils';
// import AppDispatcher from '../app-dispatcher';
import VillasStore from '../stores/villas-store';
import AppDispatcher from '../app-dispatcher';
import NodeStore from '../stores/node-store';
import SimulationStore from '../stores/simulation-store';
import ProjectStore from '../stores/project-store';
import config from '../config';
class Home extends Component {
static getStores() {
return [ VillasStore ];
return [ NodeStore, SimulationStore, ProjectStore ];
}
static calculateState() {
return {
villas: VillasStore.getState()
nodes: NodeStore.getState(),
projects: ProjectStore.getState(),
simulations: SimulationStore.getState(),
};
}
componentWillMount() {
AppDispatcher.dispatch({
type: 'projects/start-load',
token: this.state.sessionToken
});
AppDispatcher.dispatch({
type: 'simulations/start-load',
token: this.state.sessionToken
});
}
render() {
return (
<h1>Home</h1>
<div className="home-container">
<img style={{height: 120, float: 'right'}} src={require('../img/villas_web.svg')} alt="Logo VILLASweb" />
<h1>Home</h1>
<p>
Welcome to <b>{config.instance}</b>!<br />
VILLASweb is a frontend for distributed real-time simulation hosted by <a href={"mailto:" + config.admin.mail}>{config.admin.name}</a>.
</p>
<p>
This instance is hosting {this.state.projects.length} projects consisting of {this.state.nodes.length} nodes and {this.state.simulations.length} simulations.<br />
</p>
<h3>Credits</h3>
<p>VILLASweb is developed by the <a href="http://acs.eonerc.rwth-aachen.de">Institute for Automation of Complex Power Systems</a> at the <a href="https;//www.rwth-aachen.de">RWTH Aachen University</a>.</p>
<ul>
<li><a href="mailto:mgrigull@eonerc.rwth-aachen.de">Markus Grigull</a></li>
<li><a href="mailto:stvogel@eonerc.rwth-aachen.de">Steffen Vogel</a></li>
<li><a href="mailto:mstevic@eonerc.rwth-aachen.de">Marija Stevic</a></li>
</ul>
<h3>Links</h3>
<ul>
<li><a href="http://fein-aachen.org/projects/villas-framework/">Project Page</a></li>
<li><a href="https://villas.fein-aachen.org/doc/web.html">Documentation</a></li>
<li><a href="https://git.rwth-aachen.de/VILLASframework/VILLASweb">Source Code</a></li>
</ul>
<h3>Funding</h3>
<p>The development of <a href="http://fein-aachen.org/projects/villas-framework/">VILLASframework</a> projects have received funding from</p>
<ul>
<li><a href="http://www.re-serve.eu">RESERVE</a> a European Unions Horizon 2020 research and innovation programme under grant agreement No 727481</li>
<li><a href="http://www.jara.org/en/research/energy">JARA-ENERGY</a>. Jülich-Aachen Research Alliance (JARA) is an initiative of RWTH Aachen University and Forschungszentrum Jülich.</li>
</ul>
<img height={60} src={require('../img/eonerc_rwth.svg')} alt="Logo ACS" />
<img height={70} src={require('../img/jara.svg')} alt="Logo JARA" />
<img height={100} src={require('../img/european_commission.svg')} alt="Logo EU" />
</div>
);
}
}

View file

@ -22,98 +22,80 @@
import React, { Component } from 'react';
import { Container } from 'flux/utils';
import { Button, Modal, Glyphicon } from 'react-bootstrap';
import FileSaver from 'file-saver';
import AppDispatcher from '../app-dispatcher';
import ProjectStore from '../stores/project-store';
import UserStore from '../stores/user-store';
import VisualizationStore from '../stores/visualization-store';
import SimulationStore from '../stores/simulation-store';
import CustomTable from '../components/table';
import TableColumn from '../components/table-column';
import NewVisualzationDialog from '../components/dialog/new-visualization';
import EditVisualizationDialog from '../components/dialog/edit-visualization';
import ImportVisualizationDialog from '../components/dialog/import-visualization';
class Visualizations extends Component {
static getStores() {
return [ ProjectStore, VisualizationStore, UserStore ];
return [ ProjectStore, VisualizationStore, UserStore, SimulationStore ];
}
static calculateState(prevState, props) {
prevState = prevState || {};
let currentProjects = ProjectStore.getState();
let currentVisualizations = VisualizationStore.getState();
let sessionToken = UserStore.getState().token;
// load project
const sessionToken = UserStore.getState().token;
if (prevState) {
var projectUpdate = prevState.project;
let project = ProjectStore.getState().find(project => project._id === props.match.params.project);
if (project == null) {
AppDispatcher.dispatch({
type: 'projects/start-load',
data: props.match.params.project,
token: sessionToken
});
// Compare content of the visualizations array, reload projects if changed
if (JSON.stringify(prevState.visualizations) !== JSON.stringify(currentVisualizations)) {
Visualizations.loadProjects(sessionToken);
}
// Compare content of the projects array, update visualizations if changed
if (JSON.stringify(prevState.projects) !== JSON.stringify(currentProjects)) {
projectUpdate = Visualizations.findProjectInState(currentProjects, props.match.params.project);
Visualizations.loadVisualizations(projectUpdate.visualizations, sessionToken);
}
return {
projects: currentProjects,
visualizations: currentVisualizations,
sessionToken,
newModal: prevState.newModal,
deleteModal: prevState.deleteModal,
editModal: prevState.editModal,
modalData: prevState.modalData,
project: projectUpdate
};
} else {
let initialProject = Visualizations.findProjectInState(currentProjects, props.match.params.project);
// If projects have been loaded already but visualizations not (redirect from Projects page)
if (initialProject && (!currentVisualizations || currentVisualizations.length === 0)) {
Visualizations.loadVisualizations(initialProject.visualizations, sessionToken);
}
return {
projects: currentProjects,
visualizations: currentVisualizations,
sessionToken,
newModal: false,
deleteModal: false,
editModal: false,
modalData: {},
project: initialProject || {}
};
project = {};
}
// load simulation
let simulation = {};
if (project.simulation != null) {
simulation = SimulationStore.getState().find(simulation => simulation._id === project.simulation);
}
// load visualizations
let visualizations = [];
if (project.visualizations != null) {
visualizations = VisualizationStore.getState().filter(visualization => project.visualizations.includes(visualization._id));
}
return {
visualizations,
project,
simulation,
sessionToken,
newModal: prevState.newModal || false,
deleteModal: prevState.deleteModal || false,
editModal: prevState.editModal || false,
importModal: prevState.importModal || false,
modalData: prevState.modalData || {}
};
}
static findProjectInState(projects, projectId) {
return projects.find((project) => project._id === projectId);
}
static loadProjects(token) {
AppDispatcher.dispatch({
type: 'projects/start-load',
token
});
}
static loadVisualizations(visualizations, token) {
componentDidMount() {
AppDispatcher.dispatch({
type: 'visualizations/start-load',
data: visualizations,
token
token: this.state.sessionToken
});
}
componentWillMount() {
Visualizations.loadProjects(this.state.sessionToken);
AppDispatcher.dispatch({
type: 'simulations/start-load',
token: this.state.sessionToken
});
}
closeNewModal(data) {
@ -153,33 +135,78 @@ class Visualizations extends Component {
}
}
render() {
// get visualizations for this project
var visualizations = [];
if (this.state.visualizations && this.state.project.visualizations) {
visualizations = this.state.visualizations.filter(
(visualization) => this.state.project.visualizations.includes(visualization._id)
).sort(
(visA, visB) => visA.name.localeCompare(visB.name)
);
}
closeImportModal(data) {
this.setState({ importModal: false });
if (data) {
data.project = this.state.project._id;
AppDispatcher.dispatch({
type: 'visualizations/start-add',
data,
token: this.state.sessionToken
});
this.setState({ project: {} }, () => {
AppDispatcher.dispatch({
type: 'projects/start-load',
data: this.props.match.params.project,
token: this.state.sessionToken
});
});
}
}
exportVisualization(index) {
// filter properties
let visualization = Object.assign({}, this.state.visualizations[index]);
delete visualization._id;
delete visualization.project;
delete visualization.user;
visualization.widgets.forEach(widget => {
delete widget.simulator;
});
// show save dialog
const blob = new Blob([JSON.stringify(visualization, null, 2)], { type: 'application/json' });
FileSaver.saveAs(blob, 'visualization - ' + visualization.name + '.json');
}
onModalKeyPress = (event) => {
if (event.key === 'Enter') {
event.preventDefault();
this.confirmDeleteModal();
}
}
render() {
return (
<div className='section'>
<h1>{this.state.project.name}</h1>
<CustomTable data={visualizations}>
<CustomTable data={this.state.visualizations}>
<TableColumn title='Name' dataKey='name' link='/visualizations/' linkKey='_id' />
<TableColumn width='70' editButton deleteButton onEdit={(index) => this.setState({ editModal: true, modalData: visualizations[index] })} onDelete={(index) => this.setState({ deleteModal: true, modalData: visualizations[index] })} />
<TableColumn
width='100'
editButton
deleteButton
exportButton
onEdit={(index) => this.setState({ editModal: true, modalData: this.state.visualizations[index] })}
onDelete={(index) => this.setState({ deleteModal: true, modalData: this.state.visualizations[index] })}
onExport={index => this.exportVisualization(index)}
/>
</CustomTable>
<Button onClick={() => this.setState({ newModal: true })}><Glyphicon glyph="plus" /> Visualization</Button>
<Button onClick={() => this.setState({ importModal: true })}><Glyphicon glyph="import" /> Import</Button>
<NewVisualzationDialog show={this.state.newModal} onClose={(data) => this.closeNewModal(data)} />
<EditVisualizationDialog show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} visualization={this.state.modalData} />
<ImportVisualizationDialog show={this.state.importModal} onClose={data => this.closeImportModal(data)} simulation={this.state.simulation} />
<Modal show={this.state.deleteModal}>
<Modal keyboard show={this.state.deleteModal} onHide={() => this.setState({ deleteModal: false })} onKeyPress={this.onModalKeyPress}>
<Modal.Header>
<Modal.Title>Delete Visualization</Modal.Title>
</Modal.Header>

View file

@ -115,6 +115,14 @@ class Projects extends React.Component {
return simulations.length > 0;
}
onModalKeyPress = (event) => {
if (event.key === 'Enter') {
event.preventDefault();
this.confirmDeleteModal();
}
}
render() {
return (
<div className='section'>
@ -135,7 +143,7 @@ class Projects extends React.Component {
<NewProjectDialog show={this.state.newModal} onClose={(data) => this.closeNewModal(data)} simulations={this.state.simulations} />
<EditProjectDialog show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} project={this.state.modalData} simulations={this.state.simulations} />
<Modal show={this.state.deleteModal}>
<Modal keyboard show={this.state.deleteModal} onHide={() => this.setState({ deleteModal: false })} onKeyPress={this.onModalKeyPress}>
<Modal.Header>
<Modal.Title>Delete Project</Modal.Title>
</Modal.Header>

View file

@ -22,6 +22,7 @@
import React from 'react';
import { Container } from 'flux/utils';
import { Button, Modal, Glyphicon } from 'react-bootstrap';
import FileSaver from 'file-saver';
import SimulationStore from '../stores/simulation-store';
import NodeStore from '../stores/node-store';
@ -32,6 +33,7 @@ import Table from '../components/table';
import TableColumn from '../components/table-column';
import NewSimulationModelDialog from '../components/dialog/new-simulation-model';
import EditSimulationModelDialog from '../components/dialog/edit-simulation-model';
import ImportSimulationModelDialog from '../components/dialog/import-simulation-model';
class Simulation extends React.Component {
static getStores() {
@ -47,6 +49,7 @@ class Simulation extends React.Component {
newModal: false,
deleteModal: false,
editModal: false,
importModal: false,
modalData: {},
modalIndex: null,
@ -126,6 +129,20 @@ class Simulation extends React.Component {
}
}
closeImportModal(data) {
this.setState({ importModal: false });
if (data) {
this.state.simulation.models.push(data);
AppDispatcher.dispatch({
type: 'simulations/start-edit',
data: this.state.simulation,
token: this.state.sessionToken
});
}
}
getSimulatorName(simulator) {
var name = "undefined";
@ -138,6 +155,24 @@ class Simulation extends React.Component {
return name;
}
exportModel(index) {
// filter properties
let simulationModel = Object.assign({}, this.state.simulation.models[index]);
delete simulationModel.simulator;
// show save dialog
const blob = new Blob([JSON.stringify(simulationModel, null, 2)], { type: 'application/json' });
FileSaver.saveAs(blob, 'simulation model - ' + simulationModel.name + '.json');
}
onModalKeyPress = (event) => {
if (event.key === 'Enter') {
event.preventDefault();
this.confirmDeleteModal();
}
}
render() {
return (
<div className='section'>
@ -147,16 +182,26 @@ class Simulation extends React.Component {
<TableColumn title='Name' dataKey='name' />
<TableColumn title='Simulator' dataKey='simulator' width='180' modifier={(simulator) => this.getSimulatorName(simulator)} />
<TableColumn title='Length' dataKey='length' width='100' />
<TableColumn title='' width='70' editButton deleteButton onEdit={(index) => this.setState({ editModal: true, modalData: this.state.simulation.models[index], modalIndex: index })} onDelete={(index) => this.setState({ deleteModal: true, modalData: this.state.simulation.models[index], modalIndex: index })} />
<TableColumn
title=''
width='100'
editButton
deleteButton
exportButton
onEdit={(index) => this.setState({ editModal: true, modalData: this.state.simulation.models[index], modalIndex: index })}
onDelete={(index) => this.setState({ deleteModal: true, modalData: this.state.simulation.models[index], modalIndex: index })}
onExport={index => this.exportModel(index)}
/>
</Table>
<Button onClick={() => this.setState({ newModal: true })}><Glyphicon glyph="plus" /> Simulation Model</Button>
<Button onClick={() => this.setState({ importModal: true })}><Glyphicon glyph="import" /> Import</Button>
<NewSimulationModelDialog show={this.state.newModal} onClose={(data) => this.closeNewModal(data)} nodes={this.state.nodes} />
<EditSimulationModelDialog show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} data={this.state.modalData} nodes={this.state.nodes} />
<ImportSimulationModelDialog show={this.state.importModal} onClose={data => this.closeImportModal(data)} nodes={this.state.nodes} />
<Modal show={this.state.deleteModal}>
<Modal keyboard show={this.state.deleteModal} onHide={() => this.setState({ deleteModal: false })} onKeyPress={this.onModalKeyPress}>
<Modal.Header>
<Modal.Title>Delete Simulation Model</Modal.Title>
</Modal.Header>

View file

@ -22,29 +22,34 @@
import React, { Component } from 'react';
import { Container } from 'flux/utils';
import { Button, Modal, Glyphicon } from 'react-bootstrap';
import FileSaver from 'file-saver';
import AppDispatcher from '../app-dispatcher';
import SimulationStore from '../stores/simulation-store';
import UserStore from '../stores/user-store';
import NodeStore from '../stores/node-store';
import Table from '../components/table';
import TableColumn from '../components/table-column';
import NewSimulationDialog from '../components/dialog/new-simulation';
import EditSimulationDialog from '../components/dialog/edit-simulation';
import ImportSimulationDialog from '../components/dialog/import-simulation';
class Simulations extends Component {
static getStores() {
return [ SimulationStore, UserStore ];
return [ SimulationStore, UserStore, NodeStore ];
}
static calculateState() {
return {
simulations: SimulationStore.getState(),
nodes: NodeStore.getState(),
sessionToken: UserStore.getState().token,
newModal: false,
deleteModal: false,
editModal: false,
importModal: false,
modalSimulation: {}
};
}
@ -116,6 +121,43 @@ class Simulations extends Component {
}
}
closeImportModal(data) {
this.setState({ importModal: false });
if (data) {
AppDispatcher.dispatch({
type: 'simulations/start-add',
data,
token: this.state.sessionToken
});
}
}
onModalKeyPress = (event) => {
if (event.key === 'Enter') {
event.preventDefault();
this.confirmDeleteModal();
}
}
exportSimulation(index) {
// filter properties
let simulation = Object.assign({}, this.state.simulations[index]);
delete simulation._id;
delete simulation.projects;
delete simulation.running;
delete simulation.user;
simulation.models.forEach(model => {
delete model.simulator;
});
// show save dialog
const blob = new Blob([JSON.stringify(simulation, null, 2)], { type: 'application/json' });
FileSaver.saveAs(blob, 'simulation - ' + simulation.name + '.json');
}
render() {
return (
<div className='section'>
@ -123,16 +165,25 @@ class Simulations extends Component {
<Table data={this.state.simulations}>
<TableColumn title='Name' dataKey='name' link='/simulations/' linkKey='_id' />
<TableColumn width='70' editButton deleteButton onEdit={index => this.setState({ editModal: true, modalSimulation: this.state.simulations[index] })} onDelete={index => this.setState({ deleteModal: true, modalSimulation: this.state.simulations[index] })} />
<TableColumn
width='100'
editButton
deleteButton
exportButton
onEdit={index => this.setState({ editModal: true, modalSimulation: this.state.simulations[index] })}
onDelete={index => this.setState({ deleteModal: true, modalSimulation: this.state.simulations[index] })}
onExport={index => this.exportSimulation(index)}
/>
</Table>
<Button onClick={() => this.setState({ newModal: true })}><Glyphicon glyph="plus" /> Simulation</Button>
<Button onClick={() => this.setState({ importModal: true })}><Glyphicon glyph="import" /> Import</Button>
<NewSimulationDialog show={this.state.newModal} onClose={(data) => this.closeNewModal(data)} />
<EditSimulationDialog show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} simulation={this.state.modalSimulation} />
<ImportSimulationDialog show={this.state.importModal} onClose={data => this.closeImportModal(data)} nodes={this.state.nodes} />
<Modal show={this.state.deleteModal}>
<Modal keyboard show={this.state.deleteModal} onHide={() => this.setState({ deleteModal: false })} onKeyPress={this.onModalKeyPress}>
<Modal.Header>
<Modal.Title>Delete Simulation</Modal.Title>
</Modal.Header>

View file

@ -22,6 +22,7 @@
import React, { Component } from 'react';
import { Container } from 'flux/utils';
import { Button, Modal, Glyphicon } from 'react-bootstrap';
import FileSaver from 'file-saver';
import AppDispatcher from '../app-dispatcher';
import NodeStore from '../stores/node-store';
@ -32,6 +33,7 @@ import EditNodeDialog from '../components/dialog/edit-node';
import NewSimulatorDialog from '../components/dialog/new-simulator';
import EditSimulatorDialog from '../components/dialog/edit-simulator';
import NodeTree from '../components/node-tree';
import ImportNodeDialog from '../components/dialog/import-node';
class Simulators extends Component {
static getStores() {
@ -46,6 +48,8 @@ class Simulators extends Component {
newNodeModal: false,
deleteNodeModal: false,
editNodeModal: false,
importModal: false,
exportModal: false,
addSimulatorModal: false,
editSimulatorModal: false,
@ -186,6 +190,41 @@ class Simulators extends Component {
token: this.state.sessionToken
});
}
closeImportNodeModal(data) {
this.setState({ importNodeModal: false });
if (data) {
AppDispatcher.dispatch({
type: 'nodes/start-add',
data,
token: this.state.sessionToken
});
}
}
exportNode(data) {
const node = this.state.nodes.find((element) => {
return element._id === data.id;
});
// filter properties
let simulator = Object.assign({}, node);
delete simulator._id;
simulator.simulators.forEach(simulator => {
delete simulator.id;
});
// show save dialog
const blob = new Blob([JSON.stringify(simulator, null, 2)], { type: 'application/json' });
FileSaver.saveAs(blob, 'node - ' + node.name + '.json');
}
labelStyle(value) {
if (value === true) return 'success';
else return 'warning';
}
onTreeDataChange(nodes) {
// update all at once
@ -198,27 +237,74 @@ class Simulators extends Component {
});
}
onNodeModalKeyPress = (event) => {
if (event.key === 'Enter') {
event.preventDefault();
this.confirmDeleteNodeModal();
}
}
onSimulatorModalKeyPress = (event) => {
if (event.key === 'Enter') {
event.preventDefault();
this.confirmDeleteSimulatorModal();
}
}
loadFile(fileList) {
// get file
const file = fileList[0];
if (!file.type.match('application/json')) {
return;
}
// create file reader
var reader = new FileReader();
var self = this;
reader.onload = function(event) {
// read simulator
const simulator = JSON.parse(event.target.result);
self.setState({ importModal: true, modalSimulator: simulator });
};
reader.readAsText(file);
}
render() {
return (
<div className='section'>
<h1>Simulators</h1>
<Button onClick={() => this.setState({ newNodeModal: true })}><Glyphicon glyph="plus" /> Node</Button>
<Button onClick={() => this.setState({ importNodeModal: true })}><Glyphicon glyph="import" /> Import</Button>
<br />
<small><i>Hint: Node names must be unique. Simulator names must be unique on a node.</i></small>
<NodeTree data={this.state.nodes} onDataChange={(treeData) => this.onTreeDataChange(treeData)} onNodeDelete={(node) => this.showDeleteNodeModal(node)} onNodeEdit={(node) => this.showEditNodeModal(node)} onNodeAdd={(node) => this.showAddSimulatorModal(node)} onSimulatorEdit={(node, index) => this.showEditSimulatorModal(node, index)} onSimulatorDelete={(node, index) => this.showDeleteSimulatorModal(node, index)} />
<NodeTree
data={this.state.nodes}
onDataChange={(treeData) => this.onTreeDataChange(treeData)}
onNodeDelete={(node) => this.showDeleteNodeModal(node)}
onNodeEdit={(node) => this.showEditNodeModal(node)}
onNodeAdd={(node) => this.showAddSimulatorModal(node)}
onNodeExport={node => this.exportNode(node)}
onSimulatorEdit={(node, index) => this.showEditSimulatorModal(node, index)}
onSimulatorDelete={(node, index) => this.showDeleteSimulatorModal(node, index)}
/>
<NewNodeDialog show={this.state.newNodeModal} onClose={(data) => this.closeNewNodeModal(data)} nodes={this.state.nodes} />
<EditNodeDialog node={this.state.modalData} show={this.state.editNodeModal} onClose={(data) => this.closeEditNodeModal(data)} nodes={this.state.nodes} />
<NewSimulatorDialog show={this.state.addSimulatorModal} onClose={(data) => this.closeAddSimulatorModal(data)} node={this.state.modalData}/>
<ImportNodeDialog show={this.state.importNodeModal} onClose={data => this.closeImportNodeModal(data)} nodes={this.state.nodes} />
{this.state.editSimulatorModal &&
<EditSimulatorDialog simulator={this.state.modalData.simulators[this.state.modalIndex]} show={this.state.editSimulatorModal} onClose={(data) => this.closeEditSimulatorModal(data)} node={this.state.modalData} />
}
<Modal show={this.state.deleteNodeModal}>
<Modal keyboard show={this.state.deleteNodeModal} onHide={() => this.setState({ deleteNodeModal: false })} onKeyPress={this.onNodeModalKeyPress}>
<Modal.Header>
<Modal.Title>Delete Node</Modal.Title>
</Modal.Header>
@ -235,7 +321,7 @@ class Simulators extends Component {
</Modal.Footer>
</Modal>
<Modal show={this.state.deleteSimulatorModal}>
<Modal keyboard show={this.state.deleteSimulatorModal} onHide={() => this.setState({ deleteSimulatorModal: false })} onKeyPress={this.onSimulatorModalKeyPress}>
<Modal.Header>
<Modal.Title>Delete Simulator</Modal.Title>
</Modal.Header>

View file

@ -100,6 +100,14 @@ class Users extends Component {
return HUMAN_ROLE_NAMES.hasOwnProperty(role_key)? HUMAN_ROLE_NAMES[role_key] : '';
}
onModalKeyPress = (event) => {
if (event.key === 'Enter') {
event.preventDefault();
this.confirmDeleteModal();
}
}
render() {
return (
@ -119,7 +127,7 @@ class Users extends Component {
<EditUserDialog show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} user={this.state.modalData} />
<Modal show={this.state.deleteModal}>
<Modal keyboard show={this.state.deleteModal} onHide={() => this.setState({ deleteModal: false })} onKeyPress={this.onModalKeyPress}>
<Modal.Header>
<Modal.Title>Delete user</Modal.Title>
</Modal.Header>

View file

@ -467,6 +467,7 @@ class Visualization extends React.Component {
<ToolboxItem name="Slider" type="widget" disabled />
<ToolboxItem name="Gauge" type="widget" />
<ToolboxItem name="Box" type="widget" />
<ToolboxItem name="HTML" type="html" />
</div>
}

View file

@ -41,6 +41,7 @@ import WidgetNumberInput from '../components/widget-number-input';
import WidgetSlider from '../components/widget-slider';
import WidgetGauge from '../components/widget-gauge';
import WidgetBox from '../components/widget-box';
import WidgetHTML from '../components/widget-html';
import '../styles/widgets.css';
@ -173,6 +174,8 @@ class Widget extends Component {
element = <WidgetGauge widget={widget} data={this.state.simulatorData} editing={this.props.editing} simulation={this.props.simulation} />
} else if (widget.type === 'Box') {
element = <WidgetBox widget={widget} editing={this.props.editing} />
} else if (widget.type === 'HTML') {
element = <WidgetHTML widget={widget} editing={this.props.editing} />
}
const widgetClasses = classNames({

View file

@ -28,20 +28,25 @@ class FilesDataManager extends RestDataManager {
super('file', '/files');
}
upload(file, token = null) {
RestAPI.upload(this.makeURL('/upload'), file, token).then(response => {
upload(file, token = null, progressCallback = null, finishedCallback = null) {
RestAPI.upload(this.makeURL('/upload'), file, token, progressCallback).then(response => {
AppDispatcher.dispatch({
type: 'files/uploaded'
});
// Trigger a files reload
AppDispatcher.dispatch({
type: 'files/start-load',
token: token
token
});
if (finishedCallback) {
finishedCallback();
}
}).catch(error => {
AppDispatcher.dispatch({
type: 'files/upload-error',
error: error
error
});
});
}

View file

@ -21,4 +21,4 @@
import RestDataManager from './rest-data-manager';
export default new RestDataManager('simulation', '/simulations');
export default new RestDataManager('simulation', '/simulations', [ '_id', 'name', 'projects', 'models' ]);

152
src/img/eonerc_rwth.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 27 KiB

View file

@ -0,0 +1,272 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="300"
height="207.72354"
id="svg15656">
<defs
id="defs15658" />
<metadata
id="metadata15661">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-206.87716,-301.85899)"
id="layer1">
<g
transform="matrix(4.5286013,0,0,4.5286013,-1067.5668,-2047.9402)"
id="g15525">
<path
d="m 281.42172,530.9313 c 0,0 19.6275,-2.54 20.18125,-2.63 0.85,-0.13875 1.60375,-0.30125 2.3075,-0.495 1.5875,-0.435 3.04875,-1.09875 4.3425,-1.97375 1.24125,-0.83125 2.395,-2.04125 3.53875,-3.36875 0.72625,-0.845 1.49,-1.94625 2.2025,-3.00875 l 0,-0.51375 c -0.845,1.25125 -1.6775,2.33 -2.53375,3.28125 -1.135,1.26375 -2.325,2.29625 -3.53875,3.07125 -1.2525,0.8075 -2.66125,1.40875 -4.19,1.79 -0.685,0.1725 -1.4175,0.3125 -2.2425,0.4275 -0.5425,0.0762 -1.1025,0.135 -1.64375,0.19125 -0.21625,0.0238 -18.42375,1.955 -18.42375,1.955 l 0,1.27375 z"
id="path5555"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 307.74272,527.64055 c -1.22875,0.70625 -2.6475,1.22375 -4.2175,1.53625 -0.67,0.13625 -1.40125,0.24625 -2.23375,0.33625 -0.49,0.0537 -0.9825,0.0962 -1.47625,0.13875 -0.26625,0.0225 -0.5325,0.0462 -0.79875,0.07 -6.015,0.54 -11.99875,1.11375 -17.595,1.65625 l 0,1.27375 c 5.64,-0.68 11.66625,-1.40125 17.67875,-2.08125 0.2675,-0.0312 0.535,-0.0612 0.8025,-0.09 0.4925,-0.0538 0.98625,-0.10875 1.48,-0.17375 0.85625,-0.11375 1.605,-0.245 2.28875,-0.4 1.63125,-0.365 3.10375,-0.94 4.3775,-1.71125 1.265,-0.75875 2.3825,-1.8225 3.585,-3.1225 0.74875,-0.80875 1.58125,-1.95125 2.36125,-3.02375 l 0,-0.4675 c -0.91875,1.245 -1.79,2.29125 -2.65125,3.18375 -1.17375,1.21625 -2.385,2.1825 -3.60125,2.875"
id="path5559"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 311.1776,527.32868 c -1.1975,1.09875 -2.4475,1.9625 -3.71625,2.5675 -1.24125,0.59875 -2.62875,1.01875 -4.245,1.28375 -0.66625,0.11125 -1.39125,0.20125 -2.215,0.27625 l -19.58,1.6075 0,1.27 17.405,-1.84625 2.25125,-0.23625 c 0.84875,-0.0987 1.58875,-0.20875 2.26125,-0.33625 1.67375,-0.31625 3.11,-0.7875 4.38875,-1.4425 1.3075,-0.66125 2.61375,-1.76125 3.82875,-2.92625 0.815,-0.78125 1.6,-1.79 2.43875,-2.8475 l 0,-0.42375 c -0.96875,1.205 -1.89375,2.2075 -2.8175,3.05375"
id="path5563"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 306.4431,533.92568 c -1.14,0.6375 -1.9925,1.005 -3.62,1.175 -0.98375,0.10625 -1.995,0.15625 -2.97375,0.205 -0.4725,0.0237 -0.94625,0.0475 -1.42,0.0775 -2.9575,0.16875 -5.8625,0.35625 -8.82625,0.54875 l -8.18125,0.55125 0,1.27 8.26375,-0.7575 c 2.67,-0.24 5.7725,-0.51625 8.80125,-0.76375 l 2.2025,-0.17 c 0.86,-0.0662 1.565,-0.13625 2.2175,-0.22125 1.675,-0.21625 3.10625,-0.555 4.37375,-1.03375 1.365,-0.51 2.6525,-1.3025 3.94125,-2.235 1.1625,-0.84 2.77125,-2.49625 2.7725,-2.5175 l 0,-0.44875 c -1.05125,1.025 -1.65625,1.60125 -2.8075,2.36 -1.37625,0.90625 -3.72125,1.38875 -4.74375,1.96"
id="path5567"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 306.85935,536.33993 c -1.235,0.3625 -2.625,0.6075 -4.25,0.74875 -0.65875,0.0575 -1.36875,0.10125 -2.17125,0.13375 l -2.19,0.0962 c -5.44125,0.2525 -10.8575,0.535 -16.82625,0.85125 l 0,1.26625 c 5.7025,-0.44125 11.29,-0.87 16.87625,-1.2675 l 2.1825,-0.14875 c 0.84375,-0.0563 1.5425,-0.11625 2.2,-0.19 1.665,-0.18625 3.09,-0.47375 4.355,-0.87875 1.40125,-0.44375 2.7675,-1.0875 4.06,-1.9125 0.95375,-0.6075 1.90875,-1.3325 2.89875,-2.19625 l 0,-0.0225 c -1.09125,0.93125 -2.13125,1.13875 -3.1675,1.76375 -1.27,0.77 -2.605,1.36 -3.9675,1.75625"
id="path5571"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 306.65735,538.44468 c -1.24375,0.29125 -2.5925,0.4775 -4.24375,0.58875 -0.63625,0.0425 -1.32125,0.0738 -2.155,0.1 l -2.16875,0.0775 c -5.465,0.20625 -11.00625,0.44375 -16.66875,0.69 l 0,1.26625 c 5.62625,-0.38375 11.145,-0.7575 16.71125,-1.105 l 2.16375,-0.13 c 0.84875,-0.0488 1.54,-0.0975 2.17625,-0.15625 1.68125,-0.155 3.0575,-0.38125 4.33,-0.71 1.4275,-0.3675 2.815,-0.9075 4.12375,-1.6025 1.0125,-0.53625 2.0225,-1.18125 3.06875,-1.95875 l 0,-0.54125 c -1.13125,0.81625 -2.21625,1.4825 -3.30125,2.025 -1.28625,0.64375 -2.64375,1.13375 -4.03625,1.45625"
id="path5575"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 306.4721,540.50831 c -1.24875,0.22875 -2.5925,0.375 -4.23125,0.45875 -0.61625,0.0325 -1.27625,0.055 -2.13875,0.075 l -18.68,0.505 0,1.26625 16.56375,-0.865 2.14625,-0.10875 c 0.8375,-0.04 1.5225,-0.08 2.15625,-0.12875 1.66125,-0.1275 3.0275,-0.31 4.3,-0.57625 1.445,-0.3 2.8475,-0.7375 4.1675,-1.3 1.06375,-0.45125 2.12875,-1.00125 3.23875,-1.67 l 0,-0.50875 c -1.185,0.69 -2.3125,1.2425 -3.4325,1.68625 -1.30125,0.5175 -2.6775,0.91 -4.09,1.16625"
id="path5579"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 306.32547,542.58081 c -2.2925,0.30625 -4.65125,0.34125 -6.93375,0.375 -0.5225,0.007 -1.04625,0.015 -1.56875,0.0263 l -16.40125,0.295 0,1.26375 16.43,-0.7075 c 0.52,-0.0237 1.0425,-0.045 1.565,-0.065 2.2975,-0.0925 4.67375,-0.1875 6.9975,-0.55625 1.47,-0.23125 2.885,-0.575 4.20625,-1.01875 1.09375,-0.36625 2.22625,-0.84 3.37375,-1.4 l 0,-0.485 c -1.20625,0.56125 -2.3925,1.02875 -3.53125,1.37875 -1.305,0.4025 -2.69625,0.70375 -4.1375,0.89375"
id="path5583"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 306.19522,544.6663 c -2.1475,0.1775 -4.345,0.1775 -6.47125,0.1775 -0.66125,0 -1.3225,0 -1.98375,0.005 -5.43375,0.0238 -10.82375,0.0688 -16.31875,0.11625 l 0,1.26375 c 5.23375,-0.1775 10.785,-0.3625 16.3375,-0.5275 0.65875,-0.0225 1.3175,-0.0387 1.9775,-0.0562 2.1375,-0.0537 4.3475,-0.10875 6.5175,-0.34375 1.515,-0.1625 2.895,-0.40125 4.21625,-0.7275 1.1525,-0.28375 2.33625,-0.66125 3.525,-1.115 l 0,-0.465 c -1.2275,0.44 -2.45,0.8 -3.64125,1.0625 -1.30875,0.28875 -2.66875,0.4875 -4.15875,0.61"
id="path5587"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 306.11223,546.72218 c -0.895,0.035 -1.86375,0.0525 -2.9625,0.0525 -0.415,0 -0.83125,-0.002 -1.245,-0.006 l -4.2275,-0.0512 c -2.88375,-0.0213 -5.7575,-0.0263 -8.45375,-0.0287 l -7.80125,0.007 0,1.2625 7.82,-0.20375 c 2.68625,-0.0662 5.5675,-0.135 8.44375,-0.18625 l 4.22375,-0.0563 c 1.15875,-0.0175 2.6875,-0.0525 4.23625,-0.15375 1.5175,-0.0987 2.89875,-0.25 4.2225,-0.46125 1.19125,-0.19 2.40875,-0.44625 3.62625,-0.7575 l 0,-0.4475 c -1.25125,0.2875 -2.495,0.51875 -3.70125,0.68 -1.31625,0.17625 -2.68375,0.29 -4.18125,0.35"
id="path5591"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 301.8556,548.7088 -4.2125,-0.045 c -5.375,-0.0763 -10.74375,-0.17375 -16.22125,-0.27625 l 0,1.26375 c 5.2275,-0.0337 10.765,-0.0738 16.225,-0.135 l 4.215,-0.0625 c 1.42,-0.025 2.81125,-0.0512 4.2175,-0.0975 2.89625,-0.0925 5.49125,-0.29625 7.915,-0.6175 l 0,-0.44 c -2.43375,0.2625 -5.03125,0.4 -7.93,0.41875 -1.40125,0.0125 -2.805,0.003 -4.20875,-0.009"
id="path5595"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 313.99473,550.94356 -32.5725,-0.82625 0,1.26375 32.5725,0 0,-0.4375 z"
id="path5599"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 311.06173,529.71581 c -1.2375,0.99375 -2.1925,1.7 -3.7075,2.30125 -1.31875,0.525 -2.735,0.73875 -4.35375,0.96 -1.04125,0.14625 -2.13,0.36125 -3.17,0.43125 -0.4275,0.0287 -0.855,0.0562 -1.28125,0.09 l -17.1275,1.24875 0,1.2675 17.19625,-1.6675 2.23,-0.20875 c 0.82625,-0.0788 1.56125,-0.16875 2.24375,-0.28 1.66625,-0.2675 3.1025,-0.675 4.39,-1.24875 1.335,-0.58875 2.68625,-1.55375 3.9425,-2.61 0.85375,-0.7175 1.67625,-1.62 2.57,-2.60625 l 0,-0.45375 c -1.08125,1.1175 -1.97875,2.01125 -2.9325,2.77625"
id="path5603"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 294.96035,563.49518 28.5325,0 0,-12.68875 -28.5325,0 0,12.68875 z"
id="path5605"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 323.49785,532.29018 -28.5375,0 0,19.09 28.5375,0 0,-19.09 z"
id="path5607"
style="fill:#254aa5;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 309.53872,535.00493 0.9925,0 -0.8,0.62375 0.31125,0.98875 -0.7975,-0.60875 -0.7975,0.60875 0.315,-0.98875 -0.8075,-0.62375 0.99,0 0.3,-0.965 0.29375,0.965 z"
id="path5611"
style="fill:#fff200;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 309.57373,547.73581 0.995,0 -0.80125,0.60125 0.31125,0.95 -0.7975,-0.585 -0.79625,0.585 0.31375,-0.95 -0.80875,-0.60125 0.99125,0 0.3,-0.9275 0.2925,0.9275 z"
id="path5615"
style="fill:#fff200;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 312.72285,546.8928 0.995,0 -0.80125,0.6 0.31125,0.95125 -0.7975,-0.5875 -0.79625,0.5875 0.31375,-0.95125 -0.80875,-0.6 0.99125,0 0.3,-0.92875 0.2925,0.92875 z"
id="path5619"
style="fill:#fff200;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 312.72285,535.85418 0.995,0 -0.80125,0.6 0.31125,0.95125 -0.7975,-0.58625 -0.79625,0.58625 0.31375,-0.95125 -0.80875,-0.6 0.99125,0 0.3,-0.9275 0.2925,0.9275 z"
id="path5623"
style="fill:#fff200;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 315.06097,538.19205 0.9925,0 -0.8,0.59875 0.31125,0.9525 -0.7975,-0.58625 -0.795,0.58625 0.31375,-0.9525 -0.80875,-0.59875 0.99,0 0.3,-0.92875 0.29375,0.92875 z"
id="path5627"
style="fill:#fff200;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 315.06097,544.58668 0.9925,0 -0.8,0.60125 0.31125,0.9525 -0.7975,-0.5875 -0.795,0.5875 0.31375,-0.9525 -0.80875,-0.60125 0.99,0 0.3,-0.92875 0.29375,0.92875 z"
id="path5631"
style="fill:#fff200;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 315.93698,541.34055 0.995,0 -0.80125,0.6 0.31125,0.9525 -0.7975,-0.5875 -0.79625,0.5875 0.31375,-0.9525 -0.80875,-0.6 0.99,0 0.30125,-0.9275 0.2925,0.9275 z"
id="path5635"
style="fill:#fff200;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 306.3281,535.86193 0.9925,0 -0.8,0.60125 0.31125,0.95125 -0.7975,-0.5875 -0.7975,0.5875 0.315,-0.95125 -0.8075,-0.60125 0.99,0 0.3,-0.9275 0.29375,0.9275 z"
id="path5639"
style="fill:#fff200;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 304.05485,538.1998 0.995,0 -0.80125,0.6 0.31,0.9525 -0.79625,-0.5875 -0.79625,0.5875 0.3125,-0.9525 -0.8075,-0.6 0.99125,0 0.3,-0.9275 0.2925,0.9275 z"
id="path5643"
style="fill:#fff200;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 303.2111,541.34843 0.99375,0 -0.8,0.6 0.31,0.9525 -0.79625,-0.58625 -0.7975,0.58625 0.31375,-0.9525 -0.80625,-0.6 0.99,0 0.3,-0.9275 0.2925,0.9275 z"
id="path5647"
style="fill:#fff200;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 304.05485,544.59456 0.995,0 -0.80125,0.60125 0.31,0.9525 -0.79625,-0.5875 -0.79625,0.5875 0.3125,-0.9525 -0.8075,-0.60125 0.99125,0 0.3,-0.92625 0.2925,0.92625 z"
id="path5651"
style="fill:#fff200;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 306.36122,546.90068 0.9925,0 -0.8,0.6 0.30875,0.95125 -0.795,-0.58625 -0.7975,0.58625 0.315,-0.95125 -0.8075,-0.6 0.99,0 0.3,-0.92875 0.29375,0.92875 z"
id="path5655"
style="fill:#fff200;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 294.96035,564.74893 28.54,0 0,-1.62875 -28.54,0 0,1.62875 z"
id="path5657"
style="fill:#254aa5;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 327.55922,518.87968 c 0,0 5.95,9.26625 7.03875,10.97 1.0875,1.7025 2.33125,3.05 6.73375,3.965 4.4025,0.915 6.31875,1.3475 6.31875,1.3475 l 0,0.33 c 0,0 -2.7975,-0.585 -6.3975,-1.3725 -3.59875,-0.7875 -5.07,-1.05375 -6.71125,-3.29125 -1.36625,-1.8625 -6.9825,-9.77875 -6.9825,-9.77875 l 0,-2.17 z"
id="path5661"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 327.55922,521.51443 c 0,0 6.14875,8.80875 6.965,9.88875 0.81625,1.08 1.915,2.82 6.775,3.8475 1.255,0.265 6.3675,1.27125 6.3675,1.27125 l 0,0.27875 c 0,0 -3.885,-0.74875 -6.3675,-1.27125 -2.48375,-0.5225 -5.0925,-0.99875 -6.7575,-3.09875 -1.5125,-1.9075 -6.99375,-8.9425 -6.99375,-8.9425 l 0.0112,-1.97375 z"
id="path5665"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 327.55922,524.16068 c 0,0 5.79375,7.48625 6.965,8.98375 1.17,1.49625 2.78375,2.7325 6.775,3.49875 2.30125,0.4425 6.35,1.18375 6.35,1.18375 l 0,0.27875 c 0,0 -3.5475,-0.66125 -6.35,-1.16625 -2.8025,-0.505 -5.11625,-0.99375 -6.775,-2.855 -1.455,-1.63375 -6.97625,-8.055 -6.97625,-8.055 l 0.0112,-1.86875 z"
id="path5669"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 327.54773,526.76056 c 0,0 5.86625,6.79125 6.99375,8.0025 1.75625,1.88625 2.6075,2.53625 6.7575,3.325 1.66,0.31625 6.35,1.1325 6.35,1.1325 l 0,0.295 c 0,0 -3.88375,-0.69625 -6.35,-1.13125 -2.465,-0.435 -4.55125,-0.5975 -6.775,-2.73375 -1.69375,-1.62625 -6.965,-7.1375 -6.965,-7.1375 l -0.0113,-1.7525 z"
id="path5673"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 327.55922,529.46493 c 0,0 5.41625,5.56375 6.9825,7.16125 1.66125,1.69375 3.79,2.32125 6.74,2.8375 2.81625,0.4925 6.3675,1.0625 6.3675,1.0625 l 0,0.3125 c 0,0 -3.335,-0.535 -6.3675,-1.04375 -2.97375,-0.5 -5.055,-0.7775 -6.7575,-2.31625 -1.525,-1.3775 -6.965,-6.3775 -6.965,-6.3775 l 0,-1.63625 z"
id="path5677"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 327.55922,531.9718 c 0,0 5.15125,4.68875 6.9825,6.29125 1.685,1.47375 3.05125,1.9325 6.7575,2.57625 3.7075,0.64375 6.35,1.04375 6.35,1.04375 l 0,0.33125 c 0,0 -3.3525,-0.54 -6.3675,-1.01 -3.015,-0.47 -4.93625,-0.655 -6.74,-2.08875 -2.08125,-1.655 -6.9825,-5.61125 -6.9825,-5.61125 l 0,-1.5325 z"
id="path5681"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 327.54748,534.65293 c 0,0 5.87625,4.5675 6.97625,5.3675 1.1,0.80125 2.55375,1.735 6.775,2.33375 4.18625,0.5925 6.3325,0.8875 6.3325,0.8875 l 0,0.34875 c 0,0 -3.795,-0.54125 -6.3325,-0.85375 -2.53625,-0.31375 -4.48125,-0.44625 -6.7575,-1.9325 -2.21625,-1.44625 -6.9825,-4.735 -6.9825,-4.735 l -0.0113,-1.41625 z"
id="path5685"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 327.54748,537.21793 c 0,0 5.89375,3.8475 7.01125,4.4575 1.11875,0.60875 2.43125,1.60125 6.74,2.14125 4.31,0.53875 6.3325,0.7825 6.3325,0.7825 l 0,0.33125 c 0,0 -3.565,-0.3825 -6.35,-0.67875 -2.785,-0.29625 -4.6825,-0.69625 -6.7575,-1.81125 -2.075,-1.11375 -6.965,-3.91125 -6.965,-3.91125 l -0.0113,-1.31125 z"
id="path5689"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 327.55922,539.78293 c 0,0 5.21625,2.78125 6.9825,3.65 1.96875,0.96875 3.67125,1.43375 6.7575,1.7925 3.03,0.355 6.35,0.6975 6.35,0.6975 l 0,0.31375 c 0,0 -3.07,-0.3225 -6.35,-0.6275 -3.25125,-0.30125 -4.41,-0.44125 -6.7575,-1.4625 -2.1,-0.9125 -6.99375,-3.145 -6.99375,-3.145 l 0.0112,-1.21875 z"
id="path5693"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 327.55922,542.39431 c 0,0 4.10375,1.7225 7,2.84875 2.85,1.10875 5.02625,1.2025 6.7575,1.39375 0.7625,0.0838 6.315,0.64375 6.315,0.64375 l 0,0.3125 c 0,0 -3.3175,-0.26 -6.3325,-0.52125 -3.015,-0.26125 -4.31625,-0.37125 -6.7575,-1.16625 -2.47125,-0.80375 -6.99375,-2.35 -6.99375,-2.35 l 0.0112,-1.16125 z"
id="path5697"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 327.55922,545.04056 c 0,0 4.1625,1.265 7,1.99625 2.8375,0.73125 5.445,1.0275 6.7575,1.11375 1.3125,0.0875 6.315,0.4875 6.315,0.4875 l 0,0.29625 c 0,0 -2.8375,-0.19 -6.3325,-0.435 -2.82625,-0.1975 -5.09625,-0.40625 -6.7575,-0.76625 -1.84125,-0.39875 -6.99375,-1.60125 -6.99375,-1.60125 l 0.0112,-1.09125 z"
id="path5701"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 327.54773,547.65218 c 0,0 3.415,0.57625 7.01125,1.16 2.69625,0.43875 6.35,0.715 6.74,0.73125 0.39,0.0175 6.35,0.41875 6.35,0.41875 l 0,0.3125 c 0,0 -3.81375,-0.1925 -6.35,-0.33 -2.64125,-0.14375 -5.26625,-0.3 -6.7575,-0.4525 -3.34,-0.3425 -6.99375,-0.795 -6.99375,-0.795 l 0,-1.045 z"
id="path5705"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 327.55922,550.35593 c 0,0 5.36875,0.1975 7,0.2325 1.63125,0.035 13.09,0.4875 13.09,0.4875 l 0,0.31375 -20.09,-0.0125 0,-1.02125 z"
id="path5709"
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 296.6286,553.26193 c 0,-0.0588 0.0275,-0.09 0.09,-0.09 l 1.65625,0 c 0.0538,0 0.0863,0.0312 0.0863,0.085 l 0,0.28 c 0,0.0538 -0.0225,0.0813 -0.09,0.0813 l -1.22875,0 0,0.77375 1.08875,0 c 0.0587,0 0.09,0.0275 0.09,0.0812 l 0,0.27875 c 0,0.0538 -0.0225,0.0812 -0.09,0.0812 l -1.08875,0 0,0.88625 1.26,0 c 0.0537,0 0.0813,0.0275 0.0813,0.0863 l 0,0.27375 c 0,0.0588 -0.0225,0.0812 -0.0863,0.0812 l -1.67875,0 c -0.0625,0 -0.09,-0.0275 -0.09,-0.0812 l 0,-2.8175 z"
id="path5713"
style="fill:#5a5758;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 300.5711,556.16093 -0.32,0 c -0.0675,0 -0.085,-0.0312 -0.085,-0.085 l 0,-0.14 -0.009,-0.005 c -0.17625,0.1225 -0.4825,0.27 -0.75625,0.27 -0.6075,0 -0.66625,-0.36 -0.66625,-0.8325 l 0,-1.32375 c 0,-0.0537 0.0225,-0.0888 0.085,-0.0888 l 0.32875,0 c 0.0588,0 0.0813,0.0312 0.0813,0.0888 l 0,1.265 c 0,0.27125 0.0537,0.44125 0.32875,0.44125 0.19375,0 0.49,-0.14375 0.6075,-0.2025 l 0,-1.50375 c 0,-0.0537 0.0175,-0.0888 0.085,-0.0888 l 0.32,0 c 0.0625,0 0.085,0.0312 0.085,0.0888 l 0,2.03125 c 0,0.0575 -0.0225,0.085 -0.085,0.085"
id="path5717"
style="fill:#5a5758;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 301.5336,556.07543 c 0,0.0538 -0.0225,0.085 -0.085,0.085 l -0.325,0 c -0.0625,0 -0.085,-0.0263 -0.085,-0.085 l 0,-2.03 c 0,-0.0588 0.0225,-0.09 0.085,-0.09 l 0.32,0 c 0.0675,0 0.09,0.0362 0.09,0.09 l 0,0.1975 0.009,0.009 c 0.135,-0.125 0.455,-0.27875 0.66625,-0.31875 0.0588,-0.0138 0.0987,0 0.10875,0.0762 l 0.0312,0.30125 c 0.004,0.0638 0.009,0.09 -0.0862,0.10875 -0.25125,0.0488 -0.57125,0.13875 -0.72875,0.20625 l 0,1.45 z"
id="path5721"
style="fill:#5a5758;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 303.42835,554.3423 c -0.3825,0 -0.46875,0.24375 -0.46875,0.71125 0,0.4775 0.0862,0.71125 0.46375,0.71125 0.41,0 0.49125,-0.23 0.49125,-0.71125 0,-0.4775 -0.0763,-0.71125 -0.48625,-0.71125 m -0.005,1.855 c -0.90375,0 -0.9625,-0.6625 -0.9625,-1.175 0,-0.42375 0.0937,-1.1125 0.9625,-1.1125 0.87375,0 0.995,0.58125 0.995,1.1125 0,0.5125 -0.0625,1.175 -0.995,1.175"
id="path5725"
style="fill:#5a5758;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 305.76373,554.36943 c -0.185,0 -0.46875,0.13 -0.60375,0.2025 l 0,1.125 c 0.2025,0.0588 0.365,0.0763 0.51875,0.0763 0.32375,0 0.43625,-0.16625 0.43625,-0.7325 0,-0.5725 -0.1125,-0.67125 -0.35125,-0.67125 m -0.0362,1.8325 c -0.14875,0 -0.355,-0.0275 -0.5625,-0.1 l -0.005,0.005 0,0.7925 c 0,0.0487 -0.0125,0.09 -0.085,0.09 l -0.32,0 c -0.0712,0 -0.085,-0.0312 -0.085,-0.09 l 0,-2.85375 c 0,-0.0587 0.0225,-0.09 0.085,-0.09 l 0.32,0 c 0.0638,0 0.0813,0.035 0.0813,0.09 l 0,0.135 0.0138,0.009 c 0.16125,-0.1125 0.45,-0.27 0.71125,-0.27 0.58875,0 0.7425,0.445 0.7425,1.125 0,0.73375 -0.185,1.1575 -0.89625,1.1575"
id="path5729"
style="fill:#5a5758;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 307.4016,554.85555 0.89125,0 c 0,-0.275 -0.0725,-0.54 -0.445,-0.54 -0.33375,0 -0.42375,0.2025 -0.44625,0.54 m -0.005,0.3375 c 0.0138,0.455 0.17625,0.58125 0.545,0.58125 0.1625,0 0.41375,-0.0325 0.585,-0.05 0.0813,-0.009 0.10875,-0.009 0.1225,0.0763 l 0.0263,0.14875 c 0.0138,0.0675 0.005,0.10375 -0.0762,0.14 -0.17625,0.0675 -0.50375,0.1125 -0.725,0.1125 -0.86,0 -0.98625,-0.54875 -0.98625,-1.135 0,-0.43625 0.0813,-1.15625 0.96,-1.15625 0.805,0 0.94875,0.5225 0.9625,1.0075 0.005,0.1625 -0.04,0.275 -0.22,0.275 l -1.19375,0 z"
id="path5733"
style="fill:#5a5758;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 310.40323,555.15693 -0.54875,0 c -0.225,0 -0.37,0.0813 -0.37,0.37875 0,0.1975 0.0675,0.26125 0.28,0.26125 0.19375,0 0.4675,-0.11375 0.63875,-0.2125 l 0,-0.4275 z m 0.0275,0.7475 c -0.225,0.17125 -0.5275,0.2925 -0.82875,0.2925 -0.4775,0 -0.61625,-0.26125 -0.61625,-0.63875 -0.005,-0.5275 0.27875,-0.7525 0.84625,-0.7525 l 0.57625,0 0,-0.11625 c 0,-0.27 -0.1,-0.365 -0.46875,-0.365 -0.14875,0 -0.4325,0.0225 -0.6125,0.04 -0.09,0.0138 -0.1125,0.009 -0.125,-0.0537 l -0.0412,-0.16625 c -0.009,-0.0588 0.005,-0.095 0.10375,-0.135 0.19375,-0.0675 0.54875,-0.0987 0.76125,-0.0987 0.77875,0 0.8725,0.31875 0.8725,0.82375 l 0,0.86875 c 0,0.18375 0.0225,0.18875 0.1625,0.20625 0.0675,0.005 0.085,0.0187 0.085,0.0675 l 0,0.19 c 0,0.0488 -0.0312,0.08 -0.1075,0.0937 -0.0725,0.0138 -0.14375,0.0225 -0.21125,0.0225 -0.2125,0 -0.3425,-0.0625 -0.3875,-0.27375 l -0.009,-0.005 z"
id="path5737"
style="fill:#5a5758;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 313.10785,556.16093 -0.32375,0 c -0.0588,0 -0.0863,-0.0275 -0.0863,-0.085 l 0.005,-1.265 c 0,-0.26625 -0.0625,-0.44125 -0.32375,-0.44125 -0.18,0 -0.495,0.14375 -0.6175,0.2025 l 0,1.50375 c 0,0.0538 -0.0225,0.085 -0.085,0.085 l -0.32,0 c -0.0625,0 -0.085,-0.0275 -0.085,-0.085 l 0,-2.03125 c 0,-0.0575 0.0225,-0.0888 0.085,-0.0888 l 0.32,0 c 0.0625,0 0.085,0.0312 0.085,0.0888 l 0,0.13625 c 0.005,0 0.01,0.004 0.0138,0.004 0.16625,-0.11625 0.48125,-0.27 0.75625,-0.27 0.6075,0 0.6625,0.40125 0.6625,0.855 l 0,1.30625 c 0,0.0538 -0.0188,0.085 -0.0863,0.085"
id="path5741"
style="fill:#5a5758;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 297.80335,557.89043 c 0.22,0 0.585,0.0312 0.82375,0.13 0.0725,0.0325 0.0987,0.0688 0.085,0.14 l -0.04,0.185 c -0.0138,0.0538 -0.0362,0.0763 -0.1175,0.0675 -0.22,-0.0275 -0.49,-0.0587 -0.72375,-0.0587 -0.6125,0 -0.7025,0.495 -0.7025,1.08 0,0.59 0.1125,1.05375 0.7025,1.05375 0.26,0 0.48125,-0.0275 0.72375,-0.0588 0.0863,-0.009 0.10375,0.0138 0.1225,0.0725 l 0.035,0.17125 c 0.0187,0.0713 -0.004,0.1125 -0.0763,0.14375 -0.22,0.0937 -0.6125,0.135 -0.8325,0.135 -1.00375,0 -1.215,-0.7425 -1.215,-1.50875 0,-0.76875 0.18875,-1.5525 1.215,-1.5525"
id="path5745"
style="fill:#5a5758;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 299.73385,559.0923 c -0.3825,0 -0.46875,0.24375 -0.46875,0.71125 0,0.4775 0.0863,0.71125 0.46375,0.71125 0.41,0 0.49125,-0.23 0.49125,-0.71125 0,-0.4775 -0.0763,-0.71125 -0.48625,-0.71125 m -0.005,1.855 c -0.90375,0 -0.9625,-0.6625 -0.9625,-1.175 0,-0.42375 0.0937,-1.1125 0.9625,-1.1125 0.87375,0 0.995,0.58125 0.995,1.1125 0,0.5125 -0.0625,1.175 -0.995,1.175"
id="path5749"
style="fill:#5a5758;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 304.12648,560.91093 -0.32375,0 c -0.0637,0 -0.0863,-0.0275 -0.0863,-0.085 l 0,-1.27875 c 0,-0.33375 -0.08,-0.4275 -0.30625,-0.4275 -0.16625,0 -0.43625,0.13 -0.575,0.2025 0,0.045 0.004,0.12625 0.004,0.22875 l 0,1.275 c 0,0.0538 -0.0175,0.085 -0.085,0.085 l -0.32875,0 c -0.0587,0 -0.0763,-0.0275 -0.0763,-0.085 l 0,-1.30125 c 0,-0.275 -0.0725,-0.405 -0.2975,-0.405 -0.1575,0 -0.41,0.1125 -0.58125,0.2025 l 0,1.50375 c 0,0.0538 -0.0225,0.085 -0.085,0.085 l -0.32375,0 c -0.0588,0 -0.0813,-0.0275 -0.0813,-0.085 l 0,-2.03125 c 0,-0.0575 0.0225,-0.0888 0.0813,-0.0888 l 0.31875,0 c 0.0675,0 0.09,0.035 0.09,0.0888 l 0,0.13125 0.005,0.005 c 0.175,-0.1225 0.4225,-0.2575 0.67125,-0.26625 0.26,0 0.47625,0.0538 0.61625,0.315 0.225,-0.16625 0.50375,-0.315 0.7925,-0.315 0.59375,0 0.6525,0.3875 0.6525,0.8375 l 0,1.32375 c 0,0.0538 -0.0188,0.085 -0.0813,0.085"
id="path5753"
style="fill:#5a5758;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 307.65447,560.91093 -0.32375,0 c -0.0637,0 -0.0863,-0.0275 -0.0863,-0.085 l 0,-1.27875 c 0,-0.33375 -0.08,-0.4275 -0.30625,-0.4275 -0.16625,0 -0.43625,0.13 -0.575,0.2025 0,0.045 0.004,0.12625 0.004,0.22875 l 0,1.275 c 0,0.0538 -0.0175,0.085 -0.085,0.085 l -0.32875,0 c -0.0587,0 -0.0763,-0.0275 -0.0763,-0.085 l 0,-1.30125 c 0,-0.275 -0.0725,-0.405 -0.2975,-0.405 -0.1575,0 -0.41,0.1125 -0.58125,0.2025 l 0,1.50375 c 0,0.0538 -0.0225,0.085 -0.085,0.085 l -0.32375,0 c -0.0588,0 -0.0813,-0.0275 -0.0813,-0.085 l 0,-2.03125 c 0,-0.0575 0.0225,-0.0888 0.0813,-0.0888 l 0.31875,0 c 0.0675,0 0.09,0.035 0.09,0.0888 l 0,0.13125 0.005,0.005 c 0.175,-0.1225 0.4225,-0.2575 0.67125,-0.26625 0.26,0 0.47625,0.0538 0.61625,0.315 0.225,-0.16625 0.50375,-0.315 0.7925,-0.315 0.59375,0 0.6525,0.3875 0.6525,0.8375 l 0,1.32375 c 0,0.0538 -0.0188,0.085 -0.0813,0.085"
id="path5757"
style="fill:#5a5758;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 308.59422,560.82543 c 0,0.0488 -0.0175,0.085 -0.0813,0.085 l -0.32,0 c -0.0625,0 -0.09,-0.0263 -0.09,-0.085 l 0,-2.035 c 0,-0.0625 0.0275,-0.085 0.09,-0.085 l 0.32,0 c 0.0638,0 0.0813,0.0362 0.0813,0.085 l 0,2.035 z m -0.23875,-2.44375 c -0.23375,0 -0.265,-0.13125 -0.265,-0.26125 0,-0.14375 0.0488,-0.2575 0.265,-0.2575 0.22125,0 0.26625,0.10375 0.26625,0.2575 0,0.13875 -0.0412,0.26125 -0.26625,0.26125"
id="path5761"
style="fill:#5a5758;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 309.71023,560.94705 c -0.225,0 -0.52625,-0.0312 -0.6975,-0.0987 -0.09,-0.0362 -0.10875,-0.0775 -0.095,-0.14875 l 0.0312,-0.14875 c 0.0138,-0.0725 0.045,-0.0762 0.1175,-0.0675 0.1975,0.0312 0.48625,0.0538 0.63875,0.0538 0.28875,0 0.40625,-0.0763 0.40625,-0.25625 0,-0.2075 -0.0775,-0.2525 -0.36125,-0.2925 -0.44125,-0.0638 -0.8275,-0.1575 -0.8275,-0.64875 0,-0.445 0.34125,-0.67875 0.84625,-0.67875 0.18375,0 0.495,0.0263 0.68375,0.0987 0.0763,0.0312 0.10875,0.0675 0.095,0.135 l -0.0362,0.16125 c -0.0138,0.0638 -0.04,0.0725 -0.12125,0.0638 -0.19375,-0.0225 -0.44625,-0.05 -0.6125,-0.05 -0.2875,0 -0.355,0.0812 -0.355,0.2525 0,0.175 0.1075,0.1975 0.3825,0.2425 0.4275,0.0637 0.81375,0.14875 0.81375,0.685 0,0.52625 -0.445,0.6975 -0.90875,0.6975"
id="path5765"
style="fill:#5a5758;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 311.59573,560.94705 c -0.225,0 -0.52625,-0.0312 -0.6975,-0.0987 -0.09,-0.0362 -0.10875,-0.0775 -0.095,-0.14875 l 0.0312,-0.14875 c 0.0138,-0.0725 0.045,-0.0762 0.1175,-0.0675 0.1975,0.0312 0.48625,0.0538 0.63875,0.0538 0.28875,0 0.40625,-0.0763 0.40625,-0.25625 0,-0.2075 -0.0775,-0.2525 -0.36125,-0.2925 -0.44125,-0.0638 -0.8275,-0.1575 -0.8275,-0.64875 0,-0.445 0.34125,-0.67875 0.84625,-0.67875 0.18375,0 0.495,0.0263 0.68375,0.0987 0.0763,0.0312 0.10875,0.0675 0.095,0.135 l -0.0362,0.16125 c -0.0137,0.0638 -0.04,0.0725 -0.12125,0.0638 -0.19375,-0.0225 -0.44625,-0.05 -0.6125,-0.05 -0.2875,0 -0.355,0.0812 -0.355,0.2525 0,0.175 0.1075,0.1975 0.3825,0.2425 0.4275,0.0637 0.81375,0.14875 0.81375,0.685 0,0.52625 -0.445,0.6975 -0.90875,0.6975"
id="path5769"
style="fill:#5a5758;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 313.29685,560.82543 c 0,0.0488 -0.0175,0.085 -0.0813,0.085 l -0.32,0 c -0.0625,0 -0.09,-0.0263 -0.09,-0.085 l 0,-2.035 c 0,-0.0625 0.0275,-0.085 0.09,-0.085 l 0.32,0 c 0.0638,0 0.0813,0.0362 0.0813,0.085 l 0,2.035 z m -0.23875,-2.44375 c -0.23375,0 -0.265,-0.13125 -0.265,-0.26125 0,-0.14375 0.0488,-0.2575 0.265,-0.2575 0.22125,0 0.26625,0.10375 0.26625,0.2575 0,0.13875 -0.0412,0.26125 -0.26625,0.26125"
id="path5773"
style="fill:#5a5758;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 314.59735,559.0923 c -0.3825,0 -0.4675,0.24375 -0.4675,0.71125 0,0.4775 0.085,0.71125 0.4625,0.71125 0.41,0 0.49125,-0.23 0.49125,-0.71125 0,-0.4775 -0.0763,-0.71125 -0.48625,-0.71125 m -0.005,1.855 c -0.90375,0 -0.9625,-0.6625 -0.9625,-1.175 0,-0.42375 0.0937,-1.1125 0.9625,-1.1125 0.87375,0 0.995,0.58125 0.995,1.1125 0,0.5125 -0.0625,1.175 -0.995,1.175"
id="path5777"
style="fill:#5a5758;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 317.66673,560.91093 -0.32375,0 c -0.0588,0 -0.0863,-0.0275 -0.0863,-0.085 l 0.005,-1.265 c 0,-0.26625 -0.0625,-0.44125 -0.32375,-0.44125 -0.18,0 -0.495,0.14375 -0.6175,0.2025 l 0,1.50375 c 0,0.0538 -0.0225,0.085 -0.085,0.085 l -0.32,0 c -0.0625,0 -0.085,-0.0275 -0.085,-0.085 l 0,-2.03125 c 0,-0.0575 0.0225,-0.0888 0.085,-0.0888 l 0.32,0 c 0.0625,0 0.085,0.0312 0.085,0.0888 l 0,0.13625 c 0.005,0 0.009,0.004 0.0138,0.004 0.16625,-0.11625 0.4825,-0.27 0.75625,-0.27 0.6075,0 0.6625,0.40125 0.6625,0.855 l 0,1.30625 c 0,0.0538 -0.0187,0.085 -0.0863,0.085"
id="path5781"
style="fill:#5a5758;fill-opacity:1;fill-rule:nonzero;stroke:none" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 30 KiB

157
src/img/jara.svg Normal file
View file

@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="400"
height="105"
id="svg2985"
xml:space="preserve"><metadata
id="metadata2991"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs2989"><linearGradient
x1="-128"
y1="0"
x2="128"
y2="0"
id="linearGradient3509"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0,0.0852966,2.3663025,0,343.026,855.076)"><stop
id="stop3511"
style="stop-color:white;stop-opacity:1"
offset="0" /><stop
id="stop3513"
style="stop-color:#f9ac73;stop-opacity:1"
offset="0.5" /><stop
id="stop3515"
style="stop-color:#f36717;stop-opacity:1"
offset="1" /></linearGradient><linearGradient
x1="-128"
y1="0"
x2="128"
y2="0"
id="linearGradient3593"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0,0.0852966,2.3663025,0,-260.886,855.076)"><stop
id="stop3595"
style="stop-color:white;stop-opacity:1"
offset="0" /><stop
id="stop3597"
style="stop-color:#f9ac73;stop-opacity:1"
offset="0.5" /><stop
id="stop3599"
style="stop-color:#f36717;stop-opacity:1"
offset="1" /></linearGradient></defs><g
transform="matrix(1.2232225,0,0,-1.2234375,0,105)"
id="g2993"><g
transform="matrix(2.6332484,0,0,2.6332484,-1060.8763,-547.07195)"
id="g5001"
style="fill:#3f80b0;fill-opacity:1"><path
d="m 483.97399,225.734 c 0.45,0 0.601,0.014 0.779,0.079 0.25,0.093 0.4,0.329 0.4,0.629 0,0.243 -0.107,0.443 -0.307,0.558 -0.186,0.107 -0.315,0.121 -0.936,0.121 l -0.265,0 0,-1.387 0.329,0 z m -0.058,2.166 c 0.844,0 1.165,-0.05 1.495,-0.228 0.429,-0.229 0.679,-0.687 0.679,-1.237 0,-0.622 -0.286,-1.073 -0.844,-1.323 l 1.094,-1.981 -1.065,0 -0.973,1.824 -0.657,0 0,-1.824 -0.916,0 0,4.769 1.187,0"
id="path3313"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 488.90699,225.262 c -0.071,0.458 -0.286,0.679 -0.672,0.679 -0.386,0 -0.63,-0.221 -0.744,-0.679 l 1.416,0 z m -1.437,-0.643 c 0.028,-0.558 0.329,-0.873 0.822,-0.873 0.322,0 0.544,0.122 0.658,0.379 l 0.779,-0.036 c -0.15,-0.65 -0.708,-1.051 -1.459,-1.051 -1.029,0 -1.68,0.68 -1.68,1.752 0,1.101 0.658,1.831 1.652,1.831 0.958,0 1.552,-0.701 1.552,-1.831 l 0,-0.171 -2.324,0"
id="path3317"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 492.324,225.498 c -0.043,0.322 -0.214,0.472 -0.543,0.472 -0.272,0 -0.472,-0.15 -0.472,-0.351 0,-0.1 0.05,-0.193 0.143,-0.243 0.1,-0.064 0.172,-0.086 0.686,-0.236 0.515,-0.15 0.73,-0.257 0.866,-0.436 0.128,-0.15 0.192,-0.35 0.192,-0.572 0,-0.686 -0.507,-1.094 -1.38,-1.094 -0.993,0 -1.508,0.422 -1.508,1.23 l 0.829,0 c 0.029,-0.4 0.222,-0.565 0.644,-0.565 0.336,0 0.536,0.143 0.536,0.365 0,0.136 -0.057,0.2 -0.243,0.279 -0.1,0.043 -0.1,0.043 -0.7,0.221 -0.372,0.115 -0.566,0.208 -0.701,0.343 -0.15,0.151 -0.236,0.372 -0.236,0.615 0,0.658 0.543,1.102 1.358,1.102 0.83,0 1.33,-0.415 1.38,-1.13 l -0.851,0"
id="path3321"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 496.057,225.262 c -0.071,0.458 -0.286,0.679 -0.672,0.679 -0.386,0 -0.63,-0.221 -0.744,-0.679 l 1.416,0 z m -1.437,-0.643 c 0.028,-0.558 0.329,-0.873 0.822,-0.873 0.322,0 0.544,0.122 0.658,0.379 l 0.779,-0.036 c -0.15,-0.65 -0.708,-1.051 -1.459,-1.051 -1.029,0 -1.68,0.68 -1.68,1.752 0,1.101 0.658,1.831 1.652,1.831 0.958,0 1.552,-0.701 1.552,-1.831 l 0,-0.171 -2.324,0"
id="path3325"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 499.525,224.747 c -0.579,-0.071 -0.801,-0.129 -0.951,-0.236 -0.129,-0.093 -0.201,-0.229 -0.201,-0.372 0,-0.236 0.215,-0.407 0.515,-0.407 0.265,0 0.487,0.128 0.573,0.329 0.05,0.1 0.064,0.243 0.064,0.493 l 0,0.193 z m -1.931,0.765 c 0.05,0.386 0.158,0.601 0.4,0.786 0.258,0.194 0.644,0.301 1.081,0.301 0.585,0 1.008,-0.186 1.164,-0.508 0.101,-0.2 0.137,-0.407 0.137,-0.836 l 0,-1.366 c 0,-0.407 0.028,-0.579 0.114,-0.758 l -0.801,0 c -0.035,0.136 -0.042,0.215 -0.042,0.386 -0.186,-0.307 -0.487,-0.457 -0.937,-0.457 -0.779,0 -1.237,0.357 -1.237,0.965 0,0.472 0.272,0.829 0.772,1.015 0.308,0.122 0.357,0.129 1.28,0.293 l 0,0.057 c 0,0.372 -0.143,0.515 -0.515,0.515 -0.336,0 -0.529,-0.128 -0.579,-0.393 l -0.837,0"
id="path3329"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 502.128,226.527 c 0.036,-0.15 0.057,-0.336 0.057,-0.529 0,-0.036 0,-0.1 -0.008,-0.179 0.273,0.551 0.566,0.78 0.981,0.78 0.093,0 0.121,-0.008 0.264,-0.043 l 0,-0.894 c -0.165,0.107 -0.343,0.164 -0.53,0.164 -0.464,0 -0.679,-0.357 -0.679,-1.115 l 0,-1.58 -0.879,0 0,2.352 c 0,0.15 0,0.351 -0.007,0.601 0,0.193 -0.007,0.257 -0.036,0.443 l 0.837,0"
id="path3333"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 505.953,225.40499 c -0.051,0.336 -0.237,0.508 -0.544,0.508 -0.45,0 -0.743,-0.429 -0.743,-1.087 0,-0.679 0.272,-1.08 0.729,-1.08 0.351,0 0.543,0.194 0.593,0.594 l 0.851,0 c -0.043,-0.78 -0.622,-1.301 -1.444,-1.301 -0.973,0 -1.63,0.722 -1.63,1.787 0,1.08 0.657,1.795 1.644,1.795 0.808,0 1.302,-0.429 1.409,-1.216 l -0.865,0"
id="path3337"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 508.40499,227.929 0,-1.773 c 0.265,0.293 0.586,0.443 0.98,0.443 0.429,0 0.815,-0.193 0.993,-0.5 0.129,-0.215 0.158,-0.415 0.158,-1.009 l 0,-1.959 -0.88,0 0,1.766 c 0,0.343 -0.021,0.508 -0.093,0.644 -0.093,0.186 -0.264,0.286 -0.486,0.286 -0.25,0 -0.479,-0.143 -0.586,-0.372 -0.057,-0.136 -0.086,-0.307 -0.086,-0.586 l 0,-1.738 -0.879,0 0,4.798 0.879,0"
id="path3341"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 484.302,218.26899 -0.608,-1.945 1.223,0 -0.615,1.945 z m 0.465,1.051 1.622,-4.769 -0.95,0 -0.293,0.994 -1.681,0 -0.3,-0.994 -0.936,0 1.608,4.769 0.93,0"
id="path3345"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 486.955,214.551 0.879,0 0,4.798 -0.879,0 0,-4.798 z"
id="path3347"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 488.82,214.551 0.88,0 0,4.798 -0.88,0 0,-4.798 z"
id="path3349"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 490.68701,217.94799 0.879,0 0,-3.397 -0.879,0 0,3.397 z m 0.879,0 z m 0.071,0.958 c 0,-0.315 -0.2,-0.515 -0.507,-0.515 -0.315,0 -0.514,0.193 -0.514,0.501 0,0.314 0.214,0.521 0.528,0.521 0.293,0 0.493,-0.207 0.493,-0.507"
id="path3353"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 494.39801,216.167 c -0.579,-0.071 -0.801,-0.128 -0.952,-0.236 -0.128,-0.093 -0.2,-0.229 -0.2,-0.371 0,-0.237 0.215,-0.409 0.515,-0.409 0.265,0 0.486,0.129 0.573,0.33 0.049,0.1 0.064,0.243 0.064,0.493 l 0,0.193 z m -1.931,0.765 c 0.05,0.386 0.157,0.601 0.4,0.787 0.258,0.193 0.644,0.3 1.081,0.3 0.585,0 1.008,-0.186 1.164,-0.508 0.101,-0.2 0.137,-0.407 0.137,-0.836 l 0,-1.366 c 0,-0.407 0.028,-0.579 0.114,-0.758 l -0.801,0 c -0.036,0.136 -0.042,0.215 -0.042,0.387 -0.186,-0.308 -0.487,-0.458 -0.938,-0.458 -0.779,0 -1.237,0.357 -1.237,0.965 0,0.472 0.273,0.829 0.773,1.015 0.307,0.122 0.357,0.129 1.28,0.293 l 0,0.057 c 0,0.373 -0.143,0.516 -0.515,0.516 -0.336,0 -0.529,-0.129 -0.579,-0.394 l -0.837,0"
id="path3357"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 496.957,217.94799 c 0.05,-0.107 0.072,-0.25 0.072,-0.436 0.25,0.336 0.593,0.507 1.008,0.507 0.422,0 0.793,-0.186 0.973,-0.493 0.128,-0.229 0.156,-0.4 0.156,-1.015 l 0,-1.96 -0.879,0 0,1.766 c 0,0.358 -0.022,0.53 -0.085,0.659 -0.086,0.178 -0.251,0.271 -0.466,0.271 -0.25,0 -0.471,-0.136 -0.564,-0.365 -0.065,-0.129 -0.079,-0.271 -0.079,-0.593 l 0,-1.738 -0.88,0 0,2.181 c 0,0.823 -0.007,0.887 -0.107,1.216 l 0.851,0"
id="path3361"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 502.06201,216.82499 c -0.05,0.336 -0.236,0.508 -0.543,0.508 -0.45,0 -0.743,-0.43 -0.743,-1.087 0,-0.679 0.271,-1.08 0.729,-1.08 0.35,0 0.543,0.193 0.593,0.593 l 0.851,0 c -0.043,-0.779 -0.622,-1.3 -1.444,-1.3 -0.973,0 -1.631,0.721 -1.631,1.787 0,1.08 0.658,1.795 1.645,1.795 0.808,0 1.301,-0.43 1.408,-1.216 l -0.865,0"
id="path3365"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 505.74501,216.682 c -0.072,0.458 -0.287,0.679 -0.673,0.679 -0.386,0 -0.629,-0.221 -0.743,-0.679 l 1.416,0 z m -1.438,-0.643 c 0.029,-0.559 0.329,-0.873 0.823,-0.873 0.322,0 0.543,0.122 0.658,0.379 l 0.779,-0.036 c -0.151,-0.651 -0.708,-1.051 -1.459,-1.051 -1.03,0 -1.68,0.679 -1.68,1.752 0,1.101 0.658,1.83 1.652,1.83 0.958,0 1.551,-0.701 1.551,-1.83 l 0,-0.171 -2.324,0"
id="path3369"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 484.47599,236.28199 0,-3.797 c 0,-0.879 -0.029,-1.094 -0.172,-1.358 -0.221,-0.393 -0.665,-0.572 -1.43,-0.572 -0.057,0 -0.15,0 -0.271,0.007 l 0,0.765 0.156,0 c 0.415,0.007 0.637,0.072 0.723,0.222 0.071,0.107 0.079,0.214 0.079,0.915 l 0,3.818 0.915,0"
id="path3373"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 488.122,235.917 c 0,-0.265 -0.215,-0.486 -0.486,-0.486 -0.265,0 -0.486,0.221 -0.486,0.486 0,0.272 0.215,0.486 0.493,0.486 0.264,0 0.479,-0.222 0.479,-0.486 z m -1.273,0 c 0,-0.265 -0.214,-0.486 -0.486,-0.486 -0.264,0 -0.486,0.221 -0.486,0.486 0,0.272 0.214,0.486 0.493,0.486 0.265,0 0.479,-0.222 0.479,-0.486 z m -0.479,-1.008 0,-1.931 c 0,-0.536 0.172,-0.765 0.58,-0.765 0.45,0 0.672,0.286 0.672,0.88 l 0,1.816 0.879,0 0,-2.345 c 0,-0.751 0,-0.751 0.086,-1.051 l -0.829,0 c -0.036,0.078 -0.065,0.236 -0.072,0.393 -0.265,-0.336 -0.536,-0.465 -0.951,-0.465 -0.465,0 -0.865,0.208 -1.065,0.544 -0.122,0.2 -0.179,0.522 -0.179,0.944 l 0,1.98 0.879,0"
id="path3377"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 489.488,231.513 0.88,0 0,4.798 -0.88,0 0,-4.798 z"
id="path3379"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 491.354,234.90901 0.879,0 0,-3.396 -0.879,0 0,3.396 z m 0.879,0 z m 0.072,0.958 c 0,-0.314 -0.2,-0.515 -0.508,-0.515 -0.315,0 -0.514,0.193 -0.514,0.501 0,0.314 0.214,0.522 0.529,0.522 0.293,0 0.493,-0.208 0.493,-0.508"
id="path3383"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 495.151,233.787 c -0.051,0.336 -0.237,0.508 -0.543,0.508 -0.451,0 -0.744,-0.429 -0.744,-1.087 0,-0.679 0.272,-1.08 0.729,-1.08 0.351,0 0.544,0.193 0.593,0.594 l 0.851,0 c -0.043,-0.78 -0.622,-1.301 -1.444,-1.301 -0.972,0 -1.63,0.722 -1.63,1.787 0,1.08 0.658,1.795 1.645,1.795 0.807,0 1.301,-0.429 1.408,-1.216 l -0.865,0"
id="path3387"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 497.602,236.311 0,-1.773 c 0.265,0.293 0.586,0.443 0.98,0.443 0.429,0 0.815,-0.193 0.993,-0.5 0.129,-0.215 0.158,-0.415 0.158,-1.009 l 0,-1.959 -0.88,0 0,1.766 c 0,0.343 -0.021,0.508 -0.093,0.644 -0.092,0.186 -0.264,0.286 -0.485,0.286 -0.251,0 -0.479,-0.143 -0.587,-0.372 -0.057,-0.136 -0.086,-0.307 -0.086,-0.586 l 0,-1.738 -0.879,0 0,4.798 0.879,0"
id="path3391"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 503.58,235.231 -0.607,-1.945 1.222,0 -0.615,1.945 z m 0.465,1.051 1.623,-4.769 -0.951,0 -0.293,0.994 -1.68,0 -0.3,-0.994 -0.937,0 1.609,4.769 0.929,0"
id="path3395"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 508.03401,233.12899 c -0.579,-0.071 -0.801,-0.129 -0.951,-0.236 -0.129,-0.093 -0.2,-0.229 -0.2,-0.372 0,-0.236 0.214,-0.407 0.515,-0.407 0.264,0 0.485,0.129 0.572,0.329 0.05,0.1 0.064,0.243 0.064,0.493 l 0,0.193 z m -1.93,0.765 c 0.05,0.386 0.156,0.601 0.4,0.786 0.257,0.194 0.644,0.301 1.08,0.301 0.586,0 1.008,-0.186 1.165,-0.508 0.1,-0.2 0.136,-0.407 0.136,-0.836 l 0,-1.366 c 0,-0.407 0.028,-0.579 0.114,-0.758 l -0.801,0 c -0.035,0.136 -0.042,0.215 -0.042,0.386 -0.186,-0.307 -0.487,-0.457 -0.938,-0.457 -0.778,0 -1.236,0.357 -1.236,0.965 0,0.472 0.272,0.829 0.772,1.015 0.308,0.122 0.358,0.129 1.28,0.293 l 0,0.057 c 0,0.372 -0.143,0.515 -0.515,0.515 -0.336,0 -0.529,-0.128 -0.578,-0.393 l -0.837,0"
id="path3399"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 511.781,233.787 c -0.05,0.336 -0.236,0.508 -0.543,0.508 -0.45,0 -0.744,-0.429 -0.744,-1.087 0,-0.679 0.272,-1.08 0.73,-1.08 0.35,0 0.543,0.193 0.593,0.594 l 0.851,0 c -0.043,-0.78 -0.622,-1.301 -1.444,-1.301 -0.973,0 -1.631,0.722 -1.631,1.787 0,1.08 0.658,1.795 1.645,1.795 0.808,0 1.301,-0.429 1.408,-1.216 l -0.865,0"
id="path3403"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 514.23201,236.311 0,-1.773 c 0.265,0.293 0.587,0.443 0.98,0.443 0.429,0 0.815,-0.193 0.993,-0.5 0.13,-0.215 0.158,-0.415 0.158,-1.009 l 0,-1.959 -0.879,0 0,1.766 c 0,0.343 -0.022,0.508 -0.093,0.644 -0.093,0.186 -0.265,0.286 -0.486,0.286 -0.251,0 -0.479,-0.143 -0.587,-0.372 -0.057,-0.136 -0.086,-0.307 -0.086,-0.586 l 0,-1.738 -0.879,0 0,4.798 0.879,0"
id="path3407"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 519.38101,233.64399 c -0.071,0.458 -0.287,0.679 -0.672,0.679 -0.387,0 -0.629,-0.221 -0.744,-0.679 l 1.416,0 z m -1.438,-0.643 c 0.029,-0.558 0.33,-0.873 0.823,-0.873 0.322,0 0.544,0.122 0.657,0.379 l 0.78,-0.035 c -0.15,-0.651 -0.708,-1.052 -1.459,-1.052 -1.029,0 -1.68,0.68 -1.68,1.752 0,1.101 0.658,1.831 1.652,1.831 0.958,0 1.552,-0.701 1.552,-1.831 l 0,-0.171 -2.325,0"
id="path3411"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 521.747,234.90901 c 0.049,-0.107 0.071,-0.25 0.071,-0.436 0.25,0.336 0.594,0.508 1.009,0.508 0.421,0 0.793,-0.186 0.972,-0.494 0.129,-0.228 0.157,-0.4 0.157,-1.015 l 0,-1.959 -0.879,0 0,1.766 c 0,0.357 -0.022,0.529 -0.086,0.658 -0.086,0.178 -0.25,0.271 -0.465,0.271 -0.25,0 -0.472,-0.136 -0.565,-0.364 -0.064,-0.129 -0.078,-0.272 -0.078,-0.594 l 0,-1.737 -0.88,0 0,2.181 c 0,0.822 -0.007,0.886 -0.106,1.215 l 0.85,0"
id="path3415"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 414.82,236.759 0,-17.911 c 0,-4.149 -0.135,-5.162 -0.81,-6.409 -1.045,-1.855 -3.137,-2.698 -6.747,-2.698 -0.269,0 -0.707,0 -1.281,0.033 l 0,3.609 0.742,0 c 1.956,0.034 3.002,0.338 3.408,1.047 0.337,0.505 0.37,1.01 0.37,4.317 l 0,18.012 4.318,0"
id="path3419"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 426.61599,231.8 -2.867,-9.175 5.769,0 -2.902,9.175 z m 2.194,4.959 7.656,-22.5 -4.485,0 -1.385,4.689 -7.926,0 -1.417,-4.689 -4.419,0 7.591,22.5 4.385,0"
id="path3423"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 443.721,226.53801 c 2.126,0 2.834,0.068 3.677,0.371 1.181,0.438 1.889,1.551 1.889,2.969 0,1.146 -0.506,2.091 -1.45,2.631 -0.877,0.506 -1.485,0.573 -4.419,0.573 l -1.248,0 0,-6.544 1.551,0 z m -0.27,10.221 c 3.98,0 5.499,-0.236 7.051,-1.08 2.023,-1.078 3.204,-3.238 3.204,-5.835 0,-2.935 -1.349,-5.06 -3.98,-6.24 l 5.16,-9.345 -5.025,0 -4.588,8.603 -3.103,0 0,-8.603 -4.318,0 0,22.5 5.599,0"
id="path3427"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 465.72399,231.8 -2.867,-9.175 5.768,0 -2.901,9.175 z m 2.193,4.959 7.657,-22.5 -4.486,0 -1.384,4.689 -7.926,0 -1.416,-4.689 -4.421,0 7.591,22.5 4.385,0"
id="path3431"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 478.65901,238.25801 0,-25.377"
id="path3435"
style="fill:#3f80b0;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 478.65901,238.25801 0,-25.377"
id="path3439"
style="fill:#3f80b0;fill-opacity:1;stroke:#467ab0;stroke-width:0.20886751;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></g></svg>

After

Width:  |  Height:  |  Size: 17 KiB

158
src/img/villas_web.svg Normal file
View file

@ -0,0 +1,158 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="149.71165mm"
height="149.71165mm"
viewBox="0 0 149.71165 149.71165"
version="1.1"
id="svg8"
inkscape:version="0.92.1 r"
sodipodi:docname="villasweb.svg"
inkscape:export-filename="/home/markus/Development/Projects/VILLASweb/doc/villasweb.svg.png"
inkscape:export-xdpi="21.299999"
inkscape:export-ydpi="21.299999">
<defs
id="defs2">
<linearGradient
inkscape:collect="always"
id="linearGradient4611">
<stop
style="stop-color:#00a2b0;stop-opacity:1"
offset="0"
id="stop4607" />
<stop
style="stop-color:#6ec5b0;stop-opacity:1"
offset="1"
id="stop4609" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4611"
id="linearGradient4613"
x1="65.497406"
y1="153.88686"
x2="65.497406"
y2="13.88037"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.0941748"
inkscape:cx="96.131516"
inkscape:cy="285.10587"
inkscape:document-units="mm"
inkscape:current-layer="g5005"
showgrid="false"
inkscape:snap-smooth-nodes="true"
inkscape:snap-bbox="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:bbox-nodes="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="false"
inkscape:bbox-paths="false"
inkscape:snap-midpoints="true"
inkscape:window-width="1920"
inkscape:window-height="951"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:measure-start="37.9039,424.38"
inkscape:measure-end="101.386,461.031"
inkscape:snap-global="true" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(9.2667999,-9.6854979)">
<g
id="g5005"
transform="matrix(0.9623191,0,0,0.9623191,2.7484651,3.9385684)">
<path
inkscape:connector-curvature="0"
id="path4329"
d="M 65.519531,7.6875 C 30.952342,7.2288821 -1.7559504,32.928216 -9.0985572,66.77029 c -8.9441948,34.36253 9.91032274,73.19316 42.4331002,87.43912 31.90533,15.5955 73.697367,4.8306 94.125297,-24.21303 C 149.0773,101.82081 146.84579,58.724642 122.42805,32.933531 108.17012,16.922072 86.915542,7.695183 65.519531,7.6875 Z"
style="opacity:1;fill:none;fill-opacity:1;stroke:none;stroke-width:3.70000005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
sodipodi:type="star"
style="fill:none;stroke:url(#linearGradient4613);stroke-width:20.38569832;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4572"
sodipodi:sides="6"
sodipodi:cx="65.497406"
sodipodi:cy="83.983902"
sodipodi:r1="58.333843"
sodipodi:r2="50.518589"
sodipodi:arg1="-0.52359878"
sodipodi:arg2="-4.4017012e-09"
inkscape:flatsided="true"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 116.016,54.81698 0,58.33384 -50.518594,29.16693 -50.51859,-29.16693 0,-58.333839 50.51859,-29.166922 z"
transform="matrix(0.83725065,0,0,0.85461295,10.659664,12.436674)" />
<path
sodipodi:type="star"
style="fill:#007da9;fill-opacity:1;stroke:none;stroke-width:17.67682076;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4576-3"
sodipodi:sides="3"
sodipodi:cx="-80.89846"
sodipodi:cy="-21.019157"
sodipodi:r1="15.401051"
sodipodi:r2="7.7005253"
sodipodi:arg1="2.0943951"
sodipodi:arg2="3.1415927"
inkscape:flatsided="true"
inkscape:rounded="0"
inkscape:randomized="0"
d="m -88.598986,-7.6814564 0,-26.6754016 23.101576,13.337701 z"
transform="matrix(-1,0,0,-1.0094199,3.7691891,-0.07235845)"
inkscape:transform-center-x="3.7051813" />
<path
d="m 42.395834,161.35432 0,-26.6754 23.101576,13.3377 z"
inkscape:randomized="0"
inkscape:rounded="0"
inkscape:flatsided="true"
sodipodi:arg2="3.1415927"
sodipodi:arg1="2.0943951"
sodipodi:r2="7.7005253"
sodipodi:r1="15.401051"
sodipodi:cy="148.01662"
sodipodi:cx="50.096359"
sodipodi:sides="3"
id="path4593"
style="fill:#007da9;fill-opacity:1;stroke:none;stroke-width:17.67682076;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:type="star"
inkscape:transform-center-x="-3.7051807"
transform="matrix(1,0,0,1.0094199,-4.2717477,-1.5199394)"
inkscape:transform-center-y="1.8921547e-06" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.8 KiB

View file

@ -30,11 +30,11 @@ class FileStore extends ArrayStore {
reduce(state, action) {
switch (action.type) {
case 'files/start-upload':
FilesDataManager.upload(action.data, action.token);
FilesDataManager.upload(action.data, action.token, action.progressCallback, action.finishedCallback);
return state;
case 'files/uploaded':
console.log('ready uploaded');
//console.log('ready uploaded');
return state;
case 'files/upload-error':

View file

@ -84,7 +84,11 @@ body {
clear: both;
}
.app-body-spacing {
.app-footer a {
color: #333;
}
.app-body {
padding: 15px 5px 0px 5px;
}
@ -144,7 +148,7 @@ body {
.menu-sidebar ul {
padding-top: 10px;
list-style: none;
white-space: nowrap;
white-space: nowrap;
}
.menu-sidebar a::after {
@ -201,6 +205,19 @@ body {
text-decoration: none;
color: #f1f1f1;
}
/**
* Home page
*/
.home-container > ul {
margin-left: 2em;
}
.home-container > img {
margin: 20px;
}
/**
* Login form
*/
@ -238,6 +255,11 @@ body {
color: #888;
}
.table-control-checkbox input {
position: relative !important;
margin-top: 0 !important;
}
.unselectable {
-webkit-touch-callout: none !important; /* iOS Safari */
-webkit-user-select: none !important; /* Safari */