From 89809dc551a4b70e4761896adb6fdaae4f94b643 Mon Sep 17 00:00:00 2001 From: David Mosbach Date: Wed, 24 May 2023 19:17:56 +0200 Subject: [PATCH] new highlighting mode: viewers of states/actions --- app/Export.hs | 3 +- app/Workflow.hs | 6 ++- editor.css | 28 +++++++++++- editor.html | 15 ++++-- editor.js | 119 +++++++++++++++++++++++++++++++++++++++++------- 5 files changed, 146 insertions(+), 25 deletions(-) diff --git a/app/Export.hs b/app/Export.hs index 20d2975..253c8e0 100644 --- a/app/Export.hs +++ b/app/Export.hs @@ -42,7 +42,8 @@ module Export where "target" .= values ! "target", "actionData" .= object [ "mode" .= values ! "mode", - "actors" .= values ! "actors"]] : result + "actors" .= values ! "actors", + "viewers" .= values ! "viewers"]] : result instance ToJSON GraphData where toJSON (GData (nd, ed)) = object ["states" .= toJSON nd, "actions" .= toJSON ed] diff --git a/app/Workflow.hs b/app/Workflow.hs index 268fe86..a7e7eef 100644 --- a/app/Workflow.hs +++ b/app/Workflow.hs @@ -87,7 +87,7 @@ module Workflow where name :: Maybe Label, actors :: Maybe [Map String Value], viewActor :: Maybe Value, - viewers :: Maybe Value, + viewers :: Maybe [Map String Value], messages :: Maybe Value, form :: Maybe Value } deriving (Show, Generic) @@ -155,7 +155,8 @@ module Workflow where ("source", Single source), ("target", Single targetID), ("mode", Single mode), - ("actors", List $ Prelude.map Dict actors)] where + ("actors", List $ Prelude.map Dict actors), + ("viewers", List $ Prelude.map Dict viewers)] where name = if isNothing a.name then ident else case (fromJust a.name).fallback of @@ -164,5 +165,6 @@ module Workflow where source = fromMaybe initID a.source mode = fromMaybe "" a.mode actors = fromMaybe [] a.actors + viewers = fromMaybe [] a.viewers --------------------------------------- \ No newline at end of file diff --git a/editor.css b/editor.css index 3ab857d..1af35c1 100644 --- a/editor.css +++ b/editor.css @@ -4,4 +4,30 @@ #editor { border: 10px solid red; -} */ \ No newline at end of file +} */ + +body { + margin: 0 0 0 0; +} + +#settings { + line-height: 2; + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12); + top: 0px; + width: 100%; + position: fixed; + padding: 8 8 8 8; + background-color: rgb(230, 230, 230); + z-index: 2; + float: left; + overflow: auto; + align-items: center; +} + +#settings div { + width: fit-content; + position: relative; + overflow: hidden; + float: left; + margin-right: 20; +} \ No newline at end of file diff --git a/editor.html b/editor.html index 4d96b96..4d74269 100644 --- a/editor.html +++ b/editor.html @@ -8,17 +8,22 @@ - +
-
- - +
+
+ + +
+
+ + +
diff --git a/editor.js b/editor.js index 2c8a129..d86abdd 100644 --- a/editor.js +++ b/editor.js @@ -11,49 +11,122 @@ var workflow = {} var stateIdCounter = workflow.states ? workflow.states.length : 0; var actionIdCounter = workflow.states ? workflow.actions.length : 0; -//Persons & roles of the workflow +//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(getActorName(a)); + act.actionData.actorNames.push(getRoleName(a)); })); // console.log(actors); // workflow.actions.forEach(a => console.log(a.actionData.actorNames)); -function getActorName(actor) { - if (actor.tag == 'payload-reference') { - return actor['payload-label']; - } else if (actor.authorized) { - return actor.authorized['dnf-terms'][0][0].var + ' (auth)'; +function getRoleName(role) { + if (typeof role == 'string') { + return role; + } else if (role.tag == 'payload-reference') { + return role['payload-label']; + } else if (role.authorized) { + return role.authorized['dnf-terms'][0][0].var + ' (auth)'; } else { - return actor.user; - } - + return role.user || JSON.stringify(role); + } } +const NO_ACTOR = 'None'; + //Prepare actor highlighting const selectedActor = document.getElementById('actor'); var allActors = document.createElement('option'); -allActors.text = 'All Actors'; +allActors.text = NO_ACTOR; selectedActor.add(allActors); actors.forEach(actor => { var option = document.createElement('option'); - option.text = getActorName(actor); + option.text = getRoleName(actor); selectedActor.add(option); }); +//Viewers of the workflow +var viewers = []; +//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 = "All Roles"; +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_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 = []; function selectActor() { - console.log(selectedActor.value); + // console.log(selectedActor.value); highlightedSources = []; highlightedTargets = []; + selectedViewer.value = NO_VIEWER; workflow.actions.forEach(act => { if (act.actionData.mode != 'automatic' && act.actionData.actorNames.includes(selectedActor.value)) { highlightedSources.push(act.source.id); @@ -62,6 +135,17 @@ function selectActor() { }); } +function selectViewer() { + highlightedSources = []; + highlightedTargets = []; + selectedActor.value = NO_ACTOR; + workflow.states.forEach(st => { + if (st.stateData.viewerNames.includes(selectedViewer.value)) { + highlightedSources.push(st.id); + } + }); +} + var selfLoops = {}; // All edges whose targets equal their sources. var overlappingEdges = {}; // All edges whose target and source are connected by further. @@ -221,7 +305,8 @@ function removeState(state) { * @returns The colour the given node should have. */ function getNodeColour(node) { - var standard = selectedActor.value === 'All Actors' || highlightedSources.includes(node.id) || highlightedTargets.includes(node.id) + var standard = (selectedActor.value === NO_ACTOR && selectedViewer.value === NO_VIEWER) + || highlightedSources.includes(node.id) || highlightedTargets.includes(node.id) var alpha = standard ? 'ff' : '55'; if (node.stateData && node.stateData.final !== 'False' && node.stateData.final !== '') { if (node.stateData.final === 'True' || node.stateData.final === 'ok') { @@ -239,13 +324,15 @@ function getNodeColour(node) { } function isHighlightedEdge(edge) { - return (edge.actionData.mode != 'automatic' && edge.actionData.actorNames.includes(selectedActor.value)) || (edge.actionData.mode === 'automatic' && highlightedTargets.includes(edge.source.id)); + var data = edge.actionData + var selectedRole = data.mode != 'automatic' && (data.actorNames.includes(selectedActor.value) || data.viewerNames.includes(selectedViewer.value)) + return selectedRole || (data.mode === 'automatic' && highlightedTargets.includes(edge.source.id)); } function getEdgeColour(edge) { if (isHighlightedEdge(edge)) { return selection === edge ? edgeColourHighlightSelected : edgeColourHighlightDefault; - } else if (selectedActor.value !== 'All Actors') { + } else if (selectedActor.value !== NO_ACTOR) { return selection === edge ? edgeColourSubtleSelected : edgeColourSubtleDefault; } else { return selection === edge ? edgeColourSelected : edgeColourDefault;