mirror of
https://github.com/projectstorm/react-diagrams.git
synced 2026-03-13 09:50:09 +08:00
make all the things pluggable
This commit is contained in:
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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<DiagramEngineListener> {
|
||||
protected nodeFactories: FactoryBank<AbstractReactFactory<NodeModel>>;
|
||||
protected linkFactories: FactoryBank<AbstractReactFactory<LinkModel>>;
|
||||
protected portFactories: FactoryBank<AbstractFactory<PortModel>>;
|
||||
protected portFactories: FactoryBank<AbstractModelFactory<PortModel>>;
|
||||
protected labelFactories: FactoryBank<AbstractReactFactory<LabelModel>>;
|
||||
protected actionFactories: FactoryBank<AbstractActionFactory>;
|
||||
|
||||
diagramModel: DiagramModel;
|
||||
canvas: Element;
|
||||
@@ -47,6 +49,7 @@ export class DiagramEngine extends BaseObserver<DiagramEngineListener> {
|
||||
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<DiagramEngineListener> {
|
||||
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<DiagramEngineListener> {
|
||||
|
||||
//!-------------- 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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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) {
|
||||
17
lib-core/src/actions/move-canvas/MoveCanvasActionFactory.ts
Normal file
17
lib-core/src/actions/move-canvas/MoveCanvasActionFactory.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { AbstractActionFactory, ActionFactoryActivationEvent } from '../../core-actions/AbstractActionFactory';
|
||||
import { MoveCanvasAction } from './MoveCanvasAction';
|
||||
import { MouseEvent } from 'react';
|
||||
|
||||
export class MoveCanvasActionFactory extends AbstractActionFactory<MoveCanvasAction> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
32
lib-core/src/actions/move-items/MoveItemsActionFactory.ts
Normal file
32
lib-core/src/actions/move-items/MoveItemsActionFactory.ts
Normal file
@@ -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<MoveItemsAction> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { AbstractActionFactory, ActionFactoryActivationEvent } from '../../core-actions/AbstractActionFactory';
|
||||
import { MouseEvent } from 'react';
|
||||
import { SelectingAction } from './SelectingAction';
|
||||
|
||||
export class SelectingItemsActionFactory extends AbstractActionFactory<SelectingAction> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
12
lib-core/src/core-actions/AbstractAction.ts
Normal file
12
lib-core/src/core-actions/AbstractAction.ts
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
16
lib-core/src/core-actions/AbstractActionFactory.ts
Normal file
16
lib-core/src/core-actions/AbstractActionFactory.ts
Normal file
@@ -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<T extends AbstractAction = AbstractAction> extends AbstractFactory {
|
||||
abstract activate(event: ActionFactoryActivationEvent): boolean;
|
||||
|
||||
abstract generateAction(event: MouseEvent): T;
|
||||
}
|
||||
20
lib-core/src/core-actions/AbstractMouseAction.ts
Normal file
20
lib-core/src/core-actions/AbstractMouseAction.ts
Normal file
@@ -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);
|
||||
}
|
||||
@@ -79,7 +79,7 @@ export class BaseEntity<T extends BaseEntityGenerics = BaseEntityGenerics> exten
|
||||
};
|
||||
}
|
||||
|
||||
fireEvent(event: Partial<BaseEntityEvent> & object, k: keyof T['LISTENER']) {
|
||||
fireEvent<L extends Partial<BaseEntityEvent> & object>(event: L, k: keyof T['LISTENER']) {
|
||||
super.fireEvent(
|
||||
{
|
||||
entity: this,
|
||||
|
||||
@@ -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<T extends BaseModel = BaseModel> {
|
||||
export abstract class AbstractFactory<T = any> {
|
||||
/**
|
||||
* Couples the factory with the models it generates
|
||||
*/
|
||||
@@ -30,9 +25,4 @@ export abstract class AbstractFactory<T extends BaseModel = BaseModel> {
|
||||
getType(): string {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates new models (the core factory pattern)
|
||||
*/
|
||||
abstract generateModel(event: GenerateModelEvent): T;
|
||||
}
|
||||
|
||||
13
lib-core/src/core/AbstractModelFactory.ts
Normal file
13
lib-core/src/core/AbstractModelFactory.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { AbstractFactory } from './AbstractFactory';
|
||||
import { BaseModel } from '../core-models/BaseModel';
|
||||
|
||||
export interface GenerateModelEvent {
|
||||
initialConfig?: any;
|
||||
}
|
||||
|
||||
export abstract class AbstractModelFactory<T extends BaseModel> extends AbstractFactory<T> {
|
||||
/**
|
||||
* Generates new models (the core factory pattern)
|
||||
*/
|
||||
abstract generateModel(event: GenerateModelEvent): T;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AbstractFactory } from './AbstractFactory';
|
||||
import { BaseModel } from '../core-models/BaseModel';
|
||||
import { AbstractModelFactory } from './AbstractModelFactory';
|
||||
|
||||
export interface GenerateWidgetEvent<T extends BaseModel> {
|
||||
model: T;
|
||||
@@ -8,7 +8,7 @@ export interface GenerateWidgetEvent<T extends BaseModel> {
|
||||
/**
|
||||
* Further extends the AbstractFactory to add widget generation capability.
|
||||
*/
|
||||
export abstract class AbstractReactFactory<T extends BaseModel> extends AbstractFactory<T> {
|
||||
export abstract class AbstractReactFactory<T extends BaseModel> extends AbstractModelFactory<T> {
|
||||
/**
|
||||
* Generates React widgets from the model contained in the event object
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { BaseEvent, BaseListener, BaseObserver } from './BaseObserver';
|
||||
import { AbstractFactory } from './AbstractFactory';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
export interface FactoryBankListener<F extends AbstractFactory> extends BaseListener {
|
||||
/**
|
||||
@@ -24,6 +25,10 @@ export class FactoryBank<F extends AbstractFactory = AbstractFactory> extends Ba
|
||||
this.factories = {};
|
||||
}
|
||||
|
||||
getFactories(): F[] {
|
||||
return _.values(this.factories);
|
||||
}
|
||||
|
||||
clearFactories() {
|
||||
for (let factory in this.factories) {
|
||||
this.deregisterFactory(factory);
|
||||
|
||||
@@ -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<DiagramProps, DiagramState> {
|
||||
public static defaultProps: DiagramProps = {
|
||||
diagramEngine: null,
|
||||
allowLooseLinks: true,
|
||||
allowCanvasTranslation: true,
|
||||
allowCanvasZoom: true,
|
||||
inverseZoom: false,
|
||||
maxNumberPointsPerLink: Infinity, // backwards compatible default
|
||||
smartRouting: false,
|
||||
deleteKeys: [46, 8]
|
||||
};
|
||||
|
||||
onKeyUpPointer: (this: Window, ev: KeyboardEvent) => void = null;
|
||||
ref: React.RefObject<HTMLDivElement>;
|
||||
|
||||
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<DiagramProps, DiagramState> {
|
||||
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<DiagramProps, DiagramState> {
|
||||
}
|
||||
}
|
||||
|
||||
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<DiagramProps, DiagramState> {
|
||||
});
|
||||
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<DiagramProps, DiagramState> {
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<div
|
||||
@@ -280,7 +248,9 @@ export class DiagramWidget extends BaseWidget<DiagramProps, DiagramState> {
|
||||
// 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<DiagramProps, DiagramState> {
|
||||
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>(MoveItemsActionFactory.NAME);
|
||||
} catch (e) {}
|
||||
if (fac) {
|
||||
action = fac.generateAction(event);
|
||||
action.fireMouseDown(event);
|
||||
this.startFiringAction(action);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<NodeLayerWidget diagramEngine={diagramEngine} />
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -47,7 +47,7 @@ export class DefaultNodeModel extends NodeModel<DefaultNodeModelGenerics & NodeM
|
||||
super.doClone(lookupTable, clone);
|
||||
}
|
||||
|
||||
removePort(port: (DefaultNodeModelGenerics & NodeModelGenerics)['PORT']): void {
|
||||
removePort(port: DefaultPortModel): void {
|
||||
super.removePort(port);
|
||||
if (port.getOptions().in) {
|
||||
this.portsIn.splice(this.portsIn.indexOf(port));
|
||||
@@ -56,7 +56,7 @@ export class DefaultNodeModel extends NodeModel<DefaultNodeModelGenerics & NodeM
|
||||
}
|
||||
}
|
||||
|
||||
addPort<T extends (DefaultNodeModelGenerics & NodeModelGenerics)['PORT']>(port: T): T {
|
||||
addPort<T extends DefaultPortModel>(port: T): T {
|
||||
super.addPort(port);
|
||||
if (port.getOptions().in) {
|
||||
if (this.portsIn.indexOf(port) === -1) {
|
||||
|
||||
@@ -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<DefaultPortModel> {
|
||||
export class DefaultPortFactory extends AbstractModelFactory<DefaultPortModel> {
|
||||
constructor() {
|
||||
super('default');
|
||||
}
|
||||
|
||||
@@ -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<PortModelGenerics & DefaultPortModelGenerics> {
|
||||
export class DefaultPortModel extends PortModel<DefaultPortModelGenerics & PortModelGenerics> {
|
||||
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<PortModelGenerics & DefaultPortM
|
||||
});
|
||||
}
|
||||
|
||||
link(port: PortModel, factory?: AbstractFactory<LinkModel>): LinkModel {
|
||||
link(port: PortModel, factory?: AbstractModelFactory<LinkModel>): LinkModel {
|
||||
let link = this.createLinkModel(factory);
|
||||
link.setSourcePort(this);
|
||||
link.setTargetPort(port);
|
||||
@@ -68,7 +68,7 @@ export class DefaultPortModel extends PortModel<PortModelGenerics & DefaultPortM
|
||||
return true;
|
||||
}
|
||||
|
||||
createLinkModel(factory?: AbstractFactory<LinkModel>): LinkModel {
|
||||
createLinkModel(factory?: AbstractModelFactory<LinkModel>): LinkModel {
|
||||
let link = super.createLinkModel();
|
||||
if (!link && factory) {
|
||||
return factory.generateModel({});
|
||||
|
||||
Reference in New Issue
Block a user