webpack setup

This commit is contained in:
David Mosbach 2023-08-24 04:18:07 +02:00
parent bfdb0a97e7
commit 6ea4e789c7
11 changed files with 1744 additions and 72 deletions

1
.gitignore vendored
View File

@ -10,3 +10,4 @@ server.py
/spaß /spaß
/node_modules /node_modules
*.js *.js
bundle.js.LICENSE.txt

View File

@ -7,11 +7,6 @@ SPDX-License-Identifier: AGPL-3.0-or-later
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Editor</title> <title>Editor</title>
<!-- <script src="//unpkg.com/force-graph"></script> -->
<!-- <script src="https://unpkg.com/force-graph@1.43.0/dist/force-graph.min.js"></script> <- yo -->
<!-- <script src="./force-graph-master/src/force-graph.js"></script> -->
<!--<script src="../../dist/force-graph.js"></script>-->
<!-- <script src="https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.7.31/dist/flexsearch.bundle.js"></script> <- yo -->
<link rel="STYLESHEET" type="text/css" href="./editor.css"> <link rel="STYLESHEET" type="text/css" href="./editor.css">
</head> </head>
@ -289,6 +284,6 @@ SPDX-License-Identifier: AGPL-3.0-or-later
</div> </div>
<!-- <script src="./workflow.ts"></script> --> <!-- <script src="./workflow.ts"></script> -->
<script type="module" src="editor.js"></script> <script type="module" src="bundle.js"></script>
<!-- <script src="./keyboard.ts"></script> --> <!-- <script src="./keyboard.ts"></script> -->
</body> </body>

View File

@ -3,11 +3,8 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
import * as WF from './workflow.js'; import * as WF from './workflow.js';
import './forcegraph.js'; import ForceGraph, { LinkObject, NodeObject } from 'force-graph';
import 'https://unpkg.com/force-graph@1.43.0/dist/force-graph.min.js'; import { Index, IndexSearchResult } from 'flexsearch';
//@ts-ignore
import Index from './node_modules/flexsearch/src/index.js'; // 'https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.7.31/dist/flexsearch.bundle.js'
//Theme //Theme
var darkMode = false; var darkMode = false;
@ -205,7 +202,7 @@ export function search(text: string) {
}); });
} }
function format(possibleTargets: (WF.WFEdge | WF.WFNode)[], results: string[], heading: string) { function format(possibleTargets: (WF.WFEdge | WF.WFNode)[], results: IndexSearchResult, heading: string) {
var h = document.createElement('h3'); var h = document.createElement('h3');
h.innerHTML = heading; h.innerHTML = heading;
searchResultList.appendChild(h); searchResultList.appendChild(h);
@ -319,7 +316,6 @@ var workflow : WF.Workflow = new WF.Workflow({
states : [], states : [],
actions : [] actions : []
}); });
//@ts-ignore
const wfGraph = ForceGraph(); const wfGraph = ForceGraph();
function defineOnClick(item: HTMLElement, url: string, title: string) { function defineOnClick(item: HTMLElement, url: string, title: string) {
@ -370,7 +366,7 @@ fetch('http://localhost:8080/spaß/index.json')
states : data.states, states : data.states,
actions : data.actions actions : data.actions
}); });
wfGraph(document.getElementById('graph')).graphData({nodes: workflow.states, links: workflow.actions}); wfGraph(<HTMLElement>document.getElementById('graph')).graphData({nodes: workflow.states, links: workflow.actions});
Array.from(document.getElementsByClassName('graph-tooltip')).forEach(tooltip => Array.from(document.getElementsByClassName('graph-tooltip')).forEach(tooltip =>
tooltip.classList.add('menu-lightmode') tooltip.classList.add('menu-lightmode')
); );
@ -976,11 +972,11 @@ function isHighlightedViewerEdge(edge: WF.WFEdge) {
return data.viewerNames.includes(selectedViewer.value); return data.viewerNames.includes(selectedViewer.value);
} }
function getEdgeColour(edge: WF.WFEdge) { function getEdgeColour(edge: LinkObject) {
var isSelected = selection === edge || rightSelection === edge; var isSelected = selection === edge || rightSelection === edge;
if (isHighlightedActorEdge(edge)) { if (isHighlightedActorEdge(edge as WF.WFEdge)) {
return isSelected ? edgeColourHighlightSelected : edgeColourHighlightDefault; return isSelected ? edgeColourHighlightSelected : edgeColourHighlightDefault;
} else if (selectedViewer.value !== NO_VIEWER && !isHighlightedViewerEdge(edge)) { } else if (selectedViewer.value !== NO_VIEWER && !isHighlightedViewerEdge(edge as WF.WFEdge)) {
return isSelected ? edgeColourSubtleDefault : edgeColourMostSubtle; return isSelected ? edgeColourSubtleDefault : edgeColourMostSubtle;
} else if (selectedActor.value !== NO_ACTOR) { } else if (selectedActor.value !== NO_ACTOR) {
return isSelected ? (darkMode ? edgeColourSubtleSelectedDarkMode : edgeColourSubtleSelected) return isSelected ? (darkMode ? edgeColourSubtleSelectedDarkMode : edgeColourSubtleSelected)
@ -996,13 +992,14 @@ function getEdgeColour(edge: WF.WFEdge) {
.linkColor(getEdgeColour) .linkColor(getEdgeColour)
.linkCurvature('curvature') .linkCurvature('curvature')
.linkCanvasObjectMode(() => 'after') .linkCanvasObjectMode(() => 'after')
.linkCanvasObject((edge: WF.WFEdge, context: CanvasRenderingContext2D) => { .linkCanvasObject((edge: LinkObject, context: CanvasRenderingContext2D) => {
const wfEdge = edge as WF.WFEdge;
const MAX_FONT_SIZE = 4; const MAX_FONT_SIZE = 4;
const LABEL_NODE_MARGIN = wfGraph.nodeRelSize() * edge.source.val * 1.5; const LABEL_NODE_MARGIN = wfGraph.nodeRelSize() * wfEdge.source.val * 1.5;
const source = edge.source; const source = wfEdge.source;
const target = edge.target; const target = wfEdge.target;
const curvature = edge.curvature; const curvature = wfEdge.curvature;
var textPos = (source === target) var textPos = (source === target)
? {x: source.x, y: source.y} ? {x: source.x, y: source.y}
@ -1026,11 +1023,11 @@ function getEdgeColour(edge: WF.WFEdge) {
source.y > target.y && fromSource.x > source.x)); source.y > target.y && fromSource.x > source.x));
var offset = 0.5 * evLength * (isClockwise ? -curvature : curvature); var offset = 0.5 * evLength * (isClockwise ? -curvature : curvature);
textPos = {x: textPos.x + perpendicular.x * offset, y: textPos.y + perpendicular.y * offset}; textPos = {x: textPos.x + perpendicular.x * offset, y: textPos.y + perpendicular.y * offset};
} else if (edge.__controlPoints) { // Position label relative to the Bezier control points of the self loop } else if (wfEdge.__controlPoints) { // Position label relative to the Bezier control points of the self loop
edgeVector.x = edge.__controlPoints[2] - edge.__controlPoints[0]; edgeVector.x = wfEdge.__controlPoints[2] - wfEdge.__controlPoints[0];
edgeVector.y = edge.__controlPoints[3] - edge.__controlPoints[1]; edgeVector.y = wfEdge.__controlPoints[3] - wfEdge.__controlPoints[1];
var ctrlCenter = {x: edge.__controlPoints[0] + (edge.__controlPoints[2] - edge.__controlPoints[0]) / 2, var ctrlCenter = {x: wfEdge.__controlPoints[0] + (wfEdge.__controlPoints[2] - wfEdge.__controlPoints[0]) / 2,
y: edge.__controlPoints[1] + (edge.__controlPoints[3] - edge.__controlPoints[1]) / 2}; y: wfEdge.__controlPoints[1] + (wfEdge.__controlPoints[3] - wfEdge.__controlPoints[1]) / 2};
var fromSource = {x: ctrlCenter.x - source.x, y: ctrlCenter.y - source.y}; var fromSource = {x: ctrlCenter.x - source.x, y: ctrlCenter.y - source.y};
var fromSrcLen = Math.sqrt(Math.pow(fromSource.x, 2) + Math.pow(fromSource.y, 2)); var fromSrcLen = Math.sqrt(Math.pow(fromSource.x, 2) + Math.pow(fromSource.y, 2));
fromSource.x /= fromSrcLen; fromSource.x /= fromSrcLen;
@ -1047,7 +1044,7 @@ function getEdgeColour(edge: WF.WFEdge) {
if (textAngle > Math.PI / 2) textAngle = -(Math.PI - textAngle); if (textAngle > Math.PI / 2) textAngle = -(Math.PI - textAngle);
if (textAngle < -Math.PI / 2) textAngle = -(-Math.PI - textAngle); if (textAngle < -Math.PI / 2) textAngle = -(-Math.PI - textAngle);
var label = edge.name; var label = wfEdge.name;
// estimate fontSize to fit in link length // estimate fontSize to fit in link length
//context.font = '1px Sans-Serif'; //context.font = '1px Sans-Serif';
@ -1058,7 +1055,7 @@ function getEdgeColour(edge: WF.WFEdge) {
if (textLen > maxTextLength) { if (textLen > maxTextLength) {
var allowedLen = maxTextLength * (label.length / textLen); var allowedLen = maxTextLength * (label.length / textLen);
label = label.substring(0, allowedLen); label = label.substring(0, allowedLen);
if (label !== edge.name) label += '...'; if (label !== wfEdge.name) label += '...';
textLen = context.measureText(label).width; textLen = context.measureText(label).width;
} }
@ -1078,15 +1075,16 @@ function getEdgeColour(edge: WF.WFEdge) {
context.fillText(label, 0, 0); context.fillText(label, 0, 0);
context.restore(); context.restore();
}) })
.linkLineDash((edge: WF.WFEdge) => edge.actionData.mode == 'automatic' && [2, 3]) //[dash, gap] .linkLineDash((edge: LinkObject) => (edge as WF.WFEdge).actionData.mode == 'automatic' ? [2, 3] : null) //[dash, gap]
.linkWidth((edge: WF.WFEdge) => ((edge === selection || edge === rightSelection) ? 2 : 0) + ((isHighlightedActorEdge(edge) || isHighlightedViewerEdge(edge)) ? 4 : 1)) .linkWidth((edge: LinkObject) => ((edge === selection || edge === rightSelection) ? 2 : 0) + ((isHighlightedActorEdge(edge as WF.WFEdge) || isHighlightedViewerEdge(edge as WF.WFEdge)) ? 4 : 1))
.linkDirectionalParticles(2) .linkDirectionalParticles(2)
.linkDirectionalParticleColor(() => darkMode ? '#ffffff55' : '#00000055') .linkDirectionalParticleColor(() => darkMode ? '#ffffff55' : '#00000055')
.linkDirectionalParticleWidth((edge: WF.WFEdge) => (isHighlightedActorEdge(edge)) ? 3 : 0) .linkDirectionalParticleWidth((edge: LinkObject) => (isHighlightedActorEdge(edge as WF.WFEdge)) ? 3 : 0)
.nodeCanvasObject((node: WF.WFNode, ctx: CanvasRenderingContext2D) => { .nodeCanvasObject((node: NodeObject, ctx: CanvasRenderingContext2D) => {
ctx.fillStyle = getNodeColour(node); const wfNode = node as WF.WFNode;
ctx.fillStyle = getNodeColour(wfNode);
ctx.beginPath(); ctx.beginPath();
ctx.arc(node.x, node.y, 2*node.val, 0, 2 * Math.PI, false); ctx.arc(wfNode.x, wfNode.y, 2*wfNode.val, 0, 2 * Math.PI, false);
ctx.fill(); ctx.fill();
if (node === selection || node === rightSelection) { if (node === selection || node === rightSelection) {
ctx.strokeStyle = darkMode ? 'white' : 'black'; ctx.strokeStyle = darkMode ? 'white' : 'black';
@ -1096,44 +1094,47 @@ function getEdgeColour(edge: WF.WFEdge) {
ctx.stroke(); ctx.stroke();
} }
if (! (node.stateData && node.stateData.abbreviation)) return; if (! (wfNode.stateData && wfNode.stateData.abbreviation)) return;
ctx.fillStyle = 'white'; ctx.fillStyle = 'white';
ctx.font = '4px Inter'; ctx.font = '4px Inter';
ctx.textAlign = 'center'; ctx.textAlign = 'center';
ctx.textBaseline = 'middle'; ctx.textBaseline = 'middle';
ctx.fillText(node.stateData.abbreviation, node.x, node.y); ctx.fillText(wfNode.stateData.abbreviation, wfNode.x, wfNode.y);
}) })
.onNodeDragEnd((node: WF.WFNode) => { .onNodeDragEnd((node: NodeObject) => {
node.fx = node.x; node.fx = node.x;
node.fy = node.y; node.fy = node.y;
}) })
.onNodeClick((node: WF.WFNode, _: Event) => { .onNodeClick((node: NodeObject, _: MouseEvent) => {
const wfNode = node as WF.WFNode;
if (edgeFrom) { if (edgeFrom) {
connect(edgeFrom, node); connect(edgeFrom, wfNode);
edgeFrom = null; edgeFrom = null;
} else if (edgeTo) { } else if (edgeTo) {
connect(node, edgeTo); connect(wfNode, edgeTo);
edgeTo = null; edgeTo = null;
} else select(node); } else select(wfNode);
closeMenuItem(); closeMenuItem();
}) })
.onNodeRightClick((node: WF.WFNode, event: { layerX: number, layerY: number }) => { .onNodeRightClick((node: NodeObject, event: MouseEvent) => {
//@ts-ignore TODO replace layerX/layerY
openContextMenu(event.layerX, event.layerY, contextMenuSt); openContextMenu(event.layerX, event.layerY, contextMenuSt);
closeContextMenus(contextMenuBg, contextMenuEd); closeContextMenus(contextMenuBg, contextMenuEd);
// contextMenuBg.style.display = contextMenuEd.style.display = 'none'; // contextMenuBg.style.display = contextMenuEd.style.display = 'none';
rightSelection = node; rightSelection = node as WF.WFNode;
closeMenuItem(); closeMenuItem();
}) })
.onLinkClick((edge: WF.WFEdge, _: Event) => { .onLinkClick((edge: LinkObject, _: MouseEvent) => {
select(edge); select(edge as WF.WFEdge);
closeMenuItem(); closeMenuItem();
}) })
.onLinkRightClick((edge: WF.WFEdge, event: { layerX: number, layerY: number }) => { .onLinkRightClick((edge: LinkObject, event: MouseEvent) => {
//@ts-ignore TODO replace layerX/layerY
openContextMenu(event.layerX, event.layerY, contextMenuEd); openContextMenu(event.layerX, event.layerY, contextMenuEd);
closeContextMenus(contextMenuBg, contextMenuSt); closeContextMenus(contextMenuBg, contextMenuSt);
// contextMenuBg.style.display = contextMenuSt.style.display = 'none'; // contextMenuBg.style.display = contextMenuSt.style.display = 'none';
rightSelection = edge; rightSelection = edge as WF.WFEdge;
closeMenuItem() closeMenuItem()
}) })
.onBackgroundClick((_: Event) => { .onBackgroundClick((_: Event) => {
@ -1142,8 +1143,10 @@ function getEdgeColour(edge: WF.WFEdge) {
edgeFrom = edgeTo = rightSelection = null; edgeFrom = edgeTo = rightSelection = null;
closeMenuItem(); closeMenuItem();
}) })
.onBackgroundRightClick((event: { layerX: number, layerY: number }) => { .onBackgroundRightClick((event: MouseEvent) => {
//@ts-ignore TODO replace layerX/layerY
newStateCoords = wfGraph.screen2GraphCoords(event.layerX, event.layerY); newStateCoords = wfGraph.screen2GraphCoords(event.layerX, event.layerY);
//@ts-ignore TODO replace layerX/layerY
openContextMenu(event.layerX, event.layerY, contextMenuBg); openContextMenu(event.layerX, event.layerY, contextMenuBg);
closeContextMenus(contextMenuEd, contextMenuSt); closeContextMenus(contextMenuEd, contextMenuSt);
// contextMenuEd.style.display = contextMenuSt.style.display = 'none'; // contextMenuEd.style.display = contextMenuSt.style.display = 'none';

View File

@ -1,5 +0,0 @@
// SPDX-FileCopyrightText: 2023 David Mosbach <david.mosbach@campus.lmu.de>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare module 'https://*';

View File

@ -1,5 +0,0 @@
// SPDX-FileCopyrightText: 2023 David Mosbach <david.mosbach@campus.lmu.de>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
"use strict";

1659
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
"description": "Visualiser for Uni2work workflows", "description": "Visualiser for Uni2work workflows",
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "./start.sh", "start": "bash ./start.sh",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"repository": { "repository": {
@ -13,9 +13,18 @@
}, },
"author": "David Mosbach", "author": "David Mosbach",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"private": true,
"dependencies": { "dependencies": {
"commonjs": "^0.0.1", "commonjs": "^0.0.1",
"flexsearch": "^0.7.31", "flexsearch": "0.7.11",
"force-graph": "^1.43.3"
},
"devDependencies": {
"@types/flexsearch": "^0.7.3",
"@types/node": "^20.5.3",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4",
"ts-loader": "^9.4.4",
"typescript": "^5.1.6" "typescript": "^5.1.6"
} }
} }

View File

@ -5,7 +5,9 @@
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
echo 'transpiling to JS...' echo 'Transpiling to JS...'
npx tsc npx tsc
echo 'starting server...' echo 'Generating Webpack bundle...'
npx webpack
echo 'Starting server...'
npx http-server --cors -o ./editor.html npx http-server --cors -o ./editor.html

View File

@ -27,7 +27,7 @@
/* Modules */ /* Modules */
"module": "es6", /* Specify what module code is generated. */ "module": "es6", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */ // "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ "moduleResolution": "nodenext", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */

21
webpack.config.cjs Normal file
View File

@ -0,0 +1,21 @@
const path = require('path');
module.exports = {
entry: './editor.ts',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, '.')
},
};

View File

@ -2,6 +2,8 @@
// //
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
import { LinkObject, NodeObject } from "force-graph";
export type WF = { export type WF = {
states : NodeFormat[], states : NodeFormat[],
actions : EdgeFormat[] actions : EdgeFormat[]
@ -111,7 +113,7 @@ export class Workflow {
} }
} }
export class WFNode { export class WFNode implements NodeObject {
stateData: StateData; stateData: StateData;
x: number; x: number;
@ -153,7 +155,7 @@ export class StateData {
} }
} }
export class WFEdge { export class WFEdge implements LinkObject {
actionData: ActionData; actionData: ActionData;
source: WFNode; source: WFNode;