Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions demos/demo6/Demo6.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React, { Component } from 'react';
import * as RJD from '../../src/main';
import { IconNodeModel } from './IconWidget/IconNodeModel';
import { IconWidgetFactory } from './IconWidget/IconWidgetFactory';
import { IconNodeFactory, IconPortFactory } from './IconWidget/IconInstanceFactories';
import '../test.scss';

export default class Demo6 extends Component {
constructor(props) {
super(props);

// Setup the diagram engine
this.engine = new RJD.DiagramEngine();
this.engine.registerNodeFactory(new RJD.DefaultNodeFactory());
this.engine.registerLinkFactory(new RJD.DefaultLinkFactory());
this.engine.registerNodeFactory(new IconWidgetFactory());

// Setup the diagram model
this.model = new RJD.DiagramModel();
}

componentDidMount() {
setTimeout(() => {
this.testSerialization();
}, 1000);
}

createNode(options) {
const { name, color, x, y } = options;
var node = new RJD.DefaultNodeModel(name, color);
node.x = x;
node.y = y;
return node;
}

createPort(node, options) {
const { isInput, id, name } = options;
return node.addPort(new RJD.DefaultPortModel(isInput, id, name));
}

linkNodes(port1, port2) {
const link = new RJD.LinkModel();
link.setSourcePort(port1);
link.setTargetPort(port2);
return link;
}

testSerialization() {
const { engine, model } = this;

// We need this to help the system know what models to create form the JSON
engine.registerInstanceFactory(new RJD.DefaultNodeInstanceFactory());
engine.registerInstanceFactory(new RJD.DefaultPortInstanceFactory());
engine.registerInstanceFactory(new RJD.LinkInstanceFactory());
engine.registerInstanceFactory(new IconNodeFactory());
engine.registerInstanceFactory(new IconPortFactory());

// Serialize the model
const str = JSON.stringify(model.serializeDiagram());

// Deserialize the model
const model2 = new RJD.DiagramModel();
model2.deSerializeDiagram(JSON.parse(str),engine);
engine.setDiagramModel(model2);
}

render() {
const { engine, model } = this;

// Create first node and port
const node1 = this.createNode({
name: 'Node 1',
color: 'rgb(0, 192, 255)',
x: 100,
y: 150
});
const port1 = this.createPort(node1, {
isInput: false,
id: 'out-1',
name: 'Out'
});

// Create the icon node
const iconNode = new IconNodeModel('camera'); // ability to set default icon
iconNode.x = 400;
iconNode.y = 100;

// Create second node and port
const node2 = this.createNode({
name: 'Node 2',
color: 'red',
x: 800,
y: 150
});
const port2 = this.createPort(node2, {
isInput: true,
id: 'in-1',
name: 'In'
});

// Add the nodes and link to the model
model.addNode(node1);
model.addNode(iconNode);
model.addNode(node2);
model.addLink(this.linkNodes(port1, iconNode.ports['left']));
model.addLink(this.linkNodes(iconNode.ports['right'], port2));

// Load the model into the diagram engine
engine.setDiagramModel(model);

// Render the canvas
return <RJD.DiagramWidget diagramEngine={engine} />;
}
}
23 changes: 23 additions & 0 deletions demos/demo6/IconWidget/IconInstanceFactories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as SRD from '../../../src/main';
import { IconNodeModel } from './IconNodeModel';
import { IconPortModel } from './IconPortModel';

export class IconNodeFactory extends SRD.AbstractInstanceFactory {
constructor() {
super('IconNodeModel');
}

getInstance() {
return new IconNodeModel();
}
}

export class IconPortFactory extends SRD.AbstractInstanceFactory {
constructor() {
super('IconPortModel');
}

getInstance() {
return new IconPortModel();
}
}
21 changes: 21 additions & 0 deletions demos/demo6/IconWidget/IconNodeModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as SRD from '../../../src/main';
import { IconPortModel } from './IconPortModel';
export class IconNodeModel extends SRD.NodeModel {
constructor(iconName = 'camera') {
super('icon');
this.iconName = iconName;
this.addPort(new IconPortModel('left'));
this.addPort(new IconPortModel('right'));
}

serialize() {
return _.merge(super.serialize(), {
iconName: this.iconName,
});
}

deSerialize(data) {
super.deSerialize(data);
this.iconName = data.iconName;
}
}
125 changes: 125 additions & 0 deletions demos/demo6/IconWidget/IconNodeWidget.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import React, { Component, OnChanges } from 'react';
import { PortWidget } from '../../../src/main';

const ChangeIconForm = ({ icon, closeForm, changeIcon }) => (
<form style={styles.changeForm} onSubmit={closeForm}>
<div>Change Icon (font-awesome class)</div><br/>
fa-<input type="text" value={icon} onChange={changeIcon} />
<button type="submit">Change</button>
<button type="submit">Cancel</button><br/><br/>
<div>Preview: <i className={`fa fa-${icon}`}></i></div><br/><br/>
<small>
<a style={{ color: '#efefef' }}
href="http://fontawesome.io/icons/"
target="_blank">See all options on fontawesome.io</a>
</small>
</form>
);

export class IconNodeWidget extends Component {
constructor(props) {
super(props);
this.state = {
iconName: '',
showChangeForm: false,
};
this.openChangeForm = this.openChangeForm.bind(this);
this.closeChangeForm = this.closeChangeForm.bind(this);
this.changeIcon = this.changeIcon.bind(this);
}

componentWillReceiveProps(props) {
if (!this.state.iconName) {
this.setState({ iconName: props.node.iconName });
}
}

openChangeForm() {
this.setState({ showChangeForm: true });
}

closeChangeForm() {
this.setState({ showChangeForm: false });
}

changeIcon(e) {
this.setState({ iconName: e.target.value });
}

render() {
return (
<div style={styles.icon} onClick={this.openChangeForm}>
{!this.state.showChangeForm &&
<i style={styles.faIcon} className={`fa fa-${this.state.iconName || 'file'} fa-2x`}></i>
}
{this.state.showChangeForm &&
<i style={styles.faIcon.spin} className={'fa fa-cog fa-spin fa-2x'}></i>
}
<div style={{ ...styles.port, ...styles.port.left }}>
<PortWidget name="left" node={this.props.node} />
</div>
<div style={{ ...styles.port, ...styles.port.right }}>
<PortWidget name="right" node={this.props.node} />
</div>
{this.state.showChangeForm &&
<ChangeIconForm
icon={this.state.iconName}
closeForm={this.closeChangeForm}
changeIcon={this.changeIcon}
/>
}

</div>
);
}
}

// Beginnings of some css vars
const base = 8; // 8px grid for design

// styles here for now
const styles = {
icon: {
position: 'relative',
width: base * 8,
height: base * 8,
backgroundColor: 'rgba(0,0,0,0.2)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
faIcon: {
color: 'white',
spin: {
color: 'black',
}
},
port: {
position: 'absolute',
top: '50%',
borderRadius: '50%',
backgroundColor: 'white',
left: {
left: 0,
transform: 'translate(-50%, -50%)',
},
right: {
right: 0,
transform: 'translate(50%, -50%)',
},
},
changeForm: {
fontFamily: 'Arial',
position: 'absolute',
top: '100%',
left: '50%',
transform: 'translate(-50%, 20%)',
background: 'rgba(0,0,0,0.2)',
color: 'white',
padding: base * 2,
width: 300,
zIndex: 50,
}
};

export var IconNodeWidgetFactory = React.createFactory(IconNodeWidget);
20 changes: 20 additions & 0 deletions demos/demo6/IconWidget/IconPortModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as SRD from '../../../src/main';
import * as _ from 'lodash';

export class IconPortModel extends SRD.PortModel {
constructor(pos = 'left') {
super(pos);
this.position = pos;
}

serialize() {
return _.merge(super.serialize(), {
position: this.position,
});
}

deSerialize(data) {
super.deSerialize(data);
this.position = data.position;
}
}
12 changes: 12 additions & 0 deletions demos/demo6/IconWidget/IconWidgetFactory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as SRD from '../../../src/main';
import { IconNodeWidgetFactory } from './IconNodeWidget';

export class IconWidgetFactory extends SRD.NodeWidgetFactory {
constructor() {
super('icon');
}

generateReactWidget(diagramEngine, node) {
return IconNodeWidgetFactory({ node });
}
}
18 changes: 18 additions & 0 deletions demos/demo6/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## Demo 6
This demo shows an example of a custom "Icon" node type. In a graph diagram situation there may be a scenario where you want to represent a piece of data with an icon (ie, a phone number, a patient, a hospital).

### Features
* Allows user to change icon during navigation of the graph
* Allows developer to generate the correct icon classes based on data needs
* Has a fallback icon in case of no customization
* Uses fontawesome, so no extra work needs to be done to match classes to icons

### Changes
* In order to implement this feature, the keydown listener in the DiagramWidget needed to be altered. It was listening for every key event all the time. This has been fixed to stop acting on the keydown events when the user is in an input box (ie, changing the class for the icon)

### Production considerations / potential improvements
* If giving the user the ability to change the icon on the fly is something actually useful (this is debatable), then there needs to be more error handling and fallback, as well as more instruction to the user as to what types of classes they can type in. An alternate solution would be an autocomplete or dropdown.
* Removing the dependence on fontawesome and instead giving the developer the ability to integrate any other icon may be useful. If this were the task at hand, the better approach would be to create a few different node types (custom icon, image/jpg, fontawesome-specific, etc).
* The boilerplate in Demo6.js is unnecessarily large. This could be abstracted away as is done in demo4.
* Much of the styling is done in the React component. This needs to be broken out and put into scss where it makes sense.
* Not much styling has been done to the "change icon" form.
13 changes: 13 additions & 0 deletions demos/demo6/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<title>React JS Diagrams Demo 6</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="./bundle.js"></script>
<link rel="stylesheet" type="text/css" href="http://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
</head>
<body>
<div id='root'></div>
</body>
</html>
7 changes: 7 additions & 0 deletions demos/demo6/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Demo6 from './Demo6';

window.onload = () => {
ReactDOM.render(<Demo6 />, document.getElementById('root'));
};
1 change: 1 addition & 0 deletions demos/demos.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export class Demos extends React.Component {
<div className='demo-link'><a onClick={() => window.location.assign('/demos/demo3')}>Demo 3</a></div>
<div className='demo-link'><a onClick={() => window.location.assign('/demos/demo4')}>Demo 4</a></div>
<div className='demo-link'><a onClick={() => window.location.assign('/demos/demo5')}>Demo 5</a></div>
<div className='demo-link'><a onClick={() => window.location.assign('/demos/demo6')}>Demo 6</a></div>
</div>
);
}
Expand Down
5 changes: 5 additions & 0 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ config[1].entry = {
...hotEntries,
'./demos/demo5/index.js'
]
,
demo6: [
...hotEntries,
'./demos/demo6/index.js'
]
};
config[1].output.filename = '[name].js';
config[1].output.path = path.join(__dirname, 'demos');
Expand Down
Loading