瀏覽代碼

FTG+PM: Add "hack" buttons that apply style/configuration to all edges/ports to config window.

Joeri Exelmans 2 年之前
父節點
當前提交
28c5123f29
共有 1 個文件被更改,包括 196 次插入110 次删除
  1. 196 110
      src/main/webapp/myPlugins/ftgpm.js

+ 196 - 110
src/main/webapp/myPlugins/ftgpm.js

@@ -1,5 +1,7 @@
 Draw.loadPlugin(function(ui) {
 
+  const model = ui.editor.graph.model;
+
   const defaultConfig = {
     // When making an activity wider, the x-position of ports will be scaled proportionally to the new width, and vice-versa with height and y-position. This may be annoying when simply resizing an activity to add an extra port.
     movePortsOnResizeActivity: false,
@@ -15,8 +17,8 @@ Draw.loadPlugin(function(ui) {
     edgeStyleComment: "edgeStyle=0;endArrow=none;dashed=1;html=1;",
 
 
-    portLabelOffsetEdgeDirection: 10,
-    portLabelOffsetPerpendicularToEdgeDirection: 38,
+    portLabelOffsetEdgeDirection: 22,
+    portLabelOffsetPerpendicularToEdgeDirection: 24,
   };
 
   let currentConfig;
@@ -30,97 +32,149 @@ Draw.loadPlugin(function(ui) {
   }
 
   // Configuration dialog window:
-
-  // Drawio has no decent abstraction for UI elements or even displaying a dialog window.
-  // The following is based on code from src/main/webapp/js/diagramly/Dialogs.js
-  const windowDiv = document.createElement('div');
-  windowDiv.style.userSelect = 'none';
-  windowDiv.style.overflow = 'hidden';
-  windowDiv.style.padding = '10px';
-  windowDiv.style.height = '100%';
-
-  function createConfig(labelText, json, readonly) {
-    const columnDiv = document.createElement('div');
-    columnDiv.style.display = 'inline-block';
-
-    const label = document.createElement('label');
-    const textarea = document.createElement('textarea');
-    label.setAttribute('for', textarea);
-    label.innerHTML = labelText;
-    textarea.setAttribute('wrap', 'off');
-    textarea.setAttribute('spellcheck', 'false');
-    textarea.setAttribute('autocorrect', 'off');
-    textarea.setAttribute('autocomplete', 'off');
-    textarea.setAttribute('autocapitalize', 'off');
-    textarea.setAttribute('wrap', 'hard');
-    if (readonly) {
-      textarea.setAttribute('readonly', 'true');
+  function createFtgpmConfigWindow() { // only called once - this is only a function in order not to pollute namespace
+
+    // Drawio has no decent abstraction for UI elements or even displaying a dialog window.
+    // The following is based on code from src/main/webapp/js/diagramly/Dialogs.js
+    const wndDiv = document.createElement('div');
+      wndDiv.style.userSelect = 'none';
+      wndDiv.style.overflow = 'hidden';
+      wndDiv.style.padding = '10px';
+      wndDiv.style.height = '100%';
+    const wnd = new mxWindow("FTG+PM Plugin Configuration",
+      wndDiv, 100, 100, 640, 570, true, true);
+      wnd.destroyOnClose = false;
+      wnd.setMaximizable(false);
+      wnd.setResizable(false);
+      wnd.setClosable(true);
+      wnd.addListener('show', mxUtils.bind(this, function() {
+        statusText.innerHTML = "";
+      }));
+
+    function createConfig(labelText, json, readonly) {
+      const columnDiv = document.createElement('div');
+      columnDiv.style.display = 'inline-block';
+
+      const label = document.createElement('label');
+      const textarea = document.createElement('textarea');
+      label.setAttribute('for', textarea);
+      label.innerHTML = labelText;
+      textarea.setAttribute('wrap', 'off');
+      textarea.setAttribute('spellcheck', 'false');
+      textarea.setAttribute('autocorrect', 'off');
+      textarea.setAttribute('autocomplete', 'off');
+      textarea.setAttribute('autocapitalize', 'off');
+      textarea.setAttribute('wrap', 'hard');
+      if (readonly) {
+        textarea.setAttribute('readonly', 'true');
+      }
+      textarea.value = JSON.stringify(json, null, 2);
+      textarea.style.overflow = 'auto';
+      textarea.style.resize = 'none';
+      textarea.style.width = '300px';
+      textarea.style.height = '360px';
+      textarea.style.marginBottom = '16px';
+
+      columnDiv.appendChild(label);
+      mxUtils.br(columnDiv);
+      columnDiv.appendChild(textarea);
+      return [columnDiv, textarea];
     }
-    textarea.value = JSON.stringify(json, null, 2);
-    textarea.style.overflow = 'auto';
-    textarea.style.resize = 'none';
-    textarea.style.width = '300px';
-    textarea.style.height = '360px';
-    textarea.style.marginBottom = '16px';
 
-    columnDiv.appendChild(label);
-    mxUtils.br(columnDiv);
-    columnDiv.appendChild(textarea);
-    return [columnDiv, textarea];
-  }
-
-  const [defaultColumn] = createConfig("Default config (hardcoded & read-only):", defaultConfig, true);
-  const [customColumn, customTextArea] = createConfig("Custom config (acts as an 'overlay' on top of default config):", currentConfig, false);
-  windowDiv.appendChild(defaultColumn);
-  windowDiv.appendChild(customColumn);
-
-  mxUtils.br(windowDiv);
-
-  const statusText = document.createElement('div');
+    const [defaultColumn] = createConfig("Default config (hardcoded & read-only):", defaultConfig, true);
+    const [customColumn, customTextArea] = createConfig("Custom config (acts as an 'overlay' on top of default config):", currentConfig, false);
+    wndDiv.appendChild(defaultColumn);
+    wndDiv.appendChild(customColumn);
+
+    mxUtils.br(wndDiv);
+
+    const buttonsDiv = document.createElement('div');
+    buttonsDiv.style.textAlign = 'right';
+    const statusText = document.createElement('div');
+    statusText.style.display = 'inline-block';
+
+    // attempts to save user configuration and displays status text as a side effect
+    // returns true if all went well
+    function saveConfigurationAndDisplayStatus() {
+      let parsed;
+      try {
+        parsed = JSON.parse(customTextArea.value); // may throw
+      } catch (parseErr) {
+        statusText.innerHTML = "Parse error: " + parseErr.toString();
+        statusText.style.color = 'red';
+        return false;
+      }
+      if (typeof parsed !== 'object' || Array.isArray(parsed) || parsed === null) {
+        statusText.innerHTML = "JSON value is not an object";
+        statusText.style.color = 'red';
+        return false;
+      }
+      currentConfig = parsed;
+      window.localStorage.setItem('ftgpmConfig', JSON.stringify(parsed));
+      customTextArea.value = JSON.stringify(parsed, null, 2); // prettify
+      statusText.innerHTML = "Configuration successfully set and saved (in window.localStorage)";
+        statusText.style.color = 'green';
+      return true;
+    }
 
-  const buttonsDiv = document.createElement('div');
+    const applyButton =  mxUtils.button("Apply", saveConfigurationAndDisplayStatus);
+    applyButton.className = 'geBtn gePrimaryBtn';
+    applyButton.style.float = 'none'; // override geBtn style
 
-  const applyButton =  mxUtils.button("Apply", function() {
-    let parsed;
-    try {
-      parsed = JSON.parse(customTextArea.value); // may throw
-    } catch (parseErr) {
-      statusText.innerHTML = "Parse error: " + parseErr.toString();
-      return;
-    }
-    if (typeof parsed !== 'object' || Array.isArray(parsed) || parsed === null) {
-      statusText.innerHTML = "JSON value is not an object";
-      return;
-    }
-    currentConfig = parsed;
-    window.localStorage.setItem('ftgpmConfig', JSON.stringify(parsed));
-    customTextArea.value = JSON.stringify(parsed, null, 2); // prettify
-    statusText.innerHTML = "Configuration successfully set and saved (in window.localStorage)"
-  });
-  applyButton.className = 'geBtn gePrimaryBtn';
-  applyButton.style.float = 'right';
+    const resetButton = mxUtils.button("Reset", function() {
+      customTextArea.value = JSON.stringify(currentConfig, null, 2);
+    });
+    resetButton.className = 'geBtn';
+    resetButton.style.float = 'none'; // override geBtn style
+
+    buttonsDiv.appendChild(statusText);
+    buttonsDiv.appendChild(applyButton);
+    buttonsDiv.appendChild(resetButton);
+
+    // hacks buttons:
+    const forceEdgeStyleButton = mxUtils.button("Apply style rules to all edges", function() {
+      if (saveConfigurationAndDisplayStatus()) {
+        model.beginUpdate();
+        for (const [cellId, cell] of Object.entries(model.cells)) {
+          applyCellStyleFromConfig(cell);
+        }
+        model.endUpdate();
+      }
+    });
+    const forcePortSnapButton = mxUtils.button("Re-snap ports to their activities and re-position port labels", function() {
+      if (saveConfigurationAndDisplayStatus()) {
+        model.beginUpdate();
+        for (const [cellId, cell] of Object.entries(model.cells)) {
+          if (cell.geometry) {
+            // we'll force a geometry update:
+            const newGeometry = cell.geometry.clone();
+            snapToBorderIfCellIsPortAndParentIsActivity(cell, newGeometry);
+            model.setGeometry(cell, newGeometry);
+          }
+        }
+        model.endUpdate();
+      }
+    })
+    forceEdgeStyleButton.className = 'geBtn';
+    forceEdgeStyleButton.style.float = 'none'; // override geBtn style
+    forcePortSnapButton.className = 'geBtn';
+    forcePortSnapButton.style.float = 'none'; // override geBtn style
 
-  const resetButton = mxUtils.button("Reset", function() {
-    customTextArea.value = JSON.stringify(currentConfig, null, 2);
-  });
-  resetButton.className = 'geBtn';
-  resetButton.style.float = 'right';
+    hacksDiv = document.createElement('div');
+    hacksDiv.appendChild(document.createTextNode("Hacks: (use undo if you fuck things up)"));
+    mxUtils.br(hacksDiv);
+    hacksDiv.appendChild(forceEdgeStyleButton);
+    mxUtils.br(hacksDiv);
+    hacksDiv.appendChild(forcePortSnapButton);
 
-  buttonsDiv.appendChild(applyButton);
-  buttonsDiv.appendChild(resetButton);
-  buttonsDiv.appendChild(statusText);
-  windowDiv.appendChild(buttonsDiv);
+    wndDiv.appendChild(buttonsDiv);
+    mxUtils.br(wndDiv);
+    wndDiv.appendChild(hacksDiv);
 
-  const ftgpmConfigWindow = new mxWindow("FTG+PM Plugin Configuration",
-    windowDiv, 100, 100, 640, 480, true, true);
-  ftgpmConfigWindow.destroyOnClose = false;
-  ftgpmConfigWindow.setMaximizable(false);
-  ftgpmConfigWindow.setResizable(false);
-  ftgpmConfigWindow.setClosable(true);
+    return wnd;
+  }
 
-  ftgpmConfigWindow.addListener('show', mxUtils.bind(this, function() {
-    statusText.innerHTML = "";
-  }));
+  const ftgpmConfigWindow = createFtgpmConfigWindow();
 
   function getFromConfig(parameter) {
     if (currentConfig.hasOwnProperty(parameter)) {
@@ -130,19 +184,47 @@ Draw.loadPlugin(function(ui) {
     }
   }
 
-  const model = ui.editor.graph.model;
+
+  // // Force style window:
+  // const forceStyleDiv = document.createElement('div');
+
+  // const forceStyleWindow = new mxWindow("FTG+PM: Force Style Rules",
+  //   forceStyleDiv, 200, 200, 400, 90, true, true);
+  // forceStyleWindow.destroyOnClose = false;
+  // forceStyleWindow.setMaximizable(false);
+  // forceStyleWindow.setResizable(false);
+  // forceStyleWindow.setClosable(true);
+
+  // const forceEdgeStyleButton = mxUtils.button("Apply style rules to all edges", function() {
+  //   for (const [cellId, cell] of Object.entries(model.cells)) {
+  //     applyCellStyleFromConfig(cell);
+  //   }
+  // });
+  // const forcePortSnapButton = mxUtils.button("Re-snap ports to their activities and re-position port labels", function() {
+  //   for (const [cellId, cell] of Object.entries(model.cells)) {
+  //     if (cell.geometry) {
+  //       // we'll force a geometry update:
+  //       const newGeometry = cell.geometry.clone();
+  //       snapToBorderIfCellIsPortAndParentIsActivity(cell, newGeometry);
+  //       model.setGeometry(cell, newGeometry);
+  //     }
+  //   }
+  // })
+  // forceEdgeStyleButton.className = 'geBtn';
+  // forcePortSnapButton.className = 'geBtn';
+  // forceStyleDiv.appendChild(forceEdgeStyleButton);
+  // mxUtils.br(forceStyleDiv);
+  // forceStyleDiv.appendChild(forcePortSnapButton);
 
   window.onkeyup = function(e) {
     // Shortcut to show FTG+PM config:
     if (e.ctrlKey && e.key === '.') {
       ftgpmConfigWindow.show();
     }
-    // Shortcut to apply style from config to all edges:
-    if (e.ctrlKey && e.key === 'Enter') {
-      for (const [cellId, cell] of Object.entries(model.cells)) {
-        applyCellStyleFromConfig(cell);
-      }
-    }
+    // // Shortcut to apply style from config to all edges:
+    // if (e.ctrlKey && e.key === 'Enter') {
+    //   forceStyleWindow.show();
+    // }
   }
 
 
@@ -590,7 +672,7 @@ Draw.loadPlugin(function(ui) {
   }
 
   // Update port shape (so it points in the right direction), move port (and port label) to border of activity.
-  function moveToBorder(cell, cellGeometry, parentGeometry, border) {
+  function snapPortToBorder(cell, cellGeometry, parentGeometry, border) {
     const style = parseStyle(model.getStyle(cell));
     const type = cell.getAttribute(TYPE_ATTR);
     const inOrOut = isInport(type) ? "in" : "out";
@@ -650,23 +732,27 @@ Draw.loadPlugin(function(ui) {
     model.setStyle(cell, unparseStyle(style));
   }
 
+  function snapToBorderIfCellIsPortAndParentIsActivity(cell, geometry) {
+    const type = cell.getAttribute(TYPE_ATTR);
+    if (isPort(type)) {
+      // Port was moved
+      if (cell.parent) {
+        const parentType = cell.parent.getAttribute(TYPE_ATTR);
+        if (isActivityNode(parentType) || isTransformation(parentType)) {
+          // Snap port to activity border
+          const portSnappingAlgorithm = getFromConfig('newPortSnappingAlgorithm') ? closestBorder2 : closestBorder;
+          // border will be one of: 'l', 'r', 't', 'b'
+          const border = portSnappingAlgorithm(geometry, cell.parent.geometry);
+          snapPortToBorder(cell, geometry, cell.parent.geometry, border);
+        }
+      }
+    }    
+  }
+
   ui.editor.graph.addListener(mxEvent.MOVE_CELLS, (_, eventObj) => {
     // High-level event: Happens when the user releases dragged shape(s)
     for (const cell of eventObj.properties.cells) {
-      const type = cell.getAttribute(TYPE_ATTR);
-      if (isPort(type)) {
-        // Port was moved
-        if (cell.parent) {
-          const parentType = cell.parent.getAttribute(TYPE_ATTR);
-          if (isActivityNode(parentType) || isTransformation(parentType)) {
-            // Snap port to activity border
-            const portSnappingAlgorithm = getFromConfig('newPortSnappingAlgorithm') ? closestBorder2 : closestBorder;
-            // border will be one of: 'l', 'r', 't', 'b'
-            const border = portSnappingAlgorithm(cell.geometry, cell.parent.geometry);
-            moveToBorder(cell, cell.geometry, cell.parent.geometry, border);
-          }
-        }
-      }
+      snapToBorderIfCellIsPortAndParentIsActivity(cell, cell.geometry);
     }
   });
 
@@ -702,9 +788,9 @@ Draw.loadPlugin(function(ui) {
             }
             else if (isPort(childType)) {
               // Move contained ports
-              const border = closestBorder2(child.geometry, prevGeometry);
+              const border = closestBorder2(child.geometry, prevGeometry); // < what was the border in the old geometry?
               const newGeometry = child.geometry.clone();
-              moveToBorder(child, newGeometry, cell.geometry, border);
+              snapPortToBorder(child, newGeometry, cell.geometry, border);
               if (getFromConfig('movePortsOnResizeActivity')) {
                 // Scale position
                 switch (border) {
@@ -722,7 +808,7 @@ Draw.loadPlugin(function(ui) {
                     break;
                 }
               }
-              ui.editor.graph.model.setGeometry(child, newGeometry);
+              model.setGeometry(child, newGeometry);
             }
           }            
         }