mirror of
https://github.com/projectstorm/react-diagrams.git
synced 2026-03-13 09:50:09 +08:00
Compare commits
67 Commits
v5.0.0
...
react_canv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c730a9fd1 | ||
|
|
a086bc785e | ||
|
|
a7b6012a50 | ||
|
|
4117af8a33 | ||
|
|
eb7f0642a5 | ||
|
|
a8c73115ac | ||
|
|
404e12c4e8 | ||
|
|
aa6d1c336c | ||
|
|
204e05a2a1 | ||
|
|
d12220baa0 | ||
|
|
91c1ee0169 | ||
|
|
4806805037 | ||
|
|
22f4062f56 | ||
|
|
32a3c33916 | ||
|
|
75ef02dd4d | ||
|
|
665c8b3443 | ||
|
|
ccf425676f | ||
|
|
1ff3abf3ef | ||
|
|
0b1dab0de6 | ||
|
|
0988e625b1 | ||
|
|
dbaf03662f | ||
|
|
55f62587bd | ||
|
|
98bcd60396 | ||
|
|
8467d8e7ca | ||
|
|
40b4e14f15 | ||
|
|
7a78aaa9fd | ||
|
|
47214df76b | ||
|
|
cc40c398b7 | ||
|
|
188f63979b | ||
|
|
468bcea8b2 | ||
|
|
54b9feb62e | ||
|
|
9f397b1453 | ||
|
|
445702dd43 | ||
|
|
bb6a6227e2 | ||
|
|
afff63e46a | ||
|
|
03e8348a14 | ||
|
|
2c6d02f101 | ||
|
|
327dcc190d | ||
|
|
c40c5c5919 | ||
|
|
388b9931b1 | ||
|
|
ae04b5a07f | ||
|
|
9d2f450440 | ||
|
|
45d2ea0c70 | ||
|
|
94fe0fc854 | ||
|
|
adf268697a | ||
|
|
2c478db23f | ||
|
|
3ea6375011 | ||
|
|
64ec2dd0a4 | ||
|
|
e84c2e4e3e | ||
|
|
5f5f13a818 | ||
|
|
2b1a39f236 | ||
|
|
4402068a93 | ||
|
|
ed50438744 | ||
|
|
9a651c3ecc | ||
|
|
1af2f8cbe9 | ||
|
|
2c74a5cf9a | ||
|
|
140817f8c6 | ||
|
|
1debc9c891 | ||
|
|
2ee0211994 | ||
|
|
86047f69fd | ||
|
|
036d8dddcf | ||
|
|
08b81fff56 | ||
|
|
fce1e0c7fe | ||
|
|
d69f61e39d | ||
|
|
eb6fac30e0 | ||
|
|
446cc8cdff | ||
|
|
29ca66989c |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,6 +1,9 @@
|
||||
dist/
|
||||
dist/main.js
|
||||
dist/main.js.map
|
||||
/package
|
||||
*.tgz
|
||||
@types/
|
||||
|
||||
.out
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ docs
|
||||
.storybook
|
||||
.circleci
|
||||
tests
|
||||
*.md
|
||||
|
||||
# Created by https://www.gitignore.io/api/net,netbeans,sublimetext,phpstorm,windows,osx,node
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
};
|
||||
|
||||
13
CHANGELOG.md
13
CHANGELOG.md
@@ -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
|
||||
|
||||
|
||||
52
README.md
52
README.md
@@ -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.
|
||||
|
||||
[](https://gitter.im/projectstorm/react-diagrams?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://npmjs.org/package/storm-react-diagrams)
|
||||
[](https://npmjs.org/package/storm-react-diagrams)
|
||||
[](https://circleci.com/gh/projectstorm/react-diagrams/tree/master)
|
||||
[](https://gitter.im/projectstorm/react-diagrams?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://npmjs.org/package/storm-react-diagrams) [](https://npmjs.org/package/storm-react-diagrams) [](http://packagequality.com/#?package=storm-react-diagrams) [](https://circleci.com/gh/projectstorm/react-diagrams/tree/master)
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
## 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__:
|
||||
|
||||

|
||||
|
||||
## 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
11
SUMMARY.md
Normal 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
0
about-the-project.md
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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} />;
|
||||
|
||||
@@ -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} />;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
14
demos/demo-custom-node1/SimplePortFactory.ts
Normal file
14
demos/demo-custom-node1/SimplePortFactory.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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} />;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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} />;
|
||||
};
|
||||
|
||||
@@ -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)");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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} />;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
//!========================================= <<<<<<<
|
||||
|
||||
|
||||
@@ -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} />;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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} />;
|
||||
|
||||
@@ -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} />;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
182
demos/index.tsx
182
demos/index.tsx
@@ -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
10
demos/tslint.json
Normal 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
142
docs/Getting Started.md
Normal 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} />;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
BIN
images/demo3.png
BIN
images/demo3.png
Binary file not shown.
|
Before Width: | Height: | Size: 90 KiB |
BIN
images/example1.jpg
Normal file
BIN
images/example1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 438 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.5 MiB |
BIN
images/example2.jpg
Normal file
BIN
images/example2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 313 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 870 KiB |
BIN
images/example3.jpg
Normal file
BIN
images/example3.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
@@ -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'
|
||||
|
||||
56
package.json
56
package.json
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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[][]) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
21
src/main.ts
21
src/main.ts
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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%;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
126
tests/e2e/E2EHelper.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
60
tests/e2e/simple-flow.test.ts
Normal file
60
tests/e2e/simple-flow.test.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
})
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user