mirror of
https://github.com/projectstorm/react-diagrams.git
synced 2025-08-26 16:01:30 +08:00
clean refactor / consistent TSLint / Prettier
This commit is contained in:
@ -50,7 +50,7 @@ export class CodePreview extends React.Component {
|
||||
<SyntaxHighlighter
|
||||
customStyle={{width: '100%', overflowX:'hidden', tabSize: 4}}
|
||||
showLineNumbers={true}
|
||||
language='typescript'
|
||||
language='language-tsx'
|
||||
style={github}
|
||||
>
|
||||
{this.state.code}
|
||||
|
@ -15,7 +15,7 @@ import { DemoWorkspaceWidget } from "../.helpers/DemoWorkspaceWidget";
|
||||
* Tests cloning
|
||||
*/
|
||||
class CloneSelected extends React.Component<any, any> {
|
||||
constructor(props) {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
this.cloneSelected = this.cloneSelected.bind(this);
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ import {
|
||||
} from "../../src/main";
|
||||
import { action } from "@storybook/addon-actions";
|
||||
import * as React from "react";
|
||||
import { LinkFactory } from "../../src/AbstractFactory";
|
||||
import { DefaultLinkModel } from "../../src/defaults/models/DefaultLinkModel";
|
||||
import { DefaultLinkFactory } from "../../src/defaults/factories/DefaultLinkFactory";
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { DiamonNodeWidget } from "./DiamondNodeWidget";
|
||||
import { DiamondNodeModel } from "./DiamondNodeModel";
|
||||
import * as React from "react";
|
||||
|
||||
export class DiamondNodeFactory extends SRD.NodeFactory {
|
||||
export class DiamondNodeFactory extends SRD.AbstractNodeFactory {
|
||||
constructor() {
|
||||
super("diamond");
|
||||
}
|
||||
|
@ -1,8 +1,5 @@
|
||||
import * as _ from "lodash";
|
||||
import { LinkModel } from "../../src/models/LinkModel";
|
||||
import { DiagramEngine } from "../../src/DiagramEngine";
|
||||
import { PortModel } from "../../src/models/PortModel";
|
||||
import { DefaultLinkModel } from "../../src/defaults/models/DefaultLinkModel";
|
||||
import { LinkModel, DiagramEngine, PortModel, DefaultLinkModel } from "../../src/main";
|
||||
|
||||
export class DiamondPortModel extends PortModel {
|
||||
position: string | "top" | "bottom" | "left" | "right";
|
||||
|
14
demos/demo-custom-node1/SimplePortFactory.ts
Normal file
14
demos/demo-custom-node1/SimplePortFactory.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { PortModel, AbstractPortFactory } from "../../src/main";
|
||||
|
||||
export class SimplePortFactory extends AbstractPortFactory {
|
||||
cb: (initialConfig?: any) => PortModel;
|
||||
|
||||
constructor(type: string, cb: (initialConfig?: any) => PortModel) {
|
||||
super(type);
|
||||
this.cb = cb;
|
||||
}
|
||||
|
||||
getNewInstance(initialConfig?: any): PortModel {
|
||||
return this.cb(initialConfig);
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ import * as React from "react";
|
||||
// import the custom models
|
||||
import { DiamondNodeModel } from "./DiamondNodeModel";
|
||||
import { DiamondNodeFactory } from "./DiamondNodeFactory";
|
||||
import { SimplePortFactory } from "../../src/AbstractFactory";
|
||||
import { SimplePortFactory } from "./SimplePortFactory";
|
||||
import { DiamondPortModel } from "./DiamondPortModel";
|
||||
|
||||
/**
|
||||
|
@ -22,9 +22,7 @@ function distributeGraph(model) {
|
||||
let edges = mapEdges(model);
|
||||
let graph = new dagre.graphlib.Graph();
|
||||
graph.setGraph({});
|
||||
graph.setDefaultEdgeLabel(function() {
|
||||
return {};
|
||||
});
|
||||
graph.setDefaultEdgeLabel(() => ({}));
|
||||
//add elements to dagre graph
|
||||
nodes.forEach(node => {
|
||||
graph.setNode(node.id, node.metadata);
|
||||
|
@ -3,7 +3,7 @@ import * as React from "react";
|
||||
import { BodyWidget } from "./components/BodyWidget";
|
||||
import { Application } from "./Application";
|
||||
|
||||
require("./sass/main.scss");
|
||||
import "./sass/main.scss";
|
||||
|
||||
export default () => {
|
||||
var app = new Application();
|
||||
|
@ -13,6 +13,22 @@ export default () => {
|
||||
var engine = new DiagramEngine();
|
||||
engine.installDefaultFactories();
|
||||
|
||||
//2) setup the diagram model
|
||||
var model = new DiagramModel();
|
||||
|
||||
for (var i = 0; i < 8; i++) {
|
||||
for (var j = 0; j < 8; j++) {
|
||||
generateNodes(model, i * 200, j * 100);
|
||||
}
|
||||
}
|
||||
|
||||
//5) load model into engine
|
||||
engine.setDiagramModel(model);
|
||||
|
||||
//6) render the diagram!
|
||||
return <DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />;
|
||||
};
|
||||
|
||||
function generateNodes(model: DiagramModel, offsetX: number, offsetY: number) {
|
||||
//3-A) create a default node
|
||||
var node1 = new DefaultNodeModel("Node 1", "rgb(0,192,255)");
|
||||
@ -30,19 +46,3 @@ export default () => {
|
||||
//4) add the models to the root graph
|
||||
model.addAll(node1, node2, link1);
|
||||
}
|
||||
|
||||
//2) setup the diagram model
|
||||
var model = new DiagramModel();
|
||||
|
||||
for (var i = 0; i < 8; i++) {
|
||||
for (var j = 0; j < 8; j++) {
|
||||
generateNodes(model, i * 200, j * 100);
|
||||
}
|
||||
}
|
||||
|
||||
//5) load model into engine
|
||||
engine.setDiagramModel(model);
|
||||
|
||||
//6) render the diagram!
|
||||
return <DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />;
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ import { DiagramEngine, DiagramModel, DefaultNodeModel, LinkModel, DiagramWidget
|
||||
import * as React from "react";
|
||||
import { DemoWorkspaceWidget } from "../.helpers/DemoWorkspaceWidget";
|
||||
import { action } from "@storybook/addon-actions";
|
||||
var beautify = require("json-beautify");
|
||||
import * as beautify from "json-beautify";
|
||||
|
||||
export default () => {
|
||||
//1) setup the diagram engine
|
||||
|
@ -20,24 +20,6 @@ export default () => {
|
||||
var engine = new DiagramEngine();
|
||||
engine.installDefaultFactories();
|
||||
|
||||
function generateNodes(model: DiagramModel, offsetX: number, offsetY: number) {
|
||||
//3-A) create a default node
|
||||
var node1 = new DefaultNodeModel("Node 1", "rgb(0,192,255)");
|
||||
var port1 = node1.addOutPort("Out");
|
||||
node1.setPosition(100 + offsetX, 100 + offsetY);
|
||||
|
||||
//3-B) create another default node
|
||||
var node2 = new DefaultNodeModel("Node 2", "rgb(192,255,0)");
|
||||
var port2 = node2.addInPort("In");
|
||||
node2.setPosition(200 + offsetX, 100 + offsetY);
|
||||
|
||||
//3-C) link the 2 nodes together
|
||||
var link1 = port1.link(port2);
|
||||
|
||||
//4) add the models to the root graph
|
||||
model.addAll(node1, node2, link1);
|
||||
}
|
||||
|
||||
//2) setup the diagram model
|
||||
var model = new DiagramModel();
|
||||
|
||||
@ -57,3 +39,21 @@ export default () => {
|
||||
</DemoWorkspaceWidget>
|
||||
);
|
||||
};
|
||||
|
||||
function generateNodes(model: DiagramModel, offsetX: number, offsetY: number) {
|
||||
//3-A) create a default node
|
||||
var node1 = new DefaultNodeModel("Node 1", "rgb(0,192,255)");
|
||||
var port1 = node1.addOutPort("Out");
|
||||
node1.setPosition(100 + offsetX, 100 + offsetY);
|
||||
|
||||
//3-B) create another default node
|
||||
var node2 = new DefaultNodeModel("Node 2", "rgb(192,255,0)");
|
||||
var port2 = node2.addInPort("In");
|
||||
node2.setPosition(200 + offsetX, 100 + offsetY);
|
||||
|
||||
//3-C) link the 2 nodes together
|
||||
var link1 = port1.link(port2);
|
||||
|
||||
//4) add the models to the root graph
|
||||
model.addAll(node1, node2, link1);
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import { Helper } from "./.helpers/Helper";
|
||||
import { Toolkit } from "../src/Toolkit";
|
||||
|
||||
//include the SCSS for the demo
|
||||
require("./.helpers/demo.scss");
|
||||
import "./.helpers/demo.scss";
|
||||
|
||||
Toolkit.TESTING = true;
|
||||
|
||||
|
10
demos/tslint.json
Normal file
10
demos/tslint.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": [
|
||||
"../tslint.json"
|
||||
],
|
||||
"rules": {
|
||||
"no-console": false,
|
||||
"max-classes-per-file": false,
|
||||
"no-var-requires": false
|
||||
}
|
||||
}
|
16
package.json
16
package.json
@ -20,13 +20,21 @@
|
||||
"typings": "./dist/@types/src/main",
|
||||
"author": "dylanvorster",
|
||||
"scripts": {
|
||||
"test:ci": "rm -rf ./dist && node ./tests/e2e/generate-e2e.js && jest --no-cache",
|
||||
"test": "jest --no-cache",
|
||||
"build:ts": "webpack",
|
||||
"build:ts:prod": "cross-env NODE_ENV=production webpack",
|
||||
"build:sass:prod": "node-sass --output-style compressed ./src/sass/main.scss > ./dist/style.min.css",
|
||||
|
||||
"storybook": "start-storybook -p 9001 -c .storybook",
|
||||
"storybook:build": "build-storybook -c .storybook -o .out",
|
||||
"storybook:github": "storybook-to-ghpages",
|
||||
"prepublishOnly": "rm -rf ./dist && cross-env NODE_ENV=production webpack && ./node_modules/node-sass/bin/node-sass --output-style compressed ./src/sass/main.scss > ./dist/style.min.css",
|
||||
"pretty": "prettier --use-tabs --write \"{src,demos}/**/*.{ts,tsx}\" --print-width 120"
|
||||
|
||||
"pretty": "prettier --use-tabs --write \"{src,demos,tests}/**/*.{ts,tsx}\" --print-width 120",
|
||||
"lint": "tslint -p .",
|
||||
|
||||
"test:ci": "rm -rf ./dist && node ./tests/e2e/generate-e2e.js && jest --no-cache",
|
||||
"test": "jest --no-cache",
|
||||
|
||||
"prepublishOnly": "yarn run build:ts:prod && yarn run build:sass:prod"
|
||||
},
|
||||
"dependencies": {
|
||||
"closest": "^0.0.1",
|
||||
|
@ -1,48 +0,0 @@
|
||||
import { BaseModel } from "./models/BaseModel";
|
||||
import { NodeModel } from "./models/NodeModel";
|
||||
import { LinkModel } from "./models/LinkModel";
|
||||
import { DiagramEngine } from "./DiagramEngine";
|
||||
import { PortModel } from "./models/PortModel";
|
||||
import { PointModel } from "./models/PointModel";
|
||||
import { LabelModel } from "./models/LabelModel";
|
||||
|
||||
export abstract class AbstractFactory<T extends BaseModel> {
|
||||
type: string;
|
||||
|
||||
constructor(name: string) {
|
||||
this.type = name;
|
||||
}
|
||||
|
||||
getType(): string {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
abstract getNewInstance(initialConfig?: any): T;
|
||||
}
|
||||
|
||||
export abstract class NodeFactory<T extends NodeModel = NodeModel> extends AbstractFactory<T> {
|
||||
abstract generateReactWidget(diagramEngine: DiagramEngine, node: T): JSX.Element;
|
||||
}
|
||||
|
||||
export abstract class LinkFactory<T extends LinkModel = LinkModel> extends AbstractFactory<T> {
|
||||
abstract generateReactWidget(diagramEngine: DiagramEngine, link: T): JSX.Element;
|
||||
}
|
||||
|
||||
export abstract class LabelFactory<T extends LabelModel = LabelModel> extends AbstractFactory<T> {
|
||||
abstract generateReactWidget(diagramEngine: DiagramEngine, label: T): JSX.Element;
|
||||
}
|
||||
|
||||
export abstract class PortFactory<T extends PortModel = PortModel> extends AbstractFactory<T> {}
|
||||
|
||||
export class SimplePortFactory extends PortFactory {
|
||||
cb: (initialConfig?: any) => PortModel;
|
||||
|
||||
constructor(type: string, cb: (initialConfig?: any) => PortModel) {
|
||||
super(type);
|
||||
this.cb = cb;
|
||||
}
|
||||
|
||||
getNewInstance(initialConfig?: any): PortModel {
|
||||
return this.cb(initialConfig);
|
||||
}
|
||||
}
|
@ -33,9 +33,11 @@ export class BaseEntity<T extends BaseListener = BaseListener> {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
doClone(lookupTable = {}, clone) {}
|
||||
doClone(lookupTable: { [s: string]: any } = {}, clone: any) {
|
||||
/*noop*/
|
||||
}
|
||||
|
||||
clone(lookupTable = {}) {
|
||||
clone(lookupTable: { [s: string]: any } = {}) {
|
||||
// try and use an existing clone first
|
||||
if (lookupTable[this.id]) {
|
||||
return lookupTable[this.id];
|
||||
@ -53,7 +55,7 @@ export class BaseEntity<T extends BaseListener = BaseListener> {
|
||||
this.listeners = {};
|
||||
}
|
||||
|
||||
public deSerialize(data, engine: DiagramEngine) {
|
||||
public deSerialize(data: { [s: string]: any }, engine: DiagramEngine) {
|
||||
this.id = data.id;
|
||||
}
|
||||
|
||||
@ -72,7 +74,9 @@ export class BaseEntity<T extends BaseListener = BaseListener> {
|
||||
event.firing = false;
|
||||
}
|
||||
};
|
||||
|
||||
for (var i in this.listeners) {
|
||||
if (this.listeners.hasOwnProperty(i)) {
|
||||
// propagation stopped
|
||||
if (!event.firing) {
|
||||
return;
|
||||
@ -80,6 +84,7 @@ export class BaseEntity<T extends BaseListener = BaseListener> {
|
||||
cb(this.listeners[i], event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public removeListener(listener: string) {
|
||||
if (this.listeners[listener]) {
|
||||
|
@ -1,87 +0,0 @@
|
||||
import { DiagramModel } from "./models/DiagramModel";
|
||||
import { DiagramEngine } from "./DiagramEngine";
|
||||
import { SelectionModel } from "./widgets/DiagramWidget";
|
||||
import { PointModel } from "./models/PointModel";
|
||||
import { NodeModel } from "./models/NodeModel";
|
||||
|
||||
export class BaseAction {
|
||||
mouseX: number;
|
||||
mouseY: number;
|
||||
ms: number;
|
||||
|
||||
constructor(mouseX: number, mouseY: number) {
|
||||
this.mouseX = mouseX;
|
||||
this.mouseY = mouseY;
|
||||
this.ms = new Date().getTime();
|
||||
}
|
||||
}
|
||||
|
||||
export class SelectingAction extends BaseAction {
|
||||
mouseX2: number;
|
||||
mouseY2: number;
|
||||
|
||||
constructor(mouseX: number, mouseY: number) {
|
||||
super(mouseX, mouseY);
|
||||
this.mouseX2 = mouseX;
|
||||
this.mouseY2 = mouseY;
|
||||
}
|
||||
|
||||
getBoxDimensions() {
|
||||
return {
|
||||
left: this.mouseX2 > this.mouseX ? this.mouseX : this.mouseX2,
|
||||
top: this.mouseY2 > this.mouseY ? this.mouseY : this.mouseY2,
|
||||
width: Math.abs(this.mouseX2 - this.mouseX),
|
||||
height: Math.abs(this.mouseY2 - this.mouseY),
|
||||
right: this.mouseX2 < this.mouseX ? this.mouseX : this.mouseX2,
|
||||
bottom: this.mouseY2 < this.mouseY ? this.mouseY : this.mouseY2
|
||||
};
|
||||
}
|
||||
|
||||
containsElement(x: number, y: number, diagramModel: DiagramModel): boolean {
|
||||
var z = diagramModel.getZoomLevel() / 100.0;
|
||||
let dimensions = this.getBoxDimensions();
|
||||
|
||||
return (
|
||||
x * z + diagramModel.getOffsetX() > dimensions.left &&
|
||||
x * z + diagramModel.getOffsetX() < dimensions.right &&
|
||||
y * z + diagramModel.getOffsetY() > dimensions.top &&
|
||||
y * z + diagramModel.getOffsetY() < dimensions.bottom
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class MoveCanvasAction extends BaseAction {
|
||||
initialOffsetX: number;
|
||||
initialOffsetY: number;
|
||||
|
||||
constructor(mouseX: number, mouseY: number, diagramModel: DiagramModel) {
|
||||
super(mouseX, mouseY);
|
||||
this.initialOffsetX = diagramModel.getOffsetX();
|
||||
this.initialOffsetY = diagramModel.getOffsetY();
|
||||
}
|
||||
}
|
||||
|
||||
export class MoveItemsAction extends BaseAction {
|
||||
selectionModels: SelectionModel[];
|
||||
moved: boolean;
|
||||
|
||||
constructor(mouseX: number, mouseY: number, diagramEngine: DiagramEngine) {
|
||||
super(mouseX, mouseY);
|
||||
this.moved = false;
|
||||
diagramEngine.enableRepaintEntities(diagramEngine.getDiagramModel().getSelectedItems());
|
||||
var selectedItems = diagramEngine.getDiagramModel().getSelectedItems();
|
||||
|
||||
//dont allow items which are locked to move
|
||||
selectedItems = selectedItems.filter(item => {
|
||||
return !diagramEngine.isModelLocked(item);
|
||||
});
|
||||
|
||||
this.selectionModels = selectedItems.map((item: PointModel | NodeModel) => {
|
||||
return {
|
||||
model: item,
|
||||
initialX: item.x,
|
||||
initialY: item.y
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
@ -6,7 +6,10 @@ import { NodeModel } from "./models/NodeModel";
|
||||
import { PointModel } from "./models/PointModel";
|
||||
import { PortModel } from "./models/PortModel";
|
||||
import { LinkModel } from "./models/LinkModel";
|
||||
import { LabelFactory, LinkFactory, NodeFactory, PortFactory } from "./AbstractFactory";
|
||||
import { 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";
|
||||
@ -32,10 +35,10 @@ export interface DiagramEngineListener extends BaseListener {
|
||||
* Passed as a parameter to the DiagramWidget
|
||||
*/
|
||||
export class DiagramEngine extends BaseEntity<DiagramEngineListener> {
|
||||
nodeFactories: { [s: string]: NodeFactory };
|
||||
linkFactories: { [s: string]: LinkFactory };
|
||||
portFactories: { [s: string]: PortFactory };
|
||||
labelFactories: { [s: string]: LabelFactory };
|
||||
nodeFactories: { [s: string]: AbstractNodeFactory };
|
||||
linkFactories: { [s: string]: AbstractLinkFactory };
|
||||
portFactories: { [s: string]: AbstractPortFactory };
|
||||
labelFactories: { [s: string]: AbstractLabelFactory };
|
||||
|
||||
diagramModel: DiagramModel;
|
||||
canvas: Element;
|
||||
@ -82,7 +85,9 @@ export class DiagramEngine extends BaseEntity<DiagramEngineListener> {
|
||||
|
||||
repaintCanvas() {
|
||||
this.iterateListeners(listener => {
|
||||
listener.repaintCanvas && listener.repaintCanvas();
|
||||
if (listener.repaintCanvas) {
|
||||
listener.repaintCanvas();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -152,19 +157,19 @@ export class DiagramEngine extends BaseEntity<DiagramEngineListener> {
|
||||
|
||||
//!-------------- FACTORIES ------------
|
||||
|
||||
getNodeFactories(): { [s: string]: NodeFactory } {
|
||||
getNodeFactories(): { [s: string]: AbstractNodeFactory } {
|
||||
return this.nodeFactories;
|
||||
}
|
||||
|
||||
getLinkFactories(): { [s: string]: LinkFactory } {
|
||||
getLinkFactories(): { [s: string]: AbstractLinkFactory } {
|
||||
return this.linkFactories;
|
||||
}
|
||||
|
||||
getLabelFactories(): { [s: string]: LabelFactory } {
|
||||
getLabelFactories(): { [s: string]: AbstractLabelFactory } {
|
||||
return this.labelFactories;
|
||||
}
|
||||
|
||||
registerLabelFactory(factory: LabelFactory) {
|
||||
registerLabelFactory(factory: AbstractLabelFactory) {
|
||||
this.labelFactories[factory.getType()] = factory;
|
||||
this.iterateListeners(listener => {
|
||||
if (listener.labelFactoriesUpdated) {
|
||||
@ -173,7 +178,7 @@ export class DiagramEngine extends BaseEntity<DiagramEngineListener> {
|
||||
});
|
||||
}
|
||||
|
||||
registerPortFactory(factory: PortFactory) {
|
||||
registerPortFactory(factory: AbstractPortFactory) {
|
||||
this.portFactories[factory.getType()] = factory;
|
||||
this.iterateListeners(listener => {
|
||||
if (listener.portFactoriesUpdated) {
|
||||
@ -182,7 +187,7 @@ export class DiagramEngine extends BaseEntity<DiagramEngineListener> {
|
||||
});
|
||||
}
|
||||
|
||||
registerNodeFactory(factory: NodeFactory) {
|
||||
registerNodeFactory(factory: AbstractNodeFactory) {
|
||||
this.nodeFactories[factory.getType()] = factory;
|
||||
this.iterateListeners(listener => {
|
||||
if (listener.nodeFactoriesUpdated) {
|
||||
@ -191,7 +196,7 @@ export class DiagramEngine extends BaseEntity<DiagramEngineListener> {
|
||||
});
|
||||
}
|
||||
|
||||
registerLinkFactory(factory: LinkFactory) {
|
||||
registerLinkFactory(factory: AbstractLinkFactory) {
|
||||
this.linkFactories[factory.getType()] = factory;
|
||||
this.iterateListeners(listener => {
|
||||
if (listener.linkFactoriesUpdated) {
|
||||
@ -200,47 +205,43 @@ export class DiagramEngine extends BaseEntity<DiagramEngineListener> {
|
||||
});
|
||||
}
|
||||
|
||||
getPortFactory(type: string): PortFactory {
|
||||
getPortFactory(type: string): AbstractPortFactory {
|
||||
if (this.portFactories[type]) {
|
||||
return this.portFactories[type];
|
||||
}
|
||||
console.log("cannot find factory for port of type: [" + type + "]");
|
||||
return null;
|
||||
throw new Error(`cannot find factory for port of type: [${type}]`);
|
||||
}
|
||||
|
||||
getNodeFactory(type: string): NodeFactory {
|
||||
getNodeFactory(type: string): AbstractNodeFactory {
|
||||
if (this.nodeFactories[type]) {
|
||||
return this.nodeFactories[type];
|
||||
}
|
||||
console.log("cannot find factory for node of type: [" + type + "]");
|
||||
return null;
|
||||
throw new Error(`cannot find factory for node of type: [${type}]`);
|
||||
}
|
||||
|
||||
getLinkFactory(type: string): LinkFactory {
|
||||
getLinkFactory(type: string): AbstractLinkFactory {
|
||||
if (this.linkFactories[type]) {
|
||||
return this.linkFactories[type];
|
||||
}
|
||||
console.log("cannot find factory for link of type: [" + type + "]");
|
||||
return null;
|
||||
throw new Error(`cannot find factory for link of type: [${type}]`);
|
||||
}
|
||||
|
||||
getLabelFactory(type: string): LabelFactory {
|
||||
getLabelFactory(type: string): AbstractLabelFactory {
|
||||
if (this.labelFactories[type]) {
|
||||
return this.labelFactories[type];
|
||||
}
|
||||
console.log("cannot find factory for label of type: [" + type + "]");
|
||||
return null;
|
||||
throw new Error(`cannot find factory for label of type: [${type}]`);
|
||||
}
|
||||
|
||||
getFactoryForNode(node: NodeModel): NodeFactory | null {
|
||||
getFactoryForNode(node: NodeModel): AbstractNodeFactory | null {
|
||||
return this.getNodeFactory(node.getType());
|
||||
}
|
||||
|
||||
getFactoryForLink(link: LinkModel): LinkFactory | null {
|
||||
getFactoryForLink(link: LinkModel): AbstractLinkFactory | null {
|
||||
return this.getLinkFactory(link.getType());
|
||||
}
|
||||
|
||||
getFactoryForLabel(label: LabelModel): LabelFactory | null {
|
||||
getFactoryForLabel(label: LabelModel): AbstractLabelFactory | null {
|
||||
return this.getLabelFactory(label.getType());
|
||||
}
|
||||
|
||||
@ -274,7 +275,7 @@ export class DiagramEngine extends BaseEntity<DiagramEngineListener> {
|
||||
}
|
||||
|
||||
getNodeElement(node: NodeModel): Element {
|
||||
const selector = this.canvas.querySelector('.node[data-nodeid="' + node.getID() + '"]');
|
||||
const selector = this.canvas.querySelector(`.node[data-nodeid="${node.getID()}"]`);
|
||||
if (selector === null) {
|
||||
throw new Error("Cannot find Node element with nodeID: [" + node.getID() + "]");
|
||||
}
|
||||
@ -283,7 +284,7 @@ export class DiagramEngine extends BaseEntity<DiagramEngineListener> {
|
||||
|
||||
getNodePortElement(port: PortModel): any {
|
||||
var selector = this.canvas.querySelector(
|
||||
'.port[data-name="' + port.getName() + '"][data-nodeid="' + port.getParent().getID() + '"]'
|
||||
`.port[data-name="${port.getName()}"][data-nodeid="${port.getParent().getID()}"]`
|
||||
);
|
||||
if (selector === null) {
|
||||
throw new Error(
|
||||
|
@ -1,3 +1,4 @@
|
||||
// tslint:disable no-bitwise
|
||||
import closest = require("closest");
|
||||
import { PointModel } from "./models/PointModel";
|
||||
import { ROUTING_SCALING_FACTOR } from "./routing/PathFinding";
|
||||
@ -18,9 +19,9 @@ export class Toolkit {
|
||||
Toolkit.TESTING_UID++;
|
||||
return "" + Toolkit.TESTING_UID;
|
||||
}
|
||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
|
||||
var r = (Math.random() * 16) | 0,
|
||||
v = c == "x" ? r : (r & 0x3) | 0x8;
|
||||
return "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);
|
||||
});
|
||||
}
|
||||
|
11
src/actions/BaseAction.ts
Normal file
11
src/actions/BaseAction.ts
Normal file
@ -0,0 +1,11 @@
|
||||
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();
|
||||
}
|
||||
}
|
13
src/actions/MoveCanvasAction.ts
Normal file
13
src/actions/MoveCanvasAction.ts
Normal file
@ -0,0 +1,13 @@
|
||||
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();
|
||||
}
|
||||
}
|
30
src/actions/MoveItemsAction.ts
Normal file
30
src/actions/MoveItemsAction.ts
Normal file
@ -0,0 +1,30 @@
|
||||
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
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
36
src/actions/SelectingAction.ts
Normal file
36
src/actions/SelectingAction.ts
Normal file
@ -0,0 +1,36 @@
|
||||
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,13 +1,13 @@
|
||||
import * as React from "react";
|
||||
import { DiagramEngine } from "../../DiagramEngine";
|
||||
import { LabelFactory } from "../../AbstractFactory";
|
||||
import { AbstractLabelFactory } from "../../factories/AbstractLabelFactory";
|
||||
import { DefaultLabelModel } from "../models/DefaultLabelModel";
|
||||
import { DefaultLabelWidget } from "../widgets/DefaultLabelWidget";
|
||||
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export class DefaultLabelFactory extends LabelFactory<DefaultLabelModel> {
|
||||
export class DefaultLabelFactory extends AbstractLabelFactory<DefaultLabelModel> {
|
||||
constructor() {
|
||||
super("default");
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import * as React from "react";
|
||||
import { DefaultLinkWidget } from "../widgets/DefaultLinkWidget";
|
||||
import { DiagramEngine } from "../../DiagramEngine";
|
||||
import { LinkFactory } from "../../AbstractFactory";
|
||||
import { AbstractLinkFactory } from "../../factories/AbstractLinkFactory";
|
||||
import { DefaultLinkModel } from "../models/DefaultLinkModel";
|
||||
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export class DefaultLinkFactory extends LinkFactory<DefaultLinkModel> {
|
||||
export class DefaultLinkFactory extends AbstractLinkFactory<DefaultLinkModel> {
|
||||
constructor() {
|
||||
super("default");
|
||||
}
|
||||
|
@ -2,11 +2,11 @@ import { DefaultNodeModel } from "../models/DefaultNodeModel";
|
||||
import * as React from "react";
|
||||
import { DefaultNodeWidget } from "../widgets/DefaultNodeWidget";
|
||||
import { DiagramEngine } from "../../DiagramEngine";
|
||||
import { NodeFactory } from "../../AbstractFactory";
|
||||
import { AbstractNodeFactory } from "../../factories/AbstractNodeFactory";
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export class DefaultNodeFactory extends NodeFactory<DefaultNodeModel> {
|
||||
export class DefaultNodeFactory extends AbstractNodeFactory<DefaultNodeModel> {
|
||||
constructor() {
|
||||
super("default");
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { DefaultPortModel } from "../models/DefaultPortModel";
|
||||
import { PortFactory } from "../../AbstractFactory";
|
||||
import { AbstractPortFactory } from "../../factories/AbstractPortFactory";
|
||||
|
||||
export class DefaultPortFactory extends PortFactory<DefaultPortModel> {
|
||||
export class DefaultPortFactory extends AbstractPortFactory<DefaultPortModel> {
|
||||
constructor() {
|
||||
super("default");
|
||||
}
|
||||
|
@ -10,9 +10,9 @@ import { DefaultLabelModel } from "./DefaultLabelModel";
|
||||
import { LabelModel } from "../../models/LabelModel";
|
||||
|
||||
export interface DefaultLinkModelListener extends LinkModelListener {
|
||||
colorChanged?(event: BaseEvent<DefaultLinkModel> & { color: null | string });
|
||||
colorChanged?(event: BaseEvent<DefaultLinkModel> & { color: null | string }): void;
|
||||
|
||||
widthChanged?(event: BaseEvent<DefaultLinkModel> & { width: 0 | number });
|
||||
widthChanged?(event: BaseEvent<DefaultLinkModel> & { width: 0 | number }): void;
|
||||
}
|
||||
|
||||
export class DefaultLinkModel extends LinkModel<DefaultLinkModelListener> {
|
||||
@ -54,14 +54,18 @@ export class DefaultLinkModel extends LinkModel<DefaultLinkModelListener> {
|
||||
setWidth(width: number) {
|
||||
this.width = width;
|
||||
this.iterateListeners((listener: DefaultLinkModelListener, event: BaseEvent) => {
|
||||
listener.widthChanged && listener.widthChanged({ ...event, width: width });
|
||||
if (listener.widthChanged) {
|
||||
listener.widthChanged({ ...event, width: width });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setColor(color: string) {
|
||||
this.color = color;
|
||||
this.iterateListeners((listener: DefaultLinkModelListener, event: BaseEvent) => {
|
||||
listener.colorChanged && listener.colorChanged({ ...event, color: color });
|
||||
if (listener.colorChanged) {
|
||||
listener.colorChanged({ ...event, color: color });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -343,18 +343,18 @@ export class DefaultLinkWidget extends BaseWidget<DefaultLinkProps, DefaultLinkS
|
||||
}
|
||||
} else {
|
||||
//draw the multiple anchors and complex line instead
|
||||
for (let i = 0; i < points.length - 1; i++) {
|
||||
for (let j = 0; j < points.length - 1; j++) {
|
||||
paths.push(
|
||||
this.generateLink(
|
||||
Toolkit.generateLinePath(points[i], points[i + 1]),
|
||||
Toolkit.generateLinePath(points[j], points[j + 1]),
|
||||
{
|
||||
"data-linkid": this.props.link.id,
|
||||
"data-point": i,
|
||||
"data-point": j,
|
||||
onMouseDown: (event: MouseEvent) => {
|
||||
this.addPointToLink(event, i + 1);
|
||||
this.addPointToLink(event, j + 1);
|
||||
}
|
||||
},
|
||||
i
|
||||
j
|
||||
)
|
||||
);
|
||||
}
|
||||
|
15
src/factories/AbstractFactory.ts
Normal file
15
src/factories/AbstractFactory.ts
Normal file
@ -0,0 +1,15 @@
|
||||
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;
|
||||
}
|
7
src/factories/AbstractLabelFactory.ts
Normal file
7
src/factories/AbstractLabelFactory.ts
Normal file
@ -0,0 +1,7 @@
|
||||
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;
|
||||
}
|
7
src/factories/AbstractLinkFactory.ts
Normal file
7
src/factories/AbstractLinkFactory.ts
Normal file
@ -0,0 +1,7 @@
|
||||
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;
|
||||
}
|
7
src/factories/AbstractNodeFactory.ts
Normal file
7
src/factories/AbstractNodeFactory.ts
Normal file
@ -0,0 +1,7 @@
|
||||
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;
|
||||
}
|
5
src/factories/AbstractPortFactory.ts
Normal file
5
src/factories/AbstractPortFactory.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { PortModel } from "../models/PortModel";
|
||||
import { DiagramEngine } from "../DiagramEngine";
|
||||
import { AbstractFactory } from "./AbstractFactory";
|
||||
|
||||
export abstract class AbstractPortFactory<T extends PortModel = PortModel> extends AbstractFactory<T> {}
|
16
src/main.ts
16
src/main.ts
@ -15,14 +15,24 @@ export * from "./defaults/widgets/DefaultLinkWidget";
|
||||
export * from "./defaults/widgets/DefaultNodeWidget";
|
||||
export * from "./defaults/widgets/DefaultPortLabelWidget";
|
||||
|
||||
export * from "./AbstractFactory";
|
||||
export * from "./factories/AbstractFactory";
|
||||
export * from "./factories/AbstractLabelFactory";
|
||||
export * from "./factories/AbstractLinkFactory";
|
||||
export * from "./factories/AbstractNodeFactory";
|
||||
export * from "./factories/AbstractPortFactory";
|
||||
|
||||
export * from "./Toolkit";
|
||||
|
||||
export * from "./DiagramEngine";
|
||||
export * from "./models/DiagramModel";
|
||||
export * from "./BaseEntity";
|
||||
export * from "./CanvasActions";
|
||||
|
||||
export * from "./actions/BaseAction";
|
||||
export * from "./actions/MoveCanvasAction";
|
||||
export * from "./actions/MoveItemsAction";
|
||||
export * from "./actions/SelectingAction";
|
||||
|
||||
export * from "./models/SelectionModel";
|
||||
export * from "./models/DiagramModel";
|
||||
export * from "./models/BaseModel";
|
||||
export * from "./models/DiagramModel";
|
||||
export * from "./models/LinkModel";
|
||||
|
@ -53,7 +53,9 @@ export class DiagramModel extends BaseEntity<DiagramListener> {
|
||||
setGridSize(size: number = 0) {
|
||||
this.gridSize = size;
|
||||
this.iterateListeners((listener, event) => {
|
||||
listener.gridUpdated && listener.gridUpdated({ ...event, size: size });
|
||||
if (listener.gridUpdated) {
|
||||
listener.gridUpdated({ ...event, size: size });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -169,7 +171,9 @@ export class DiagramModel extends BaseEntity<DiagramListener> {
|
||||
this.zoom = zoom;
|
||||
|
||||
this.iterateListeners((listener, event) => {
|
||||
listener.zoomUpdated && listener.zoomUpdated({ ...event, zoom: zoom });
|
||||
if (listener.zoomUpdated) {
|
||||
listener.zoomUpdated({ ...event, zoom: zoom });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -177,22 +181,27 @@ export class DiagramModel extends BaseEntity<DiagramListener> {
|
||||
this.offsetX = offsetX;
|
||||
this.offsetY = offsetY;
|
||||
this.iterateListeners((listener, event) => {
|
||||
listener.offsetUpdated && listener.offsetUpdated({ ...event, offsetX: offsetX, offsetY: offsetY });
|
||||
if (listener.offsetUpdated) {
|
||||
listener.offsetUpdated({ ...event, offsetX: offsetX, offsetY: offsetY });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setOffsetX(offsetX: number) {
|
||||
this.offsetX = offsetX;
|
||||
this.iterateListeners((listener, event) => {
|
||||
listener.offsetUpdated && listener.offsetUpdated({ ...event, offsetX: offsetX, offsetY: this.offsetY });
|
||||
if (listener.offsetUpdated) {
|
||||
listener.offsetUpdated({ ...event, offsetX: offsetX, offsetY: this.offsetY });
|
||||
}
|
||||
});
|
||||
}
|
||||
setOffsetY(offsetY: number) {
|
||||
this.offsetY = offsetY;
|
||||
|
||||
this.iterateListeners((listener, event) => {
|
||||
listener.offsetUpdated &&
|
||||
if (listener.offsetUpdated) {
|
||||
listener.offsetUpdated({ ...event, offsetX: this.offsetX, offsetY: this.offsetY });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -247,7 +256,9 @@ export class DiagramModel extends BaseEntity<DiagramListener> {
|
||||
});
|
||||
this.links[link.getID()] = link;
|
||||
this.iterateListeners((listener, event) => {
|
||||
listener.linksUpdated && listener.linksUpdated({ ...event, link: link, isCreated: true });
|
||||
if (listener.linksUpdated) {
|
||||
listener.linksUpdated({ ...event, link: link, isCreated: true });
|
||||
}
|
||||
});
|
||||
return link;
|
||||
}
|
||||
@ -260,7 +271,9 @@ export class DiagramModel extends BaseEntity<DiagramListener> {
|
||||
});
|
||||
this.nodes[node.getID()] = node;
|
||||
this.iterateListeners((listener, event) => {
|
||||
listener.nodesUpdated && listener.nodesUpdated({ ...event, node: node, isCreated: true });
|
||||
if (listener.nodesUpdated) {
|
||||
listener.nodesUpdated({ ...event, node: node, isCreated: true });
|
||||
}
|
||||
});
|
||||
return node;
|
||||
}
|
||||
@ -269,7 +282,9 @@ export class DiagramModel extends BaseEntity<DiagramListener> {
|
||||
link = this.getLink(link);
|
||||
delete this.links[link.getID()];
|
||||
this.iterateListeners((listener, event) => {
|
||||
listener.linksUpdated && listener.linksUpdated({ ...event, link: link as LinkModel, isCreated: false });
|
||||
if (listener.linksUpdated) {
|
||||
listener.linksUpdated({ ...event, link: link as LinkModel, isCreated: false });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -277,7 +292,9 @@ export class DiagramModel extends BaseEntity<DiagramListener> {
|
||||
node = this.getNode(node);
|
||||
delete this.nodes[node.getID()];
|
||||
this.iterateListeners((listener, event) => {
|
||||
listener.nodesUpdated && listener.nodesUpdated({ ...event, node: node as NodeModel, isCreated: false });
|
||||
if (listener.nodesUpdated) {
|
||||
listener.nodesUpdated({ ...event, node: node as NodeModel, isCreated: false });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -158,7 +158,9 @@ export class LinkModel<T extends LinkModelListener = LinkModelListener> extends
|
||||
}
|
||||
this.sourcePort = port;
|
||||
this.iterateListeners((listener: LinkModelListener, event) => {
|
||||
listener.sourcePortChanged && listener.sourcePortChanged({ ...event, port: port });
|
||||
if (listener.sourcePortChanged) {
|
||||
listener.sourcePortChanged({ ...event, port: port });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -180,7 +182,9 @@ export class LinkModel<T extends LinkModelListener = LinkModelListener> extends
|
||||
}
|
||||
this.targetPort = port;
|
||||
this.iterateListeners((listener: LinkModelListener, event) => {
|
||||
listener.targetPortChanged && listener.targetPortChanged({ ...event, port: port });
|
||||
if (listener.targetPortChanged) {
|
||||
listener.targetPortChanged({ ...event, port: port });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -222,7 +226,7 @@ export class LinkModel<T extends LinkModelListener = LinkModelListener> extends
|
||||
}
|
||||
}
|
||||
|
||||
addPoint<T extends PointModel>(pointModel: T, index = 1): T {
|
||||
addPoint<P extends PointModel>(pointModel: P, index = 1): P {
|
||||
pointModel.setParent(this);
|
||||
this.points.splice(index, 0, pointModel);
|
||||
return pointModel;
|
||||
|
@ -26,14 +26,13 @@ export class NodeModel extends BaseModel<DiagramModel, BaseModelListener> {
|
||||
//store position
|
||||
let oldX = this.x;
|
||||
let oldY = this.y;
|
||||
for (let port in this.ports) {
|
||||
_.forEach(this.ports[port].getLinks(), link => {
|
||||
let point = link.getPointForPort(this.ports[port]);
|
||||
_.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;
|
||||
}
|
||||
@ -43,13 +42,13 @@ export class NodeModel extends BaseModel<DiagramModel, BaseModelListener> {
|
||||
|
||||
// add the points of each link that are selected here
|
||||
if (this.isSelected()) {
|
||||
for (let portName in this.ports) {
|
||||
_.forEach(this.ports, port => {
|
||||
entities = entities.concat(
|
||||
_.map(this.ports[portName].getLinks(), link => {
|
||||
return link.getPointForPort(this.ports[portName]);
|
||||
_.map(port.getLinks(), link => {
|
||||
return link.getPointForPort(port);
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
@ -82,18 +81,18 @@ export class NodeModel extends BaseModel<DiagramModel, BaseModelListener> {
|
||||
doClone(lookupTable = {}, clone) {
|
||||
// also clone the ports
|
||||
clone.ports = {};
|
||||
_.values(this.ports).forEach(port => {
|
||||
_.forEach(this.ports, port => {
|
||||
clone.addPort(port.clone(lookupTable));
|
||||
});
|
||||
}
|
||||
|
||||
remove() {
|
||||
super.remove();
|
||||
for (var i in this.ports) {
|
||||
_.forEach(this.ports[i].getLinks(), link => {
|
||||
_.forEach(this.ports, port => {
|
||||
_.forEach(port.getLinks(), link => {
|
||||
link.remove();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getPortFromID(id): PortModel | null {
|
||||
|
8
src/models/SelectionModel.ts
Normal file
8
src/models/SelectionModel.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { BaseModel, BaseModelListener } from "./BaseModel";
|
||||
import { BaseEntity } from "../BaseEntity";
|
||||
|
||||
export interface SelectionModel {
|
||||
model: BaseModel<BaseEntity, BaseModelListener>;
|
||||
initialX: number;
|
||||
initialY: number;
|
||||
}
|
@ -4,21 +4,19 @@ import * as _ from "lodash";
|
||||
import { LinkLayerWidget } from "./layers/LinkLayerWidget";
|
||||
import { NodeLayerWidget } from "./layers/NodeLayerWidget";
|
||||
import { Toolkit } from "../Toolkit";
|
||||
import { BaseAction, MoveCanvasAction, MoveItemsAction, SelectingAction } from "../CanvasActions";
|
||||
import { 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 SelectionModel {
|
||||
model: BaseModel<BaseEntity, BaseModelListener>;
|
||||
initialX: number;
|
||||
initialY: number;
|
||||
}
|
||||
|
||||
export interface DiagramProps extends BaseWidgetProps {
|
||||
diagramEngine: DiagramEngine;
|
||||
|
||||
@ -60,6 +58,8 @@ export class DiagramWidget extends BaseWidget<DiagramProps, DiagramState> {
|
||||
deleteKeys: [46, 8]
|
||||
};
|
||||
|
||||
onKeyUpPointer: (this: Window, ev: KeyboardEvent) => void = null;
|
||||
|
||||
constructor(props: DiagramProps) {
|
||||
super("srd-diagram", props);
|
||||
this.onMouseMove = this.onMouseMove.bind(this);
|
||||
@ -74,8 +74,6 @@ export class DiagramWidget extends BaseWidget<DiagramProps, DiagramState> {
|
||||
};
|
||||
}
|
||||
|
||||
onKeyUpPointer: null;
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.diagramEngine.removeListener(this.state.diagramEngineListener);
|
||||
this.props.diagramEngine.setCanvas(null);
|
||||
|
@ -53,7 +53,9 @@ export class LinkLayerWidget extends BaseWidget<LinkLayerProps, LinkLayerState>
|
||||
link.sourcePort.updateCoords(portCoords);
|
||||
|
||||
this.props.diagramEngine.linksThatHaveInitiallyRendered[link.id] = true;
|
||||
} catch (ex) {}
|
||||
} catch (ignore) {
|
||||
/*noop*/
|
||||
}
|
||||
}
|
||||
if (link.targetPort !== null) {
|
||||
try {
|
||||
@ -64,15 +66,16 @@ export class LinkLayerWidget extends BaseWidget<LinkLayerProps, LinkLayerState>
|
||||
link.targetPort.updateCoords(portCoords);
|
||||
|
||||
this.props.diagramEngine.linksThatHaveInitiallyRendered[link.id] = true;
|
||||
} catch (ex) {}
|
||||
} catch (ignore) {
|
||||
/*noop*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//generate links
|
||||
var generatedLink = this.props.diagramEngine.generateWidgetForLink(link);
|
||||
if (!generatedLink) {
|
||||
console.log("no link generated for type: " + link.getType());
|
||||
return null;
|
||||
throw new Error(`no link generated for type: ${link.getType()}`);
|
||||
}
|
||||
|
||||
return (
|
||||
|
109
tests/e2e/E2EHelper.ts
Normal file
109
tests/e2e/E2EHelper.ts
Normal file
@ -0,0 +1,109 @@
|
||||
import { ElementHandle, Page } from "puppeteer";
|
||||
import { NodeModel } from "../../src/models/NodeModel";
|
||||
import { LinkModel } from "../../src/models/LinkModel";
|
||||
import * as _ from "lodash";
|
||||
|
||||
export class E2EElement {
|
||||
helper: E2EHelper;
|
||||
page: Page;
|
||||
element: ElementHandle;
|
||||
id: string;
|
||||
|
||||
constructor(helper: E2EHelper, page: Page, element: ElementHandle, id: string) {
|
||||
this.page = page;
|
||||
this.element = element;
|
||||
this.id = id;
|
||||
this.helper = helper;
|
||||
}
|
||||
}
|
||||
|
||||
export class E2ENode extends E2EElement {
|
||||
async port(id: string): Promise<E2EPort> {
|
||||
return new E2EPort(this.helper, this.page, await this.element.$(`div[data-name="${id}"]`), id, this);
|
||||
}
|
||||
|
||||
async model(): Promise<any> {
|
||||
return await this.page.evaluate(id => {
|
||||
return window["diagram_instance"]
|
||||
.getDiagramModel()
|
||||
.getNode(id)
|
||||
.serialize();
|
||||
}, this.id);
|
||||
}
|
||||
}
|
||||
|
||||
export class E2EPort extends E2EElement {
|
||||
parent: E2ENode;
|
||||
|
||||
constructor(helper: E2EHelper, page: Page, element: ElementHandle, id: string, parent: E2ENode) {
|
||||
super(helper, page, element, id);
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
async link(port: E2EPort): Promise<E2ELink> {
|
||||
let currentLinks = _.flatMap((await this.parent.model()).ports, "links");
|
||||
|
||||
let bounds = await this.element.boundingBox();
|
||||
|
||||
// click on this port
|
||||
this.page.mouse.move(bounds.x, bounds.y);
|
||||
this.page.mouse.down();
|
||||
|
||||
let bounds2 = await port.element.boundingBox();
|
||||
|
||||
// drag to other port
|
||||
this.page.mouse.move(bounds2.x, bounds2.y);
|
||||
this.page.mouse.up();
|
||||
|
||||
// get the parent to get the link
|
||||
return await this.helper.link(
|
||||
_.difference(_.flatMap((await this.parent.model()).ports, "links"), currentLinks)[0]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class E2ELink extends E2EElement {
|
||||
async model(): Promise<any> {
|
||||
return await this.page.evaluate(id => {
|
||||
return window["diagram_instance"]
|
||||
.getDiagramModel()
|
||||
.getLink(id)
|
||||
.serialize();
|
||||
}, this.id);
|
||||
}
|
||||
|
||||
async exists(): Promise<boolean> {
|
||||
return await this.page.evaluate(id => {
|
||||
return !!document.querySelector(`path[data-linkid="${id}"]`);
|
||||
}, this.id);
|
||||
}
|
||||
|
||||
async select(): Promise<any> {
|
||||
const point = await this.page.evaluate(id => {
|
||||
const path = document.querySelector(`path[data-linkid="${id}"]`) as SVGPathElement;
|
||||
return path.getPointAtLength(path.getTotalLength() / 2);
|
||||
}, this.id);
|
||||
await this.page.keyboard.down("Shift");
|
||||
await this.page.mouse.move(point.x, point.y);
|
||||
await this.page.mouse.down();
|
||||
await this.page.keyboard.up("Shift");
|
||||
}
|
||||
}
|
||||
|
||||
export class E2EHelper {
|
||||
page: Page;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
async link(id): Promise<E2ELink> {
|
||||
let selector = await this.page.waitForSelector(`path[data-linkid="${id}"]`);
|
||||
return new E2ELink(this, this.page, selector, id);
|
||||
}
|
||||
|
||||
async node(id): Promise<E2ENode> {
|
||||
let selector = await this.page.waitForSelector(`div[data-nodeid="${id}"]`);
|
||||
return new E2ENode(this, this.page, selector, id);
|
||||
}
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
import {ElementHandle, Page} from "puppeteer";
|
||||
import {NodeModel} from "../../src/models/NodeModel";
|
||||
import {LinkModel} from "../../src/models/LinkModel";
|
||||
import * as _ from "lodash";
|
||||
|
||||
export class E2EElement{
|
||||
helper: E2EHelper;
|
||||
page: Page;
|
||||
element: ElementHandle;
|
||||
id: string;
|
||||
|
||||
constructor(helper: E2EHelper,page: Page, element: ElementHandle, id: string) {
|
||||
this.page = page;
|
||||
this.element = element;
|
||||
this.id = id;
|
||||
this.helper = helper;
|
||||
}
|
||||
}
|
||||
|
||||
export class E2ENode extends E2EElement{
|
||||
|
||||
async port(id: string): Promise<E2EPort>{
|
||||
return new E2EPort(this.helper,this.page, await this.element.$('div[data-name="'+id+'"]'), id, this);
|
||||
}
|
||||
|
||||
async model(): Promise<any>{
|
||||
return await this.page.evaluate((id) => {
|
||||
return window['diagram_instance'].getDiagramModel().getNode(id).serialize();
|
||||
}, this.id)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class E2EPort extends E2EElement{
|
||||
|
||||
parent: E2ENode;
|
||||
|
||||
constructor(helper: E2EHelper,page: Page, element: ElementHandle, id: string, parent: E2ENode) {
|
||||
super(helper,page, element, id);
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
|
||||
async link(port: E2EPort): Promise<E2ELink>{
|
||||
|
||||
let currentLinks = _.flatMap((await this.parent.model()).ports, 'links');
|
||||
|
||||
let bounds = await this.element.boundingBox();
|
||||
|
||||
// click on this port
|
||||
this.page.mouse.move(bounds.x, bounds.y);
|
||||
this.page.mouse.down();
|
||||
|
||||
let bounds2 = await port.element.boundingBox();
|
||||
|
||||
// drag to other port
|
||||
this.page.mouse.move(bounds2.x, bounds2.y);
|
||||
this.page.mouse.up();
|
||||
|
||||
// get the parent to get the link
|
||||
return await this.helper.link(_.difference(_.flatMap((await this.parent.model()).ports, 'links'), currentLinks)[0]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class E2ELink extends E2EElement{
|
||||
|
||||
async model(): Promise<any>{
|
||||
return await this.page.evaluate((id) => {
|
||||
return window['diagram_instance'].getDiagramModel().getLink(id).serialize();
|
||||
}, this.id)
|
||||
}
|
||||
|
||||
async exists():Promise<boolean>{
|
||||
return await this.page.evaluate((id) => {
|
||||
return !!document.querySelector('path[data-linkid="' + id + '"]');
|
||||
}, this.id);
|
||||
}
|
||||
|
||||
async select(): Promise<any>{
|
||||
let point = await this.page.evaluate((id) => {
|
||||
let path = (document.querySelector('path[data-linkid="' + id + '"]') as SVGPathElement);
|
||||
let point = path.getPointAtLength(path.getTotalLength()/2);
|
||||
return {
|
||||
x: point.x,
|
||||
y: point.y
|
||||
}
|
||||
}, this.id);
|
||||
await this.page.keyboard.down('Shift');
|
||||
await this.page.mouse.move(point.x, point.y);
|
||||
await this.page.mouse.down();
|
||||
await this.page.keyboard.up('Shift');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class E2EHelper {
|
||||
|
||||
page: Page;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
async link(id): Promise<E2ELink> {
|
||||
let selector = await this.page.waitForSelector('path[data-linkid="' + id + '"]');
|
||||
return new E2ELink(this,this.page, selector, id);
|
||||
}
|
||||
|
||||
async node(id): Promise<E2ENode> {
|
||||
let selector = await this.page.waitForSelector('div[data-nodeid="' + id + '"]');
|
||||
return new E2ENode(this,this.page, selector, id);
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import "jest";
|
||||
import * as puppeteer from "puppeteer"
|
||||
import * as puppeteer from "puppeteer";
|
||||
import { E2EHelper } from "./E2EHelper";
|
||||
|
||||
var browser;
|
||||
@ -7,7 +7,7 @@ var browser;
|
||||
async function itShould(demo: string, directive, test: (page: puppeteer.Page, helper: E2EHelper) => any) {
|
||||
it(directive, async () => {
|
||||
let page = await browser.newPage();
|
||||
await page.goto('file://' + __dirname + '/../../dist/e2e/' + demo + "/index.html");
|
||||
await page.goto("file://" + __dirname + "/../../dist/e2e/" + demo + "/index.html");
|
||||
let helper = new E2EHelper(page);
|
||||
await test(page, helper);
|
||||
await page.close();
|
||||
@ -19,7 +19,7 @@ beforeAll(async () => {
|
||||
console.log("using CircleCI");
|
||||
|
||||
browser = await puppeteer.launch({
|
||||
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
||||
args: ["--no-sandbox", "--disable-setuid-sandbox"]
|
||||
});
|
||||
} else {
|
||||
browser = await puppeteer.launch({
|
||||
@ -32,31 +32,26 @@ afterAll(() => {
|
||||
browser.close();
|
||||
});
|
||||
|
||||
|
||||
describe("simple test", async () => {
|
||||
|
||||
itShould("demo-simple", 'should delete a link and create a new one', async (page, helper) => {
|
||||
|
||||
|
||||
itShould("demo-simple", "should delete a link and create a new one", async (page, helper) => {
|
||||
// get the existing link
|
||||
|
||||
let link = await helper.link('12');
|
||||
let link = await helper.link("12");
|
||||
await expect(await link.exists()).toBeTruthy();
|
||||
|
||||
// remove it
|
||||
await link.select();
|
||||
await page.keyboard.press('Backspace');
|
||||
await page.keyboard.press("Del");
|
||||
|
||||
await expect(await link.exists()).toBeFalsy();
|
||||
|
||||
// create a new link
|
||||
let node1 = await helper.node('6');
|
||||
let node2 = await helper.node('9');
|
||||
let node1 = await helper.node("6");
|
||||
let node2 = await helper.node("9");
|
||||
|
||||
let port1 = await node1.port('7');
|
||||
let port2 = await node2.port('10');
|
||||
let port1 = await node1.port("7");
|
||||
let port2 = await node2.port("10");
|
||||
|
||||
let newlink = await port1.link(port2);
|
||||
await expect(await newlink.exists()).toBeTruthy();
|
||||
});
|
||||
})
|
||||
});
|
@ -1,19 +1,18 @@
|
||||
import PathFinding from "../../src/routing/PathFinding";
|
||||
|
||||
describe('calculating start and end points', () => {
|
||||
|
||||
describe("calculating start and end points", () => {
|
||||
beforeEach(() => {
|
||||
this.pathFinding = new PathFinding(null);
|
||||
});
|
||||
|
||||
test('return correct object for valid walkable input', () => {
|
||||
test("return correct object for valid walkable input", () => {
|
||||
const matrix = [
|
||||
[0, 0, 0, 0, 1, 1],
|
||||
[0, 0, 0, 0, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 0, 0, 0, 0],
|
||||
[1, 1, 0, 0, 0, 0],
|
||||
[1, 1, 0, 0, 0, 0]
|
||||
];
|
||||
const path = [[0, 5], [1, 4], [2, 3], [3, 2], [4, 1], [5, 0]];
|
||||
|
||||
@ -21,23 +20,18 @@ describe('calculating start and end points', () => {
|
||||
|
||||
expect(result.start).toEqual({
|
||||
x: 2,
|
||||
y: 3,
|
||||
y: 3
|
||||
});
|
||||
expect(result.end).toEqual({
|
||||
x: 3,
|
||||
y: 2,
|
||||
y: 2
|
||||
});
|
||||
expect(result.pathToStart).toEqual([[0, 5], [1, 4]]);
|
||||
expect(result.pathToEnd).toEqual([[3, 2], [4, 1], [5, 0]]);
|
||||
});
|
||||
|
||||
test('undefined is returned when no walkable path exists', () => {
|
||||
const matrix = [
|
||||
[0, 0, 1, 1],
|
||||
[0, 0, 1, 1],
|
||||
[1, 1, 0, 0],
|
||||
[1, 1, 0, 0],
|
||||
];
|
||||
test("undefined is returned when no walkable path exists", () => {
|
||||
const matrix = [[0, 0, 1, 1], [0, 0, 1, 1], [1, 1, 0, 0], [1, 1, 0, 0]];
|
||||
const path = [[0, 3], [1, 2], [2, 1], [3, 0]];
|
||||
|
||||
const result = this.pathFinding.calculateLinkStartEndCoords(matrix, path);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import initStoryshots from '@storybook/addon-storyshots';
|
||||
import 'raf/polyfill';
|
||||
import initStoryshots from "@storybook/addon-storyshots";
|
||||
import "raf/polyfill";
|
||||
|
||||
initStoryshots({ configPath: __dirname });
|
9
tests/tslint.json
Normal file
9
tests/tslint.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": [
|
||||
"../tslint.json"
|
||||
],
|
||||
"rules": {
|
||||
"no-console": false,
|
||||
"max-classes-per-file": false
|
||||
}
|
||||
}
|
@ -9,7 +9,11 @@
|
||||
"comment-format": false,
|
||||
"max-line-length": false,
|
||||
"object-literal-sort-keys": false,
|
||||
"quotemark": false,
|
||||
"quotemark": [true, "double", "jsx-double"],
|
||||
"arrow-parens": false,
|
||||
"indent": [true, "tabs", 2],
|
||||
"semicolon": false,
|
||||
"object-literal-key-quotes": [true, "as-needed"],
|
||||
"no-var-keyword": false,
|
||||
"jsdoc-format": false,
|
||||
"prefer-const": false,
|
||||
|
@ -14,7 +14,7 @@ if(process.env.NODE_ENV === 'production'){
|
||||
}
|
||||
}));
|
||||
plugins.push(new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': JSON.stringify('production'),
|
||||
'process.env.NODE_ENV': 'production',
|
||||
}));
|
||||
}
|
||||
|
||||
|
11
yarn.lock
11
yarn.lock
@ -2328,6 +2328,13 @@ create-react-class@^15.5.2, create-react-class@^15.6.2:
|
||||
loose-envify "^1.3.1"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
cross-env@^5.1.3:
|
||||
version "5.1.3"
|
||||
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.1.3.tgz#f8ae18faac87692b0a8b4d2f7000d4ec3a85dfd7"
|
||||
dependencies:
|
||||
cross-spawn "^5.1.0"
|
||||
is-windows "^1.0.0"
|
||||
|
||||
cross-spawn@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982"
|
||||
@ -2335,7 +2342,7 @@ cross-spawn@^3.0.0:
|
||||
lru-cache "^4.0.1"
|
||||
which "^1.2.9"
|
||||
|
||||
cross-spawn@^5.0.1:
|
||||
cross-spawn@^5.0.1, cross-spawn@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
|
||||
dependencies:
|
||||
@ -4306,7 +4313,7 @@ is-utf8@^0.2.0:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
|
||||
|
||||
is-windows@^1.0.2:
|
||||
is-windows@^1.0.0, is-windows@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
|
||||
|
||||
|
Reference in New Issue
Block a user