ftgpm.js 71 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877
  1. Draw.loadPlugin(function(ui) {
  2. const model = ui.editor.graph.model;
  3. const defaultConfig = {
  4. // 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.
  5. movePortsOnResizeActivity: false,
  6. // The new algorithm probably gives better results under all circumstances.
  7. newPortSnappingAlgorithm: true,
  8. // Edge styles.
  9. edgeStyleCtrlFlow: "edgeStyle=orthogonalEdgeStyle;endArrow=classic;rounded=0;html=1;strokeWidth=2;fontSize=14;strokeColor=#004C99;jumpStyle=gap;",
  10. edgeStyleDataFlow: "edgeStyle=orthogonalEdgeStyle;endArrow=open;rounded=0;html=1;strokeWidth=1;fontSize=14;fontColor=#000000;fillColor=#d5e8d4;strokeColor=#6D9656;endFill=0;jumpStyle=gap;",
  11. edgeStyleTypedBy: "edgeStyle=0;endArrow=blockThin;html=1;strokeWidth=1;fontSize=14;fontColor=#000000;dashed=1;endFill=1;endSize=6;strokeColor=#999999;",
  12. edgeStyleParentVersion: "edgeStyle=orthogonalEdgeStyle;endArrow=classic;rounded=0;html=1;fontSize=11;fontColor=#000000;fillColor=#fff2cc;strokeColor=#d6b656;rounded=0;jumpStyle=gap;",
  13. edgeStyleSSLink: "edgeStyle=0;endArrow=blockThin;html=1;fontSize=11;strokeColor=#694B2E;rounded=0;dashed=1;dashPattern=1 4;endFill=1;",
  14. edgeStyleComment: "edgeStyle=0;endArrow=none;dashed=1;html=1;",
  15. portLabelOffsetEdgeDirection: 22,
  16. portLabelOffsetPerpendicularToEdgeDirection: 24,
  17. };
  18. let currentConfig;
  19. const existingConfig = window.localStorage.getItem('ftgpmConfig');
  20. if (existingConfig === null) { // unlike Object and Map, localStorage returns 'null' when item doesn't exist...
  21. currentConfig = {};
  22. } else {
  23. console.log("Have existing ftgpm config...", existingConfig)
  24. currentConfig = JSON.parse(existingConfig);
  25. }
  26. // Configuration dialog window:
  27. function createFtgpmConfigWindow() { // only called once - this is only a function in order not to pollute namespace
  28. // Drawio has no decent abstraction for UI elements or even displaying a dialog window.
  29. // The following is based on code from src/main/webapp/js/diagramly/Dialogs.js
  30. const wndDiv = document.createElement('div');
  31. wndDiv.style.userSelect = 'none';
  32. wndDiv.style.overflow = 'hidden';
  33. wndDiv.style.padding = '10px';
  34. wndDiv.style.height = '100%';
  35. const wnd = new mxWindow("FTG+PM Plugin Configuration",
  36. wndDiv, 100, 100, 640, 570, true, true);
  37. wnd.destroyOnClose = false;
  38. wnd.setMaximizable(false);
  39. wnd.setResizable(false);
  40. wnd.setClosable(true);
  41. wnd.addListener('show', mxUtils.bind(this, function() {
  42. statusText.innerHTML = "";
  43. }));
  44. function createConfig(labelText, json, readonly) {
  45. const columnDiv = document.createElement('div');
  46. columnDiv.style.display = 'inline-block';
  47. const label = document.createElement('label');
  48. const textarea = document.createElement('textarea');
  49. label.setAttribute('for', textarea);
  50. label.innerHTML = labelText;
  51. textarea.setAttribute('wrap', 'off');
  52. textarea.setAttribute('spellcheck', 'false');
  53. textarea.setAttribute('autocorrect', 'off');
  54. textarea.setAttribute('autocomplete', 'off');
  55. textarea.setAttribute('autocapitalize', 'off');
  56. textarea.setAttribute('wrap', 'hard');
  57. if (readonly) {
  58. textarea.setAttribute('readonly', 'true');
  59. }
  60. textarea.value = JSON.stringify(json, null, 2);
  61. textarea.style.overflow = 'auto';
  62. textarea.style.resize = 'none';
  63. textarea.style.width = '300px';
  64. textarea.style.height = '360px';
  65. textarea.style.marginBottom = '16px';
  66. columnDiv.appendChild(label);
  67. mxUtils.br(columnDiv);
  68. columnDiv.appendChild(textarea);
  69. return [columnDiv, textarea];
  70. }
  71. const [defaultColumn] = createConfig("Default config (hardcoded & read-only):", defaultConfig, true);
  72. const [customColumn, customTextArea] = createConfig("Custom config (acts as an 'overlay' on top of default config):", currentConfig, false);
  73. wndDiv.appendChild(defaultColumn);
  74. wndDiv.appendChild(customColumn);
  75. mxUtils.br(wndDiv);
  76. const buttonsDiv = document.createElement('div');
  77. buttonsDiv.style.textAlign = 'right';
  78. const statusText = document.createElement('div');
  79. statusText.style.display = 'inline-block';
  80. // attempts to save user configuration and displays status text as a side effect
  81. // returns true if all went well
  82. function saveConfigurationAndDisplayStatus() {
  83. let parsed;
  84. try {
  85. parsed = JSON.parse(customTextArea.value); // may throw
  86. } catch (parseErr) {
  87. statusText.innerHTML = "Parse error: " + parseErr.toString();
  88. statusText.style.color = 'red';
  89. return false;
  90. }
  91. if (typeof parsed !== 'object' || Array.isArray(parsed) || parsed === null) {
  92. statusText.innerHTML = "JSON value is not an object";
  93. statusText.style.color = 'red';
  94. return false;
  95. }
  96. currentConfig = parsed;
  97. window.localStorage.setItem('ftgpmConfig', JSON.stringify(parsed));
  98. customTextArea.value = JSON.stringify(parsed, null, 2); // prettify
  99. statusText.innerHTML = "Configuration successfully set and saved (in window.localStorage)";
  100. statusText.style.color = 'green';
  101. return true;
  102. }
  103. const applyButton = mxUtils.button("Apply", saveConfigurationAndDisplayStatus);
  104. applyButton.className = 'geBtn gePrimaryBtn';
  105. applyButton.style.float = 'none'; // override geBtn style
  106. const resetButton = mxUtils.button("Reset", function() {
  107. customTextArea.value = JSON.stringify(currentConfig, null, 2);
  108. });
  109. resetButton.className = 'geBtn';
  110. resetButton.style.float = 'none'; // override geBtn style
  111. buttonsDiv.appendChild(statusText);
  112. buttonsDiv.appendChild(applyButton);
  113. buttonsDiv.appendChild(resetButton);
  114. // hacks buttons:
  115. const forceEdgeStyleButton = mxUtils.button("Apply style rules to all edges", function() {
  116. if (saveConfigurationAndDisplayStatus()) {
  117. model.beginUpdate();
  118. for (const [cellId, cell] of Object.entries(model.cells)) {
  119. applyCellStyleFromConfig(cell);
  120. }
  121. model.endUpdate();
  122. }
  123. });
  124. const forcePortSnapButton = mxUtils.button("Re-snap ports to their activities and re-position port labels", function() {
  125. if (saveConfigurationAndDisplayStatus()) {
  126. model.beginUpdate();
  127. for (const [cellId, cell] of Object.entries(model.cells)) {
  128. if (cell.geometry) {
  129. // we'll force a geometry update:
  130. const newGeometry = cell.geometry.clone();
  131. snapToBorderIfCellIsPortAndParentIsActivity(cell, newGeometry);
  132. model.setGeometry(cell, newGeometry);
  133. }
  134. }
  135. model.endUpdate();
  136. }
  137. })
  138. forceEdgeStyleButton.className = 'geBtn';
  139. forceEdgeStyleButton.style.float = 'none'; // override geBtn style
  140. forcePortSnapButton.className = 'geBtn';
  141. forcePortSnapButton.style.float = 'none'; // override geBtn style
  142. hacksDiv = document.createElement('div');
  143. hacksDiv.appendChild(document.createTextNode("Hacks: (use undo if you fuck things up)"));
  144. mxUtils.br(hacksDiv);
  145. hacksDiv.appendChild(forceEdgeStyleButton);
  146. mxUtils.br(hacksDiv);
  147. hacksDiv.appendChild(forcePortSnapButton);
  148. wndDiv.appendChild(buttonsDiv);
  149. mxUtils.br(wndDiv);
  150. wndDiv.appendChild(hacksDiv);
  151. return wnd;
  152. }
  153. const ftgpmConfigWindow = createFtgpmConfigWindow();
  154. function getFromConfig(parameter) {
  155. if (currentConfig.hasOwnProperty(parameter)) {
  156. return currentConfig[parameter];
  157. } else {
  158. return defaultConfig[parameter];
  159. }
  160. }
  161. // // Force style window:
  162. // const forceStyleDiv = document.createElement('div');
  163. // const forceStyleWindow = new mxWindow("FTG+PM: Force Style Rules",
  164. // forceStyleDiv, 200, 200, 400, 90, true, true);
  165. // forceStyleWindow.destroyOnClose = false;
  166. // forceStyleWindow.setMaximizable(false);
  167. // forceStyleWindow.setResizable(false);
  168. // forceStyleWindow.setClosable(true);
  169. // const forceEdgeStyleButton = mxUtils.button("Apply style rules to all edges", function() {
  170. // for (const [cellId, cell] of Object.entries(model.cells)) {
  171. // applyCellStyleFromConfig(cell);
  172. // }
  173. // });
  174. // const forcePortSnapButton = mxUtils.button("Re-snap ports to their activities and re-position port labels", function() {
  175. // for (const [cellId, cell] of Object.entries(model.cells)) {
  176. // if (cell.geometry) {
  177. // // we'll force a geometry update:
  178. // const newGeometry = cell.geometry.clone();
  179. // snapToBorderIfCellIsPortAndParentIsActivity(cell, newGeometry);
  180. // model.setGeometry(cell, newGeometry);
  181. // }
  182. // }
  183. // })
  184. // forceEdgeStyleButton.className = 'geBtn';
  185. // forcePortSnapButton.className = 'geBtn';
  186. // forceStyleDiv.appendChild(forceEdgeStyleButton);
  187. // mxUtils.br(forceStyleDiv);
  188. // forceStyleDiv.appendChild(forcePortSnapButton);
  189. window.onkeyup = function(e) {
  190. // Shortcut to show FTG+PM config:
  191. if (e.ctrlKey && e.key === '.') {
  192. ftgpmConfigWindow.show();
  193. }
  194. // // Shortcut to apply style from config to all edges:
  195. // if (e.ctrlKey && e.key === 'Enter') {
  196. // forceStyleWindow.show();
  197. // }
  198. }
  199. // For auto-updating port orientation
  200. // Note: this is only the 'shape', not the full style
  201. const encodedPortShapes = {
  202. in: {
  203. l: `stencil(pZNdD4IgFIZ/DbcNYa3rZvU/yE7JRHBAX/++Y0c3zXC2btzO+4wHfFEm81CqBpjgJZM7JoRc4wPHO42C06hCA0Wk7Ka8VkcDREL0roK7PsXOoG0JXseWyj3jW9Y6OJN54axFiXY2jMiAo0xpi2v5g2Td9s9uWnWna3CHGiJ4yjNKmTgsFmf/iGWOSep1ZH5URXXx7mpP01VJdnYevoAeN6rtdwJ6XLsbJFuba6cXGG0Hgg9Dtun7+UUydshFhsK4ADNXiXmiCWxQG0NfY+rKJh2/U/oD3sEL)`,
  204. r: `stencil(pZNdD4IgFIZ/DbcNYa3rZvU/yE7JQnBAVv++o0eXH9ls3bid9xkP8qJMpiFXJTDBcyZ3TAi5xgeOdxoFp1GFErJIWaW8VkcDREL07gp3fYqtQdscvI41lXvGt6x2cCbTzFmLEu1sGJAeR5nSFtfyB8na7Z/ttGrfrsQdCojgKU8oZeKwWJz8I5YpJnPHkelRZdeLdzd7mq6aZWfn4QPocKnqfiegw4Wr4H24cW3f6ukMRtueYShINl0/vzhGErlIkRkX4MtdYj5TBVaojaHPce7OJiU3Kf0CTfAC)`,
  205. t: `stencil(nZPRDoIgGIWfhtuGsNZ1s3oP0r9kIjggrbfv1183S23VDds5Z3zAAZhMQ6FqYIIXTB6YEILjgLIlKbckVaghi+Q1ymt1NkBJiN6V0Oo8DgRtC/A6dqk8Mr5nHZIzmWbOWoRoZ8NLMskRprTFufxOML4ZNvAYNKkaV6gggic3IZeJ09/g5BewTNFZO45Mzyorr97dbD6ftZpdnIeFYIxr1fU7C8a4cg1MDrfU2XI7I8BoOwHIxdZ/ISS7t4K/g2TGBfhwleivNIENamPoNa5d2azj3qUf0BtP)`,
  206. b: `stencil(nZNRD4IgFIV/Da8NYa3nZvU/EG/JQnBAWv++q1c3m9pWL27nnPHJPQCTeaxUA0zwiskTE0Jw/KDsSMo9SRUb0Im8VgWjCguUxBT8HTpTppFgXAXBpD6VZ8aPrEdyJnPtnUOI8S5+JLMcYco4XMufBOO7cQOvUZNq8A81JAjkZuQycfkbnP0Cljk6W+PIvFD6fgv+4crlqs3s6gOsBFPcqL7fRTDFtW9hNtzHaJNcr2ciWONmhOywXvwvDLn/Yxva+ghfzhL9jSqwQmMtXcetM1uUPLj0BAbjDQ==)`,
  207. }
  208. };
  209. encodedPortShapes.out = {
  210. r: encodedPortShapes.in.l,
  211. l: encodedPortShapes.in.r,
  212. t: encodedPortShapes.in.b,
  213. b: encodedPortShapes.in.t,
  214. }
  215. // Styles for our different edge types
  216. // Mapping from 'pmRole' attribute to key in config.
  217. const edgeStyles = {
  218. ctrl_flow: 'edgeStyleCtrlFlow',
  219. data_flow: 'edgeStyleDataFlow',
  220. typed_by: 'edgeStyleTypedBy',
  221. parent_version: 'edgeStyleParentVersion',
  222. comment_edge: 'edgeStyleComment',
  223. };
  224. function applyCellStyleFromConfig(cell) {
  225. const ftgpmType = cell.getAttribute(TYPE_ATTR);
  226. if (ftgpmType) {
  227. if (edgeStyles.hasOwnProperty(ftgpmType)) {
  228. setEdgeStyle(cell, ftgpmType);
  229. }
  230. // TODO: do the same on nodes.
  231. }
  232. }
  233. const TYPE_ATTR = "pmRole"; // cell attribute holding the ftgpm type
  234. function parseStyle(str) {
  235. const pairs = str.split(';').filter(s => s !== '');
  236. const map = new Map();
  237. pairs.map(pair => {
  238. const [key,value] = pair.split('=');
  239. map.set(key, value);
  240. });
  241. return map;
  242. }
  243. function unparseStyle(map) {
  244. let str = "";
  245. for (const [key, value] of map.entries()) {
  246. if (value === undefined) {
  247. str += key + ';';
  248. } else {
  249. str += key + '=' + value + ';';
  250. }
  251. }
  252. return str;
  253. }
  254. // copy selected keys (=array) from fromMap to toMap
  255. function mapAssign(toMap, fromMap, keys) {
  256. for (const key of keys) {
  257. const value = fromMap.get(key);
  258. if (value !== undefined) {
  259. toMap.set(key, value);
  260. }
  261. }
  262. }
  263. function reverseEdge(edge, sourceCell, targetCell) {
  264. // Reverse source and target
  265. ui.editor.graph.model.setTerminals(edge, targetCell, sourceCell);
  266. // Reverse 'waypoints'
  267. if (edge.geometry.points) {
  268. const newGeometry = edge.geometry.clone();
  269. newGeometry.points.reverse();
  270. ui.editor.graph.model.setGeometry(edge, newGeometry);
  271. }
  272. // Reverse entry and exit points
  273. const oldStyle = parseStyle(model.getStyle(edge));
  274. const newStyle = new Map(oldStyle);
  275. // entry becomes exit
  276. newStyle.set('entryX', oldStyle.get('exitX'));
  277. newStyle.set('entryY', oldStyle.get('exitY'));
  278. // exit becomes entry
  279. newStyle.set('exitX', oldStyle.get('entryX'));
  280. newStyle.set('exitY', oldStyle.get('entryY'));
  281. // entryD becomes exitD
  282. newStyle.set('entryDx', oldStyle.get('exitDx'));
  283. newStyle.set('entryDy', oldStyle.get('exitDy'));
  284. // exitD becomes entryD
  285. newStyle.set('exitDx', oldStyle.get('entryDx'));
  286. newStyle.set('exitDy', oldStyle.get('entryDy'));
  287. model.setStyle(edge, unparseStyle(newStyle));
  288. }
  289. // Simply sets the data attribute TYPE_ATTR to ftgpm_type.
  290. function setFtgpmType(cell, ftgpm_type) {
  291. // Set type attribute
  292. let value = model.getValue(cell);
  293. if (!value) {
  294. // Workaround: 'value' must be an XML element
  295. value = mxUtils.createXmlDocument().createElement('object');
  296. value.setAttribute('label', '');
  297. }
  298. value.setAttribute(TYPE_ATTR, ftgpm_type);
  299. model.setValue(cell, value);
  300. }
  301. function setEdgeStyle(edge, style_type) {
  302. // Update style
  303. // Workaround: don't overwrite connection points
  304. const oldstyle = parseStyle(model.getStyle(edge));
  305. const newstyle = new Map(parseStyle(getFromConfig(edgeStyles[style_type])));
  306. mapAssign(newstyle, oldstyle, [
  307. // retain these properties from oldstyle:
  308. "entryX", "entryY", "entryDx", "entryDy",
  309. "exitX", "exitY", "exitDx", "exitDy"
  310. ]);
  311. model.setStyle(edge, unparseStyle(newstyle));
  312. }
  313. function isArtifact(type) {
  314. return type === "artifact";
  315. }
  316. function isFormalism(type) {
  317. return type === "formalism";
  318. }
  319. function isTransformation(type) {
  320. return type === "auto_transformation" || type === "transformation" || type === "comp_transformation";
  321. }
  322. function isActivityNode(type) {
  323. return type === "autom_activity" || type === "activity" || type === "comp_activity";
  324. }
  325. function isControlFlowPort(type) {
  326. return type === "ctrl_in" || type === "ctrl_out";
  327. }
  328. function isDataPort(type) {
  329. return type === "data_in" || type === "data_out";
  330. }
  331. function isInport(type) {
  332. return type === "data_in" || type === "ctrl_in";
  333. }
  334. function isOutport(type) {
  335. return type === "data_out" || type === "ctrl_out";
  336. }
  337. function isPort(type) {
  338. return ["data_in", "data_out", "ctrl_in", "ctrl_out"].includes(type);
  339. }
  340. function isControlFlowNode(type) {
  341. return isControlFlowPort(type) || type === "initial" || type === "final" || type === "fork_join";
  342. }
  343. function isDataFlowNode(type) {
  344. return isDataPort(type) || isArtifact(type) || isFormalism(type);
  345. }
  346. function isTraceEvent(type) {
  347. return type === "traceevent_begin" || type === "traceevent_end";
  348. }
  349. function isArtifactVersion(type) {
  350. return type === "artifact_version";
  351. }
  352. function getFlowType(type) {
  353. if (isDataFlowNode(type)) {
  354. return "data";
  355. }
  356. if (isControlFlowNode(type)) {
  357. return "ctrl";
  358. }
  359. // throw new Error("unknown flow type");
  360. console.log("unknown flow type");
  361. }
  362. // [ condition, flow-type ]
  363. const edgeTypeRules = [
  364. // PM control flow
  365. [ (src, tgt) => isControlFlowNode(src) && isControlFlowNode(tgt), "ctrl_flow" ],
  366. // PM data flow
  367. [ (src, tgt) => isDataFlowNode(src) && isDataFlowNode(tgt), "data_flow" ],
  368. // FTG data flow
  369. [ (src, tgt) => isTransformation(src) && isFormalism(tgt), "data_flow" ],
  370. [ (src, tgt) => isFormalism(src) && isTransformation(tgt), "data_flow" ],
  371. [ (src,tgt) => isActivityNode(src) && isTransformation(tgt), "typed_by" ],
  372. [ (src,tgt) => isArtifact(src) && isFormalism(tgt), "typed_by" ],
  373. [ (src,tgt) => isTraceEvent(src) && isArtifactVersion(tgt), "data_flow"],
  374. [ (src,tgt) => isArtifactVersion(src) && isTraceEvent(tgt), "data_flow"],
  375. [ (src,tgt) => isTraceEvent(src) && isTraceEvent(tgt), "ctrl_flow"],
  376. [ (src,tgt) => src === "traceevent_begin" && tgt === "ctrl_in", "typed_by"],
  377. [ (src,tgt) => src === "traceevent_end" && tgt === "ctrl_out", "typed_by"],
  378. [ (src,tgt) => isArtifactVersion(src) && isArtifact(tgt), "typed_by"],
  379. [ (src,tgt) => isArtifactVersion(src) && isArtifactVersion(tgt), "parent_version"],
  380. [ (src,tgt) => isArtifactVersion(src) && tgt === "storage", "ss_link"],
  381. [ (src,tgt) => isArtifactVersion(src) && tgt === "real_object", "ss_link"],
  382. [ (src,tgt) => src === "traceevent_begin" && tgt === "service", "ss_link"],
  383. [ (src,tgt) => src === "comment" || tgt === "comment", "comment_edge"],
  384. ];
  385. // Very specific: This function is used to determine the correct direction of an arrow automatically
  386. // An activity or artifact at the highest level (= a direct child of a layer) has level 0
  387. // Every time something is nested in an activity, the level increases by 1.
  388. // Ports are special: they can only be a child of an activity, but they have the same level as the activity.
  389. function getNestedLevel(cell) {
  390. function getLvlRecursive(cell) {
  391. if (!cell.parent) {
  392. throw Error("getNestedLevel called with parentless cell (i.e. cell is root, or is deleted)");
  393. }
  394. if (model.isLayer(cell)) {
  395. return 0;
  396. // throw Error("getNestedLevel called with layer cell");
  397. }
  398. if (cell.parent === model.isLayer(cell.parent)) {
  399. return 0;
  400. }
  401. else if (isActivityNode(cell.parent.getAttribute(TYPE_ATTR))) {
  402. return getLvlRecursive(cell.parent) + 1;
  403. }
  404. else {
  405. return getLvlRecursive(cell.parent);
  406. }
  407. }
  408. const baseLevel = getLvlRecursive(cell);
  409. if (isPort(cell.getAttribute(TYPE_ATTR))) {
  410. return baseLevel - 1;
  411. } else {
  412. return baseLevel;
  413. }
  414. }
  415. function isDirectionless(type) {
  416. return isArtifact(type) || type === "fork_join";
  417. }
  418. ui.editor.graph.addListener(mxEvent.CELL_CONNECTED, (_, eventObj) => {
  419. // Happens whenever an edge is (dis)connected.
  420. const edge = eventObj.properties.edge;
  421. const sourceCell = edge.source;
  422. const targetCell = edge.getTerminal();
  423. // This will change the edge style WITHIN the transaction of the edit operation.
  424. // The terminal-change and style-change will be one edit operation from point of view of undo manager.
  425. const sourceType = sourceCell ? sourceCell.getAttribute(TYPE_ATTR) : null;
  426. const targetType = targetCell ? targetCell.getAttribute(TYPE_ATTR) : null;
  427. // Update style if necessary
  428. for (const [cond, linkType] of edgeTypeRules) {
  429. if (cond(sourceType, targetType)) {
  430. setFtgpmType(edge, linkType);
  431. setEdgeStyle(edge, linkType);
  432. }
  433. }
  434. if (sourceCell && targetCell) {
  435. // Auto-correct edge direction in certain cases.
  436. // We call 'reverseEdge' if we have an illegal situation, but reversing the arrow gives a legal situation.
  437. const sourceLvl = getNestedLevel(sourceCell);
  438. const targetLvl = getNestedLevel(targetCell);
  439. // We have 2 flow types: ctrl and data. They remain segregated.
  440. if (getFlowType(sourceType) === getFlowType(targetType)) {
  441. if (sourceLvl === targetLvl) {
  442. // Equally nested ...
  443. if (isInport(sourceType) && isOutport(targetType)
  444. || isInport(sourceType) && isDirectionless(targetType)
  445. || isDirectionless(sourceType) && isOutport(targetType)) {
  446. reverseEdge(edge, sourceCell, targetCell);
  447. }
  448. }
  449. else if (sourceLvl === targetLvl + 1) {
  450. // Source of arrow is nested deeper ...
  451. if (isInport(sourceType) && isInport(targetType)
  452. || isDirectionless(sourceType) && isInport(targetType)) {
  453. // Should flow from shallowly nested input port to deeply nested input port:
  454. reverseEdge(edge, sourceCell, targetCell);
  455. }
  456. }
  457. else if (sourceLvl + 1 === targetLvl) {
  458. // Target of arrow is nested deeper ...
  459. if (isOutport(sourceType) && isOutport(targetType)
  460. || isOutport(sourceType) && isDirectionless(targetType)) {
  461. // Should flow from deeply nested output port to shallowly nested output port
  462. reverseEdge(edge, sourceCell, targetCell);
  463. }
  464. }
  465. }
  466. }
  467. });
  468. function outsideOffset(wOrH) {
  469. return 0;
  470. }
  471. function centerOffset(wOrH) {
  472. return -wOrH / 2;
  473. }
  474. function insideOffset(wOrH) {
  475. return -wOrH;
  476. }
  477. // Change this to position ports outside, inside or centered at the edge.
  478. const borderOffset = centerOffset;
  479. function rightOrBottomBorder(wOrH, parentWorH) {
  480. return parentWorH + borderOffset(wOrH);
  481. }
  482. function leftOrTopBorder(wOrH) {
  483. return -wOrH - borderOffset(wOrH);
  484. }
  485. function cellCenter(cellGeometry) {
  486. // Coordinates are relative to topleft corner of parent shape
  487. const {x,y, width, height} = cellGeometry;
  488. // Center of cell
  489. return {
  490. x: x + width/2,
  491. y: y + height/2,
  492. };
  493. }
  494. // Deprecated snapping algorithm
  495. function closestBorder(cellGeometry, parentGeometry) {
  496. // Cell center is relative to parent: (0,0) is topleft
  497. const c = cellCenter(cellGeometry);
  498. // 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.
  499. //
  500. // 2
  501. // /
  502. // +----+
  503. // |\ t/|
  504. // |l\/ |
  505. // | /\r|
  506. // |/b \|
  507. // +----+
  508. // \
  509. // 1
  510. const slope = parentGeometry.height / parentGeometry.width;
  511. const above1 = c.y < slope * c.x;
  512. const above2 = c.y < parentGeometry.height - slope * c.x;
  513. if (above1)
  514. if (above2)
  515. return 't';
  516. else
  517. return 'r';
  518. else
  519. if (above2)
  520. return 'l';
  521. else
  522. return 'b';
  523. }
  524. // Improved snapping algorithm
  525. function closestBorder2(cellGeometry, parentGeometry) {
  526. // Cell center is relative to parent: (0,0) is topleft
  527. const c = cellCenter(cellGeometry);
  528. const slope = 1; // fixed slope, 45 degrees
  529. const above1 = c.y < slope * c.x;
  530. const above3 = c.y < (-slope) * c.x + parentGeometry.height ;
  531. const above4 = c.y < slope * (c.x - parentGeometry.width) + parentGeometry.height;
  532. const above2 = c.y < (-slope) * (c.x - parentGeometry.width);
  533. if (parentGeometry.width >= parentGeometry.height) {
  534. // Parent's width > height
  535. //
  536. // 1 2
  537. // \ /
  538. // +------+ --> x
  539. // |\ t /|
  540. // |l\__/ |__ 5
  541. // | / \r|
  542. // |/ b \|
  543. // +------+
  544. // / \
  545. // 3 4
  546. // |
  547. // V
  548. // y
  549. const above5 = c.y < parentGeometry.height / 2;
  550. if (!above1 && above3) {
  551. return 'l';
  552. }
  553. if (!above2 && above4) {
  554. return 'r';
  555. }
  556. if (above1 && above2 && above5) {
  557. return 't';
  558. }
  559. if (!above3 && !above4 && !above5) {
  560. return 'b';
  561. }
  562. }
  563. else {
  564. // Parent's height > width
  565. // 1 2
  566. // \ /
  567. // +----+
  568. // |\ t/|
  569. // | \/ |
  570. // |l | |
  571. // | | |
  572. // | /\r|
  573. // |/b \|
  574. // +----+
  575. // / | \
  576. // 3 5 4
  577. const leftOf5 = c.x < parentGeometry.width / 2;
  578. if (above1 && above2) {
  579. return 't';
  580. }
  581. if (!above3 && !above4) {
  582. return 'b';
  583. }
  584. if (above3 && !above1 && leftOf5) {
  585. return 'l';
  586. }
  587. if (above4 && !above2 && !leftOf5) {
  588. return 'r';
  589. }
  590. }
  591. }
  592. // Update port shape (so it points in the right direction), move port (and port label) to border of activity.
  593. function snapPortToBorder(cell, cellGeometry, parentGeometry, border) {
  594. const style = parseStyle(model.getStyle(cell));
  595. const type = cell.getAttribute(TYPE_ATTR);
  596. const inOrOut = isInport(type) ? "in" : "out";
  597. style.set('shape', encodedPortShapes[inOrOut][border]);
  598. switch (border) {
  599. case 't':
  600. cellGeometry.width = 35;
  601. cellGeometry.height = 20;
  602. cellGeometry.y = leftOrTopBorder(cellGeometry.height);
  603. style.set('align', 'right');
  604. style.set('verticalAlign', 'bottom');
  605. cellGeometry.offset = new mxPoint(
  606. -getFromConfig('portLabelOffsetPerpendicularToEdgeDirection'),
  607. -getFromConfig('portLabelOffsetEdgeDirection'));
  608. break;
  609. case 'r':
  610. cellGeometry.width = 20;
  611. cellGeometry.height = 35;
  612. cellGeometry.x = rightOrBottomBorder(cellGeometry.width, parentGeometry.width);
  613. style.set('align', 'left');
  614. style.set('verticalAlign', 'bottom');
  615. cellGeometry.offset = new mxPoint(
  616. getFromConfig('portLabelOffsetEdgeDirection'),
  617. -getFromConfig('portLabelOffsetPerpendicularToEdgeDirection'));
  618. break;
  619. case 'l':
  620. cellGeometry.width = 20;
  621. cellGeometry.height = 35;
  622. cellGeometry.x = leftOrTopBorder(cellGeometry.width);
  623. style.set('align', 'right');
  624. style.set('verticalAlign', 'top');
  625. cellGeometry.offset = new mxPoint(
  626. -getFromConfig('portLabelOffsetEdgeDirection'),
  627. getFromConfig('portLabelOffsetPerpendicularToEdgeDirection'));
  628. break;
  629. case 'b':
  630. cellGeometry.width = 35;
  631. cellGeometry.height = 20;
  632. cellGeometry.y = rightOrBottomBorder(cellGeometry.height, parentGeometry.height);
  633. style.set('align', 'left');
  634. style.set('verticalAlign', 'top');
  635. cellGeometry.offset = new mxPoint(
  636. getFromConfig('portLabelOffsetPerpendicularToEdgeDirection'),
  637. getFromConfig('portLabelOffsetEdgeDirection'));
  638. break;
  639. }
  640. model.setStyle(cell, unparseStyle(style));
  641. }
  642. function snapToBorderIfCellIsPortAndParentIsActivity(cell, geometry) {
  643. const type = cell.getAttribute(TYPE_ATTR);
  644. if (isPort(type)) {
  645. // Port was moved
  646. if (cell.parent) {
  647. const parentType = cell.parent.getAttribute(TYPE_ATTR);
  648. if (isActivityNode(parentType) || isTransformation(parentType)) {
  649. // Snap port to activity border
  650. const portSnappingAlgorithm = getFromConfig('newPortSnappingAlgorithm') ? closestBorder2 : closestBorder;
  651. // border will be one of: 'l', 'r', 't', 'b'
  652. const border = portSnappingAlgorithm(geometry, cell.parent.geometry);
  653. snapPortToBorder(cell, geometry, cell.parent.geometry, border);
  654. }
  655. }
  656. }
  657. }
  658. ui.editor.graph.addListener(mxEvent.MOVE_CELLS, (_, eventObj) => {
  659. // High-level event: Happens when the user releases dragged shape(s)
  660. for (const cell of eventObj.properties.cells) {
  661. snapToBorderIfCellIsPortAndParentIsActivity(cell, cell.geometry);
  662. }
  663. });
  664. ui.editor.graph.addListener(mxEvent.RESIZE_CELLS, (_, eventObj) => {
  665. // High-level event: Happens when the user resized cell(s)
  666. for (let i=0; i<eventObj.properties.cells.length; i++) {
  667. const cell = eventObj.properties.cells[i];
  668. const type = cell.getAttribute(TYPE_ATTR);
  669. if (isActivityNode(type) || isTransformation(type)) {
  670. // Activity was resized...
  671. // In drawio, 'arcSize' of a rounded rectangle is relative to size of shape.
  672. // Retain apparent arc size of activity:
  673. const style = parseStyle(model.getStyle(cell));
  674. const newArcSize = 1000 / Math.sqrt(cell.geometry.width * cell.geometry.height);
  675. style.set("arcSize", newArcSize);
  676. model.setStyle(cell, unparseStyle(style));
  677. if (cell.children) {
  678. // Need this for moving contained ports:
  679. const prevGeometry = eventObj.properties.previous[i];
  680. const scaleW = cell.geometry.width / prevGeometry.width;
  681. const scaleH = cell.geometry.height / prevGeometry.height;
  682. for (const child of cell.children) {
  683. const childType = child.getAttribute(TYPE_ATTR);
  684. // Keep activity icon in place
  685. if (childType === "activityIcon") {
  686. newIconGeometry = child.geometry.clone();
  687. newIconGeometry.x = cell.geometry.width - 28;
  688. newIconGeometry.y = 4;
  689. model.setGeometry(child, newIconGeometry);
  690. }
  691. else if (isPort(childType)) {
  692. // Move contained ports
  693. const border = closestBorder2(child.geometry, prevGeometry); // < what was the border in the old geometry?
  694. const newGeometry = child.geometry.clone();
  695. snapPortToBorder(child, newGeometry, cell.geometry, border);
  696. if (getFromConfig('movePortsOnResizeActivity')) {
  697. // Scale position
  698. switch (border) {
  699. case 't':
  700. newGeometry.x = child.geometry.x * scaleW;
  701. break;
  702. case 'l':
  703. newGeometry.y = child.geometry.y * scaleH;
  704. break;
  705. case 'b':
  706. newGeometry.x = child.geometry.x * scaleW;
  707. break;
  708. case 'r':
  709. newGeometry.y = child.geometry.y * scaleH;
  710. break;
  711. }
  712. }
  713. model.setGeometry(child, newGeometry);
  714. }
  715. }
  716. }
  717. }
  718. }
  719. });
  720. // Hardcoded primitive shape library.
  721. // To alter, make changes to the library in the drawio webapp, then 'save' (this downloads the library as XML), and copy+paste the contents of the XML file below:
  722. const encodedLibCommon = `<mxlibrary>[
  723. {"xml":"dVLNToQwEH6aXk1L0Q1Hl9U9mRj34NF0YRaqhdm0o4JPb6GUhegeSGbm+xvaMpk33d6qc/2EJRgmH5jMLSKFqulyMIYlXJdM7liScP+x5PEKKkaUn5WFlv4R4PEdCvIMo45D2G4ER0nzggbCpCBr3k4GvwMUzUfmOtdRH0VQVnCYWs7kFtry3lrvIXeFUc7pwg9raoZU4UtHFj/gVZdU+4k33p6wpYP+GQwEnxk5GrRjhOQ8zbPMI2GvIfHqb4vFsnvABsj2nmLBKNJfa51yoa1m3ix9Rt0O59UHRsrXCoeftoCJtLyXqOui7oZzeZtkqRCbu3SzdiFlK6A/Lr5Y7H4ZjWcf2+lGY3t5OUG9fFi/","w":40.0035294117647,"h":40,"aspect":"fixed","title":"Control Flow"},
  724. {"xml":"dZJNb4MwDIZ/Te6BULQdN1h7mjSthx2ntHFptoBR8DbYr18+gILWIiH5431sJw4TRd3vrGzPz6jAMPHERGERKVp1X4AxLOVaMVGyNOXuZ+n2RjYJWd5KCw1dAfDwAUdyCiMPvlkZkgGpX9FAjChJ8v1k8CempuJBue7b0TBBoCrYjy5n4hEa9WCtqyFKbKFxkTPVvmXizI4sfsKbVnQeIydsaK9/PZ1ko1+gQRuqCx4+H9fGLOJqA3cqmysuMnl5n2/yOMjWQeNY8Uh+2Js3lizOuQOsgezgJBaMJP295mQX3WrWzegL6sZf9RAVGV8THX7ZI4yi5Uonrr/OkbQV0D/OGYtpL6GwqMkd1z+5l2cW6eUr/AM=","w":40,"h":40,"aspect":"fixed","title":"Data Flow"},
  725. {"xml":"dVJNT8MwDP01uafNhMQROrYTEmJIHFHamDZbWlepgZZfT5qkX2KLFMnP9vNzHDOR1f3RyrZ6RgWGiScmMotIwar7DIxhKdeKiT1LU+4uSw83oomP8lZaaOgKAfMzFOQyjMxHsb0Pekr9igaCh4YW1Ec+hMhU2yduZTsaJg6oEk4RciYeoVEP1uKPg7nB4vJW6ca5K6pH2cSZHVm8wLtWVEXPJzZ00r9jiWQXcYYGrZcQ3B/nV7KrQEWSEzpoYxYUK9zNEqsS9/64SHja2PTNwSWr9x4BayDrJsItGEn6e8uTXYDlnDdTX1A348SHkLHjW0aHX7aAmLT+2YnXX+eRtCXQP54zVt0uLv9hE4xbMMFl2wJ7vYx/","w":40,"h":40,"aspect":"fixed","title":"Typed-by"},
  726. {"xml":"dVRtc6IwEP41zNx96Ex4EY+PyptFi56tBf1yEyBCLBAuiQL99ZcotHbmOqMz++zus3mS3UXR7arzKWyKJ5KhUtFdRbcpIfxmVZ2NylLRAM4U3VE0DYi/onnfRNVrFDSQopr/h0CSE0q5yChhIg9zrsFJQyivYYWEORQYHdeMFNeDu4QpKkiZIcq+HlhtSTlmc1r+GRmjME3Cr5oZ70cKK2AjTcZRneLyR3MIM8d4zL3Hg5DuJGm4hzo9XHYC9e402C7ms1heSpuL3x6k+ntsawl/XwtotIuj51a/VPvvvDnlweFpGpBtarSbxdrQbGAW9gxEb9Ml/PX6ljpbdwUoWU43LF4THwQxQ1F/0k+RtTfXQTgxYOfheK+dnvCMgK7B2mWSay+ZxaxzVEc11O2FjwOjP4XL6iVnXnUUyrAfrZ9Rox4Wk902jrtp1WyA+hpk9R5dyAyFJuVZG5jxiiWBd06gmca+DxZ5bjn8XE/FPXa7PmOFV7ClMXPCGMcVtqPutw/C416E6TIoZGPlmxBHZ+7qp6LP2wJz9NyINonnbMVYCV/BK9lqVZjioTPSCgAEoIjhd5hcm3DD6ZkyfEFbGRi9FbnInNUwMLJKhTh0M8zHopySN2STktBrO3UADNuyPiIRznghIpo8g5zrDGVD7SMuyzueY7iqN5E8cQFc5y9EyHceTOFJSYXTUSfhkN/pvk3aBVGOum/XQL0bQB8RcQXai5R20CZH9LYpoEA4LwaWPuwDHMY9/2B+FNsQXMuNGo5+0KwbpR+wan4tQY5Hhvj9UgrjTtOn67onIxw2d4SfX4gb+/4D8g8=","w":20,"h":35,"aspect":"fixed","title":"Control Input Port"},
  727. {"xml":"dVRbc6IwFP41zOw+dCZc1PKo3CxadG0t6MtOgAixQNgkCvTXN1GwdmY7AzPnO9cvOedE0a2y9Sis82eSokLRHUW3KCH8KpWthYpC0QBOFd1WNA2IX9HcH6zqxQpqSFHF/xNA4iNKuPAoYCyL2RfjqCaUV7BEQuwTDIqLR0JOvNcXMEE5KVJE2feK5YYUgzunxd9byEBNk/A7a8a7IYblsJYi46hKcPGr3gepbTxl7tNekLfjJNhBne7PW4E6Z+Jv5rNpJI+lzcS3A4n+EVlazD9WAhrN/OA65aNq/ZvVx8zfP098skmMZj1fGZoFxrk1BeH7ZAEf394Te+MsASWLyZpFK+IBP2Io7I76MTR345UfjAzYujjaacdnPCWgrbF2HmXaa2oy8xRWYQV1a+5h3+iOwaJ8zZhbHgQz7IWrF1Sr+/lou4midlLWa6C++Wm1Q2cyRcGY8rTxx9GSxb57iuE4iTwPzLPMtPmpmohzbLddynI3ZwtjagcRjkpshe0fDwSHnTDThZ/L1so7IbbOnOVvRZ81OebopRZ9EtfZiMESupyXstmqEMVFp6QRAAhAEcMfML404YqTE2X4jDbSMGhLcpY+y35kZJYSceikmA9JOSXvyCIFoZd26gAYlmneLCFOeS4smqxBTlWK0j73ARfFXZxtOKo7knHiALjKXomgbz+MhSYhJU4GnoRDfsf7OmlnRDlqf1wE9W4APUTEEWgnXJqemxzR666AHOEs76P0fiNgP+/ZLfKWbE1wJXeqLz3U7q7wQTW/ZyCHA0P8fiuFcEfpS3VZkwH2qzvAryfiGn3/gnwC","w":20,"h":35,"aspect":"fixed","title":"Control Output Port"},
  728. {"xml":"dVRtk6IwDP41zNx98KZQRP2ovLnoqueuC/rlpkCBukC5torur78isLoztyPO5EnS5EmTVIFmcXEZqrJnGuNcgbYCTUapaKXiYuI8VzRAYgVaiqYB+Vc05xurerOCCjFciv8coOERR0J65Chsklk347CiTJSowFLsAvSKm0dMyk6dowhnNI8x418TFlua995IoD/9iZ6Y1sCvnLm49kd4hqpG5AKXEcl/VIdVbOlPqfN0kNStMFrtEWSH806iqz3ytvPZNGiK0mby24MIfgSmFoqPtYR6PU8cuxir5t9ZdUy9w/PIo9tIrzfzta6ZwMjMKfDfRws0fnuPrK29BIwuRhserKkLvIBj/3qER3+yN9beaqiji0OCvXZ8JlMKLhXRzsNUe40nfHLyS79E0Jy7xNOvx9WieE25UySSGXH99Quu1MN8uNsGwWVUVBugvnlxucdnOsUrg4m49oxgyUPPOYXIiALXBfM0nVjiVI5kHbvdNeaZk/GFPrVWAQkKYvqX3y5YJXtpZgsvaxrb3Am1ILeXPxU4qzMi8Esl2ySvs5ZjJXWZKJpWq1KUFx3TWgIgAcOcfKDw1oQWRyfGyRlvG0OvLei58Vl2A9NEKbBAdkxEH1Qw+o5NmlN2ayc0rIkxND4tPolF1vkyeipjHHexuSRKyvSVSprWoDkR0YJEnTWhpXhpiaja3VvCB7TEiej9SZ4/sLChM7K1W04hR/JeZzuZZ8wEvny7NurDwLqYypLZVbpcW+vwlzHWoa6OxwYYy5+GB6reBqm7apuhb3cPZJikWRcXdhuGugVKP2N/pttQUjY72pEbwC5Ml3vQ0+1D0CThWDyuuRQeWN9Vt83rYfcW9PD+5rSnH5+kfw==","w":20,"h":35,"aspect":"fixed","title":"Data Input Port"},
  729. {"xml":"dVRtb6MwDP41SHcfJvFWWD+2vHW0o71uHbRfTgECpAPCJWmB/folLXSddJNA8mP7cezYjqRZVecR0BTPOIWlpDmSZhGM2VWqOguWpaTKKJU0W1JVmf+S6v5gVS5WuQEE1uw/BBwfYcK4RwlicZh9MU4aTFgNKsjFIcCouHik+MQGfQkSWOAyhYR+P7Ha4nJ0Bwz8vVHG1FQBv2dNWT9yaAEaIVIG6wSVv5pDkNr6U+4+HXjydpwEe6CRw3nHUe+Y/nYxn0WiLHXOv72caB+RpcbsY82h3i4y16keFevfvDnm/uHZ9PE20dvNYq2rlmwU1kwO380leHx7T+yts5IJXpobGq2xJ/sRhWF/1I7hdG+s/WCig85F0V49PqMZlrsGqedJrr6mUzo9hXVYA81aeMjX+2OwrF5z6lYZzwx54foFNsphMdlto6gzq2YjK29+Wu/hGc9gYBCWtr4RrWjsu6cYGEnkefIiz6c2O9Umr2O361NauAVd6jM7iFBUISvs/nhykO25mSz9QrRW3Am2NeqsfkvavC0Qgy8N7xO/zpYPFtcVrBLNVrjILzrFLQcyBwRS9AHiSxOuODkRis5wKwyjtsJn4bMaRkZEqSADTorYGJQR/A4tXGJyaadm2FNjYtwsIUpZMfgSfKpTmA6xM1SWdzxHc01HFTxeAKrzV8zTtx9EpARXKBlYg5WjO98VzNhYB2Z8Br/quk7iGRIGux8XRbkbUA9iXiLpuUs75C5G+LpLcgFRXgwsbdgYMOxDfmPegm0wqsXODUePZ/dX+KA8fo+As4xCdr+1XLhL6Ut1WaMRDqs9wq8n5Mq+f2E+AQ==","w":20,"h":35,"aspect":"fixed","title":"Data Output Port"},
  730. {"xml":"dVLbboMwDP2avE4BtGqvK9v6VFVavyAEj2TNBQW3wL5+DgnrKrUPKD7H59ixA6tqO+2C6NXet2BY9c6qOniPKbJTDcawkuuWVW+sLDl9rPx4kC2WLO9FAId3DL75BomkMKKJzaJAemujOjntpzdwJ7F2KCO8bT7gvFoGJfoYOo90bEelEY69kJEbaUjiFNrYuKCwEfLUBX927eGMRjvIfCvC6UAujTMx/Ik/EymM7hxBA1802fYCAbUU5jXT6GPxQf8sRWKVdO+og+nhdop/4+zAW8Awk2TULaqkeEkL5Ap0p7Jrs0mcGBLu/pzXVVOQN7TCvPwVXh95kd78A78=","w":80,"h":66,"aspect":"fixed","title":"Comment"},
  731. {"xml":"dZLdbsMgDIWfhvv8qA+wpluvKk19gYoGKzABrhx3Sd5+BEKbaO1FJB/b3zE4iLpx45HkTZ9QgRX1p6gbQuQUubEBa0VVGCXqg6iqInyi+npTLWO1uEkCzy8AvP5Ay6HDyus87BCLEXFntJAyLToX+AuoDlI1+8fm7eiep8yBVx9EOATl0YfkXslew8yWQWh2dgmTa/R/d+5yNeoI6IBpCi2DUaxTxy5drdBgOs3bHIGVbH639rJPsnvYPSZ8o/HzXqatTSZ6vFMLS9N6/5kbX3MsqQP+x4VgdalnKq40y+VfZfl8E4leP5k/","w":50,"h":50,"aspect":"fixed","title":"Comment Edge"}]</mxlibrary>`;
  732. const encodedLibFTG = `<mxlibrary>[
  733. {"xml":"dVPbkqIwEP0aqnbfuKzKPsrNwXHUGVFX3wK0EAkkGyKIXz/h4jpW7RSVok+fTqfTfaIYdn6dccTSNxoDUQxXMWxOqeit/GoDIYqu4lgxHEXXVbkU3fuG1TpWZYhDIf6zgYZniISMIChsD3M6clSgHORv2Jx/UAI9JzgqyhPlORKYFgNPUAQpJTHw8unULkvnmEYCV1g0QcOg5+4F6i18rr0Uzf24MkWsNUsBRYTJj/PRC53ST1x396e+yVvYdnbbnfbTCdlm86VvMSNUzb95drWspT0qvCadZO/GOWNHbL9tSr9KF27AwF0purVbZHVpvEtLq6PosE3JSDeO1PesiTmn82OiRtGlus7HTlabxi/LqGJ3vHl9+ZhQx0z8IEaXZnWRg9EtIOeb5i4Dflhc6NI7IF/8Rt709Xg9xXtZ5rrJNajetcN4H9D4pQrXpTXbIbFkN5Pu/e1ZJvFWe2bWB3nbn4ph1SkWsGGys9JRSzlIXyrydkSaNDmU+IbCrk9qi6lA4gvOQSA3xmIIjzllAeIJiIGPaCEQLoAPWDY6pvUAGHAsE3SkHAlmJawfLuuECbEpobybkeE67Sf9/WAr4AKu36pP+zLvGVCZkzcypMaxSPuISS9QNQWcpOLZhwaBJf92PqQsjUFCdziI+w4fj6gLfXpjnw==","w":70,"h":70,"aspect":"fixed","title":"(FTG) ActivityType"},
  734. {"xml":"pVZZc6pIFP41Vs283IJGE31k05BrtxK3wBsCaVabQWT79XO6wcTcWWqqxsSyz/b1WT48ThQ9b1elV0SYBWE2UcyJopeMVcMpb/UwyyZIioOJYkwQkuA9Qct/sMrCKhVeGV6qvwlg5yT0K/DIvDO/zBDG2cXLQ/gYg/M3loWDrSq9y/WDlblXxewy2jPPDyOWBWF5/XarQBEK1a/iOq66fVeEg81necGucfWJexsN98wRF78Xda26ex7XyCv48VqFFz/Ofkvc5dm4WtQ0j+9ND+XpetofP07qc3ZIX4mlFcpZmv+Rp62mEX12WXbRc2orSVq4sY53V6uO1ua+CM3NBGnHddpcFRtOcuP7ziHKZkhxmbXUnuev7NWlku/f6vb1yUibuTLVlDown3Y/X96emTGn1j7wbt3mBhNDWpglvWySfemsb4wsHc+qFt5S/em2H8EJ0tx2uRzWtuw8nfYseKnP26u2OnoVKfo5O1mHBECWm1Mxbxyo9veJojURNG1XQMtB0QBPQBdVOZ+dDMcyvMa9dxZ9krjMKq96kPOw8swgrkb3oGTF3itpWI12n10qL76E5ShDowPWjEIRljEACCOMJC6u4fZLpX3EWaazjJViRopp8D/QD4Otw7IK23+kpfww71XIALPswKWJgyoaPJ4H5kpRGNOo+q7zRubRz8gvjsNhpNBdHFn/L0/Ar7z3RgJb/p31d54q/4mnce5R+NR4E2Lfy9b8si1/APhjpBhnVlUsBweRheb5KS3Z7RI8tPNDvB4w1CymPLZinAPetRA1GR9xG0JumrhSvWuluwbOATBioqiDiJbXmgJTW6AQ0rcvBLmdNj2f2pvfS7H38ib5BqvXSqAE3UzB3az2c7/GidpgfdEHuR9bL27hvgf6WaELK1Ep7i1K9j6yDFva6BYNUJYGK247KCS2qLc6Fi6KpO3OkohB423SROudFFmmW58vNrXfg8xHi8rdabafzymJp80acJ1LSu0V93mLAnTsz0jOLF2iIscX/LTuFvkZzSTnM36WnJFE96sscU+z3t2plbWKpOBF6zfxvHbzxS1YLRX3ndTn0+HmQMwatYWDjjcfHehPk+vlzM9J4ZyaBxzIAy2a8P1V1GEZVoMTi74JvAzunPVrXXs95/gG+VXbpG2c9zdmreyFlUodNijdGJoBPZxaxqFfJ/YMMLrNbipbBu7gLJNY7XFKe5yAX2+jdXIQfcW98xCD0UZXp3jptMR4MwabqWBd4EjQN/g0Z5hj8bns025n4BZnTof7V4sYjmT39ozoU35/D36y8IM5rRMTZmg1oJ9ulg7PwwJ5avcHmQz4HdZVmfQO5GS3PL9h5pDT0pEA3xg5IG+E/z3OnvI4nFAKs292ho2IicHfv5GdKm0MFfTmQ5zdQi843onXB/lyuQcf4T/Gifp4PSIvk0qE+yeU+ytQV7cx8I3jkITeiM7lu781Bf8e7zm+BfnweAz4zifu0FfIcZgP73lPkuhEwNfuhSyRPRX+Y5yog+OJek3akb0L87Zmok9QN+fMQ5zgBMc7JoBnYiHf+3qPG/BGbsA8cOJIx0Tk30L+Bt6rMDeYR8LxzWaoU9TdYsPm+ingd1/+ePqFayKsf/a1JWPdxwQqTLGQiXEY/Me4oQ6zhf5CvcAPI4IaId5IR9tDjP44Q/WhtsH/3qtxfjIBjm4Aj+ydTz5uIDds+CDzZ0TcK3g89AF4nn7vM+f72OeW9Opnn/HeBn/ahLFVw3dP7SvuZUv5lyz/1+BHV3jfmHxF+hm7hA/yrxvVyzLWqGXJmut95/6yg3NWP0iwQS7wnfy4o0P/Vl7jOnzjgXftf9qa6O+35hiAlB/zp+ni6zUAPOxUhH6g+V/Xqvz8Q5H/72YF8et3q3D99rP2Tw==","w":70,"h":70,"aspect":"fixed","title":"(FTG) ActivityType (Composite)"},
  735. {"xml":"dVPJkqMwDP0aqmZuLJOlj4FAmnQ6+zLJzcEKOBjsMQZCvn4MgUmnavpAIb0nPdmWpFlOcpsIxKNPhoFqlqtZjmBMPqzk5gClmqkTrFljzTR19Wmm9w1rNKzOkYBU/ieBna8QSBVB0bkuNm7IXooSUL82OVkzCg9OCpRmFyYSJAlLW56iACJGMYjspWqj0gCjQJKCyGpbcXhwKJdMiQDudPOW6E5u1u7rpTJZdefIIsRrM5OQBoT+uJ688zjzQ9fd/y7v6nqOE9/3l8NoQHfxdO7b3Drrwz9JfLPtudNLvSoaxCvrGvMTcT43mV9EM3fLwV1opr2fxWVmrZRllEFw3EW0Z1on5nv2YDhl01OoB0Fe3Kb9cVwOrV+2VWC3v/l4Xw/YeBj6W4zyapGrjpk20OvdcOdbcZzlbO4dkS/fkDf6ON0u+KCOuawSA4qVcewftgy/F+dlZk/2SM75fcgO/u6qRLzFgQ/Lo7rtT82yy4hI2HD15Aoo1ZwoLJJJ3TtDmQIyckfn5p302mcSyS9+AhK5mMg2HAvGt0iEIFs+YKlEJAXR5UOQi4wUsK6FO5UChCQBoiNKwlRhCcG4LmGrvmBWtlEcBFH1Gi3VQcIzWD4h+0IodRhlommp5XnOuP+m8Mcc1CXg9u0UG1/GYwJMaYpKhZQEy+gRMXgMuh4BCSP5iqF2UMN/mc+VUEY7cZ3bLknnPpexCX3Z1b8=","w":70,"h":70,"aspect":"fixed","title":"(FTG) ActivityType (Automated)"},
  736. {"xml":"pVbZcqs4EP0aV8283GKxE/uRzY5yLWy8Bt4wELFaDGC2r5+WwImTuXdqqsaVFOru00e9CTGRtaxdFW4eYuoH6UQ2JrJWUFoNq6zVgjSdSELkT2R9IkkC/E+k5W+sIrcKuVsE1+oXDvQSB14FiNS9sM10bpxd3SyAx+ic7WgaDLaqcK/lOy0yt4rodbSnrheENPWDovyyK2fhCsWrojqqukOXB4PNo1lOy6j64L2NBvdWUWAP/O+Ge0oSE79mW1bdPcAydHO2LKvg6kXpH7GzvOglIoZxemt6yFvTkv70flae02PyaiI1ly/C/K8saVXV1GbXZRc+J5YcJ7kTaXhfojpcG4c8MDYTST2tk6aULViJjefZxzCdSbJD0VJ9nr/SV4cInner29cnPWnm8lSVa9942v982T1TfU7QwXdv3eYGrZTUII170TAPhb2+UXNpu6hauEvlp9O++2cIc9tlYlBbov10PlD/pb5sS3V1cisz7+f0jI4xkCw353ze2JDtnxNZbUKo5j6HXoCigQECXVhlrKkiLIugjHr3wuskMJlWbvUgZ0HlGn5UjXC/oPnBLUhQjXaPXis3ugbFKEOhfdqMQh4UERBwI7Qkystg+6lS36M01WhKC94jebnU9KcF6IfG1kFRBe1v51V86PcqoMBZdABpIr8KB8TzMNJCGEQkrL7q3HEkyYfn5/DDYhyhuzgeh385Gt8PhDtONvLux+E+p/J/mtMocwk8VVaEyHPTNdtsy04GO1+yfqEVnAcA8ChU10tIQW9X/6Gc7/z3wKGkEWG+FWUz4JY5z0l/j1p2qlS+pXLXCncNrH2YiImsDKK0LGsCk9rCCEna9sWUnE6dXs7tzeuFyH3ZCZ5O67Xsy343k3E3q73Mq3GsNFhb9H7mRejFyZ03X7vIZIFiheAeEfPgSUi3hI2GiC+lib9itqNsRoi4q1PuSKGw3SPB1Em0jZtwvRdCZDj15WoR681PPWlROXvV8rI5MaNpswZe+5oQa8Uwu9CXTv1FElOkCYTH+IKf1t0iu0gzwf7wn8UXSSCHVRo751nv7JUKrULBf1H7TTSvnWxx81dL2Xkz68v5eLPBZy21uS2dbp50JD8NphdTLzNz+9w88EAc0qIJ3l55HkhHDY4R2XG+FPac9WtNfb1k+AbxVdu4bey3HUUra4ESocM6IRtd1aGGU6Qf+3VszYCj2+ynItJxB2vRjJQeJ6THMeB6S1rHR15X3NsPPljaaMoUL+3W1Hf6YDNkrHEeAeoGT2OGGRfryyHp9jpucWp3uH9Fpm4LVm/NTG3K9u8BJ3Ic9GkdG9BD1IB+ulnaLA4E8tTqj6I58HdYU0SztyEmq2XxDT2HmJa2APz6OAPihuPvftaU+eGYEOh9s9ctyTQw4L2buVeEja6A3njws1qoBeM7s/wgXib3gOH40Y/nx/LhcRlEMBk+JgwvQ17dRsc3xmPG5GZqTL7j0RTwPT4wfgTxMH8M/PYH71BXiHHoD6t5b8bh2QSs1XNZMA+E40c/ngfj4/kapDMPDvQbzXidIG82Mw9+fCYY3ykGPgNz+V7Xu9/AN84G9APHtnCKefwtxK/jgwJ9g37EjN9ohjx53i3WLaafAn/3icfTT15DwtpHXVtzzPsUQ4YJ5rKpHwf86DfkYbRQX8gX5kMPIUfw15PR9uCjPfZQechtwN9rNfZPNGFGN8BnHuyPedxAbFj3QGZnhO/L53ioA8x58rXObN7HOrdmr3zUGR8swJMmiFAN757ak53rlrCXLPtT4WssuN+Y7Ir0UnoNHuTvN6qbprRRioI25f3O/XYHZ7R+kOAGucI7+fGODrxbUUZ1sGOOd+1/ujWlX9+ao4Mk/5g/TRefv4Hg4U6VpB/S/J/Xqvj8Qxb/780K4ucHLYd++d79Gw==","w":70,"h":70,"aspect":"fixed","title":"(FTG) ActivityType (Composite, Automated)"},
  737. {"xml":"dVNNj5swEP01ltpLBSZE22MC6V62qrS7Us8GJuCu8bCDE5L++o6NCRupK0Kw35sZv/mwyIr+8khq6H5iA0ZkB5EVhOjmVX8pwBghE92IrBRSJvwK+eMTNg1sMigC6/7jgNUfqB1bGFX5w8pAbg3b7sdBWWZGdzUQmO37ycvY12iQRLZjktrqCwfkX8H/H1ZfV3NetfM3t6oH/iwnVLRyM8LC5mMXOKjvnzFKkI6UHY9IvXIa7cyHqIHd1U6ftbu+XgeIvkbV0KFpgMa7kiwVkn57X7w1Y0l4sg1405TlTJ128DJwRAYm7hFjnetNpEdH+Aa/deO6iNRondIW6LY3Rg2jrkL8hJGGcHhV1IKLJorqF/3X0w/f8s2GSym3m/Qh2yaetfgUG+WdCeoTjfoMzzDOPh6FC1ewiZtK1W9tyOLXyRmWEnFWZrnzalbiQ/fg1KHRiw5CpxbaOwyorQslzPciL326nWpwivSRM426U+nTMAanHRFOYwwYLGJlk1u1inmWSote2v6ojVkgIbND6Z/bIJyBHFw+nez0QysfATkhurLJFBviLfJ5+pMOdNtFt+8RU3FA2pvrek94Ecdj2cabs2zXGxpM7y7wPw==","w":150,"h":90,"aspect":"fixed","title":"(FTG) ActivityType w/ ports"},
  738. {"xml":"dVNtj5swDP41kbZvFNRK97HA9b5smnR3fyAQF7ILMTOhtPv1c0JSrtJOAmL78RuPY1FUw/WF5Nj/RAVGFM+iqAjRrdJwrcAYkWdaiaIWeZ7xK/LTF+guoNkoCaz7TwA2v6F17GFk44vVATwY9i2nUVpGJnczEJDDn9m3UbZokERxZJC65hsn5Kfi7yfp++bOUreeeysH4CNVaGjDVgs3tpZN5tD98IqxhdyRtNMZaZBOo13xkDWgx9bpi3a399sIMdbIFno0Cmh6oETODjkJqJR3jhGJutyrj6xuVORLrx28jZyc9YXHxd32bvAU7gJF1kltge66MXKcdBPiM7YowvFdUgcuulj8EWfgYYJ2pklf4BUm/TcFwZXJUVFpZPvREc5W/Zqd4WLRzrUtD1WutXzqAZx8VjpVInQywT5gRG1dYGdfin3tR99LhUuEz/wvb2sPO2aklMbgciTCZbon5CYCk16bHOEHVOslqS36xsqzNiaZRF6cTlV9eLpP+ALk4Prlld19GsULIP8O3dhl0cr10WO/XuusB931Mewp2mScfHcP3RaAhTjepMaVSOq2esH1YTP/AQ==","w":150,"h":90,"aspect":"fixed","title":"(FTG) ActivityType w/ ports (Automated)"},
  739. {"xml":"pVZbc6s2EP41nmlfzoCANH7EiDicWjjEzgW/cYsAA6IYm8uv70oIx+k5p9OZJk7Q3j7tfrtCXmhW2a+boE4Ji5NiodkLzWoYa6dV2VtJUSyQksULDS8QUuBvgR5+YVWFVamDJqnanwSwME+iFjyKIOSbYWG8K8B3daqDCiyndigSYbn768zTWEWsYM1CM8HY0PA3AISPBf9vVr9/usOKTk+jCsoEHvMOYfNpmzSQ2LTtrBbZl89MpoDaJqhOH6wpgzZj1WQXqMJqRm12ydphP9SJjC2CKElZESfN6QslEStrdsraK+5ZRszUIS5+ZfWTCtSwcxUn3FWFPLsUgHY1bAWKDpoHurQtC2k+tQ07Jm9Z3KZSE7GqDbIqaa5yUQT1KQsFvgKauGH1Pmho0kqXoIl22cjN998MXQeO0Z2u3mt3CrdWbCM7yIObJDo3p+ySPCenKYZrkx6ojaUQBtGRiiq257aAVKQeMqtgJIIpEw5dJm1gx9mcR8PaYDbzgJplVSu4NVYLA/Ny0yBmnTR/QKUybxXxMoqCdWbTsO4kAYWHZFa5smVNQ4YrxlNbfWRFMasWSLMx/71OyCVp2qT/5cirN61cJwwKagZw6WRDuIcxHQslTTKayrCl1AVycug19PMAwUKOxyzKI/Uvx+ufMx3IqXWieaLnGdT+dQaB55ovszKgnCPOQhYFhZiEJz7c/IhoOGRty0pwEFmsro2/4fND/NxgmEVGeWzL+CwHp1rUhD+yng/9Smxpzlpl1sA6huGAd8Mkwmm+0AVa9XAUkPX06KLDsNLDt/4cjUoWPD4rEWaXjRZr8WBoZDAuURldSG52xFqOcRllzuOhPrzHVqjRpZOblIwOdfcRcrCnbC2Hxqg4xmtue9HczKHB+rU+oFR52jmKi2n2lHfpZqekjn24hJVHvfe4iNCyPexWXlTeUzfTuw3g+tWRemvu85zG6HUMkVo4lkJFjo/kbjMsyxAZin+NN/IQKXS/LvLDmzEedmbrrFMlflyN2+z+ciiX53j9oB3e3Uv49nL2IWaD+tpHr+cIvdA/ba5Xi6h0a/+tu8GBPNCyS96/izoc7HQkd+izwCtgT2PcWKvvYUnOkF/7lPed//7MnLW3dI7KQDClW7zCwKHu4Jdxk3sGYAzbna46mAywVt3MHMmRjiQHv9FDm/xF8EpG/yaGoK1l6uTB7138jCebrRFL4CjAGzxtg3As3pf9cdhh0pPCH8j43XGxr3ijZ7iWzvcfwU8VftCnTW5DD50O9Pr2wed5OCDr3viiuhP+QCxTdUcfcvJ6nt/Uc8jpwVcAH8sZULfCf47zdB5Hckqh990Oe8i1CfhHZ3dnKltsgt6+ifN64ILjvfH6IF8uj+Aj/GWcqI/XI/KyqeJy/5xyfw3qGraYnDmOm9Oza3F59nd08B/JnuM7kA+PJ4DvX3EnXiHHqT+c89HN0zcXfL1RyIq7p8Jfxok6OJ6o16aDuz9Avx1D8AR185m5iRMzwfFec8CziZBnXue4CU/OBvSD5L7ymov8e8gfk70JfYN+5Bzf7qY6Rd09wR7X64A/fPoT/RPXRsS68tq7su7XHCo8EiG7+GXyl3FTHXYP/EK9MB84hRohHh+l7SbGuu2heVPb5D9zJfunujCjW8Bz9/51HreQG8ERyPyMiH3FHE88wJwfv/LM513y3LujeeWZ7D3wp12SORd491wi7VA9Uf6S5Z8fLs+SXW6kht/WX+SfXeNfb9Af72zlv16L6OfX4hwwX1XDJOuTdHNpIvQN3f94bap/fNPU/3tzgvj5pVe4fvlO/Dc=","w":150,"h":90,"aspect":"fixed","title":"(FTG) ActivityType (Composite)"},
  740. {"xml":"pVZZc6pIFP41Vs283IJGMtdHpNFwx8bgkgTf2NKAQDMIsvz6Od2AmrvVVE3KaJ/tO9uH7UzRs3ZdukVEWBCmM8WYKXrJWDWcslYP03SGpDiYKXiGkAT/M7T6hVUWVqlwyzCvfhLAvCT0K/BIXY8nw8L4lILv8lK4OVguVZeGwvL0T83LWPosZeVM0cBYUu8PAISXDu8Ppz/v7nCiw6eau1kIH1MGr7zbBg0UNqSd1KL6bMfGElBVuvnlg5WZW8UsH+wCVVg1v4qvcdUduiIcY1PXDyOWBmF5+TQSn2UFu8TVDbceI9y6YoAeBt8bppkiLn4e931GqGR1HohgGRpoIsiwL6AGUDSwVdBFVZaOZp/llRvnYXmT09QtLrEn0CTQBCUrDm5Jw2p0ydlmXBU3l6Ffl5f4Gu7CS9xPQWELMwxGwXP9MxVVbesqhWSjHnLnsHt3yMWhs7ByjSCeMpWsciczDyhYnFdiiOpypmLOkMgNWDOaP6CX/VCDDPNZumnKGq0sWXMZAS9Vyc6hPrAH54yXsvyI03RSzZCyWun4aXFb/TUsq7D9JZflh1WsQwYNlB24NHFQRaOHOvBdisKYRmPYYtS5IyXoLfT+ZMBhXO8kjs/Kb56b78nqjnQ0/YmqE4eU33II5lrwY5y5lM+ITyH23VRs/oWzlnNfwR6rgKvgIKpY3hb9MM8P8feAoaUx5bEV41x0L4XoCX/ELSftUqTUJq00aeAcABngoR9EeEyvdIaWLVAZ6S/PFjp1y7n31tZ+L8Xu807yMbtulEAJOlUhnXr1M/9KEq0h+qIPMj82n0/F6T3QPYUuzESjpDepdfCRiW1pq5s0QOk5WHPbUbFik7rr1+KEIullb0oWpvFL0kSbvRSZxunq5Ta134PUR4vqtF/afvaVWvG82QCuk5+pveY+uyhAr72H5NTUJSpqfCZPm26ReUiVnFu8mnhIood1mpze1P601ypzHUnB87Lfxl+vp2xRB+uVcnq3rt7bsXYgZoPawkGvtY+O9G+D6+XUz6zCeWsecKAOtGjC92+iDxObDUlMuhN4KeRU+42+/OZlpIb6qpekbZz3HTPX9sI8Sx3BlG7xEsMM5yY+9pvEVgGj2+7nsolJB2fZirWenGlPEvDrbbRJjmKupHceYgja6tqcrJzWwjs82AyF6AJHgrnBp6ESjsX3cjh3e0xakjod6b+ZFnYku7dVS5/z/D34ycIP9rRJDNih2YB+vl05vA4T5LndH2VrwO+IrslW70BNdsvrG3YONa0cCfDxyAF5K/ynOHvO40hCKey+2WMbWQYBf7+29pq0xRrojYc4u4VZcLw33h/Uy+UefIT/GCf64/2IugwqWdw/odxfgb66LSY1x7ESWls6lyd/cw7+PTlwfBPq4fEE8J0b7jBXqHHYD595byXRmwW+di9kyTpQ4T/GiT44nujXoJ11OMG+TVXMCfrmnHmIE5zgeK8J4BlEyNNcp7gBb+QG7IMkjvSaiPpbqB+TgwZ7g30kHN9ohj5F3y3BNtfPAb+7+5P5HddARL/NtbXGvl8T6PBMhGzh4+A/xg19GC3MF/oFfuAIeoR4fB5tDzH64w61h94G/2lW4/5kCzi6BTzr4Nz4uIXaCPZB5s+IyCt4PMwBeH7+PGfO93HOrdVrtzmTgw3+tAlj8wrfPVdfOeUvlH/J8tcPl2XGrg9SyW/nT/LPru3PN+aPd7T0X69F9PNrcQqYrqpukOeD9HBpIvQFff3x2pT/+qLI//fmBPH+a1a4fvqx+y8=","w":150,"h":90,"aspect":"fixed","title":"(FTG) ActivityType (Composite, Automated)"},
  741. {"xml":"dVPBboMwDP2aXCcgotuuUNrTLmulnVNwIWsSoyQd7b5+JoS2TBsC4ff8nNixw3ipL1sr+u4NG1CMV4yXFtFPlr6UoBTLEtkwvmZZltDHss0/3jR4k15YMP6PADx8Qu1JocRh3GwdnLkRGugXg/U7Kph8R7RaKOn05Aq64Nhfe4hyJWroUDVg3SKJOadshMt0nb/OO1g8mwZGacJ4MXTSw66nFYkY6FSI67weM03JdJ1ocIjaVgnnou28xRN8yMZ3UXpE40tUaMM2PAkP8VRNa4ir6YDARuEu5jMKwskUoj61IbV5DYOGFEWNWtZRKc4enfyeAx2lLU27x35JFOg96iVHaEXIAsWLQ9h7TFqDF1Uj/VyDVOqhhopvnqvsVu2DZ7V+XeXjgj1K40Mb8oJe6n2ZPOUsJ1E5YrKJ+YXTG07venp5MTXyC6yHy78Dlj70dwtINdgrSYbYDPK+TDOYdCDbLkbxyIk4NO0t8j6tZMSRmWGc3xne70mQLq7RDw==","w":80,"h":30,"aspect":"fixed","title":"(FTG) Type"}]</mxlibrary>`
  742. const encodedLibPM = `<mxlibrary>[
  743. {"xml":"dVPbkppAEP0aqpKHTQ2MAX3k4mXjEpWNRn0DaWFkuDgzgPr1O4DEtSr7QNF9Tp/u6sso2E4vU+YXsZuHQBU8VrDN8lx0VnqxgVJFQyRUsKNoGpKfok2+YNWWRYXPIBP/EeTBCQ5CRlA/aIo5LdlKUi+n0CEkI4L4tCP61G3cc1Uurr2Ex37RmFxAdiD0W7ifeA5/jcZjb1nfZH3bXt82cDU5J4llFqe/GirjYxIXq1Owhz/2frsi0h1b9nwxHVoiNsa/irO/OZZUT0znFa/BnZ95UnhNNicirLLKyPXcOvi5DW2maNZ2EVZRUm2XEzNQ32bvM35Y6ccV1yv992gx28iQ0C3RyXHNZWSsd+gUqFryVrnYkFSZDWQjk91+Hs2nhpfyWWnvJG7NxVn+Bu75aMrune8KtuqYCHgv/EPTci2XJ7FYpM1AVWkeCaV2TnPWzgYjNLBHI4lzwfIEeibLMym3GHBy84N2kEj6EBJxd5tcLBe++EQzOJSMkwq8RtejBTCSgoAmr9wNKTgsH5DVbbICJuDy5aGonxY8hVxq2VWGXDv2Rf2hYn2oD7FhaKqu6gjDi4q7NDUJRdzF4e7eUAwkisUz5vPOj/5lf1ymNO531bv3W+3dx5toQ5+ezAc=","w":30,"h":30,"aspect":"fixed","title":"(PM) Initial"},
  744. {"xml":"dVNRj6IwEP41JHcPe6F0ZeFRQI2onGIOlbeiFeoVim0R3F+/BfRckzMpycz3zTdDZ6YadPNmwlGZLdgBUw2ONOhyxmRv5Y2LKdUMnRw06GmGoatPM8YvWNCxeok4LuR/BCw54b1UERQlbTGvIztJHjKKe+RICkR7+J64i3quKeT1LhAZKltTSFzsCf1xjOZzT0zT0fKKLuvlCHtxpP7BXK75ppmx4Xhh2tbcWdn2buRP4xRNYESHwdgE09ovXLEKfntWM77gaVmDSil36TWez4pJIhea4ajjidQJAAvJ6g9OmT/e7tMInvIkHE1NvdF3E2GFYqwCZ4tqScXg4jd2bgxUqrPJAagmjuUHIWpK2yxYtY0Hn8HAAnOH7tdpePY2CydT4ncbxeCkjG3AotpPomgY10U8DY7Hs6VLUcHVJigUP8qOqftTg06dEYnXJdq37ajVWBWWybxtNVDmkVDqMsp41zeo6++ubStcSM7+4tfMhhxkphg1A4djQT5R0vVeVz4+EHlz2xKcSSS/0RzvKy7IBYet7o6WmJMcS9yWU+MkpcDLB+T0w79gLnHzcrPAt52YYKa0/KpCrj37Bn4BaFqmBT8+DGACU4f4DcA+TX27j/Jgv6B6hkmayWcMid5P/2V/rLIybqt4d2/LfXcfj6gLfXpjXw==","w":30,"h":30,"aspect":"fixed","title":"(PM) Final"},
  745. {"xml":"dVK7csIwEPwatRnZjgvKYBIqGihSZoR1YIGs88jila/PnSSHMBMKzdzt7r0lqqa/Lr0auhVqsKJ6F1XjEUOy+msD1opSGi2qhShLSU+UH0/YIrJyUB5c+CcAtwdoAyms2nKxRSRjSL9GCwnZoT9+HdC4RE3Jo/Kx7hhuU5DHk9PAUimq+aUzATaDapm90HiEdaHnkgWZO3RhY76ZLFg+Bo9H+DQ6dASVrDDWNmjRx+yVlK/NbEa4smbvCGtpQCByfgYfTKvsWyZ6ozX3lHNOORw6BgeaKoycs56LekEIaBPUNk7BnXloT340Z1jDmBrk/tIiuBRcn266+LOfJWAPwd9IcslTsULKlzoFdmD2XQ7MN5JqTP7+N/h+OTLy1ic333Jy738mSh++1A8=","w":100.5,"h":20,"aspect":"fixed","title":"(PM) Fork / Join"},
  746. {"xml":"dVPbbptAEP0apPYhEewGHB5tnOQlVaUkUh+rBaawzcKSYTF2v76zF4wtJcKYnTkzZ+ca8aI7PqEY2h+6BhXxh4gXqLXxp+5YgFIRi2Ud8X3EWExvxB6/QBOHxoNA6M0nDrr8C5UhCyVKe9negZki291E6tGcFDh19jHZGHaVVhojviUQm/IbsdGvoP+L0/fVnE6N/6a96IA+C32JZ8yxEWROw6UFRTmt/i6P7kWHeJiojDxIc/KII7/S//b3WdDxOnAbwDd3k6NUooJWqxpwvKrZUkJmxevqrlVhqKe+BmuaUJRzKw28DsRIipmaSLrWdCrAo0H9Dr9kbdqgqXRvhOwBz7JSYhhl6fhj0tSohzeBDZhgIrB6lf8svLm9S3ma5jm7y/OcE9br59BH64pQTTjKA7zA6D2sFo6D6OsglKJ6b1wOPyejKJCgp7h6Ggzh47DXdmDEQy2XKFAbscDWYdCyN66A6S5K9zbZVtR6DvAfyjNEnTArS6UKP0lURf64tY9NTik9bxH1PAZP34wDoIHjlzOdXPToCTTFijQX8cmjN8ltwrP77J5vNizJkizmcJNwTzOHbliW1O9G3IJs2oU6LEwswng0Z/51jegQhmMRw2It4rrAzvRqv/8D","w":150,"h":120.00000000000001,"aspect":"fixed","title":"(PM) Activity"},
  747. {"xml":"dVNtb5tADP41SNuHVnAUwj4mpOmXTZPaSvs4HeDBrQdHjQnJfv18xxESqZV4OfuxH7+cHcR5e3pC2Tc/TAU6iB+DOEdjaD61pxy0DkSoqiDeB0KE/Abi8AkaOTTsJUJHHziY4i+UxBZaFjbY3oGpZtvdyOqBzhqcOn0fbQ670miDQbxlEOviC7Pxk/P36vR1NedTPf+TTrbAv4W+wAvm2Biic39twVmOq7+ro302Ph8hS1JHRecZceQ3+t9zPAs6XgduPfjqIjlKLUtojK4Ah5ueyZFMKwl8JwlH77H0Vljxtu1ruwSasaucc8TpT40ieOk5FCsmvl3WNdRqDw+E5g1+qYoarylNR1J1gBdZa9kPqnD8IWsqNP2rxBrIm0gsX9Q/C2/ukyh9SEORhfyKLGO0M9/9FVtnhHLEQR3hGYbZx2rh1Muu8kIhy7faVfFzJM2peD1n1vHMyDkTG7gFko+VWvJAQ3KBrUNvVEeut8kuSPa23EZWZvLwH67U5x0JW4bWZtoimmlYLJTW+Tx23Nn4cMj36bfLTBwBCU6fjnt0dUtPYDhX5JEJzzN6F91HcZqlWbzZiCiN0jCGuyieaSZ/H5YlmdcmbEDVzULtdymUfnLqC/+6YXzw47GIfucWcd1tZ3qz+v8B","w":150,"h":120.00000000000001,"aspect":"fixed","title":"(PM) Activity (Automated)"},
  748. {"xml":"pVbbkqJIEP0aI3YfpqMoxNZHBLSZtbDx0ja+bHCbAgRxEeTy9ZtVgGLM9MRGbLdKZebJU3kry5GoJNUysy8BST0/HonaSFSyNM3bVVIpfhyPMAq9kaiOMEbwHuHFF1aBW9HFzvxz/guH1Il8NwdEbDtsM5UbJzFg5wWor3kd+1w9+adgMczdNE6zkSiDMaPOH8AGLwU+B6s/H3BY0fYpne3Eh0dP72R3G2cDU15fhgiIsnj48zySTdrFg203D29hXrcWTv6k/7vdjxk5LzfKnXHHd+KUse36QRp7fnZ9qpmbJpf0GuadZ54VnUdfW8zE57I/yoWztDh7PoMKEH4ZANH2AluBooTugi7Ik7gzX/MsPfmH0MuDTuOm59wOz352l+PYvlxDh/Mj0HhZetnZGfXzDmJn7jZsmPn1RRIm4wnCUwRvPJ2C9ZyuuhYz58x3i+wa3vyNf219mNavLvbZ6wTHdk+UZ7Eu8hhC6fQQ2Rlmxm4jYRsnfm5rXtjHkaW53ZuZwyUNzzmvrTQfSSpLN7C9tOzMPyDTLm4BszTiOC3lLEvLa48I41hpxw4qKy5k9n+fiZuf5X715bgLgy4t/RRizWBkUN1avwkvgjiZTqbi6ysWJsIEif43QWxpyq4fjEVqjw0K/JAGPXV3lpDdTQ698z9OGCy68ejF7sz95vx9Neo61P55BsXfziDUmQ9+mNgUnnNWqtC1Yz4J72y4QyAUVSfN8zQBAI9ifm/8oOg/+N+AQ45DynzzlM2yfb3wnNQfYcWGfs63lHst6jWw9mA44MS3Il5cb3SE5xUcBay8vxn4WM/HzqEq3AaF9tsGuWp6W4me6NWSSGrp5ibujURySZRZ4yVuqL8dL8dPT3FEOtMjmZJGp8bOxbpqorWiUw/HJ2/JbHvRCHVqLz8uRxyg962ODJWG71EZrLYo0LXjzTmb1Pz0YhfP8uN2brrJlBrhuFwBr3U+UXPJMJvAwx+Ng4VYVxDlMb6RyaqeJQ6WkHX3lyIHI7pbxtHxIDXHrZzrywB5b/NmHU5vx2RWeMuFePw0bs5hX1jgs8LVxcIfhYv39C+N6YXYTYyLdSgHPBAHnpX+53eeh67qJYl0uuF8MewpNStl/t1JSAHx5e9RVVqfm1RfmjP9hGqiUrpW5yrUcKyr+2YVmRJw1OvtWNBVUsNaMEK5ISfakAhwjYlX0Z7XlTTWwIfgtSKPycKqDHWjtjZNJArnQVA3eGoSYVysL7tTvVVJRWKrJs133VAtZDamZChjtn8DOIHjoE+rSIMe6iXox+uFxeLQQR6bzV4wWv6aKLJgNBbEZFYsvrbnENPCQsCvdjMgrDm+9zPHzI9ElELvy61qYkMjgHcLYyujtSqDXhv4mRXUgvEdWH4QL5MbwHB858fzY/nwuDSKDIaPKMOLkFe9VknBeIyIFobC5B6vjwHfkB3j1yEe5k+A37rztnWFGNv+sJo3RhQcDMCaDZeRsaMc3/nxPBgfz1ejtbE7Qr91idcJ8mYzM/DjM8H4PiLg0wiX+7r2fi1fNxvQDxJZ6CPi8VcQv0p2MvQN+hExfq1s8+R5V0Q1mX4M/PUDT8YPXg0T5V7Xyujy/oggwxPhsqHuW3zn1+ahVVBfyBfmQw0gR/BXT51t4KMMeygPcmvxfa26/gkGzOga+IyddZ/HNcRGVBdkdkb4vnyO2zrAnJ+e68zmvatzZTTyvc5kZwKeln6o3+C75+aKx/M7ZV+y7PXT5Qk/Af2hnLH7eiAn6W0gPd/NX1/0P9+x/+kuxb++S3uH/urq7tbxT7coxi94+ot79PVFFP7vTQri41cyhz79iP4X","w":150,"h":120.00000000000001,"aspect":"fixed","title":"(PM) Activity (Composite)"},
  749. {"xml":"pVZbk6o4EP41Vu0+nKkQxNFHJOhw1uCgzgVftridAIK4CHL59dsJ4DB7zmxt1U7pkO7++kvfQpzIWlqvc+cS0swPkomsT2Qtz7KiW6W1FiTJBKPIn8hkgjGC7wSvvrBKwoouTh6ci184ZG4ceAUgEsflmxFhnCWAXZagvhZNEgj17K+Sx7D0siTLJ7IKxpy5vwEbfDT4P1r9/gGHFeueytlJA3gM9G5+twk2MBXNZYyAKMsPf5FHusv6eLDjFdEtKprOIsg/6f/s9uNGwSuMam88iJ0EZeJ4QZglfpBfP9XMy9JLdo2K3rPIy97DKYssdYrA/6dhKDrm4ud+fNQR51l59oWzBHlVIeywv0AMoKig7aALizTpzdciz07BW+QXYa/xsnPhROcgv8tJ4lyukSv4EWj8PLscnJwFRQ9xcm8ftdz8+KBIs+kM4TmCL57PwXrONn3vuXMeeGV+jW7BLrh2Plwb1Bfn7PeC63gnJrLYlkUCofR6iOwMw+R0kfCN06BwdD8a4sizwhnM3OGSRedCFF1ZThTC0w0dP6t68w/ItI9bwjyNJMkqNc+z6jogoiTRunmEysqrlUZmi/uw3IK8COovz4E06tI6yCDWHGYJNZ31m/QgybP5bC4/PmJpJs2QHHyT5I6m6vvBWZTuPKEwiFg4UPeHDDn9SLE7/8fRg0U/HoPYH8Z/OZhfnQEDav95BuV/nUGoszgRUeoweC55qSLPScQkPPOpj4BQJm5WwKwDQESxvDd+VPQf4m/EoSYR475FxmfZuV5ETuRHVPOhX4ot1UGLBg2sfRgOeBV0Il5db2yClzUcBaw9P5n42Cyn7ltdei2KnKcd8kh228i+7DeKTBvl5qXejcZqRbVF66deZDwdL8d3X3NltjBildHWYObBwwax0FYzmI+Tk7/mthfZjAzmrF8vRxyi572BTMKi57gKN3sUGvrx5p4tZr37iYcXxXG/tLx0zsxoWm2A1z6fmLXmmF3o49fWxVJiaIiJGJ/obNMsUhcryL77K7GLETusk/j4prTHvVoY6xD5T8t2G81vx3RR+uuVfHw3b+7bS2mDzwbXFxu/lh5+YX/oXC8lXmpe7LdqxANx4EUVvH8XeRjEqGhssJ3gS2BPpd1oy+9uSkuIr3iO68p+32XG2loYJ9RQwtiWLAnUcGqQl3YTWwpwNNv9VDIIbWAtmZHa0hNraQy41sKb+EXUlbb2yIfiraZO6cquTbIjnU2XqSZ4ENQNnrpCORfvy+HU7AmtaWI3tP1umMRGVmsppjbl+7eAkwQO+rSJdeihUYF+ul3ZPA4D5KnVvkhmx99QTZXM1oaYrJrH1/UcYlrZCPhJPwPSVuAHP2vK/WjMGPS+2hMLmzoFvFeaexVtiQp6feRn1VALzvfG84N4udwCRuB7P5Efz0fEpTNkcnzMOF6GvJotoSXnMWNWmhqXB7wxBXxLD5zfgHi4PwV++87b1RVi7PrDa96acfhmAtZqhYzMAxP43k/kwflEvjprzMMR+m0ook6QN5+ZkZ+YCc73GgOfToU81HXw6/j62YB+0NhGr7GIv4b4CT2o0DfoR8z59arLU+RdU2Jx/RT4mw88nX7w6phq97rWZp/3awwZnqiQTfLS4Xu/Lg+9hvpCvjAfJIQcwZ+cetvIRxv3UB3l1uGHWvX9k0yY0S3wmQf7Po9biI0SD2R+RsS+Yo67OsCcnz7Xmc97X+fabNV7nenBAjyrgsi4wbvn5snH8zPjL1n++enyhN+GwVjO+X09ktPsNpI+381fX/Q/37H/6S7Fv75LB4fh6urv1ulPtyjGD3j+i3v08UGW/u9NCuLHz2cB/fTr+m8=","w":150,"h":120.00000000000001,"aspect":"fixed","title":"(PM) Activity (Composite, Automated)"},
  750. {"xml":"dVPJcoMwDP0aXzssJZ0eC1lOvTSZ6dkBBdwYizFKk/TrKxuTQCedYZGenqRnWxZp0V42VnbNO1agRboSaWERabDaSwFaiyRSlUiXIkkifkWy/ica+2jUSQuGHiTg/gtKYoaWe9ds6YOZkS3wj3GRvrlvktG1GyBfr/1ADQNdWlIH6Yr4iJYlNKgrsP1Mgi/5IMHX9fjOd0gm6hPnzhfW03VsbPFkKnBUVpmfG0Ww7bg7A2feP8Yaat2aYjb7RlZ4Dtxay74Pdk8Wj/CpKmoC9aC0LlCj9W3SVbp+WbGMXGpVG8ZK3kqwjoiGtkHPM/t+D3NZHmsvbaxh0DAjL7FVZWgqT4S9+oFRA8tWpmZvwZ4FDsm9LxvfFE4ULZavi8wxWyC5qhQFYofKkN/1LOeHT62InjKRcU7hfLYZ+ePHNz++8/lhHUiSgo6Jyh12cyBHImwDNhzgN1iCy78jGE/OdQPIy7BXppzDIThGNIxp1ICqm5CWBkyGyapvqfeBZiPMyuiGER/d+1Xy1NlN+wU=","w":100,"h":30,"aspect":"fixed","title":"(PM) Artifact"}]</mxlibrary>`;
  751. const encodedLibTrace = `<mxlibrary>[
  752. {"xml":"jVNLb9swDP41ug7yK12OjZP2smFAO2DHQrZYW6ssGbQSJ/v1o2TJTYZ12MGwSH58fHywoh7OjyjG/quVoFlxYEWN1rrlNZxr0JrlXElW7Fmec/pY/vCBNQtWPgoE4/7iYJuf0DpCaNH4ZB7QQKcMK+5ZvhHDyIqdaSb/I1lTjF1Dj84FRSVap07KXV6MGIDkhKEEV7Doh0kzWnT/mcBD/x088BuerIalfIeiBTgR35eFyYLQpO2tloDTTWtuGQTLH6SC+1pGQLQpbOpz7sXbEUzukkqae+XgeaQKSJ5ptlR47wbf74yek0P7Bj+UdD1pKM6utcYJZQAjorVai3FSTYjISSPRjt8FduAixNgvcYTejNAecVIneIJJ/UpOcB6FkVFoRPvWoT0a+e3oNCWLesptaCfEksuHHsCJg1QpE1onkpmHaSrjQlOrHav2nlAvpJ2j+ZW4PC81ZJ6b0NrO94h2nmJAge0K+LTdlPyuLO8yXhb5503IRzWCjOBXpXVttcXQ2mJfHrKHam3ilYXzst5u1xU5ATo4f3gV2dUAH8ESZbwQZI5D8Yh4ObwH1fXRrajiFsWl6lbX9xujR1yKJMarS+L7dQfozfH/Bg==","w":120,"h":35,"aspect":"fixed","title":"(Trace) Begin-event"},
  753. {"xml":"jVNLb9swDP41vg7yK12OjZP2smFAO2DHQrZZW6tsGjQTJ/v1o2TZTYZ12MGwyO/jm4zSojs/kh7ar1iDjdJDlBaEyPOrOxdgbZQoU0fpPkoSJV+UPHyAxh5Vgybo+S8GWP6EioVhdemCOQL0YnwfJRvdDVG668vR/US24mFXyqNhr8h1xeZk+PLS6w5EXjji/ooW7GjRDEj8nwEc9d/OfXXdE1qYk2fSFcBJqn1xdcy4FV2LtgYab9pym79H/ijJm69JeEaFR56BpcmJE2/7P/JlyWhqDcPzICmIPMlgJe+WO9fsWJ4jE77BD1NzKxrxs6uwZ216oMCo0Fo9jKb0HpVoasLhu6YGOFB6/BLm52CC6kijOcETjObXYgTnQbvJeqHU1VtDeOzrb0e2EizoJXYvC6HnWM51B6wPtVkiEbJeYOWHaXr2Xc13Ub53BbW6xinAr1LL85xD7GrT1uJ0T4TTGBxqqlbCp+0mU3dZdherLE0+b3w8yRHqQH411hZokXxr0312iB/ytYlXiFJZsd2uG3ICYjh/eBLx1QAfAaVkughlCkNxjHA2qgXTtMEszcMaha1qVtP3A5NHWIpFDCe3iO+n7ak3l/8b","w":120,"h":35,"aspect":"fixed","title":"(Trace) End-event"},
  754. {"xml":"dVRdd6o6EP01rHXOGx9iy6Mg9lgrpfW7L3cFCBANCZIg6K8/E0Rt17rnQZzZ2XtmkkxGs7yifalQmc95gqlm+ZrlVZzLq1W0HqZUM3WSaNZYM00dfpo5+ceq0a3qJaowk/8j4NEexxIYFEUq2bhbHFLgujUYmTI002aowPAHRM0aqa9py3N5ha5sCPlNoJJ2mi7iVa2wTtRhy07eFUdRjHNOE1yJn1UXn5z2dFRJkqJY/ncCFuHsyrht01TuzxMQ8nzTihx1WYXELCb0l/zaJns+z/zpJs2lCBYnROjAitIPzXSPwt6ssuzlWENQtStu5ktU62ETL3YX9zIopi3L38aeUTXuQb4j4QFvYbcFZHXnaFAPCjtCrh5Kep6CPscZxWKuIk0Z2sdn1zu6JIirp2nC1zLWL6ATs7f1wnMnyFx5tp7/ecnjmeME6bitQ1Bmx1yVkh3i4m0yZeet7YDotNs/O0YT0Jmzi3bHtDo4aDfO2zVDljn4eh+JP0LMLt75q2jXT9t8EH6WzjpIQGocQ4OPLOwvNvYgNPXL7us1rPmKza0FJAraYOSx1aQOm4lO/Pp5WX/M/I9S7jbs+Dx7t7aZ4PIziMVHMconYxycT020ens6hFuPMCeNRvrrHso34FAmXmO+po26C2v8G9qjyYnEixJuHYAGWh2wXBaq/QwwIxQfsorXLHmvJSUM93gHYXXj0IQuXGrCm97JKBKit0tOmMSVf4KOv2FCVvyAPU551bWENRw7Q3sIKymh9BvuW5Mn31QRK5QQiHBbY1zV4SJKMgZujFUOAKAdJYkRHfULBUkS1Xduyplc9D34KGFDEpn3+xFwAoRl4A0f3pKXN8EVcLmUvOixmBckvttMIjidqveTipdLVGVY3gmUolKQ6F5DheMaXs8Jf2JBLneUSyTRg1VgifyEyL7M60tT+8TtP4eK8e0BvmAOIaozUJp+u4phXAePnmOS5b3M6kOh/uFnd+ljRIHRP+qb2w+tm/sYjh31x+z8Cw==","w":110,"h":31,"aspect":"fixed","title":"(Trace) Versioned Artifact"},
  755. {"xml":"dVRbd6o6EP41rLX3Gxex5VFQu62V2nrvy1kBAkRDgkkQ9NfvCaK2a539IM58880lycwYTlA0LwKV+YwnmBrOyHACwbm6SkUTYEoN2ySJ4QwN2zbhZ9jjf1it1mqWSGCm/seBR3scK2BQFOlkw9bYp8D1KxAyLRi2y1CB4Q+IhjPQX9tV5/IKXdmReNCvCCT5HuKEhSSctS66pjZkm/AaXGNtzBZbttHb2imKcc5pAv4/D1V8ctrRkVAkRbH6755FM25KSzldD36/GlurP29NqvMtoMxRW4pUmMWE/lJf22TPZ9losklzJcPFCRHac6L0w7D9o3Q3qyx7OVYQVJ+b2/kSVea8jhe7i3/pFZOG5W/DwBK1f1DvSAbAW7hNAVn9GepVvcKNkG/OFT1PwD/HGcVypiNNGNrHZz84+iSMxdMk4WsVmxfwk9O39SLwx8heBa6Z/3nJ46nnhemwqebgmR1zXUp2iIu38YSdt64HTqfd/tmz6pBOvV20O6bi4KHdMG/WDDl27+t9IP9IOb0E56+iWT9t8978s/TWYQKu1nFu8YGDR4uN25vb5mX39Tqv+IrNnAUkCptwELDVuJrXY5OMqudl9TEdfZRqt2HH5+m7s80kV59hLD+KQT4e4vB8qqPV29Nhvg0I89JoYL7uoXwLLmUc1PZrWuu3cIa/oYHqnCi8KKEVAKhhPADLVaFb1tLdh+JDJnjFkvdKUcJwh7cQ1i8OjevDoya87pSMIik7ueSEKSxGJ5iSGyaV4AcccMpF2xJOf+j13T5YUkLpN3zkjJ9Gto4oUEIgws3GuK7DR5RkugdjrHMAAG2pSIzooDMUJEl03/kpZ2rR9eCjhA1JVN6dR8INEJaB1n9oS17eHK6Az5XiRYfFvCDxXWYKwe2ITk8EL5dIZFjdCZSiUpLoXoPgCin0TcdxBVN1wp9YkssNLbBCo4Sorsz7+Cnc/HMRWd8G8AVzCCHOQKm742qG1c1sjkmWd25OFwp12yC7uz7WGgjdUN/UbtHd1MdCbak/9u1f","w":110,"h":31,"aspect":"fixed","title":"(Trace) Versioned Artifact (w/ version)"},
  756. {"xml":"dZLdTsMwDIWfJvdpA72HDnY1CbEHQFnjdoG0npxstDw9+Wm3VLBKlXLs88VOHCbqftySPB13qMAw8cJETYgurfqxBmNYybViYsPKkvufla93skXM8pMkGNw/AB4+oXHeYeQhFNvEZET6dzSQIgn/uABZjUPKLxWifV3cumkhQXWwnyVn4hkG9USE3142RlqrGx88uj6ULvyyxcHt9U+wF4uu0SDF7QSPX4hrY7J427ZlE7ayjvALsoyqDtVj5TOE50GBmvtIZwjd3b2nIjvYFrAHR5O3EBjp9GXNSZtkd/Vd0TfUQ7jgKTke+JqweKYGZlM+yIUbZ06sOSepA/eH84us21soTmaR89AXeXtcic7f3i8=","w":43,"h":40,"aspect":"fixed","title":"(Trace) Parent Version"}]</mxlibrary>`;
  757. const encodedLibSS = `<mxlibrary>[
  758. {"xml":"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","w":160,"h":40,"aspect":"fixed","title":"(S/S) Service"},
  759. {"xml":"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","w":160,"h":40,"aspect":"fixed","title":"(S/S) Storage"},
  760. {"xml":"jVTBbtswDP0aXwdHSrr02LhdLx06dLsXsk3b2mTTkOQ42dePkuVERp2hBgyIj3wkRT4o4Vl7etaib75jCSrhTwnPNKKdTu0pA6USlsoy4Y8JYyn9Cft2w7vx3rQXGjq7QsD8NxSWIpTIXbFH79xN8HsJptCytxI7AqdUKy7Peg2ZfDklCmhQlaDNso/2DRVMkAah3jFizT0zZy6vY+x5pplG9O5YnJXsqABP+GFspIWfPRUlx0izI6yxrbvPho45DhRZvuQXQBR/au3Q18FSGgi4kX/dcftlf8/3+5Sle07fV+eqsLMZKtS+C175j/BSY/9L6BrcdFMCCgoUlFJfbKVEb2TuL+CQFo+CrJcwcQdpKAZt5BHeIPTgUbTCiogIVjyV0oZuK6lU1NLd/fbAaGqHaZpH0BZON1WwiYb8DEip9ZlCRlnaJkTcTUpJG5B1E2jbgImw1/pCvWT7gbJzippr84lxnky+5GNVGbCxIukQdXSFvBJmM8hmXfOhxFGoAWZFs6WIInGsaWfWmDSYDTmwG5JxO6GQh672eTe7T29SO6f4IImrhDq6XoQIpXB80BpHE5BPLZmtLzkQdovFzHuKFMBWBMD+I4DFChf7ur5f03rj5+0f","w":160,"h":40,"aspect":"fixed","title":"(S/S) Real-world object"},
  761. {"xml":"dVJNbwIhEP01XBs+thqPda2empjae4MydaksYwBb7a8vC4vupvVAMm/mvXkDAxF1e145eWxeUIEh4pmI2iGGHLXnGowhnGpFxIJwTuMhfHmnylKVHqUDG/4R4PYTdiEyjNx2ZotUTJL2FQ3kjPfvRttDLpTWiTd29eFSJKD2sOkhJWIOVj05h98Rbg3uDm+NtjHdhLZzZTH8QBs2+qcTsA774PAANRp0qaWYzKo5j35zhyerQPWdlfRNAqwHaxkCOJsynFbZfKlNMcrX6Aa8+0ZscLcVYAvBXSLFgZFBf4110me4v/Ku0jVq2z3uJTMqOlZ4PLkd9KThEovuXHQPlIpHPqsYm06q6bhLkG4P4U+XGAxmv6XSqgrs11/g7Ztl9fAX/gI=","w":40.0035294117647,"h":40,"aspect":"fixed","title":"Trace - S/S Link"}]</mxlibrary>`;
  762. // plugin loaded last will be on top
  763. ui.loadLibrary(new LocalLibrary(ui, encodedLibSS, "FTG+PM - S/S"));
  764. ui.loadLibrary(new LocalLibrary(ui, encodedLibTrace, "FTG+PM - Trace"));
  765. ui.loadLibrary(new LocalLibrary(ui, encodedLibPM, "FTG+PM - PM"));
  766. ui.loadLibrary(new LocalLibrary(ui, encodedLibFTG, "FTG+PM - FTG"));
  767. ui.loadLibrary(new LocalLibrary(ui, encodedLibCommon, "FTG+PM - Common"));
  768. // For debugging only
  769. window.ui = ui;
  770. });