| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623 |
- import {
- assign,
- find,
- forEach,
- isArray,
- isNumber,
- map,
- matchPattern,
- omit,
- sortBy
- } from 'min-dash';
- import {
- getBBox,
- getParents
- } from '../../util/Elements';
- import { eachElement } from '../../util/Elements';
- /**
- * @typedef {Function} <copyPaste.canCopyElements> listener
- *
- * @param {Object} context
- * @param {Array<djs.model.Base>} context.elements
- *
- * @returns {Array<djs.model.Base>|boolean} - Return elements to be copied or false to disallow
- * copying.
- */
- /**
- * @typedef {Function} <copyPaste.copyElement> listener
- *
- * @param {Object} context
- * @param {Object} context.descriptor
- * @param {djs.model.Base} context.element
- * @param {Array<djs.model.Base>} context.elements
- */
- /**
- * @typedef {Function} <copyPaste.createTree> listener
- *
- * @param {Object} context
- * @param {djs.model.Base} context.element
- * @param {Array<djs.model.Base>} context.children - Add children to be added to tree.
- */
- /**
- * @typedef {Function} <copyPaste.elementsCopied> listener
- *
- * @param {Object} context
- * @param {Object} context.elements
- * @param {Object} context.tree
- */
- /**
- * @typedef {Function} <copyPaste.pasteElement> listener
- *
- * @param {Object} context
- * @param {Object} context.cache - Already created elements.
- * @param {Object} context.descriptor
- */
- /**
- * @typedef {Function} <copyPaste.pasteElements> listener
- *
- * @param {Object} context
- * @param {Object} context.hints - Add hints before pasting.
- */
- /**
- * Copy and paste elements.
- *
- * @param {Canvas} canvas
- * @param {Create} create
- * @param {Clipboard} clipboard
- * @param {ElementFactory} elementFactory
- * @param {EventBus} eventBus
- * @param {Modeling} modeling
- * @param {Mouse} mouse
- * @param {Rules} rules
- */
- export default function CopyPaste(
- canvas,
- create,
- clipboard,
- elementFactory,
- eventBus,
- modeling,
- mouse,
- rules
- ) {
- this._canvas = canvas;
- this._create = create;
- this._clipboard = clipboard;
- this._elementFactory = elementFactory;
- this._eventBus = eventBus;
- this._modeling = modeling;
- this._mouse = mouse;
- this._rules = rules;
- eventBus.on('copyPaste.copyElement', function(context) {
- var descriptor = context.descriptor,
- element = context.element,
- elements = context.elements;
- // default priority (priority = 1)
- descriptor.priority = 1;
- descriptor.id = element.id;
- var parentCopied = find(elements, function(e) {
- return e === element.parent;
- });
- // do NOT reference parent if parent wasn't copied
- if (parentCopied) {
- descriptor.parent = element.parent.id;
- }
- // attachers (priority = 2)
- if (isAttacher(element)) {
- descriptor.priority = 2;
- descriptor.host = element.host.id;
- }
- // connections (priority = 3)
- if (isConnection(element)) {
- descriptor.priority = 3;
- descriptor.source = element.source.id;
- descriptor.target = element.target.id;
- descriptor.waypoints = copyWaypoints(element);
- }
- // labels (priority = 4)
- if (isLabel(element)) {
- descriptor.priority = 4;
- descriptor.labelTarget = element.labelTarget.id;
- }
- forEach([ 'x', 'y', 'width', 'height' ], function(property) {
- if (isNumber(element[ property ])) {
- descriptor[ property ] = element[ property ];
- }
- });
- descriptor.hidden = element.hidden;
- descriptor.collapsed = element.collapsed;
- });
- eventBus.on('copyPaste.pasteElements', function(context) {
- var hints = context.hints;
- assign(hints, {
- createElementsBehavior: false
- });
- });
- }
- CopyPaste.$inject = [
- 'canvas',
- 'create',
- 'clipboard',
- 'elementFactory',
- 'eventBus',
- 'modeling',
- 'mouse',
- 'rules'
- ];
- /**
- * Copy elements.
- *
- * @param {Array<djs.model.Base>} elements
- *
- * @returns {Object}
- */
- CopyPaste.prototype.copy = function(elements) {
- var allowed,
- tree;
- if (!isArray(elements)) {
- elements = elements ? [ elements ] : [];
- }
- allowed = this._eventBus.fire('copyPaste.canCopyElements', {
- elements: elements
- });
- if (allowed === false) {
- tree = {};
- } else {
- tree = this.createTree(isArray(allowed) ? allowed : elements);
- }
- // we set an empty tree, selection of elements
- // to copy was empty.
- this._clipboard.set(tree);
- this._eventBus.fire('copyPaste.elementsCopied', {
- elements: elements,
- tree: tree
- });
- return tree;
- };
- /**
- * Paste elements.
- *
- * @param {Object} [context]
- * @param {djs.model.base} [context.element] - Parent.
- * @param {Point} [context.point] - Position.
- * @param {Object} [context.hints] - Hints.
- */
- CopyPaste.prototype.paste = function(context) {
- var tree = this._clipboard.get();
- if (this._clipboard.isEmpty()) {
- return;
- }
- var hints = context && context.hints || {};
- this._eventBus.fire('copyPaste.pasteElements', {
- hints: hints
- });
- var elements = this._createElements(tree);
- // paste directly
- if (context && context.element && context.point) {
- return this._paste(elements, context.element, context.point, hints);
- }
- this._create.start(this._mouse.getLastMoveEvent(), elements, {
- hints: hints || {}
- });
- };
- /**
- * Paste elements directly.
- *
- * @param {Array<djs.model.Base>} elements
- * @param {djs.model.base} target
- * @param {Point} position
- * @param {Object} [hints]
- */
- CopyPaste.prototype._paste = function(elements, target, position, hints) {
- // make sure each element has x and y
- forEach(elements, function(element) {
- if (!isNumber(element.x)) {
- element.x = 0;
- }
- if (!isNumber(element.y)) {
- element.y = 0;
- }
- });
- var bbox = getBBox(elements);
- // center elements around cursor
- forEach(elements, function(element) {
- if (isConnection(element)) {
- element.waypoints = map(element.waypoints, function(waypoint) {
- return {
- x: waypoint.x - bbox.x - bbox.width / 2,
- y: waypoint.y - bbox.y - bbox.height / 2
- };
- });
- }
- assign(element, {
- x: element.x - bbox.x - bbox.width / 2,
- y: element.y - bbox.y - bbox.height / 2
- });
- });
- return this._modeling.createElements(elements, position, target, assign({}, hints));
- };
- /**
- * Create elements from tree.
- */
- CopyPaste.prototype._createElements = function(tree) {
- var self = this;
- var eventBus = this._eventBus;
- var cache = {};
- var elements = [];
- forEach(tree, function(branch, depth) {
- depth = parseInt(depth, 10);
- // sort by priority
- branch = sortBy(branch, 'priority');
- forEach(branch, function(descriptor) {
- // remove priority
- var attrs = assign({}, omit(descriptor, [ 'priority' ]));
- if (cache[ descriptor.parent ]) {
- attrs.parent = cache[ descriptor.parent ];
- } else {
- delete attrs.parent;
- }
- eventBus.fire('copyPaste.pasteElement', {
- cache: cache,
- descriptor: attrs
- });
- var element;
- if (isConnection(attrs)) {
- attrs.source = cache[ descriptor.source ];
- attrs.target = cache[ descriptor.target ];
- element = cache[ descriptor.id ] = self.createConnection(attrs);
- elements.push(element);
- return;
- }
- if (isLabel(attrs)) {
- attrs.labelTarget = cache[ attrs.labelTarget ];
- element = cache[ descriptor.id ] = self.createLabel(attrs);
- elements.push(element);
- return;
- }
- if (attrs.host) {
- attrs.host = cache[ attrs.host ];
- }
- element = cache[ descriptor.id ] = self.createShape(attrs);
- elements.push(element);
- });
- });
- return elements;
- };
- CopyPaste.prototype.createConnection = function(attrs) {
- var connection = this._elementFactory.createConnection(omit(attrs, [ 'id' ]));
- return connection;
- };
- CopyPaste.prototype.createLabel = function(attrs) {
- var label = this._elementFactory.createLabel(omit(attrs, [ 'id' ]));
- return label;
- };
- CopyPaste.prototype.createShape = function(attrs) {
- var shape = this._elementFactory.createShape(omit(attrs, [ 'id' ]));
- return shape;
- };
- /**
- * Check wether element has relations to other elements e.g. attachers, labels and connections.
- *
- * @param {Object} element
- * @param {Array<djs.model.Base>} elements
- *
- * @returns {boolean}
- */
- CopyPaste.prototype.hasRelations = function(element, elements) {
- var labelTarget,
- source,
- target;
- if (isConnection(element)) {
- source = find(elements, matchPattern({ id: element.source.id }));
- target = find(elements, matchPattern({ id: element.target.id }));
- if (!source || !target) {
- return false;
- }
- }
- if (isLabel(element)) {
- labelTarget = find(elements, matchPattern({ id: element.labelTarget.id }));
- if (!labelTarget) {
- return false;
- }
- }
- return true;
- };
- /**
- * Create a tree-like structure from elements.
- *
- * @example
- * tree: {
- * 0: [
- * { id: 'Shape_1', priority: 1, ... },
- * { id: 'Shape_2', priority: 1, ... },
- * { id: 'Connection_1', source: 'Shape_1', target: 'Shape_2', priority: 3, ... },
- * ...
- * ],
- * 1: [
- * { id: 'Shape_3', parent: 'Shape1', priority: 1, ... },
- * ...
- * ]
- * };
- *
- * @param {Array<djs.model.base>} elements
- *
- * @return {Object}
- */
- CopyPaste.prototype.createTree = function(elements) {
- var rules = this._rules,
- self = this;
- var tree = {},
- elementsData = [];
- var parents = getParents(elements);
- function canCopy(element, elements) {
- return rules.allowed('element.copy', {
- element: element,
- elements: elements
- });
- }
- function addElementData(element, depth) {
- // (1) check wether element has already been added
- var foundElementData = find(elementsData, function(elementsData) {
- return element === elementsData.element;
- });
- // (2) add element if not already added
- if (!foundElementData) {
- elementsData.push({
- element: element,
- depth: depth
- });
- return;
- }
- // (3) update depth
- if (foundElementData.depth < depth) {
- elementsData = removeElementData(foundElementData, elementsData);
- elementsData.push({
- element: foundElementData.element,
- depth: depth
- });
- }
- }
- function removeElementData(elementData, elementsData) {
- var index = elementsData.indexOf(elementData);
- if (index !== -1) {
- elementsData.splice(index, 1);
- }
- return elementsData;
- }
- // (1) add elements
- eachElement(parents, function(element, _index, depth) {
- // do NOT add external labels directly
- if (isLabel(element)) {
- return;
- }
- // always copy external labels
- forEach(element.labels, function(label) {
- addElementData(label, depth);
- });
- function addRelatedElements(elements) {
- elements && elements.length && forEach(elements, function(element) {
- // add external labels
- forEach(element.labels, function(label) {
- addElementData(label, depth);
- });
- addElementData(element, depth);
- });
- }
- forEach([ element.attachers, element.incoming, element.outgoing ], addRelatedElements);
- addElementData(element, depth);
- var children = [];
- if (element.children) {
- children = element.children.slice();
- }
- // allow others to add children to tree
- self._eventBus.fire('copyPaste.createTree', {
- element: element,
- children: children
- });
- return children;
- });
- elements = map(elementsData, function(elementData) {
- return elementData.element;
- });
- // (2) copy elements
- elementsData = map(elementsData, function(elementData) {
- elementData.descriptor = {};
- self._eventBus.fire('copyPaste.copyElement', {
- descriptor: elementData.descriptor,
- element: elementData.element,
- elements: elements
- });
- return elementData;
- });
- // (3) sort elements by priority
- elementsData = sortBy(elementsData, function(elementData) {
- return elementData.descriptor.priority;
- });
- elements = map(elementsData, function(elementData) {
- return elementData.element;
- });
- // (4) create tree
- forEach(elementsData, function(elementData) {
- var depth = elementData.depth;
- if (!self.hasRelations(elementData.element, elements)) {
- removeElement(elementData.element, elements);
- return;
- }
- if (!canCopy(elementData.element, elements)) {
- removeElement(elementData.element, elements);
- return;
- }
- if (!tree[depth]) {
- tree[depth] = [];
- }
- tree[depth].push(elementData.descriptor);
- });
- return tree;
- };
- // helpers //////////
- function isAttacher(element) {
- return !!element.host;
- }
- function isConnection(element) {
- return !!element.waypoints;
- }
- function isLabel(element) {
- return !!element.labelTarget;
- }
- function copyWaypoints(element) {
- return map(element.waypoints, function(waypoint) {
- waypoint = copyWaypoint(waypoint);
- if (waypoint.original) {
- waypoint.original = copyWaypoint(waypoint.original);
- }
- return waypoint;
- });
- }
- function copyWaypoint(waypoint) {
- return assign({}, waypoint);
- }
- function removeElement(element, elements) {
- var index = elements.indexOf(element);
- if (index === -1) {
- return elements;
- }
- return elements.splice(index, 1);
- }
|