From c242c4a39a232026baebed1e7aab725fab9e994d Mon Sep 17 00:00:00 2001 From: David Mosbach Date: Mon, 29 May 2023 18:26:12 +0200 Subject: [PATCH] workflows are now loaded via server requests --- editor.js | 635 +++++++++++++++++++++++++++--------------------------- 1 file changed, 316 insertions(+), 319 deletions(-) diff --git a/editor.js b/editor.js index 0e6ad20..b612b6a 100644 --- a/editor.js +++ b/editor.js @@ -48,150 +48,29 @@ function openSearchMenu(menuitem) { var workflow = {} -// fetch('./test.json') -// .then((response) => response.json()) -// .then((data) => { -// for (var key in data) -// workflow[key] = data[key]; -// }); - -// Counters for placeholder IDs of states/actions added via GUI -var stateIdCounter = workflow.states ? workflow.states.length : 0; -var actionIdCounter = workflow.states ? workflow.actions.length : 0; - -//Parse workflow - -workflow.states.forEach(state => { - var messages = []; - state.stateData.messages.forEach(msg => messages.push(new Message(msg))); - state.stateData.messages = messages; - var viewers = []; - state.stateData.viewers.forEach(v => viewers.push(new Role(v))); - state.stateData.viewers = viewers; - state.stateData.payload = new Payload(state.stateData.payload); -}) - -workflow.actions.forEach(action => { - var messages = []; - action.actionData.messages.forEach(msg => messages.push(new Message(msg))); - action.actionData.messages = messages; - var viewers = []; - action.actionData.viewers.forEach(v => viewers.push(new Role(v))); - action.actionData.viewers = viewers; - var actors = []; - action.actionData.actors.forEach(v => actors.push(new Role(v))); - action.actionData.actors = actors; - var viewActors = []; - action.actionData['actor Viewers'].forEach(v => viewActors.push(new Role(v))); - action.actionData['actor Viewers'] = viewActors; - action.actionData.form = new Payload(action.actionData.form); -}) +fetch('http://localhost:8080/test.json') + .then((response) => response.json()) + .then((data) => { + for (var key in data) + workflow[key] = data[key]; + runnn(); + }); //Actors of the workflow var actors = []; -workflow.actions.forEach(act => act.actionData.actors.forEach(a => { - var includes = false; - actors.forEach(actor => includes = includes || equalRoles(a, actor)); - (!includes) && actors.push(a); - (!act.actionData.actorNames) && (act.actionData.actorNames = []); - act.actionData.actorNames.push(getRoleName(a)); -})); -// console.log(actors); -// workflow.actions.forEach(a => console.log(a.actionData.actorNames)); - -function getRoleName(role) { - if (typeof role == 'string') { - return role; - } else if (role instanceof Role) { - return role.name; - } else { - return JSON.stringify(role); - } -} - -const NO_ACTOR = 'None'; - -//Prepare actor highlighting const selectedActor = document.getElementById('actor'); -var allActors = document.createElement('option'); -allActors.text = NO_ACTOR; -selectedActor.add(allActors); -actors.forEach(actor => { - var option = document.createElement('option'); - option.text = getRoleName(actor); - selectedActor.add(option); -}); - - //Viewers of the workflow var viewers = []; +const selectedViewer = document.getElementById('viewer'); //Actions/States with no explicit viewers var viewableByAll = [] //Possible initiators var initiators = [] //Implicit state from which initial actions can be selected var initState = null; -//Identify all viewers of every action -workflow.actions.forEach(act => { - if (act.actionData.viewers.length === 0) { - viewableByAll.push(act.actionData); - } else { - act.actionData.viewers.forEach(v => { - var includes = false; - viewers.forEach(viewer => includes = includes || equalRoles(v, viewer)); - (!includes) && viewers.push(v); - (!act.actionData.viewerNames) && (act.actionData.viewerNames = []); - act.actionData.viewerNames.push(getRoleName(v)); - }) - } - if (act.actionData.mode === 'initial') { - act.actionData.actorNames.forEach(an => !initiators.includes(an) && initiators.push(an)); - } -}); -//Identify all viewers of every state -workflow.states.forEach(st => { - if (st.name === '@@INIT') { - initState = st; - } else if (st.stateData.viewers.length === 0) { - viewableByAll.push(st.stateData); - } else { - st.stateData.viewers.forEach(v => { - var includes = false; - viewers.forEach(viewer => includes = includes || equalRoles(v, viewer)); - (!includes) && viewers.push(v); - (!st.stateData.viewerNames) && (st.stateData.viewerNames = []); - st.stateData.viewerNames.push(getRoleName(v)); - }) - } -}); - -initState.stateData.viewerNames = initiators; - -const ALL_VIEW = "Not explicitly specified"; -if (viewableByAll.length > 0) { - viewers.push(ALL_VIEW); - var viewerNames = [] - viewers.forEach(viewer => viewerNames.push(getRoleName(viewer))); - viewableByAll.forEach(data => { - data.viewerNames = viewerNames; - }); -} - - +const NO_ACTOR = 'None'; const NO_VIEWER = NO_ACTOR; -//Prepare viewer highlighting -const selectedViewer = document.getElementById('viewer'); -var allViewers = document.createElement('option'); -allViewers.text = NO_VIEWER; -selectedViewer.add(allViewers); -viewers.forEach(viewer => { - var option = document.createElement('option'); - option.text = getRoleName(viewer); - selectedViewer.add(option); -}); - - //source & target nodes of all currently highlighted actions var highlightedSources = []; var highlightedTargets = []; @@ -223,12 +102,6 @@ function selectViewer() { }); } - -var selfLoops = {}; // All edges whose targets equal their sources. -var overlappingEdges = {}; // All edges whose target and source are connected by further. -const selfLoopCurvMin = 0.5; // Minimum curvature of a self loop. -const curvatureMinMax = 0.2; // Minimum/maximum curvature (1 +/- x) of overlapping edges. - var selection = null; // The currently selected node/edge. var rightSelection = null; // The currently right clicked node/edge. var edgeTo = null; // Target of an edge to be created. @@ -241,35 +114,179 @@ const sideButtons = document.getElementById('sidebuttons'); const contextMenuBg = document.getElementById('ctmenubg'); //Click on background const contextMenuSt = document.getElementById('ctmenust'); //Click on state const contextMenuEd = document.getElementById('ctmenued'); //Click on edge +// Counters for placeholder IDs of states/actions added via GUI +var stateIdCounter = 0; +var actionIdCounter = 0; +var stateAbbreviations = []; +var newStateCoords = {'x': 0, 'y': 0}; //Initial coordinates of the next new state -const edgeColourDefault = '#999999ff'; -const edgeColourSelected = '#000000ff'; -const edgeColourHighlightDefault = '#6ed4d4'; -const edgeColourHighlightSelected = 'magenta'; -const edgeColourSubtleDefault = '#99999955'; -const edgeColourSubtleSelected = '#00000055'; +/** + * Marks the given item as selected. + * @param {*} item The node or edge to select. + */ +function select(item) { + contextMenuEd.style.display = contextMenuSt.style.display = contextMenuBg.style.display = 'none'; + edgeFrom = edgeTo = rightSelection = null; + selection = selection === item ? null : item; + if (selection === item) { + while (sideContent.firstChild) + sideContent.removeChild(sideContent.lastChild); + sidePanel.style.display = 'block' + sideHeading.innerHTML = item.name; + var data = document.createElement('div'); + var content = generatePanelContent(selection); + content.forEach(c => data.appendChild(c)); + sideContent.appendChild(data); + var spStyle = window.getComputedStyle(sidePanel); + var shStyle = window.getComputedStyle(sideHeading); + sideContent.style.top = sideHeading.offsetHeight + parseFloat(spStyle.paddingTop) + parseFloat(shStyle.marginTop) + parseFloat(shStyle.marginBottom); + var sbStyle = window.getComputedStyle(sideButtons); + sideContent.style.bottom = sideButtons.offsetHeight + parseFloat(spStyle.paddingBottom) + parseFloat(sbStyle.marginTop) + parseFloat(sbStyle.marginBottom); + // console.log(sideHeading.offsetHeight + shStyle.marginTop + shStyle.marginBottom); + } else { + sidePanel.style.display = 'none'; + } + console.log(item); +} + +function deselect() { + sidePanel.style.display = 'none'; + selection = null; +} + + +function rightSelect() { + select(rightSelection); +} + +/** + * Adds a new state to the workflow and auto-selects it. + */ +function addState() { + var nodeId = stateIdCounter ++; + var x = newStateCoords.x; + var y = newStateCoords.y; + state = {id: nodeId, x: x, y: y, name: 'state_' + nodeId, fx: x, fy: y, val: 5}; + workflow.states.push(state); + updateGraph(); + select(state); +} + +/** + * Adds a new action between two states. + * @param {*} source The source state. + * @param {*} target The target state. + */ +function connect(source, target) { + let linkId = actionIdCounter ++; + action = {id: linkId, source: source, target: target, name: 'action_' + linkId, actionData: { + viewerNames: [], actorNames: [] + }}; + workflow.actions.push(action); + updateGraph(); + select(action); +} + +function markEdgeTo() { + edgeTo = rightSelection; + contextMenuSt.style.display = 'none'; +} + +function markEdgeFrom() { + edgeFrom = rightSelection; + contextMenuSt.style.display = 'none'; +} + +function removeSelection() { + if (selection) { + if (selection.actionData) removeAction(selection); + else removeState(selection); + deselect(); + edgeFrom = edgeTo = rightSelection = null; + contextMenuEd.style.display = contextMenuSt.style.display = contextMenuBg.style.display = 'none'; + } +} + +function removeRightSelection() { + if (rightSelection) { + if (rightSelection.actionData) removeAction(rightSelection); + else removeState(rightSelection); + if (selection === rightSelection) deselect(); + edgeFrom = edgeTo = rightSelection = null; + contextMenuEd.style.display = contextMenuSt.style.display = contextMenuBg.style.display = 'none'; + } +} + +function generatePanelContent(selection) { + var children = []; + var data = selection.stateData || selection.actionData + for (var key in data) { + if (key === 'viewerNames' || key === 'actorNames') continue; + var h = document.createElement('h2'); + var heading = document.createTextNode(key.substring(0,1).toUpperCase() + key.substring(1)); + h.appendChild(heading); + children.push(h); + var content = data[key]; + if (content instanceof Array && content.length > 0 && content[0] instanceof Message) { + content.forEach(msg => msg.format().forEach(child => children.push(child))); + } else if (content instanceof Payload) { + content.format().forEach(child => children.push(child)); + } else if (content instanceof Array && content.length > 0 && content[0] instanceof Role) { + var viewerList = document.createElement('ul'); + content.forEach(viewer => { + var v = document.createElement('li'); + v.appendChild(document.createTextNode(viewer.name)); + viewerList.appendChild(v); + }); + children.push(viewerList); + } else { + var p = document.createElement('p'); + var text = document.createTextNode(JSON.stringify(data[key])); + p.appendChild(text); + children.push(p); + } + + } + return children; +} + +/** + * Removes an edge from the workflow. + * @param {*} action The action to remove. + */ +function removeAction(action) { + workflow.actions.splice(workflow.actions.indexOf(action), 1); +} /** - * Checks if two roles are equal. - * @param {*} role1 - * @param {*} role2 - * @returns + * Removes a state from the workflow. + * @param {*} state The state to remove. */ -function equalRoles(role1, role2) { - role1 instanceof Role && (role1 = role1.json); - role2 instanceof Role && (role2 = role2.json); - var equal = role1.tag === role2.tag; - if (role1.tag == 'payload-reference') { - equal = equal && (role1['payload-label'] === role2['payload-label']); - } else if (role1.tag == 'user') { - equal = equal && (role1.user === role2.user); - } else if (role1.tag == 'authorized') { - equal = equal && (role1.authorized['dnf-terms'][0][0].var === role2.authorized['dnf-terms'][0][0].var); - } - return equal; +function removeState(state) { + workflow.actions + .filter(edge => edge.source === state || edge.target === state) + .forEach(edge => removeAction(edge)); + workflow.states.splice(workflow.states.indexOf(state), 1); + var abbreviation = state.stateData && state.stateData.abbreviation; + abbreviation && stateAbbreviations.splice(stateAbbreviations.indexOf(abbreviation), 1); } +var selfLoops = {}; // All edges whose targets equal their sources. +var overlappingEdges = {}; // All edges whose target and source are connected by further. +const selfLoopCurvMin = 0.5; // Minimum curvature of a self loop. +const curvatureMinMax = 0.2; // Minimum/maximum curvature (1 +/- x) of overlapping edges. + +const wfGraph = ForceGraph() + +/** + * Updates the nodes and edges of the workflow graph. + */ +function updateGraph() { + identifyOverlappingEdges() + computeCurvatures() + wfGraph(document.getElementById('graph')).graphData({nodes: workflow.states, links: workflow.actions}); +} /** * Identifies and stores self loops as well as overlapping edges (i.e. multiple edges sharing the @@ -319,170 +336,153 @@ function computeCurvatures() { } -function generatePanelContent(selection) { - var children = []; - var data = selection.stateData || selection.actionData - for (var key in data) { - if (key === 'viewerNames' || key === 'actorNames') continue; - var h = document.createElement('h2'); - var heading = document.createTextNode(key.substring(0,1).toUpperCase() + key.substring(1)); - h.appendChild(heading); - children.push(h); - var content = data[key]; - if (content instanceof Array && content.length > 0 && content[0] instanceof Message) { - content.forEach(msg => msg.format().forEach(child => children.push(child))); - } else if (content instanceof Payload) { - content.format().forEach(child => children.push(child)); - } else if (content instanceof Array && content.length > 0 && content[0] instanceof Role) { - var viewerList = document.createElement('ul'); - content.forEach(viewer => { - var v = document.createElement('li'); - v.appendChild(document.createTextNode(viewer.name)); - viewerList.appendChild(v); - }); - children.push(viewerList); - } else { - var p = document.createElement('p'); - var text = document.createTextNode(JSON.stringify(data[key])); - p.appendChild(text); - children.push(p); - } - - } - return children; -} -/** - * Marks the given item as selected. - * @param {*} item The node or edge to select. - */ -function select(item) { - contextMenuEd.style.display = contextMenuSt.style.display = contextMenuBg.style.display = 'none'; - edgeFrom = edgeTo = rightSelection = null; - selection = selection === item ? null : item; - if (selection === item) { - while (sideContent.firstChild) - sideContent.removeChild(sideContent.lastChild); - sidePanel.style.display = 'block' - sideHeading.innerHTML = item.name; - var data = document.createElement('div'); - var content = generatePanelContent(selection); - content.forEach(c => data.appendChild(c)); - sideContent.appendChild(data); - var spStyle = window.getComputedStyle(sidePanel); - var shStyle = window.getComputedStyle(sideHeading); - sideContent.style.top = sideHeading.offsetHeight + parseFloat(spStyle.paddingTop) + parseFloat(shStyle.marginTop) + parseFloat(shStyle.marginBottom); - var sbStyle = window.getComputedStyle(sideButtons); - sideContent.style.bottom = sideButtons.offsetHeight + parseFloat(spStyle.paddingBottom) + parseFloat(sbStyle.marginTop) + parseFloat(sbStyle.marginBottom); - // console.log(sideHeading.offsetHeight + shStyle.marginTop + shStyle.marginBottom); +function runnn() { + +stateIdCounter = workflow.states ? workflow.states.length : 0; +actionIdCounter = workflow.states ? workflow.actions.length : 0; + +//Parse workflow + +workflow.states.forEach(state => { + var messages = []; + state.stateData.messages.forEach(msg => messages.push(new Message(msg))); + state.stateData.messages = messages; + var viewers = []; + state.stateData.viewers.forEach(v => viewers.push(new Role(v))); + state.stateData.viewers = viewers; + state.stateData.payload = new Payload(state.stateData.payload); +}) + +workflow.actions.forEach(action => { + var messages = []; + action.actionData.messages.forEach(msg => messages.push(new Message(msg))); + action.actionData.messages = messages; + var viewers = []; + action.actionData.viewers.forEach(v => viewers.push(new Role(v))); + action.actionData.viewers = viewers; + var actors = []; + action.actionData.actors.forEach(v => actors.push(new Role(v))); + action.actionData.actors = actors; + var viewActors = []; + action.actionData['actor Viewers'].forEach(v => viewActors.push(new Role(v))); + action.actionData['actor Viewers'] = viewActors; + action.actionData.form = new Payload(action.actionData.form); +}) + +workflow.actions.forEach(act => act.actionData.actors.forEach(a => { + var includes = false; + actors.forEach(actor => includes = includes || equalRoles(a, actor)); + (!includes) && actors.push(a); + (!act.actionData.actorNames) && (act.actionData.actorNames = []); + act.actionData.actorNames.push(getRoleName(a)); +})); +// console.log(actors); +// workflow.actions.forEach(a => console.log(a.actionData.actorNames)); + +function getRoleName(role) { + if (typeof role == 'string') { + return role; + } else if (role instanceof Role) { + return role.name; } else { - sidePanel.style.display = 'none'; + return JSON.stringify(role); + } +} + +//Prepare actor highlighting +var allActors = document.createElement('option'); +allActors.text = NO_ACTOR; +selectedActor.add(allActors); +actors.forEach(actor => { + var option = document.createElement('option'); + option.text = getRoleName(actor); + selectedActor.add(option); +}); + +//Identify all viewers of every action +workflow.actions.forEach(act => { + if (act.actionData.viewers.length === 0) { + viewableByAll.push(act.actionData); + } else { + act.actionData.viewers.forEach(v => { + var includes = false; + viewers.forEach(viewer => includes = includes || equalRoles(v, viewer)); + (!includes) && viewers.push(v); + (!act.actionData.viewerNames) && (act.actionData.viewerNames = []); + act.actionData.viewerNames.push(getRoleName(v)); + }) } - console.log(item); -} - - -function rightSelect() { - select(rightSelection); -} - -function deselect() { - sidePanel.style.display = 'none'; - selection = null; -} - - -/** - * Updates the nodes and edges of the workflow graph. - */ -function updateGraph() { - identifyOverlappingEdges() - computeCurvatures() - Graph.graphData({nodes: workflow.states, links: workflow.actions}); -} - - -/** - * Adds a new action between two states. - * @param {*} source The source state. - * @param {*} target The target state. - */ -function connect(source, target) { - let linkId = actionIdCounter ++; - action = {id: linkId, source: source, target: target, name: 'action_' + linkId, actionData: { - viewerNames: [], actorNames: [] - }}; - workflow.actions.push(action); - updateGraph(); - select(action); -} - - -/** - * Adds a new state to the workflow and auto-selects it. - */ -function addState() { - var nodeId = stateIdCounter ++; - var x = newStateCoords.x; - var y = newStateCoords.y; - state = {id: nodeId, x: x, y: y, name: 'state_' + nodeId, fx: x, fy: y, val: 5}; - workflow.states.push(state); - updateGraph(); - select(state); -} - -function markEdgeTo() { - edgeTo = rightSelection; - contextMenuSt.style.display = 'none'; -} - -function markEdgeFrom() { - edgeFrom = rightSelection; - contextMenuSt.style.display = 'none'; -} - -function removeSelection() { - if (selection) { - if (selection.actionData) removeAction(selection); - else removeState(selection); - deselect(); - edgeFrom = edgeTo = rightSelection = null; - contextMenuEd.style.display = contextMenuSt.style.display = contextMenuBg.style.display = 'none'; + if (act.actionData.mode === 'initial') { + act.actionData.actorNames.forEach(an => !initiators.includes(an) && initiators.push(an)); } -} - -function removeRightSelection() { - if (rightSelection) { - if (rightSelection.actionData) removeAction(rightSelection); - else removeState(rightSelection); - if (selection === rightSelection) deselect(); - edgeFrom = edgeTo = rightSelection = null; - contextMenuEd.style.display = contextMenuSt.style.display = contextMenuBg.style.display = 'none'; +}); +//Identify all viewers of every state +workflow.states.forEach(st => { + if (st.name === '@@INIT') { + initState = st; + } else if (st.stateData.viewers.length === 0) { + viewableByAll.push(st.stateData); + } else { + st.stateData.viewers.forEach(v => { + var includes = false; + viewers.forEach(viewer => includes = includes || equalRoles(v, viewer)); + (!includes) && viewers.push(v); + (!st.stateData.viewerNames) && (st.stateData.viewerNames = []); + st.stateData.viewerNames.push(getRoleName(v)); + }) } +}); + +initState.stateData.viewerNames = initiators; + +const ALL_VIEW = "Not explicitly specified"; +if (viewableByAll.length > 0) { + viewers.push(ALL_VIEW); + var viewerNames = [] + viewers.forEach(viewer => viewerNames.push(getRoleName(viewer))); + viewableByAll.forEach(data => { + data.viewerNames = viewerNames; + }); } +//Prepare viewer highlighting +var allViewers = document.createElement('option'); +allViewers.text = NO_VIEWER; +selectedViewer.add(allViewers); +viewers.forEach(viewer => { + var option = document.createElement('option'); + option.text = getRoleName(viewer); + selectedViewer.add(option); +}); + +const edgeColourDefault = '#999999ff'; +const edgeColourSelected = '#000000ff'; +const edgeColourHighlightDefault = '#6ed4d4'; +const edgeColourHighlightSelected = 'magenta'; +const edgeColourSubtleDefault = '#99999955'; +const edgeColourSubtleSelected = '#00000055'; + /** - * Removes an edge from the workflow. - * @param {*} action The action to remove. + * Checks if two roles are equal. + * @param {*} role1 + * @param {*} role2 + * @returns */ -function removeAction(action) { - workflow.actions.splice(workflow.actions.indexOf(action), 1); -} - - -/** - * Removes a state from the workflow. - * @param {*} state The state to remove. - */ -function removeState(state) { - workflow.actions - .filter(edge => edge.source === state || edge.target === state) - .forEach(edge => removeAction(edge)); - workflow.states.splice(workflow.states.indexOf(state), 1); - var abbreviation = state.stateData && state.stateData.abbreviation; - abbreviation && stateAbbreviations.splice(stateAbbreviations.indexOf(abbreviation), 1); +function equalRoles(role1, role2) { + role1 instanceof Role && (role1 = role1.json); + role2 instanceof Role && (role2 = role2.json); + var equal = role1.tag === role2.tag; + if (role1.tag == 'payload-reference') { + equal = equal && (role1['payload-label'] === role2['payload-label']); + } else if (role1.tag == 'user') { + equal = equal && (role1.user === role2.user); + } else if (role1.tag == 'authorized') { + equal = equal && (role1.authorized['dnf-terms'][0][0].var === role2.authorized['dnf-terms'][0][0].var); + } + return equal; } @@ -531,7 +531,6 @@ function getEdgeColour(edge) { } //Compute abbreviations of the names of all states -var stateAbbreviations = []; workflow.states.forEach(state => { // var label = node.name.substring(0, 5); var label = state.name.split(' '); // [node.name.substring(0, 6), node.name.substring(6, 12), node.name.substring(12, 18)]; @@ -563,10 +562,7 @@ function openContextMenu(x, y, menu) { edgeFrom = edgeTo = null; } -var newStateCoords = {'x': 0, 'y': 0}; //Initial coordinates of the next new state - -const Graph = ForceGraph() - (document.getElementById('graph')) + wfGraph .linkDirectionalArrowLength(6) .linkDirectionalArrowRelPos(1) .linkColor(getEdgeColour) @@ -574,7 +570,7 @@ const Graph = ForceGraph() .linkCanvasObjectMode(() => 'after') .linkCanvasObject((edge, context) => { const MAX_FONT_SIZE = 4; - const LABEL_NODE_MARGIN = Graph.nodeRelSize() * edge.source.val * 1.5; + const LABEL_NODE_MARGIN = wfGraph.nodeRelSize() * edge.source.val * 1.5; const source = edge.source; const target = edge.target; @@ -716,7 +712,7 @@ const Graph = ForceGraph() closeMenuItem(); }) .onBackgroundRightClick(event => { - newStateCoords = Graph.screen2GraphCoords(event.layerX, event.layerY); + newStateCoords = wfGraph.screen2GraphCoords(event.layerX, event.layerY); openContextMenu(event.layerX, event.layerY, contextMenuBg); contextMenuEd.style.display = contextMenuSt.style.display = 'none'; edgeFrom = edgeTo = rightSelection = null; @@ -724,4 +720,5 @@ const Graph = ForceGraph() }) .autoPauseRedraw(false); -updateGraph(); \ No newline at end of file +updateGraph(); + } \ No newline at end of file