diagram-js
Version:
A toolbox for displaying and modifying diagrams on the web
741 lines (634 loc) • 16.9 kB
JavaScript
import {
assign,
forEach,
isArray
} from 'min-dash';
import AlignElementsHandler from './cmd/AlignElementsHandler';
import AppendShapeHandler from './cmd/AppendShapeHandler';
import CreateConnectionHandler from './cmd/CreateConnectionHandler';
import CreateElementsHandler from './cmd/CreateElementsHandler';
import CreateLabelHandler from './cmd/CreateLabelHandler';
import CreateShapeHandler from './cmd/CreateShapeHandler';
import DeleteConnectionHandler from './cmd/DeleteConnectionHandler';
import DeleteElementsHandler from './cmd/DeleteElementsHandler';
import DeleteShapeHandler from './cmd/DeleteShapeHandler';
import DistributeElementsHandler from './cmd/DistributeElementsHandler';
import LayoutConnectionHandler from './cmd/LayoutConnectionHandler';
import MoveConnectionHandler from './cmd/MoveConnectionHandler';
import MoveElementsHandler from './cmd/MoveElementsHandler';
import MoveShapeHandler from './cmd/MoveShapeHandler';
import ReconnectConnectionHandler from './cmd/ReconnectConnectionHandler';
import ReplaceShapeHandler from './cmd/ReplaceShapeHandler';
import ResizeShapeHandler from './cmd/ResizeShapeHandler';
import SpaceToolHandler from './cmd/SpaceToolHandler';
import ToggleShapeCollapseHandler from './cmd/ToggleShapeCollapseHandler';
import UpdateAttachmentHandler from './cmd/UpdateAttachmentHandler';
import UpdateWaypointsHandler from './cmd/UpdateWaypointsHandler';
import { isModelElement } from '../../model';
/**
* @typedef {import('../../model/Types').Element} Element
* @typedef {import('../../model/Types').Connection} Connection
* @typedef {import('../../model/Types').Parent} Parent
* @typedef {import('../../model/Types').Shape} Shape
* @typedef {import('../../model/Types').Label} Label
*
* @typedef {import('../../command/CommandStack').default} CommandStack
* @typedef {import('../../core/ElementFactory').default} ElementFactory
* @typedef {import('../../core/EventBus').default} EventBus
*
* @typedef {import('../../command/CommandStack').CommandHandlerConstructor} CommandHandlerConstructor
*
* @typedef {import('../../util/Types').Dimensions} Dimensions
* @typedef {import('../../util/Types').Direction} Direction
* @typedef {import('../../util/Types').Point} Point
* @typedef {import('../../util/Types').Rect} Rect
*
* @typedef { 'x' | 'y' } ModelingDistributeAxis
*
* @typedef { 'width' | 'height' } ModelingDistributeDimension
*
* @typedef { {
* bottom?: number;
* center?: number;
* left?: number;
* middle?: number;
* right?: number;
* top?: number;
* } } ModelingAlignAlignment
*
* @typedef { {
* [key: string]: any;
* } } ModelingHints
*
* @typedef { {
* attach?: boolean;
* } & ModelingHints } ModelingMoveElementsHints
*
* @typedef { {
* attach?: boolean;
* } & ModelingHints } ModelingCreateShapeHints
*/
/**
* @template {Element} U
*
* @typedef { {
* elements: U[],
* range: {
* min: number;
* max: number;
* } }
* } ModelingDistributeGroup
*/
/**
* The basic modeling entry point.
*
* @template {Connection} [T=Connection]
* @template {Element} [U=Element]
* @template {Label} [V=Label]
* @template {Parent} [W=Parent]
* @template {Shape} [X=Shape]
*
* @param {EventBus} eventBus
* @param {ElementFactory} elementFactory
* @param {CommandStack} commandStack
*/
export default function Modeling(eventBus, elementFactory, commandStack) {
this._eventBus = eventBus;
this._elementFactory = elementFactory;
this._commandStack = commandStack;
var self = this;
eventBus.on('diagram.init', function() {
// register modeling handlers
self.registerHandlers(commandStack);
});
}
Modeling.$inject = [ 'eventBus', 'elementFactory', 'commandStack' ];
/**
* Get a map of all command handlers.
*
* @return {Map<string, CommandHandlerConstructor>}
*/
Modeling.prototype.getHandlers = function() {
return {
'shape.append': AppendShapeHandler,
'shape.create': CreateShapeHandler,
'shape.delete': DeleteShapeHandler,
'shape.move': MoveShapeHandler,
'shape.resize': ResizeShapeHandler,
'shape.replace': ReplaceShapeHandler,
'shape.toggleCollapse': ToggleShapeCollapseHandler,
'spaceTool': SpaceToolHandler,
'label.create': CreateLabelHandler,
'connection.create': CreateConnectionHandler,
'connection.delete': DeleteConnectionHandler,
'connection.move': MoveConnectionHandler,
'connection.layout': LayoutConnectionHandler,
'connection.updateWaypoints': UpdateWaypointsHandler,
'connection.reconnect': ReconnectConnectionHandler,
'elements.create': CreateElementsHandler,
'elements.move': MoveElementsHandler,
'elements.delete': DeleteElementsHandler,
'elements.distribute': DistributeElementsHandler,
'elements.align': AlignElementsHandler,
'element.updateAttachment': UpdateAttachmentHandler
};
};
/**
* Register handlers with the command stack
*
* @param {CommandStack} commandStack
*/
Modeling.prototype.registerHandlers = function(commandStack) {
forEach(this.getHandlers(), function(handler, id) {
commandStack.registerHandler(id, handler);
});
};
/**
* Move a shape by the given delta and optionally to a new parent.
*
* @param {X} shape
* @param {Point} delta
* @param {W} [newParent]
* @param {number} [newParentIndex]
* @param {ModelingHints} [hints]
*/
Modeling.prototype.moveShape = function(shape, delta, newParent, newParentIndex, hints) {
if (typeof newParentIndex === 'object') {
hints = newParentIndex;
newParentIndex = null;
}
var context = {
shape: shape,
delta: delta,
newParent: newParent,
newParentIndex: newParentIndex,
hints: hints || {}
};
this._commandStack.execute('shape.move', context);
};
/**
* Update the attachment of a shape.
*
* @param {X} shape
* @param {X} [newHost=undefined]
*/
Modeling.prototype.updateAttachment = function(shape, newHost) {
var context = {
shape: shape,
newHost: newHost
};
this._commandStack.execute('element.updateAttachment', context);
};
/**
* Move elements by a given delta and optionally to a new parent.
*
* @param {U[]} shapes
* @param {Point} delta
* @param {W} [target]
* @param {ModelingMoveElementsHints} [hints]
*/
Modeling.prototype.moveElements = function(shapes, delta, target, hints) {
hints = hints || {};
var attach = hints.attach;
var newParent = target,
newHost;
if (attach === true) {
newHost = target;
newParent = target.parent;
} else if (attach === false) {
newHost = null;
}
var context = {
shapes: shapes,
delta: delta,
newParent: newParent,
newHost: newHost,
hints: hints
};
this._commandStack.execute('elements.move', context);
};
/**
* Move a shape by the given delta and optionally to a new parent.
*
* @param {T} connection
* @param {Point} delta
* @param {W} [newParent]
* @param {number} [newParentIndex]
* @param {ModelingHints} [hints]
*/
Modeling.prototype.moveConnection = function(connection, delta, newParent, newParentIndex, hints) {
if (typeof newParentIndex === 'object') {
hints = newParentIndex;
newParentIndex = undefined;
}
var context = {
connection: connection,
delta: delta,
newParent: newParent,
newParentIndex: newParentIndex,
hints: hints || {}
};
this._commandStack.execute('connection.move', context);
};
/**
* Layout a connection.
*
* @param {T} connection
* @param {ModelingHints} [hints]
*/
Modeling.prototype.layoutConnection = function(connection, hints) {
var context = {
connection: connection,
hints: hints || {}
};
this._commandStack.execute('connection.layout', context);
};
/**
* Create a connection.
*
* @overlord
*
* @param {U} source
* @param {U} target
* @param {Partial<T>} connection
* @param {W} parent
* @param {ModelingHints} [hints]
*
* @return {T}
*/
/**
* Create a connection.
*
* @param {U} source
* @param {U} target
* @param {number} parentIndex
* @param {Partial<T>} connection
* @param {W} parent
* @param {ModelingHints} [hints]
*
* @return {T}
*/
Modeling.prototype.createConnection = function(source, target, parentIndex, connection, parent, hints) {
if (typeof parentIndex === 'object') {
hints = parent;
parent = connection;
connection = parentIndex;
parentIndex = undefined;
}
connection = this._create('connection', connection);
var context = {
source: source,
target: target,
parent: parent,
parentIndex: parentIndex,
connection: connection,
hints: hints
};
this._commandStack.execute('connection.create', context);
return context.connection;
};
/**
* Create a shape.
*
* @overlord
*
* @param {Partial<X>} shape
* @param {Point} position
* @param {W} target
* @param {ModelingCreateShapeHints} [hints]
*
* @return {X}
*/
/**
* Create a shape.
*
* @param {Partial<X>} shape
* @param {Point} position
* @param {W} target
* @param {number} parentIndex
* @param {ModelingCreateShapeHints} [hints]
*
* @return {X}
*/
Modeling.prototype.createShape = function(shape, position, target, parentIndex, hints) {
if (typeof parentIndex !== 'number') {
hints = parentIndex;
parentIndex = undefined;
}
hints = hints || {};
var attach = hints.attach,
parent,
host;
shape = this._create('shape', shape);
if (attach) {
parent = target.parent;
host = target;
} else {
parent = target;
}
var context = {
position: position,
shape: shape,
parent: parent,
parentIndex: parentIndex,
host: host,
hints: hints
};
this._commandStack.execute('shape.create', context);
return context.shape;
};
/**
* Create elements.
*
* @param {Partial<U>[]} elements
* @param {Point} position
* @param {W} parent
* @param {number} [parentIndex]
* @param {ModelingHints} [hints]
*
* @return {U[]}
*/
Modeling.prototype.createElements = function(elements, position, parent, parentIndex, hints) {
if (!isArray(elements)) {
elements = [ elements ];
}
if (typeof parentIndex !== 'number') {
hints = parentIndex;
parentIndex = undefined;
}
hints = hints || {};
var context = {
position: position,
elements: elements,
parent: parent,
parentIndex: parentIndex,
hints: hints
};
this._commandStack.execute('elements.create', context);
return context.elements;
};
/**
* Create a label.
*
* @param {U} labelTarget
* @param {Point} position
* @param {Partial<V>} label
* @param {W} [parent]
*
* @return {V}
*/
Modeling.prototype.createLabel = function(labelTarget, position, label, parent) {
label = this._create('label', label);
var context = {
labelTarget: labelTarget,
position: position,
parent: parent || labelTarget.parent,
shape: label
};
this._commandStack.execute('label.create', context);
return context.shape;
};
/**
* Create and connect a shape to a source.
*
* @param {U} source
* @param {Partial<X>} shape
* @param {Point} position
* @param {W} target
* @param {ModelingHints} [hints]
*
* @return {X}
*/
Modeling.prototype.appendShape = function(source, shape, position, target, hints) {
hints = hints || {};
shape = this._create('shape', shape);
var context = {
source: source,
position: position,
target: target,
shape: shape,
connection: hints.connection,
connectionParent: hints.connectionParent,
hints: hints
};
this._commandStack.execute('shape.append', context);
return context.shape;
};
/**
* Remove elements.
*
* @param {U[]} elements
*/
Modeling.prototype.removeElements = function(elements) {
var context = {
elements: elements
};
this._commandStack.execute('elements.delete', context);
};
/**
* Distribute elements along a given axis.
*
* @param {ModelingDistributeGroup<U>[]} groups
* @param {ModelingDistributeAxis} axis
* @param {ModelingDistributeDimension} dimension
*/
Modeling.prototype.distributeElements = function(groups, axis, dimension) {
var context = {
groups: groups,
axis: axis,
dimension: dimension
};
this._commandStack.execute('elements.distribute', context);
};
/**
* Remove a shape.
*
* @param {X} shape
* @param {ModelingHints} [hints]
*/
Modeling.prototype.removeShape = function(shape, hints) {
var context = {
shape: shape,
hints: hints || {}
};
this._commandStack.execute('shape.delete', context);
};
/**
* Remove a connection.
*
* @param {T} connection
* @param {ModelingHints} [hints]
*/
Modeling.prototype.removeConnection = function(connection, hints) {
var context = {
connection: connection,
hints: hints || {}
};
this._commandStack.execute('connection.delete', context);
};
/**
* Replace a shape.
*
* @param {X} oldShape
* @param {Partial<X>} newShape
* @param {ModelingHints} [hints]
*
* @return {X}
*/
Modeling.prototype.replaceShape = function(oldShape, newShape, hints) {
var context = {
oldShape: oldShape,
newData: newShape,
hints: hints || {}
};
this._commandStack.execute('shape.replace', context);
return context.newShape;
};
/**
* Align elements.
*
* @param {U[]} elements
* @param {ModelingAlignAlignment} alignment
*/
Modeling.prototype.alignElements = function(elements, alignment) {
var context = {
elements: elements,
alignment: alignment
};
this._commandStack.execute('elements.align', context);
};
/**
* Resize a shape.
*
* @param {X} shape
* @param {Rect} newBounds
* @param {Dimensions} [minBounds]
* @param {ModelingHints} [hints]
*/
Modeling.prototype.resizeShape = function(shape, newBounds, minBounds, hints) {
var context = {
shape: shape,
newBounds: newBounds,
minBounds: minBounds,
hints: hints
};
this._commandStack.execute('shape.resize', context);
};
/**
* Create space along an horizontally or vertically.
*
* @param {X[]} movingShapes
* @param {X[]} resizingShapes
* @param {Point} delta
* @param {Direction} direction
* @param {number} start
*/
Modeling.prototype.createSpace = function(movingShapes, resizingShapes, delta, direction, start) {
var context = {
delta: delta,
direction: direction,
movingShapes: movingShapes,
resizingShapes: resizingShapes,
start: start
};
this._commandStack.execute('spaceTool', context);
};
/**
* Update a connetions waypoints.
*
* @param {T} connection
* @param {Point[]} newWaypoints
* @param {ModelingHints} [hints]
*/
Modeling.prototype.updateWaypoints = function(connection, newWaypoints, hints) {
var context = {
connection: connection,
newWaypoints: newWaypoints,
hints: hints || {}
};
this._commandStack.execute('connection.updateWaypoints', context);
};
/**
* Reconnect a connections source and/or target.
*
* @param {T} connection
* @param {U} source
* @param {U} target
* @param {Point|Point[]} dockingOrPoints
* @param {ModelingHints} [hints]
*/
Modeling.prototype.reconnect = function(connection, source, target, dockingOrPoints, hints) {
var context = {
connection: connection,
newSource: source,
newTarget: target,
dockingOrPoints: dockingOrPoints,
hints: hints || {}
};
this._commandStack.execute('connection.reconnect', context);
};
/**
* Reconnect a connections source.
*
* @param {T} connection
* @param {U} newSource
* @param {Point|Point[]} dockingOrPoints
* @param {ModelingHints} [hints]
*/
Modeling.prototype.reconnectStart = function(connection, newSource, dockingOrPoints, hints) {
if (!hints) {
hints = {};
}
this.reconnect(connection, newSource, connection.target, dockingOrPoints, assign(hints, {
docking: 'source'
}));
};
/**
* Reconnect a connections target.
*
* @param {T} connection
* @param {U} newTarget
* @param {Point|Point[]} dockingOrPoints
* @param {ModelingHints} [hints]
*/
Modeling.prototype.reconnectEnd = function(connection, newTarget, dockingOrPoints, hints) {
if (!hints) {
hints = {};
}
this.reconnect(connection, connection.source, newTarget, dockingOrPoints, assign(hints, {
docking: 'target'
}));
};
/**
* Connect two elements.
*
* @param {U} source
* @param {U} target
* @param {Partial<T>} [attrs]
* @param {ModelingHints} [hints]
*
* @return {T}
*/
Modeling.prototype.connect = function(source, target, attrs, hints) {
return this.createConnection(source, target, attrs || {}, source.parent, hints);
};
Modeling.prototype._create = function(type, attrs) {
if (isModelElement(attrs)) {
return attrs;
} else {
return this._elementFactory.create(type, attrs);
}
};
/**
* Collapse or expand a shape.
*
* @param {X} shape
* @param {ModelingHints} [hints]
*/
Modeling.prototype.toggleCollapse = function(shape, hints) {
var context = {
shape: shape,
hints: hints || {}
};
this._commandStack.execute('shape.toggleCollapse', context);
};