262 lines
6.0 KiB
JavaScript
262 lines
6.0 KiB
JavaScript
|
|
import {
|
||
|
|
flatten,
|
||
|
|
forEach,
|
||
|
|
filter,
|
||
|
|
find,
|
||
|
|
groupBy,
|
||
|
|
map,
|
||
|
|
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';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @typedef {import('../../model').Base} Base
|
||
|
|
*
|
||
|
|
* @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 = map(allShapes, function(shape) {
|
||
|
|
return (shape.incoming || []).concat(shape.outgoing || []);
|
||
|
|
});
|
||
|
|
|
||
|
|
return flatten(allShapes.concat(allConnections));
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 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 {Base} 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 && target && 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 {Base} 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;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Checks if an element is a connection.
|
||
|
|
*/
|
||
|
|
function isConnection(element) {
|
||
|
|
return element.waypoints;
|
||
|
|
}
|