Commit 78d265fc authored by Patrik Meijer's avatar Patrik Meijer
Browse files

Adding files and deps for AttributeEditor

parent fe459cac
# See https://help.github.com/ignore-files/ for more about ignoring files.
# IDE files
.idea
# dependencies
/node_modules
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# webgme specific
blob-local-storage
server.log
server-error.log
# dss specific
outputs
scripts/py_modelica_exporter/Icons
scripts/py_modelica_exporter/components.json
scripts/py_modelica_exporter/modelica.json
simres_example.json
**/*.pyc
/src/common/comp_flat.json
/src/common/comp_stats.json
# Vagrant
deployment/.vagrant
deployment/*.log
public/**/*.js
public/**/*.css
public/**/*.svg
public/**/*.map
This diff is collapsed.
......@@ -18,5 +18,10 @@
"material-ui"
],
"author": "WebGME Dev Team",
"license": "MIT"
"license": "MIT",
"dependencies": {
"@material-ui/core": "^3.1.0",
"@material-ui/icons": "^3.0.1",
"blockies": "0.0.2"
}
}
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import CardHeader from '@material-ui/core/CardHeader';
import Territory from '../Territory';
import AttributeItem from '../AttributeItem';
export const AttributeTypes = {
string: 'string',
number: 'number',
color: 'color',
boolean: 'boolean',
asset: 'asset',
};
export default class AttributeEditor extends Component {
static propTypes = {
gmeClient: PropTypes.object.isRequired,
selection: PropTypes.arrayOf(PropTypes.string).isRequired,
children: PropTypes.object,
fullWidthWidgets: PropTypes.bool,
hideReadOnly: PropTypes.bool,
};
static defaultProps = {
fullWidthWidgets: false,
hideReadOnly: false,
children: null,
}
state = {
loadedNodes: [],
territory: {},
attributes: [],
};
componentWillReceiveProps(newProps) {
const {selection} = newProps;
const territory = {};
selection.forEach((item) => {
territory[item] = {children: 0};
});
// TODO clearing the loadedNodes should not be necessary, where are the unload events???
if (selection[0] !== this.props.selection[0]) {
this.setState({loadedNodes: [], territory});
}
}
onComponentDidMount() {
const {selection} = this.props;
const territory = {};
selection.forEach((item) => {
territory[item] = {children: 0};
});
this.setState({territory});
}
handleEvents = (hash, loads, updates, unloads) => {
// TODO update to handle multiple objects as well
const {selection, gmeClient} = this.props;
let {attributes} = this.state;
const {loadedNodes} = this.state;
// console.log('handleEvents');
selection.forEach((nodeId) => {
if (loads.indexOf(nodeId) !== -1 || updates.indexOf(nodeId) !== -1) {
loadedNodes.push(nodeId);
}
if (unloads.indexOf(nodeId) !== -1) {
loadedNodes.splice(loadedNodes.indexOf(nodeId), 1);
}
});
if (loadedNodes.length > 0) {
const nodeObj = gmeClient.getNode(loadedNodes[0]);
const attributeNames = nodeObj.getValidAttributeNames();
attributes = attributeNames
.map((id) => {
const attrMeta = nodeObj.getAttributeMeta(id);
return {
name: id,
value: nodeObj.getAttribute(id),
type: attrMeta.type || 'string',
enum: attrMeta.enum || null,
readonly: attrMeta.readonly,
description: attrMeta.description,
unit: attrMeta.unit,
};
});
} else {
attributes = [];
}
this.setState({loadedNodes, attributes});
};
somethingChanges = (what, how) => {
const {selection, gmeClient} = this.props;
const transaction = selection.length > 1;
if (transaction) {
gmeClient.startTransaction('Updating attributes of the selection');
}
selection.forEach((nodeId) => {
gmeClient.setAttribute(nodeId, what, how);
});
if (transaction) {
gmeClient.completeTransaction('Update multiple attributes finished.');
}
};
render() {
const {selection, gmeClient, hideReadOnly} = this.props;
const {
territory, attributes, loadedNodes,
} = this.state;
const attributeItems = attributes
.filter(attr => !hideReadOnly || !attr.readonly)
.map((attribute) => {
const onChangeFn = (newValue) => {
this.somethingChanges(attribute.name, newValue);
};
let {type} = attribute;
switch (attribute.type) {
case 'string':
case 'boolean':
case 'asset':
break;
case 'integer':
case 'float':
type = AttributeTypes.number;
break;
default:
type = AttributeTypes.string;
}
return (
<AttributeItem
style={{marginBottom: attribute.description ? 30 : 0}}
key={attribute.name}
value={attribute.value}
name={attribute.name}
fullWidth={this.props.fullWidthWidgets}
type={type}
values={attribute.enum}
description={attribute.description}
unit={attribute.unit}
readonly={attribute.readonly}
onFullChange={onChangeFn}
invalidChars={attribute.name === 'name' ? /[^\w]/gi : null} // This is Modelica specific
/>);
});
const icon = (loadedNodes.length > 0 && this.props.children) ?
React.Children.map(this.props.children, (child) => {
const nodeId = selection[0]; // This assumes only one node.
return React.cloneElement(child, {nodeId});
})
: null;
return (
<Card>
<Territory
activeNode={selection[0]}
gmeClient={gmeClient}
territory={territory}
onUpdate={this.handleEvents}
onlyActualEvents
/>
<div style={{textAlign: 'center', width: '100%'}}>
<CardHeader title="Parameters"/>
{icon}
</div>
<CardContent>
{attributeItems}
</CardContent>
</Card>
);
}
}
export {default} from './AttributeEditor';
\ No newline at end of file
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import Input, {InputAdornment} from '@material-ui/core/Input';
import FormControl from '@material-ui/core/FormControl';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import FormHelperText from '@material-ui/core/FormHelperText';
import FormLabel from '@material-ui/core/FormLabel';
import InvertColors from '@material-ui/icons/InvertColors';
import InvertColorsOff from '@material-ui/icons/InvertColorsOff';
import IconButton from '@material-ui/core/IconButton';
import Select from '@material-ui/core/Select';
import Switch from '@material-ui/core/Switch';
import {GithubPicker} from 'react-color';
export const AttributeTypes = {
string: 'string',
number: 'number',
color: 'color',
boolean: 'boolean',
asset: 'asset',
};
export default class AttributeItem extends Component {
static propTypes = {
onChange: PropTypes.func,
onFullChange: PropTypes.func,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.bool,
]).isRequired,
values: PropTypes.arrayOf(PropTypes.string),
readonly: PropTypes.bool,
style: PropTypes.object,
fullWidth: PropTypes.bool,
noTriggerOnBlur: PropTypes.bool,
name: PropTypes.string.isRequired,
description: PropTypes.string,
unit: PropTypes.string,
type: PropTypes.string.isRequired,
invalidChars: PropTypes.object,
};
static defaultProps = {
onChange: () => {
},
onFullChange: () => {
},
values: null,
description: null,
unit: null,
style: null,
fullWidth: false,
readonly: false,
noTriggerOnBlur: false,
invalidChars: null,
}
state = {
value: this.props.value,
picking: false,
};
componentWillReceiveProps(nextProps) {
this.setState({value: nextProps.value, picking: false});
}
onColorChange = (color) => {
const {value} = this.props;
if (color.hex !== value) {
this.props.onChange(color.hex);
this.props.onFullChange(color.hex);
this.setState({value: color.hex, picking: false});
}
this.setState({picking: false});
};
onChange = (event) => {
const {
type, value, values,
} = this.props;
const newValue = this.eventToAttrValue(event);
switch (type) {
case AttributeTypes.boolean:
this.props.onChange(newValue);
this.props.onFullChange(newValue);
this.setState({value: newValue});
break;
case AttributeTypes.string:
case AttributeTypes.asset:
if (newValue !== value) {
this.props.onChange(newValue);
if (values && values.length > 0) {
this.props.onFullChange(newValue);
}
}
this.setState({value: newValue});
break;
case AttributeTypes.number:
if (newValue !== value) {
this.props.onChange(newValue);
if (values && values.length > 0) {
this.props.onFullChange(newValue);
}
}
this.setState({value: newValue});
break;
default:
break;
}
};
onKeyPress = (event) => {
const newValue = this.eventToAttrValue(event);
if (this.props.type !== AttributeTypes.color) {
if (event.charCode === 13 && newValue !== this.props.value) {
this.props.onFullChange(newValue);
}
this.setState({value: newValue});
}
};
onFocus = () => {
if (this.props.type === AttributeTypes.color) {
this.setState({picking: true});
}
};
onBlur = (event) => {
const newValue = this.eventToAttrValue(event);
const {type, value, noTriggerOnBlur} = this.props;
if (type !== AttributeTypes.color) {
if (!noTriggerOnBlur && newValue !== value) {
this.props.onFullChange(newValue);
}
this.setState({value: newValue});
}
};
getContent = () => {
const {type, values, readonly} = this.props;
const {value, picking} = this.state;
if (values && values.length > 0) {
// enum case
return (
<Select disabled={readonly} native value={value} onChange={this.onChange}>
{values.map(option => (<option key={option}>{option}</option>))}
</Select>);
}
let content;
switch (type) {
case AttributeTypes.boolean:
content = (<FormControlLabel
disabled={readonly}
control={<Switch checked={value} onChange={this.onChange}/>}
/>);
break;
case AttributeTypes.string:
content = (<Input
disabled={readonly}
value={value}
onChange={this.onChange}
onKeyPress={this.onKeyPress}
onBlur={this.onBlur}
/>);
break;
case AttributeTypes.number:
content = (<Input
disabled={readonly}
type="number"
value={value}
onChange={this.onChange}
onKeyPress={this.onKeyPress}
onBlur={this.onBlur}
/>);
break;
case AttributeTypes.color:
content = [];
content.push(<Input
key="input"
value={this.state.value}
endAdornment={
<InputAdornment position="end">
<IconButton onClick={() => {
this.setState({picking: !picking});
}}
>
{picking ? <InvertColorsOff/> : <InvertColors nativeColor={value}/>}
</IconButton>
</InputAdornment>
}
/>);
if (picking) {
content.push(<GithubPicker
key="picker"
color={this.state.value}
onChange={this.onColorChange}
/>);
}
break;
default:
content = null;
}
return content;
};
eventToAttrValue = (event) => {
const {type, invalidChars} = this.props;
switch (type) {
case AttributeTypes.boolean:
return event.target.checked;
case AttributeTypes.string:
if (invalidChars instanceof RegExp) {
return event.target.value.replace(invalidChars, '');
}
return event.target.value;
case AttributeTypes.asset:
return event.target.value;
case AttributeTypes.number:
return Number(event.target.value);
default:
return null;
}
};
render() {
const content = this.getContent();
return (
<FormControl
style={this.props.style || {}}
fullWidth={this.props.fullWidth}
onBlur={this.onBlur}
onFocus={this.onFocus}
>
<FormLabel>{this.props.unit ? `${this.props.name} [${this.props.unit}]` : this.props.name}</FormLabel>
{content}
<FormHelperText>{this.props.description}</FormHelperText>
</FormControl>);
}
}
export {default} from './AttributeItem';
\ No newline at end of file
<
import {Component} from 'react';
import PropTypes from 'prop-types';
export default class Territory extends Component {
static propTypes = {
gmeClient: PropTypes.object.isRequired,
onUpdate: PropTypes.func.isRequired,
territory: PropTypes.object,
onlyActualEvents: PropTypes.bool,
reuseTerritory: PropTypes.bool,
};
static defaultProps = {
territory: null,
onlyActualEvents: true,
reuseTerritory: true,
};
componentDidMount() {
const {gmeClient, territory} = this.props;
this.uiId = gmeClient.addUI(null, this.getEventHandler());
if (territory) {
gmeClient.updateTerritory(this.uiId, territory);
}
}
componentWillReceiveProps(nextProps) {
const {gmeClient} = nextProps;
const {territory, reuseTerritory} = this.props;
if (JSON.stringify(territory) !== JSON.stringify(nextProps.territory)) {
if (!reuseTerritory) {
gmeClient.removeUI(this.uiId);
this.uiId = gmeClient.addUI(null, this.getEventHandler());
}
gmeClient.updateTerritory(this.uiId, nextProps.territory || {});
}
}
componentWillUnmount() {
const {gmeClient} = this.props;
gmeClient.removeUI(this.uiId);
}
getEventHandler() {
const {onUpdate, onlyActualEvents, gmeClient} = this.props;
return (events) => {
const load = [];
const update = [];
const unload = [];
const hash = gmeClient.getActiveRootHash();
events.forEach((event) => {
switch (event.etype) {
case 'load':
load.push(event.eid);
break;
case 'update':
update.push(event.eid);
break;
case 'unload':
unload.push(event.eid);