123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472 |
- import {
- isArray,
- forEach,
- map,
- matchPattern,
- find,
- findIndex,
- sortBy,
- reduce
- } from 'min-dash';
- import { getBBox } from '../../util/Elements';
- import {
- center,
- delta as posDelta
- } from '../../util/PositionUtil';
- import {
- getTopLevel
- } from '../../util/CopyPasteUtil';
- import {
- eachElement
- } from '../../util/Elements';
- export default function CopyPaste(
- eventBus, modeling, elementFactory,
- rules, clipboard, canvas) {
- this._eventBus = eventBus;
- this._modeling = modeling;
- this._elementFactory = elementFactory;
- this._rules = rules;
- this._canvas = canvas;
- this._clipboard = clipboard;
- this._descriptors = [];
- // Element creation priorities:
- // - 1: Independent shapes
- // - 2: Attached shapes
- // - 3: Connections
- // - 4: labels
- this.registerDescriptor(function(element, descriptor) {
- // Base priority
- descriptor.priority = 1;
- descriptor.id = element.id;
- if (element.parent) {
- descriptor.parent = element.parent.id;
- }
- if (element.labelTarget) {
- // Labels priority
- descriptor.priority = 4;
- descriptor.labelTarget = element.labelTarget.id;
- }
- if (element.host) {
- // Attached shapes priority
- descriptor.priority = 2;
- descriptor.host = element.host.id;
- }
- if (typeof element.x === 'number') {
- descriptor.x = element.x;
- descriptor.y = element.y;
- }
- if (element.width) {
- descriptor.width = element.width;
- descriptor.height = element.height;
- }
- if (element.waypoints) {
- // Connections priority
- descriptor.priority = 3;
- descriptor.waypoints = [];
- forEach(element.waypoints, function(waypoint) {
- var wp = {
- x: waypoint.x,
- y: waypoint.y
- };
- if (waypoint.original) {
- wp.original = {
- x: waypoint.original.x,
- y: waypoint.original.y
- };
- }
- descriptor.waypoints.push(wp);
- });
- }
- if (element.source && element.target) {
- descriptor.source = element.source.id;
- descriptor.target = element.target.id;
- }
- return descriptor;
- });
- }
- CopyPaste.$inject = [
- 'eventBus',
- 'modeling',
- 'elementFactory',
- 'rules',
- 'clipboard',
- 'canvas'
- ];
- /**
- * Copy a number of elements.
- *
- * @param {djs.model.Base} selectedElements
- *
- * @return {Object} the copied tree
- */
- CopyPaste.prototype.copy = function(selectedElements) {
- var clipboard = this._clipboard,
- tree, bbox;
- if (!isArray(selectedElements)) {
- selectedElements = selectedElements ? [ selectedElements ] : [];
- }
- if (!selectedElements.length) {
- return;
- }
- tree = this.createTree(selectedElements);
- bbox = this._bbox = center(getBBox(tree.allShapes));
- // not needed after computing the center position of the copied elements
- delete tree.allShapes;
- forEach(tree, function(elements) {
- forEach(elements, function(element) {
- var delta, labelTarget;
- // set label's relative position to their label target
- if (element.labelTarget) {
- labelTarget = find(elements, matchPattern({ id: element.labelTarget }));
- // just grab the delta from the first waypoint
- if (labelTarget.waypoints) {
- delta = posDelta(element, labelTarget.waypoints[0]);
- } else {
- delta = posDelta(element, labelTarget);
- }
- } else
- if (element.priority === 3) {
- // connections have priority 3
- delta = [];
- forEach(element.waypoints, function(waypoint) {
- var waypointDelta = posDelta(waypoint, bbox);
- delta.push(waypointDelta);
- });
- } else {
- delta = posDelta(element, bbox);
- }
- element.delta = delta;
- });
- });
- this._eventBus.fire('elements.copy', { context: { tree: tree } });
- // if tree is empty, means that nothing can be or is allowed to be copied
- if (Object.keys(tree).length === 0) {
- clipboard.clear();
- } else {
- clipboard.set(tree);
- }
- this._eventBus.fire('elements.copied', { context: { tree: tree } });
- return tree;
- };
- // Allow pasting under the cursor
- CopyPaste.prototype.paste = function(context) {
- var clipboard = this._clipboard,
- modeling = this._modeling,
- eventBus = this._eventBus,
- rules = this._rules;
- var tree = clipboard.get(),
- topParent = context.element,
- position = context.point,
- newTree, canPaste;
- if (clipboard.isEmpty()) {
- return;
- }
- newTree = reduce(tree, function(pasteTree, elements, depthStr) {
- var depth = parseInt(depthStr, 10);
- if (isNaN(depth)) {
- return pasteTree;
- }
- pasteTree[depth] = elements;
- return pasteTree;
- }, {});
- canPaste = rules.allowed('elements.paste', {
- tree: newTree,
- target: topParent
- });
- if (!canPaste) {
- eventBus.fire('elements.paste.rejected', {
- context: {
- tree: newTree,
- position: position,
- target: topParent
- }
- });
- return;
- }
- modeling.pasteElements(newTree, topParent, position);
- };
- CopyPaste.prototype._computeDelta = function(elements, element) {
- var bbox = this._bbox,
- delta = {};
- // set label's relative position to their label target
- if (element.labelTarget) {
- return posDelta(element, element.labelTarget);
- }
- // connections have prority 3
- if (element.priority === 3) {
- delta = [];
- forEach(element.waypoints, function(waypoint) {
- var waypointDelta = posDelta(waypoint, bbox);
- delta.push(waypointDelta);
- });
- } else {
- delta = posDelta(element, bbox);
- }
- return delta;
- };
- /**
- * Checks if the element in question has a relations to other elements.
- * Possible dependants: connections, labels, attachers
- *
- * @param {Array} elements
- * @param {Object} element
- *
- * @return {Boolean}
- */
- CopyPaste.prototype.hasRelations = function(elements, element) {
- var source, target, labelTarget;
- if (element.waypoints) {
- source = find(elements, matchPattern({ id: element.source.id }));
- target = find(elements, matchPattern({ id: element.target.id }));
- if (!source || !target) {
- return false;
- }
- }
- if (element.labelTarget) {
- labelTarget = find(elements, matchPattern({ id: element.labelTarget.id }));
- if (!labelTarget) {
- return false;
- }
- }
- return true;
- };
- CopyPaste.prototype.registerDescriptor = function(descriptor) {
- if (typeof descriptor !== 'function') {
- throw new Error('the descriptor must be a function');
- }
- if (this._descriptors.indexOf(descriptor) !== -1) {
- throw new Error('this descriptor is already registered');
- }
- this._descriptors.push(descriptor);
- };
- CopyPaste.prototype._executeDescriptors = function(data) {
- if (!data.descriptor) {
- data.descriptor = {};
- }
- forEach(this._descriptors, function(descriptor) {
- data.descriptor = descriptor(data.element, data.descriptor);
- });
- return data;
- };
- /**
- * Creates a tree like structure from an arbitrary collection of elements
- *
- * @example
- * tree: {
- * 0: [
- * { id: 'shape_12da', priority: 1, ... },
- * { id: 'shape_01bj', priority: 1, ... },
- * { id: 'connection_79fa', source: 'shape_12da', target: 'shape_01bj', priority: 3, ... },
- * ],
- * 1: [ ... ]
- * };
- *
- * @param {Array} elements
- * @return {Object}
- */
- CopyPaste.prototype.createTree = function(elements) {
- var rules = this._rules,
- self = this;
- var tree = {},
- includedElements = [],
- _elements;
- var topLevel = getTopLevel(elements);
- tree.allShapes = [];
- function canCopy(collection, element) {
- return rules.allowed('element.copy', {
- collection: collection,
- element: element
- });
- }
- function includeElement(data) {
- var idx = findIndex(includedElements, matchPattern({ element: data.element })),
- element;
- if (idx !== -1) {
- element = includedElements[idx];
- } else {
- return includedElements.push(data);
- }
- // makes sure that it has the correct depth
- if (element.depth < data.depth) {
- includedElements.splice(idx, 1);
- includedElements.push(data);
- }
- }
- eachElement(topLevel, function(element, i, depth) {
- var nestedChildren = element.children;
- // don't add labels directly
- if (element.labelTarget) {
- return;
- }
- function getNested(lists) {
- forEach(lists, function(list) {
- if (list && list.length) {
- forEach(list, function(elem) {
- forEach(elem.labels, function(label) {
- includeElement({
- element: label,
- depth: depth
- });
- });
- includeElement({
- element: elem,
- depth: depth
- });
- });
- }
- });
- }
- // fetch element's labels
- forEach(element.labels, function(label) {
- includeElement({
- element: label,
- depth: depth
- });
- });
- getNested([ element.attachers, element.incoming, element.outgoing ]);
- includeElement({
- element: element,
- depth: depth
- });
- if (nestedChildren) {
- return nestedChildren;
- }
- });
- includedElements = map(includedElements, function(data) {
- // this is where other registered descriptors hook in
- return self._executeDescriptors(data);
- });
- // order the elements to check if the ones dependant on others (by relationship)
- // can be copied. f.ex: label needs it's label target
- includedElements = sortBy(includedElements, function(data) {
- return data.descriptor.priority;
- });
- _elements = map(includedElements, function(data) {
- return data.element;
- });
- forEach(includedElements, function(data) {
- var depth = data.depth;
- if (!self.hasRelations(tree.allShapes, data.element)) {
- return;
- }
- if (!canCopy(_elements, data.element)) {
- return;
- }
- tree.allShapes.push(data.element);
- // create depth branches
- if (!tree[depth]) {
- tree[depth] = [];
- }
- tree[depth].push(data.descriptor);
- });
- return tree;
- };
|