housekeeping

This commit is contained in:
Dylan Vorster
2017-04-25 21:02:38 +02:00
parent a8722353c3
commit 01485add5c
24 changed files with 249 additions and 226 deletions

View File

@ -6,11 +6,15 @@ A super simple, no-nonsense diagramming library written in React that just works
[![NPM](https://img.shields.io/npm/v/storm-react-diagrams.svg)](https://npmjs.org/package/storm-react-diagrams)
[![NPM](https://img.shields.io/npm/dt/storm-react-diagrams.svg)](https://npmjs.org/package/storm-react-diagrams)
![Demo2](./demo2.png)
![Demo2](./images/example1.png)
![Demo2](./images/example2.png)
![Demo2](./images/example3.png)
## 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__:
![Demo2](./custom-nodes.png)
![Demo2](./images/demo3.png)
## 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 didnt 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
![__Delete__](./images/rjdDelete.gif)
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
![Shift + Mouse Drag](./images/mouseDrag.gif)
#### Why is there no JSX?
__Shift + Mouse Click__ selects the item (items can be multi-selected)
![Shift + Mouse Click](./images/shiftClick.gif)
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
![Mouse Drag](./images/canvasDrag.gif)
#### How do I make my own elements?
__Mouse Wheel__ zooms the diagram in / out
![Mouse Wheel](./images/mouseWheel.gif)
Take a look at the __defaults__ directory, with specific attention to the __DefaultNodeWidget__
__Click Link + Drag__ creates a new link point
![Click Link + Drag](./images/createPoint.gif)
#### 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.
![Demo](./demo.gif)
#### 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
![Click Node Port + Drag](./images/createLink.gif)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

View File

@ -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);

View File

@ -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
View File

@ -0,0 +1,35 @@
## Questions
#### Why didnt 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
images/createLink.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
images/createPoint.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
images/demo3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

BIN
images/example1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

View File

Before

Width:  |  Height:  |  Size: 870 KiB

After

Width:  |  Height:  |  Size: 870 KiB

BIN
images/example3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
images/mouseDrag.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

BIN
images/mouseWheel.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

BIN
images/rjdDelete.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
images/shiftClick.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View File

@ -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"
}
}

View File

@ -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);
}
}

View File

@ -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')),

View File

@ -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'
}
];

View File

@ -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"