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;
|
||||
word-wrap: break-word;
|
||||
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>
|
||||
</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 id="graph"></div>
|
||||
</div>
|
||||
|
||||
107
editor.js
107
editor.js
@ -123,7 +123,8 @@ var highlightedSources = [];
|
||||
var highlightedTargets = [];
|
||||
|
||||
function selectActor() {
|
||||
// console.log(selectedActor.value);
|
||||
contextMenuEd.style.display = contextMenuSt.style.display = contextMenuBg.style.display = 'none';
|
||||
rightSelection = null;
|
||||
highlightedSources = [];
|
||||
highlightedTargets = [];
|
||||
selectedViewer.value = NO_VIEWER;
|
||||
@ -136,6 +137,8 @@ function selectActor() {
|
||||
}
|
||||
|
||||
function selectViewer() {
|
||||
contextMenuEd.style.display = contextMenuSt.style.display = contextMenuBg.style.display = 'none';
|
||||
rightSelection = null;
|
||||
highlightedSources = [];
|
||||
highlightedTargets = [];
|
||||
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.
|
||||
|
||||
var selection = null; // The currently selected node/edge.
|
||||
var rightSelection = null; // The currently right clicked node/edge.
|
||||
const sidePanel = document.getElementById('sidepanel');
|
||||
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 edgeColourSelected = '#000000ff';
|
||||
@ -254,6 +262,8 @@ function generatePanelContent(selection) {
|
||||
* @param {*} item The node or edge to select.
|
||||
*/
|
||||
function select(item) {
|
||||
contextMenuEd.style.display = contextMenuSt.style.display = contextMenuBg.style.display = 'none';
|
||||
rightSelection = null;
|
||||
selection = selection === item ? null : item;
|
||||
if (selection === item) {
|
||||
while (sideContent.firstChild)
|
||||
@ -268,7 +278,11 @@ function select(item) {
|
||||
sidePanel.style.display = 'none';
|
||||
}
|
||||
console.log(item);
|
||||
// TODO
|
||||
}
|
||||
|
||||
|
||||
function rightSelect() {
|
||||
select(rightSelection);
|
||||
}
|
||||
|
||||
|
||||
@ -296,17 +310,33 @@ function connect(source, target) {
|
||||
|
||||
|
||||
/**
|
||||
* Adds a new state to the workflow.
|
||||
* @param {*} x The x coordinate on the canvas.
|
||||
* @param {*} y The y coordinate on the canvas.
|
||||
* @returns The new state.
|
||||
* Adds a new state to the workflow and auto-selects it.
|
||||
*/
|
||||
function addState(x, y) {
|
||||
let nodeId = stateIdCounter ++;
|
||||
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();
|
||||
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)
|
||||
.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);
|
||||
}
|
||||
|
||||
|
||||
@ -340,18 +372,19 @@ function getNodeColour(node) {
|
||||
var standard = (selectedActor.value === NO_ACTOR && selectedViewer.value === NO_VIEWER)
|
||||
|| highlightedSources.includes(node.id) || highlightedTargets.includes(node.id)
|
||||
var alpha = standard ? 'ff' : '55';
|
||||
var isSelected = selection === node || rightSelection === node;
|
||||
if (node.stateData && node.stateData.final !== 'False' && node.stateData.final !== '') {
|
||||
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') {
|
||||
return (selection === node ? '#f77474' : '#f25050') + alpha;
|
||||
return (isSelected ? '#f77474' : '#f25050') + alpha;
|
||||
} else {
|
||||
//console.log(node.stateData.final);
|
||||
}
|
||||
} else if (node.name === '@@INIT') {
|
||||
return (selection === node ? '#e8cd84' : '#d1ad4b') + alpha;
|
||||
return (isSelected ? '#e8cd84' : '#d1ad4b') + alpha;
|
||||
} else {
|
||||
return (selection === node ? '#5fbad9' : '#4496b3') + alpha;
|
||||
return (isSelected ? '#5fbad9' : '#4496b3') + alpha;
|
||||
}
|
||||
}
|
||||
|
||||
@ -364,12 +397,13 @@ function isHighlightedEdge(edge) {
|
||||
}
|
||||
|
||||
function getEdgeColour(edge) {
|
||||
var isSelected = selection === edge || rightSelection === edge;
|
||||
if (isHighlightedEdge(edge)) {
|
||||
return selection === edge ? edgeColourHighlightSelected : edgeColourHighlightDefault;
|
||||
return isSelected ? edgeColourHighlightSelected : edgeColourHighlightDefault;
|
||||
} else if (selectedActor.value !== NO_ACTOR) {
|
||||
return selection === edge ? edgeColourSubtleSelected : edgeColourSubtleDefault;
|
||||
return isSelected ? edgeColourSubtleSelected : edgeColourSubtleDefault;
|
||||
} else {
|
||||
return selection === edge ? edgeColourSelected : edgeColourDefault;
|
||||
return isSelected ? edgeColourSelected : edgeColourDefault;
|
||||
}
|
||||
}
|
||||
|
||||
@ -393,6 +427,20 @@ workflow.states.forEach(state => {
|
||||
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()
|
||||
(document.getElementById('graph'))
|
||||
.linkDirectionalArrowLength(6)
|
||||
@ -505,13 +553,28 @@ const Graph = ForceGraph()
|
||||
node.fy = node.y;
|
||||
})
|
||||
.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))
|
||||
.onLinkRightClick((edge, _) => removeAction(edge))
|
||||
.onBackgroundClick(event => {
|
||||
var coords = Graph.screen2GraphCoords(event.layerX, event.layerY);
|
||||
var newState = addState(coords.x, coords.y);
|
||||
selection = newState;
|
||||
.onLinkRightClick((edge, event) => {
|
||||
openContextMenu(event.layerX, event.layerY, contextMenuEd);
|
||||
contextMenuBg.style.display = contextMenuSt.style.display = 'none';
|
||||
rightSelection = edge;
|
||||
})
|
||||
.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);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user