housekeeping
83
README.md
@ -6,11 +6,15 @@ A super simple, no-nonsense diagramming library written in React that just works
|
||||
[](https://npmjs.org/package/storm-react-diagrams)
|
||||
[](https://npmjs.org/package/storm-react-diagrams)
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Introduction
|
||||
|
||||
A no-nonsense diagramming library written entirely in React with the help of Lodash. It aims to be:
|
||||
A no-nonsense diagramming library written entirely in React with the help of Lodash, and a single polyfill. It aims to be:
|
||||
|
||||
* Simple, and void of any fuss/complications when implementing it into your own application
|
||||
* Customizable without having to hack the core (adapters/factories etc..)
|
||||
@ -28,22 +32,17 @@ or
|
||||
yarn add storm-react-diagrams
|
||||
```
|
||||
|
||||
* Its only dependency is Lodash and obviously React so it will install that too.
|
||||
## How to build
|
||||
|
||||
#### How to build
|
||||
Simply run ```webpack``` in the root directory (or ```export NODE_ENV=production && webpack``` if you want a production build) and it will spit out the transpiled code and typescript definitions into the dist directory as a single file. __It will also compile the code for the demos__ .We use webpack for this because TSC cannot compile a single UMD file (TSC can currently only output multiple UMD files).
|
||||
|
||||
Simply run ```webpack``` in the root directory (or ```webpack -p``` if you want a production build) and it will spit out the transpiled code and typescript definitions into the dist directory as a single file. __It will also compile the code for the demos__ .We use webpack for this because TSC cannot compile a single UMD file (TSC can currently only output multiple UMD files).
|
||||
_NOTE:_ We turn off name mangeling in production builds because we require class names to be left intact when serializing.
|
||||
|
||||
#### How to see the examples
|
||||
|
||||
1. checkout the project
|
||||
2. run ```webpack``` in the root
|
||||
3. open up one of the __demos__ folders and load the corresponding index.html file.
|
||||
## Make your own nodes
|
||||
|
||||
To see how to create your own nodes like the one below, take a look at __demo3__:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## How does it work
|
||||
|
||||
@ -62,57 +61,29 @@ a link can be connected to it.
|
||||
|
||||
[Event System](docs/Events.md)
|
||||
|
||||
## DiagramWidget props
|
||||
|
||||
- onLinkStateChanged (link, isConnected)
|
||||
- diagramEngine
|
||||
|
||||
|
||||
## Questions
|
||||
|
||||
#### Why didn’t I render the nodes as SVG's?
|
||||
[Questions](docs/Questions.md)
|
||||
|
||||
Because its vastly better to render nodes as standard HTML so that we can embed input controls and not have
|
||||
to deal with the complexities of trying to get SVG to work like we want it to. I also created this primarily to embed into
|
||||
enterprise applications where the nodes themselves are highly interactive with buttons and other controls that cave when I try to use SVG.
|
||||
## Usage
|
||||
|
||||
#### Why Typescript?
|
||||
__Delete__ removes any selected items
|
||||

|
||||
|
||||
Because it can transpile into any level of ECMA Script, and the library got really complicated, so I ported it to Typescript
|
||||
to accommodate the heavy architectural changes I was starting to make. <3 Type Script
|
||||
__Shift + Mouse Drag__ triggers a multi-selection box
|
||||

|
||||
|
||||
#### Why is there no JSX?
|
||||
__Shift + Mouse Click__ selects the item (items can be multi-selected)
|
||||

|
||||
|
||||
Because most of the library is 95% all logic anyway, and I construct very complex DOM elements with many dynamic properties. JSX
|
||||
Would just get in the way, and I personally hate JSX for a multitude of reasons anyway.
|
||||
__Mouse Drag__ drags the entire diagram
|
||||

|
||||
|
||||
#### How do I make my own elements?
|
||||
__Mouse Wheel__ zooms the diagram in / out
|
||||

|
||||
|
||||
Take a look at the __defaults__ directory, with specific attention to the __DefaultNodeWidget__
|
||||
__Click Link + Drag__ creates a new link point
|
||||

|
||||
|
||||
#### How do I use the library?
|
||||
|
||||
Take a look at the demo folders, they have simple and complex examples of the complete usage.
|
||||
|
||||
## Usage Demo and Guide
|
||||
|
||||
This is a demo of the interaction taken directly from the test folder.
|
||||
|
||||

|
||||
|
||||
#### Key commands
|
||||
|
||||
__del key__ will remove anything selected including links
|
||||
|
||||
__shift and drag__ will trigger a multi selection box
|
||||
|
||||
__shift and select nodes/links/points__ will select multiple nodes
|
||||
|
||||
__drag canvas__ will drag the complete diagram
|
||||
|
||||
__mouse wheel__ will zoom in or out the entire diagram
|
||||
|
||||
__click link and drag__ will create a new link anchor/point that you can then drag around
|
||||
|
||||
__click node-port and drag__ will create a new link that is anchored to the port, allowing you
|
||||
to drag the link to another connecting port
|
||||
__Click Node Port + Drag__ creates a new link
|
||||

|
||||
|
BIN
custom-nodes.png
Before Width: | Height: | Size: 34 KiB |
@ -16,7 +16,7 @@ export interface DiamonNodeWidgetState {
|
||||
export class DiamonNodeWidget extends React.Component<DiamonNodeWidgetProps, DiamonNodeWidgetState> {
|
||||
|
||||
public static defaultProps: DiamonNodeWidgetProps = {
|
||||
size:150,
|
||||
size: 150,
|
||||
node: null
|
||||
};
|
||||
|
||||
@ -34,27 +34,27 @@ export class DiamonNodeWidget extends React.Component<DiamonNodeWidgetProps, Dia
|
||||
<g id="Layer_1">
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<polygon fill="cyan" stroke="#000000" stroke-width="3" stroke-miterlimit="10" points="10,`+(this.props.size/2)+` `+(this.props.size/2)+`,10 `+(this.props.size-10)+`,`+(this.props.size/2)+` `+(this.props.size/2)+`,`+(this.props.size-10)+` "/>
|
||||
<polygon fill="purple" stroke="#000000" stroke-width="3" stroke-miterlimit="10" points="10,`+(this.props.size/2)+` `+(this.props.size/2)+`,10 `+(this.props.size-10)+`,`+(this.props.size/2)+` `+(this.props.size/2)+`,`+(this.props.size-10)+` "/>
|
||||
</g>
|
||||
`}}),
|
||||
|
||||
//left node
|
||||
React.DOM.div({style: {position: 'absolute', zIndex:10,top:this.props.size/2 - 5}},
|
||||
React.DOM.div({style: {position: 'absolute', zIndex:10,top:this.props.size/2 - 8, left: -8}},
|
||||
React.createElement(SRD.PortWidget,{name: 'left', node: this.props.node})
|
||||
),
|
||||
|
||||
//top node
|
||||
React.DOM.div({style: {position: 'absolute', zIndex:10,left:this.props.size/2-8}},
|
||||
React.DOM.div({style: {position: 'absolute', zIndex:10,left:this.props.size/2-8,top:-8}},
|
||||
React.createElement(SRD.PortWidget,{name: 'top', node: this.props.node})
|
||||
),
|
||||
|
||||
//right
|
||||
React.DOM.div({style: {position: 'absolute', zIndex:10,left:this.props.size-10,top:this.props.size/2}},
|
||||
React.DOM.div({style: {position: 'absolute', zIndex:10,left:this.props.size-8,top:this.props.size/2 - 8}},
|
||||
React.createElement(SRD.PortWidget,{name: 'right', node: this.props.node})
|
||||
),
|
||||
|
||||
//bottom
|
||||
React.DOM.div({style: {position: 'absolute', zIndex:10,left :this.props.size/2 - 8,top:this.props.size-10}},
|
||||
React.DOM.div({style: {position: 'absolute', zIndex:10,left :this.props.size/2 - 8,top:this.props.size-8}},
|
||||
React.createElement(SRD.PortWidget,{name: 'bottom', node: this.props.node})
|
||||
),
|
||||
)
|
||||
@ -63,4 +63,3 @@ export class DiamonNodeWidget extends React.Component<DiamonNodeWidgetProps, Dia
|
||||
}
|
||||
|
||||
export var DiamonNodeWidgetFactory = React.createFactory(DiamonNodeWidget);
|
||||
|
||||
|
@ -7,41 +7,41 @@ an event on the model itself.
|
||||
|
||||
All models will fire these events:
|
||||
|
||||
#### lockChanged?(entity: BaseEntity,locked: boolean)
|
||||
lockChanged?(entity: BaseEntity,locked: boolean)
|
||||
|
||||
Fires when the lock state of the entity changes. If an element is locked, it cannot be moved or deletes.
|
||||
|
||||
## All Base models excluding DiagramModel
|
||||
|
||||
|
||||
#### selectionChanged?(item: BaseModel, isSelected:boolean)
|
||||
selectionChanged?(item: BaseModel, isSelected:boolean)
|
||||
|
||||
When the _selected_ property of a model changes
|
||||
|
||||
#### entityRemoved?(item:any)
|
||||
entityRemoved?(item:any)
|
||||
|
||||
When the entity is going to be deleted. The DiagramModel listenes for this event to when to remove the model from itself.
|
||||
When the entity is going to be deleted. The DiagramModel listeners for this event to when to remove the model from itself.
|
||||
|
||||
|
||||
## DiagramModel
|
||||
|
||||
#### nodesUpdated(node: any, isCreated:boolean)
|
||||
nodesUpdated(node: any, isCreated:boolean)
|
||||
|
||||
When nodes are added or removed
|
||||
|
||||
#### linksUpdated(link: any, isCreated:boolean)
|
||||
linksUpdated(link: any, isCreated:boolean)
|
||||
|
||||
when links are added or removed
|
||||
|
||||
#### controlsUpdated() [DEPRECIATED]
|
||||
controlsUpdated() [DEPRECIATED]
|
||||
|
||||
_depreciated, use offsetUpdated and zoomUpdated instead_
|
||||
|
||||
#### offsetUpdated(model: DiagramModel,offsetX: number, offsetY: number)
|
||||
offsetUpdated(model: DiagramModel,offsetX: number, offsetY: number)
|
||||
|
||||
to know when the canvas was translated in any direction
|
||||
|
||||
#### zoomUpdated(model: DiagramModel,zoom: number)
|
||||
zoomUpdated(model: DiagramModel,zoom: number)
|
||||
|
||||
to know when the zoom level of the canvas was updated
|
||||
|
||||
@ -49,19 +49,19 @@ to know when the zoom level of the canvas was updated
|
||||
|
||||
The diagram engine
|
||||
|
||||
#### nodeFactoriesUpdated
|
||||
nodeFactoriesUpdated
|
||||
|
||||
When node factories have been added or removed from the engine
|
||||
|
||||
#### linkFactoriesUpdated
|
||||
linkFactoriesUpdated
|
||||
|
||||
When link factories have been added or removed from the engine
|
||||
|
||||
## LinkModel
|
||||
|
||||
#### sourcePortChanged?(item:LinkModel,target: null|PortModel)
|
||||
sourcePortChanged?(item:LinkModel,target: null|PortModel)
|
||||
|
||||
#### targetPortChanged?(item:LinkModel,target: null|PortModel)
|
||||
targetPortChanged?(item:LinkModel,target: null|PortModel)
|
||||
|
||||
|
||||
# Example of usage
|
||||
|
35
docs/Questions.md
Normal file
@ -0,0 +1,35 @@
|
||||
## Questions
|
||||
|
||||
#### Why didn’t I render the nodes as SVG's?
|
||||
|
||||
Because its vastly better to render nodes as standard HTML so that we can embed input controls and not have
|
||||
to deal with the complexities of trying to get SVG to work like we want it to. I also created this primarily to embed into
|
||||
enterprise applications where the nodes themselves are highly interactive with buttons and other controls that cave when I try to use SVG.
|
||||
|
||||
#### Why Typescript?
|
||||
|
||||
Because it can transpile into any level of ECMA Script, and the library got really complicated, so I ported it to Typescript
|
||||
to accommodate the heavy architectural changes I was starting to make. <3 Type Script
|
||||
|
||||
#### Why is there no JSX?
|
||||
|
||||
Because most of the library is 95% all logic anyway, and I construct very complex DOM elements with many dynamic properties. JSX
|
||||
Would just get in the way, and I personally hate JSX for a multitude of reasons anyway.
|
||||
|
||||
#### But I really want JSX :C
|
||||
|
||||
Changing the internals of this library to be JSX is a pointless endeavor because I built this library in such a way that you never need to hack it to get what you want out of it. If the library is missing something, then I would rather add it as a pluggable feature, or provide some mechanism to allow you to do this.
|
||||
|
||||
Just because this library is NOT JSX, doesn't however mean that you cant use JSX when you want to integrate it into your own system :)
|
||||
|
||||
#### How do I make my own elements?
|
||||
|
||||
Take a look at the __defaults__ directory, with specific attention to the __DefaultNodeWidget__
|
||||
|
||||
That being said, the defaults directory is an EXAMPLE of how you can create your own elements. A number of people want to use the defaults as is, which is cool, but is recommended to create your own base node like the onw you see
|
||||
|
||||
#### How do I use the library?
|
||||
|
||||
Take a look at the demo folders, they have simple and complex examples of the complete usage.
|
||||
|
||||
A good example of a real-world example is Demo 5
|
BIN
images/canvasDrag.gif
Normal file
After Width: | Height: | Size: 94 KiB |
BIN
images/createLink.gif
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
images/createPoint.gif
Normal file
After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
BIN
images/demo3.png
Normal file
After Width: | Height: | Size: 90 KiB |
BIN
images/example1.png
Normal file
After Width: | Height: | Size: 2.5 MiB |
Before Width: | Height: | Size: 870 KiB After Width: | Height: | Size: 870 KiB |
BIN
images/example3.png
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
images/mouseDrag.gif
Normal file
After Width: | Height: | Size: 354 KiB |
BIN
images/mouseWheel.gif
Normal file
After Width: | Height: | Size: 161 KiB |
BIN
images/rjdDelete.gif
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
images/shiftClick.gif
Normal file
After Width: | Height: | Size: 113 KiB |
@ -22,6 +22,7 @@
|
||||
"prepare": "webpack && ./node_modules/node-sass/bin/node-sass --output-style compressed ./src/sass.scss > ./dist/style.css"
|
||||
},
|
||||
"dependencies": {
|
||||
"closest": "^0.0.1",
|
||||
"lodash": "^4.13.1",
|
||||
"react": "^15.1.0"
|
||||
},
|
||||
@ -29,14 +30,14 @@
|
||||
"@types/lodash": "^4.14.52",
|
||||
"@types/react": "^15.0.9",
|
||||
"@types/react-dom": "^0.14.23",
|
||||
"source-map-loader": "^0.1.6",
|
||||
"ts-loader": "^2.0.0",
|
||||
"typescript": "^2.1.6",
|
||||
"css-loader": "^0.26.1",
|
||||
"node-sass": "^4.5.0",
|
||||
"react-dom": "^15.1.0",
|
||||
"sass-loader": "^6.0.0",
|
||||
"source-map-loader": "^0.1.6",
|
||||
"style-loader": "^0.13.1",
|
||||
"ts-loader": "^2.0.0",
|
||||
"typescript": "^2.1.6",
|
||||
"webpack": "^2.2.1"
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import closest = require("closest");
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
@ -12,4 +13,17 @@ export class Toolkit {
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the closest element as a polyfill
|
||||
*
|
||||
* @param {Element} element [description]
|
||||
* @param {string} selector [description]
|
||||
*/
|
||||
public static closest(element: Element,selector:string){
|
||||
if(document.body.closest){
|
||||
return element.closest(selector);
|
||||
}
|
||||
return closest(element,selector);
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import * as _ from "lodash";
|
||||
import {PointModel, NodeModel, BaseModel, BaseModelListener, LinkModel,PortModel} from "../Common";
|
||||
import {LinkLayerWidget} from "./LinkLayerWidget";
|
||||
import {NodeLayerWidget} from "./NodeLayerWidget";
|
||||
import {Toolkit} from "../Toolkit";
|
||||
|
||||
export interface SelectionModel{
|
||||
model: BaseModel<BaseModelListener>;
|
||||
@ -196,9 +197,9 @@ export class DiagramWidget extends React.Component<DiagramProps, DiagramState> {
|
||||
var diagramModel = this.props.diagramEngine.diagramModel;
|
||||
|
||||
//is it a port
|
||||
var element = target.closest('.port[data-name]');
|
||||
var element = Toolkit.closest(target,'.port[data-name]');
|
||||
if(element){
|
||||
var nodeElement = target.closest('.node[data-nodeid]') as HTMLElement;
|
||||
var nodeElement = Toolkit.closest(target,'.node[data-nodeid]') as HTMLElement;
|
||||
return {
|
||||
model: diagramModel.getNode(nodeElement.getAttribute('data-nodeid')).getPort(element.getAttribute('data-name')),
|
||||
element: element
|
||||
@ -206,7 +207,7 @@ export class DiagramWidget extends React.Component<DiagramProps, DiagramState> {
|
||||
}
|
||||
|
||||
//look for a point
|
||||
element = target.closest('.point[data-id]');
|
||||
element = Toolkit.closest(target,'.point[data-id]');
|
||||
if(element){
|
||||
return {
|
||||
model: diagramModel.getLink(element.getAttribute('data-linkid')).getPointModel(element.getAttribute('data-id')),
|
||||
@ -215,7 +216,7 @@ export class DiagramWidget extends React.Component<DiagramProps, DiagramState> {
|
||||
}
|
||||
|
||||
//look for a link
|
||||
element = target.closest('[data-linkid]');
|
||||
element = Toolkit.closest(target,'[data-linkid]');
|
||||
if(element){
|
||||
return {
|
||||
model: diagramModel.getLink(element.getAttribute('data-linkid')),
|
||||
@ -224,7 +225,7 @@ export class DiagramWidget extends React.Component<DiagramProps, DiagramState> {
|
||||
}
|
||||
|
||||
//look for a node
|
||||
element = target.closest('.node[data-nodeid]');
|
||||
element = Toolkit.closest(target,'.node[data-nodeid]');
|
||||
if(element){
|
||||
return {
|
||||
model: diagramModel.getNode(element.getAttribute('data-nodeid')),
|
||||
|
@ -1,4 +1,22 @@
|
||||
var webpack = require("webpack");
|
||||
var plugins = [];
|
||||
|
||||
//do we minify it all
|
||||
if(process.env.NODE_ENV === 'production'){
|
||||
plugins.push(new webpack.optimize.UglifyJsPlugin({
|
||||
mangle: {
|
||||
keep_fnames: true
|
||||
},
|
||||
compress: {
|
||||
keep_fnames: true,
|
||||
warnings: false,
|
||||
}
|
||||
}));
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': JSON.stringify('production')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
@ -32,20 +50,7 @@ module.exports = [
|
||||
root: '_'
|
||||
}
|
||||
},
|
||||
plugins:[
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
mangle: {
|
||||
keep_fnames: true
|
||||
},
|
||||
compress: {
|
||||
keep_fnames: true,
|
||||
warnings: false,
|
||||
}
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': JSON.stringify('production')
|
||||
})
|
||||
],
|
||||
plugins:plugins,
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
@ -64,7 +69,7 @@ module.exports = [
|
||||
resolve: {
|
||||
extensions: [".tsx", ".ts", ".js"]
|
||||
},
|
||||
// devtool: 'source-map'
|
||||
devtool: 'eval-cheap-module-source-map'
|
||||
},
|
||||
//for building the demos and tests
|
||||
{
|
||||
@ -101,20 +106,7 @@ module.exports = [
|
||||
root: '_'
|
||||
}
|
||||
},
|
||||
plugins:[
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
mangle: {
|
||||
keep_fnames: true
|
||||
},
|
||||
compress: {
|
||||
keep_fnames: true,
|
||||
warnings: false,
|
||||
}
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': JSON.stringify('production')
|
||||
})
|
||||
],
|
||||
plugins:plugins,
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
@ -146,6 +138,6 @@ module.exports = [
|
||||
resolve: {
|
||||
extensions: [".tsx", ".ts", ".js"]
|
||||
},
|
||||
// devtool: 'source-map'
|
||||
devtool: 'eval-cheap-module-source-map'
|
||||
}
|
||||
];
|
||||
|
10
yarn.lock
@ -433,6 +433,12 @@ clone@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.2.tgz#260b7a99ebb1edfe247538175f783243cb19d149"
|
||||
|
||||
closest@^0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/closest/-/closest-0.0.1.tgz#26da6f80b3e0e17e71f80f12782819e9f653495c"
|
||||
dependencies:
|
||||
matches-selector "0.0.1"
|
||||
|
||||
co@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
||||
@ -1427,6 +1433,10 @@ map-obj@^1.0.0, map-obj@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
|
||||
|
||||
matches-selector@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/matches-selector/-/matches-selector-0.0.1.tgz#1df5262243ae341c1a0804dd302048267ac713bb"
|
||||
|
||||
math-expression-evaluator@^1.2.14:
|
||||
version "1.2.16"
|
||||
resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.16.tgz#b357fa1ca9faefb8e48d10c14ef2bcb2d9f0a7c9"
|
||||
|