diagram-js
Version:
A toolbox for displaying and modifying diagrams on the web
259 lines (200 loc) • 6.17 kB
JavaScript
import {
flatten,
forEach,
filter,
find,
groupBy,
matchPattern,
size
} from 'min-dash';
import {
selfAndAllChildren
} from '../../util/Elements';
import {
append as svgAppend,
attr as svgAttr,
create as svgCreate,
remove as svgRemove
} from 'tiny-svg';
import { translate } from '../../util/SvgTransformUtil';
import { isConnection } from '../../util/ModelUtil';
/**
* @typedef {import('../../model/Types').Element} Element
*
* @typedef {import('../../core/Canvas').default} Canvas
* @typedef {import('../../core/EventBus').default} EventBus
* @typedef {import('../preview-support/PreviewSupport').default} PreviewSupport
* @typedef {import('../../draw/Styles').default} Styles
*/
var LOW_PRIORITY = 499;
var MARKER_DRAGGING = 'djs-dragging',
MARKER_OK = 'drop-ok',
MARKER_NOT_OK = 'drop-not-ok',
MARKER_NEW_PARENT = 'new-parent',
MARKER_ATTACH = 'attach-ok';
/**
* Provides previews for moving shapes when moving.
*
* @param {EventBus} eventBus
* @param {Canvas} canvas
* @param {Styles} styles
* @param {PreviewSupport} previewSupport
*/
export default function MovePreview(
eventBus, canvas, styles, previewSupport) {
function getVisualDragShapes(shapes) {
var elements = getAllDraggedElements(shapes);
var filteredElements = removeEdges(elements);
return filteredElements;
}
function getAllDraggedElements(shapes) {
var allShapes = selfAndAllChildren(shapes, true);
var allConnections = allShapes.flatMap(shape =>
(shape.incoming || []).concat(shape.outgoing || [])
);
var allElements = allShapes.concat(allConnections);
var uniqueElements = [ ...new Set(allElements) ];
return uniqueElements;
}
/**
* Sets drop marker on an element.
*/
function setMarker(element, marker) {
[ MARKER_ATTACH, MARKER_OK, MARKER_NOT_OK, MARKER_NEW_PARENT ].forEach(function(m) {
if (m === marker) {
canvas.addMarker(element, m);
} else {
canvas.removeMarker(element, m);
}
});
}
/**
* Make an element draggable.
*
* @param {Object} context
* @param {Element} element
* @param {boolean} addMarker
*/
function makeDraggable(context, element, addMarker) {
previewSupport.addDragger(element, context.dragGroup);
if (addMarker) {
canvas.addMarker(element, MARKER_DRAGGING);
}
if (context.allDraggedElements) {
context.allDraggedElements.push(element);
} else {
context.allDraggedElements = [ element ];
}
}
// assign a low priority to this handler
// to let others modify the move context before
// we draw things
eventBus.on('shape.move.start', LOW_PRIORITY, function(event) {
var context = event.context,
dragShapes = context.shapes,
allDraggedElements = context.allDraggedElements;
var visuallyDraggedShapes = getVisualDragShapes(dragShapes);
if (!context.dragGroup) {
var dragGroup = svgCreate('g');
svgAttr(dragGroup, styles.cls('djs-drag-group', [ 'no-events' ]));
var activeLayer = canvas.getActiveLayer();
svgAppend(activeLayer, dragGroup);
context.dragGroup = dragGroup;
}
// add previews
visuallyDraggedShapes.forEach(function(shape) {
previewSupport.addDragger(shape, context.dragGroup);
});
// cache all dragged elements / gfx
// so that we can quickly undo their state changes later
if (!allDraggedElements) {
allDraggedElements = getAllDraggedElements(dragShapes);
} else {
allDraggedElements = flatten([
allDraggedElements,
getAllDraggedElements(dragShapes)
]);
}
// add dragging marker
forEach(allDraggedElements, function(e) {
canvas.addMarker(e, MARKER_DRAGGING);
});
context.allDraggedElements = allDraggedElements;
// determine, if any of the dragged elements have different parents
context.differentParents = haveDifferentParents(dragShapes);
});
// update previews
eventBus.on('shape.move.move', LOW_PRIORITY, function(event) {
var context = event.context,
dragGroup = context.dragGroup,
target = context.target,
parent = context.shape.parent,
canExecute = context.canExecute;
if (target) {
if (canExecute === 'attach') {
setMarker(target, MARKER_ATTACH);
} else if (context.canExecute && parent && target.id !== parent.id) {
setMarker(target, MARKER_NEW_PARENT);
} else {
setMarker(target, context.canExecute ? MARKER_OK : MARKER_NOT_OK);
}
}
translate(dragGroup, event.dx, event.dy);
});
eventBus.on([ 'shape.move.out', 'shape.move.cleanup' ], function(event) {
var context = event.context,
target = context.target;
if (target) {
setMarker(target, null);
}
});
// remove previews
eventBus.on('shape.move.cleanup', function(event) {
var context = event.context,
allDraggedElements = context.allDraggedElements,
dragGroup = context.dragGroup;
// remove dragging marker
forEach(allDraggedElements, function(e) {
canvas.removeMarker(e, MARKER_DRAGGING);
});
if (dragGroup) {
svgRemove(dragGroup);
}
});
// API //////////////////////
/**
* Make an element draggable.
*
* @param {Object} context
* @param {Element} element
* @param {boolean} addMarker
*/
this.makeDraggable = makeDraggable;
}
MovePreview.$inject = [
'eventBus',
'canvas',
'styles',
'previewSupport'
];
// helpers //////////////////////
/**
* returns elements minus all connections
* where source or target is not elements
*/
function removeEdges(elements) {
var filteredElements = filter(elements, function(element) {
if (!isConnection(element)) {
return true;
} else {
return (
find(elements, matchPattern({ id: element.source.id })) &&
find(elements, matchPattern({ id: element.target.id }))
);
}
});
return filteredElements;
}
function haveDifferentParents(elements) {
return size(groupBy(elements, function(e) { return e.parent && e.parent.id; })) !== 1;
}