mirror of
https://github.com/projectstorm/react-diagrams.git
synced 2026-03-13 09:50:09 +08:00
Compare commits
13 Commits
changeset-
...
react_canv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c730a9fd1 | ||
|
|
a086bc785e | ||
|
|
a7b6012a50 | ||
|
|
4117af8a33 | ||
|
|
eb7f0642a5 | ||
|
|
a8c73115ac | ||
|
|
404e12c4e8 | ||
|
|
aa6d1c336c | ||
|
|
204e05a2a1 | ||
|
|
d12220baa0 | ||
|
|
91c1ee0169 | ||
|
|
4806805037 | ||
|
|
22f4062f56 |
@@ -22,7 +22,7 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
loader: 'awesome-typescript-loader',
|
||||
loader: 'awesome-typescript-loader?declaration=false',
|
||||
},
|
||||
{
|
||||
test: /\.(woff|woff2|eot|ttf|otf|svg)$/,
|
||||
|
||||
@@ -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} />;
|
||||
|
||||
@@ -118,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
|
||||
@@ -150,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} />;
|
||||
|
||||
@@ -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} />;
|
||||
|
||||
@@ -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} />;
|
||||
};
|
||||
|
||||
@@ -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)");
|
||||
|
||||
@@ -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} />;
|
||||
|
||||
@@ -14,7 +14,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();
|
||||
@@ -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
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
export default () => {
|
||||
// setup the diagram engine
|
||||
var engine = new DiagramEngine();
|
||||
engine.installDefaultFactories();
|
||||
engine.installDefaults();
|
||||
|
||||
var model = new DiagramModel();
|
||||
|
||||
@@ -33,7 +33,7 @@ export default () => {
|
||||
|
||||
model.addAll(node1, node2, link1);
|
||||
|
||||
engine.setDiagramModel(model);
|
||||
engine.setModel(model);
|
||||
|
||||
var props = {
|
||||
diagramEngine: engine,
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
export default () => {
|
||||
// setup the diagram engine
|
||||
var engine = new DiagramEngine();
|
||||
engine.installDefaultFactories();
|
||||
engine.installDefaults();
|
||||
|
||||
var model = new DiagramModel();
|
||||
|
||||
@@ -46,7 +46,7 @@ export default () => {
|
||||
});
|
||||
});
|
||||
|
||||
engine.setDiagramModel(model);
|
||||
engine.setModel(model);
|
||||
|
||||
var props = {
|
||||
diagramEngine: engine,
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
export default () => {
|
||||
//1) setup the diagram engine
|
||||
var engine = new DiagramEngine();
|
||||
engine.installDefaultFactories();
|
||||
engine.installDefaults();
|
||||
|
||||
var model = new DiagramModel();
|
||||
|
||||
@@ -51,7 +51,7 @@ export default () => {
|
||||
|
||||
model.addAll(node3, node4, link2);
|
||||
|
||||
engine.setDiagramModel(model);
|
||||
engine.setModel(model);
|
||||
|
||||
//!========================================= <<<<<<<
|
||||
|
||||
|
||||
@@ -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} />;
|
||||
|
||||
@@ -11,7 +11,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();
|
||||
@@ -23,7 +23,7 @@ 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} />;
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { DiagramEngine, DiagramModel, DefaultNodeModel, LinkModel, DiagramWidget } from "storm-react-diagrams";
|
||||
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";
|
||||
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
|
||||
|
||||
@@ -4,7 +4,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();
|
||||
@@ -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} />;
|
||||
|
||||
@@ -11,7 +11,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();
|
||||
@@ -34,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} />;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -18,7 +18,7 @@ import { DemoWorkspaceWidget } from "../.helpers/DemoWorkspaceWidget";
|
||||
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,11 +30,13 @@ 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>
|
||||
);
|
||||
|
||||
180
demos/index.tsx
180
demos/index.tsx
@@ -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()
|
||||
|
||||
@@ -1,115 +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: { [s: string]: any } = {}, clone: any) {
|
||||
/*noop*/
|
||||
}
|
||||
|
||||
clone(lookupTable: { [s: string]: any } = {}) {
|
||||
// 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: { [s: string]: any }, 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) {
|
||||
if (this.listeners.hasOwnProperty(i)) {
|
||||
// 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,365 +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 { AbstractLabelFactory } from "./factories/AbstractLabelFactory";
|
||||
import { AbstractLinkFactory } from "./factories/AbstractLinkFactory";
|
||||
import { AbstractNodeFactory } from "./factories/AbstractNodeFactory";
|
||||
import { AbstractPortFactory } from "./factories/AbstractPortFactory";
|
||||
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]: AbstractNodeFactory };
|
||||
linkFactories: { [s: string]: AbstractLinkFactory };
|
||||
portFactories: { [s: string]: AbstractPortFactory };
|
||||
labelFactories: { [s: string]: AbstractLabelFactory };
|
||||
|
||||
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 => {
|
||||
if (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]: AbstractNodeFactory } {
|
||||
return this.nodeFactories;
|
||||
}
|
||||
|
||||
getLinkFactories(): { [s: string]: AbstractLinkFactory } {
|
||||
return this.linkFactories;
|
||||
}
|
||||
|
||||
getLabelFactories(): { [s: string]: AbstractLabelFactory } {
|
||||
return this.labelFactories;
|
||||
}
|
||||
|
||||
registerLabelFactory(factory: AbstractLabelFactory) {
|
||||
this.labelFactories[factory.getType()] = factory;
|
||||
this.iterateListeners(listener => {
|
||||
if (listener.labelFactoriesUpdated) {
|
||||
listener.labelFactoriesUpdated();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
registerPortFactory(factory: AbstractPortFactory) {
|
||||
this.portFactories[factory.getType()] = factory;
|
||||
this.iterateListeners(listener => {
|
||||
if (listener.portFactoriesUpdated) {
|
||||
listener.portFactoriesUpdated();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
registerNodeFactory(factory: AbstractNodeFactory) {
|
||||
this.nodeFactories[factory.getType()] = factory;
|
||||
this.iterateListeners(listener => {
|
||||
if (listener.nodeFactoriesUpdated) {
|
||||
listener.nodeFactoriesUpdated();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
registerLinkFactory(factory: AbstractLinkFactory) {
|
||||
this.linkFactories[factory.getType()] = factory;
|
||||
this.iterateListeners(listener => {
|
||||
if (listener.linkFactoriesUpdated) {
|
||||
listener.linkFactoriesUpdated();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getPortFactory(type: string): AbstractPortFactory {
|
||||
if (this.portFactories[type]) {
|
||||
return this.portFactories[type];
|
||||
}
|
||||
throw new Error(`cannot find factory for port of type: [${type}]`);
|
||||
}
|
||||
|
||||
getNodeFactory(type: string): AbstractNodeFactory {
|
||||
if (this.nodeFactories[type]) {
|
||||
return this.nodeFactories[type];
|
||||
}
|
||||
throw new Error(`cannot find factory for node of type: [${type}]`);
|
||||
}
|
||||
|
||||
getLinkFactory(type: string): AbstractLinkFactory {
|
||||
if (this.linkFactories[type]) {
|
||||
return this.linkFactories[type];
|
||||
}
|
||||
throw new Error(`cannot find factory for link of type: [${type}]`);
|
||||
}
|
||||
|
||||
getLabelFactory(type: string): AbstractLabelFactory {
|
||||
if (this.labelFactories[type]) {
|
||||
return this.labelFactories[type];
|
||||
}
|
||||
throw new Error(`cannot find factory for label of type: [${type}]`);
|
||||
}
|
||||
|
||||
getFactoryForNode(node: NodeModel): AbstractNodeFactory | null {
|
||||
return this.getNodeFactory(node.getType());
|
||||
}
|
||||
|
||||
getFactoryForLink(link: LinkModel): AbstractLinkFactory | null {
|
||||
return this.getLinkFactory(link.getType());
|
||||
}
|
||||
|
||||
getFactoryForLabel(label: LabelModel): AbstractLabelFactory | 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 {
|
||||
@@ -371,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,29 +3,11 @@ 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, c => {
|
||||
const r = (Math.random() * 16) | 0;
|
||||
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
export class Toolkit extends TK {
|
||||
/**
|
||||
* Finds the closest element as a polyfill
|
||||
*
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { BaseAction } from "./BaseAction";
|
||||
import { DiagramModel } from "../models/DiagramModel";
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { BaseAction } from "./BaseAction";
|
||||
import { SelectionModel } from "../models/SelectionModel";
|
||||
import { PointModel } from "../models/PointModel";
|
||||
import { NodeModel } from "../models/NodeModel";
|
||||
import { DiagramEngine } from "../DiagramEngine";
|
||||
|
||||
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,36 +0,0 @@
|
||||
import { BaseAction } from "./BaseAction";
|
||||
import { DiagramModel } from "../models/DiagramModel";
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,19 @@
|
||||
import * as React from "react";
|
||||
import { DiagramEngine } from "../../DiagramEngine";
|
||||
import { AbstractLabelFactory } from "../../factories/AbstractLabelFactory";
|
||||
import { DefaultLabelModel } from "../models/DefaultLabelModel";
|
||||
import { DefaultLabelWidget } from "../widgets/DefaultLabelWidget";
|
||||
import { AbstractElementFactory } from "@projectstorm/react-canvas";
|
||||
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export class DefaultLabelFactory extends AbstractLabelFactory<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 { AbstractLinkFactory } from "../../factories/AbstractLinkFactory";
|
||||
import { AbstractElementFactory } from "@projectstorm/react-canvas";
|
||||
import { DefaultLinkModel } from "../models/DefaultLinkModel";
|
||||
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export class DefaultLinkFactory extends AbstractLinkFactory<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 AbstractLinkFactory<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 { AbstractNodeFactory } from "../../factories/AbstractNodeFactory";
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export class DefaultNodeFactory extends AbstractNodeFactory<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 { AbstractPortFactory } from "../../factories/AbstractPortFactory";
|
||||
import { AbstractElementFactory } from "@projectstorm/react-canvas";
|
||||
import { DiagramEngine } from "storm-react-diagrams";
|
||||
|
||||
export class DefaultPortFactory extends AbstractPortFactory<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,9 +1,9 @@
|
||||
import { LabelModel } from "../../models/LabelModel";
|
||||
import * as _ from "lodash";
|
||||
import { DiagramEngine } from "../../DiagramEngine";
|
||||
import { DeserializeEvent } from "@projectstorm/react-canvas";
|
||||
|
||||
export class DefaultLabelModel extends LabelModel {
|
||||
label: string;
|
||||
protected label: string;
|
||||
|
||||
constructor() {
|
||||
super("default");
|
||||
@@ -14,9 +14,9 @@ export class DefaultLabelModel extends LabelModel {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
deSerialize(ob, engine: DiagramEngine) {
|
||||
super.deSerialize(ob, engine);
|
||||
this.label = ob.label;
|
||||
deSerialize(event: DeserializeEvent) {
|
||||
super.deSerialize(event);
|
||||
this.label = event.data.label;
|
||||
}
|
||||
|
||||
serialize() {
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
/**
|
||||
* @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 }): void;
|
||||
@@ -16,9 +11,9 @@ export interface DefaultLinkModelListener extends LinkModelListener {
|
||||
}
|
||||
|
||||
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,7 +48,7 @@ export class DefaultLinkModel extends LinkModel<DefaultLinkModelListener> {
|
||||
|
||||
setWidth(width: number) {
|
||||
this.width = width;
|
||||
this.iterateListeners((listener: DefaultLinkModelListener, event: BaseEvent) => {
|
||||
this.iterateListeners("width changed", (listener: DefaultLinkModelListener, event: BaseEvent) => {
|
||||
if (listener.widthChanged) {
|
||||
listener.widthChanged({ ...event, width: width });
|
||||
}
|
||||
@@ -62,10 +57,22 @@ export class DefaultLinkModel extends LinkModel<DefaultLinkModelListener> {
|
||||
|
||||
setColor(color: string) {
|
||||
this.color = color;
|
||||
this.iterateListeners((listener: DefaultLinkModelListener, event: BaseEvent) => {
|
||||
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,7 +308,9 @@ export class DefaultLinkWidget extends BaseWidget<DefaultLinkProps, DefaultLinkS
|
||||
// See @link{#isSmartRoutingApplicable()}.
|
||||
if (paths.length === 0) {
|
||||
if (points.length === 2) {
|
||||
var isHorizontal = Math.abs(points[0].x - points[1].x) > Math.abs(points[0].y - points[1].y);
|
||||
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
|
||||
@@ -327,7 +332,7 @@ export class DefaultLinkWidget extends BaseWidget<DefaultLinkProps, DefaultLinkS
|
||||
|
||||
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);
|
||||
@@ -348,7 +353,7 @@ export class DefaultLinkWidget extends BaseWidget<DefaultLinkProps, DefaultLinkS
|
||||
this.generateLink(
|
||||
Toolkit.generateLinePath(points[j], points[j + 1]),
|
||||
{
|
||||
"data-linkid": this.props.link.id,
|
||||
"data-linkid": this.props.link.getID(),
|
||||
"data-point": j,
|
||||
onMouseDown: (event: MouseEvent) => {
|
||||
this.addPointToLink(event, j + 1);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { BaseModel } from "../models/BaseModel";
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { LabelModel } from "../models/LabelModel";
|
||||
import { DiagramEngine } from "../DiagramEngine";
|
||||
import { AbstractFactory } from "./AbstractFactory";
|
||||
|
||||
export abstract class AbstractLabelFactory<T extends LabelModel = LabelModel> extends AbstractFactory<T> {
|
||||
abstract generateReactWidget(diagramEngine: DiagramEngine, link: T): JSX.Element;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { LinkModel } from "../models/LinkModel";
|
||||
import { DiagramEngine } from "../DiagramEngine";
|
||||
import { AbstractFactory } from "./AbstractFactory";
|
||||
|
||||
export abstract class AbstractLinkFactory<T extends LinkModel = LinkModel> extends AbstractFactory<T> {
|
||||
abstract generateReactWidget(diagramEngine: DiagramEngine, link: T): JSX.Element;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { NodeModel } from "../models/NodeModel";
|
||||
import { DiagramEngine } from "../DiagramEngine";
|
||||
import { AbstractFactory } from "./AbstractFactory";
|
||||
|
||||
export abstract class AbstractNodeFactory<T extends NodeModel = NodeModel> extends AbstractFactory<T> {
|
||||
abstract generateReactWidget(diagramEngine: DiagramEngine, node: T): JSX.Element;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { PortModel } from "../models/PortModel";
|
||||
import { DiagramEngine } from "../DiagramEngine";
|
||||
import { AbstractFactory } from "./AbstractFactory";
|
||||
|
||||
export abstract class AbstractPortFactory<T extends PortModel = PortModel> extends AbstractFactory<T> {}
|
||||
20
src/main.ts
20
src/main.ts
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
|
||||
export * from "./Toolkit";
|
||||
export * from "./BaseEntity";
|
||||
export * from "./DiagramEngine";
|
||||
|
||||
export * from "./defaults/models/DefaultNodeModel";
|
||||
@@ -21,21 +20,8 @@ export * from "./defaults/widgets/DefaultLabelWidget";
|
||||
export * from "./defaults/widgets/DefaultNodeWidget";
|
||||
export * from "./defaults/widgets/DefaultPortLabelWidget";
|
||||
|
||||
export * from "./factories/AbstractFactory";
|
||||
export * from "./factories/AbstractLabelFactory";
|
||||
export * from "./factories/AbstractLinkFactory";
|
||||
export * from "./factories/AbstractNodeFactory";
|
||||
export * from "./factories/AbstractPortFactory";
|
||||
|
||||
export * from "./routing/PathFinding";
|
||||
|
||||
export * from "./actions/BaseAction";
|
||||
export * from "./actions/MoveCanvasAction";
|
||||
export * from "./actions/MoveItemsAction";
|
||||
export * from "./actions/SelectingAction";
|
||||
|
||||
export * from "./models/SelectionModel";
|
||||
export * from "./models/BaseModel";
|
||||
export * from "./models/DiagramModel";
|
||||
export * from "./models/LinkModel";
|
||||
export * from "./models/NodeModel";
|
||||
@@ -43,11 +29,5 @@ export * from "./models/PointModel";
|
||||
export * from "./models/PortModel";
|
||||
export * from "./models/LabelModel";
|
||||
|
||||
export * from "./widgets/DiagramWidget";
|
||||
export * from "./widgets/LinkWidget";
|
||||
export * from "./widgets/NodeWidget";
|
||||
export * from "./widgets/PortWidget";
|
||||
export * from "./widgets/BaseWidget";
|
||||
|
||||
export * from "./widgets/layers/LinkLayerWidget";
|
||||
export * from "./widgets/layers/NodeLayerWidget";
|
||||
|
||||
@@ -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<T> {
|
||||
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,240 +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) => {
|
||||
if (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) => {
|
||||
if (listener.zoomUpdated) {
|
||||
listener.zoomUpdated({ ...event, zoom: zoom });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setOffset(offsetX: number, offsetY: number) {
|
||||
this.offsetX = offsetX;
|
||||
this.offsetY = offsetY;
|
||||
this.iterateListeners((listener, event) => {
|
||||
if (listener.offsetUpdated) {
|
||||
listener.offsetUpdated({ ...event, offsetX: offsetX, offsetY: offsetY });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setOffsetX(offsetX: number) {
|
||||
this.offsetX = offsetX;
|
||||
this.iterateListeners((listener, event) => {
|
||||
if (listener.offsetUpdated) {
|
||||
listener.offsetUpdated({ ...event, offsetX: offsetX, offsetY: this.offsetY });
|
||||
}
|
||||
});
|
||||
}
|
||||
setOffsetY(offsetY: number) {
|
||||
this.offsetY = offsetY;
|
||||
|
||||
this.iterateListeners((listener, event) => {
|
||||
if (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[] {
|
||||
@@ -249,13 +57,8 @@ export class DiagramModel extends BaseEntity<DiagramListener> {
|
||||
}
|
||||
|
||||
addLink(link: LinkModel): LinkModel {
|
||||
link.addListener({
|
||||
entityRemoved: () => {
|
||||
this.removeLink(link);
|
||||
}
|
||||
});
|
||||
this.links[link.getID()] = link;
|
||||
this.iterateListeners((listener, event) => {
|
||||
this.linksLayer.addEntity(link);
|
||||
this.iterateListeners("link added", (listener, event) => {
|
||||
if (listener.linksUpdated) {
|
||||
listener.linksUpdated({ ...event, link: link, isCreated: true });
|
||||
}
|
||||
@@ -264,13 +67,8 @@ export class DiagramModel extends BaseEntity<DiagramListener> {
|
||||
}
|
||||
|
||||
addNode(node: NodeModel): NodeModel {
|
||||
node.addListener({
|
||||
entityRemoved: () => {
|
||||
this.removeNode(node);
|
||||
}
|
||||
});
|
||||
this.nodes[node.getID()] = node;
|
||||
this.iterateListeners((listener, event) => {
|
||||
this.nodesLayer.addEntity(node);
|
||||
this.iterateListeners("node added", (listener, event) => {
|
||||
if (listener.nodesUpdated) {
|
||||
listener.nodesUpdated({ ...event, node: node, isCreated: true });
|
||||
}
|
||||
@@ -279,9 +77,8 @@ export class DiagramModel extends BaseEntity<DiagramListener> {
|
||||
}
|
||||
|
||||
removeLink(link: LinkModel | string) {
|
||||
link = this.getLink(link);
|
||||
delete this.links[link.getID()];
|
||||
this.iterateListeners((listener, event) => {
|
||||
this.linksLayer.removeEntity(link);
|
||||
this.iterateListeners("link removed", (listener, event) => {
|
||||
if (listener.linksUpdated) {
|
||||
listener.linksUpdated({ ...event, link: link as LinkModel, isCreated: false });
|
||||
}
|
||||
@@ -289,9 +86,8 @@ export class DiagramModel extends BaseEntity<DiagramListener> {
|
||||
}
|
||||
|
||||
removeNode(node: NodeModel | string) {
|
||||
node = this.getNode(node);
|
||||
delete this.nodes[node.getID()];
|
||||
this.iterateListeners((listener, event) => {
|
||||
this.nodesLayer.removeEntity(node);
|
||||
this.iterateListeners("node removed", (listener, event) => {
|
||||
if (listener.nodesUpdated) {
|
||||
listener.nodesUpdated({ ...event, node: node as NodeModel, isCreated: false });
|
||||
}
|
||||
@@ -299,10 +95,10 @@ export class DiagramModel extends BaseEntity<DiagramListener> {
|
||||
}
|
||||
|
||||
getLinks(): { [s: string]: LinkModel } {
|
||||
return this.links;
|
||||
return this.linksLayer.getEntities();
|
||||
}
|
||||
|
||||
getNodes(): { [s: string]: NodeModel } {
|
||||
return this.nodes;
|
||||
return this.nodesLayer.getEntities();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
import { BaseModel } from "./BaseModel";
|
||||
import { LinkModel } from "./LinkModel";
|
||||
import * as _ from "lodash";
|
||||
import { DiagramEngine } from "../DiagramEngine";
|
||||
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(ob, engine: DiagramEngine) {
|
||||
super.deSerialize(ob, engine);
|
||||
this.offsetX = ob.offsetX;
|
||||
this.offsetY = ob.offsetY;
|
||||
deSerialize(event: DeserializeEvent) {
|
||||
super.deSerialize(event);
|
||||
this.offsetX = event.data.offsetX;
|
||||
this.offsetY = event.data.offsetY;
|
||||
}
|
||||
|
||||
serialize() {
|
||||
|
||||
@@ -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,11 +109,11 @@ 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) {
|
||||
@@ -156,7 +124,7 @@ export class LinkModel<T extends LinkModelListener = LinkModelListener> extends
|
||||
this.sourcePort.removeLink(this);
|
||||
}
|
||||
this.sourcePort = port;
|
||||
this.iterateListeners((listener: LinkModelListener, event) => {
|
||||
this.iterateListeners("source port changed", (listener: T, event) => {
|
||||
if (listener.sourcePortChanged) {
|
||||
listener.sourcePortChanged({ ...event, port: port });
|
||||
}
|
||||
@@ -179,7 +147,7 @@ export class LinkModel<T extends LinkModelListener = LinkModelListener> extends
|
||||
this.targetPort.removeLink(this);
|
||||
}
|
||||
this.targetPort = port;
|
||||
this.iterateListeners((listener: LinkModelListener, event) => {
|
||||
this.iterateListeners("target port chnaged", (listener: T, event) => {
|
||||
if (listener.targetPortChanged) {
|
||||
listener.targetPortChanged({ ...event, port: port });
|
||||
}
|
||||
@@ -191,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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
this.points.removeEntity(pointModel);
|
||||
}
|
||||
|
||||
addPoint<P extends PointModel>(pointModel: P, index = 1): P {
|
||||
pointModel.setParent(this);
|
||||
this.points.splice(index, 0, pointModel);
|
||||
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,118 +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;
|
||||
_.forEach(this.ports, port => {
|
||||
_.forEach(port.getLinks(), link => {
|
||||
let point = link.getPointForPort(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()) {
|
||||
_.forEach(this.ports, port => {
|
||||
entities = entities.concat(
|
||||
_.map(port.getLinks(), link => {
|
||||
return link.getPointForPort(port);
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
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 = {};
|
||||
_.forEach(this.ports, port => {
|
||||
clone.addPort(port.clone(lookupTable));
|
||||
});
|
||||
}
|
||||
|
||||
remove() {
|
||||
super.remove();
|
||||
_.forEach(this.ports, port => {
|
||||
_.forEach(port.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);
|
||||
@@ -120,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,8 +0,0 @@
|
||||
import { BaseModel, BaseModelListener } from "./BaseModel";
|
||||
import { BaseEntity } from "../BaseEntity";
|
||||
|
||||
export interface SelectionModel {
|
||||
model: BaseModel<BaseEntity, BaseModelListener>;
|
||||
initialX: number;
|
||||
initialY: number;
|
||||
}
|
||||
@@ -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,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,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,554 +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 } from "../actions/BaseAction";
|
||||
import { MoveCanvasAction } from "../actions/MoveCanvasAction";
|
||||
import { MoveItemsAction } from "../actions/MoveItemsAction";
|
||||
import { SelectingAction } from "../actions/SelectingAction";
|
||||
import { NodeModel } from "../models/NodeModel";
|
||||
import { PointModel } from "../models/PointModel";
|
||||
import { PortModel } from "../models/PortModel";
|
||||
import { LinkModel } from "../models/LinkModel";
|
||||
import { SelectionModel } from "../models/SelectionModel";
|
||||
import { BaseModel, BaseModelListener } from "../models/BaseModel";
|
||||
import { BaseEntity } from "../BaseEntity";
|
||||
import { BaseWidget, BaseWidgetProps } from "./BaseWidget";
|
||||
|
||||
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]
|
||||
};
|
||||
|
||||
onKeyUpPointer: (this: Window, ev: KeyboardEvent) => void = null;
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
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 necessarily 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);
|
||||
_.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)) {
|
||||
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()];
|
||||
}
|
||||
});
|
||||
|
||||
//check for / remove any loose links in any models which have been moved
|
||||
if (!this.props.allowLooseLinks && this.state.wasMoved) {
|
||||
_.forEach(this.state.action.selectionModels, model => {
|
||||
//only care about points connecting to things
|
||||
if (!(model.model instanceof PointModel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let selectedPoint: PointModel = model.model;
|
||||
let link: LinkModel = selectedPoint.getLink();
|
||||
if (link.getSourcePort() === null || link.getTargetPort() === null) {
|
||||
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;
|
||||
}
|
||||
|
||||
let link: LinkModel = model.model.getLink();
|
||||
let sourcePort: PortModel = link.getSourcePort();
|
||||
let 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,92 +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 (ignore) {
|
||||
/*noop*/
|
||||
}
|
||||
}
|
||||
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 (ignore) {
|
||||
/*noop*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//generate links
|
||||
var generatedLink = this.props.diagramEngine.generateWidgetForLink(link);
|
||||
if (!generatedLink) {
|
||||
throw new Error(`no link generated for type: ${link.getType()}`);
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user