|
|
@@ -77,7 +77,6 @@ Draw.loadPlugin(function(ui) {
|
|
|
for (const [from, to, linkType] of fromTo) {
|
|
|
if (from(sourceType) && to(targetType)) {
|
|
|
setStyle(linkType);
|
|
|
- return true;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -119,6 +118,8 @@ Draw.loadPlugin(function(ui) {
|
|
|
if (version === 'ports' || !version) {
|
|
|
|
|
|
ui.editor.graph.addListener(mxEvent.CELL_CONNECTED, (_, eventObj) => {
|
|
|
+ // Happens whenever an edge is (dis)connected.
|
|
|
+
|
|
|
// This will change the edge style WITHIN the transaction of the edit operation.
|
|
|
// The terminal-change and style-change will be one edit operation from point of view of undo manager.
|
|
|
|
|
|
@@ -135,6 +136,7 @@ Draw.loadPlugin(function(ui) {
|
|
|
return isDataPort(type) || isArtifact(type);
|
|
|
}
|
|
|
|
|
|
+ // [ from, to, style ]
|
|
|
const fromTo = [
|
|
|
// PM control flow
|
|
|
[ isControlFlowNode, isControlFlowNode, "control_flow" ],
|
|
|
@@ -151,8 +153,169 @@ Draw.loadPlugin(function(ui) {
|
|
|
checkEdge(ui, eventObj.properties.edge, fromTo);
|
|
|
});
|
|
|
|
|
|
+ const portClasses = [ "data_in", "data_out", "ctrl_in", "ctrl_out", ];
|
|
|
+ function isPort(type) {
|
|
|
+ return portClasses.includes(type);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Mapping from parent shape to attached ports
|
|
|
+ const portMapping = new Map();
|
|
|
+ // Mapping from attached port to parent shape
|
|
|
+ const reversePortMapping = new Map();
|
|
|
+
|
|
|
+ function outsideOffset(wOrH) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ function centerOffset(wOrH) {
|
|
|
+ return -wOrH / 2;
|
|
|
+ }
|
|
|
+ function insideOffset(wOrH) {
|
|
|
+ return -wOrH;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Change this to position ports outside, inside or centered at the edge.
|
|
|
+ const borderOffset = insideOffset;
|
|
|
+
|
|
|
+ function rightOrBottomBorder(wOrH, parentWorH) {
|
|
|
+ return parentWorH + borderOffset(wOrH);
|
|
|
+ }
|
|
|
+ function leftOrTopBorder(wOrH) {
|
|
|
+ return -wOrH - borderOffset(wOrH);
|
|
|
+ }
|
|
|
+
|
|
|
+ function attachPortToParent(cell, moveCell) {
|
|
|
+ // Coordinates are relative to topleft corner of parent shape
|
|
|
+ const {x,y, width, height} = cell.geometry;
|
|
|
+ // Center of dragged shape
|
|
|
+ const cX = x + width/2;
|
|
|
+ const cY = y + height/2;
|
|
|
+
|
|
|
+ const {width: pWidth, height: pHeight} = cell.parent.geometry;
|
|
|
+ console.log(cX, cY, pWidth, pHeight);
|
|
|
+
|
|
|
+ // We draw two imaginary diagonals through the parent shape, to determine whether the child shape is more close to the top, right, bottom or left side.
|
|
|
+ //
|
|
|
+ // 2
|
|
|
+ // /
|
|
|
+ // +----+
|
|
|
+ // |\ t/|
|
|
|
+ // |l\/ |
|
|
|
+ // | /\r|
|
|
|
+ // |/b \|
|
|
|
+ // +----+
|
|
|
+ // \
|
|
|
+ // 1
|
|
|
+ const slope = pHeight / pWidth;
|
|
|
+ const above1 = cY < slope * cX;
|
|
|
+ const above2 = cY < pHeight - slope * cX;
|
|
|
+
|
|
|
+ let portList = portMapping.get(cell.parent);
|
|
|
+ if (portList === undefined) {
|
|
|
+ portList = [];
|
|
|
+ portMapping.set(cell.parent, portList);
|
|
|
+ }
|
|
|
+ // Remove cell from existing list
|
|
|
+ const prevParent = reversePortMapping.get(cell);
|
|
|
+ const prevPortList = portMapping.get(prevParent);
|
|
|
+ if (prevPortList) {
|
|
|
+ const idx = prevPortList.findIndex(({cell: c}) => c === cell);
|
|
|
+ if (idx >= 0) {
|
|
|
+ console.log("removed from list");
|
|
|
+ prevPortList.splice(idx, 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Because the cell was moved, a new mxGeometry object was already created for it.
|
|
|
+ // We simply overwrite a property of this new mxGeometry, to include the positioning of the port on the edge in the same micro-operation.
|
|
|
+
|
|
|
+ if (above1) {
|
|
|
+ if (above2) {
|
|
|
+ // console.log("top");
|
|
|
+ if (moveCell) {
|
|
|
+ cell.geometry.y = leftOrTopBorder(height);
|
|
|
+ }
|
|
|
+ portList.push({cell, side: 't'});
|
|
|
+ } else {
|
|
|
+ // console.log("right");
|
|
|
+ if (moveCell) {
|
|
|
+ cell.geometry.x = rightOrBottomBorder(width, pWidth);
|
|
|
+ }
|
|
|
+ portList.push({cell, side: 'r'});
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (above2) {
|
|
|
+ // console.log("left");
|
|
|
+ if (moveCell) {
|
|
|
+ cell.geometry.x = leftOrTopBorder(width);
|
|
|
+ }
|
|
|
+ portList.push({cell, side: 'l'});
|
|
|
+ } else {
|
|
|
+ // console.log("bottom");
|
|
|
+ if (moveCell) {
|
|
|
+ cell.geometry.y = rightOrBottomBorder(height, pHeight);
|
|
|
+ }
|
|
|
+ portList.push({cell, side: 'b'});
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ reversePortMapping.set(cell, cell.parent);
|
|
|
+ }
|
|
|
+
|
|
|
+ function findPortsAndAttach(cells) {
|
|
|
+ for (const cell of cells) {
|
|
|
+ if (cell.parent && cell.parent.geometry && isPort(cell.getAttribute("pmRole"))) {
|
|
|
+ attachPortToParent(cell, true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ui.editor.graph.addListener(mxEvent.MOVE_CELLS, (_, eventObj) => {
|
|
|
+ // console.log("MOVE_CELLS:", eventObj);
|
|
|
+
|
|
|
+ // High-level event: Happens when the user releases a dragged shape
|
|
|
+ findPortsAndAttach(eventObj.properties.cells);
|
|
|
+ })
|
|
|
+
|
|
|
+ ui.editor.graph.addListener(mxEvent.RESIZE_CELLS, (_, eventObj) => {
|
|
|
+ console.log("RESIZE");
|
|
|
+ console.log("EVENT:", eventObj);
|
|
|
+
|
|
|
+ for (const parent of eventObj.properties.cells) {
|
|
|
+ if (!parent.geometry) continue;
|
|
|
+ const portList = portMapping.get(parent);
|
|
|
+ if (portList !== undefined) {
|
|
|
+ for (const {cell, side} of portList) {
|
|
|
+ // We cannot just overwrite a property of the existing mxGeometry, because this would not trigger a 'move' micro-operation in the current user action. If we were to undo this edit, the port would stay in its new place. The correct implementation is to create a new mxGeometry object, and to not touch the old one, as it is used by undo/redo.
|
|
|
+ if (side === 'r') {
|
|
|
+ const newGeometry = cell.geometry.clone();
|
|
|
+ newGeometry.x = rightOrBottomBorder(cell.geometry.width, parent.geometry.width);
|
|
|
+ ui.editor.graph.model.setGeometry(cell, newGeometry);
|
|
|
+ } else if (side === 'b') {
|
|
|
+ const newGeometry = cell.geometry.clone();
|
|
|
+ newGeometry.y = rightOrBottomBorder(cell.geometry.height, parent.geometry.height);
|
|
|
+ ui.editor.graph.model.setGeometry(cell, newGeometry);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // ui.editor.undoManager.addListener(null, (_, eventObj) => {
|
|
|
+ // console.log("UNDO:", eventObj);
|
|
|
+ // });
|
|
|
+
|
|
|
+ // Upon loading a model,
|
|
|
+ ui.editor.graph.addListener(mxEvent.ROOT, (_, eventObj) => {
|
|
|
+ console.log("GRAPH EVENT:", eventObj);
|
|
|
+
|
|
|
+ // Root has changed (new model loaded)
|
|
|
+ findPortsAndAttach(Object.values(ui.editor.graph.model.cells));
|
|
|
+ });
|
|
|
+
|
|
|
+ window.ui = ui;
|
|
|
+
|
|
|
ui.loadLibrary(new LocalLibrary(ui, portsExamplesLib, "FTG+PM with ports: Examples"));
|
|
|
- ui.loadLibrary(new LocalLibrary(ui, portsPrimitivesLib, "FTG+PM with ports: Primitives"));
|
|
|
+ ui.loadLibrary(new LocalLibrary(ui, portsPrimitivesLib, "FTG+PM with ports: Primitives"));
|
|
|
|
|
|
console.log("Activated FTG+PM with ports")
|
|
|
}
|