workflows are now loaded via server requests
This commit is contained in:
parent
dfb4a8f0bb
commit
c242c4a39a
635
editor.js
635
editor.js
@ -48,150 +48,29 @@ function openSearchMenu(menuitem) {
|
|||||||
|
|
||||||
var workflow = {}
|
var workflow = {}
|
||||||
|
|
||||||
// fetch('./test.json')
|
fetch('http://localhost:8080/test.json')
|
||||||
// .then((response) => response.json())
|
.then((response) => response.json())
|
||||||
// .then((data) => {
|
.then((data) => {
|
||||||
// for (var key in data)
|
for (var key in data)
|
||||||
// workflow[key] = data[key];
|
workflow[key] = data[key];
|
||||||
// });
|
runnn();
|
||||||
|
});
|
||||||
// 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);
|
|
||||||
})
|
|
||||||
|
|
||||||
//Actors of the workflow
|
//Actors of the workflow
|
||||||
var actors = [];
|
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');
|
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
|
//Viewers of the workflow
|
||||||
var viewers = [];
|
var viewers = [];
|
||||||
|
const selectedViewer = document.getElementById('viewer');
|
||||||
//Actions/States with no explicit viewers
|
//Actions/States with no explicit viewers
|
||||||
var viewableByAll = []
|
var viewableByAll = []
|
||||||
//Possible initiators
|
//Possible initiators
|
||||||
var initiators = []
|
var initiators = []
|
||||||
//Implicit state from which initial actions can be selected
|
//Implicit state from which initial actions can be selected
|
||||||
var initState = null;
|
var initState = null;
|
||||||
//Identify all viewers of every action
|
const NO_ACTOR = 'None';
|
||||||
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_VIEWER = NO_ACTOR;
|
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
|
//source & target nodes of all currently highlighted actions
|
||||||
var highlightedSources = [];
|
var highlightedSources = [];
|
||||||
var highlightedTargets = [];
|
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 selection = null; // The currently selected node/edge.
|
||||||
var rightSelection = null; // The currently right clicked node/edge.
|
var rightSelection = null; // The currently right clicked node/edge.
|
||||||
var edgeTo = null; // Target of an edge to be created.
|
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 contextMenuBg = document.getElementById('ctmenubg'); //Click on background
|
||||||
const contextMenuSt = document.getElementById('ctmenust'); //Click on state
|
const contextMenuSt = document.getElementById('ctmenust'); //Click on state
|
||||||
const contextMenuEd = document.getElementById('ctmenued'); //Click on edge
|
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';
|
* Marks the given item as selected.
|
||||||
const edgeColourHighlightDefault = '#6ed4d4';
|
* @param {*} item The node or edge to select.
|
||||||
const edgeColourHighlightSelected = 'magenta';
|
*/
|
||||||
const edgeColourSubtleDefault = '#99999955';
|
function select(item) {
|
||||||
const edgeColourSubtleSelected = '#00000055';
|
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.
|
* Removes a state from the workflow.
|
||||||
* @param {*} role1
|
* @param {*} state The state to remove.
|
||||||
* @param {*} role2
|
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
function equalRoles(role1, role2) {
|
function removeState(state) {
|
||||||
role1 instanceof Role && (role1 = role1.json);
|
workflow.actions
|
||||||
role2 instanceof Role && (role2 = role2.json);
|
.filter(edge => edge.source === state || edge.target === state)
|
||||||
var equal = role1.tag === role2.tag;
|
.forEach(edge => removeAction(edge));
|
||||||
if (role1.tag == 'payload-reference') {
|
workflow.states.splice(workflow.states.indexOf(state), 1);
|
||||||
equal = equal && (role1['payload-label'] === role2['payload-label']);
|
var abbreviation = state.stateData && state.stateData.abbreviation;
|
||||||
} else if (role1.tag == 'user') {
|
abbreviation && stateAbbreviations.splice(stateAbbreviations.indexOf(abbreviation), 1);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
function runnn() {
|
||||||
* Marks the given item as selected.
|
|
||||||
* @param {*} item The node or edge to select.
|
stateIdCounter = workflow.states ? workflow.states.length : 0;
|
||||||
*/
|
actionIdCounter = workflow.states ? workflow.actions.length : 0;
|
||||||
function select(item) {
|
|
||||||
contextMenuEd.style.display = contextMenuSt.style.display = contextMenuBg.style.display = 'none';
|
//Parse workflow
|
||||||
edgeFrom = edgeTo = rightSelection = null;
|
|
||||||
selection = selection === item ? null : item;
|
workflow.states.forEach(state => {
|
||||||
if (selection === item) {
|
var messages = [];
|
||||||
while (sideContent.firstChild)
|
state.stateData.messages.forEach(msg => messages.push(new Message(msg)));
|
||||||
sideContent.removeChild(sideContent.lastChild);
|
state.stateData.messages = messages;
|
||||||
sidePanel.style.display = 'block'
|
var viewers = [];
|
||||||
sideHeading.innerHTML = item.name;
|
state.stateData.viewers.forEach(v => viewers.push(new Role(v)));
|
||||||
var data = document.createElement('div');
|
state.stateData.viewers = viewers;
|
||||||
var content = generatePanelContent(selection);
|
state.stateData.payload = new Payload(state.stateData.payload);
|
||||||
content.forEach(c => data.appendChild(c));
|
})
|
||||||
sideContent.appendChild(data);
|
|
||||||
var spStyle = window.getComputedStyle(sidePanel);
|
workflow.actions.forEach(action => {
|
||||||
var shStyle = window.getComputedStyle(sideHeading);
|
var messages = [];
|
||||||
sideContent.style.top = sideHeading.offsetHeight + parseFloat(spStyle.paddingTop) + parseFloat(shStyle.marginTop) + parseFloat(shStyle.marginBottom);
|
action.actionData.messages.forEach(msg => messages.push(new Message(msg)));
|
||||||
var sbStyle = window.getComputedStyle(sideButtons);
|
action.actionData.messages = messages;
|
||||||
sideContent.style.bottom = sideButtons.offsetHeight + parseFloat(spStyle.paddingBottom) + parseFloat(sbStyle.marginTop) + parseFloat(sbStyle.marginBottom);
|
var viewers = [];
|
||||||
// console.log(sideHeading.offsetHeight + shStyle.marginTop + shStyle.marginBottom);
|
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 {
|
} 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);
|
if (act.actionData.mode === 'initial') {
|
||||||
}
|
act.actionData.actorNames.forEach(an => !initiators.includes(an) && initiators.push(an));
|
||||||
|
|
||||||
|
|
||||||
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';
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
//Identify all viewers of every state
|
||||||
function removeRightSelection() {
|
workflow.states.forEach(st => {
|
||||||
if (rightSelection) {
|
if (st.name === '@@INIT') {
|
||||||
if (rightSelection.actionData) removeAction(rightSelection);
|
initState = st;
|
||||||
else removeState(rightSelection);
|
} else if (st.stateData.viewers.length === 0) {
|
||||||
if (selection === rightSelection) deselect();
|
viewableByAll.push(st.stateData);
|
||||||
edgeFrom = edgeTo = rightSelection = null;
|
} else {
|
||||||
contextMenuEd.style.display = contextMenuSt.style.display = contextMenuBg.style.display = 'none';
|
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.
|
* Checks if two roles are equal.
|
||||||
* @param {*} action The action to remove.
|
* @param {*} role1
|
||||||
|
* @param {*} role2
|
||||||
|
* @returns
|
||||||
*/
|
*/
|
||||||
function removeAction(action) {
|
function equalRoles(role1, role2) {
|
||||||
workflow.actions.splice(workflow.actions.indexOf(action), 1);
|
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']);
|
||||||
* Removes a state from the workflow.
|
} else if (role1.tag == 'user') {
|
||||||
* @param {*} state The state to remove.
|
equal = equal && (role1.user === role2.user);
|
||||||
*/
|
} else if (role1.tag == 'authorized') {
|
||||||
function removeState(state) {
|
equal = equal && (role1.authorized['dnf-terms'][0][0].var === role2.authorized['dnf-terms'][0][0].var);
|
||||||
workflow.actions
|
}
|
||||||
.filter(edge => edge.source === state || edge.target === state)
|
return equal;
|
||||||
.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -531,7 +531,6 @@ function getEdgeColour(edge) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Compute abbreviations of the names of all states
|
//Compute abbreviations of the names of all states
|
||||||
var stateAbbreviations = [];
|
|
||||||
workflow.states.forEach(state => {
|
workflow.states.forEach(state => {
|
||||||
// var label = node.name.substring(0, 5);
|
// 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)];
|
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;
|
edgeFrom = edgeTo = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var newStateCoords = {'x': 0, 'y': 0}; //Initial coordinates of the next new state
|
wfGraph
|
||||||
|
|
||||||
const Graph = ForceGraph()
|
|
||||||
(document.getElementById('graph'))
|
|
||||||
.linkDirectionalArrowLength(6)
|
.linkDirectionalArrowLength(6)
|
||||||
.linkDirectionalArrowRelPos(1)
|
.linkDirectionalArrowRelPos(1)
|
||||||
.linkColor(getEdgeColour)
|
.linkColor(getEdgeColour)
|
||||||
@ -574,7 +570,7 @@ const Graph = ForceGraph()
|
|||||||
.linkCanvasObjectMode(() => 'after')
|
.linkCanvasObjectMode(() => 'after')
|
||||||
.linkCanvasObject((edge, context) => {
|
.linkCanvasObject((edge, context) => {
|
||||||
const MAX_FONT_SIZE = 4;
|
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 source = edge.source;
|
||||||
const target = edge.target;
|
const target = edge.target;
|
||||||
@ -716,7 +712,7 @@ const Graph = ForceGraph()
|
|||||||
closeMenuItem();
|
closeMenuItem();
|
||||||
})
|
})
|
||||||
.onBackgroundRightClick(event => {
|
.onBackgroundRightClick(event => {
|
||||||
newStateCoords = Graph.screen2GraphCoords(event.layerX, event.layerY);
|
newStateCoords = wfGraph.screen2GraphCoords(event.layerX, event.layerY);
|
||||||
openContextMenu(event.layerX, event.layerY, contextMenuBg);
|
openContextMenu(event.layerX, event.layerY, contextMenuBg);
|
||||||
contextMenuEd.style.display = contextMenuSt.style.display = 'none';
|
contextMenuEd.style.display = contextMenuSt.style.display = 'none';
|
||||||
edgeFrom = edgeTo = rightSelection = null;
|
edgeFrom = edgeTo = rightSelection = null;
|
||||||
@ -724,4 +720,5 @@ const Graph = ForceGraph()
|
|||||||
})
|
})
|
||||||
.autoPauseRedraw(false);
|
.autoPauseRedraw(false);
|
||||||
|
|
||||||
updateGraph();
|
updateGraph();
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user