new highlighting mode: viewers of states/actions

This commit is contained in:
David Mosbach 2023-05-24 19:17:56 +02:00
parent 48f7de6220
commit 89809dc551
5 changed files with 146 additions and 25 deletions

View File

@ -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]

View File

@ -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
---------------------------------------

View File

@ -4,4 +4,30 @@
#editor {
border: 10px solid red;
} */
} */
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;
}

View File

@ -8,17 +8,22 @@
<!--<script src="../../dist/force-graph.js"></script>-->
</head>
<body style="margin: 0 0 0 0;">
<body>
<div id="editor">
<!-- <br/>
<div style="text-align: center; color: silver">
<b>New node:</b> click on the canvas, <b>New link:</b> drag one node close enough to another one,
<b>Rename</b> node or link by clicking on it, <b>Remove</b> node or link by right-clicking on it
</div> -->
<div id="settings" style="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;">
<label for="actor">Highlight actor: </label>
<select name="actor" id="actor" onchange="selectActor();"></select>
<div id="settings">
<div>
<label for="actor">Highlight actor: </label>
<select name="actor" id="actor" onchange="selectActor();"></select>
</div>
<div>
<label for="viewer">Highlight viewer: </label>
<select name="viewer" id="viewer" onchange="selectViewer();"></select>
</div>
</div>
<div id="graph"></div>
</div>

119
editor.js
View File

@ -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;