Compare commits

..

67 Commits

Author SHA1 Message Date
Dylan Vorster
2c730a9fd1 wip 2018-05-05 12:22:02 +02:00
Dylan Vorster
a086bc785e more wip 2018-05-02 11:31:18 +02:00
Dylan Vorster
a7b6012a50 improved generics 2018-05-02 11:23:56 +02:00
Dylan Vorster
4117af8a33 wip 2018-05-02 11:20:23 +02:00
Dylan Vorster
eb7f0642a5 more moving toward react-canvas 2018-05-01 19:23:46 +02:00
Dylan Vorster
a8c73115ac no longer need these actions 2018-05-01 12:15:20 +02:00
Dylan Vorster
404e12c4e8 more stuff we no longer need 2018-04-27 20:42:38 +02:00
Dylan Vorster
aa6d1c336c wip 2018-04-27 20:28:12 +02:00
Dylan Vorster
204e05a2a1 wip 2018-04-27 19:26:18 +02:00
Dylan Vorster
d12220baa0 more refactoring 2018-04-27 16:17:20 +02:00
Dylan Vorster
91c1ee0169 WIP 2018-04-27 15:54:28 +02:00
Dylan Vorster
4806805037 use base zoom and extend from more react-canvas 2018-04-27 15:24:04 +02:00
Dylan Vorster
22f4062f56 Move to react-canvas ;) 2018-04-27 15:08:40 +02:00
Dylan Vorster
32a3c33916 Merge pull request #230 from ganesh-sankey/master
on deserialization of diagram, labels are not getting rendered bug re…
2018-04-23 17:37:37 +02:00
Ganesh Kakade
75ef02dd4d on deserialization of diagram, labels are not getting rendered bug resolved 2018-04-23 20:44:24 +05:30
Dylan Vorster
665c8b3443 Update README.md 2018-04-15 16:36:51 +02:00
Dylan Vorster
ccf425676f Update README.md 2018-04-15 16:36:39 +02:00
Dylan Vorster
1ff3abf3ef Update README.md 2018-04-15 16:36:26 +02:00
Dylan Vorster
0b1dab0de6 Merge pull request #217 from yngndrw/allow-loose-links-fix-and-refactor
Allow loose links fix and refactor (Fix for #171)
2018-04-02 02:28:00 +02:00
Andrew Young
0988e625b1 Added E2E tests, but could not get them to run ? 2018-04-01 23:14:24 +01:00
Andrew Young
dbaf03662f Added @types directory to the .gitignore file 2018-04-01 20:45:19 +01:00
Andrew Young
55f62587bd Re-write the "allowLooseLinks" validation functionality - Now checks both end points on any selected links that have been moved, resulting in more reliable validation 2018-04-01 20:27:55 +01:00
Andrew Young
98bcd60396 Code consistency change 2018-04-01 20:26:41 +01:00
Andrew Young
8467d8e7ca Fixed a typo 2018-04-01 20:26:04 +01:00
Dylan Vorster
40b4e14f15 Merge pull request #207 from wader/always-remove-link-from-old-source-target-port
Always remove link from old source/target port on port change
2018-03-27 17:16:06 +02:00
Mattias Wadman
7a78aaa9fd Always remove link from old source/target port on port change
Was only removed from old if new port is null
2018-03-23 13:43:58 +01:00
Dylan Vorster
47214df76b Update README.md 2018-03-23 10:11:35 +02:00
Dylan Vorster
cc40c398b7 Merge branch 'master' of github.com:projectstorm/react-diagrams 2018-03-21 15:04:02 +02:00
Dylan Vorster
188f63979b 5.1.1 2018-03-21 15:03:05 +02:00
Dylan Vorster
468bcea8b2 Merge pull request #202 from projectstorm/hotfix
Hotfix
2018-03-21 15:03:02 +02:00
Dylan Vorster
54b9feb62e whoops 2018-03-21 14:58:31 +02:00
Dylan Vorster
9f397b1453 fix types directory 2018-03-21 14:55:51 +02:00
Dylan Vorster
445702dd43 Hotfix 2018-03-21 14:52:13 +02:00
Dylan Vorster
bb6a6227e2 Merge pull request #201 from johnetrent/master
Change UglifyJsPlugin's ECMAScript target version to 5
2018-03-21 12:24:09 +02:00
John Trent
afff63e46a Change UglifyJsPlugin's ECMAScript target version to 5 2018-03-20 16:49:18 -07:00
John Trent
03e8348a14 Run prettier on webpack.config.js 2018-03-20 16:45:07 -07:00
Dylan Vorster
2c6d02f101 5.1.0 2018-03-17 14:44:17 +02:00
Dylan Vorster
327dcc190d general improvements 2018-03-17 14:43:35 +02:00
Dylan Vorster
c40c5c5919 improve spacing 2018-03-17 14:30:01 +02:00
Dylan Vorster
388b9931b1 Merge branch '5.1.0' of github.com:projectstorm/react-diagrams into 5.1.0 2018-03-17 14:26:18 +02:00
Dylan Vorster
ae04b5a07f Updates README.md
Auto commit by GitBook Editor
2018-03-17 12:26:03 +00:00
Dylan Vorster
9d2f450440 upgrade images 2018-03-17 14:25:52 +02:00
Dylan Vorster
45d2ea0c70 Merge branch 'master' of github.com:projectstorm/react-diagrams into 5.1.0 2018-03-17 14:16:47 +02:00
Dylan Vorster
94fe0fc854 Updates docs/Getting Started.md
Auto commit by GitBook Editor
2018-03-17 12:16:26 +00:00
Dylan Vorster
adf268697a Deletes test.md
Auto commit by GitBook Editor
2018-03-17 11:08:00 +00:00
Dylan Vorster
2c478db23f Updates test.md
Auto commit by GitBook Editor
2018-03-17 11:01:44 +00:00
Dylan Vorster
3ea6375011 rather import from storm-react-diagrams to further test the library 2018-03-13 22:46:06 +02:00
Dylan Vorster
64ec2dd0a4 Merge pull request #190 from projectstorm/source_map
sourcemap support
2018-03-13 21:42:50 +02:00
Dylan Vorster
e84c2e4e3e fix source map 2018-03-13 21:36:48 +02:00
Dylan Vorster
5f5f13a818 really really fix source maps -_- 2018-03-10 15:52:30 +02:00
Dylan Vorster
2b1a39f236 fix source maps 2018-03-10 15:44:24 +02:00
Dylan Vorster
4402068a93 sourcemap support 2018-03-10 15:32:24 +02:00
Dylan Vorster
ed50438744 Merge pull request #186 from maxleiko/master
fix main.ts exports
2018-03-08 19:25:33 +02:00
maxleiko
9a651c3ecc fix main.ts exports 2018-03-08 18:16:58 +01:00
Dylan Vorster
1af2f8cbe9 Update README.md 2018-03-03 21:57:47 +02:00
Dylan Vorster
2c74a5cf9a Update CHANGELOG.md 2018-03-03 21:57:17 +02:00
Dylan Vorster
140817f8c6 Merge pull request #179 from maxleiko/master
clean refactor / consistent TSLint / Prettier
2018-03-02 19:04:32 +02:00
maxleiko
1debc9c891 clean refactor / consistent TSLint / Prettier 2018-03-02 16:31:50 +01:00
Dylan Vorster
2ee0211994 Merge pull request #173 from wader/basemodel-extends-t
BaseModel extends use T instead of BaseModelListener
2018-02-28 19:55:25 +02:00
Dylan Vorster
86047f69fd Merge pull request #175 from smeijer/feature/add-cross-platform-support
feat(build): add cross-env to support building on various platforms.
2018-02-28 15:59:01 +02:00
Dylan Vorster
036d8dddcf Merge pull request #174 from smeijer/feature/add-vertical-flow-support
feat(link): improve rendering of vertical links
2018-02-28 15:58:18 +02:00
Stephan Meijer
08b81fff56 feat(build): add cross-env to support building on various platforms. 2018-02-28 10:50:30 +01:00
Stephan Meijer
fce1e0c7fe feat(link): improve rendering of vertical links 2018-02-28 10:21:06 +01:00
Mattias Wadman
d69f61e39d BaseModel extends use T instead of BaseModelListener
Use T instead otherwise LinkModelListener will not be used for LinkModel.
Fixes type warning about link.addListener argument.
Probably fixes same issue with iterateListeners.
2018-02-28 00:06:36 +01:00
Dylan Vorster
eb6fac30e0 update readme 2018-02-25 14:12:22 +02:00
Dylan Vorster
446cc8cdff Merge pull request #145 from projectstorm/links_refactor
5.0.0 (WIP)
2018-02-25 14:09:31 +02:00
Dylan Vorster
29ca66989c Update README.md 2018-02-18 20:33:49 +02:00
105 changed files with 2860 additions and 3418 deletions

1
.envrc Normal file
View File

@@ -0,0 +1 @@
PATH_add ./node_modules/.bin

3
.gitignore vendored
View File

@@ -1,6 +1,9 @@
dist/
dist/main.js
dist/main.js.map
/package
*.tgz
@types/
.out

View File

@@ -5,6 +5,7 @@ docs
.storybook
.circleci
tests
*.md
# Created by https://www.gitignore.io/api/net,netbeans,sublimetext,phpstorm,windows,osx,node

View File

@@ -50,7 +50,7 @@ export class CodePreview extends React.Component {
<SyntaxHighlighter
customStyle={{width: '100%', overflowX:'hidden', tabSize: 4}}
showLineNumbers={true}
language='typescript'
language='language-tsx'
style={github}
>
{this.state.code}

View File

@@ -22,7 +22,7 @@ module.exports = {
},
{
test: /\.tsx?$/,
loader: 'ts-loader',
loader: 'awesome-typescript-loader?declaration=false',
},
{
test: /\.(woff|woff2|eot|ttf|otf|svg)$/,
@@ -31,6 +31,9 @@ module.exports = {
]
},
resolve: {
alias: {
'storm-react-diagrams': path.join(__dirname, "..", "src", "main")
},
extensions: [".tsx", ".ts", ".js"]
}
};

1
.yarnrc Normal file
View File

@@ -0,0 +1 @@
--ignore-engines true

View File

@@ -1,4 +1,15 @@
__5.0.0__
__5.1.0__
* [api] Rename XXXFactory into AbstractXXXFactory
* [refactor] tslint and prettier are now the same
* [refactor] Each class now explicitely has its own class file (consistency)
* [feature] Smooth vertical links (no longer limited to horizontal)
* [feature] Dedicated documentation via gitbook
* [bug] forgot to export some
* [refactor] consistently use lodash where possible
* [maintenance] upgrade node modules
__5.0.0__ http://dylanv.blog/2018/03/03/storm-react-diagrams-5-0-0/
PR: https://github.com/projectstorm/react-diagrams/pull/145

View File

@@ -1,66 +1,46 @@
# STORM React Diagrams
__DEMO__: http://www.projectstorm.io/react-diagrams
**PSA**: React Diagrams is currently getting a bit of a rewrite to enable much more advanced features. To see the new foundation WIP visit [https://github.com/projectstorm/react-canvas](https://github.com/projectstorm/react-canvas).
__Latest Release Notes__: http://dylanv.blog/2018/01/18/storm-react-diagrams-v4-0-0/
---
**DEMO**: [http://www.projectstorm.io/react-diagrams](http://www.projectstorm.io/react-diagrams)
**DOCS:** [https://projectstorm.gitbooks.io/react-diagrams](https://projectstorm.gitbooks.io/react-diagrams)
**RELEASE NOTES** : [http://dylanv.blog/2018/03/03/storm-react-diagrams-5-0-0/](http://dylanv.blog/2018/03/03/storm-react-diagrams-5-0-0/)
A super simple, no-nonsense diagramming library written in React that just works.
[![Join the chat at https://gitter.im/projectstorm/react-diagrams](https://badges.gitter.im/projectstorm/react-diagrams.svg)](https://gitter.im/projectstorm/react-diagrams?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![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)
[![CircleCI](https://circleci.com/gh/projectstorm/react-diagrams/tree/master.svg?style=svg)](https://circleci.com/gh/projectstorm/react-diagrams/tree/master)
[![Join the chat at https://gitter.im/projectstorm/react-diagrams](https://badges.gitter.im/projectstorm/react-diagrams.svg)](https://gitter.im/projectstorm/react-diagrams?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![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) [![Package Quality](http://npm.packagequality.com/shield/storm-react-diagrams.svg)](http://packagequality.com/#?package=storm-react-diagrams) [![CircleCI](https://circleci.com/gh/projectstorm/react-diagrams/tree/master.svg?style=svg)](https://circleci.com/gh/projectstorm/react-diagrams/tree/master)
![Personal Project](./images/example1.png)
![Personal Project](./images/example1.jpg)
![](./images/example2.png)
![](./images/example2.jpg)
![](./images/example3.png)
![](./images/example3.jpg)
## Introduction
A no-nonsense diagramming library written entirely in React with the help of a few small libraries. 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..)
* Customizable without having to hack the core \(adapters/factories etc..\)
* Simple to operate and understand without sugar and magic
* Fast and optimized to handle large diagrams with hundreds of nodes/links
* Super easy to use, and should work as you expect it to
* Perfect for creating declarative systems such as programmatic pipelines and visual programming languages
## Usage
#### Installing
`npm install storm-react-diagrams` or `yarn add storm-react-diagrams`
#### Getting started
The best place to start is by looking at the simple demos `demos/demo-simple` and going from there.
#### Run the demos
After running `yarn install` you must then run: `yarn run storybook`
#### Building from source
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.
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 `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.
We use webpack for this because TSC cannot compile a single UMD file \(TSC can currently only output multiple UMD files\).
## Make your own nodes
To see how to create your own nodes like the one below, take a look at __demos/demo-custom-link1__:
![Demo2](./images/demo3.png)
## Learn More
[Architecture Questions](docs/Architecture%20Questions.md)
[Interactive Questions](docs/Interactive%20Usage.md)
[Testing the Library](docs/Testing.md)
## [Checkout the docs](https://projectstorm.gitbooks.io/react-diagrams)

11
SUMMARY.md Normal file
View File

@@ -0,0 +1,11 @@
# Summary
* [Introduction](README.md)
* [Interacting with diagrams](/docs/Interactive Usage.md)
* [Getting Started](/docs/Getting Started.md)
* [About the project](about-the-project.md)
* [Testing](/docs/Testing.md)
* [Architecture Questions](/docs/Architecture Questions.md)

0
about-the-project.md Normal file
View File

View File

@@ -1,80 +1,80 @@
@import "../../src/sass/main";
.srd-demo-workspace{
background: black;
display: flex;
flex-direction: column;
height: 100%;
border-radius: 5px;
overflow: hidden;
background: black;
display: flex;
flex-direction: column;
height: 100%;
border-radius: 5px;
overflow: hidden;
&__toolbar{
padding: 5px;
display: flex;
flex-shrink: 0;
&__toolbar{
padding: 5px;
display: flex;
flex-shrink: 0;
button{
background: rgb(60,60,60);
font-size: 14px;
padding: 5px 10px;
border: none;
color: white;
outline: none;
cursor: pointer;
margin: 2px;
border-radius: 3px;
button{
background: rgb(60,60,60);
font-size: 14px;
padding: 5px 10px;
border: none;
color: white;
outline: none;
cursor: pointer;
margin: 2px;
border-radius: 3px;
&:hover{
background: rgb(0,192,255);
}
}
}
&:hover{
background: rgb(0,192,255);
}
}
}
&__content{
flex-grow: 1;
height: 100%;
}
&__content{
flex-grow: 1;
height: 100%;
}
}
.docs-preview-wrapper{
background: rgb(60,60,60);
border-radius: 10px;
overflow: hidden;
padding: 10px;
margin-top: 20px;
margin-bottom: 20px;
background: rgb(60,60,60);
border-radius: 10px;
overflow: hidden;
padding: 10px;
margin-top: 20px;
margin-bottom: 20px;
}
.srd-demo-canvas{
height: 100%;
min-height: 300px;
background-color: rgb(60,60,60) !important;
$color: rgba(white, .05);
background-image:
linear-gradient(0deg,
transparent 24%,
$color 25%,
$color 26%,
transparent 27%,
transparent 74%,
$color 75%,
$color 76%,
transparent 77%,
transparent),
linear-gradient(90deg,
transparent 24%,
$color 25%,
$color 26%,
transparent 27%,
transparent 74%,
$color 75%,
$color 76%,
transparent 77%,
transparent);
background-size:50px 50px;
height: 100%;
min-height: 300px;
background-color: rgb(60,60,60) !important;
$color: rgba(white, .05);
background-image:
linear-gradient(0deg,
transparent 24%,
$color 25%,
$color 26%,
transparent 27%,
transparent 74%,
$color 75%,
$color 76%,
transparent 77%,
transparent),
linear-gradient(90deg,
transparent 24%,
$color 25%,
$color 26%,
transparent 27%,
transparent 74%,
$color 75%,
$color 76%,
transparent 77%,
transparent);
background-size:50px 50px;
.pointui{
fill: rgba(white,0.5);
}
.pointui{
fill: rgba(white,0.5);
}
}

View File

@@ -6,7 +6,7 @@ import {
NodeModel,
DiagramWidget,
BaseModel
} from "../../src/main";
} from "storm-react-diagrams";
import * as _ from "lodash";
import * as React from "react";
import { DemoWorkspaceWidget } from "../.helpers/DemoWorkspaceWidget";
@@ -15,7 +15,7 @@ import { DemoWorkspaceWidget } from "../.helpers/DemoWorkspaceWidget";
* Tests cloning
*/
class CloneSelected extends React.Component<any, any> {
constructor(props) {
constructor(props: any) {
super(props);
this.cloneSelected = this.cloneSelected.bind(this);
}
@@ -59,7 +59,7 @@ class CloneSelected extends React.Component<any, any> {
export default () => {
//1) setup the diagram engine
var engine = new DiagramEngine();
engine.installDefaultFactories();
engine.installDefaults();
//2) setup the diagram model
var model = new DiagramModel();
@@ -81,7 +81,7 @@ export default () => {
model.addAll(node1, node2, link1);
//5) load model into engine
engine.setDiagramModel(model);
engine.setModel(model);
//6) render the diagram!
return <CloneSelected engine={engine} model={model} />;

View File

@@ -7,13 +7,12 @@ import {
DiagramWidget,
LinkWidget,
LinkProps,
DefaultLinkWidget
} from "../../src/main";
DefaultLinkWidget,
DefaultLinkModel,
DefaultLinkFactory
} from "storm-react-diagrams";
import { action } from "@storybook/addon-actions";
import * as React from "react";
import { LinkFactory } from "../../src/AbstractFactory";
import { DefaultLinkModel } from "../../src/defaults/models/DefaultLinkModel";
import { DefaultLinkFactory } from "../../src/defaults/factories/DefaultLinkFactory";
export class AdvancedLinkModel extends DefaultLinkModel {
constructor() {
@@ -119,7 +118,7 @@ export class AdvancedLinkFactory extends DefaultLinkFactory {
export default () => {
//1) setup the diagram engine
var engine = new DiagramEngine();
engine.installDefaultFactories();
engine.installDefaults();
engine.registerLinkFactory(new AdvancedLinkFactory());
// create some nodes
@@ -151,7 +150,7 @@ export default () => {
model.addAll(node1, node2, node3, node4);
// load model into engine
engine.setDiagramModel(model);
engine.setModel(model);
// render the diagram!
return <DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />;

View File

@@ -1,9 +1,9 @@
import * as SRD from "../../src/main";
import * as SRD from "storm-react-diagrams";
import { DiamonNodeWidget } from "./DiamondNodeWidget";
import { DiamondNodeModel } from "./DiamondNodeModel";
import * as React from "react";
export class DiamondNodeFactory extends SRD.NodeFactory {
export class DiamondNodeFactory extends SRD.AbstractNodeFactory {
constructor() {
super("diamond");
}

View File

@@ -1,7 +1,7 @@
import * as SRD from "../../src/main";
import { NodeModel } from "storm-react-diagrams";
import { DiamondPortModel } from "./DiamondPortModel";
export class DiamondNodeModel extends SRD.NodeModel {
export class DiamondNodeModel extends NodeModel {
constructor() {
super("diamond");
this.addPort(new DiamondPortModel("top"));

View File

@@ -1,6 +1,6 @@
import * as React from "react";
import { DiamondNodeModel } from "./DiamondNodeModel";
import { PortWidget } from "../../src/main";
import { PortWidget } from "storm-react-diagrams";
export interface DiamonNodeWidgetProps {
node: DiamondNodeModel;
@@ -39,10 +39,10 @@ export class DiamonNodeWidget extends React.Component<DiamonNodeWidgetProps, Dia
dangerouslySetInnerHTML={{
__html:
`
<g id="Layer_1">
</g>
<g id="Layer_2">
<polygon fill="purple" stroke="#000000" stroke-width="3" stroke-miterlimit="10" points="10,` +
<g id="Layer_1">
</g>
<g id="Layer_2">
<polygon fill="purple" stroke="#000000" stroke-width="3" stroke-miterlimit="10" points="10,` +
this.props.size / 2 +
` ` +
this.props.size / 2 +
@@ -55,8 +55,8 @@ export class DiamonNodeWidget extends React.Component<DiamonNodeWidgetProps, Dia
`,` +
(this.props.size - 10) +
` "/>
</g>
`
</g>
`
}}
/>
<div

View File

@@ -1,8 +1,5 @@
import * as _ from "lodash";
import { LinkModel } from "../../src/models/LinkModel";
import { DiagramEngine } from "../../src/DiagramEngine";
import { PortModel } from "../../src/models/PortModel";
import { DefaultLinkModel } from "../../src/defaults/models/DefaultLinkModel";
import { LinkModel, DiagramEngine, PortModel, DefaultLinkModel } from "storm-react-diagrams";
export class DiamondPortModel extends PortModel {
position: string | "top" | "bottom" | "left" | "right";

View File

@@ -0,0 +1,14 @@
import { PortModel, AbstractPortFactory } from "storm-react-diagrams";
export class SimplePortFactory extends AbstractPortFactory {
cb: (initialConfig?: any) => PortModel;
constructor(type: string, cb: (initialConfig?: any) => PortModel) {
super(type);
this.cb = cb;
}
getNewInstance(initialConfig?: any): PortModel {
return this.cb(initialConfig);
}
}

View File

@@ -5,13 +5,13 @@ import {
LinkModel,
DefaultPortModel,
DiagramWidget
} from "../../src/main";
} from "storm-react-diagrams";
import * as React from "react";
// import the custom models
import { DiamondNodeModel } from "./DiamondNodeModel";
import { DiamondNodeFactory } from "./DiamondNodeFactory";
import { SimplePortFactory } from "../../src/AbstractFactory";
import { SimplePortFactory } from "./SimplePortFactory";
import { DiamondPortModel } from "./DiamondPortModel";
/**
@@ -20,7 +20,7 @@ import { DiamondPortModel } from "./DiamondPortModel";
export default () => {
//1) setup the diagram engine
var engine = new DiagramEngine();
engine.installDefaultFactories();
engine.installDefaults();
// register some other factories as well
engine.registerPortFactory(new SimplePortFactory("diamond", config => new DiamondPortModel()));
@@ -50,7 +50,7 @@ export default () => {
model.addAll(node1, node2, node3, link1, link2);
//5) load model into engine
engine.setDiagramModel(model);
engine.setModel(model);
//6) render the diagram!
return <DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />;

View File

@@ -22,9 +22,7 @@ function distributeGraph(model) {
let edges = mapEdges(model);
let graph = new dagre.graphlib.Graph();
graph.setGraph({});
graph.setDefaultEdgeLabel(function() {
return {};
});
graph.setDefaultEdgeLabel(() => ({}));
//add elements to dagre graph
nodes.forEach(node => {
graph.setNode(node.id, node.metadata);

View File

@@ -7,7 +7,7 @@ import {
LinkModel,
DefaultPortModel,
DiagramWidget
} from "../../src/main";
} from "storm-react-diagrams";
import { distributeElements } from "./dagre-utils";
import * as React from "react";
import { DemoWorkspaceWidget } from "../.helpers/DemoWorkspaceWidget";
@@ -40,7 +40,7 @@ class Demo8Widget extends React.Component<any, any> {
const { engine } = this.props;
const model = engine.getDiagramModel();
let distributedModel = getDistributedModel(engine, model);
engine.setDiagramModel(distributedModel);
engine.setModel(distributedModel);
this.forceUpdate();
}
@@ -68,7 +68,7 @@ function getDistributedModel(engine, model) {
export default () => {
//1) setup the diagram engine
let engine = new DiagramEngine();
engine.installDefaultFactories();
engine.installDefaults();
//2) setup the diagram model
let model = new DiagramModel();
@@ -114,7 +114,7 @@ export default () => {
//5) load model into engine
let model2 = getDistributedModel(engine, model);
engine.setDiagramModel(model2);
engine.setModel(model2);
return <Demo8Widget engine={engine} />;
};

View File

@@ -1,4 +1,4 @@
import * as SRD from "../../src/main";
import * as SRD from "storm-react-diagrams";
/**
* @author Dylan Vorster
@@ -9,14 +9,14 @@ export class Application {
constructor() {
this.diagramEngine = new SRD.DiagramEngine();
this.diagramEngine.installDefaultFactories();
this.diagramEngine.installDefaults();
this.newModel();
}
public newModel() {
this.activeModel = new SRD.DiagramModel();
this.diagramEngine.setDiagramModel(this.activeModel);
this.diagramEngine.setModel(this.activeModel);
//3-A) create a default node
var node1 = new SRD.DefaultNodeModel("Node 1", "rgb(0,192,255)");

View File

@@ -3,7 +3,7 @@ import * as _ from "lodash";
import { TrayWidget } from "./TrayWidget";
import { Application } from "../Application";
import { TrayItemWidget } from "./TrayItemWidget";
import { DefaultNodeModel, DiagramWidget } from "../../../src/main";
import { DefaultNodeModel, DiagramWidget } from "storm-react-diagrams";
export interface BodyWidgetProps {
app: Application;

View File

@@ -3,7 +3,7 @@ import * as React from "react";
import { BodyWidget } from "./components/BodyWidget";
import { Application } from "./Application";
require("./sass/main.scss");
import "./sass/main.scss";
export default () => {
var app = new Application();

View File

@@ -1,47 +1,47 @@
.body{
flex-grow: 1;
display: flex;
flex-direction: column;
min-height: 100%;
flex-grow: 1;
display: flex;
flex-direction: column;
min-height: 100%;
.header{
display: flex;
background: rgb(30,30,30);
flex-grow: 0;
flex-shrink: 0;
color: white;
font-family: Helvetica, Arial;
padding: 10px;
>*{
align-self:center;
}
}
.header{
display: flex;
background: rgb(30,30,30);
flex-grow: 0;
flex-shrink: 0;
color: white;
font-family: Helvetica, Arial;
padding: 10px;
>*{
align-self:center;
}
}
.content{
display: flex;
flex-grow: 1;
.content{
display: flex;
flex-grow: 1;
.diagram-layer{
position: relative;
flex-grow: 1;
}
.diagram-layer{
position: relative;
flex-grow: 1;
}
.tray{
min-width: 200px;
background: rgb(20,20,20);
flex-grow: 0;
flex-shrink: 0;
.tray{
min-width: 200px;
background: rgb(20,20,20);
flex-grow: 0;
flex-shrink: 0;
.tray-item{
color: white;
font-family: Helvetica, Arial;
padding: 5px;
margin: 0px 10px;
border: solid 1px;
border-radius: 5px;
margin-bottom: 2px;
cursor: pointer;
}
}
}
.tray-item{
color: white;
font-family: Helvetica, Arial;
padding: 5px;
margin: 0px 10px;
border: solid 1px;
border-radius: 5px;
margin-bottom: 2px;
cursor: pointer;
}
}
}
}

View File

@@ -1,4 +1,4 @@
import { DiagramEngine, DiagramModel, DefaultNodeModel, LinkModel, DiagramWidget } from "../../src/main";
import { DiagramEngine, DiagramModel, DefaultNodeModel, LinkModel, DiagramWidget } from "storm-react-diagrams";
import * as React from "react";
/**
@@ -7,7 +7,7 @@ import * as React from "react";
export default () => {
//1) setup the diagram engine
var engine = new DiagramEngine();
engine.installDefaultFactories();
engine.installDefaults();
//2) setup the diagram model
var model = new DiagramModel();
@@ -30,7 +30,7 @@ export default () => {
model.addAll(node1, node2, link1);
//5) load model into engine
engine.setDiagramModel(model);
engine.setModel(model);
//6) render the diagram!
return <DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />;

View File

@@ -4,17 +4,17 @@ import {
DefaultNodeModel,
LinkModel,
DefaultPortModel,
DiagramWidget
} from "../../src/main";
DiagramWidget,
DefaultLinkModel
} from "storm-react-diagrams";
import * as React from "react";
import { DemoWorkspaceWidget } from "../.helpers/DemoWorkspaceWidget";
import { action } from "@storybook/addon-actions";
import { DefaultLinkModel } from "../../src/defaults/models/DefaultLinkModel";
export default () => {
// setup the diagram engine
const engine = new DiagramEngine();
engine.installDefaultFactories();
engine.installDefaults();
// setup the diagram model
const model = new DiagramModel();
@@ -53,7 +53,7 @@ export default () => {
model.addAll(node1, node2, node3, node4, link1, link2, link3);
// load model into engine and render
engine.setDiagramModel(model);
engine.setModel(model);
return (
<DemoWorkspaceWidget

View File

@@ -1,6 +1,12 @@
import * as SRD from "../../src/main";
import * as React from "react";
import { DiagramEngine, DiagramModel, DefaultNodeModel, LinkModel, DiagramWidget } from "../../src/main";
import {
DiagramEngine,
DiagramModel,
DefaultNodeModel,
LinkModel,
DiagramWidget,
DiagramProps
} from "storm-react-diagrams";
/**
* Shows that a limit of points can be set for links
@@ -8,7 +14,7 @@ import { DiagramEngine, DiagramModel, DefaultNodeModel, LinkModel, DiagramWidget
export default () => {
// setup the diagram engine
var engine = new DiagramEngine();
engine.installDefaultFactories();
engine.installDefaults();
var model = new DiagramModel();
@@ -27,12 +33,12 @@ export default () => {
model.addAll(node1, node2, link1);
engine.setDiagramModel(model);
engine.setModel(model);
var props = {
diagramEngine: engine,
maxNumberPointsPerLink: 5
} as SRD.DiagramProps;
} as DiagramProps;
return (
<div>

View File

@@ -1,7 +1,13 @@
import * as SRD from "../../src/main";
import * as React from "react";
import { action } from "@storybook/addon-actions";
import { DiagramEngine, DiagramModel, DefaultNodeModel, LinkModel, DiagramWidget } from "../../src/main";
import {
DiagramEngine,
DiagramModel,
DiagramProps,
DefaultNodeModel,
LinkModel,
DiagramWidget
} from "storm-react-diagrams";
/**
* Shows some of the events triggered when elements are selected
@@ -9,7 +15,7 @@ import { DiagramEngine, DiagramModel, DefaultNodeModel, LinkModel, DiagramWidget
export default () => {
// setup the diagram engine
var engine = new DiagramEngine();
engine.installDefaultFactories();
engine.installDefaults();
var model = new DiagramModel();
@@ -40,12 +46,12 @@ export default () => {
});
});
engine.setDiagramModel(model);
engine.setModel(model);
var props = {
diagramEngine: engine,
maxNumberPointsPerLink: 0 // no extra points so link selection is fired straight away
} as SRD.DiagramProps;
} as DiagramProps;
return (
<div>

View File

@@ -1,6 +1,13 @@
import * as SRD from "../../src/main";
import * as React from "react";
import { DiagramEngine, DiagramModel, DefaultNodeModel, LinkModel, PointModel, DiagramWidget } from "../../src/main";
import {
DiagramEngine,
DiagramModel,
DefaultNodeModel,
LinkModel,
PointModel,
DiagramWidget,
DiagramProps
} from "storm-react-diagrams";
/**
*
@@ -11,7 +18,7 @@ import { DiagramEngine, DiagramModel, DefaultNodeModel, LinkModel, PointModel, D
export default () => {
//1) setup the diagram engine
var engine = new DiagramEngine();
engine.installDefaultFactories();
engine.installDefaults();
var model = new DiagramModel();
@@ -44,7 +51,7 @@ export default () => {
model.addAll(node3, node4, link2);
engine.setDiagramModel(model);
engine.setModel(model);
//!========================================= <<<<<<<
@@ -54,7 +61,7 @@ export default () => {
allowLooseLinks: false,
allowCanvasTranslation: false,
allowCanvasZoom: false
} as SRD.DiagramProps;
} as DiagramProps;
//!========================================= <<<<<<<

View File

@@ -1,4 +1,4 @@
import { DiagramEngine, DiagramModel, DefaultNodeModel, LinkModel, DiagramWidget } from "../../src/main";
import { DiagramEngine, DiagramModel, DefaultNodeModel, LinkModel, DiagramWidget } from "storm-react-diagrams";
import * as React from "react";
import { DemoWorkspaceWidget } from "../.helpers/DemoWorkspaceWidget";
@@ -31,7 +31,7 @@ class NodeDelayedPosition extends React.Component<any, any> {
node.x += 30;
node.y += 30;
model2.deSerializeDiagram(obj, engine);
engine.setDiagramModel(model2);
engine.setModel(model2);
this.forceUpdate();
}
@@ -57,7 +57,7 @@ class NodeDelayedPosition extends React.Component<any, any> {
export default () => {
//1) setup the diagram engine
var engine = new DiagramEngine();
engine.installDefaultFactories();
engine.installDefaults();
//2) setup the diagram model
var model = new DiagramModel();
@@ -79,7 +79,7 @@ export default () => {
model.addAll(node1, node2, link1);
//5) load model into engine
engine.setDiagramModel(model);
engine.setModel(model);
//6) render the diagram!
return <NodeDelayedPosition engine={engine} model={model} />;

View File

@@ -1,4 +1,4 @@
import { DiagramEngine, DiagramModel, DefaultNodeModel, LinkModel, DiagramWidget } from "../../src/main";
import { DiagramEngine, DiagramModel, DefaultNodeModel, LinkModel, DiagramWidget } from "storm-react-diagrams";
import * as React from "react";
/**
@@ -11,25 +11,7 @@ import * as React from "react";
export default () => {
//1) setup the diagram engine
var engine = new DiagramEngine();
engine.installDefaultFactories();
function generateNodes(model: DiagramModel, offsetX: number, offsetY: number) {
//3-A) create a default node
var node1 = new DefaultNodeModel("Node 1", "rgb(0,192,255)");
var port1 = node1.addOutPort("Out");
node1.setPosition(100 + offsetX, 100 + offsetY);
//3-B) create another default node
var node2 = new DefaultNodeModel("Node 2", "rgb(192,255,0)");
var port2 = node2.addInPort("In");
node2.setPosition(200 + offsetX, 100 + offsetY);
//3-C) link the 2 nodes together
var link1 = port1.link(port2);
//4) add the models to the root graph
model.addAll(node1, node2, link1);
}
engine.installDefaults();
//2) setup the diagram model
var model = new DiagramModel();
@@ -41,8 +23,26 @@ export default () => {
}
//5) load model into engine
engine.setDiagramModel(model);
engine.setModel(model);
//6) render the diagram!
return <DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />;
};
function generateNodes(model: DiagramModel, offsetX: number, offsetY: number) {
//3-A) create a default node
var node1 = new DefaultNodeModel("Node 1", "rgb(0,192,255)");
var port1 = node1.addOutPort("Out");
node1.setPosition(100 + offsetX, 100 + offsetY);
//3-B) create another default node
var node2 = new DefaultNodeModel("Node 2", "rgb(192,255,0)");
var port2 = node2.addInPort("In");
node2.setPosition(200 + offsetX, 100 + offsetY);
//3-C) link the 2 nodes together
var link1 = port1.link(port2);
//4) add the models to the root graph
model.addAll(node1, node2, link1);
}

View File

@@ -1,13 +1,14 @@
import { DiagramEngine, DiagramModel, DefaultNodeModel, LinkModel, DiagramWidget } from "../../src/main";
import { DiagramEngine, DiagramModel, DefaultNodeModel, LinkModel } from "storm-react-diagrams";
import * as React from "react";
import { DemoWorkspaceWidget } from "../.helpers/DemoWorkspaceWidget";
import { action } from "@storybook/addon-actions";
var beautify = require("json-beautify");
import * as beautify from "json-beautify";
import {DeserializeEvent} from "../../../react-canvas/src/models/BaseModel";
export default () => {
//1) setup the diagram engine
var engine = new DiagramEngine();
engine.installDefaultFactories();
engine.installDefaults();
//2) setup the diagram model
var model = new DiagramModel();
@@ -29,17 +30,17 @@ export default () => {
model.addAll(node1, node2, link1);
//5) load model into engine
engine.setDiagramModel(model);
engine.setModel(model);
//!------------- SERIALIZING ------------------
var str = JSON.stringify(model.serializeDiagram());
var str = JSON.stringify(model.serialize());
//!------------- DESERIALIZING ----------------
var model2 = new DiagramModel();
model2.deSerializeDiagram(JSON.parse(str), engine);
engine.setDiagramModel(model2);
model2.deSerialize(new DeserializeEvent(JSON.parse(str), engine));
engine.setModel(model2);
return (
<DemoWorkspaceWidget

View File

@@ -1,10 +1,10 @@
import { DiagramEngine, DiagramModel, DefaultNodeModel, LinkModel, DiagramWidget } from "../../src/main";
import { DiagramEngine, DiagramModel, DefaultNodeModel, LinkModel, DiagramWidget } from "storm-react-diagrams";
import * as React from "react";
export default () => {
//1) setup the diagram engine
var engine = new DiagramEngine();
engine.installDefaultFactories();
engine.installDefaults();
//2) setup the diagram model
var model = new DiagramModel();
@@ -31,7 +31,7 @@ export default () => {
model.addAll(node1, node2, node3, link1);
//5) load model into engine
engine.setDiagramModel(model);
engine.setModel(model);
//6) render the diagram!
return <DiagramWidget className="srd-demo-canvas" diagramEngine={engine} allowLooseLinks={false} />;

View File

@@ -1,11 +1,17 @@
import { DiagramEngine, DiagramModel, DefaultNodeModel, LinkModel, DiagramWidget } from "../../src/main";
import {
DiagramEngine,
DiagramModel,
DefaultNodeModel,
LinkModel,
DiagramWidget,
DefaultLinkModel
} from "storm-react-diagrams";
import * as React from "react";
import { DefaultLinkModel } from "../../src/defaults/models/DefaultLinkModel";
export default () => {
//1) setup the diagram engine
var engine = new DiagramEngine();
engine.installDefaultFactories();
engine.installDefaults();
//2) setup the diagram model
var model = new DiagramModel();
@@ -28,7 +34,7 @@ export default () => {
model.addAll(node1, node2, link1);
//5) load model into engine
engine.setDiagramModel(model);
engine.setModel(model);
//6) render the diagram!
return <DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />;

View File

@@ -5,7 +5,7 @@ import {
LinkModel,
DefaultPortModel,
DiagramWidget
} from "../../src/main";
} from "storm-react-diagrams";
import * as React from "react";
import { DemoWorkspaceWidget } from "../.helpers/DemoWorkspaceWidget";
import { action } from "@storybook/addon-actions";
@@ -13,7 +13,7 @@ import { action } from "@storybook/addon-actions";
export default () => {
// setup the diagram engine
const engine = new DiagramEngine();
engine.installDefaultFactories();
engine.installDefaults();
// setup the diagram model
const model = new DiagramModel();
@@ -43,7 +43,7 @@ export default () => {
model.addAll(node1, node2, node3, node4, node5, link1, link2);
// load model into engine and render
engine.setDiagramModel(model);
engine.setModel(model);
return (
<DemoWorkspaceWidget

View File

@@ -5,7 +5,7 @@ import {
LinkModel,
DefaultPortModel,
DiagramWidget
} from "../../src/main";
} from "storm-react-diagrams";
import * as React from "react";
import { DemoWorkspaceWidget } from "../.helpers/DemoWorkspaceWidget";
@@ -18,25 +18,7 @@ import { DemoWorkspaceWidget } from "../.helpers/DemoWorkspaceWidget";
export default () => {
//1) setup the diagram engine
var engine = new DiagramEngine();
engine.installDefaultFactories();
function generateNodes(model: DiagramModel, offsetX: number, offsetY: number) {
//3-A) create a default node
var node1 = new DefaultNodeModel("Node 1", "rgb(0,192,255)");
var port1 = node1.addOutPort("Out");
node1.setPosition(100 + offsetX, 100 + offsetY);
//3-B) create another default node
var node2 = new DefaultNodeModel("Node 2", "rgb(192,255,0)");
var port2 = node2.addInPort("In");
node2.setPosition(200 + offsetX, 100 + offsetY);
//3-C) link the 2 nodes together
var link1 = port1.link(port2);
//4) add the models to the root graph
model.addAll(node1, node2, link1);
}
engine.installDefaults();
//2) setup the diagram model
var model = new DiagramModel();
@@ -48,12 +30,32 @@ export default () => {
}
//5) load model into engine
engine.setDiagramModel(model);
engine.setModel(model);
//6) render the diagram!
return (
<DemoWorkspaceWidget buttons={<button onClick={() => engine.zoomToFit()}>Zoom to fit</button>}>
<DemoWorkspaceWidget
buttons={<button onClick={() => engine.getCanvasWidget().zoomToFit()}>Zoom to fit</button>}
>
<DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />
</DemoWorkspaceWidget>
);
};
function generateNodes(model: DiagramModel, offsetX: number, offsetY: number) {
//3-A) create a default node
var node1 = new DefaultNodeModel("Node 1", "rgb(0,192,255)");
var port1 = node1.addOutPort("Out");
node1.setPosition(100 + offsetX, 100 + offsetY);
//3-B) create another default node
var node2 = new DefaultNodeModel("Node 2", "rgb(192,255,0)");
var port2 = node2.addInPort("In");
node2.setPosition(200 + offsetX, 100 + offsetY);
//3-C) link the 2 nodes together
var link1 = port1.link(port2);
//4) add the models to the root graph
model.addAll(node1, node2, link1);
}

View File

@@ -6,7 +6,7 @@ import { Helper } from "./.helpers/Helper";
import { Toolkit } from "../src/Toolkit";
//include the SCSS for the demo
require("./.helpers/demo.scss");
import "./.helpers/demo.scss";
Toolkit.TESTING = true;
@@ -34,96 +34,96 @@ storiesOf("Simple Usage", module)
require("./demo-simple/docs.md")
)
)
.add(
"Simple flow example",
Helper.makeDemo(require("./demo-simple-flow/index").default(), require("!!raw-loader!./demo-simple-flow/index"))
)
.add(
"Performance demo",
Helper.makeDemo(require("./demo-performance/index").default(), require("!!raw-loader!./demo-performance/index"))
)
.add(
"Locked widget",
Helper.makeDemo(require("./demo-locks/index").default(), require("!!raw-loader!./demo-locks/index"))
)
.add(
"Canvas grid size",
Helper.makeDemo(require("./demo-grid/index").default(), require("!!raw-loader!./demo-grid/index"))
)
.add(
"Limiting link points",
Helper.makeDemo(
require("./demo-limit-points/index").default(),
require("!!raw-loader!./demo-limit-points/index")
)
)
.add(
"Events and listeners",
Helper.makeDemo(require("./demo-listeners/index").default(), require("!!raw-loader!./demo-listeners/index"))
)
.add(
"Zoom to fit",
Helper.makeDemo(require("./demo-zoom-to-fit/index").default(), require("!!raw-loader!./demo-zoom-to-fit/index"))
)
.add(
"Links with labels",
Helper.makeDemo(
require("./demo-labelled-links/index").default(),
require("!!raw-loader!./demo-labelled-links/index")
)
);
storiesOf("Advanced Techniques", module)
.add(
"Clone Selected",
Helper.makeDemo(require("./demo-cloning/index").default(), require("!!raw-loader!./demo-cloning/index"))
)
.add(
"Serializing and de-serializing",
Helper.makeDemo(require("./demo-serializing/index").default(), require("!!raw-loader!./demo-serializing/index"))
)
.add(
"Programatically modifying graph",
Helper.makeDemo(
require("./demo-mutate-graph/index").default(),
require("!!raw-loader!./demo-mutate-graph/index")
)
)
.add(
"Large application",
Helper.makeDemo(
require("./demo-drag-and-drop/index").default(),
require("!!raw-loader!./demo-drag-and-drop/components/BodyWidget")
)
)
.add(
"Smart routing",
Helper.makeDemo(
require("./demo-smart-routing/index").default(),
require("!!raw-loader!./demo-smart-routing/index")
)
);
storiesOf("Custom Models", module)
.add(
"Custom diamond node",
Helper.makeDemo(
require("./demo-custom-node1/index").default(),
require("!!raw-loader!./demo-custom-node1/index")
)
)
.add(
"Custom animated links",
Helper.makeDemo(
require("./demo-custom-link1/index").default(),
require("!!raw-loader!./demo-custom-link1/index")
)
);
storiesOf("3rd party libraries", module).add(
"Auto Distribute (Dagre)",
Helper.makeDemo(require("./demo-dagre/index").default(), require("!!raw-loader!./demo-dagre/index"))
);
// .add(
// "Simple flow example",
// Helper.makeDemo(require("./demo-simple-flow/index").default(), require("!!raw-loader!./demo-simple-flow/index"))
// )
// .add(
// "Performance demo",
// Helper.makeDemo(require("./demo-performance/index").default(), require("!!raw-loader!./demo-performance/index"))
// )
// .add(
// "Locked widget",
// Helper.makeDemo(require("./demo-locks/index").default(), require("!!raw-loader!./demo-locks/index"))
// )
// .add(
// "Canvas grid size",
// Helper.makeDemo(require("./demo-grid/index").default(), require("!!raw-loader!./demo-grid/index"))
// )
// .add(
// "Limiting link points",
// Helper.makeDemo(
// require("./demo-limit-points/index").default(),
// require("!!raw-loader!./demo-limit-points/index")
// )
// )
// .add(
// "Events and listeners",
// Helper.makeDemo(require("./demo-listeners/index").default(), require("!!raw-loader!./demo-listeners/index"))
// )
// .add(
// "Zoom to fit",
// Helper.makeDemo(require("./demo-zoom-to-fit/index").default(), require("!!raw-loader!./demo-zoom-to-fit/index"))
// )
// .add(
// "Links with labels",
// Helper.makeDemo(
// require("./demo-labelled-links/index").default(),
// require("!!raw-loader!./demo-labelled-links/index")
// )
// );
//
// storiesOf("Advanced Techniques", module)
// .add(
// "Clone Selected",
// Helper.makeDemo(require("./demo-cloning/index").default(), require("!!raw-loader!./demo-cloning/index"))
// )
// .add(
// "Serializing and de-serializing",
// Helper.makeDemo(require("./demo-serializing/index").default(), require("!!raw-loader!./demo-serializing/index"))
// )
// .add(
// "Programatically modifying graph",
// Helper.makeDemo(
// require("./demo-mutate-graph/index").default(),
// require("!!raw-loader!./demo-mutate-graph/index")
// )
// )
// .add(
// "Large application",
// Helper.makeDemo(
// require("./demo-drag-and-drop/index").default(),
// require("!!raw-loader!./demo-drag-and-drop/components/BodyWidget")
// )
// )
// .add(
// "Smart routing",
// Helper.makeDemo(
// require("./demo-smart-routing/index").default(),
// require("!!raw-loader!./demo-smart-routing/index")
// )
// );
//
// storiesOf("Custom Models", module)
// .add(
// "Custom diamond node",
// Helper.makeDemo(
// require("./demo-custom-node1/index").default(),
// require("!!raw-loader!./demo-custom-node1/index")
// )
// )
// .add(
// "Custom animated links",
// Helper.makeDemo(
// require("./demo-custom-link1/index").default(),
// require("!!raw-loader!./demo-custom-link1/index")
// )
// );
//
// storiesOf("3rd party libraries", module).add(
// "Auto Distribute (Dagre)",
// Helper.makeDemo(require("./demo-dagre/index").default(), require("!!raw-loader!./demo-dagre/index"))
// );
// enable this to log mouse location when writing new puppeteer tests
//Helper.logMousePosition()

10
demos/tslint.json Normal file
View File

@@ -0,0 +1,10 @@
{
"extends": [
"../tslint.json"
],
"rules": {
"no-console": false,
"max-classes-per-file": false,
"no-var-requires": false
}
}

142
docs/Getting Started.md Normal file
View File

@@ -0,0 +1,142 @@
# Getting started
## Installation via NPM
The first thing you need to do, is grab the distribution files on NPM. You can do this either using yarn or npm
**Via yarn:**
```
yarn install storm-react-diagrams
```
**Via npm:**
```
npm install storm-react-diagrams
```
When you run this in your project directory, this will install the library into node\_modules/storm-react-diagrams. You will then find a dist folder that contains all the minified and production ready code.
This will also install React and a few other dependencies that you need in order to use this library.
## Including the library
When including the library you will need both the javascript files as well as the raw BEM styles. Both are included in the dist folder and there are numerous ways to integrate them into your project:
#### Getting the javascript files
**Using Typescript / ES6: \(recommended\)**
```js
import * as SRD from "storm-react-diagrams"
```
**Using RequireJS:**
```js
var SRD = require("storm-react-diagrams)
```
**As a script tag \(not recommended\)**
```html
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="node_modules/storm-react-diagrams/dist/main.js">
```
#### Getting the CSS
**Using RequireJS / Typescript/ ES6 \(recommended\)**
Make sure you have the style-loader enabled and then:
```js
require("storm-react-diagrams/dist/style.min.css");
```
or make sure you have the sass-loader enabled and then:
```js
require("storm-react-diagrams/src/sass/main.scss");
```
If you are using typescript and get a "require function not found" then make sure to
```
yarn add @types/node
```
which will give you the typescript definition files for requireJS
**Using SASS:**
setup your include paths on webpack or lib sass using the following option
```
includePaths: ["node_modules"]
```
and then if you want the raw sass source code:
```sass
@import "~storm-react-diagrams/src/sass/main";
```
or if you want the minified css
```sass
@import "~storm-react-diagrams/dist/style.min";
```
**Using a style tag**
or if you want the minified css
```html
<link rel="stylesheet" href="node_modules/dist/style.min.css">
```
## Render your first diagram
In your library code
```js
// 1) setup the diagram engine
var engine = new SRD.DiagramEngine();
engine.installDefaultFactories();
// 2) setup the diagram model
var model = new SRD.DiagramModel();
// 3) create a default node
var node1 = new SRD.DefaultNodeModel("Node 1", "rgb(0,192,255)");
let port1 = node1.addOutPort("Out");
node1.setPosition(100, 100);
// 4) create another default node
var node2 = new SRD.DefaultNodeModel("Node 2", "rgb(192,255,0)");
let port2 = node2.addInPort("In");
node2.setPosition(400, 100);
// 5) link the ports
let link1 = port1.link(port2);
// 6) add the models to the root graph
model.addAll(node1, node2, link1);
// 7) load model into engine
engine.setDiagramModel(model);
```
And then create an instance of the diagram widget. An example of the simplest possible react widget to do this would be:
```jsx
function SimpleDiagramWidget(props) {
return <SRD.DiagramWidget diagramEngine={props.engine} />;
}
```

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

BIN
images/example1.jpg Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

BIN
images/example2.jpg Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 870 KiB

BIN
images/example3.jpg Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -1,3 +1,4 @@
const path = require("path");
// jest.config.js
module.exports = {
verbose: true,
@@ -10,11 +11,12 @@ module.exports = {
"node"
],
transform: {
".*test_loader.*": __dirname+"/tests/helpers/storybook-loader.js",
".*test_loader.*": path.join(__dirname, "tests", "helpers", "storybook-loader.js" ),
"^.+\\.tsx?$": "ts-jest",
},
moduleNameMapper:{
"\\.(scss|css|png)$": __dirname+"/tests/helpers/css-mock.js"
"\\.(scss|css|png)$": path.join(__dirname,"tests","helpers","css-mock.js"),
"storm-react-diagrams": path.join(__dirname, "src", "main")
},
roots:[
__dirname+'/tests'

View File

@@ -1,6 +1,6 @@
{
"name": "storm-react-diagrams",
"version": "5.0.0",
"version": "5.1.1",
"repository": {
"type": "git",
"url": "https://github.com/projectstorm/react-diagrams.git"
@@ -20,61 +20,69 @@
"typings": "./dist/@types/src/main",
"author": "dylanvorster",
"scripts": {
"test:ci": "rm -rf ./dist && node ./tests/e2e/generate-e2e.js && jest --no-cache",
"test": "jest --no-cache",
"build:ts": "webpack",
"build:ts:prod": "cross-env NODE_ENV=production webpack",
"build:sass:prod": "node-sass --output-style compressed ./src/sass/main.scss > ./dist/style.min.css",
"storybook": "start-storybook -p 9001 -c .storybook",
"storybook:build": "build-storybook -c .storybook -o .out",
"storybook:github": "storybook-to-ghpages",
"prepublishOnly": "rm -rf ./dist && export NODE_ENV=production && webpack && ./node_modules/node-sass/bin/node-sass --output-style compressed ./src/sass/main.scss > ./dist/style.min.css",
"pretty": "prettier --use-tabs --write \"{src,demos}/**/*.{ts,tsx}\" --print-width 120"
"pretty": "prettier --use-tabs --write \"{src,demos,tests}/**/*.{ts,tsx}\" --print-width 120",
"lint": "tslint -p .",
"test:ci": "rm -rf ./dist && node ./tests/e2e/generate-e2e.js && jest --no-cache",
"test": "jest --no-cache",
"prepublishOnly": "yarn run build:ts:prod && yarn run build:sass:prod"
},
"dependencies": {
"closest": "^0.0.1",
"lodash": "^4.17.5",
"pathfinding": "^0.4.18",
"paths-js": "^0.4.5",
"paths-js": "^0.4.7",
"react": "^16.2.0"
},
"devDependencies": {
"@storybook/addon-actions": "^3.3.14",
"@storybook/addon-options": "^3.3.14",
"@storybook/addon-storyshots": "^3.3.14",
"@storybook/addons": "^3.3.14",
"@storybook/react": "^3.3.14",
"@storybook/addon-actions": "^3.3.15",
"@storybook/addon-options": "^3.3.15",
"@storybook/addon-storyshots": "^3.3.15",
"@storybook/addons": "^3.3.15",
"@storybook/react": "^3.3.15",
"@storybook/storybook-deployer": "^2.3.0",
"@types/jest": "^22.1.3",
"@types/jest": "^22.2.0",
"@types/lodash": "^4.14.104",
"@types/node": "^9.4.6",
"@types/node": "^9.4.7",
"@types/promise": "^7.1.30",
"@types/puppeteer": "^1.0.0",
"@types/react": "^16.0.38",
"copy-webpack-plugin": "^4.4.2",
"@types/puppeteer": "^1.1.0",
"@types/react": "^16.0.40",
"awesome-typescript-loader": "^4.0.1",
"copy-webpack-plugin": "^4.5.1",
"cross-env": "^5.1.4",
"css-loader": "^0.28.10",
"dagre": "^0.8.2",
"enzyme": "^3.3.0",
"file-loader": "^1.1.9",
"file-loader": "^1.1.11",
"glob": "^7.1.2",
"jest": "^22.4.2",
"jest-cli": "^22.4.2",
"json-beautify": "^1.0.1",
"node-sass": "^4.7.2",
"prettier": "^1.10.2",
"prettier": "^1.11.1",
"puppeteer": "^1.1.1",
"raf": "^3.4.0",
"raw-loader": "^0.5.1",
"react-dom": "^16.2.0",
"react-syntax-highlighter": "^7.0.1",
"react-syntax-highlighter": "^7.0.2",
"react-test-renderer": "^16.2.0",
"sass-loader": "^6.0.6",
"sass-loader": "^6.0.7",
"source-map-loader": "^0.2.3",
"storybook-host": "^4.1.5",
"storybook-readme": "^3.2.1",
"style-loader": "^0.20.2",
"ts-jest": "^22.0.4",
"ts-loader": "^3.5.0",
"style-loader": "^0.20.3",
"ts-jest": "^22.4.1",
"tslint": "^5.9.1",
"ts-loader": "^4.1.0",
"typescript": "^2.7.2",
"uglifyjs-webpack-plugin": "^1.2.3",
"val-loader": "^1.1.0",
"webpack": "^3.11.0"
"webpack": "^4.1.1",
"webpack-cli": "^2.0.11"
}
}

View File

@@ -1,48 +0,0 @@
import { BaseModel } from "./models/BaseModel";
import { NodeModel } from "./models/NodeModel";
import { LinkModel } from "./models/LinkModel";
import { DiagramEngine } from "./DiagramEngine";
import { PortModel } from "./models/PortModel";
import { PointModel } from "./models/PointModel";
import { LabelModel } from "./models/LabelModel";
export abstract class AbstractFactory<T extends BaseModel> {
type: string;
constructor(name: string) {
this.type = name;
}
getType(): string {
return this.type;
}
abstract getNewInstance(initialConfig?: any): T;
}
export abstract class NodeFactory<T extends NodeModel = NodeModel> extends AbstractFactory<T> {
abstract generateReactWidget(diagramEngine: DiagramEngine, node: T): JSX.Element;
}
export abstract class LinkFactory<T extends LinkModel = LinkModel> extends AbstractFactory<T> {
abstract generateReactWidget(diagramEngine: DiagramEngine, link: T): JSX.Element;
}
export abstract class LabelFactory<T extends LabelModel = LabelModel> extends AbstractFactory<T> {
abstract generateReactWidget(diagramEngine: DiagramEngine, label: T): JSX.Element;
}
export abstract class PortFactory<T extends PortModel = PortModel> extends AbstractFactory<T> {}
export class SimplePortFactory extends PortFactory {
cb: (initialConfig?: any) => PortModel;
constructor(type: string, cb: (initialConfig?: any) => PortModel) {
super(type);
this.cb = cb;
}
getNewInstance(initialConfig?: any): PortModel {
return this.cb(initialConfig);
}
}

View File

@@ -1,110 +0,0 @@
import { Toolkit } from "./Toolkit";
import * as _ from "lodash";
import { DiagramEngine } from "./DiagramEngine";
/**
* @author Dylan Vorster
*/
export interface BaseEvent<T extends BaseEntity = any> {
entity: BaseEntity<BaseListener>;
stopPropagation: () => any;
firing: boolean;
id: string;
}
export interface BaseListener<T extends BaseEntity = any> {
lockChanged?(event: BaseEvent<T> & { locked: boolean }): void;
}
export type BaseEntityType = "node" | "link" | "port" | "point";
export class BaseEntity<T extends BaseListener = BaseListener> {
public listeners: { [s: string]: T };
public id: string;
public locked: boolean;
constructor(id?: string) {
this.listeners = {};
this.id = id || Toolkit.UID();
this.locked = false;
}
getID() {
return this.id;
}
doClone(lookupTable = {}, clone) {}
clone(lookupTable = {}) {
// try and use an existing clone first
if (lookupTable[this.id]) {
return lookupTable[this.id];
}
let clone = _.clone(this);
clone.id = Toolkit.UID();
clone.clearListeners();
lookupTable[this.id] = clone;
this.doClone(lookupTable, clone);
return clone;
}
clearListeners() {
this.listeners = {};
}
public deSerialize(data, engine: DiagramEngine) {
this.id = data.id;
}
public serialize() {
return {
id: this.id
};
}
public iterateListeners(cb: (t: T, event: BaseEvent) => any) {
let event: BaseEvent = {
id: Toolkit.UID(),
firing: true,
entity: this,
stopPropagation: () => {
event.firing = false;
}
};
for (var i in this.listeners) {
// propagation stopped
if (!event.firing) {
return;
}
cb(this.listeners[i], event);
}
}
public removeListener(listener: string) {
if (this.listeners[listener]) {
delete this.listeners[listener];
return true;
}
return false;
}
public addListener(listener: T): string {
var uid = Toolkit.UID();
this.listeners[uid] = listener;
return uid;
}
public isLocked(): boolean {
return this.locked;
}
public setLocked(locked: boolean = true) {
this.locked = locked;
this.iterateListeners((listener, event) => {
if (listener.lockChanged) {
listener.lockChanged({ ...event, locked: locked });
}
});
}
}

View File

@@ -1,87 +0,0 @@
import { DiagramModel } from "./models/DiagramModel";
import { DiagramEngine } from "./DiagramEngine";
import { SelectionModel } from "./widgets/DiagramWidget";
import { PointModel } from "./models/PointModel";
import { NodeModel } from "./models/NodeModel";
export class BaseAction {
mouseX: number;
mouseY: number;
ms: number;
constructor(mouseX: number, mouseY: number) {
this.mouseX = mouseX;
this.mouseY = mouseY;
this.ms = new Date().getTime();
}
}
export class SelectingAction extends BaseAction {
mouseX2: number;
mouseY2: number;
constructor(mouseX: number, mouseY: number) {
super(mouseX, mouseY);
this.mouseX2 = mouseX;
this.mouseY2 = mouseY;
}
getBoxDimensions() {
return {
left: this.mouseX2 > this.mouseX ? this.mouseX : this.mouseX2,
top: this.mouseY2 > this.mouseY ? this.mouseY : this.mouseY2,
width: Math.abs(this.mouseX2 - this.mouseX),
height: Math.abs(this.mouseY2 - this.mouseY),
right: this.mouseX2 < this.mouseX ? this.mouseX : this.mouseX2,
bottom: this.mouseY2 < this.mouseY ? this.mouseY : this.mouseY2
};
}
containsElement(x: number, y: number, diagramModel: DiagramModel): boolean {
var z = diagramModel.getZoomLevel() / 100.0;
let dimensions = this.getBoxDimensions();
return (
x * z + diagramModel.getOffsetX() > dimensions.left &&
x * z + diagramModel.getOffsetX() < dimensions.right &&
y * z + diagramModel.getOffsetY() > dimensions.top &&
y * z + diagramModel.getOffsetY() < dimensions.bottom
);
}
}
export class MoveCanvasAction extends BaseAction {
initialOffsetX: number;
initialOffsetY: number;
constructor(mouseX: number, mouseY: number, diagramModel: DiagramModel) {
super(mouseX, mouseY);
this.initialOffsetX = diagramModel.getOffsetX();
this.initialOffsetY = diagramModel.getOffsetY();
}
}
export class MoveItemsAction extends BaseAction {
selectionModels: SelectionModel[];
moved: boolean;
constructor(mouseX: number, mouseY: number, diagramEngine: DiagramEngine) {
super(mouseX, mouseY);
this.moved = false;
diagramEngine.enableRepaintEntities(diagramEngine.getDiagramModel().getSelectedItems());
var selectedItems = diagramEngine.getDiagramModel().getSelectedItems();
//dont allow items which are locked to move
selectedItems = selectedItems.filter(item => {
return !diagramEngine.isModelLocked(item);
});
this.selectionModels = selectedItems.map((item: PointModel | NodeModel) => {
return {
model: item,
initialX: item.x,
initialY: item.y
};
});
}
}

View File

@@ -1,364 +1,26 @@
import { BaseEntity, BaseListener } from "./BaseEntity";
import { DiagramModel } from "./models/DiagramModel";
import * as _ from "lodash";
import { BaseModel, BaseModelListener } from "./models/BaseModel";
import { NodeModel } from "./models/NodeModel";
import { PointModel } from "./models/PointModel";
import { PortModel } from "./models/PortModel";
import { LinkModel } from "./models/LinkModel";
import { LabelFactory, LinkFactory, NodeFactory, PortFactory } from "./AbstractFactory";
import { DefaultLinkFactory, DefaultNodeFactory } from "./main";
import { ROUTING_SCALING_FACTOR } from "./routing/PathFinding";
import { DefaultPortFactory } from "./defaults/factories/DefaultPortFactory";
import { LabelModel } from "./models/LabelModel";
import { DefaultLabelFactory } from "./defaults/factories/DefaultLabelFactory";
import { Toolkit } from "./Toolkit";
/**
* @author Dylan Vorster
*/
export interface DiagramEngineListener extends BaseListener {
portFactoriesUpdated?(): void;
import { CanvasEngine } from "@projectstorm/react-canvas";
import { DefaultLabelFactory, DefaultLinkFactory, DefaultNodeFactory, DefaultPortFactory } from "storm-react-diagrams";
nodeFactoriesUpdated?(): void;
linkFactoriesUpdated?(): void;
labelFactoriesUpdated?(): void;
repaintCanvas?(): void;
}
/**
* Passed as a parameter to the DiagramWidget
*/
export class DiagramEngine extends BaseEntity<DiagramEngineListener> {
nodeFactories: { [s: string]: NodeFactory };
linkFactories: { [s: string]: LinkFactory };
portFactories: { [s: string]: PortFactory };
labelFactories: { [s: string]: LabelFactory };
diagramModel: DiagramModel;
canvas: Element;
export class DiagramEngine extends CanvasEngine<DiagramModel> {
paintableWidgets: {};
linksThatHaveInitiallyRendered: {};
nodesRendered: boolean;
maxNumberPointsPerLink: number;
smartRouting: boolean;
// calculated only when smart routing is active
canvasMatrix: number[][] = [];
routingMatrix: number[][] = [];
// used when at least one element has negative coordinates
hAdjustmentFactor: number = 0;
vAdjustmentFactor: number = 0;
constructor() {
super();
this.diagramModel = new DiagramModel();
this.nodeFactories = {};
this.linkFactories = {};
this.portFactories = {};
this.labelFactories = {};
this.canvas = null;
this.paintableWidgets = null;
this.linksThatHaveInitiallyRendered = {};
if (Toolkit.TESTING) {
Toolkit.TESTING_UID = 0;
//pop it onto the window so our E2E helpers can find it
if (window) {
(window as any)["diagram_instance"] = this;
}
}
this.smartRouting = false;
}
installDefaultFactories() {
this.registerNodeFactory(new DefaultNodeFactory());
this.registerLinkFactory(new DefaultLinkFactory());
this.registerPortFactory(new DefaultPortFactory());
this.registerLabelFactory(new DefaultLabelFactory());
}
repaintCanvas() {
this.iterateListeners(listener => {
listener.repaintCanvas && listener.repaintCanvas();
});
}
clearRepaintEntities() {
this.paintableWidgets = null;
}
enableRepaintEntities(entities: BaseModel<BaseEntity, BaseModelListener>[]) {
this.paintableWidgets = {};
entities.forEach(entity => {
//if a node is requested to repaint, add all of its links
if (entity instanceof NodeModel) {
_.forEach(entity.getPorts(), port => {
_.forEach(port.getLinks(), link => {
this.paintableWidgets[link.getID()] = true;
});
});
}
if (entity instanceof PointModel) {
this.paintableWidgets[entity.getLink().getID()] = true;
}
this.paintableWidgets[entity.getID()] = true;
});
}
/**
* Checks to see if a model is locked by running through
* its parents to see if they are locked first
*/
isModelLocked(model: BaseEntity<BaseListener>) {
//always check the diagram model
if (this.diagramModel.isLocked()) {
return true;
}
return model.isLocked();
}
recalculatePortsVisually() {
this.nodesRendered = false;
this.linksThatHaveInitiallyRendered = {};
}
canEntityRepaint(baseModel: BaseModel<BaseEntity, BaseModelListener>) {
//no rules applied, allow repaint
if (this.paintableWidgets === null) {
return true;
}
return this.paintableWidgets[baseModel.getID()] !== undefined;
}
setCanvas(canvas: Element | null) {
this.canvas = canvas;
}
setDiagramModel(model: DiagramModel) {
this.diagramModel = model;
this.recalculatePortsVisually();
}
getDiagramModel(): DiagramModel {
return this.diagramModel;
}
//!-------------- FACTORIES ------------
getNodeFactories(): { [s: string]: NodeFactory } {
return this.nodeFactories;
}
getLinkFactories(): { [s: string]: LinkFactory } {
return this.linkFactories;
}
getLabelFactories(): { [s: string]: LabelFactory } {
return this.labelFactories;
}
registerLabelFactory(factory: LabelFactory) {
this.labelFactories[factory.getType()] = factory;
this.iterateListeners(listener => {
if (listener.labelFactoriesUpdated) {
listener.labelFactoriesUpdated();
}
});
}
registerPortFactory(factory: PortFactory) {
this.portFactories[factory.getType()] = factory;
this.iterateListeners(listener => {
if (listener.portFactoriesUpdated) {
listener.portFactoriesUpdated();
}
});
}
registerNodeFactory(factory: NodeFactory) {
this.nodeFactories[factory.getType()] = factory;
this.iterateListeners(listener => {
if (listener.nodeFactoriesUpdated) {
listener.nodeFactoriesUpdated();
}
});
}
registerLinkFactory(factory: LinkFactory) {
this.linkFactories[factory.getType()] = factory;
this.iterateListeners(listener => {
if (listener.linkFactoriesUpdated) {
listener.linkFactoriesUpdated();
}
});
}
getPortFactory(type: string): PortFactory {
if (this.portFactories[type]) {
return this.portFactories[type];
}
console.log("cannot find factory for port of type: [" + type + "]");
return null;
}
getNodeFactory(type: string): NodeFactory {
if (this.nodeFactories[type]) {
return this.nodeFactories[type];
}
console.log("cannot find factory for node of type: [" + type + "]");
return null;
}
getLinkFactory(type: string): LinkFactory {
if (this.linkFactories[type]) {
return this.linkFactories[type];
}
console.log("cannot find factory for link of type: [" + type + "]");
return null;
}
getLabelFactory(type: string): LabelFactory {
if (this.labelFactories[type]) {
return this.labelFactories[type];
}
console.log("cannot find factory for label of type: [" + type + "]");
return null;
}
getFactoryForNode(node: NodeModel): NodeFactory | null {
return this.getNodeFactory(node.getType());
}
getFactoryForLink(link: LinkModel): LinkFactory | null {
return this.getLinkFactory(link.getType());
}
getFactoryForLabel(label: LabelModel): LabelFactory | null {
return this.getLabelFactory(label.getType());
}
generateWidgetForLink(link: LinkModel): JSX.Element | null {
var linkFactory = this.getFactoryForLink(link);
if (!linkFactory) {
throw new Error("Cannot find link factory for link: " + link.getType());
}
return linkFactory.generateReactWidget(this, link);
}
generateWidgetForNode(node: NodeModel): JSX.Element | null {
var nodeFactory = this.getFactoryForNode(node);
if (!nodeFactory) {
throw new Error("Cannot find widget factory for node: " + node.getType());
}
return nodeFactory.generateReactWidget(this, node);
}
getRelativeMousePoint(event): { x: number; y: number } {
var point = this.getRelativePoint(event.clientX, event.clientY);
return {
x: (point.x - this.diagramModel.getOffsetX()) / (this.diagramModel.getZoomLevel() / 100.0),
y: (point.y - this.diagramModel.getOffsetY()) / (this.diagramModel.getZoomLevel() / 100.0)
};
}
getRelativePoint(x, y) {
var canvasRect = this.canvas.getBoundingClientRect();
return { x: x - canvasRect.left, y: y - canvasRect.top };
}
getNodeElement(node: NodeModel): Element {
const selector = this.canvas.querySelector('.node[data-nodeid="' + node.getID() + '"]');
if (selector === null) {
throw new Error("Cannot find Node element with nodeID: [" + node.getID() + "]");
}
return selector;
}
getNodePortElement(port: PortModel): any {
var selector = this.canvas.querySelector(
'.port[data-name="' + port.getName() + '"][data-nodeid="' + port.getParent().getID() + '"]'
);
if (selector === null) {
throw new Error(
"Cannot find Node Port element with nodeID: [" +
port.getParent().getID() +
"] and name: [" +
port.getName() +
"]"
);
}
return selector;
}
getPortCenter(port: PortModel) {
var sourceElement = this.getNodePortElement(port);
var sourceRect = sourceElement.getBoundingClientRect();
var rel = this.getRelativePoint(sourceRect.left, sourceRect.top);
return {
x:
sourceElement.offsetWidth / 2 +
(rel.x - this.diagramModel.getOffsetX()) / (this.diagramModel.getZoomLevel() / 100.0),
y:
sourceElement.offsetHeight / 2 +
(rel.y - this.diagramModel.getOffsetY()) / (this.diagramModel.getZoomLevel() / 100.0)
};
}
/**
* Calculate rectangular coordinates of the port passed in.
*/
getPortCoords(
port: PortModel
): {
x: number;
y: number;
width: number;
height: number;
} {
const sourceElement = this.getNodePortElement(port);
const sourceRect = sourceElement.getBoundingClientRect();
const canvasRect = this.canvas.getBoundingClientRect() as ClientRect;
return {
x:
(sourceRect.x - this.diagramModel.getOffsetX()) / (this.diagramModel.getZoomLevel() / 100.0) -
canvasRect.left,
y:
(sourceRect.y - this.diagramModel.getOffsetY()) / (this.diagramModel.getZoomLevel() / 100.0) -
canvasRect.top,
width: sourceRect.width,
height: sourceRect.height
};
}
/**
* Determine the width and height of the node passed in.
* It currently assumes nodes have a rectangular shape, can be overriden for customised shapes.
*/
getNodeDimensions(node: NodeModel): { width: number; height: number } {
if (!this.canvas) {
return {
width: 0,
height: 0
};
}
const nodeElement = this.getNodeElement(node);
const nodeRect = nodeElement.getBoundingClientRect();
return {
width: nodeRect.width,
height: nodeRect.height
};
installDefaults() {
super.installDefaults();
this.registerElementFactory(new DefaultLabelFactory());
this.registerElementFactory(new DefaultLinkFactory());
this.registerElementFactory(new DefaultNodeFactory());
this.registerElementFactory(new DefaultPortFactory());
}
getMaxNumberPointsPerLink(): number {
@@ -370,213 +32,10 @@ export class DiagramEngine extends BaseEntity<DiagramEngineListener> {
}
isSmartRoutingEnabled() {
return !!this.smartRouting;
return this.smartRouting;
}
setSmartRoutingStatus(status: boolean) {
this.smartRouting = status;
}
/**
* A representation of the canvas in the following format:
*
* +-----------------+
* | 0 0 0 0 0 0 0 0 |
* | 0 0 0 0 0 0 0 0 |
* | 0 0 0 0 0 0 0 0 |
* | 0 0 0 0 0 0 0 0 |
* | 0 0 0 0 0 0 0 0 |
* +-----------------+
*
* In which all walkable points are marked by zeros.
* It uses @link{#ROUTING_SCALING_FACTOR} to reduce the matrix dimensions and improve performance.
*/
getCanvasMatrix(): number[][] {
if (this.canvasMatrix.length === 0) {
this.calculateCanvasMatrix();
}
return this.canvasMatrix;
}
calculateCanvasMatrix() {
const {
width: canvasWidth,
hAdjustmentFactor,
height: canvasHeight,
vAdjustmentFactor
} = this.calculateMatrixDimensions();
this.hAdjustmentFactor = hAdjustmentFactor;
this.vAdjustmentFactor = vAdjustmentFactor;
const matrixWidth = Math.ceil(canvasWidth / ROUTING_SCALING_FACTOR);
const matrixHeight = Math.ceil(canvasHeight / ROUTING_SCALING_FACTOR);
this.canvasMatrix = _.range(0, matrixHeight).map(() => {
return new Array(matrixWidth).fill(0);
});
}
/**
* A representation of the canvas in the following format:
*
* +-----------------+
* | 0 0 1 1 0 0 0 0 |
* | 0 0 1 1 0 0 1 1 |
* | 0 0 0 0 0 0 1 1 |
* | 1 1 0 0 0 0 0 0 |
* | 1 1 0 0 0 0 0 0 |
* +-----------------+
*
* In which all points blocked by a node (and its ports) are
* marked as 1; points were there is nothing (ie, free) receive 0.
*/
getRoutingMatrix(): number[][] {
if (this.routingMatrix.length === 0) {
this.calculateRoutingMatrix();
}
return this.routingMatrix;
}
calculateRoutingMatrix(): void {
const matrix = _.cloneDeep(this.getCanvasMatrix());
// nodes need to be marked as blocked points
this.markNodes(matrix);
// same thing for ports
this.markPorts(matrix);
this.routingMatrix = matrix;
}
/**
* The routing matrix does not have negative indexes, but elements could be negatively positioned.
* We use the functions below to translate back and forth between these coordinates, relying on the
* calculated values of hAdjustmentFactor and vAdjustmentFactor.
*/
translateRoutingX(x: number, reverse: boolean = false) {
return x + this.hAdjustmentFactor * (reverse ? -1 : 1);
}
translateRoutingY(y: number, reverse: boolean = false) {
return y + this.vAdjustmentFactor * (reverse ? -1 : 1);
}
/**
* Despite being a long method, we simply iterate over all three collections (nodes, ports and points)
* to find the highest X and Y dimensions, so we can build the matrix large enough to contain all elements.
*/
calculateMatrixDimensions = (): {
width: number;
hAdjustmentFactor: number;
height: number;
vAdjustmentFactor: number;
} => {
const allNodesCoords = _.values(this.diagramModel.nodes).map(item => ({
x: item.x,
width: item.width,
y: item.y,
height: item.height
}));
const allLinks = _.values(this.diagramModel.links);
const allPortsCoords = _.flatMap(allLinks.map(link => [link.sourcePort, link.targetPort]))
.filter(port => port !== null)
.map(item => ({
x: item.x,
width: item.width,
y: item.y,
height: item.height
}));
const allPointsCoords = _.flatMap(allLinks.map(link => link.points)).map(item => ({
// points don't have width/height, so let's just use 0
x: item.x,
width: 0,
y: item.y,
height: 0
}));
const canvas = this.canvas as HTMLDivElement;
const minX =
Math.floor(
Math.min(_.minBy(_.concat(allNodesCoords, allPortsCoords, allPointsCoords), item => item.x).x, 0) /
ROUTING_SCALING_FACTOR
) * ROUTING_SCALING_FACTOR;
const maxXElement = _.maxBy(
_.concat(allNodesCoords, allPortsCoords, allPointsCoords),
item => item.x + item.width
);
const maxX = Math.max(maxXElement.x + maxXElement.width, canvas.offsetWidth);
const minY =
Math.floor(
Math.min(_.minBy(_.concat(allNodesCoords, allPortsCoords, allPointsCoords), item => item.y).y, 0) /
ROUTING_SCALING_FACTOR
) * ROUTING_SCALING_FACTOR;
const maxYElement = _.maxBy(
_.concat(allNodesCoords, allPortsCoords, allPointsCoords),
item => item.y + item.height
);
const maxY = Math.max(maxYElement.y + maxYElement.height, canvas.offsetHeight);
return {
width: Math.ceil(Math.abs(minX) + maxX),
hAdjustmentFactor: Math.abs(minX) / ROUTING_SCALING_FACTOR + 1,
height: Math.ceil(Math.abs(minY) + maxY),
vAdjustmentFactor: Math.abs(minY) / ROUTING_SCALING_FACTOR + 1
};
};
/**
* Updates (by reference) where nodes will be drawn on the matrix passed in.
*/
markNodes = (matrix: number[][]): void => {
_.values(this.diagramModel.nodes).forEach(node => {
const startX = Math.floor(node.x / ROUTING_SCALING_FACTOR);
const endX = Math.ceil((node.x + node.width) / ROUTING_SCALING_FACTOR);
const startY = Math.floor(node.y / ROUTING_SCALING_FACTOR);
const endY = Math.ceil((node.y + node.height) / ROUTING_SCALING_FACTOR);
for (let x = startX - 1; x <= endX + 1; x++) {
for (let y = startY - 1; y < endY + 1; y++) {
this.markMatrixPoint(matrix, this.translateRoutingX(x), this.translateRoutingY(y));
}
}
});
};
/**
* Updates (by reference) where ports will be drawn on the matrix passed in.
*/
markPorts = (matrix: number[][]): void => {
const allElements = _.flatMap(
_.values(this.diagramModel.links).map(link => [].concat(link.sourcePort, link.targetPort))
);
allElements.filter(port => port !== null).forEach(port => {
const startX = Math.floor(port.x / ROUTING_SCALING_FACTOR);
const endX = Math.ceil((port.x + port.width) / ROUTING_SCALING_FACTOR);
const startY = Math.floor(port.y / ROUTING_SCALING_FACTOR);
const endY = Math.ceil((port.y + port.height) / ROUTING_SCALING_FACTOR);
for (let x = startX - 1; x <= endX + 1; x++) {
for (let y = startY - 1; y < endY + 1; y++) {
this.markMatrixPoint(matrix, this.translateRoutingX(x), this.translateRoutingY(y));
}
}
});
};
markMatrixPoint = (matrix: number[][], x: number, y: number) => {
if (matrix[y] !== undefined && matrix[y][x] !== undefined) {
matrix[y][x] = 1;
}
};
zoomToFit() {
const xFactor = this.canvas.clientWidth / this.canvas.scrollWidth;
const yFactor = this.canvas.clientHeight / this.canvas.scrollHeight;
const zoomFactor = xFactor < yFactor ? xFactor : yFactor;
this.diagramModel.setZoomLevel(this.diagramModel.getZoomLevel() * zoomFactor);
this.diagramModel.setOffset(0, 0);
this.repaintCanvas();
}
}

View File

@@ -1,30 +1,13 @@
// tslint:disable no-bitwise
import closest = require("closest");
import { PointModel } from "./models/PointModel";
import { ROUTING_SCALING_FACTOR } from "./routing/PathFinding";
import * as Path from "paths-js/path";
import { Toolkit as TK } from "@projectstorm/react-canvas";
/**
* @author Dylan Vorster
*/
export class Toolkit {
static TESTING: boolean = false;
static TESTING_UID = 0;
/**
* Generats a unique ID (thanks Stack overflow :3)
* @returns {String}
*/
public static UID(): string {
if (Toolkit.TESTING) {
Toolkit.TESTING_UID++;
return "" + Toolkit.TESTING_UID;
}
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);
});
}
export class Toolkit extends TK {
/**
* Finds the closest element as a polyfill
*
@@ -43,9 +26,12 @@ export class Toolkit {
}
public static generateCurvePath(firstPoint: PointModel, lastPoint: PointModel, curvy: number = 0): string {
return `M${firstPoint.x},${firstPoint.y} C ${firstPoint.x + curvy},${firstPoint.y} ${lastPoint.x + -curvy},${
lastPoint.y
} ${lastPoint.x},${lastPoint.y}`;
var isHorizontal = Math.abs(firstPoint.x - lastPoint.x) > Math.abs(firstPoint.y - lastPoint.y);
var curvyX = isHorizontal ? curvy : 0;
var curvyY = isHorizontal ? 0 : curvy;
return `M${firstPoint.x},${firstPoint.y} C ${firstPoint.x + curvyX},${firstPoint.y + curvyY}
${lastPoint.x - curvyX},${lastPoint.y - curvyY} ${lastPoint.x},${lastPoint.y}`;
}
public static generateDynamicPath(pathCoords: number[][]) {

View File

@@ -1,22 +1,19 @@
import * as React from "react";
import { DiagramEngine } from "../../DiagramEngine";
import { LabelFactory } from "../../AbstractFactory";
import { DefaultLabelModel } from "../models/DefaultLabelModel";
import { DefaultLabelWidget } from "../widgets/DefaultLabelWidget";
import { AbstractElementFactory } from "@projectstorm/react-canvas";
/**
* @author Dylan Vorster
*/
export class DefaultLabelFactory extends LabelFactory<DefaultLabelModel> {
export class DefaultLabelFactory extends AbstractElementFactory<DefaultLabelModel> {
constructor() {
super("default");
}
generateReactWidget(diagramEngine: DiagramEngine, label: DefaultLabelModel): JSX.Element {
return <DefaultLabelWidget model={label} />;
generateWidget(engine: DiagramEngine, model: DefaultLabelModel): JSX.Element {
return <DefaultLabelWidget model={model} />;
}
getNewInstance(initialConfig?: any): DefaultLabelModel {
generateModel(): DefaultLabelModel {
return new DefaultLabelModel();
}
}

View File

@@ -1,25 +1,22 @@
import * as React from "react";
import { DefaultLinkWidget } from "../widgets/DefaultLinkWidget";
import { DiagramEngine } from "../../DiagramEngine";
import { LinkFactory } from "../../AbstractFactory";
import { AbstractElementFactory } from "@projectstorm/react-canvas";
import { DefaultLinkModel } from "../models/DefaultLinkModel";
/**
* @author Dylan Vorster
*/
export class DefaultLinkFactory extends LinkFactory<DefaultLinkModel> {
export class DefaultLinkFactory extends AbstractElementFactory<DefaultLinkModel> {
constructor() {
super("default");
}
generateReactWidget(diagramEngine: DiagramEngine, link: DefaultLinkModel): JSX.Element {
generateWidget(engine: DiagramEngine, model: DefaultLinkModel): JSX.Element {
return React.createElement(DefaultLinkWidget, {
link: link,
diagramEngine: diagramEngine
link: model,
diagramEngine: engine
});
}
getNewInstance(initialConfig?: any): DefaultLinkModel {
generateModel(): DefaultLinkModel {
return new DefaultLinkModel();
}
@@ -27,8 +24,8 @@ export class DefaultLinkFactory extends LinkFactory<DefaultLinkModel> {
return (
<path
className={selected ? widget.bem("--path-selected") : ""}
strokeWidth={model.width}
stroke={model.color}
strokeWidth={model.getWidth()}
stroke={model.getColor()}
d={path}
/>
);

View File

@@ -2,23 +2,21 @@ import { DefaultNodeModel } from "../models/DefaultNodeModel";
import * as React from "react";
import { DefaultNodeWidget } from "../widgets/DefaultNodeWidget";
import { DiagramEngine } from "../../DiagramEngine";
import { NodeFactory } from "../../AbstractFactory";
/**
* @author Dylan Vorster
*/
export class DefaultNodeFactory extends NodeFactory<DefaultNodeModel> {
import { AbstractElementFactory } from "@projectstorm/react-canvas";
export class DefaultNodeFactory extends AbstractElementFactory<DefaultNodeModel> {
constructor() {
super("default");
}
generateReactWidget(diagramEngine: DiagramEngine, node: DefaultNodeModel): JSX.Element {
generateWidget(diagramEngine: DiagramEngine, model: DefaultNodeModel): JSX.Element {
return React.createElement(DefaultNodeWidget, {
node: node,
node: model,
diagramEngine: diagramEngine
});
}
getNewInstance(initialConfig?: any): DefaultNodeModel {
generateModel(): DefaultNodeModel {
return new DefaultNodeModel();
}
}

View File

@@ -1,12 +1,17 @@
import { DefaultPortModel } from "../models/DefaultPortModel";
import { PortFactory } from "../../AbstractFactory";
import { AbstractElementFactory } from "@projectstorm/react-canvas";
import { DiagramEngine } from "storm-react-diagrams";
export class DefaultPortFactory extends PortFactory<DefaultPortModel> {
export class DefaultPortFactory extends AbstractElementFactory<DefaultPortModel> {
constructor() {
super("default");
}
getNewInstance(initialConfig?: any): DefaultPortModel {
generateWidget(engine: DiagramEngine, model: DefaultPortModel): JSX.Element {
return null;
}
generateModel(): DefaultPortModel {
return new DefaultPortModel(true, "unknown");
}
}

View File

@@ -1,7 +1,9 @@
import { LabelModel } from "../../models/LabelModel";
import * as _ from "lodash";
import { DeserializeEvent } from "@projectstorm/react-canvas";
export class DefaultLabelModel extends LabelModel {
label: string;
protected label: string;
constructor() {
super("default");
@@ -11,4 +13,15 @@ export class DefaultLabelModel extends LabelModel {
setLabel(label: string) {
this.label = label;
}
deSerialize(event: DeserializeEvent) {
super.deSerialize(event);
this.label = event.data.label;
}
serialize() {
return _.merge(super.serialize(), {
label: this.label
});
}
}

View File

@@ -1,24 +1,19 @@
/**
* @author Dylan Vorster
*/
import { LinkModel, LinkModelListener } from "../../models/LinkModel";
import { BaseEvent } from "../../BaseEntity";
import * as _ from "lodash";
import { PointModel } from "../../models/PointModel";
import { DiagramEngine } from "../../DiagramEngine";
import { DefaultLabelModel } from "./DefaultLabelModel";
import { LabelModel } from "../../models/LabelModel";
import { BaseEvent, DeserializeEvent } from "@projectstorm/react-canvas";
export interface DefaultLinkModelListener extends LinkModelListener {
colorChanged?(event: BaseEvent<DefaultLinkModel> & { color: null | string });
colorChanged?(event: BaseEvent<DefaultLinkModel> & { color: null | string }): void;
widthChanged?(event: BaseEvent<DefaultLinkModel> & { width: 0 | number });
widthChanged?(event: BaseEvent<DefaultLinkModel> & { width: 0 | number }): void;
}
export class DefaultLinkModel extends LinkModel<DefaultLinkModelListener> {
width: number;
color: string;
curvyness: number;
protected width: number;
protected color: string;
protected curvyness: number;
constructor(type: string = "default") {
super(type);
@@ -35,11 +30,11 @@ export class DefaultLinkModel extends LinkModel<DefaultLinkModelListener> {
});
}
deSerialize(ob, engine: DiagramEngine) {
super.deSerialize(ob, engine);
this.color = ob.color;
this.width = ob.width;
this.curvyness = ob.curvyness;
deSerialize(event: DeserializeEvent) {
super.deSerialize(event);
this.color = event.data.color;
this.width = event.data.width;
this.curvyness = event.data.curvyness;
}
addLabel(label: LabelModel | string) {
@@ -53,15 +48,31 @@ export class DefaultLinkModel extends LinkModel<DefaultLinkModelListener> {
setWidth(width: number) {
this.width = width;
this.iterateListeners((listener: DefaultLinkModelListener, event: BaseEvent) => {
listener.widthChanged && listener.widthChanged({ ...event, width: width });
this.iterateListeners("width changed", (listener: DefaultLinkModelListener, event: BaseEvent) => {
if (listener.widthChanged) {
listener.widthChanged({ ...event, width: width });
}
});
}
setColor(color: string) {
this.color = color;
this.iterateListeners((listener: DefaultLinkModelListener, event: BaseEvent) => {
listener.colorChanged && listener.colorChanged({ ...event, color: color });
this.iterateListeners("color changed", (listener: DefaultLinkModelListener, event: BaseEvent) => {
if (listener.colorChanged) {
listener.colorChanged({ ...event, color: color });
}
});
}
getWidth() {
return this.width;
}
getColor() {
return this.color;
}
getCurvyness() {
return this.curvyness;
}
}

View File

@@ -1,17 +1,12 @@
import { DefaultPortModel } from "./DefaultPortModel";
import * as _ from "lodash";
import { DefaultPortModel } from "./DefaultPortModel";
import { NodeModel } from "../../models/NodeModel";
import { Toolkit } from "../../Toolkit";
import { DiagramEngine } from "../../DiagramEngine";
import { DeserializeEvent } from "@projectstorm/react-canvas";
/**
* @author Dylan Vorster
*/
export class DefaultNodeModel extends NodeModel {
name: string;
color: string;
ports: { [s: string]: DefaultPortModel };
export class DefaultNodeModel extends NodeModel<DefaultPortModel> {
protected name: string;
protected color: string;
constructor(name: string = "Untitled", color: string = "rgb(0,192,255)") {
super("default");
@@ -27,10 +22,10 @@ export class DefaultNodeModel extends NodeModel {
return this.addPort(new DefaultPortModel(false, Toolkit.UID(), label));
}
deSerialize(object, engine: DiagramEngine) {
super.deSerialize(object, engine);
this.name = object.name;
this.color = object.color;
deSerialize(event: DeserializeEvent) {
super.deSerialize(event);
this.name = event.data.name;
this.color = event.data.color;
}
serialize() {
@@ -41,13 +36,13 @@ export class DefaultNodeModel extends NodeModel {
}
getInPorts(): DefaultPortModel[] {
return _.filter(this.ports, portModel => {
return _.filter(this.getPorts(), portModel => {
return portModel.in;
});
}
getOutPorts(): DefaultPortModel[] {
return _.filter(this.ports, portModel => {
return _.filter(this.getPorts(), portModel => {
return !portModel.in;
});
}

View File

@@ -1,24 +1,24 @@
import * as _ from "lodash";
import { PortModel } from "../../models/PortModel";
import { DiagramEngine } from "../../DiagramEngine";
import { DefaultLinkModel } from "./DefaultLinkModel";
import { LinkModel } from "../../models/LinkModel";
import { DeserializeEvent } from "@projectstorm/react-canvas";
export class DefaultPortModel extends PortModel {
in: boolean;
label: string;
links: { [id: string]: DefaultLinkModel };
constructor(isInput: boolean, name: string, label: string = null, id?: string) {
super(name, "default", id);
constructor(isInput: boolean, name: string, label: string = null) {
super(name, "default");
this.in = isInput;
this.label = label || name;
}
deSerialize(object, engine: DiagramEngine) {
super.deSerialize(object, engine);
this.in = object.in;
this.label = object.label;
deSerialize(event: DeserializeEvent) {
super.deSerialize(event);
this.in = event.data.in;
this.label = event.data.label;
}
serialize() {

View File

@@ -1,6 +1,6 @@
import * as React from "react";
import { DefaultLabelModel } from "../models/DefaultLabelModel";
import { BaseWidget, BaseWidgetProps } from "../../widgets/BaseWidget";
import { BaseWidget, BaseWidgetProps } from "@projectstorm/react-canvas";
export interface DefaultLabelWidgetProps extends BaseWidgetProps {
model: DefaultLabelModel;

View File

@@ -7,7 +7,7 @@ import { DefaultLinkModel } from "../models/DefaultLinkModel";
import PathFinding from "../../routing/PathFinding";
import * as _ from "lodash";
import { LabelModel } from "../../models/LabelModel";
import { BaseWidget, BaseWidgetProps } from "../../widgets/BaseWidget";
import { BaseWidget, BaseWidgetProps } from "@projectstorm/react-canvas";
export interface DefaultLinkProps extends BaseWidgetProps {
color?: string;
@@ -70,7 +70,7 @@ export class DefaultLinkWidget extends BaseWidget<DefaultLinkProps, DefaultLinkS
}
}
addPointToLink = (event: MouseEvent, index: number): void => {
addPointToLink(event: MouseEvent, index: number) {
if (
!event.shiftKey &&
!this.props.diagramEngine.isModelLocked(this.props.link) &&
@@ -82,14 +82,14 @@ export class DefaultLinkWidget extends BaseWidget<DefaultLinkProps, DefaultLinkS
this.props.link.addPoint(point, index);
this.props.pointAdded(point, event);
}
};
}
generatePoint(pointIndex: number): JSX.Element {
let x = this.props.link.points[pointIndex].x;
let y = this.props.link.points[pointIndex].y;
let x = this.props.link.points[pointIndex].getPoint().x;
let y = this.props.link.points[pointIndex].getPoint().y;
return (
<g key={"point-" + this.props.link.points[pointIndex].id}>
<g key={"point-" + this.props.link.points[pointIndex].getID()}>
<circle
cx={x}
cy={y}
@@ -107,8 +107,8 @@ export class DefaultLinkWidget extends BaseWidget<DefaultLinkProps, DefaultLinkS
onMouseEnter={() => {
this.setState({ selected: true });
}}
data-id={this.props.link.points[pointIndex].id}
data-linkid={this.props.link.id}
data-id={this.props.link.points[pointIndex].getID()}
data-linkid={this.props.link.getID()}
cx={x}
cy={y}
r={15}
@@ -123,15 +123,15 @@ export class DefaultLinkWidget extends BaseWidget<DefaultLinkProps, DefaultLinkS
const canvas = this.props.diagramEngine.canvas as HTMLElement;
return (
<foreignObject
key={label.id}
key={label.getID()}
className={this.bem("__label")}
width={canvas.offsetWidth}
height={canvas.offsetHeight}
>
<div ref={ref => (this.refLabels[label.id] = ref)}>
<div ref={ref => (this.refLabels[label.getID()] = ref)}>
{this.props.diagramEngine
.getFactoryForLabel(label)
.generateReactWidget(this.props.diagramEngine, label)}
.getFactoryForElement(label)
.generateWidget(this.props.diagramEngine, label)}
</div>
</foreignObject>
);
@@ -141,7 +141,7 @@ export class DefaultLinkWidget extends BaseWidget<DefaultLinkProps, DefaultLinkS
var props = this.props;
var Bottom = React.cloneElement(
(props.diagramEngine.getFactoryForLink(this.props.link) as DefaultLinkFactory).generateLinkSegment(
(props.diagramEngine.getFactoryForElement(this.props.link) as DefaultLinkFactory).generateLinkSegment(
this.props.link,
this,
this.state.selected || this.props.link.isSelected(),
@@ -267,7 +267,10 @@ export class DefaultLinkWidget extends BaseWidget<DefaultLinkProps, DefaultLinkS
if (this.isSmartRoutingApplicable()) {
// first step: calculate a direct path between the points being linked
const directPathCoords = this.pathFinding.calculateDirectPath(_.first(points), _.last(points));
const directPathCoords = this.pathFinding.calculateDirectPath(
_.first(points).getPoint(),
_.last(points).getPoint()
);
const routingMatrix = diagramEngine.getRoutingMatrix();
// now we need to extract, from the routing matrix, the very first walkable points
@@ -305,10 +308,15 @@ export class DefaultLinkWidget extends BaseWidget<DefaultLinkProps, DefaultLinkS
// See @link{#isSmartRoutingApplicable()}.
if (paths.length === 0) {
if (points.length === 2) {
var isHorizontal =
Math.abs(points[0].getPoint().x - points[1].getPoint().x) >
Math.abs(points[0].getPoint().x - points[1].getPoint().y);
var xOrY = isHorizontal ? "x" : "y";
//draw the smoothing
//if the points are too close, just draw a straight line
var margin = 50;
if (Math.abs(points[0].x - points[1].x) < 50) {
if (Math.abs(points[0][xOrY] - points[1][xOrY]) < 50) {
margin = 5;
}
@@ -317,14 +325,14 @@ export class DefaultLinkWidget extends BaseWidget<DefaultLinkProps, DefaultLinkS
//some defensive programming to make sure the smoothing is
//always in the right direction
if (pointLeft.x > pointRight.x) {
if (pointLeft[xOrY] > pointRight[xOrY]) {
pointLeft = points[1];
pointRight = points[0];
}
paths.push(
this.generateLink(
Toolkit.generateCurvePath(pointLeft, pointRight, this.props.link.curvyness),
Toolkit.generateCurvePath(pointLeft, pointRight, this.props.link.getCurvyness()),
{
onMouseDown: event => {
this.addPointToLink(event, 1);
@@ -340,18 +348,18 @@ export class DefaultLinkWidget extends BaseWidget<DefaultLinkProps, DefaultLinkS
}
} else {
//draw the multiple anchors and complex line instead
for (let i = 0; i < points.length - 1; i++) {
for (let j = 0; j < points.length - 1; j++) {
paths.push(
this.generateLink(
Toolkit.generateLinePath(points[i], points[i + 1]),
Toolkit.generateLinePath(points[j], points[j + 1]),
{
"data-linkid": this.props.link.id,
"data-point": i,
"data-linkid": this.props.link.getID(),
"data-point": j,
onMouseDown: (event: MouseEvent) => {
this.addPointToLink(event, i + 1);
this.addPointToLink(event, j + 1);
}
},
i
j
)
);
}

View File

@@ -3,7 +3,7 @@ import * as _ from "lodash";
import { DefaultNodeModel } from "../models/DefaultNodeModel";
import { DefaultPortLabel } from "./DefaultPortLabelWidget";
import { DiagramEngine } from "../../DiagramEngine";
import { BaseWidget, BaseWidgetProps } from "../../widgets/BaseWidget";
import { BaseWidget, BaseWidgetProps } from "@projectstorm/react-canvas";
export interface DefaultNodeProps extends BaseWidgetProps {
node: DefaultNodeModel;

View File

@@ -1,7 +1,7 @@
import * as React from "react";
import { DefaultPortModel } from "../models/DefaultPortModel";
import { PortWidget } from "../../widgets/PortWidget";
import { BaseWidget, BaseWidgetProps } from "../../widgets/BaseWidget";
import { BaseWidget, BaseWidgetProps } from "@projectstorm/react-canvas";
export interface DefaultPortLabelProps extends BaseWidgetProps {
model: DefaultPortModel;

View File

@@ -2,37 +2,32 @@
* @author Dylan Vorster
*/
//export defaults
export * from "./Toolkit";
export * from "./DiagramEngine";
export * from "./defaults/models/DefaultNodeModel";
export * from "./defaults/models/DefaultPortModel";
export * from "./defaults/models/DefaultLinkModel";
export * from "./defaults/models/DefaultLabelModel";
export * from "./defaults/factories/DefaultLinkFactory";
export * from "./defaults/factories/DefaultNodeFactory";
export * from "./defaults/factories/DefaultPortFactory";
export * from "./defaults/factories/DefaultLabelFactory";
export * from "./defaults/widgets/DefaultLinkWidget";
export * from "./defaults/widgets/DefaultLabelWidget";
export * from "./defaults/widgets/DefaultNodeWidget";
export * from "./defaults/widgets/DefaultPortLabelWidget";
export * from "./AbstractFactory";
export * from "./Toolkit";
export * from "./routing/PathFinding";
export * from "./DiagramEngine";
export * from "./models/DiagramModel";
export * from "./BaseEntity";
export * from "./CanvasActions";
export * from "./models/BaseModel";
export * from "./models/DiagramModel";
export * from "./models/LinkModel";
export * from "./models/NodeModel";
export * from "./models/PointModel";
export * from "./models/PortModel";
export * from "./models/LabelModel";
export * from "./widgets/DiagramWidget";
export * from "./widgets/layers/LinkLayerWidget";
export * from "./widgets/LinkWidget";
export * from "./widgets/layers/NodeLayerWidget";
export * from "./widgets/NodeWidget";
export * from "./widgets/PortWidget";

View File

@@ -1,85 +0,0 @@
import { BaseEntity, BaseListener } from "../BaseEntity";
import * as _ from "lodash";
import { BaseEvent } from "../BaseEntity";
import { DiagramEngine } from "../DiagramEngine";
export interface BaseModelListener extends BaseListener {
selectionChanged?(event: BaseEvent<BaseModel> & { isSelected: boolean }): void;
entityRemoved?(event: BaseEvent<BaseModel>): void;
}
/**
* @author Dylan Vorster
*/
export class BaseModel<
X extends BaseEntity = BaseEntity,
T extends BaseModelListener = BaseModelListener
> extends BaseEntity<BaseModelListener> {
type: string;
selected: boolean;
parent: X;
constructor(type?: string, id?: string) {
super(id);
this.type = type;
this.selected = false;
}
public getParent(): X {
return this.parent;
}
public setParent(parent: X) {
this.parent = parent;
}
public getSelectedEntities(): BaseModel<any, T>[] {
if (this.isSelected()) {
return [this];
}
return [];
}
public deSerialize(ob, engine: DiagramEngine) {
super.deSerialize(ob, engine);
this.type = ob.type;
this.selected = ob.selected;
}
public serialize() {
return _.merge(super.serialize(), {
type: this.type,
selected: this.selected
});
}
public getType(): string {
return this.type;
}
public getID(): string {
return this.id;
}
public isSelected(): boolean {
return this.selected;
}
public setSelected(selected: boolean = true) {
this.selected = selected;
this.iterateListeners((listener, event) => {
if (listener.selectionChanged) {
listener.selectionChanged({ ...event, isSelected: selected });
}
});
}
public remove() {
this.iterateListeners((listener, event) => {
if (listener.entityRemoved) {
listener.entityRemoved(event);
}
});
}
}

View File

@@ -1,231 +1,48 @@
import { BaseListener, BaseEntity, BaseEvent, BaseEntityType } from "../BaseEntity";
import * as _ from "lodash";
import { DiagramEngine } from "../DiagramEngine";
import { LinkModel } from "./LinkModel";
import { NodeModel } from "./NodeModel";
import { PortModel } from "./PortModel";
import { BaseModel, BaseModelListener } from "./BaseModel";
import { PointModel } from "./PointModel";
/**
* @author Dylan Vorster
*
*/
export interface DiagramListener extends BaseListener {
import { BaseModel, BaseEvent, CanvasModel, CanvasModelListener, CanvasLayerModel } from "@projectstorm/react-canvas";
export interface DiagramListener extends CanvasModelListener {
nodesUpdated?(event: BaseEvent & { node: NodeModel; isCreated: boolean }): void;
linksUpdated?(event: BaseEvent & { link: LinkModel; isCreated: boolean }): void;
offsetUpdated?(event: BaseEvent<DiagramModel> & { offsetX: number; offsetY: number }): void;
zoomUpdated?(event: BaseEvent<DiagramModel> & { zoom: number }): void;
gridUpdated?(event: BaseEvent<DiagramModel> & { size: number }): void;
}
/**
*
*/
export class DiagramModel extends BaseEntity<DiagramListener> {
//models
links: { [s: string]: LinkModel };
nodes: { [s: string]: NodeModel };
//control variables
offsetX: number;
offsetY: number;
zoom: number;
rendered: boolean;
gridSize: number;
export class DiagramModel extends CanvasModel<DiagramListener> {
linksLayer: CanvasLayerModel<LinkModel>;
nodesLayer: CanvasLayerModel<NodeModel>;
constructor() {
super();
this.links = {};
this.nodes = {};
this.linksLayer = new CanvasLayerModel();
this.nodesLayer = new CanvasLayerModel();
this.offsetX = 0;
this.offsetY = 0;
this.zoom = 100;
this.rendered = false;
this.gridSize = 0;
}
this.linksLayer.setSVG(true);
this.linksLayer.setTransformable(true);
setGridSize(size: number = 0) {
this.gridSize = size;
this.iterateListeners((listener, event) => {
listener.gridUpdated && listener.gridUpdated({ ...event, size: size });
});
}
this.nodesLayer.setSVG(false);
this.nodesLayer.setTransformable(true);
getGridPosition(pos) {
if (this.gridSize === 0) {
return pos;
}
return this.gridSize * Math.floor((pos + this.gridSize / 2) / this.gridSize);
}
deSerializeDiagram(object: any, diagramEngine: DiagramEngine) {
this.deSerialize(object, diagramEngine);
this.offsetX = object.offsetX;
this.offsetY = object.offsetY;
this.zoom = object.zoom;
this.gridSize = object.gridSize;
// deserialize nodes
_.forEach(object.nodes, (node: any) => {
let nodeOb = diagramEngine.getNodeFactory(node.type).getNewInstance(node);
nodeOb.setParent(this);
nodeOb.deSerialize(node, diagramEngine);
this.addNode(nodeOb);
});
// deserialze links
_.forEach(object.links, (link: any) => {
let linkOb = diagramEngine.getLinkFactory(link.type).getNewInstance();
linkOb.setParent(this);
linkOb.deSerialize(link, diagramEngine);
this.addLink(linkOb);
});
}
serializeDiagram() {
return _.merge(this.serialize(), {
offsetX: this.offsetX,
offsetY: this.offsetY,
zoom: this.zoom,
gridSize: this.gridSize,
links: _.map(this.links, link => {
return link.serialize();
}),
nodes: _.map(this.nodes, node => {
return node.serialize();
})
});
}
clearSelection(ignore: BaseModel<BaseEntity, BaseModelListener> | null = null) {
_.forEach(this.getSelectedItems(), element => {
if (ignore && ignore.getID() === element.getID()) {
return;
}
element.setSelected(false); //TODO dont fire the listener
});
}
getSelectedItems(...filters: BaseEntityType[]): BaseModel<BaseEntity, BaseModelListener>[] {
if (!Array.isArray(filters)) {
filters = [filters];
}
var items = [];
// run through nodes
items = items.concat(
_.flatMap(this.nodes, node => {
return node.getSelectedEntities();
})
);
// find all the links
items = items.concat(
_.flatMap(this.links, link => {
return link.getSelectedEntities();
})
);
//find all points
items = items.concat(
_.flatMap(this.links, link => {
return _.flatMap(link.points, point => {
return point.getSelectedEntities();
});
})
);
items = _.uniq(items);
if (filters.length > 0) {
items = _.filter(_.uniq(items), (item: BaseModel<any>) => {
if (_.includes(filters, "node") && item instanceof NodeModel) {
return true;
}
if (_.includes(filters, "link") && item instanceof LinkModel) {
return true;
}
if (_.includes(filters, "port") && item instanceof PortModel) {
return true;
}
if (_.includes(filters, "point") && item instanceof PointModel) {
return true;
}
return false;
});
}
return items;
}
setZoomLevel(zoom: number) {
this.zoom = zoom;
this.iterateListeners((listener, event) => {
listener.zoomUpdated && listener.zoomUpdated({ ...event, zoom: zoom });
});
}
setOffset(offsetX: number, offsetY: number) {
this.offsetX = offsetX;
this.offsetY = offsetY;
this.iterateListeners((listener, event) => {
listener.offsetUpdated && listener.offsetUpdated({ ...event, offsetX: offsetX, offsetY: offsetY });
});
}
setOffsetX(offsetX: number) {
this.offsetX = offsetX;
this.iterateListeners((listener, event) => {
listener.offsetUpdated && listener.offsetUpdated({ ...event, offsetX: offsetX, offsetY: this.offsetY });
});
}
setOffsetY(offsetY: number) {
this.offsetY = offsetY;
this.iterateListeners((listener, event) => {
listener.offsetUpdated &&
listener.offsetUpdated({ ...event, offsetX: this.offsetX, offsetY: this.offsetY });
});
}
getOffsetY() {
return this.offsetY;
}
getOffsetX() {
return this.offsetX;
}
getZoomLevel() {
return this.zoom;
this.addLayer(this.linksLayer);
this.addLayer(this.nodesLayer);
}
getNode(node: string | NodeModel): NodeModel | null {
if (node instanceof NodeModel) {
return node;
}
if (!this.nodes[node]) {
return null;
}
return this.nodes[node];
return this.nodesLayer.getEntity[node] || null;
}
getLink(link: string | LinkModel): LinkModel | null {
if (link instanceof LinkModel) {
return link;
}
if (!this.links[link]) {
return null;
}
return this.links[link];
return this.linksLayer.getEntity(link) || null;
}
addAll(...models: BaseModel[]): BaseModel[] {
@@ -240,52 +57,48 @@ export class DiagramModel extends BaseEntity<DiagramListener> {
}
addLink(link: LinkModel): LinkModel {
link.addListener({
entityRemoved: () => {
this.removeLink(link);
this.linksLayer.addEntity(link);
this.iterateListeners("link added", (listener, event) => {
if (listener.linksUpdated) {
listener.linksUpdated({ ...event, link: link, isCreated: true });
}
});
this.links[link.getID()] = link;
this.iterateListeners((listener, event) => {
listener.linksUpdated && listener.linksUpdated({ ...event, link: link, isCreated: true });
});
return link;
}
addNode(node: NodeModel): NodeModel {
node.addListener({
entityRemoved: () => {
this.removeNode(node);
this.nodesLayer.addEntity(node);
this.iterateListeners("node added", (listener, event) => {
if (listener.nodesUpdated) {
listener.nodesUpdated({ ...event, node: node, isCreated: true });
}
});
this.nodes[node.getID()] = node;
this.iterateListeners((listener, event) => {
listener.nodesUpdated && listener.nodesUpdated({ ...event, node: node, isCreated: true });
});
return node;
}
removeLink(link: LinkModel | string) {
link = this.getLink(link);
delete this.links[link.getID()];
this.iterateListeners((listener, event) => {
listener.linksUpdated && listener.linksUpdated({ ...event, link: link as LinkModel, isCreated: false });
this.linksLayer.removeEntity(link);
this.iterateListeners("link removed", (listener, event) => {
if (listener.linksUpdated) {
listener.linksUpdated({ ...event, link: link as LinkModel, isCreated: false });
}
});
}
removeNode(node: NodeModel | string) {
node = this.getNode(node);
delete this.nodes[node.getID()];
this.iterateListeners((listener, event) => {
listener.nodesUpdated && listener.nodesUpdated({ ...event, node: node as NodeModel, isCreated: false });
this.nodesLayer.removeEntity(node);
this.iterateListeners("node removed", (listener, event) => {
if (listener.nodesUpdated) {
listener.nodesUpdated({ ...event, node: node as NodeModel, isCreated: false });
}
});
}
getLinks(): { [s: string]: LinkModel } {
return this.links;
return this.linksLayer.getEntities();
}
getNodes(): { [s: string]: NodeModel } {
return this.nodes;
return this.nodesLayer.getEntities();
}
}

View File

@@ -1,13 +1,27 @@
import { BaseModel } from "./BaseModel";
import { LinkModel } from "./LinkModel";
import * as _ from "lodash";
import { BaseModel, DeserializeEvent } from "@projectstorm/react-canvas";
export class LabelModel extends BaseModel<LinkModel> {
offsetX: number;
offsetY: number;
constructor(type?: string, id?: string) {
super(type, id);
constructor(type?: string) {
super(type);
this.offsetX = 0;
this.offsetY = 0;
}
deSerialize(event: DeserializeEvent) {
super.deSerialize(event);
this.offsetX = event.data.offsetX;
this.offsetY = event.data.offsetY;
}
serialize() {
return _.merge(super.serialize(), {
offsetX: this.offsetX,
offsetY: this.offsetY
});
}
}

View File

@@ -1,97 +1,71 @@
import { BaseModel, BaseModelListener } from "./BaseModel";
import { PortModel } from "./PortModel";
import { PointModel } from "./PointModel";
import * as _ from "lodash";
import { BaseEvent } from "../BaseEntity";
import { LabelModel } from "./LabelModel";
import { DiagramEngine } from "../DiagramEngine";
import { DiagramModel } from "./DiagramModel";
import {
BaseEvent,
GraphModel,
GraphModelOrdered,
CanvasElementModel,
CanvasElementModelListener,
Rectangle,
DeserializeEvent
} from "@projectstorm/react-canvas";
export interface LinkModelListener extends BaseModelListener {
sourcePortChanged?(event: BaseEvent<LinkModel> & { port: null | PortModel }): void;
export interface LinkModelListener<T extends LinkModel = any> extends CanvasElementModelListener<T> {
sourcePortChanged?(event: BaseEvent<T> & { port: null | PortModel }): void;
targetPortChanged?(event: BaseEvent<LinkModel> & { port: null | PortModel }): void;
targetPortChanged?(event: BaseEvent<T> & { port: null | PortModel }): void;
}
export class LinkModel<T extends LinkModelListener = LinkModelListener> extends BaseModel<DiagramModel, T> {
sourcePort: PortModel | null;
targetPort: PortModel | null;
labels: LabelModel[];
points: PointModel[];
extras: {};
export class LinkModel<T extends LinkModelListener = LinkModelListener> extends CanvasElementModel<T> {
protected sourcePort: PortModel | null;
protected targetPort: PortModel | null;
protected labels: GraphModel<LabelModel, LinkModel>;
protected points: GraphModelOrdered<PointModel, LinkModel>;
constructor(linkType: string = "default", id?: string) {
super(linkType, id);
this.points = [new PointModel(this, { x: 0, y: 0 }), new PointModel(this, { x: 0, y: 0 })];
this.extras = {};
constructor(linkType: string = "default") {
super(linkType);
this.points = new GraphModelOrdered();
this.labels = new GraphModel();
this.points.setParentDelegate(this);
this.labels.setParentDelegate(this);
this.sourcePort = null;
this.targetPort = null;
this.labels = [];
}
deSerialize(ob, engine: DiagramEngine) {
super.deSerialize(ob, engine);
this.extras = ob.extras;
this.points = _.map(ob.points || [], (point: { x; y }) => {
var p = new PointModel(this, { x: point.x, y: point.y });
p.deSerialize(point, engine);
return p;
});
setDimensions(dimensions: Rectangle) {
throw new Error("Method not implemented.");
}
//deserialize labels
_.forEach(ob.labels || [], (label: any) => {
let labelOb = engine.getLabelFactory(label.type).getNewInstance();
labelOb.deSerialize(label, engine);
this.addLabel(labelOb);
});
getDimensions(): Rectangle {
throw new Error("Method not implemented.");
}
if (ob.target) {
this.setTargetPort(
this.getParent()
.getNode(ob.target)
.getPortFromID(ob.targetPort)
);
deSerialize(event: DeserializeEvent) {
super.deSerialize(event);
this.points.deSerialize(event.subset("points"));
this.labels.deSerialize(event.subset("labels"));
if (event.data.target) {
this.setTargetPort(event.cache[event.data.targetPort] as PortModel);
}
if (ob.source) {
this.setSourcePort(
this.getParent()
.getNode(ob.source)
.getPortFromID(ob.sourcePort)
);
if (event.data.source) {
this.setSourcePort(event.cache[event.data.sourcePort] as PortModel);
}
}
serialize() {
return _.merge(super.serialize(), {
source: this.sourcePort ? this.sourcePort.getParent().id : null,
sourcePort: this.sourcePort ? this.sourcePort.id : null,
target: this.targetPort ? this.targetPort.getParent().id : null,
targetPort: this.targetPort ? this.targetPort.id : null,
points: _.map(this.points, point => {
return point.serialize();
}),
extras: this.extras,
labels: _.map(this.labels, label => {
return label.serialize();
})
source: this.sourcePort ? this.sourcePort.getParent().getID() : null,
sourcePort: this.sourcePort ? this.sourcePort.getID() : null,
target: this.targetPort ? this.targetPort.getParent().getID() : null,
targetPort: this.targetPort ? this.targetPort.getID() : null,
points: this.points.serialize(),
labels: this.labels.serialize()
});
}
doClone(lookupTable = {}, clone) {
clone.setPoints(
_.map(this.getPoints(), (point: PointModel) => {
return point.clone(lookupTable);
})
);
if (this.sourcePort) {
clone.setSourcePort(this.sourcePort.clone(lookupTable));
}
if (this.targetPort) {
clone.setTargetPort(this.targetPort.clone(lookupTable));
}
}
remove() {
if (this.sourcePort) {
this.sourcePort.removeLink(this);
@@ -99,25 +73,19 @@ export class LinkModel<T extends LinkModelListener = LinkModelListener> extends
if (this.targetPort) {
this.targetPort.removeLink(this);
}
super.remove();
}
isLastPoint(point: PointModel) {
var index = this.getPointIndex(point);
return index === this.points.length - 1;
return index === this.points.count() - 1;
}
getPointIndex(point: PointModel) {
return this.points.indexOf(point);
return _.values(this.points.getEntities()).indexOf(point);
}
getPointModel(id: string): PointModel | null {
for (var i = 0; i < this.points.length; i++) {
if (this.points[i].id === id) {
return this.points[i];
}
}
return null;
return this.points.getEntities()[id];
}
getPortForPoint(point: PointModel): PortModel {
@@ -141,24 +109,25 @@ export class LinkModel<T extends LinkModelListener = LinkModelListener> extends
}
getFirstPoint(): PointModel {
return this.points[0];
return _.values(this.points.getEntities())[0];
}
getLastPoint(): PointModel {
return this.points[this.points.length - 1];
return _.values(this.points.getEntities())[this.points.count() - 1];
}
setSourcePort(port: PortModel) {
if (port !== null) {
port.addLink(this);
} else if (this.sourcePort !== null) {
}
if (this.sourcePort !== null) {
this.sourcePort.removeLink(this);
} else {
return;
}
this.sourcePort = port;
this.iterateListeners((listener: LinkModelListener, event) => {
listener.sourcePortChanged && listener.sourcePortChanged({ ...event, port: port });
this.iterateListeners("source port changed", (listener: T, event) => {
if (listener.sourcePortChanged) {
listener.sourcePortChanged({ ...event, port: port });
}
});
}
@@ -173,14 +142,15 @@ export class LinkModel<T extends LinkModelListener = LinkModelListener> extends
setTargetPort(port: PortModel) {
if (port !== null) {
port.addLink(this);
} else if (this.targetPort !== null) {
}
if (this.targetPort !== null) {
this.targetPort.removeLink(this);
} else {
return;
}
this.targetPort = port;
this.iterateListeners((listener: LinkModelListener, event) => {
listener.targetPortChanged && listener.targetPortChanged({ ...event, port: port });
this.iterateListeners("target port chnaged", (listener: T, event) => {
if (listener.targetPortChanged) {
listener.targetPortChanged({ ...event, port: port });
}
});
}
@@ -189,46 +159,34 @@ export class LinkModel<T extends LinkModelListener = LinkModelListener> extends
}
addLabel(label: LabelModel) {
label.setParent(this);
this.labels.push(label);
this.labels.addEntity(label);
}
getPoints(): PointModel[] {
return this.points;
return this.points.getArray();
}
setPoints(points: PointModel[]) {
_.forEach(points, point => {
point.setParent(this);
point.setLink(this);
});
this.points = points;
this.points.addEntities(points);
}
removePoint(pointModel: PointModel) {
this.points.splice(this.getPointIndex(pointModel), 1);
this.points.removeEntity(pointModel);
}
removePointsBefore(pointModel: PointModel) {
this.points.splice(0, this.getPointIndex(pointModel));
}
removePointsAfter(pointModel: PointModel) {
this.points.splice(this.getPointIndex(pointModel) + 1);
}
removeMiddlePoints() {
if (this.points.length > 2) {
this.points.splice(0, this.points.length - 2);
}
}
addPoint<T extends PointModel>(pointModel: T, index = 1): T {
pointModel.setParent(this);
this.points.splice(index, 0, pointModel);
addPoint<P extends PointModel>(pointModel: P, index = 1): P {
pointModel.setLink(this);
this.points.addEntity(pointModel, index);
return pointModel;
}
generatePoint(x: number = 0, y: number = 0): PointModel {
return new PointModel(this, { x: x, y: y });
generatePoint(x: number, y: number): PointModel {
let point = new PointModel(this);
point.getPoint().x = x;
point.getPoint().y = y;
return point;
}
}

View File

@@ -1,119 +1,67 @@
import { BaseModel, BaseModelListener } from "./BaseModel";
import { PortModel } from "./PortModel";
import * as _ from "lodash";
import { DiagramEngine } from "../DiagramEngine";
import { DiagramModel } from "./DiagramModel";
import { Rectangle, CanvasElementModel, GraphModel, DeserializeEvent } from "@projectstorm/react-canvas";
export class NodeModel extends BaseModel<DiagramModel, BaseModelListener> {
x: number;
y: number;
extras: any;
ports: { [s: string]: PortModel };
export class NodeModel<T extends PortModel = PortModel> extends CanvasElementModel {
protected dimensions: Rectangle;
protected ports: GraphModel<T, null>;
// calculated post rendering so routing can be done correctly
width: number;
height: number;
constructor(nodeType: string = "default", id?: string) {
super(nodeType, id);
this.x = 0;
this.y = 0;
this.extras = {};
this.ports = {};
constructor(nodeType: string = "default") {
super(nodeType);
this.dimensions = new Rectangle(0, 0, 0, 0);
this.ports = new GraphModel("ports");
}
setPosition(x, y) {
//store position
let oldX = this.x;
let oldY = this.y;
for (let port in this.ports) {
_.forEach(this.ports[port].getLinks(), link => {
let point = link.getPointForPort(this.ports[port]);
point.x = point.x + x - oldX;
point.y = point.y + y - oldY;
});
}
this.x = x;
this.y = y;
setDimensions(dimensions: Rectangle) {
this.dimensions = dimensions;
}
getSelectedEntities() {
let entities = super.getSelectedEntities();
// add the points of each link that are selected here
if (this.isSelected()) {
for (let portName in this.ports) {
entities = entities.concat(
_.map(this.ports[portName].getLinks(), link => {
return link.getPointForPort(this.ports[portName]);
})
);
}
}
return entities;
getDimensions(): Rectangle {
return this.dimensions;
}
deSerialize(ob, engine: DiagramEngine) {
super.deSerialize(ob, engine);
this.x = ob.x;
this.y = ob.y;
this.extras = ob.extras;
setPosition(x:number, y:number){
this.dimensions.updateDimensions(x,y, this.dimensions.getWidth(), this.dimensions.getHeight());
}
deSerialize(event: DeserializeEvent) {
super.deSerialize(event);
this.dimensions.deserialize(event.data.dimensions);
//deserialize ports
_.forEach(ob.ports, (port: any) => {
let portOb = engine.getPortFactory(port.type).getNewInstance();
portOb.deSerialize(port, engine);
let ports = event.subset("ports");
_.forEach(ports.data, (port: any, index) => {
let portOb = event.engine.getFactory(port.type).generateModel() as T;
portOb.deSerialize(ports.subset(index));
this.addPort(portOb);
});
}
serialize() {
return _.merge(super.serialize(), {
x: this.x,
y: this.y,
extras: this.extras,
ports: _.map(this.ports, port => {
return port.serialize();
})
dimensions: this.dimensions.serialize(),
ports: this.ports.serialize()
});
}
doClone(lookupTable = {}, clone) {
// also clone the ports
clone.ports = {};
_.values(this.ports).forEach(port => {
clone.addPort(port.clone(lookupTable));
});
}
remove() {
super.remove();
for (var i in this.ports) {
_.forEach(this.ports[i].getLinks(), link => {
link.remove();
});
}
}
getPortFromID(id): PortModel | null {
for (var i in this.ports) {
if (this.ports[i].id === id) {
getPortFromID(id): T | null {
for (let i in this.ports) {
if (this.ports[i].getID() === id) {
return this.ports[i];
}
}
return null;
}
getPort(name: string): PortModel | null {
return this.ports[name];
getPort(name: string): T | null {
return this.ports.getEntities()[name];
}
getPorts(): { [s: string]: PortModel } {
return this.ports;
getPorts(): { [s: string]: T } {
return this.ports.getEntities();
}
removePort(port: PortModel) {
removePort(port: T) {
//clear the parent node reference
if (this.ports[port.name]) {
this.ports[port.name].setParent(null);
@@ -121,14 +69,8 @@ export class NodeModel extends BaseModel<DiagramModel, BaseModelListener> {
}
}
addPort<T extends PortModel>(port: T): T {
port.setParent(this);
this.ports[port.name] = port;
addPort(port: T): T {
this.ports.addEntity(port);
return port;
}
updateDimensions({ width, height }: { width: number; height: number }) {
this.width = width;
this.height = height;
}
}

View File

@@ -1,69 +1,62 @@
import { BaseModel, BaseModelListener } from "./BaseModel";
import { LinkModel } from "./LinkModel";
import * as _ from "lodash";
import { DiagramEngine } from "../DiagramEngine";
import {
Point,
CanvasElementModel,
CanvasElementModelListener,
Rectangle,
DeserializeEvent
} from "@projectstorm/react-canvas";
export class PointModel extends BaseModel<LinkModel, BaseModelListener> {
x: number;
y: number;
export class PointModel extends CanvasElementModel<CanvasElementModelListener> {
protected point: Point;
protected link: LinkModel;
constructor(link: LinkModel, points: { x: number; y: number }) {
super();
this.x = points.x;
this.y = points.y;
this.parent = link;
constructor(link: LinkModel) {
super("point");
this.link = link;
}
getSelectedEntities() {
if (super.isSelected() && !this.isConnectedToPort()) {
return [this];
}
return [];
setDimensions(dimensions: Rectangle) {
this.point = dimensions.getTopLeft();
}
getDimensions(): Rectangle {
return new Rectangle(this.point, 10, 10);
}
isConnectedToPort(): boolean {
return this.parent.getPortForPoint(this) !== null;
return this.link.getPortForPoint(this) !== null;
}
setLink(link: LinkModel) {
this.link = link;
}
getLink(): LinkModel {
return this.getParent();
return this.link;
}
deSerialize(ob, engine: DiagramEngine) {
super.deSerialize(ob, engine);
this.x = ob.x;
this.y = ob.y;
deSerialize(event: DeserializeEvent) {
super.deSerialize(event);
this.point = new Point(event.data["x"], event.data["y"]);
}
serialize() {
return _.merge(super.serialize(), {
x: this.x,
y: this.y
x: this.point.x,
y: this.point.y
});
}
remove() {
//clear references
if (this.parent) {
this.parent.removePoint(this);
if (this.link) {
this.link.removePoint(this);
}
super.remove();
}
updateLocation(points: { x: number; y: number }) {
this.x = points.x;
this.y = points.y;
}
getX(): number {
return this.x;
}
getY(): number {
return this.y;
}
isLocked() {
return super.isLocked() || this.getParent().isLocked();
getPoint(): Point {
return this.point;
}
}

View File

@@ -1,49 +1,37 @@
import { BaseModel, BaseModelListener } from "./BaseModel";
import { BaseModel, BaseListener, DeserializeEvent } from "@projectstorm/react-canvas";
import { NodeModel } from "./NodeModel";
import { LinkModel } from "./LinkModel";
import * as _ from "lodash";
import { DiagramEngine } from "../DiagramEngine";
export class PortModel extends BaseModel<NodeModel, BaseModelListener> {
export class PortModel extends BaseModel<NodeModel, BaseListener> {
name: string;
links: { [id: string]: LinkModel };
maximumLinks: number;
// calculated post rendering so routing can be done correctly
x: number;
y: number;
width: number;
height: number;
constructor(name: string, type?: string, id?: string, maximumLinks?: number) {
super(type, id);
constructor(name: string, type?: string, maximumLinks?: number) {
super(type);
this.name = name;
this.links = {};
this.maximumLinks = maximumLinks;
}
deSerialize(ob, engine: DiagramEngine) {
super.deSerialize(ob, engine);
this.name = ob.name;
this.maximumLinks = ob.maximumLinks;
deSerialize(event: DeserializeEvent) {
super.deSerialize(event);
this.name = event.data.name;
this.maximumLinks = event.data.maximumLinks;
}
serialize() {
return _.merge(super.serialize(), {
name: this.name,
parentNode: this.parent.id,
parentNode: this.parent.getID(),
links: _.map(this.links, link => {
return link.id;
return link.getID();
}),
maximumLinks: this.maximumLinks
});
}
doClone(lookupTable = {}, clone) {
clone.links = {};
clone.parentNode = this.getParent().clone(lookupTable);
}
getNode(): NodeModel {
return this.getParent();
}
@@ -84,18 +72,7 @@ export class PortModel extends BaseModel<NodeModel, BaseModelListener> {
return null;
}
updateCoords({ x, y, width, height }: { x: number; y: number; width: number; height: number }) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
canLinkToPort(port: PortModel): boolean {
return true;
}
isLocked() {
return super.isLocked() || this.getParent().isLocked();
}
}

View File

@@ -1,133 +0,0 @@
import * as PF from "pathfinding";
import { DiagramEngine } from "../main";
/*
it can be very expensive to calculate routes when every single pixel on the canvas
is individually represented. Using the factor below, we combine values in order
to achieve the best trade-off between accuracy and performance.
*/
export const ROUTING_SCALING_FACTOR = 5;
const pathFinderInstance = new PF.JumpPointFinder({
heuristic: PF.Heuristic.manhattan,
diagonalMovement: PF.DiagonalMovement.Never
});
export default class PathFinding {
instance: any;
diagramEngine: DiagramEngine;
constructor(diagramEngine: DiagramEngine) {
this.instance = pathFinderInstance;
this.diagramEngine = diagramEngine;
}
/**
* Taking as argument a fully unblocked walking matrix, this method
* finds a direct path from point A to B.
*/
calculateDirectPath(
from: {
x: number;
y: number;
},
to: {
x: number;
y: number;
}
): number[][] {
const matrix = this.diagramEngine.getCanvasMatrix();
const grid = new PF.Grid(matrix);
return pathFinderInstance.findPath(
this.diagramEngine.translateRoutingX(Math.floor(from.x / ROUTING_SCALING_FACTOR)),
this.diagramEngine.translateRoutingY(Math.floor(from.y / ROUTING_SCALING_FACTOR)),
this.diagramEngine.translateRoutingX(Math.floor(to.x / ROUTING_SCALING_FACTOR)),
this.diagramEngine.translateRoutingY(Math.floor(to.y / ROUTING_SCALING_FACTOR)),
grid
);
}
/**
* Using @link{#calculateDirectPath}'s result as input, we here
* determine the first walkable point found in the matrix that includes
* blocked paths.
*/
calculateLinkStartEndCoords(
matrix: number[][],
path: number[][]
): {
start: {
x: number;
y: number;
};
end: {
x: number;
y: number;
};
pathToStart: number[][];
pathToEnd: number[][];
} {
const startIndex = path.findIndex(point => matrix[point[1]][point[0]] === 0);
const endIndex =
path.length -
1 -
path
.slice()
.reverse()
.findIndex(point => matrix[point[1]][point[0]] === 0);
// are we trying to create a path exclusively through blocked areas?
// if so, let's fallback to the linear routing
if (startIndex === -1 || endIndex === -1) {
return undefined;
}
const pathToStart = path.slice(0, startIndex);
const pathToEnd = path.slice(endIndex);
return {
start: {
x: path[startIndex][0],
y: path[startIndex][1]
},
end: {
x: path[endIndex][0],
y: path[endIndex][1]
},
pathToStart,
pathToEnd
};
}
/**
* Puts everything together: merges the paths from/to the centre of the ports,
* with the path calculated around other elements.
*/
calculateDynamicPath(
routingMatrix: number[][],
start: {
x: number;
y: number;
},
end: {
x: number;
y: number;
},
pathToStart: number[][],
pathToEnd: number[][]
) {
// generate the path based on the matrix with obstacles
const grid = new PF.Grid(routingMatrix);
const dynamicPath = pathFinderInstance.findPath(start.x, start.y, end.x, end.y, grid);
// aggregate everything to have the calculated path ready for rendering
const pathCoords = pathToStart
.concat(dynamicPath, pathToEnd)
.map(coords => [
this.diagramEngine.translateRoutingX(coords[0], true),
this.diagramEngine.translateRoutingY(coords[1], true)
]);
return PF.Util.compressPath(pathCoords);
}
}

View File

@@ -1,13 +1,13 @@
.srd-diagram{
position: relative;
flex-grow: 1;
display: flex;
cursor: move;
overflow: hidden;
position: relative;
flex-grow: 1;
display: flex;
cursor: move;
overflow: hidden;
&__selector{
position: absolute;
background-color: rgba(0,192,255,0.2);
border: solid 2px rgb(0,192,255);
}
&__selector{
position: absolute;
background-color: rgba(0,192,255,0.2);
border: solid 2px rgb(0,192,255);
}
}

View File

@@ -1,11 +0,0 @@
.srd-link-layer{
position: absolute;
height: 100%;
width: 100%;
transform-origin: 0 0;
overflow: visible !important;
top:0;
bottom: 0;
left: 0;
right: 0;
}

View File

@@ -1,11 +0,0 @@
.srd-node-layer{
top:0;
left:0;
right:0;
bottom:0;
position: absolute;
pointer-events: none;
transform-origin: 0 0;
width: 100%;
height: 100%;
}

View File

@@ -1,14 +1,14 @@
.srd-node{
position: absolute;
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Chrome/Safari/Opera */
user-select: none;
cursor: move;
pointer-events: all;
position: absolute;
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Chrome/Safari/Opera */
user-select: none;
cursor: move;
pointer-events: all;
&--selected{
> * {
border-color:rgb(0,192,255) !important;
}
}
&--selected{
> * {
border-color:rgb(0,192,255) !important;
}
}
}

View File

@@ -1,9 +1,9 @@
.srd-port{
width: 15px;
height: 15px;
background: rgba(white,0.1);
&:hover,&.selected{
background: rgb(192,255,0);
}
width: 15px;
height: 15px;
background: rgba(white,0.1);
&:hover,&.selected{
background: rgb(192,255,0);
}
}

View File

@@ -1,9 +1,9 @@
.srd-default-label{
background: rgba(70, 70, 70, 0.8);
border: 1px solid #333;
border-radius: 4px;
color: #fff;
display: inline-block;
font-size: smaller;
padding: 5px;
background: rgba(70, 70, 70, 0.8);
border: 1px solid #333;
border-radius: 4px;
color: #fff;
display: inline-block;
font-size: smaller;
padding: 5px;
}

View File

@@ -1,39 +1,39 @@
.srd-default-link{
@keyframes dash {
from {
stroke-dashoffset: 24;
}
to {
stroke-dashoffset: 0;
}
}
@keyframes dash {
from {
stroke-dashoffset: 24;
}
to {
stroke-dashoffset: 0;
}
}
path{
fill:none;
pointer-events:all;
}
path{
fill:none;
pointer-events:all;
}
&--path-selected{
stroke: rgb(0,192,255) !important;
stroke-dasharray: 10,2;
animation: dash 1s linear infinite;
}
&--path-selected{
stroke: rgb(0,192,255) !important;
stroke-dasharray: 10,2;
animation: dash 1s linear infinite;
}
&__label {
pointer-events: none;
&__label {
pointer-events: none;
> div{
display: inline-block;
position: absolute;
}
}
> div{
display: inline-block;
position: absolute;
}
}
&__point{
fill: rgba(white,0.5);
}
&__point{
fill: rgba(white,0.5);
}
&--point-selected{
fill: rgb(0,192,255);
}
&--point-selected{
fill: rgb(0,192,255);
}
}

View File

@@ -1,43 +1,43 @@
.srd-default-node {
background-color: rgb(30, 30, 30);
border-radius: 5px;
font-family: sans-serif;
color: white;
border: solid 2px black;
overflow: visible;
font-size: 11px;
background-color: rgb(30, 30, 30);
border-radius: 5px;
font-family: sans-serif;
color: white;
border: solid 2px black;
overflow: visible;
font-size: 11px;
&__title {
background: rgba(black, 0.3);
display: flex;
white-space: nowrap;
> * {
align-self: center;
}
.fa {
padding: 5px;
opacity: 0.2;
cursor: pointer;
&__title {
background: rgba(black, 0.3);
display: flex;
white-space: nowrap;
> * {
align-self: center;
}
.fa {
padding: 5px;
opacity: 0.2;
cursor: pointer;
&:hover {
opacity: 1.0;
}
}
}
&:hover {
opacity: 1.0;
}
}
}
&__name {
flex-grow: 1;
padding: 5px 5px;
}
&__name {
flex-grow: 1;
padding: 5px 5px;
}
&__ports {
display: flex;
background-image: linear-gradient(rgba(black, 0.1), rgba(black, 0.2));
}
&__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, &__out{
flex-grow: 1;
display: flex;
flex-direction: column;
}
}

View File

@@ -1,24 +1,24 @@
.srd-default-port{
$p: &;
$p: &;
display: flex;
margin-top: 1px;
display: flex;
margin-top: 1px;
> * {
align-self: center;
}
> * {
align-self: center;
}
&__name {
padding: 0 5px;
}
&__name {
padding: 0 5px;
}
&--out{
justify-content: flex-end;
&--out{
justify-content: flex-end;
#{$p}__name {
justify-content: flex-end;
text-align: right;
}
}
#{$p}__name {
justify-content: flex-end;
text-align: right;
}
}
}

View File

@@ -1,6 +1,4 @@
@import "DiagramWidget";
@import "LinkLayerWidget";
@import "NodeLayerWidget";
@import "NodeWidget";
@import "PortWidget";
@@ -8,4 +6,4 @@
@import "defaults/DefaultNodeWidget";
@import "defaults/DefaultPortWidget";
@import "defaults/DefaultLabelWidget";
@import "defaults/DefaultLinkWidget";
@import "defaults/DefaultLinkWidget";

View File

@@ -1,44 +0,0 @@
import * as React from "react";
import * as _ from "lodash";
export interface BaseWidgetProps {
/**
* Override the base class name
*/
baseClass?: string;
/**
* append additional classes
*/
className?: string;
/**
* Additional props to add
*/
extraProps?: any;
}
export class BaseWidget<P extends BaseWidgetProps = BaseWidgetProps, S = any> extends React.Component<P, S> {
className: string;
constructor(name: string, props: P) {
super(props);
this.className = name;
}
bem(selector: string): string {
return (this.props.baseClass || this.className) + selector + " ";
}
getClassName(): string {
return (
(this.props.baseClass || this.className) + " " + (this.props.className ? this.props.className + " " : "")
);
}
getProps(): any {
return {
...((this.props.extraProps as any) || {}),
className: this.getClassName()
};
}
}

View File

@@ -1,561 +0,0 @@
import * as React from "react";
import { DiagramEngine } from "../DiagramEngine";
import * as _ from "lodash";
import { LinkLayerWidget } from "./layers/LinkLayerWidget";
import { NodeLayerWidget } from "./layers/NodeLayerWidget";
import { Toolkit } from "../Toolkit";
import { BaseAction, MoveCanvasAction, MoveItemsAction, SelectingAction } from "../CanvasActions";
import { NodeModel } from "../models/NodeModel";
import { PointModel } from "../models/PointModel";
import { PortModel } from "../models/PortModel";
import { LinkModel } from "../models/LinkModel";
import { BaseModel, BaseModelListener } from "../models/BaseModel";
import { BaseEntity } from "../BaseEntity";
import { BaseWidget, BaseWidgetProps } from "./BaseWidget";
export interface SelectionModel {
model: BaseModel<BaseEntity, BaseModelListener>;
initialX: number;
initialY: number;
}
export interface DiagramProps extends BaseWidgetProps {
diagramEngine: DiagramEngine;
allowLooseLinks?: boolean;
allowCanvasTranslation?: boolean;
allowCanvasZoom?: boolean;
inverseZoom?: boolean;
maxNumberPointsPerLink?: number;
smartRouting?: boolean;
actionStartedFiring?: (action: BaseAction) => boolean;
actionStillFiring?: (action: BaseAction) => void;
actionStoppedFiring?: (action: BaseAction) => void;
deleteKeys?: number[];
}
export interface DiagramState {
action: BaseAction | null;
wasMoved: boolean;
renderedNodes: boolean;
windowListener: any;
diagramEngineListener: any;
document: any;
}
/**
* @author Dylan Vorster
*/
export class DiagramWidget extends BaseWidget<DiagramProps, DiagramState> {
public static defaultProps: DiagramProps = {
diagramEngine: null,
allowLooseLinks: true,
allowCanvasTranslation: true,
allowCanvasZoom: true,
inverseZoom: false,
maxNumberPointsPerLink: Infinity, // backwards compatible default
smartRouting: false,
deleteKeys: [46, 8]
};
constructor(props: DiagramProps) {
super("srd-diagram", props);
this.onMouseMove = this.onMouseMove.bind(this);
this.onMouseUp = this.onMouseUp.bind(this);
this.state = {
action: null,
wasMoved: false,
renderedNodes: false,
windowListener: null,
diagramEngineListener: null,
document: null
};
}
onKeyUpPointer: null;
componentWillUnmount() {
this.props.diagramEngine.removeListener(this.state.diagramEngineListener);
this.props.diagramEngine.setCanvas(null);
window.removeEventListener("keyup", this.onKeyUpPointer);
window.removeEventListener("mouseUp", this.onMouseUp);
window.removeEventListener("mouseMove", this.onMouseMove);
}
componentWillReceiveProps(nextProps: DiagramProps) {
if (this.props.diagramEngine !== nextProps.diagramEngine) {
this.props.diagramEngine.removeListener(this.state.diagramEngineListener);
const diagramEngineListener = nextProps.diagramEngine.addListener({
repaintCanvas: () => this.forceUpdate()
});
this.setState({ diagramEngineListener });
}
}
componentWillUpdate(nextProps: DiagramProps) {
if (this.props.diagramEngine.diagramModel.id !== nextProps.diagramEngine.diagramModel.id) {
this.setState({ renderedNodes: false });
nextProps.diagramEngine.diagramModel.rendered = true;
}
if (!nextProps.diagramEngine.diagramModel.rendered) {
this.setState({ renderedNodes: false });
nextProps.diagramEngine.diagramModel.rendered = true;
}
}
componentDidUpdate() {
if (!this.state.renderedNodes) {
this.setState({
renderedNodes: true
});
}
}
componentDidMount() {
this.onKeyUpPointer = this.onKeyUp.bind(this);
//add a keyboard listener
this.setState({
document: document,
renderedNodes: true,
diagramEngineListener: this.props.diagramEngine.addListener({
repaintCanvas: () => {
this.forceUpdate();
}
})
});
window.addEventListener("keyup", this.onKeyUpPointer, false);
// dont focus the window when in test mode - jsdom fails
if (process.env.NODE_ENV !== "test") {
window.focus();
}
}
/**
* Gets a model and element under the mouse cursor
*/
getMouseElement(event): { model: BaseModel<BaseEntity, BaseModelListener>; element: Element } {
var target = event.target as Element;
var diagramModel = this.props.diagramEngine.diagramModel;
//is it a port
var element = Toolkit.closest(target, ".port[data-name]");
if (element) {
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
};
}
//look for a point
element = Toolkit.closest(target, ".point[data-id]");
if (element) {
return {
model: diagramModel
.getLink(element.getAttribute("data-linkid"))
.getPointModel(element.getAttribute("data-id")),
element: element
};
}
//look for a link
element = Toolkit.closest(target, "[data-linkid]");
if (element) {
return {
model: diagramModel.getLink(element.getAttribute("data-linkid")),
element: element
};
}
//look for a node
element = Toolkit.closest(target, ".node[data-nodeid]");
if (element) {
return {
model: diagramModel.getNode(element.getAttribute("data-nodeid")),
element: element
};
}
return null;
}
fireAction() {
if (this.state.action && this.props.actionStillFiring) {
this.props.actionStillFiring(this.state.action);
}
}
stopFiringAction(shouldSkipEvent?: boolean) {
if (this.props.actionStoppedFiring && !shouldSkipEvent) {
this.props.actionStoppedFiring(this.state.action);
}
this.setState({ action: null });
}
startFiringAction(action: BaseAction) {
var setState = true;
if (this.props.actionStartedFiring) {
setState = this.props.actionStartedFiring(action);
}
if (setState) {
this.setState({ action: action });
}
}
onMouseMove(event) {
var diagramEngine = this.props.diagramEngine;
var diagramModel = diagramEngine.getDiagramModel();
//select items so draw a bounding box
if (this.state.action instanceof SelectingAction) {
var relative = diagramEngine.getRelativePoint(event.clientX, event.clientY);
_.forEach(diagramModel.getNodes(), node => {
if ((this.state.action as SelectingAction).containsElement(node.x, node.y, diagramModel)) {
node.setSelected(true);
}
});
_.forEach(diagramModel.getLinks(), link => {
var allSelected = true;
_.forEach(link.points, point => {
if ((this.state.action as SelectingAction).containsElement(point.x, point.y, diagramModel)) {
point.setSelected(true);
} else {
allSelected = false;
}
});
if (allSelected) {
link.setSelected(true);
}
});
this.state.action.mouseX2 = relative.x;
this.state.action.mouseY2 = relative.y;
this.fireAction();
this.setState({ action: this.state.action });
return;
} else if (this.state.action instanceof MoveItemsAction) {
let amountX = event.clientX - this.state.action.mouseX;
let amountY = event.clientY - this.state.action.mouseY;
let amountZoom = diagramModel.getZoomLevel() / 100;
_.forEach(this.state.action.selectionModels, model => {
// in this case we need to also work out the relative grid position
if (
model.model instanceof NodeModel ||
(model.model instanceof PointModel && !model.model.isConnectedToPort())
) {
model.model.x = diagramModel.getGridPosition(model.initialX + amountX / amountZoom);
model.model.y = diagramModel.getGridPosition(model.initialY + amountY / amountZoom);
// update port coordinates as well
if (model.model instanceof NodeModel) {
_.forEach(model.model.getPorts(), port => {
const portCoords = this.props.diagramEngine.getPortCoords(port);
port.updateCoords(portCoords);
});
}
if (diagramEngine.isSmartRoutingEnabled()) {
diagramEngine.calculateRoutingMatrix();
}
} else if (model.model instanceof PointModel) {
// we want points that are connected to ports, to not neccesarilly snap to grid
// this stuff needs to be pixel perfect, dont touch it
model.model.x = model.initialX + diagramModel.getGridPosition(amountX / amountZoom);
model.model.y = model.initialY + diagramModel.getGridPosition(amountY / amountZoom);
}
});
if (diagramEngine.isSmartRoutingEnabled()) {
diagramEngine.calculateCanvasMatrix();
}
this.fireAction();
if (!this.state.wasMoved) {
this.setState({ wasMoved: true });
} else {
this.forceUpdate();
}
} else if (this.state.action instanceof MoveCanvasAction) {
//translate the actual canvas
if (this.props.allowCanvasTranslation) {
diagramModel.setOffset(
this.state.action.initialOffsetX + (event.clientX - this.state.action.mouseX),
this.state.action.initialOffsetY + (event.clientY - this.state.action.mouseY)
);
this.fireAction();
this.forceUpdate();
}
}
}
onKeyUp(event) {
//delete all selected
if (this.props.deleteKeys.indexOf(event.keyCode) !== -1) {
_.forEach(this.props.diagramEngine.getDiagramModel().getSelectedItems(), element => {
//only delete items which are not locked
if (!this.props.diagramEngine.isModelLocked(element)) {
element.remove();
}
});
this.forceUpdate();
}
}
onMouseUp(event) {
var diagramEngine = this.props.diagramEngine;
//are we going to connect a link to something?
if (this.state.action instanceof MoveItemsAction) {
var element = this.getMouseElement(event);
var linkConnected = false;
_.forEach(this.state.action.selectionModels, model => {
//only care about points connecting to things
if (!(model.model instanceof PointModel)) {
return;
}
if (element && element.model instanceof PortModel && !diagramEngine.isModelLocked(element.model)) {
linkConnected = true;
let link = model.model.getLink();
if (link.getTargetPort() !== null) {
//if this was a valid link already and we are adding a node in the middle, create 2 links from the original
if (link.getTargetPort() !== element.model && link.getSourcePort() !== element.model) {
const targetPort = link.getTargetPort();
let newLink = link.clone({});
newLink.setSourcePort(element.model);
newLink.setTargetPort(targetPort);
link.setTargetPort(element.model);
targetPort.removeLink(link);
newLink.removePointsBefore(newLink.getPoints()[link.getPointIndex(model.model)]);
link.removePointsAfter(model.model);
diagramEngine.getDiagramModel().addLink(newLink);
//if we are connecting to the same target or source, remove tweener points
} else if (link.getTargetPort() === element.model) {
link.removePointsAfter(model.model);
} else if (link.getSourcePort() === element.model) {
link.removePointsBefore(model.model);
}
} else {
link.setTargetPort(element.model);
}
delete this.props.diagramEngine.linksThatHaveInitiallyRendered[link.getID()];
}
//if we moved a NodeModel and allowLooseLinks is false, we know that any links involved were valid
if ((!this.props.allowLooseLinks && element.model instanceof NodeModel) || !this.state.wasMoved) {
linkConnected = true;
}
});
//do we want to allow loose links on the diagram model or not
if (!linkConnected && !this.props.allowLooseLinks) {
_.forEach(this.state.action.selectionModels, model => {
//only care about points connecting to things
if (!(model.model instanceof PointModel)) {
return;
}
var link = model.model.getLink();
if (link.isLastPoint(model.model)) {
link.remove();
}
});
}
//remove any invalid links
_.forEach(this.state.action.selectionModels, model => {
//only care about points connecting to things
if (!(model.model instanceof PointModel)) {
return;
}
var link = model.model.getLink();
var sourcePort: PortModel = link.getSourcePort();
var targetPort: PortModel = link.getTargetPort();
if (sourcePort !== null && targetPort !== null) {
if (!sourcePort.canLinkToPort(targetPort)) {
//link not allowed
link.remove();
} else if (
_.some(
_.values(targetPort.getLinks()),
(l: LinkModel) =>
l !== link && (l.getSourcePort() === sourcePort || l.getTargetPort() === sourcePort)
)
) {
//link is a duplicate
link.remove();
}
}
});
diagramEngine.clearRepaintEntities();
this.stopFiringAction(!this.state.wasMoved);
} else {
diagramEngine.clearRepaintEntities();
this.stopFiringAction();
}
this.state.document.removeEventListener("mousemove", this.onMouseMove);
this.state.document.removeEventListener("mouseup", this.onMouseUp);
}
drawSelectionBox() {
let dimensions = (this.state.action as SelectingAction).getBoxDimensions();
return (
<div
className={this.bem("__selector")}
style={{
top: dimensions.top,
left: dimensions.left,
width: dimensions.width,
height: dimensions.height
}}
/>
);
}
render() {
var diagramEngine = this.props.diagramEngine;
diagramEngine.setMaxNumberPointsPerLink(this.props.maxNumberPointsPerLink);
diagramEngine.setSmartRoutingStatus(this.props.smartRouting);
var diagramModel = diagramEngine.getDiagramModel();
return (
<div
{...this.getProps()}
ref={ref => {
if (ref) {
this.props.diagramEngine.setCanvas(ref);
}
}}
onWheel={event => {
if (this.props.allowCanvasZoom) {
event.preventDefault();
event.stopPropagation();
const oldZoomFactor = diagramModel.getZoomLevel() / 100;
let scrollDelta = this.props.inverseZoom ? -event.deltaY : event.deltaY;
//check if it is pinch gesture
if (event.ctrlKey && scrollDelta % 1 !== 0) {
/*Chrome and Firefox sends wheel event with deltaY that
have fractional part, also `ctrlKey` prop of the event is true
though ctrl isn't pressed
*/
scrollDelta /= 3;
} else {
scrollDelta /= 60;
}
if (diagramModel.getZoomLevel() + scrollDelta > 10) {
diagramModel.setZoomLevel(diagramModel.getZoomLevel() + scrollDelta);
}
const zoomFactor = diagramModel.getZoomLevel() / 100;
const boundingRect = event.currentTarget.getBoundingClientRect();
const clientWidth = boundingRect.width;
const clientHeight = boundingRect.height;
// compute difference between rect before and after scroll
const widthDiff = clientWidth * zoomFactor - clientWidth * oldZoomFactor;
const heightDiff = clientHeight * zoomFactor - clientHeight * oldZoomFactor;
// compute mouse coords relative to canvas
const clientX = event.clientX - boundingRect.left;
const clientY = event.clientY - boundingRect.top;
// compute width and height increment factor
const xFactor = (clientX - diagramModel.getOffsetX()) / oldZoomFactor / clientWidth;
const yFactor = (clientY - diagramModel.getOffsetY()) / oldZoomFactor / clientHeight;
diagramModel.setOffset(
diagramModel.getOffsetX() - widthDiff * xFactor,
diagramModel.getOffsetY() - heightDiff * yFactor
);
diagramEngine.enableRepaintEntities([]);
this.forceUpdate();
}
}}
onMouseDown={event => {
this.setState({ ...this.state, wasMoved: false });
diagramEngine.clearRepaintEntities();
var model = this.getMouseElement(event);
//the canvas was selected
if (model === null) {
//is it a multiple selection
if (event.shiftKey) {
var relative = diagramEngine.getRelativePoint(event.clientX, event.clientY);
this.startFiringAction(new SelectingAction(relative.x, relative.y));
} else {
//its a drag the canvas event
diagramModel.clearSelection();
this.startFiringAction(new MoveCanvasAction(event.clientX, event.clientY, diagramModel));
}
} else if (model.model instanceof PortModel) {
//its a port element, we want to drag a link
if (!this.props.diagramEngine.isModelLocked(model.model)) {
var relative = diagramEngine.getRelativeMousePoint(event);
var sourcePort = model.model;
var link = sourcePort.createLinkModel();
link.setSourcePort(sourcePort);
if (link) {
link.removeMiddlePoints();
if (link.getSourcePort() !== sourcePort) {
link.setSourcePort(sourcePort);
}
link.setTargetPort(null);
link.getFirstPoint().updateLocation(relative);
link.getLastPoint().updateLocation(relative);
diagramModel.clearSelection();
link.getLastPoint().setSelected(true);
diagramModel.addLink(link);
this.startFiringAction(
new MoveItemsAction(event.clientX, event.clientY, diagramEngine)
);
}
} else {
diagramModel.clearSelection();
}
} else {
//its some or other element, probably want to move it
if (!event.shiftKey && !model.model.isSelected()) {
diagramModel.clearSelection();
}
model.model.setSelected(true);
this.startFiringAction(new MoveItemsAction(event.clientX, event.clientY, diagramEngine));
}
this.state.document.addEventListener("mousemove", this.onMouseMove);
this.state.document.addEventListener("mouseup", this.onMouseUp);
}}
>
{this.state.renderedNodes && (
<LinkLayerWidget
diagramEngine={diagramEngine}
pointAdded={(point: PointModel, event) => {
this.state.document.addEventListener("mousemove", this.onMouseMove);
this.state.document.addEventListener("mouseup", this.onMouseUp);
event.stopPropagation();
diagramModel.clearSelection(point);
this.setState({
action: new MoveItemsAction(event.clientX, event.clientY, diagramEngine)
});
}}
/>
)}
<NodeLayerWidget diagramEngine={diagramEngine} />
{this.state.action instanceof SelectingAction && this.drawSelectionBox()}
</div>
);
}
}

View File

@@ -1,30 +0,0 @@
import * as React from "react";
import { DiagramEngine } from "../DiagramEngine";
import { LinkModel } from "../models/LinkModel";
import { BaseWidget, BaseWidgetProps } from "./BaseWidget";
export interface LinkProps extends BaseWidgetProps {
link: LinkModel;
diagramEngine: DiagramEngine;
children?: any;
}
export interface LinkState {}
/**
* @author Dylan Vorster
*/
export class LinkWidget extends BaseWidget<LinkProps, LinkState> {
constructor(props: LinkProps) {
super("srd-link", props);
this.state = {};
}
shouldComponentUpdate() {
return this.props.diagramEngine.canEntityRepaint(this.props.link);
}
render() {
return this.props.children;
}
}

View File

@@ -1,8 +1,7 @@
import * as React from "react";
import { DiagramEngine } from "../DiagramEngine";
import { NodeModel } from "../models/NodeModel";
import { Toolkit } from "../Toolkit";
import { BaseWidget, BaseWidgetProps } from "./BaseWidget";
import { BaseWidget, BaseWidgetProps } from "@projectstorm/react-canvas";
export interface NodeProps extends BaseWidgetProps {
node: NodeModel;
@@ -12,19 +11,12 @@ export interface NodeProps extends BaseWidgetProps {
export interface NodeState {}
/**
* @author Dylan Vorster
*/
export class NodeWidget extends BaseWidget<NodeProps, NodeState> {
constructor(props: NodeProps) {
super("srd-node", props);
this.state = {};
}
shouldComponentUpdate() {
return this.props.diagramEngine.canEntityRepaint(this.props.node);
}
getClassName() {
return "node " + super.getClassName() + (this.props.node.isSelected() ? this.bem("--selected") : "");
}
@@ -33,10 +25,10 @@ export class NodeWidget extends BaseWidget<NodeProps, NodeState> {
return (
<div
{...this.getProps()}
data-nodeid={this.props.node.id}
data-nodeid={this.props.node.getID()}
style={{
top: this.props.node.y,
left: this.props.node.x
top: this.props.node.getDimensions().getTopLeft().y,
left: this.props.node.getDimensions().getTopLeft().x
}}
>
{this.props.children}

View File

@@ -1,6 +1,6 @@
import * as React from "react";
import { NodeModel } from "../models/NodeModel";
import { BaseWidget, BaseWidgetProps } from "./BaseWidget";
import { BaseWidget, BaseWidgetProps } from "@projectstorm/react-canvas";
export interface PortProps extends BaseWidgetProps {
name: string;
@@ -11,9 +11,6 @@ export interface PortState {
selected: boolean;
}
/**
* @author Dylan Vorster
*/
export class PortWidget extends BaseWidget<PortProps, PortState> {
constructor(props: PortProps) {
super("srd-port", props);

View File

@@ -1,89 +0,0 @@
import * as React from "react";
import { DiagramEngine } from "../../DiagramEngine";
import { LinkWidget } from "../LinkWidget";
import * as _ from "lodash";
import { PointModel } from "../../models/PointModel";
import { BaseWidget, BaseWidgetProps } from "../BaseWidget";
export interface LinkLayerProps extends BaseWidgetProps {
diagramEngine: DiagramEngine;
pointAdded: (point: PointModel, event: MouseEvent) => any;
}
export interface LinkLayerState {}
/**
* @author Dylan Vorster
*/
export class LinkLayerWidget extends BaseWidget<LinkLayerProps, LinkLayerState> {
constructor(props: LinkLayerProps) {
super("srd-link-layer", props);
this.state = {};
}
render() {
var diagramModel = this.props.diagramEngine.getDiagramModel();
return (
<svg
{...this.getProps()}
style={{
transform:
"translate(" +
diagramModel.getOffsetX() +
"px," +
diagramModel.getOffsetY() +
"px) scale(" +
diagramModel.getZoomLevel() / 100.0 +
")"
}}
>
{//only perform these actions when we have a diagram
this.props.diagramEngine.canvas &&
_.map(diagramModel.getLinks(), link => {
if (
this.props.diagramEngine.nodesRendered &&
!this.props.diagramEngine.linksThatHaveInitiallyRendered[link.id]
) {
if (link.sourcePort !== null) {
try {
const portCenter = this.props.diagramEngine.getPortCenter(link.sourcePort);
link.points[0].updateLocation(portCenter);
const portCoords = this.props.diagramEngine.getPortCoords(link.sourcePort);
link.sourcePort.updateCoords(portCoords);
this.props.diagramEngine.linksThatHaveInitiallyRendered[link.id] = true;
} catch (ex) {}
}
if (link.targetPort !== null) {
try {
const portCenter = this.props.diagramEngine.getPortCenter(link.targetPort);
_.last(link.points).updateLocation(portCenter);
const portCoords = this.props.diagramEngine.getPortCoords(link.targetPort);
link.targetPort.updateCoords(portCoords);
this.props.diagramEngine.linksThatHaveInitiallyRendered[link.id] = true;
} catch (ex) {}
}
}
//generate links
var generatedLink = this.props.diagramEngine.generateWidgetForLink(link);
if (!generatedLink) {
console.log("no link generated for type: " + link.getType());
return null;
}
return (
<LinkWidget key={link.getID()} link={link} diagramEngine={this.props.diagramEngine}>
{React.cloneElement(generatedLink, {
pointAdded: this.props.pointAdded
})}
</LinkWidget>
);
})}
</svg>
);
}
}

View File

@@ -1,64 +0,0 @@
import * as React from "react";
import { DiagramEngine } from "../../DiagramEngine";
import * as _ from "lodash";
import { NodeWidget } from "../NodeWidget";
import { NodeModel } from "../../models/NodeModel";
import { BaseWidget, BaseWidgetProps } from "../BaseWidget";
export interface NodeLayerProps extends BaseWidgetProps {
diagramEngine: DiagramEngine;
}
export interface NodeLayerState {}
export class NodeLayerWidget extends BaseWidget<NodeLayerProps, NodeLayerState> {
constructor(props: NodeLayerProps) {
super("srd-node-layer", props);
this.state = {};
}
updateNodeDimensions = () => {
if (!this.props.diagramEngine.nodesRendered) {
const diagramModel = this.props.diagramEngine.getDiagramModel();
_.map(diagramModel.getNodes(), node => {
node.updateDimensions(this.props.diagramEngine.getNodeDimensions(node));
});
}
};
componentDidUpdate() {
this.updateNodeDimensions();
this.props.diagramEngine.nodesRendered = true;
}
render() {
var diagramModel = this.props.diagramEngine.getDiagramModel();
return (
<div
{...this.getProps()}
style={{
transform:
"translate(" +
diagramModel.getOffsetX() +
"px," +
diagramModel.getOffsetY() +
"px) scale(" +
diagramModel.getZoomLevel() / 100.0 +
")"
}}
>
{_.map(diagramModel.getNodes(), (node: NodeModel) => {
return React.createElement(
NodeWidget,
{
diagramEngine: this.props.diagramEngine,
key: node.id,
node: node
},
this.props.diagramEngine.generateWidgetForNode(node)
);
})}
</div>
);
}
}

126
tests/e2e/E2EHelper.ts Normal file
View File

@@ -0,0 +1,126 @@
import { ElementHandle, Page } from "puppeteer";
import * as _ from "lodash";
export class E2EElement {
helper: E2EHelper;
page: Page;
element: ElementHandle;
id: string;
constructor(helper: E2EHelper, page: Page, element: ElementHandle, id: string) {
this.page = page;
this.element = element;
this.id = id;
this.helper = helper;
}
}
export class E2ENode extends E2EElement {
async port(id: string): Promise<E2EPort> {
return new E2EPort(this.helper, this.page, await this.element.$(`div[data-name="${id}"]`), id, this);
}
async model(): Promise<any> {
return await this.page.evaluate(id => {
return window["diagram_instance"]
.getDiagramModel()
.getNode(id)
.serialize();
}, this.id);
}
}
export class E2EPort extends E2EElement {
parent: E2ENode;
constructor(helper: E2EHelper, page: Page, element: ElementHandle, id: string, parent: E2ENode) {
super(helper, page, element, id);
this.parent = parent;
}
async link(port: E2EPort): Promise<E2ELink> {
let currentLinks = _.flatMap((await this.parent.model()).ports, "links");
let bounds = await this.element.boundingBox();
// click on this port
this.page.mouse.move(bounds.x, bounds.y);
this.page.mouse.down();
let bounds2 = await port.element.boundingBox();
// drag to other port
this.page.mouse.move(bounds2.x, bounds2.y);
this.page.mouse.up();
// get the parent to get the link
return await this.helper.link(
_.difference(_.flatMap((await this.parent.model()).ports, "links"), currentLinks)[0]
);
}
async linkToPoint(x: number, y: number): Promise<E2ELink> {
let currentLinks = _.flatMap((await this.parent.model()).ports, "links");
let bounds = await this.element.boundingBox();
// click on this port
this.page.mouse.move(bounds.x, bounds.y);
this.page.mouse.down();
// drag to point
this.page.mouse.move(x, y);
this.page.mouse.up();
// get the parent to get the link
return await this.helper.link(
_.difference(_.flatMap((await this.parent.model()).ports, "links"), currentLinks)[0]
);
}
}
export class E2ELink extends E2EElement {
async model(): Promise<any> {
return await this.page.evaluate(id => {
return window["diagram_instance"]
.getDiagramModel()
.getLink(id)
.serialize();
}, this.id);
}
async exists(): Promise<boolean> {
return await this.page.evaluate(id => {
return !!document.querySelector(`path[data-linkid="${id}"]`);
}, this.id);
}
async select(): Promise<any> {
const point = await this.page.evaluate(id => {
const path = document.querySelector(`path[data-linkid="${id}"]`) as SVGPathElement;
return path.getPointAtLength(path.getTotalLength() / 2);
}, this.id);
await this.page.keyboard.down("Shift");
await this.page.mouse.move(point.x, point.y);
await this.page.mouse.down();
await this.page.keyboard.up("Shift");
}
}
export class E2EHelper {
page: Page;
constructor(page: Page) {
this.page = page;
}
async link(id): Promise<E2ELink> {
let selector = await this.page.waitForSelector(`path[data-linkid="${id}"]`);
return new E2ELink(this, this.page, selector, id);
}
async node(id): Promise<E2ENode> {
let selector = await this.page.waitForSelector(`div[data-nodeid="${id}"]`);
return new E2ENode(this, this.page, selector, id);
}
}

View File

@@ -1,115 +0,0 @@
import {ElementHandle, Page} from "puppeteer";
import {NodeModel} from "../../src/models/NodeModel";
import {LinkModel} from "../../src/models/LinkModel";
import * as _ from "lodash";
export class E2EElement{
helper: E2EHelper;
page: Page;
element: ElementHandle;
id: string;
constructor(helper: E2EHelper,page: Page, element: ElementHandle, id: string) {
this.page = page;
this.element = element;
this.id = id;
this.helper = helper;
}
}
export class E2ENode extends E2EElement{
async port(id: string): Promise<E2EPort>{
return new E2EPort(this.helper,this.page, await this.element.$('div[data-name="'+id+'"]'), id, this);
}
async model(): Promise<any>{
return await this.page.evaluate((id) => {
return window['diagram_instance'].getDiagramModel().getNode(id).serialize();
}, this.id)
}
}
export class E2EPort extends E2EElement{
parent: E2ENode;
constructor(helper: E2EHelper,page: Page, element: ElementHandle, id: string, parent: E2ENode) {
super(helper,page, element, id);
this.parent = parent;
}
async link(port: E2EPort): Promise<E2ELink>{
let currentLinks = _.flatMap((await this.parent.model()).ports, 'links');
let bounds = await this.element.boundingBox();
// click on this port
this.page.mouse.move(bounds.x, bounds.y);
this.page.mouse.down();
let bounds2 = await port.element.boundingBox();
// drag to other port
this.page.mouse.move(bounds2.x, bounds2.y);
this.page.mouse.up();
// get the parent to get the link
return await this.helper.link(_.difference(_.flatMap((await this.parent.model()).ports, 'links'), currentLinks)[0]);
}
}
export class E2ELink extends E2EElement{
async model(): Promise<any>{
return await this.page.evaluate((id) => {
return window['diagram_instance'].getDiagramModel().getLink(id).serialize();
}, this.id)
}
async exists():Promise<boolean>{
return await this.page.evaluate((id) => {
return !!document.querySelector('path[data-linkid="' + id + '"]');
}, this.id);
}
async select(): Promise<any>{
let point = await this.page.evaluate((id) => {
let path = (document.querySelector('path[data-linkid="' + id + '"]') as SVGPathElement);
let point = path.getPointAtLength(path.getTotalLength()/2);
return {
x: point.x,
y: point.y
}
}, this.id);
await this.page.keyboard.down('Shift');
await this.page.mouse.move(point.x, point.y);
await this.page.mouse.down();
await this.page.keyboard.up('Shift');
}
}
export class E2EHelper {
page: Page;
constructor(page: Page) {
this.page = page;
}
async link(id): Promise<E2ELink> {
let selector = await this.page.waitForSelector('path[data-linkid="' + id + '"]');
return new E2ELink(this,this.page, selector, id);
}
async node(id): Promise<E2ENode> {
let selector = await this.page.waitForSelector('div[data-nodeid="' + id + '"]');
return new E2ENode(this,this.page, selector, id);
}
}

View File

@@ -14,7 +14,7 @@ glob.glob(__dirname + "/../../demos/demo-*/index.tsx", {}, (err, files) => {
copy.push({to: path.basename(path.dirname(entryFile)), from: __dirname+"/index.html"});
});
webpack({
config = {
entry: entry,
plugins: [
new CopyWebpackPlugin(copy)
@@ -29,12 +29,21 @@ glob.glob(__dirname + "/../../demos/demo-*/index.tsx", {}, (err, files) => {
use: ['style-loader','css-loader', 'sass-loader']
}].concat(config.module.rules)
},
resolve: config.resolve,
}, (err, stats) => {
resolve: {
...config.resolve,
alias: {
'storm-react-diagrams': path.join(__dirname, "..", "..", "src", "main")
}
},
};
console.log(config);
webpack(config, (err, stats) => {
if (err || stats.hasErrors()) {
// Handle errors here
return;
}
});
});
});

View File

@@ -0,0 +1,60 @@
import "jest";
import * as puppeteer from "puppeteer";
import { E2EHelper } from "./E2EHelper";
var browser;
async function itShould(demo: string, directive, test: (page: puppeteer.Page, helper: E2EHelper) => any) {
it(directive, async () => {
let page = await browser.newPage();
await page.goto("file://" + __dirname + "/../../dist/e2e/" + demo + "/index.html");
let helper = new E2EHelper(page);
await test(page, helper);
await page.close();
});
}
beforeAll(async () => {
if (process.env.CIRCLECI) {
console.log("using CircleCI");
browser = await puppeteer.launch({
args: ["--no-sandbox", "--disable-setuid-sandbox"]
});
} else {
browser = await puppeteer.launch({
headless: false
});
}
});
afterAll(() => {
browser.close();
});
describe("simple flow test", async () => {
itShould("demo-simple-flow", "drag link to port adds a link", async (page, helper) => {
// create a new link
let node1 = await helper.node("6");
let node2 = await helper.node("9");
let port1 = await node1.port("7");
let port2 = await node2.port("10");
let newlink = await port1.link(port2);
await expect(await newlink.exists()).toBeTruthy();
});
itShould("demo-simple-flow", "drag link to node does not add a link", async (page, helper) => {
// create a new link
let node1 = await helper.node("6");
let node2 = await helper.node("9");
let port1 = await node1.port("7");
let node2Bounds = await node2.element.boundingBox();
let newlink = await port1.linkToPoint(node2Bounds.x, node2Bounds.y);
await expect(await newlink.exists()).toBeFalsy();
});
});

View File

@@ -1,13 +1,13 @@
import "jest";
import * as puppeteer from "puppeteer"
import {E2EHelper} from "./E2EHelper";
import * as puppeteer from "puppeteer";
import { E2EHelper } from "./E2EHelper";
var browser;
async function itShould(demo: string, directive, test: (page: puppeteer.Page, helper: E2EHelper) => any) {
it(directive, async () => {
let page = await browser.newPage();
await page.goto('file://' + __dirname + '/../../dist/e2e/' + demo + "/index.html");
await page.goto("file://" + __dirname + "/../../dist/e2e/" + demo + "/index.html");
let helper = new E2EHelper(page);
await test(page, helper);
await page.close();
@@ -19,7 +19,7 @@ beforeAll(async () => {
console.log("using CircleCI");
browser = await puppeteer.launch({
args: ['--no-sandbox', '--disable-setuid-sandbox']
args: ["--no-sandbox", "--disable-setuid-sandbox"]
});
} else {
browser = await puppeteer.launch({
@@ -32,31 +32,26 @@ afterAll(() => {
browser.close();
});
describe("simple test", async () => {
itShould("demo-simple", 'should delete a link and create a new one', async (page, helper) => {
itShould("demo-simple", "should delete a link and create a new one", async (page, helper) => {
// get the existing link
let link = await helper.link('12');
let link = await helper.link("12");
await expect(await link.exists()).toBeTruthy();
// remove it
await link.select();
await page.keyboard.press('Backspace');
await page.keyboard.press("Del");
await expect(await link.exists()).toBeFalsy();
// create a new link
let node1 = await helper.node('6');
let node2 = await helper.node('9');
let node1 = await helper.node("6");
let node2 = await helper.node("9");
let port1 = await node1.port('7');
let port2 = await node2.port('10');
let port1 = await node1.port("7");
let port2 = await node2.port("10");
let newlink = await port1.link(port2);
await expect(await newlink.exists()).toBeTruthy();
});
})
});

View File

@@ -1,47 +1,41 @@
import PathFinding from "../../src/routing/PathFinding";
describe('calculating start and end points', () => {
describe("calculating start and end points", () => {
beforeEach(() => {
this.pathFinding = new PathFinding(null);
});
beforeEach(() => {
this.pathFinding = new PathFinding(null);
});
test('return correct object for valid walkable input', () => {
const matrix = [
[0, 0, 0, 0, 1, 1],
[0, 0, 0, 0, 1, 1],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[1, 1, 0, 0, 0, 0],
[1, 1, 0, 0, 0, 0],
];
const path = [[0, 5], [1, 4], [2, 3], [3, 2], [4, 1], [5, 0]];
test("return correct object for valid walkable input", () => {
const matrix = [
[0, 0, 0, 0, 1, 1],
[0, 0, 0, 0, 1, 1],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[1, 1, 0, 0, 0, 0],
[1, 1, 0, 0, 0, 0]
];
const path = [[0, 5], [1, 4], [2, 3], [3, 2], [4, 1], [5, 0]];
const result = this.pathFinding.calculateLinkStartEndCoords(matrix, path);
const result = this.pathFinding.calculateLinkStartEndCoords(matrix, path);
expect(result.start).toEqual({
x: 2,
y: 3,
});
expect(result.end).toEqual({
x: 3,
y: 2,
});
expect(result.pathToStart).toEqual([[ 0, 5 ], [ 1, 4 ]]);
expect(result.pathToEnd).toEqual([[ 3, 2 ], [ 4, 1 ], [ 5, 0 ]]);
});
expect(result.start).toEqual({
x: 2,
y: 3
});
expect(result.end).toEqual({
x: 3,
y: 2
});
expect(result.pathToStart).toEqual([[0, 5], [1, 4]]);
expect(result.pathToEnd).toEqual([[3, 2], [4, 1], [5, 0]]);
});
test('undefined is returned when no walkable path exists', () => {
const matrix = [
[0, 0, 1, 1],
[0, 0, 1, 1],
[1, 1, 0, 0],
[1, 1, 0, 0],
];
const path = [[0, 3], [1, 2], [2, 1], [3, 0]];
test("undefined is returned when no walkable path exists", () => {
const matrix = [[0, 0, 1, 1], [0, 0, 1, 1], [1, 1, 0, 0], [1, 1, 0, 0]];
const path = [[0, 3], [1, 2], [2, 1], [3, 0]];
const result = this.pathFinding.calculateLinkStartEndCoords(matrix, path);
const result = this.pathFinding.calculateLinkStartEndCoords(matrix, path);
expect(result).toBeUndefined();
});
});
expect(result).toBeUndefined();
});
});

View File

@@ -571,12 +571,12 @@ exports[`Storyshots Tests demos/demo-custom-node1/index.tsx 1`] = `
dangerouslySetInnerHTML={
Object {
"__html": "
<g id=\\"Layer_1\\">
</g>
<g id=\\"Layer_2\\">
<polygon fill=\\"purple\\" stroke=\\"#000000\\" stroke-width=\\"3\\" stroke-miterlimit=\\"10\\" points=\\"10,75 75,10 140,75 75,140 \\"/>
</g>
",
<g id=\\"Layer_1\\">
</g>
<g id=\\"Layer_2\\">
<polygon fill=\\"purple\\" stroke=\\"#000000\\" stroke-width=\\"3\\" stroke-miterlimit=\\"10\\" points=\\"10,75 75,10 140,75 75,140 \\"/>
</g>
",
}
}
height={150}

View File

@@ -1,4 +1,4 @@
import initStoryshots from '@storybook/addon-storyshots';
import 'raf/polyfill';
import initStoryshots from "@storybook/addon-storyshots";
import "raf/polyfill";
initStoryshots({ configPath: __dirname });
initStoryshots({ configPath: __dirname });

View File

@@ -1,5 +1,5 @@
import * as React from "react";
import { storiesOf, addDecorator } from "@storybook/react";
import {Toolkit} from "../../src/Toolkit";
import { Toolkit } from "../../src/Toolkit";
Toolkit.TESTING = true;
Toolkit.TESTING = true;

Some files were not shown because too many files have changed in this diff Show More