improved interaction & styling
This commit is contained in:
parent
9ab5eec5e9
commit
89c1d44141
147
editor.ts
147
editor.ts
@ -220,9 +220,12 @@ export function search(text: string) {
|
||||
head.classList.add('search-result-head');
|
||||
r.appendChild(head);
|
||||
var info = document.createElement('div');
|
||||
if ((<WF.WFEdge | WF.WFNode>target).hasOwnProperty('actionData'))
|
||||
info.innerText = (<WF.WFEdge>target).source.name + ' → ' + (<WF.WFEdge>target).target.name;
|
||||
else
|
||||
if ((<WF.WFEdge | WF.WFNode>target).hasOwnProperty('actionData')) {
|
||||
var eTarget = (<WF.WFEdge>target)
|
||||
var src = (eTarget.source instanceof WF.WFNode) ? eTarget.source.name : '?';
|
||||
var tgt = (eTarget.target instanceof WF.WFNode) ? eTarget.target.name : '?';
|
||||
info.innerText = src + ' → ' + tgt;
|
||||
} else
|
||||
info.innerText = (<WF.WFNode>target).stateData.abbreviation;
|
||||
info.setAttribute('title', info.innerText);
|
||||
info.classList.add('search-result-info');
|
||||
@ -421,7 +424,7 @@ export function selectViewer() {
|
||||
});
|
||||
}
|
||||
|
||||
var selection : WF.WFNode | WF.WFEdge | null = null; // The currently selected node/edge.
|
||||
var selection : WF.WFNode | WF.WFGhostNode | WF.WFEdge | null = null; // The currently selected node/edge.
|
||||
var rightSelection : WF.WFNode | WF.WFEdge | null = null; // The currently right clicked node/edge.
|
||||
var edgeTo : WF.WFNode | null = null; // Target of an edge to be created.
|
||||
var edgeFrom : WF.WFNode | null = null; // Start on an edge to be created.
|
||||
@ -510,15 +513,16 @@ document.querySelectorAll('.delete-item').forEach(elem => elem.addEventListener(
|
||||
* Marks the given item as selected.
|
||||
* @param {*} item The node or edge to select.
|
||||
*/
|
||||
export function select(item: WF.WFEdge | WF.WFNode) {
|
||||
export function select(item: WF.WFEdge | WF.WFNode | WF.WFGhostNode) {
|
||||
closeContextMenus(contextMenuEd, contextMenuSt, contextMenuBg);
|
||||
edgeFrom = edgeTo = rightSelection = null;
|
||||
selection = selection === item ? null : item;
|
||||
if (selection === item) {
|
||||
if (!(item instanceof WF.WFGhostNode) && selection === item) {
|
||||
while (sideContent.firstChild)
|
||||
sideContent.removeChild(<ChildNode>sideContent.lastChild);
|
||||
function callback() {
|
||||
if (item.hasOwnProperty('actionData')) {
|
||||
if (item instanceof WF.WFGhostNode) return;
|
||||
if (item instanceof WF.WFEdge) {
|
||||
sideInfoEdge.style.display = 'block';
|
||||
sideInfoNode.style.display = 'none';
|
||||
} else {
|
||||
@ -589,20 +593,17 @@ export function addEdge() {
|
||||
id: `@@ghost@(${x},${y})`,
|
||||
x: x,
|
||||
y: y,
|
||||
name: 'Drag me!',
|
||||
fx: x,
|
||||
fy: y,
|
||||
val: 5 });
|
||||
val: 7 });
|
||||
var ghostState2 = new WF.WFGhostNode({
|
||||
id: `@@ghost@(${x+200},${y})`,
|
||||
x: x + 200,
|
||||
id: `@@ghost@(${x+50},${y})`,
|
||||
x: x + 50,
|
||||
y: y,
|
||||
name: 'Drag me!',
|
||||
fx: x + 200,
|
||||
fx: x + 50,
|
||||
fy: y,
|
||||
val: 5 });
|
||||
val: 7 });
|
||||
workflow.states.push(ghostState, ghostState2);
|
||||
console.log('is ghost:', ghostState instanceof WF.WFGhostNode, ghostState2 instanceof WF.WFGhostNode);
|
||||
updateGraph();
|
||||
connect(ghostState, ghostState2);
|
||||
}
|
||||
@ -804,7 +805,7 @@ function prepareWorkflow() {
|
||||
|
||||
//Create search index
|
||||
workflow.states.forEach(state =>
|
||||
nodeIndex.add(state.id, state.name)
|
||||
(state instanceof WF.WFNode) && nodeIndex.add(state.id, state.name)
|
||||
);
|
||||
workflow.actions.forEach(action =>
|
||||
actionIndex.add(action.id, action.name)
|
||||
@ -965,30 +966,75 @@ const edgeColourSubtleSelected = '#00000055';
|
||||
const edgeColourSubtleSelectedDarkMode = '#ffffff55';
|
||||
const edgeColourMostSubtle = '#99999944';
|
||||
|
||||
class Colour {
|
||||
|
||||
private baseValue: string;
|
||||
baseDark: string;
|
||||
|
||||
constructor(r: number, g: number, b: number) {
|
||||
var arr = [r,g,b];
|
||||
if (! arr.every((val: number) => val >= 0 && val <= 255))
|
||||
throw new Error('rgb out of bounds (0,255)');
|
||||
this.baseValue = '#' + arr.map((v: number) => v.toString(16).padStart(2, '0')).join('');
|
||||
this.baseDark = '#' + arr.map((v: number) => (Math.max(v-80, 0)).toString(16).padStart(2, '0')).join('');
|
||||
Object.seal(this);
|
||||
console.log('colour:', this.baseValue, 'dark:', this.baseDark);
|
||||
|
||||
}
|
||||
|
||||
value(alpha?: number) {
|
||||
if (alpha === undefined) return this.baseValue;
|
||||
if (! (alpha >= 0 && alpha <= 255))
|
||||
throw new Error('rgba out of bounds (0,255)');
|
||||
return this.baseValue + alpha.toString(16).padStart(2, '0');
|
||||
}
|
||||
|
||||
dark(alpha?: number) {
|
||||
if (alpha === undefined) return this.baseDark;
|
||||
if (! (alpha >= 0 && alpha <= 255))
|
||||
throw new Error('rgba out of bounds (0,255)');
|
||||
return this.baseDark + alpha.toString(16).padStart(2, '0');
|
||||
}
|
||||
}
|
||||
|
||||
const nodeColourDefault = new Colour(0x36, 0x79, 0xd2);
|
||||
const nodeColourSelected = new Colour(0x53, 0x8c, 0xd9);
|
||||
const nodeColourDefaultUnknown = new Colour(0xee, 0xaa, 0x00);
|
||||
const nodeColourSelectedUnknown = new Colour(0xff, 0xbc, 0x15);
|
||||
const nodeColourDefaultFinal = new Colour(0x31, 0xa8, 0x10);
|
||||
const nodeColourSelectedFinal = new Colour(0x3a, 0xc7, 0x13);
|
||||
const nodeColourDefaultNotOk = new Colour(0xe7, 0x21, 0x5a);
|
||||
const nodeColourSelectedNotOk = new Colour(0xec, 0x4e, 0x7b);
|
||||
const nodeColourDefaultInit = new Colour(0xee, 0xaa, 0x00);
|
||||
const nodeColourSelectedInit = new Colour(0xff, 0xbc, 0x15);
|
||||
const nodeColourGhost = new Colour(0xff, 0xff, 0xff);
|
||||
const nodeColourGhostDark = new Colour(0x00, 0x00, 0x00);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param node
|
||||
* @returns The colour the given node should have.
|
||||
*/
|
||||
function getNodeColour(node: WF.WFNode | WF.WFGhostNode) {
|
||||
var standard = (selectedActor.value === NO_ACTOR && selectedViewer.value === NO_VIEWER)
|
||||
|| highlightedSources.includes(node.id) || highlightedTargets.includes(node.id)
|
||||
var alpha = standard ? 'ff' : '55';
|
||||
function getNodeColour(node: WF.WFNode | WF.WFGhostNode) : Colour {
|
||||
var isSelected = selection === node || rightSelection === node;
|
||||
if (node instanceof WF.WFNode && node.stateData.final !== 'false' && node.stateData.final !== '') {
|
||||
if (node.stateData.final === 'true' || node.stateData.final === 'ok') {
|
||||
return (isSelected ? '#3ac713' : '#31a810') + alpha;
|
||||
return isSelected ? nodeColourSelectedFinal : nodeColourDefaultFinal;
|
||||
} else if (node.stateData.final === 'not-ok') {
|
||||
return (isSelected ? '#ec4e7b' : '#e7215a') + alpha;
|
||||
return isSelected ? nodeColourSelectedNotOk : nodeColourDefaultNotOk;
|
||||
} else {
|
||||
return (isSelected ? '#ffbc15' : '#eeaa00') + alpha;
|
||||
return isSelected ? nodeColourSelectedUnknown : nodeColourDefaultUnknown;
|
||||
}
|
||||
} else if (node.name === '@@INIT') {
|
||||
return (isSelected ? '#ffbc15' : '#eeaa00') + alpha;
|
||||
} else if (node instanceof WF.WFGhostNode) {
|
||||
return '#cafc03ff';
|
||||
return darkMode ? nodeColourGhostDark : nodeColourGhost;
|
||||
} else if (node.name === '@@INIT') {
|
||||
return isSelected ? nodeColourSelectedInit : nodeColourDefaultInit;
|
||||
} else {
|
||||
return (isSelected ? '#538cd9' : '#3679d2') + alpha;
|
||||
return isSelected ? nodeColourSelected : nodeColourDefault;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1114,48 +1160,59 @@ function getEdgeColour(edge: LinkObject) {
|
||||
.linkDirectionalParticleWidth((edge: LinkObject) => (isHighlightedActorEdge(edge as WF.WFEdge)) ? 3 : 0)
|
||||
.nodeCanvasObject((node: NodeObject, ctx: CanvasRenderingContext2D) => {
|
||||
const wfNode = (node instanceof WF.WFNode) ? node as WF.WFNode : node as WF.WFGhostNode;
|
||||
ctx.fillStyle = getNodeColour(wfNode);
|
||||
var standard = (selectedActor.value === NO_ACTOR && selectedViewer.value === NO_VIEWER)
|
||||
|| highlightedSources.includes(wfNode.id) || highlightedTargets.includes(wfNode.id)
|
||||
var alpha : number;
|
||||
if (wfNode instanceof WF.WFGhostNode) alpha = 0x80;
|
||||
else if (standard) alpha = 0xff;
|
||||
else alpha = 0x55;
|
||||
var colour = getNodeColour(wfNode);
|
||||
ctx.save();
|
||||
ctx.fillStyle = colour.value(alpha);
|
||||
ctx.shadowColor = colour.dark(0x80);
|
||||
ctx.shadowBlur = 20;
|
||||
ctx.beginPath();
|
||||
ctx.arc(wfNode.x, wfNode.y, 2*wfNode.val, 0, 2 * Math.PI, false);
|
||||
ctx.fill();
|
||||
|
||||
var selected = (node === selection || node === rightSelection);
|
||||
ctx.lineWidth = selected ? 1 : 0.2;
|
||||
ctx.restore();
|
||||
|
||||
if (node instanceof WF.WFGhostNode) {
|
||||
ctx.save()
|
||||
ctx.setLineDash([1, 2]);
|
||||
ctx.strokeStyle = darkMode ? 'white' : 'black';
|
||||
ctx.strokeStyle = nodeColourDefaultNotOk.value();
|
||||
ctx.shadowColor = nodeColourDefaultNotOk.dark(0x80);
|
||||
ctx.shadowBlur = 20;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
} else {
|
||||
} else if (node === selection || node === rightSelection) {
|
||||
ctx.save();
|
||||
ctx.lineCap = 'round';
|
||||
if (selected)
|
||||
ctx.strokeStyle = darkMode ? 'white' : 'black';
|
||||
else
|
||||
ctx.strokeStyle = !darkMode ? 'white' : 'black';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = darkMode ? 'white' : 'black';
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.font = '4px Inter';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
|
||||
if (wfNode instanceof WF.WFNode && wfNode.stateData.abbreviation)
|
||||
if (wfNode instanceof WF.WFNode && wfNode.stateData.abbreviation) {
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.fillText(wfNode.stateData.abbreviation, wfNode.x, wfNode.y);
|
||||
else if (wfNode instanceof WF.WFGhostNode)
|
||||
} else if (wfNode instanceof WF.WFGhostNode) {
|
||||
ctx.fillStyle = darkMode ? 'white' : 'black';
|
||||
ctx.fillText(wfNode.text, wfNode.x, wfNode.y);
|
||||
}
|
||||
})
|
||||
.onNodeDragEnd((node: NodeObject) => {
|
||||
node.fx = node.x;
|
||||
node.fy = node.y;
|
||||
})
|
||||
.onNodeClick((node: NodeObject, _: MouseEvent) => {
|
||||
const wfNode = node as WF.WFNode;
|
||||
const wfNode = node as (WF.WFNode | WF.WFGhostNode);
|
||||
if (edgeFrom) {
|
||||
connect(edgeFrom, wfNode);
|
||||
edgeFrom = null;
|
||||
@ -1166,6 +1223,10 @@ function getEdgeColour(edge: LinkObject) {
|
||||
closeMenuItem();
|
||||
})
|
||||
.onNodeRightClick((node: NodeObject, event: MouseEvent) => {
|
||||
if (node instanceof WF.WFGhostNode) {
|
||||
closeContextMenus(contextMenuEd, contextMenuSt, contextMenuBg);
|
||||
return;
|
||||
}
|
||||
//@ts-ignore TODO replace layerX/layerY
|
||||
openContextMenu(event.layerX, event.layerY, contextMenuSt);
|
||||
closeContextMenus(contextMenuBg, contextMenuEd);
|
||||
@ -1210,21 +1271,19 @@ function getEdgeColour(edge: LinkObject) {
|
||||
(<any>wfGraph.d3Force('charge')).initialize = function(_nodes: NodeObject[], ...args: any) {
|
||||
var nodes : WF.WFNode[] = [];
|
||||
_nodes.forEach(node => (node instanceof WF.WFNode && nodes.push(node)));
|
||||
console.log('rem. total:', nodes.length); //TODO already store them instead of computing each tick
|
||||
//TODO already store them instead of computing each tick
|
||||
oldChargeInit(nodes, args);
|
||||
};
|
||||
|
||||
(<any>wfGraph.d3Force('center')).initialize = function(_nodes: NodeObject[], ...args: any) {
|
||||
var nodes : WF.WFNode[] = [];
|
||||
_nodes.forEach(node => (node instanceof WF.WFNode && nodes.push(node)));
|
||||
console.log('rem. total:', nodes.length);
|
||||
oldCenterInit(nodes, args);
|
||||
};
|
||||
|
||||
(<any>wfGraph.d3Force('link')).initialize = function(_nodes: NodeObject[], ...args: any) {
|
||||
var nodes : WF.WFNode[] = [];
|
||||
_nodes.forEach(node => (node instanceof WF.WFNode && nodes.push(node)));
|
||||
console.log('rem. total:', nodes.length);
|
||||
oldLinkInit(nodes, args);
|
||||
};
|
||||
|
||||
|
||||
6
start.sh
6
start.sh
@ -5,9 +5,9 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
|
||||
# echo 'Transpiling to JS...'
|
||||
# npx tsc
|
||||
echo 'Transpiling to JS & generating Webpack bundle...'
|
||||
echo 'Transpiling to JS...'
|
||||
npx tsc
|
||||
echo 'Generating Webpack bundle...'
|
||||
npx webpack
|
||||
echo 'Starting server...'
|
||||
npx http-server --cors -o ./editor.html
|
||||
|
||||
@ -1,18 +1,19 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: './editor.ts',
|
||||
mode: 'development',
|
||||
entry: './editor.js',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
use: 'js-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.tsx', '.ts', '.js'],
|
||||
extensions: ['.js'],
|
||||
},
|
||||
output: {
|
||||
filename: 'bundle.js',
|
||||
|
||||
@ -26,7 +26,6 @@ export type GhostNodeFormat = {
|
||||
fx: number,
|
||||
fy: number,
|
||||
id: string,
|
||||
name: string,
|
||||
val: number
|
||||
}
|
||||
|
||||
@ -173,17 +172,15 @@ export class WFGhostNode implements NodeObject {
|
||||
fx: number;
|
||||
fy: number;
|
||||
id: string;
|
||||
name: string;
|
||||
val: number;
|
||||
|
||||
constructor(json: GhostNodeFormat) {
|
||||
this.text = 'Drag me!';
|
||||
this.text = '?';
|
||||
this.x = json.x;
|
||||
this.y = json.y;
|
||||
this.fx = json.fx;
|
||||
this.fy = json.fy;
|
||||
this.id = json.id;
|
||||
this.name = json.name;
|
||||
this.val = json.val;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user