From 3a2d3ccb7370674fdc2377bc5c423db0f243d467 Mon Sep 17 00:00:00 2001 From: Dylan Vorster Date: Sun, 28 Jul 2019 18:13:03 +0200 Subject: [PATCH] make all the things pluggable --- lib-all/index.ts | 16 +- lib-core/index.ts | 17 ++- lib-core/src/DiagramEngine.ts | 20 ++- lib-core/src/actions/BaseMouseAction.ts | 22 --- .../{ => move-canvas}/MoveCanvasAction.ts | 14 +- .../move-canvas/MoveCanvasActionFactory.ts | 17 +++ .../{ => move-items}/MoveItemsAction.ts | 62 ++++---- .../move-items/MoveItemsActionFactory.ts | 32 ++++ .../{ => selecting-items}/SelectingAction.ts | 12 +- .../SelectingItemsActionFactory.ts | 17 +++ lib-core/src/core-actions/AbstractAction.ts | 12 ++ .../src/core-actions/AbstractActionFactory.ts | 16 ++ .../src/core-actions/AbstractMouseAction.ts | 20 +++ lib-core/src/core-models/BaseEntity.ts | 2 +- lib-core/src/core/AbstractFactory.ts | 12 +- lib-core/src/core/AbstractModelFactory.ts | 13 ++ lib-core/src/core/AbstractReactFactory.tsx | 4 +- lib-core/src/core/FactoryBank.ts | 5 + lib-core/src/widgets/DiagramWidget.tsx | 143 ++++++++---------- lib-core/src/widgets/LinkWidget.tsx | 1 + lib-defaults/src/node/DefaultNodeModel.ts | 4 +- lib-defaults/src/port/DefaultPortFactory.tsx | 4 +- lib-defaults/src/port/DefaultPortModel.ts | 10 +- 23 files changed, 296 insertions(+), 179 deletions(-) delete mode 100644 lib-core/src/actions/BaseMouseAction.ts rename lib-core/src/actions/{ => move-canvas}/MoveCanvasAction.ts (51%) create mode 100644 lib-core/src/actions/move-canvas/MoveCanvasActionFactory.ts rename lib-core/src/actions/{ => move-items}/MoveItemsAction.ts (74%) create mode 100644 lib-core/src/actions/move-items/MoveItemsActionFactory.ts rename lib-core/src/actions/{ => selecting-items}/SelectingAction.ts (85%) create mode 100644 lib-core/src/actions/selecting-items/SelectingItemsActionFactory.ts create mode 100644 lib-core/src/core-actions/AbstractAction.ts create mode 100644 lib-core/src/core-actions/AbstractActionFactory.ts create mode 100644 lib-core/src/core-actions/AbstractMouseAction.ts create mode 100644 lib-core/src/core/AbstractModelFactory.ts diff --git a/lib-all/index.ts b/lib-all/index.ts index f9d8959..a1eee04 100644 --- a/lib-all/index.ts +++ b/lib-all/index.ts @@ -1,4 +1,9 @@ -import { DiagramEngine } from '@projectstorm/react-diagrams-core'; +import { + DiagramEngine, + MoveCanvasActionFactory, + MoveItemsActionFactory, + SelectingItemsActionFactory +} from '@projectstorm/react-diagrams-core'; import { DefaultLabelFactory, DefaultLinkFactory, @@ -16,10 +21,17 @@ export * from '@projectstorm/react-diagrams-routing'; */ export default (): DiagramEngine => { const engine = new DiagramEngine(); + + // register model factories engine.getLabelFactories().registerFactory(new DefaultLabelFactory()); - engine.getNodeFactories().registerFactory(new DefaultNodeFactory()); + engine.getNodeFactories().registerFactory(new DefaultNodeFactory() as any); // i cant figure out why engine.getLinkFactories().registerFactory(new DefaultLinkFactory()); engine.getLinkFactories().registerFactory(new PathFindingLinkFactory()); engine.getPortFactories().registerFactory(new DefaultPortFactory()); + + // register the default interaction behaviours + engine.getActionFactories().registerFactory(new MoveCanvasActionFactory()); + engine.getActionFactories().registerFactory(new SelectingItemsActionFactory()); + engine.getActionFactories().registerFactory(new MoveItemsActionFactory()); return engine; }; diff --git a/lib-core/index.ts b/lib-core/index.ts index 060bb18..b39d134 100644 --- a/lib-core/index.ts +++ b/lib-core/index.ts @@ -1,13 +1,22 @@ import './sass/main.scss'; -export * from './src/actions/BaseMouseAction'; -export * from './src/actions/MoveCanvasAction'; -export * from './src/actions/SelectingAction'; -export * from './src/actions/MoveItemsAction'; +export * from './src/core-actions/AbstractAction'; +export * from './src/core-actions/AbstractActionFactory'; +export * from './src/core-actions/AbstractMouseAction'; + +export * from './src/actions/move-canvas/MoveCanvasActionFactory'; +export * from './src/actions/move-canvas/MoveCanvasAction'; + +export * from './src/actions/selecting-items/SelectingAction'; +export * from './src/actions/selecting-items/SelectingItemsActionFactory'; + +export * from './src/actions/move-items/MoveItemsAction'; +export * from './src/actions/move-items/MoveItemsActionFactory'; export * from './src/core/BaseObserver'; export * from './src/core/FactoryBank'; export * from './src/core/AbstractFactory'; +export * from './src/core/AbstractModelFactory'; export * from './src/core/AbstractReactFactory'; export * from './src/core-models/BaseModel'; diff --git a/lib-core/src/DiagramEngine.ts b/lib-core/src/DiagramEngine.ts index ac0e30b..4d06e2b 100644 --- a/lib-core/src/DiagramEngine.ts +++ b/lib-core/src/DiagramEngine.ts @@ -8,12 +8,13 @@ import { PortModel } from './models/PortModel'; import { LinkModel } from './models/LinkModel'; import { LabelModel } from './models/LabelModel'; import { FactoryBank } from './core/FactoryBank'; -import { AbstractFactory } from './core/AbstractFactory'; import { AbstractReactFactory } from './core/AbstractReactFactory'; import { BaseListener, BaseObserver } from './core/BaseObserver'; import { Point } from '@projectstorm/react-diagrams-geometry'; import { Toolkit } from './Toolkit'; import { MouseEvent } from 'react'; +import { AbstractActionFactory } from './core-actions/AbstractActionFactory'; +import { AbstractModelFactory } from './core/AbstractModelFactory'; export interface DiagramEngineListener extends BaseListener { canvasReady?(): void; @@ -29,8 +30,9 @@ export interface DiagramEngineListener extends BaseListener { export class DiagramEngine extends BaseObserver { protected nodeFactories: FactoryBank>; protected linkFactories: FactoryBank>; - protected portFactories: FactoryBank>; + protected portFactories: FactoryBank>; protected labelFactories: FactoryBank>; + protected actionFactories: FactoryBank; diagramModel: DiagramModel; canvas: Element; @@ -47,6 +49,7 @@ export class DiagramEngine extends BaseObserver { this.linkFactories = new FactoryBank(); this.portFactories = new FactoryBank(); this.labelFactories = new FactoryBank(); + this.actionFactories = new FactoryBank(); const setup = (factory: FactoryBank) => { factory.registerListener({ @@ -63,6 +66,7 @@ export class DiagramEngine extends BaseObserver { setup(this.linkFactories); setup(this.portFactories); setup(this.labelFactories); + setup(this.actionFactories); this.canvas = null; this.paintableWidgets = null; @@ -194,22 +198,26 @@ export class DiagramEngine extends BaseObserver { //!-------------- FACTORIES ------------ - getNodeFactories(): FactoryBank { + getNodeFactories() { return this.nodeFactories; } - getLinkFactories(): FactoryBank { + getLinkFactories() { return this.linkFactories; } - getLabelFactories(): FactoryBank { + getLabelFactories() { return this.labelFactories; } - getPortFactories(): FactoryBank { + getPortFactories() { return this.portFactories; } + getActionFactories() { + return this.actionFactories; + } + getFactoryForNode(node: NodeModel | string) { if (typeof node === 'string') { return this.nodeFactories.getFactory(node); diff --git a/lib-core/src/actions/BaseMouseAction.ts b/lib-core/src/actions/BaseMouseAction.ts deleted file mode 100644 index 512e847..0000000 --- a/lib-core/src/actions/BaseMouseAction.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { DiagramModel } from '../models/DiagramModel'; -import { MouseEvent } from 'react'; - -export abstract class BaseMouseAction { - mouseX: number; - mouseY: number; - ms: number; - model: DiagramModel; - - constructor(mouseX: number, mouseY: number, model: DiagramModel) { - this.mouseX = mouseX; - this.mouseY = mouseY; - this.model = model; - this.ms = new Date().getTime(); - } - - abstract fireMouseDown(event: MouseEvent); - - abstract fireMouseMove(event: MouseEvent); - - abstract fireMouseUp(event: MouseEvent); -} diff --git a/lib-core/src/actions/MoveCanvasAction.ts b/lib-core/src/actions/move-canvas/MoveCanvasAction.ts similarity index 51% rename from lib-core/src/actions/MoveCanvasAction.ts rename to lib-core/src/actions/move-canvas/MoveCanvasAction.ts index 203bbe5..f7d7f69 100644 --- a/lib-core/src/actions/MoveCanvasAction.ts +++ b/lib-core/src/actions/move-canvas/MoveCanvasAction.ts @@ -1,15 +1,15 @@ -import { BaseMouseAction } from './BaseMouseAction'; -import { DiagramModel } from '../models/DiagramModel'; +import { AbstractMouseAction } from '../../core-actions/AbstractMouseAction'; import { MouseEvent } from 'react'; +import { DiagramEngine } from '../../DiagramEngine'; -export class MoveCanvasAction extends BaseMouseAction { +export class MoveCanvasAction extends AbstractMouseAction { initialOffsetX: number; initialOffsetY: number; - constructor(mouseX: number, mouseY: number, diagramModel: DiagramModel) { - super(mouseX, mouseY, diagramModel); - this.initialOffsetX = diagramModel.getOffsetX(); - this.initialOffsetY = diagramModel.getOffsetY(); + constructor(mouseX: number, mouseY: number, engine: DiagramEngine) { + super(mouseX, mouseY, engine); + this.initialOffsetX = this.model.getOffsetX(); + this.initialOffsetY = this.model.getOffsetY(); } fireMouseMove(event: MouseEvent) { diff --git a/lib-core/src/actions/move-canvas/MoveCanvasActionFactory.ts b/lib-core/src/actions/move-canvas/MoveCanvasActionFactory.ts new file mode 100644 index 0000000..34e5f58 --- /dev/null +++ b/lib-core/src/actions/move-canvas/MoveCanvasActionFactory.ts @@ -0,0 +1,17 @@ +import { AbstractActionFactory, ActionFactoryActivationEvent } from '../../core-actions/AbstractActionFactory'; +import { MoveCanvasAction } from './MoveCanvasAction'; +import { MouseEvent } from 'react'; + +export class MoveCanvasActionFactory extends AbstractActionFactory { + constructor() { + super('move-canvas'); + } + + generateAction(event: MouseEvent): MoveCanvasAction { + return new MoveCanvasAction(event.clientX, event.clientY, this.engine); + } + + activate(event: ActionFactoryActivationEvent): boolean { + return !event.selectedEntity && !event.mouseEvent.shiftKey; + } +} diff --git a/lib-core/src/actions/MoveItemsAction.ts b/lib-core/src/actions/move-items/MoveItemsAction.ts similarity index 74% rename from lib-core/src/actions/MoveItemsAction.ts rename to lib-core/src/actions/move-items/MoveItemsAction.ts index a2b420c..e19561b 100644 --- a/lib-core/src/actions/MoveItemsAction.ts +++ b/lib-core/src/actions/move-items/MoveItemsAction.ts @@ -1,24 +1,22 @@ -import { BaseMouseAction } from './BaseMouseAction'; -import { SelectionModel } from '../models/SelectionModel'; -import { PointModel } from '../models/PointModel'; -import { NodeModel } from '../models/NodeModel'; -import { DiagramEngine } from '../DiagramEngine'; -import { BasePositionModel } from '../core-models/BasePositionModel'; +import { AbstractMouseAction } from '../../core-actions/AbstractMouseAction'; +import { SelectionModel } from '../../models/SelectionModel'; +import { PointModel } from '../../models/PointModel'; +import { NodeModel } from '../../models/NodeModel'; +import { DiagramEngine } from '../../DiagramEngine'; +import { BasePositionModel } from '../../core-models/BasePositionModel'; import * as _ from 'lodash'; -import { PortModel } from '../models/PortModel'; -import { LinkModel } from '../models/LinkModel'; +import { PortModel } from '../../models/PortModel'; +import { LinkModel } from '../../models/LinkModel'; import { MouseEvent } from 'react'; -export class MoveItemsAction extends BaseMouseAction { +export class MoveItemsAction extends AbstractMouseAction { selectionModels: SelectionModel[]; moved: boolean; - diagramEngine: DiagramEngine; allowLooseLinks: boolean; constructor(mouseX: number, mouseY: number, diagramEngine: DiagramEngine, allowLooseLinks: boolean) { - super(mouseX, mouseY, diagramEngine.getDiagramModel()); + super(mouseX, mouseY, diagramEngine); this.allowLooseLinks = allowLooseLinks; - this.diagramEngine = diagramEngine; this.moved = false; } @@ -38,7 +36,7 @@ export class MoveItemsAction extends BaseMouseAction { if (model.model instanceof NodeModel) { // update port coordinates as well _.forEach(model.model.getPorts(), port => { - const portCoords = this.diagramEngine.getPortCoords(port); + const portCoords = this.engine.getPortCoords(port); port.updateCoords(portCoords); }); } @@ -55,36 +53,42 @@ export class MoveItemsAction extends BaseMouseAction { } fireMouseUp(event: MouseEvent) { - const element = this.diagramEngine.getMouseElement(event); + const element = this.engine.getMouseElement(event); _.forEach(this.selectionModels, model => { //only care about points connecting to things if (!(model.model instanceof PointModel)) { return; } - if (element && element.model instanceof PortModel && !this.diagramEngine.isModelLocked(element.model)) { + if (element && element.model instanceof PortModel && !this.engine.isModelLocked(element.model)) { let link = model.model.getLink(); + + //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()) { - //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); + link.getLastPoint().setPosition(this.engine.getPortCenter(element.model)); targetPort.removeLink(link); newLink.removePointsBefore(newLink.getPoints()[link.getPointIndex(model.model)]); link.removePointsAfter(model.model); - this.diagramEngine.getDiagramModel().addLink(newLink); + this.engine.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.diagramEngine.linksThatHaveInitiallyRendered[link.getID()]; + + // set the target port + else { + link.setTargetPort(element.model); + link.getLastPoint().setPosition(this.engine.getPortCenter(element.model)); + } + delete this.engine.linksThatHaveInitiallyRendered[link.getID()]; } }); @@ -130,15 +134,21 @@ export class MoveItemsAction extends BaseMouseAction { } }); - this.diagramEngine.clearRepaintEntities(); + this.engine.clearRepaintEntities(); } fireMouseDown(event: React.MouseEvent) { - const selectedElement = this.diagramEngine.getMouseElement(event); + const selectedElement = this.engine.getMouseElement(event); + + // clear selection first? + if (!selectedElement.model.isSelected()) { + this.model.clearSelection(); + } + if (selectedElement.model instanceof PortModel) { //its a port element, we want to drag a link - if (!this.diagramEngine.isModelLocked(selectedElement.model)) { - const portCenter = this.diagramEngine.getPortCenter(selectedElement.model); + if (!this.engine.isModelLocked(selectedElement.model)) { + const portCenter = this.engine.getPortCenter(selectedElement.model); const sourcePort = selectedElement.model; const link = sourcePort.createLinkModel(); link.setSourcePort(sourcePort); @@ -165,7 +175,7 @@ export class MoveItemsAction extends BaseMouseAction { if (!(item instanceof BasePositionModel)) { return false; } - return !this.diagramEngine.isModelLocked(item); + return !this.engine.isModelLocked(item); }); this.selectionModels = selectedItems.map((item: PointModel | NodeModel) => { @@ -175,6 +185,6 @@ export class MoveItemsAction extends BaseMouseAction { initialY: item.getY() }; }); - this.diagramEngine.enableRepaintEntities(this.model.getSelectedItems()); + this.engine.enableRepaintEntities(this.model.getSelectedItems()); } } diff --git a/lib-core/src/actions/move-items/MoveItemsActionFactory.ts b/lib-core/src/actions/move-items/MoveItemsActionFactory.ts new file mode 100644 index 0000000..f3b72b8 --- /dev/null +++ b/lib-core/src/actions/move-items/MoveItemsActionFactory.ts @@ -0,0 +1,32 @@ +import { AbstractActionFactory, ActionFactoryActivationEvent } from '../../core-actions/AbstractActionFactory'; +import { MouseEvent } from 'react'; +import { MoveItemsAction } from './MoveItemsAction'; + +export interface MoveItemsActionFactoryOptions { + allowLooseLinks?: boolean; +} + +export class MoveItemsActionFactory extends AbstractActionFactory { + options: MoveItemsActionFactoryOptions; + + static NAME = 'move-items'; + + constructor(options: MoveItemsActionFactoryOptions = {}) { + super(MoveItemsActionFactory.NAME); + this.options = { + ...options, + allowLooseLinks: options.allowLooseLinks == null ? true : options.allowLooseLinks + }; + } + + generateAction(event: MouseEvent): MoveItemsAction { + return new MoveItemsAction(event.clientX, event.clientY, this.engine, this.options.allowLooseLinks); + } + + activate(event: ActionFactoryActivationEvent): boolean { + if (event.selectedModel) { + return !this.engine.isModelLocked(event.selectedModel); + } + return false; + } +} diff --git a/lib-core/src/actions/SelectingAction.ts b/lib-core/src/actions/selecting-items/SelectingAction.ts similarity index 85% rename from lib-core/src/actions/SelectingAction.ts rename to lib-core/src/actions/selecting-items/SelectingAction.ts index bfb96e7..5015e60 100644 --- a/lib-core/src/actions/SelectingAction.ts +++ b/lib-core/src/actions/selecting-items/SelectingAction.ts @@ -1,17 +1,15 @@ -import { BaseMouseAction } from './BaseMouseAction'; -import { DiagramModel } from '../models/DiagramModel'; +import { AbstractMouseAction } from '../../core-actions/AbstractMouseAction'; +import { DiagramModel } from '../../models/DiagramModel'; import * as _ from 'lodash'; -import { DiagramEngine } from '../DiagramEngine'; +import { DiagramEngine } from '../../DiagramEngine'; import { MouseEvent } from 'react'; -export class SelectingAction extends BaseMouseAction { +export class SelectingAction extends AbstractMouseAction { mouseX2: number; mouseY2: number; - engine: DiagramEngine; constructor(mouseX: number, mouseY: number, engine: DiagramEngine) { - super(mouseX, mouseY, engine.getDiagramModel()); - this.engine = engine; + super(mouseX, mouseY, engine); this.mouseX2 = mouseX; this.mouseY2 = mouseY; } diff --git a/lib-core/src/actions/selecting-items/SelectingItemsActionFactory.ts b/lib-core/src/actions/selecting-items/SelectingItemsActionFactory.ts new file mode 100644 index 0000000..2f0c158 --- /dev/null +++ b/lib-core/src/actions/selecting-items/SelectingItemsActionFactory.ts @@ -0,0 +1,17 @@ +import { AbstractActionFactory, ActionFactoryActivationEvent } from '../../core-actions/AbstractActionFactory'; +import { MouseEvent } from 'react'; +import { SelectingAction } from './SelectingAction'; + +export class SelectingItemsActionFactory extends AbstractActionFactory { + constructor() { + super('select-items'); + } + + generateAction(event: MouseEvent): SelectingAction { + return new SelectingAction(event.clientX, event.clientY, this.engine); + } + + activate(event: ActionFactoryActivationEvent): boolean { + return !event.selectedEntity && event.mouseEvent.shiftKey; + } +} diff --git a/lib-core/src/core-actions/AbstractAction.ts b/lib-core/src/core-actions/AbstractAction.ts new file mode 100644 index 0000000..43e798b --- /dev/null +++ b/lib-core/src/core-actions/AbstractAction.ts @@ -0,0 +1,12 @@ +import { DiagramEngine } from '../DiagramEngine'; +import { DiagramModel } from '../models/DiagramModel'; + +export class AbstractAction { + engine: DiagramEngine; + model: DiagramModel; + + constructor(engine: DiagramEngine) { + this.engine = engine; + this.model = engine.getDiagramModel(); + } +} diff --git a/lib-core/src/core-actions/AbstractActionFactory.ts b/lib-core/src/core-actions/AbstractActionFactory.ts new file mode 100644 index 0000000..d412144 --- /dev/null +++ b/lib-core/src/core-actions/AbstractActionFactory.ts @@ -0,0 +1,16 @@ +import { AbstractAction } from './AbstractAction'; +import { AbstractFactory } from '../core/AbstractFactory'; +import { MouseEvent } from 'react'; +import { BaseModel } from '../core-models/BaseModel'; + +export interface ActionFactoryActivationEvent { + selectedModel: BaseModel; + selectedEntity: HTMLElement; + mouseEvent: MouseEvent; +} + +export abstract class AbstractActionFactory extends AbstractFactory { + abstract activate(event: ActionFactoryActivationEvent): boolean; + + abstract generateAction(event: MouseEvent): T; +} diff --git a/lib-core/src/core-actions/AbstractMouseAction.ts b/lib-core/src/core-actions/AbstractMouseAction.ts new file mode 100644 index 0000000..1eadb92 --- /dev/null +++ b/lib-core/src/core-actions/AbstractMouseAction.ts @@ -0,0 +1,20 @@ +import { MouseEvent } from 'react'; +import { AbstractAction } from './AbstractAction'; +import { DiagramEngine } from '../DiagramEngine'; + +export abstract class AbstractMouseAction extends AbstractAction { + protected mouseX: number; + protected mouseY: number; + + constructor(mouseX: number, mouseY: number, engine: DiagramEngine) { + super(engine); + this.mouseX = mouseX; + this.mouseY = mouseY; + } + + abstract fireMouseDown(event: MouseEvent); + + abstract fireMouseMove(event: MouseEvent); + + abstract fireMouseUp(event: MouseEvent); +} diff --git a/lib-core/src/core-models/BaseEntity.ts b/lib-core/src/core-models/BaseEntity.ts index 7a58525..dacc0c2 100644 --- a/lib-core/src/core-models/BaseEntity.ts +++ b/lib-core/src/core-models/BaseEntity.ts @@ -79,7 +79,7 @@ export class BaseEntity exten }; } - fireEvent(event: Partial & object, k: keyof T['LISTENER']) { + fireEvent & object>(event: L, k: keyof T['LISTENER']) { super.fireEvent( { entity: this, diff --git a/lib-core/src/core/AbstractFactory.ts b/lib-core/src/core/AbstractFactory.ts index baaba69..020c9de 100644 --- a/lib-core/src/core/AbstractFactory.ts +++ b/lib-core/src/core/AbstractFactory.ts @@ -1,15 +1,10 @@ -import { BaseModel } from '../core-models/BaseModel'; import { DiagramEngine } from '../DiagramEngine'; -export interface GenerateModelEvent { - initialConfig?: any; -} - /** * Base factory for all the different types of entities. * Gets registered with the engine, and is used to generate models */ -export abstract class AbstractFactory { +export abstract class AbstractFactory { /** * Couples the factory with the models it generates */ @@ -30,9 +25,4 @@ export abstract class AbstractFactory { getType(): string { return this.type; } - - /** - * Generates new models (the core factory pattern) - */ - abstract generateModel(event: GenerateModelEvent): T; } diff --git a/lib-core/src/core/AbstractModelFactory.ts b/lib-core/src/core/AbstractModelFactory.ts new file mode 100644 index 0000000..a1bdbaa --- /dev/null +++ b/lib-core/src/core/AbstractModelFactory.ts @@ -0,0 +1,13 @@ +import { AbstractFactory } from './AbstractFactory'; +import { BaseModel } from '../core-models/BaseModel'; + +export interface GenerateModelEvent { + initialConfig?: any; +} + +export abstract class AbstractModelFactory extends AbstractFactory { + /** + * Generates new models (the core factory pattern) + */ + abstract generateModel(event: GenerateModelEvent): T; +} diff --git a/lib-core/src/core/AbstractReactFactory.tsx b/lib-core/src/core/AbstractReactFactory.tsx index 92afbcd..8469035 100644 --- a/lib-core/src/core/AbstractReactFactory.tsx +++ b/lib-core/src/core/AbstractReactFactory.tsx @@ -1,5 +1,5 @@ -import { AbstractFactory } from './AbstractFactory'; import { BaseModel } from '../core-models/BaseModel'; +import { AbstractModelFactory } from './AbstractModelFactory'; export interface GenerateWidgetEvent { model: T; @@ -8,7 +8,7 @@ export interface GenerateWidgetEvent { /** * Further extends the AbstractFactory to add widget generation capability. */ -export abstract class AbstractReactFactory extends AbstractFactory { +export abstract class AbstractReactFactory extends AbstractModelFactory { /** * Generates React widgets from the model contained in the event object */ diff --git a/lib-core/src/core/FactoryBank.ts b/lib-core/src/core/FactoryBank.ts index 4297bb8..c6af905 100644 --- a/lib-core/src/core/FactoryBank.ts +++ b/lib-core/src/core/FactoryBank.ts @@ -1,5 +1,6 @@ import { BaseEvent, BaseListener, BaseObserver } from './BaseObserver'; import { AbstractFactory } from './AbstractFactory'; +import * as _ from 'lodash'; export interface FactoryBankListener extends BaseListener { /** @@ -24,6 +25,10 @@ export class FactoryBank extends Ba this.factories = {}; } + getFactories(): F[] { + return _.values(this.factories); + } + clearFactories() { for (let factory in this.factories) { this.deregisterFactory(factory); diff --git a/lib-core/src/widgets/DiagramWidget.tsx b/lib-core/src/widgets/DiagramWidget.tsx index 68a9874..9bdee37 100644 --- a/lib-core/src/widgets/DiagramWidget.tsx +++ b/lib-core/src/widgets/DiagramWidget.tsx @@ -3,68 +3,46 @@ import { DiagramEngine } from '../DiagramEngine'; import * as _ from 'lodash'; import { LinkLayerWidget } from './layers/LinkLayerWidget'; import { NodeLayerWidget } from './layers/NodeLayerWidget'; -import { BaseMouseAction } from '../actions/BaseMouseAction'; -import { MoveCanvasAction } from '../actions/MoveCanvasAction'; -import { MoveItemsAction } from '../actions/MoveItemsAction'; -import { SelectingAction } from '../actions/SelectingAction'; +import { AbstractMouseAction } from '../core-actions/AbstractMouseAction'; +import { MoveItemsAction } from '../actions/move-items/MoveItemsAction'; +import { SelectingAction } from '../actions/selecting-items/SelectingAction'; import { PointModel } from '../models/PointModel'; -import { PortModel } from '../models/PortModel'; import { BaseWidget, BaseWidgetProps } from './BaseWidget'; import { MouseEvent } from 'react'; +import { ActionFactoryActivationEvent } from '../core-actions/AbstractActionFactory'; +import { AbstractAction } from '../core-actions/AbstractAction'; +import { MoveItemsActionFactory } from '../actions/move-items/MoveItemsActionFactory'; export interface DiagramProps extends BaseWidgetProps { diagramEngine: DiagramEngine; - allowLooseLinks?: boolean; - allowCanvasTranslation?: boolean; + // zoom allowCanvasZoom?: boolean; inverseZoom?: boolean; - maxNumberPointsPerLink?: number; - smartRouting?: boolean; - actionStartedFiring?: (action: BaseMouseAction) => boolean; - actionStillFiring?: (action: BaseMouseAction) => void; - actionStoppedFiring?: (action: BaseMouseAction) => void; + actionStartedFiring?: (action: AbstractAction) => boolean; + actionStillFiring?: (action: AbstractAction) => void; + actionStoppedFiring?: (action: AbstractAction) => void; deleteKeys?: number[]; } export interface DiagramState { - action: BaseMouseAction | null; - windowListener: any; + action: AbstractAction; diagramEngineListener: any; } -/** - * @author Dylan Vorster - */ export class DiagramWidget extends BaseWidget { - 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; ref: React.RefObject; constructor(props: DiagramProps) { super('srd-diagram', props); - this.onMouseMove = this.onMouseMove.bind(this); - this.onMouseUp = this.onMouseUp.bind(this); + this.ref = React.createRef(); this.state = { action: null, - wasMoved: false, - renderedNodes: false, - windowListener: null, - diagramEngineListener: null, - document: null + diagramEngineListener: null }; } @@ -132,7 +110,7 @@ export class DiagramWidget extends BaseWidget { this.setState({ action: null }); } - startFiringAction(action: BaseMouseAction) { + startFiringAction(action: AbstractAction) { var setState = true; if (this.props.actionStartedFiring) { setState = this.props.actionStartedFiring(action); @@ -142,18 +120,30 @@ export class DiagramWidget extends BaseWidget { } } - onMouseMove(event) { + onMouseUp = event => { + if (this.state.action && this.state.action instanceof AbstractMouseAction) { + this.state.action.fireMouseUp(event); + } + this.props.diagramEngine.clearRepaintEntities(); + this.stopFiringAction(); + document.removeEventListener('mousemove', this.onMouseMove); + document.removeEventListener('mouseup', this.onMouseUp); + }; + + onMouseMove = event => { //select items so draw a bounding box if (this.state.action) { - this.state.action.fireMouseMove(event); + if (this.state.action && this.state.action instanceof AbstractMouseAction) { + this.state.action.fireMouseMove(event); + } this.fireAction(); this.forceUpdate(); } - } + }; - onKeyUp(event) { + onKeyUp = event => { //delete all selected - if (this.props.deleteKeys.indexOf(event.keyCode) !== -1) { + if ((this.props.deleteKeys || [46, 8]).indexOf(event.keyCode) !== -1) { _.forEach(this.props.diagramEngine.getDiagramModel().getSelectedItems(), element => { //only delete items which are not locked if (!this.props.diagramEngine.isModelLocked(element)) { @@ -162,17 +152,7 @@ export class DiagramWidget extends BaseWidget { }); this.forceUpdate(); } - } - - onMouseUp(event) { - if (this.state.action) { - this.state.action.fireMouseUp(event); - } - this.props.diagramEngine.clearRepaintEntities(); - this.stopFiringAction(); - document.removeEventListener('mousemove', this.onMouseMove); - document.removeEventListener('mouseup', this.onMouseUp); - } + }; drawSelectionBox() { let dimensions = (this.state.action as SelectingAction).getBoxDimensions(); @@ -189,41 +169,29 @@ export class DiagramWidget extends BaseWidget { ); } - getActionForEvent(event: MouseEvent): BaseMouseAction { + getActionForEvent(event: MouseEvent): AbstractAction { + event.persist(); this.props.diagramEngine.clearRepaintEntities(); const { diagramEngine } = this.props; const model = diagramEngine.getMouseElement(event); - const relative = diagramEngine.getRelativePoint(event.clientX, event.clientY); - // the canvas was selected - if (model === null) { - // is it a multiple selection - if (event.shiftKey) { - return new SelectingAction(relative.x, relative.y, diagramEngine); - } else { - // its a drag the canvas event - return new MoveCanvasAction(event.clientX, event.clientY, diagramEngine.getDiagramModel()); + const activateEvent: ActionFactoryActivationEvent = { + selectedModel: model && model.model, + selectedEntity: model && (model.element as HTMLElement), + mouseEvent: event + }; + + for (let factory of diagramEngine.getActionFactories().getFactories()) { + if (factory.activate(activateEvent)) { + return factory.generateAction(event); } } - // its a port element, we want to drag a link - else if (model.model instanceof PortModel) { - if (!this.props.diagramEngine.isModelLocked(model.model)) { - return new MoveItemsAction(event.clientX, event.clientY, diagramEngine, this.props.allowLooseLinks); - } else { - diagramEngine.getDiagramModel().clearSelection(); - } - } - // its some or other element, probably want to move it - if (!event.shiftKey && !model.model.isSelected()) { - diagramEngine.getDiagramModel().clearSelection(); - } - return new MoveItemsAction(event.clientX, event.clientY, diagramEngine, this.props.allowLooseLinks); + return null; } render() { - var diagramEngine = this.props.diagramEngine; - diagramEngine.setMaxNumberPointsPerLink(this.props.maxNumberPointsPerLink); - var diagramModel = diagramEngine.getDiagramModel(); + const diagramEngine = this.props.diagramEngine; + const diagramModel = diagramEngine.getDiagramModel(); return (
{ // try and get an action for this event const action = this.getActionForEvent(event); if (action) { - action.fireMouseDown(event); + if (action instanceof AbstractMouseAction) { + action.fireMouseDown(event); + } this.startFiringAction(action); } document.addEventListener('mousemove', this.onMouseMove); @@ -293,9 +263,18 @@ export class DiagramWidget extends BaseWidget { document.addEventListener('mouseup', this.onMouseUp); event.stopPropagation(); diagramModel.clearSelection(point); - const action = new MoveItemsAction(event.clientX, event.clientY, diagramEngine, this.props.allowLooseLinks); - action.fireMouseDown(event); - this.startFiringAction(action); + + // TODO implement this better and more generic + let action: MoveItemsAction = null; + let fac: MoveItemsActionFactory = null; + try { + fac = diagramEngine.getActionFactories().getFactory(MoveItemsActionFactory.NAME); + } catch (e) {} + if (fac) { + action = fac.generateAction(event); + action.fireMouseDown(event); + this.startFiringAction(action); + } }} /> diff --git a/lib-core/src/widgets/LinkWidget.tsx b/lib-core/src/widgets/LinkWidget.tsx index 6cc4a98..25bfcfe 100644 --- a/lib-core/src/widgets/LinkWidget.tsx +++ b/lib-core/src/widgets/LinkWidget.tsx @@ -6,6 +6,7 @@ import { BaseEntityEvent } from '../core-models/BaseEntity'; import { BasePositionModel } from '../core-models/BasePositionModel'; import { PointModel } from '../models/PointModel'; import { PortModel } from '../models/PortModel'; +import { MouseEvent } from 'react'; export interface LinkProps { link: LinkModel; diff --git a/lib-defaults/src/node/DefaultNodeModel.ts b/lib-defaults/src/node/DefaultNodeModel.ts index 581dd6b..5d4f484 100644 --- a/lib-defaults/src/node/DefaultNodeModel.ts +++ b/lib-defaults/src/node/DefaultNodeModel.ts @@ -47,7 +47,7 @@ export class DefaultNodeModel extends NodeModel(port: T): T { + addPort(port: T): T { super.addPort(port); if (port.getOptions().in) { if (this.portsIn.indexOf(port) === -1) { diff --git a/lib-defaults/src/port/DefaultPortFactory.tsx b/lib-defaults/src/port/DefaultPortFactory.tsx index 46f0e48..1fc8e89 100644 --- a/lib-defaults/src/port/DefaultPortFactory.tsx +++ b/lib-defaults/src/port/DefaultPortFactory.tsx @@ -1,7 +1,7 @@ -import { AbstractFactory } from '@projectstorm/react-diagrams-core'; +import { AbstractModelFactory } from '@projectstorm/react-diagrams-core'; import { DefaultPortModel } from './DefaultPortModel'; -export class DefaultPortFactory extends AbstractFactory { +export class DefaultPortFactory extends AbstractModelFactory { constructor() { super('default'); } diff --git a/lib-defaults/src/port/DefaultPortModel.ts b/lib-defaults/src/port/DefaultPortModel.ts index 84be080..70b6306 100644 --- a/lib-defaults/src/port/DefaultPortModel.ts +++ b/lib-defaults/src/port/DefaultPortModel.ts @@ -1,12 +1,12 @@ import * as _ from 'lodash'; import { - AbstractFactory, DiagramEngine, LinkModel, PortModel, PortModelAlignment, PortModelGenerics, - PortModelOptions + PortModelOptions, + AbstractModelFactory } from '@projectstorm/react-diagrams-core'; import { DefaultLinkModel } from '../link/DefaultLinkModel'; import { DefaultNodeModel } from '../node/DefaultNodeModel'; @@ -21,7 +21,7 @@ export interface DefaultPortModelGenerics { PARENT: DefaultNodeModel; } -export class DefaultPortModel extends PortModel { +export class DefaultPortModel extends PortModel { constructor(isIn: boolean, name?: string, label?: string); constructor(options: DefaultPortModelOptions); constructor(options: DefaultPortModelOptions | boolean, name?: string, label?: string) { @@ -54,7 +54,7 @@ export class DefaultPortModel extends PortModel): LinkModel { + link(port: PortModel, factory?: AbstractModelFactory): LinkModel { let link = this.createLinkModel(factory); link.setSourcePort(this); link.setTargetPort(port); @@ -68,7 +68,7 @@ export class DefaultPortModel extends PortModel): LinkModel { + createLinkModel(factory?: AbstractModelFactory): LinkModel { let link = super.createLinkModel(); if (!link && factory) { return factory.generateModel({});