added context menus with quick actions
This commit is contained in:
parent
4dbe66ab5f
commit
57b6aeacdf
37
editor.css
37
editor.css
@ -76,4 +76,41 @@ body {
|
|||||||
hyphens: auto;
|
hyphens: auto;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
height: 80%;
|
height: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contextmenu {
|
||||||
|
display: none;
|
||||||
|
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
|
||||||
|
border-radius: 5px;
|
||||||
|
line-height: 2;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contextmenu div {
|
||||||
|
padding: 5px;
|
||||||
|
background-color: rgb(230, 230, 230);
|
||||||
|
opacity: 0.95;
|
||||||
|
transition: background-color 100ms ease-out 50ms;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contextmenu .menutop {
|
||||||
|
border-top-left-radius: 5px;
|
||||||
|
border-top-right-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contextmenu .menubottom {
|
||||||
|
border-bottom-left-radius: 5px;
|
||||||
|
border-bottom-right-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contextmenu div:hover {
|
||||||
|
background-color: rgb(240, 240, 240);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contextmenu div:active {
|
||||||
|
background-color: rgb(250, 250, 250);
|
||||||
}
|
}
|
||||||
59
editor.html
59
editor.html
@ -35,7 +35,64 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div id="sidecontent"></div>
|
<div id="sidecontent"></div>
|
||||||
|
</div>
|
||||||
|
<div id="ctmenubg" class="contextmenu">
|
||||||
|
<div class="menutop" onclick="addState()">
|
||||||
|
<svg height="10" width="10" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="5" cy="5" r="4" stroke="rgb(63, 63, 63)" stroke-width="2" fill="none" />
|
||||||
|
</svg>
|
||||||
|
New State
|
||||||
|
</div>
|
||||||
|
<div class="menubottom">
|
||||||
|
<svg height="10" width="10" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<polyline points="1,1 9,9" style="fill:none;stroke:rgb(63, 63, 63);stroke-width:2" stroke-linecap="round" />
|
||||||
|
</svg>
|
||||||
|
New Edge
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="ctmenust" class="contextmenu">
|
||||||
|
<div class="menutop">
|
||||||
|
<svg height="10" width="10" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<polyline points="1,1 9,9" style="fill:none;stroke:rgb(63, 63, 63);stroke-width:2" stroke-linecap="round" />
|
||||||
|
</svg>
|
||||||
|
New Edge from here
|
||||||
|
</div>
|
||||||
|
<div class="menucenter">
|
||||||
|
<svg height="10" width="10" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<polyline points="1,9 9,1" style="fill:none;stroke:rgb(63, 63, 63);stroke-width:2" stroke-linecap="round" />
|
||||||
|
</svg>
|
||||||
|
New Edge to here
|
||||||
|
</div>
|
||||||
|
<div class="menucenter" onclick="rightSelect()">
|
||||||
|
<svg height="10" width="10" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<polyline points="1,9 9,1" style="fill:none;stroke:rgb(63, 63, 63);stroke-width:4" />
|
||||||
|
<polyline points="1,9 8,2" style="fill:none;stroke:rgb(63, 63, 63);stroke-width:2" stroke-linecap="round" />
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</div>
|
||||||
|
<div class="menubottom" onclick="removeRightSelection()">
|
||||||
|
<svg height="10" width="10" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<polyline points="1,1 9,9" style="fill:none;stroke:rgb(183, 76, 76);stroke-width:2" stroke-linecap="round" />
|
||||||
|
<polyline points="9,1 1,9" style="fill:none;stroke:rgb(183, 76, 76);stroke-width:2" stroke-linecap="round" />
|
||||||
|
</svg>
|
||||||
|
Delete
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="ctmenued" class="contextmenu">
|
||||||
|
<div class="menutop" onclick="rightSelect()">
|
||||||
|
<svg height="10" width="10" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<polyline points="1,9 9,1" style="fill:none;stroke:rgb(63, 63, 63);stroke-width:4" />
|
||||||
|
<polyline points="1,9 8,2" style="fill:none;stroke:rgb(63, 63, 63);stroke-width:2" stroke-linecap="round" />
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</div>
|
||||||
|
<div class="menubottom" onclick="removeRightSelection()">
|
||||||
|
<svg height="10" width="10" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<polyline points="1,1 9,9" style="fill:none;stroke:rgb(183, 76, 76);stroke-width:2" stroke-linecap="round" />
|
||||||
|
<polyline points="9,1 1,9" style="fill:none;stroke:rgb(183, 76, 76);stroke-width:2" stroke-linecap="round" />
|
||||||
|
</svg>
|
||||||
|
Delete
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="graph"></div>
|
<div id="graph"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
107
editor.js
107
editor.js
@ -123,7 +123,8 @@ var highlightedSources = [];
|
|||||||
var highlightedTargets = [];
|
var highlightedTargets = [];
|
||||||
|
|
||||||
function selectActor() {
|
function selectActor() {
|
||||||
// console.log(selectedActor.value);
|
contextMenuEd.style.display = contextMenuSt.style.display = contextMenuBg.style.display = 'none';
|
||||||
|
rightSelection = null;
|
||||||
highlightedSources = [];
|
highlightedSources = [];
|
||||||
highlightedTargets = [];
|
highlightedTargets = [];
|
||||||
selectedViewer.value = NO_VIEWER;
|
selectedViewer.value = NO_VIEWER;
|
||||||
@ -136,6 +137,8 @@ function selectActor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function selectViewer() {
|
function selectViewer() {
|
||||||
|
contextMenuEd.style.display = contextMenuSt.style.display = contextMenuBg.style.display = 'none';
|
||||||
|
rightSelection = null;
|
||||||
highlightedSources = [];
|
highlightedSources = [];
|
||||||
highlightedTargets = [];
|
highlightedTargets = [];
|
||||||
selectedActor.value = NO_ACTOR;
|
selectedActor.value = NO_ACTOR;
|
||||||
@ -153,8 +156,13 @@ const selfLoopCurvMin = 0.5; // Minimum curvature of a self loop.
|
|||||||
const curvatureMinMax = 0.2; // Minimum/maximum curvature (1 +/- x) of overlapping edges.
|
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.
|
||||||
const sidePanel = document.getElementById('sidepanel');
|
const sidePanel = document.getElementById('sidepanel');
|
||||||
const sideContent = document.getElementById('sidecontent');
|
const sideContent = document.getElementById('sidecontent');
|
||||||
|
//Context menus
|
||||||
|
const contextMenuBg = document.getElementById('ctmenubg'); //Click on background
|
||||||
|
const contextMenuSt = document.getElementById('ctmenust'); //Click on state
|
||||||
|
const contextMenuEd = document.getElementById('ctmenued'); //Click on edge
|
||||||
|
|
||||||
const edgeColourDefault = '#999999ff';
|
const edgeColourDefault = '#999999ff';
|
||||||
const edgeColourSelected = '#000000ff';
|
const edgeColourSelected = '#000000ff';
|
||||||
@ -254,6 +262,8 @@ function generatePanelContent(selection) {
|
|||||||
* @param {*} item The node or edge to select.
|
* @param {*} item The node or edge to select.
|
||||||
*/
|
*/
|
||||||
function select(item) {
|
function select(item) {
|
||||||
|
contextMenuEd.style.display = contextMenuSt.style.display = contextMenuBg.style.display = 'none';
|
||||||
|
rightSelection = null;
|
||||||
selection = selection === item ? null : item;
|
selection = selection === item ? null : item;
|
||||||
if (selection === item) {
|
if (selection === item) {
|
||||||
while (sideContent.firstChild)
|
while (sideContent.firstChild)
|
||||||
@ -268,7 +278,11 @@ function select(item) {
|
|||||||
sidePanel.style.display = 'none';
|
sidePanel.style.display = 'none';
|
||||||
}
|
}
|
||||||
console.log(item);
|
console.log(item);
|
||||||
// TODO
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function rightSelect() {
|
||||||
|
select(rightSelection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -296,17 +310,33 @@ function connect(source, target) {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new state to the workflow.
|
* Adds a new state to the workflow and auto-selects it.
|
||||||
* @param {*} x The x coordinate on the canvas.
|
|
||||||
* @param {*} y The y coordinate on the canvas.
|
|
||||||
* @returns The new state.
|
|
||||||
*/
|
*/
|
||||||
function addState(x, y) {
|
function addState() {
|
||||||
let nodeId = stateIdCounter ++;
|
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};
|
state = {id: nodeId, x: x, y: y, name: 'state_' + nodeId, fx: x, fy: y, val: 5};
|
||||||
workflow.states.push(state);
|
workflow.states.push(state);
|
||||||
updateGraph();
|
updateGraph();
|
||||||
return state;
|
select(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addEdge() {
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeRightSelection() {
|
||||||
|
if (rightSelection) {
|
||||||
|
if (rightSelection.actionData) removeAction(rightSelection);
|
||||||
|
else removeState(rightSelection);
|
||||||
|
if (selection === rightSelection) {
|
||||||
|
selection = null;
|
||||||
|
sidePanel.style.display = 'none';
|
||||||
|
}
|
||||||
|
rightSelection = null;
|
||||||
|
contextMenuEd.style.display = contextMenuSt.style.display = contextMenuBg.style.display = 'none';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -328,6 +358,8 @@ function removeState(state) {
|
|||||||
.filter(edge => edge.source === state || edge.target === state)
|
.filter(edge => edge.source === state || edge.target === state)
|
||||||
.forEach(edge => removeAction(edge));
|
.forEach(edge => removeAction(edge));
|
||||||
workflow.states.splice(workflow.states.indexOf(state), 1);
|
workflow.states.splice(workflow.states.indexOf(state), 1);
|
||||||
|
var abbreviation = state.stateData && state.stateData.abbreviation;
|
||||||
|
abbreviation && stateAbbreviations.splice(stateAbbreviations.indexOf(abbreviation), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -340,18 +372,19 @@ function getNodeColour(node) {
|
|||||||
var standard = (selectedActor.value === NO_ACTOR && selectedViewer.value === NO_VIEWER)
|
var standard = (selectedActor.value === NO_ACTOR && selectedViewer.value === NO_VIEWER)
|
||||||
|| highlightedSources.includes(node.id) || highlightedTargets.includes(node.id)
|
|| highlightedSources.includes(node.id) || highlightedTargets.includes(node.id)
|
||||||
var alpha = standard ? 'ff' : '55';
|
var alpha = standard ? 'ff' : '55';
|
||||||
|
var isSelected = selection === node || rightSelection === node;
|
||||||
if (node.stateData && node.stateData.final !== 'False' && node.stateData.final !== '') {
|
if (node.stateData && node.stateData.final !== 'False' && node.stateData.final !== '') {
|
||||||
if (node.stateData.final === 'True' || node.stateData.final === 'ok') {
|
if (node.stateData.final === 'True' || node.stateData.final === 'ok') {
|
||||||
return (selection === node ? '#a4eb34' : '#7fad36') + alpha;
|
return (isSelected ? '#a4eb34' : '#7fad36') + alpha;
|
||||||
} else if (node.stateData.final === 'not-ok') {
|
} else if (node.stateData.final === 'not-ok') {
|
||||||
return (selection === node ? '#f77474' : '#f25050') + alpha;
|
return (isSelected ? '#f77474' : '#f25050') + alpha;
|
||||||
} else {
|
} else {
|
||||||
//console.log(node.stateData.final);
|
//console.log(node.stateData.final);
|
||||||
}
|
}
|
||||||
} else if (node.name === '@@INIT') {
|
} else if (node.name === '@@INIT') {
|
||||||
return (selection === node ? '#e8cd84' : '#d1ad4b') + alpha;
|
return (isSelected ? '#e8cd84' : '#d1ad4b') + alpha;
|
||||||
} else {
|
} else {
|
||||||
return (selection === node ? '#5fbad9' : '#4496b3') + alpha;
|
return (isSelected ? '#5fbad9' : '#4496b3') + alpha;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,12 +397,13 @@ function isHighlightedEdge(edge) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getEdgeColour(edge) {
|
function getEdgeColour(edge) {
|
||||||
|
var isSelected = selection === edge || rightSelection === edge;
|
||||||
if (isHighlightedEdge(edge)) {
|
if (isHighlightedEdge(edge)) {
|
||||||
return selection === edge ? edgeColourHighlightSelected : edgeColourHighlightDefault;
|
return isSelected ? edgeColourHighlightSelected : edgeColourHighlightDefault;
|
||||||
} else if (selectedActor.value !== NO_ACTOR) {
|
} else if (selectedActor.value !== NO_ACTOR) {
|
||||||
return selection === edge ? edgeColourSubtleSelected : edgeColourSubtleDefault;
|
return isSelected ? edgeColourSubtleSelected : edgeColourSubtleDefault;
|
||||||
} else {
|
} else {
|
||||||
return selection === edge ? edgeColourSelected : edgeColourDefault;
|
return isSelected ? edgeColourSelected : edgeColourDefault;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,6 +427,20 @@ workflow.states.forEach(state => {
|
|||||||
state.stateData.abbreviation = labelString;
|
state.stateData.abbreviation = labelString;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} event
|
||||||
|
* @param {HTMLElement} menu
|
||||||
|
*/
|
||||||
|
function openContextMenu(x, y, menu) {
|
||||||
|
menu.style.top = y - 25;
|
||||||
|
menu.style.left = x + 20;
|
||||||
|
menu.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
var newStateCoords = {'x': 0, 'y': 0}; //Initial coordinates of the next new state
|
||||||
|
|
||||||
const Graph = ForceGraph()
|
const Graph = ForceGraph()
|
||||||
(document.getElementById('graph'))
|
(document.getElementById('graph'))
|
||||||
.linkDirectionalArrowLength(6)
|
.linkDirectionalArrowLength(6)
|
||||||
@ -505,13 +553,28 @@ const Graph = ForceGraph()
|
|||||||
node.fy = node.y;
|
node.fy = node.y;
|
||||||
})
|
})
|
||||||
.onNodeClick((node, _) => select(node))
|
.onNodeClick((node, _) => select(node))
|
||||||
.onNodeRightClick((node, _) => removeState(node))
|
.onNodeRightClick((node, event) => {
|
||||||
|
openContextMenu(event.layerX, event.layerY, contextMenuSt);
|
||||||
|
contextMenuBg.style.display = contextMenuEd.style.display = 'none';
|
||||||
|
rightSelection = node;
|
||||||
|
})
|
||||||
.onLinkClick((edge, _) => select(edge))
|
.onLinkClick((edge, _) => select(edge))
|
||||||
.onLinkRightClick((edge, _) => removeAction(edge))
|
.onLinkRightClick((edge, event) => {
|
||||||
.onBackgroundClick(event => {
|
openContextMenu(event.layerX, event.layerY, contextMenuEd);
|
||||||
var coords = Graph.screen2GraphCoords(event.layerX, event.layerY);
|
contextMenuBg.style.display = contextMenuSt.style.display = 'none';
|
||||||
var newState = addState(coords.x, coords.y);
|
rightSelection = edge;
|
||||||
selection = newState;
|
})
|
||||||
|
.onBackgroundClick(_ => {
|
||||||
|
contextMenuEd.style.display = contextMenuSt.style.display = contextMenuBg.style.display = 'none';
|
||||||
|
sidePanel.style.display = 'none';
|
||||||
|
rightSelection = null;
|
||||||
|
selection = null;
|
||||||
|
})
|
||||||
|
.onBackgroundRightClick(event => {
|
||||||
|
newStateCoords = Graph.screen2GraphCoords(event.layerX, event.layerY);
|
||||||
|
openContextMenu(event.layerX, event.layerY, contextMenuBg);
|
||||||
|
contextMenuEd.style.display = contextMenuSt.style.display = 'none';
|
||||||
|
rightSelection = null;
|
||||||
})
|
})
|
||||||
.autoPauseRedraw(false);
|
.autoPauseRedraw(false);
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user