First Import

This commit is contained in:
Dylan Vorster
2016-06-03 11:12:26 +02:00
commit c37809967c
18 changed files with 1267 additions and 0 deletions

186
.gitignore vendored Normal file
View File

@@ -0,0 +1,186 @@
# Created by https://www.gitignore.io/api/net,netbeans,sublimetext,phpstorm,windows,osx,node
#!! ERROR: net is undefined. Use list command to see defined gitignore types !!#
### NetBeans ###
nbproject/private/
build/
nbbuild/
dist/
nbdist/
nbactions.xml
.nb-gradle/
### SublimeText ###
# cache files for sublime text
*.tmlanguage.cache
*.tmPreferences.cache
*.stTheme.cache
# workspace files are user-specific
*.sublime-workspace
# project files should be checked into the repository, unless a significant
# proportion of contributors will probably not be using SublimeText
# *.sublime-project
# sftp configuration file
sftp-config.json
# Package control specific files
Package Control.last-run
Package Control.ca-list
Package Control.ca-bundle
Package Control.system-ca-bundle
Package Control.cache/
Package Control.ca-certs/
bh_unicode_properties.cache
# Sublime-github package stores a github token in this file
# https://packagecontrol.io/packages/sublime-github
GitHub.sublime-settings
### PhpStorm ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
.idea/workspace.xml
.idea/tasks.xml
.idea/dictionaries
.idea/vcs.xml
.idea/jsLibraryMappings.xml
# Sensitive or high-churn files:
.idea/dataSources.ids
.idea/dataSources.xml
.idea/dataSources.local.xml
.idea/sqlDataSources.xml
.idea/dynamic.xml
.idea/uiDesigner.xml
# Gradle:
.idea/gradle.xml
.idea/libraries
# Mongo Explorer plugin:
.idea/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
### PhpStorm Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
### Windows ###
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
### OSX ###
*.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Node ###
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
jspm_packages
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history

View File

@@ -0,0 +1,7 @@
auxiliary.org-netbeans-modules-css-prep.sass_2e_compiler_2e_options=
auxiliary.org-netbeans-modules-css-prep.sass_2e_configured=true
auxiliary.org-netbeans-modules-css-prep.sass_2e_enabled=false
auxiliary.org-netbeans-modules-css-prep.sass_2e_mappings=/scss:/css
files.encoding=UTF-8
site.root.folder=
source.folder=

9
nbproject/project.xml Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://www.netbeans.org/ns/project/1">
<type>org.netbeans.modules.web.clientproject</type>
<configuration>
<data xmlns="http://www.netbeans.org/ns/clientside-project/1">
<name>storm-react-flow-2</name>
</data>
</configuration>
</project>

26
package.json Normal file
View File

@@ -0,0 +1,26 @@
{
"name": "storm-react-flow-2",
"version": "1.0.0",
"keywords": [
"util",
"functional",
"server",
"client",
"browser"
],
"main": "src/main.js",
"author": "dylanvorster",
"contributors": [],
"dependencies": {
"color": "^0.11.1",
"react": "^15.1.0"
},
"devDependencies": {
"css-loader": "^0.23.1",
"node-sass": "^3.7.0",
"react-dom": "^15.1.0",
"sass-loader": "^3.2.0",
"style-loader": "^0.13.1",
"webpack": "^1.13.1"
}
}

181
src/Engine.js Normal file
View File

@@ -0,0 +1,181 @@
var _ = require("lodash");
/**
* @author Dylan Vorster
*/
module.exports = function(){
return {
state:{
links:{},
nodes:{},
factories: {},
canvas: null,
offsetX:0,
offsetY:0,
zoom: 100,
listeners:{},
selectedLink: null
},
update: function(){
this.fireEvent({type:'repaint'});
},
getRelativeMousePoint: function(event){
return this.getRelativePoint((event.pageX/(this.state.zoom/100.0))-this.state.offsetX,(event.pageY/(this.state.zoom/100.0))-this.state.offsetY);
},
getRelativePoint: function(x,y){
var canvasRect = this.state.canvas.getBoundingClientRect();
return {x: x-canvasRect.left,y:y-canvasRect.top};
},
fireEvent: function(event){
_.forEach(this.state.listeners,function(listener){
listener(event);
});
},
removeListener: function(id){
delete this.state.listeners[id];
},
registerListener: function(cb){
var id = this.UID();
this.state.listeners[id] = cb;
return id;
},
setZoom: function(zoom){
this.state.zoom = zoom;
},
setOffset: function(x,y){
this.state.offsetX = x;
this.state.offsetY = y;
},
loadModel: function(model){
this.state.links = {};
this.state.node = {};
model.nodes.forEach(function(node){
this.addNode(node);
}.bind(this));
model.links.forEach(function(link){
this.addLink(link);
}.bind(this));
},
updateNode: function(node){
//find the links and move those as well
},
UID: function(){
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
},
getNodePortElement: function(node,port){
return this.state.canvas.querySelector('.port[data-name="'+port+'"][data-nodeid="'+node.id+'"]')
},
getAllConnectedPorts: function(node){
return this.state.canvas.querySelectorAll('.port[data-nodeid="'+node.id+'"]');
},
getNodeLinks: function(node){
return _.values(_.filter(this.state.links,function(link,index){
return link.source == node.id || link.target == node.id;
}));
},
removeLink: function(link){
delete this.state.links[link.id];
this.update();
},
removeNode: function(node){
//remove the links
var links = this.getNodeLinks(node);
links.forEach(function(link){
this.removeLink(link);
}.bind(this));
//remove the node
delete this.state.nodes[node.id];
this.update();
},
getPortCenter: function(node,port){
var sourceElement = this.getNodePortElement(node,port);
var sourceRect = sourceElement.getBoundingClientRect();
var rel = this.getRelativePoint(sourceRect.left,sourceRect.top);
return {
x: (sourceElement.offsetWidth/2) + rel.x,
y: (sourceElement.offsetHeight/2) + rel.y
};
},
setSelectedLink: function(link){
this.state.selectedLink = link;
},
addLink: function(link){
var DefaultLink = {
id: this.UID(),
source: null,
sourcePort: null,
target: null,
targetPort: null,
points: []
};
var FinalLink = _.merge(DefaultLink,link);
this.state.links[FinalLink.id] = FinalLink;
return FinalLink;
},
addNode: function(node){
var DefaultNode = {
id: this.UID(),
type: 'default',
data:{}
};
var FinalNode = _.merge(DefaultNode,node);
this.state.nodes[FinalNode.id] = FinalNode;
},
getLink: function(id){
return this.state.links[id];
},
getNode: function(id){
return this.state.nodes[id];
},
getNodeFactory: function(type){
if(this.state.factories[type] === undefined){
throw "Cannot find node factory for: "+type;
}
return this.state.factories[type];
},
registerNodeFactory: function(factory){
var DefaultFactory = {
type: "factoty",
generateModel: function(model){
return null;
}
};
var FinalModel = _.merge(DefaultFactory,factory);
this.state.factories[FinalModel.type] = FinalModel;
}
};
};

3
src/main.js Normal file
View File

@@ -0,0 +1,3 @@
module.exports = {
};

123
src/sass.scss Normal file
View File

@@ -0,0 +1,123 @@
.storm-flow-canvas{
top:0px;
left:0px;
bottom:0;
right:0;
position: absolute;
flex-grow: 1;
display: flex;
overflow: hidden;
svg{
width: 100%;
height: 100%;
}
.node-view{
top:0;
left:0;
right:0;
bottom:0;
position: absolute;
pointer-events: none;
}
.node{
position: absolute;
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Chrome/Safari/Opera */
user-select: none;
cursor: move;
pointer-events: all;
}
@keyframes dash {
from {
stroke-dashoffset: 24;
}
to {
stroke-dashoffset: 0;
}
}
path{
fill:none;
stroke:black;
pointer-events:all;
&.selected{
stroke: rgb(0,192,255) !important;
stroke-dasharray: 10,2;
animation: dash 1s linear infinite;
}
}
.port{
width: 15px;
height: 15px;
background: rgba(white,0.1);
&:hover,&.selected{
background: rgb(192,255,0);
}
}
.basic-node{
background-color: rgb(30,30,30);
border-radius: 5px;
box-shadow: 0 0 10px rgba(black,0.5);
font-family:Arial;
color: white;
border: solid 1px black;
overflow: hidden;
font-size: 11px;
.title{
/* background-image: linear-gradient(rgba(black,0.1),rgba(black,0.2));*/
background: rgba(black,0.3);
display: flex;
>*{
align-self: center;
}
.fa{
padding: 5px;
opacity: 0.2;
cursor: pointer;
&:hover{
opacity: 1.0;
}
}
.name{
flex-grow: 1;
padding: 5px 5px;
}
}
.ports{
display: flex;
background-image: linear-gradient(rgba(black,0.1),rgba(black,0.2));
.in, .out{
flex-grow: 1;
display: flex;
flex-direction: column;
}
.in-port,.out-port{
display: flex;
margin-top: 1px;
>*{
align-self: center;
}
.name{
flex-grow:1;
padding: 0 5px;
}
}
.out-port{
.name{
text-align: right;
}
}
}
}
}

View File

@@ -0,0 +1,48 @@
var React = require("react");
var Port = require("./PortWidget");
/**
* @author Dylan Vorster
*/
module.exports = React.createClass({
displayName: "BasicNodeWidget",
getInitialState: function () {
return {
};
},
getDefaultProps: function () {
return {
name: "Node",
node: null,
inPorts:[],
outPorts: [],
color: 'rgb(50,50,50)',
removeAction: function(){
console.log("remove node");
}
};
},
render: function () {
return (
React.DOM.div({className:'basic-node', style: {background:this.props.color }},
React.DOM.div({className:'title'},
React.DOM.div({className:'name'},this.props.name),
React.DOM.div({className:'fa fa-close',onClick: this.props.removeAction})
),
React.DOM.div({className:'ports'},
React.DOM.div({className:'in'},this.props.inPorts.map(function(port){
return React.DOM.div({className:'in-port'},
React.createElement(Port,{name:port,node:this.props.node}),
React.DOM.div({className:'name'},port)
);
}.bind(this))),
React.DOM.div({className:'out'},this.props.outPorts.map(function(port){
return React.DOM.div({className:'out-port'},
React.DOM.div({className:'name'},port),
React.createElement(Port,{name:port,node:this.props.node})
);
}.bind(this)))
)
)
);
}
});

196
src/widgets/CanvasWidget.js Normal file
View File

@@ -0,0 +1,196 @@
var React = require("react");
var SVGWidget = require("./SVGWidget");
var NodeView = require("./NodeViewWidget");
var _ = require("lodash");
/**
* @author Dylan Vorster
*/
module.exports = React.createClass({
displayName: "CanvasWidget",
getInitialState: function () {
return {
selectedPointID: null,
selectedLink: null,
selectedModel: null,
initialX: null,
initialY: null,
initialObjectX: null,
initialObjectY: null,
listenerID: null
};
},
getDefaultProps: function () {
return {
engine: null
};
},
componentWillUnmount: function(){
this.props.engine.removeListener(this.state.listenerID);
},
componentDidMount: function(){
this.props.engine.state.canvas = this.refs.canvas;
var listenerID = this.props.engine.registerListener(function(event){
if(event.type === 'repaint'){
this.forceUpdate();
}
}.bind(this));
this.setState({listenerID: listenerID});
setTimeout(function(){
//check for any links that dont have points
_.forEach(this.props.engine.state.links,function(link){
if(link.points.length === 0){
link.points.push(this.props.engine.getPortCenter(this.props.engine.getNode(link.source),link.sourcePort));
link.points.push(this.props.engine.getPortCenter(this.props.engine.getNode(link.target),link.targetPort));
this.forceUpdate();
}
}.bind(this));
}.bind(this),10);
//add a keybaord listener
window.addEventListener('keydown',function(){
if(this.props.engine.state.selectedLink){
this.props.engine.removeLink(this.props.engine.state.selectedLink);
}
}.bind(this));
window.focus();
},
render: function () {
return (
React.DOM.div({
style:{
zoom: this.props.engine.state.zoom+"%",
},
ref:'canvas',
className:'storm-flow-canvas',
onWheel: function(event){
this.props.engine.setZoom(this.props.engine.state.zoom+(event.deltaY/60));
this.forceUpdate();
}.bind(this),
onMouseMove: function(event){
//move the node
if(this.state.selectedModel){
this.state.selectedModel.x = this.state.initialObjectX+((event.pageX-this.state.initialX)/(this.props.engine.state.zoom/100));
this.state.selectedModel.y = this.state.initialObjectY+((event.pageY-this.state.initialY)/(this.props.engine.state.zoom/100));
this.forceUpdate();
}
//move the point
else if(this.state.selectedPointID){
var point = _.find(this.state.selectedLink.points,{id:this.state.selectedPointID});
var rel = this.props.engine.getRelativeMousePoint(event);
point.x = rel.x;
point.y = rel.y;
this.forceUpdate();
}
//move the canvas
else if(this.state.initialObjectX !== null){
this.props.engine.setOffset(
this.state.initialObjectX+((event.pageX-this.state.initialX)/(this.props.engine.state.zoom/100)),
this.state.initialObjectY+((event.pageY-this.state.initialY)/(this.props.engine.state.zoom/100))
);
this.forceUpdate();
}
}.bind(this),
onMouseDown: function(event){
//look for a port
var element = event.target.closest('.port[data-name]');
if(element){
var nodeElement = event.target.closest('.node[data-nodeid]');
var rel = this.props.engine.getRelativeMousePoint(event);
var id = this.props.engine.UID();
var FinalLink = this.props.engine.addLink({
source: nodeElement.dataset.nodeid,
sourcePort: element.dataset.name,
points:[{x:0,y:0},{x:rel.x,y:rel.y,id: id}]
});
this.setState({
selectedPointID: id,
selectedLink: FinalLink
});
return;
}
//look for a point
element = event.target.closest('.point[data-id]');
if(element){
//chrome fix o_O
if(element.dataset === undefined){
element.dataset = {
id:element.getAttribute('data-id'),
linkid: element.getAttribute('data-linkid')
};
}
this.setState({
selectedPointID: element.dataset.id,
selectedLink: this.props.engine.getLink(element.dataset.linkid)
});
return;
}
//look for an element
element = event.target.closest('.node[data-nodeid]');
if(element){
var model = this.props.engine.getNode(element.dataset['nodeid']);
this.setState({
selectedModel: model,
initialX: event.pageX,
initialY: event.pageY,
initialObjectX: model.x,
initialObjectY: model.y
});
return;
}
//probably just the canvas
this.setState({
initialX: event.pageX,
initialY: event.pageY,
initialObjectX: this.props.engine.state.offsetX,
initialObjectY: this.props.engine.state.offsetY
});
}.bind(this),
onMouseUp: function(event){
if(this.state.selectedPointID){
var element = event.target.closest('.port[data-name]');
if(element){
var nodeElement = event.target.closest('.node[data-nodeid]');
this.state.selectedLink.target = nodeElement.dataset.nodeid;
this.state.selectedLink.targetPort = element.dataset.name;
}
}
this.setState({
selectedLink: null,
selectedPort: null,
selectedPointID: null,
selectedModel: null,
initialX: null,
initialY: null,
initialObjectX: null,
initialObjectY: null
});
}.bind(this),
},
React.createElement(SVGWidget,{newPoint: function(link,pointID){
this.setState({
selectedPointID: pointID,
selectedLink: link
});;
}.bind(this),engine: this.props.engine}),
React.createElement(NodeView,{engine: this.props.engine})
)
);
}
});

172
src/widgets/LinkWidget.js Normal file
View File

@@ -0,0 +1,172 @@
var React = require("react");
var _ = require("lodash");
/**
* @author Dylan Vorster
*/
module.exports = React.createClass({
displayName: "LinkWidget",
getInitialState: function(){
return {
selected: false,
};
},
getDefaultProps: function () {
return {
width: 3,
link:null,
engine: null,
smooth: false,
newPoint: function(id){
}
};
},
getPoint: function(index){
if(index === 0){
return this.props.link.points[index];
}
if(this.props.link.target !== null && index === this.props.link.points.length-1){
return this.props.link.points[index];
}
return {
x: this.props.link.points[index].x+this.props.engine.state.offsetX,
y: this.props.link.points[index].y+this.props.engine.state.offsetY
};
},
setSelected: function(selected){
this.setState({selected: selected});
if(selected){
this.props.engine.setSelectedLink(selected?this.props.link:null);
}
},
generatePoint: function(pointIndex){
return React.DOM.g(null,
React.DOM.circle({
className:'point',
cx:this.getPoint(pointIndex).x,
cy:this.getPoint(pointIndex).y,
r:5,
fill:'black',
}),
React.DOM.circle({
className:'point',
'data-linkid':this.props.link.id,
'data-id':this.props.link.points[pointIndex].id,
cx:this.getPoint(pointIndex).x,
cy:this.getPoint(pointIndex).y,
r:15,
opacity: 0,
onMouseLeave: function(){
this.setSelected(false);
}.bind(this),
onMouseEnter: function(){
this.setSelected(true);
}.bind(this)
})
);
},
generateLink: function(extraProps){
var Bottom = React.DOM.path(_.merge({
className:this.state.selected?'selected':'',
strokeWidth:this.props.width,
stroke:'black'
},extraProps));
var Top = React.DOM.path(_.merge({
onMouseLeave: function(){
this.setSelected(false);
}.bind(this),
onMouseEnter: function(){
this.setSelected(true);
}.bind(this),
strokeOpacity:0,
strokeWidth: 20,
onContextMenu: function(event){
event.preventDefault();
this.props.engine.removeLink(this.props.link);
}.bind(this),
},extraProps));
return React.DOM.g(null,
Bottom,
Top
);
},
render: function () {
var points = this.props.link.points;
points.forEach(function(point){
if(point.id === undefined){
point.id = this.props.engine.UID();
}
}.bind(this));
var paths = [];
if(points.length === 2){
paths.push(this.generateLink({
onMouseDown: function(event){
var point = this.props.engine.getRelativeMousePoint(event);
point.id = this.props.engine.UID();
this.props.link.points.splice(1,0,point);
this.forceUpdate();
this.props.newPoint(point.id);
}.bind(this),
d:
" M"+this.getPoint(0).x+" "+this.getPoint(0).y
+" C"+(this.getPoint(0).x+50)+" "+this.getPoint(0).y
+" " +(this.getPoint(1).x-50)+" "+this.getPoint(1).y
+" " +this.getPoint(1).x+" "+this.getPoint(1).y
}));
if(this.props.link.target === null){
paths.push(this.generatePoint(1));
}
}else{
var ds = [];
if(this.props.smooth){
ds.push(" M"+this.getPoint(0).x+" "+this.getPoint(0).y+" C "+(this.getPoint(0).x+50)+" "+this.getPoint(0).y+" "+this.getPoint(1).x+" "+this.getPoint(1).y+" "+this.getPoint(1).x+" "+this.getPoint(1).y);
for(var i = 1;i < points.length-2;i++){
ds.push(" M "+this.getPoint(i).x+" "+this.getPoint(i).y+" L "+this.getPoint(i+1).x+" "+this.getPoint(i+1).y);
}
ds.push(" M"+this.getPoint(i).x+" "+this.getPoint(i).y+" C "+this.getPoint(i).x+" "+this.getPoint(i).y+" "+(this.getPoint(i+1).x-50)+" "+this.getPoint(i+1).y+" "+this.getPoint(i+1).x+" "+this.getPoint(i+1).y);
}else{
var ds = [];
for(var i = 0;i < points.length-1;i++){
ds.push(" M "+this.getPoint(i).x+" "+this.getPoint(i).y+" L "+this.getPoint(i+1).x+" "+this.getPoint(i+1).y);
}
}
paths = ds.map(function(data,index){
return this.generateLink({
'data-link':this.props.link.id,
'data-point':index,
onMouseDown: function(event){
var point = this.props.engine.getRelativeMousePoint(event);
point.id = this.props.engine.UID();
this.props.link.points.splice(index+1,0,point);
this.forceUpdate();
this.props.newPoint(point.id);
}.bind(this),
d:data
});
}.bind(this));
//render the circles
for(var i = 1;i < points.length-1;i++){
paths.push(this.generatePoint(i));
}
if(this.props.link.target === null){
paths.push(this.generatePoint(points.length-1));
}
}
return (
React.DOM.g(null, paths)
);
}
});

View File

@@ -0,0 +1,32 @@
var React = require("react");
var _ = require("lodash");
var Node = require("./NodeWidget");
/**
* @author Dylan Vorster
*/
module.exports = React.createClass({
displayName: "NodeViewWidget",
getInitialState: function () {
return {
};
},
getDefaultProps: function () {
return {
engine: null
};
},
render: function () {
return (
React.DOM.div({className:'node-view'},
_.map(this.props.engine.state.nodes,function(node){
return(
React.createElement(Node,{node: node,engine: this.props.engine},
this.props.engine.getNodeFactory(node.type).generateModel(node))
);
}.bind(this))
)
);
}
});

31
src/widgets/NodeWidget.js Normal file
View File

@@ -0,0 +1,31 @@
var React = require("react");
/**
* @author Dylan Vorster
*/
module.exports = React.createClass({
displayName: "NodeWidget",
getInitialState: function () {
return {
mouseDown: false
};
},
getDefaultProps: function () {
return {
node: null,
engine: null,
};
},
componentDidMount: function(){
},
render: function () {
return (
React.DOM.div({
'data-nodeid': this.props.node.id,
className:'node',
style:{top:this.props.node.y+this.props.engine.state.offsetY,left: this.props.node.x+this.props.engine.state.offsetX}},
this.props.children
)
);
}
});

33
src/widgets/PortWidget.js Normal file
View File

@@ -0,0 +1,33 @@
var React = require("react");
/**
* @author Dylan Vorster
*/
module.exports = React.createClass({
displayName: "displayName",
getInitialState: function () {
return {
selected: false,
};
},
getDefaultProps: function () {
return {
name: "unknown",
element: null
};
},
render: function () {
return (
React.DOM.div({
onMouseEnter: function(){
this.setState({selected: true});
}.bind(this),
onMouseLeave: function(){
this.setState({selected: false});
}.bind(this),
className:'port'+(this.state.selected?' selected':''),
'data-name':this.props.name,
'data-nodeid': this.props.node.id
})
);
}
});

43
src/widgets/SVGWidget.js Normal file
View File

@@ -0,0 +1,43 @@
var React = require("react");
var LinkWidget = require("./LinkWidget");
var _ = require("lodash");
/**
* @author Dylan Vorster
*/
module.exports = React.createClass({
displayName: "SVG Widget",
getInitialState: function () {
return {
};
},
getDefaultProps: function () {
return {
engine: null,
newPoint: function(link,pointID){
}
};
},
render: function () {
return (
React.DOM.svg({},
_.map(this.props.engine.state.links,function(link){
if(link.points.length < 2){
return;
}else{
if(link.source !== null){
link.points[0] = this.props.engine.getPortCenter(this.props.engine.getNode(link.source),link.sourcePort);
}
if(link.target !== null){
link.points[link.points.length-1] = this.props.engine.getPortCenter(this.props.engine.getNode(link.target),link.targetPort);
}
}
return React.createElement(LinkWidget,{newPoint: function(pointID){
this.props.newPoint(link,pointID);
}.bind(this),link: link,engine: this.props.engine});
}.bind(this))
)
);
}
});

13
tests/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<title>STORM Flow Test</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="./bundle.js"></script>
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
</head>
<body>
</body>
</html>

128
tests/test.js Normal file
View File

@@ -0,0 +1,128 @@
var React = require("react");
var ReactDOM = require("react-dom");
var Canvas = require("../src/widgets/CanvasWidget");
var BasicNodeWidget = require("../src/widgets/BasicNodeWidget");
require("./test.scss");
window.onload = function () {
var Engine = require("../src/Engine")();
var Model = {
links:[
{
id: 1,
source: 1,
sourcePort: 'out',
target: 2,
targetPort: 'in',
},
{
id: 2,
source: 1,
sourcePort: 'out',
target: 3,
targetPort: 'in'
},
{
id: 3,
source: 2,
sourcePort: 'out',
target: 4,
targetPort: 'in'
},
{
id: 4,
source: 4,
sourcePort: 'out',
target: 5,
targetPort: 'in2'
},
{
id: 5,
source: 2,
sourcePort: 'out',
target: 5,
targetPort: 'in'
}
],
nodes:[
{
id:1,
type: 'action',
data: {
name: "Create User",
outVariables: ['out']
},
x:50,
y:50
},
{
id:2,
type: 'action',
data: {
name: "Add Card to User",
inVariables: ['in','in 2'],
outVariables: ['out']
},
x:250,
y:50
},
{
id:3,
type: 'action',
data: {
color: 'rgb(0,192,255)',
name: "Remove User",
inVariables: ['in']
},
x:250,
y:150
},
{
id:4,
type: 'action',
data: {
color: 'rgb(0,192,255)',
name: "Remove User",
inVariables: ['in'],
outVariables: ['out']
},
x:500,
y:150
},
{
id:5,
type: 'action',
data: {
color: 'rgb(192,255,0)',
name: "Complex Action 2",
inVariables: ['in','in2','in3']
},
x:800,
y:100
},
]
};
Engine.registerNodeFactory({
type:'action',
generateModel: function(model){
return React.createElement(BasicNodeWidget,{
removeAction: function(){
Engine.removeNode(model);
},
color: model.data.color,
node: model,
name: model.data.name,
inPorts: model.data.inVariables,
outPorts: model.data.outVariables
});
}
});
Engine.loadModel(Model);
ReactDOM.render(React.createElement(Canvas,{engine: Engine}), document.body);
};

12
tests/test.scss Normal file
View File

@@ -0,0 +1,12 @@
*{
margin: 0;
padding: 0;
}
html,body{
width: 100%;
height: 100%;
background: rgb(60,60,60);
/*display: flex;*/
}
@import "../src/sass.scss";

24
webpack.config.js Normal file
View File

@@ -0,0 +1,24 @@
var webpack = require("webpack");
var path = require("path");
module.exports = {
watch: true,
entry: "./tests/test.js",
output: {
path: path.resolve(__dirname, "tests"),
filename: "bundle.js"
},
plugins: [
new webpack.optimize.DedupePlugin()
],
module: {
loaders: [
{
test: /\.scss$/,
loaders: ["style", "css", "sass"]
}
]
},
devServer: {
contentBase: "./tests",
}
};