diff --git a/lib-core/src/core/BaseObserver.ts b/lib-core/src/core/BaseObserver.ts index b9b6cc4..8e1fc9f 100644 --- a/lib-core/src/core/BaseObserver.ts +++ b/lib-core/src/core/BaseObserver.ts @@ -1,14 +1,32 @@ -import { Toolkit } from '../Toolkit'; +import { Toolkit } from "../Toolkit"; export interface BaseEvent { firing: boolean; stopPropagation: () => any; } +export interface BaseEventProxy extends BaseEvent { + function: string; +} + /** * Listeners are always in the form of an object that contains methods that take events */ -export type BaseListener = { [key: string]: (event: BaseEvent) => any }; +export type BaseListener = { + /** + * Generic event that fires before a specific event was fired + */ + eventWillFire?: (event: BaseEvent & { function: string }) => void; + + /** + * Generic event that fires after a specific event was fired (even if it was consumed) + */ + eventDidFire?: (event: BaseEvent & { function: string }) => void; + /** + * Type for other events that will fire + */ + [key: string]: (event: BaseEvent) => any +}; /** * Base observer pattern class for working with listeners @@ -20,6 +38,19 @@ export class BaseObserver { this.listeners = {}; } + private fireEventInternal(fire: boolean, k: keyof L, event: BaseEvent, ) { + this.iterateListeners(listener => { + // returning false here will instruct itteration to stop + if (!fire && !event.firing) { + return false; + } + // fire selected listener + if (listener[k]) { + listener[k](event as BaseEvent); + } + }); + } + fireEvent(event: Partial & object, k: keyof L) { event = { firing: true, @@ -29,15 +60,20 @@ export class BaseObserver { ...event }; - this.iterateListeners(listener => { - // returning false here will instruct itteration to stop - if (!event.firing) { - return false; - } - if (listener[k]) { - listener[k](event as BaseEvent); - } - }); + // fire pre + this.fireEventInternal( true, "eventWillFire", { + ...event, + function: k + } as BaseEventProxy); + + // fire main event + this.fireEventInternal( false, k, event as BaseEvent); + + // fire post + this.fireEventInternal( true, "eventDidFire", { + ...event, + function: k + } as BaseEventProxy); } iterateListeners(cb: (listener: L) => any) { diff --git a/lib-core/src/models/NodeModel.ts b/lib-core/src/models/NodeModel.ts index b46f3b3..9a40ee2 100644 --- a/lib-core/src/models/NodeModel.ts +++ b/lib-core/src/models/NodeModel.ts @@ -1,10 +1,10 @@ -import { BaseModel, BaseModelGenerics, BaseModelListener } from "../core-models/BaseModel"; -import { PortModel } from './PortModel'; +import { BaseModelListener } from "../core-models/BaseModel"; import * as _ from 'lodash'; import { DiagramEngine } from '../DiagramEngine'; import { BaseEntityEvent } from '../core-models/BaseEntity'; import { BasePositionModel, BasePositionModelGenerics } from "../core-models/BasePositionModel"; import { DiagramModel } from "./DiagramModel"; +import { PortModel } from "./PortModel"; export interface NodeModelListener extends BaseModelListener { positionChanged?(event: BaseEntityEvent): void; @@ -13,11 +13,12 @@ export interface NodeModelListener extends BaseModelListener { export interface NodeModelGenerics extends BasePositionModelGenerics{ LISTENER: NodeModelListener; PARENT: DiagramModel; + PORT: PortModel; } export class NodeModel extends BasePositionModel { - ports: { [s: string]: PortModel }; + ports: { [s: string]: G['PORT'] }; // calculated post rendering so routing can be done correctly width: number; @@ -100,7 +101,7 @@ export class NodeModel extends }); } - getPortFromID(id): PortModel | null { + getPortFromID(id): G['PORT'] | null { for (var i in this.ports) { if (this.ports[i].getID() === id) { return this.ports[i]; @@ -109,15 +110,15 @@ export class NodeModel extends return null; } - getPort(name: string): PortModel | null { + getPort(name: string): G['PORT'] | null { return this.ports[name]; } - getPorts(): { [s: string]: PortModel } { + getPorts(): { [s: string]: G['PORT'] } { return this.ports; } - removePort(port: PortModel) { + removePort(port: G['PORT']) { //clear the parent node reference if (this.ports[port.getName()]) { this.ports[port.getName()].setParent(null); @@ -125,7 +126,7 @@ export class NodeModel extends } } - addPort(port: T): T { + addPort(port: T): T { port.setParent(this); this.ports[port.getName()] = port; return port; diff --git a/lib-defaults/src/label/DefaultLabelModel.tsx b/lib-defaults/src/label/DefaultLabelModel.tsx index 8c6e697..85954db 100644 --- a/lib-defaults/src/label/DefaultLabelModel.tsx +++ b/lib-defaults/src/label/DefaultLabelModel.tsx @@ -1,26 +1,41 @@ -import { DiagramEngine, LabelModel } from '@projectstorm/react-diagrams-core'; +import { + BaseModelGenerics, + BaseModelOptions, + DiagramEngine, + LabelModel, +} from "@projectstorm/react-diagrams-core"; -export class DefaultLabelModel extends LabelModel { - label: string; +export interface DefaultLabelModelOptions extends Omit{ + label?: string; +} - constructor() { - super('default'); +export interface DefaultLabelModelGenerics{ + OPTIONS: DefaultLabelModelOptions; +} + +export class DefaultLabelModel extends LabelModel { + + constructor(options: DefaultLabelModelOptions = {}) { + super({ + ...options, + type: 'default' + }); this.offsetY = -23; } setLabel(label: string) { - this.label = label; + this.options.label = label; } deSerialize(ob, engine: DiagramEngine) { super.deSerialize(ob, engine); - this.label = ob.label; + this.options.label = ob.label; } serialize() { return { ...super.serialize(), - label: this.label + label: this.options.label }; } } diff --git a/lib-defaults/src/label/DefaultLabelWidget.tsx b/lib-defaults/src/label/DefaultLabelWidget.tsx index 83eb7fd..d709a0d 100644 --- a/lib-defaults/src/label/DefaultLabelWidget.tsx +++ b/lib-defaults/src/label/DefaultLabelWidget.tsx @@ -12,6 +12,6 @@ export class DefaultLabelWidget extends BaseWidget { } render() { - return
{this.props.model.label}
; + return
{this.props.model.getOptions().label}
; } } diff --git a/lib-defaults/src/node/DefaultNodeFactory.tsx b/lib-defaults/src/node/DefaultNodeFactory.tsx index 0eef0ac..46f7e98 100644 --- a/lib-defaults/src/node/DefaultNodeFactory.tsx +++ b/lib-defaults/src/node/DefaultNodeFactory.tsx @@ -2,9 +2,7 @@ import * as React from 'react'; import { AbstractReactFactory } from '@projectstorm/react-diagrams-core'; import { DefaultNodeModel } from './DefaultNodeModel'; import { DefaultNodeWidget } from './DefaultNodeWidget'; -/** - * @author Dylan Vorster - */ + export class DefaultNodeFactory extends AbstractReactFactory { constructor() { super('default'); diff --git a/lib-defaults/src/node/DefaultNodeModel.ts b/lib-defaults/src/node/DefaultNodeModel.ts index ef47db1..63893df 100644 --- a/lib-defaults/src/node/DefaultNodeModel.ts +++ b/lib-defaults/src/node/DefaultNodeModel.ts @@ -1,48 +1,71 @@ import * as _ from 'lodash'; -import { DiagramEngine, NodeModel, NodeModelListener, Toolkit } from '@projectstorm/react-diagrams-core'; +import { + BaseModelOptions, + DiagramEngine, + NodeModel, + NodeModelGenerics, +} from "@projectstorm/react-diagrams-core"; import { DefaultPortModel } from '../port/DefaultPortModel'; -export class DefaultNodeModel extends NodeModel { - name: string; - color: string; - ports: { [s: string]: DefaultPortModel }; +export interface DefaultNodeModelOptions extends Omit{ + name?: string; + color?: string; +} - constructor(name: string = 'Untitled', color: string = 'rgb(0,192,255)') { - super('default'); - this.name = name; - this.color = color; +export interface DefaultNodeModelGenerics{ + PORT: DefaultPortModel; + OPTIONS: DefaultNodeModelOptions; +} + +export class DefaultNodeModel extends NodeModel { + + constructor(options: DefaultNodeModelOptions = {}) { + super({ + type: 'default', + name: 'Untitled', + color: 'rgb(0,192,255)', + ...options + }); } addInPort(label: string): DefaultPortModel { - return this.addPort(new DefaultPortModel(true, Toolkit.UID(), label)); + return this.addPort(new DefaultPortModel({ + in: true, + name: label, + label: label + })); } addOutPort(label: string): DefaultPortModel { - return this.addPort(new DefaultPortModel(false, Toolkit.UID(), label)); + return this.addPort(new DefaultPortModel({ + in: false, + name: label, + label: label + })); } deSerialize(object, engine: DiagramEngine) { super.deSerialize(object, engine); - this.name = object.name; - this.color = object.color; + this.options.name = object.name; + this.options.color = object.color; } serialize() { return _.merge(super.serialize(), { - name: this.name, - color: this.color + name: this.options.name, + color: this.options.color }); } getInPorts(): DefaultPortModel[] { return _.filter(this.ports, portModel => { - return portModel.in; + return portModel.getOptions().in; }); } getOutPorts(): DefaultPortModel[] { return _.filter(this.ports, portModel => { - return !portModel.in; + return !portModel.getOptions().in; }); } } diff --git a/lib-defaults/src/node/DefaultNodeWidget.tsx b/lib-defaults/src/node/DefaultNodeWidget.tsx index b2650cb..8b7a20e 100644 --- a/lib-defaults/src/node/DefaultNodeWidget.tsx +++ b/lib-defaults/src/node/DefaultNodeWidget.tsx @@ -49,15 +49,34 @@ export interface DefaultNodeProps extends BaseWidgetProps { * for both all the input ports on the left, and the output ports on the right. */ export class DefaultNodeWidget extends BaseWidget { + + listener: any; + generatePort = port => { return ; }; + componentWillUnmount(): void { + + // release repaint listener + if(this.listener){ + this.listener(); + } + } + + componentDidMount(): void { + this.listener = this.props.node.registerListener({ + eventDidFire: () => { + this.forceUpdate() + } + }) + } + render() { return ( - + - {this.props.node.name} + {this.props.node.getOptions().name} {_.map(this.props.node.getInPorts(), this.generatePort)} diff --git a/lib-defaults/src/port/DefaultPortModel.ts b/lib-defaults/src/port/DefaultPortModel.ts index 4c028e6..42b5391 100644 --- a/lib-defaults/src/port/DefaultPortModel.ts +++ b/lib-defaults/src/port/DefaultPortModel.ts @@ -7,6 +7,7 @@ import { PortModelOptions } from "@projectstorm/react-diagrams-core"; import { DefaultLinkModel } from '../link/DefaultLinkModel'; +import { DefaultNodeModel } from "../node/DefaultNodeModel"; export interface DefaultPortModelOptions extends Omit{ label?: string; @@ -15,7 +16,7 @@ export interface DefaultPortModelOptions extends Omit{ export interface DefaultPortModelGenerics{ OPTIONS: DefaultPortModelOptions; - PARENT: DefaultLinkModel; + PARENT: DefaultNodeModel; } export class DefaultPortModel extends PortModel { diff --git a/lib-routing/src/engine/PathFinding.ts b/lib-routing/src/engine/PathFinding.ts index 2dd564c..6365904 100644 --- a/lib-routing/src/engine/PathFinding.ts +++ b/lib-routing/src/engine/PathFinding.ts @@ -1,5 +1,6 @@ import * as PF from 'pathfinding'; import { PathFindingLinkFactory } from '../link/PathFindingLinkFactory'; +import { PointModel } from "@projectstorm/react-diagrams-core"; /* it can be very expensive to calculate routes when every single pixel on the canvas @@ -26,23 +27,17 @@ export default class PathFinding { * finds a direct path from point A to B. */ calculateDirectPath( - from: { - x: number; - y: number; - }, - to: { - x: number; - y: number; - } + from: PointModel, + to: PointModel ): number[][] { const matrix = this.factory.getCanvasMatrix(); const grid = new PF.Grid(matrix); return pathFinderInstance.findPath( - this.factory.translateRoutingX(Math.floor(from.x / this.factory.ROUTING_SCALING_FACTOR)), - this.factory.translateRoutingY(Math.floor(from.y / this.factory.ROUTING_SCALING_FACTOR)), - this.factory.translateRoutingX(Math.floor(to.x / this.factory.ROUTING_SCALING_FACTOR)), - this.factory.translateRoutingY(Math.floor(to.y / this.factory.ROUTING_SCALING_FACTOR)), + this.factory.translateRoutingX(Math.floor(from.getX() / this.factory.ROUTING_SCALING_FACTOR)), + this.factory.translateRoutingY(Math.floor(from.getY() / this.factory.ROUTING_SCALING_FACTOR)), + this.factory.translateRoutingX(Math.floor(to.getX() / this.factory.ROUTING_SCALING_FACTOR)), + this.factory.translateRoutingY(Math.floor(to.getY() / this.factory.ROUTING_SCALING_FACTOR)), grid ); } diff --git a/lib-routing/src/link/PathFindingLinkFactory.tsx b/lib-routing/src/link/PathFindingLinkFactory.tsx index 7dbb203..87c47df 100644 --- a/lib-routing/src/link/PathFindingLinkFactory.tsx +++ b/lib-routing/src/link/PathFindingLinkFactory.tsx @@ -125,26 +125,26 @@ export class PathFindingLinkFactory extends AbstractReactFactory { const allNodesCoords = _.values(this.engine.diagramModel.getNodes()).map(item => ({ - x: item.x, + x: item.getX(), width: item.width, - y: item.y, + y: item.getY(), height: item.height })); const allLinks = _.values(this.engine.diagramModel.getLinks()); - const allPortsCoords = _.flatMap(allLinks.map(link => [link.sourcePort, link.targetPort])) + const allPortsCoords = _.flatMap(allLinks.map(link => [link.getSourcePort(), link.getTargetPort()])) .filter(port => port !== null) .map(item => ({ - x: item.x, + x: item.getX(), width: item.width, - y: item.y, + y: item.getY(), height: item.height })); - const allPointsCoords = _.flatMap(allLinks.map(link => link.points)).map(item => ({ + const allPointsCoords = _.flatMap(allLinks.map(link => link.getPoints())).map(item => ({ // points don't have width/height, so let's just use 0 - x: item.x, + x: item.getX(), width: 0, - y: item.y, + y: item.getY(), height: 0 })); @@ -181,10 +181,10 @@ export class PathFindingLinkFactory extends AbstractReactFactory { _.values(this.engine.diagramModel.getNodes()).forEach(node => { - const startX = Math.floor(node.x / this.ROUTING_SCALING_FACTOR); - const endX = Math.ceil((node.x + node.width) / this.ROUTING_SCALING_FACTOR); - const startY = Math.floor(node.y / this.ROUTING_SCALING_FACTOR); - const endY = Math.ceil((node.y + node.height) / this.ROUTING_SCALING_FACTOR); + const startX = Math.floor(node.getX() / this.ROUTING_SCALING_FACTOR); + const endX = Math.ceil((node.getX() + node.width) / this.ROUTING_SCALING_FACTOR); + const startY = Math.floor(node.getY() / this.ROUTING_SCALING_FACTOR); + const endY = Math.ceil((node.getY() + node.height) / this.ROUTING_SCALING_FACTOR); for (let x = startX - 1; x <= endX + 1; x++) { for (let y = startY - 1; y < endY + 1; y++) { @@ -199,7 +199,7 @@ export class PathFindingLinkFactory extends AbstractReactFactory { const allElements = _.flatMap( - _.values(this.engine.diagramModel.getLinks()).map(link => [].concat(link.sourcePort, link.targetPort)) + _.values(this.engine.diagramModel.getLinks()).map(link => [].concat(link.getSourcePort(), link.getTargetPort())) ); allElements .filter(port => port !== null) diff --git a/lib-routing/src/link/PathFindingLinkModel.ts b/lib-routing/src/link/PathFindingLinkModel.ts index 8e37705..ddc678c 100644 --- a/lib-routing/src/link/PathFindingLinkModel.ts +++ b/lib-routing/src/link/PathFindingLinkModel.ts @@ -3,6 +3,8 @@ import { LinkModel } from '@projectstorm/react-diagrams-core'; export class PathFindingLinkModel extends LinkModel { constructor() { - super(PathFindingLinkFactory.NAME); + super({ + type: PathFindingLinkFactory.NAME + }); } } diff --git a/lib-routing/src/link/PathFindingLinkWidget.tsx b/lib-routing/src/link/PathFindingLinkWidget.tsx index d6ae30b..458b618 100644 --- a/lib-routing/src/link/PathFindingLinkWidget.tsx +++ b/lib-routing/src/link/PathFindingLinkWidget.tsx @@ -1,9 +1,9 @@ -import * as React from 'react'; -import * as _ from 'lodash'; -import { BaseWidget, BaseWidgetProps, DiagramEngine, LabelModel, PointModel } from '@projectstorm/react-diagrams-core'; -import PathFinding from '../engine/PathFinding'; -import { PathFindingLinkFactory } from './PathFindingLinkFactory'; -import { PathFindingLinkModel } from './PathFindingLinkModel'; +import * as React from "react"; +import * as _ from "lodash"; +import { BaseWidget, BaseWidgetProps, DiagramEngine, LabelModel, PointModel } from "@projectstorm/react-diagrams-core"; +import PathFinding from "../engine/PathFinding"; +import { PathFindingLinkFactory } from "./PathFindingLinkFactory"; +import { PathFindingLinkModel } from "./PathFindingLinkModel"; export interface PathFindingLinkWidgetProps extends BaseWidgetProps { color?: string; @@ -21,7 +21,7 @@ export interface PathFindingLinkWidgetState { export class PathFindingLinkWidget extends BaseWidget { public static defaultProps: PathFindingLinkWidgetProps = { - color: 'black', + color: "black", width: 3, link: null, engine: null, @@ -37,7 +37,7 @@ export class PathFindingLinkWidget extends BaseWidget { + _.forEach(this.props.link.getLabels(), (label, index) => { this.calculateLabelPosition(label, index + 1); }); } componentDidUpdate() { - if (this.props.link.labels.length > 0) { + if (this.props.link.getLabels().length > 0) { window.requestAnimationFrame(this.calculateAllLabelPosition.bind(this)); } } componentDidMount() { - if (this.props.link.labels.length > 0) { + if (this.props.link.getLabels().length > 0) { window.requestAnimationFrame(this.calculateAllLabelPosition.bind(this)); } } 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.getPoints()[pointIndex].getX(); + let y = this.props.link.getPoints()[pointIndex].getY(); return ( - + { this.setState({ selected: true }); }} - data-id={this.props.link.points[pointIndex].getID()} + data-id={this.props.link.getPoints()[pointIndex].getID()} data-linkid={this.props.link.getID()} cx={x} cy={y} r={15} opacity={0} - className={'point ' + this.bem('__point')} + className={"point " + this.bem("__point")} /> ); @@ -104,9 +104,12 @@ export class PathFindingLinkWidget extends BaseWidget
(this.refLabels[label.getID()] = ref)}> @@ -132,7 +135,7 @@ export class PathFindingLinkWidget extends BaseWidget ref && this.refPaths.push(ref)} @@ -142,7 +145,7 @@ export class PathFindingLinkWidget extends BaseWidget { this.setState({ selected: false }); }, @@ -150,7 +153,7 @@ export class PathFindingLinkWidget extends BaseWidget { @@ -162,7 +165,7 @@ export class PathFindingLinkWidget extends BaseWidget + {Bottom} {Top} @@ -176,7 +179,7 @@ export class PathFindingLinkWidget extends BaseWidget previousValue + currentValue, 0) * - (index / (this.props.link.labels.length + 1)); + (index / (this.props.link.getLabels().length + 1)); // find the path where the label will be rendered and calculate the relative position let pathIndex = 0; @@ -214,7 +217,7 @@ export class PathFindingLinkWidget extends BaseWidget {paths} - {_.map(this.props.link.labels, labelModel => { + {_.map(this.props.link.getLabels(), labelModel => { return this.generateLabel(labelModel); })}