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ß
/node_modules
*.js
bundle.js.LICENSE.txt

View File

@ -7,11 +7,6 @@ SPDX-License-Identifier: AGPL-3.0-or-later
<head>
<meta charset="utf-8">
<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">
</head>
@ -289,6 +284,6 @@ SPDX-License-Identifier: AGPL-3.0-or-later
</div>
<!-- <script src="./workflow.ts"></script> -->
<script type="module" src="editor.js"></script>
<script type="module" src="bundle.js"></script>
<!-- <script src="./keyboard.ts"></script> -->
</body>

View File

@ -3,11 +3,8 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import * as WF from './workflow.js';
import './forcegraph.js';
import 'https://unpkg.com/force-graph@1.43.0/dist/force-graph.min.js';
//@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'
import ForceGraph, { LinkObject, NodeObject } from 'force-graph';
import { Index, IndexSearchResult } from 'flexsearch';
//Theme
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');
h.innerHTML = heading;
searchResultList.appendChild(h);
@ -319,7 +316,6 @@ var workflow : WF.Workflow = new WF.Workflow({
states : [],
actions : []
});
//@ts-ignore
const wfGraph = ForceGraph();
function defineOnClick(item: HTMLElement, url: string, title: string) {
@ -370,7 +366,7 @@ fetch('http://localhost:8080/spaß/index.json')
states : data.states,
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 =>
tooltip.classList.add('menu-lightmode')
);
@ -779,7 +775,7 @@ function prepareWorkflow() {
selectedViewer.value = NO_VIEWER;
//Create search index
workflow.states.forEach(state =>
workflow.states.forEach(state =>
nodeIndex.add(state.id, state.name)
);
workflow.actions.forEach(action =>
@ -976,11 +972,11 @@ function isHighlightedViewerEdge(edge: WF.WFEdge) {
return data.viewerNames.includes(selectedViewer.value);
}
function getEdgeColour(edge: WF.WFEdge) {
function getEdgeColour(edge: LinkObject) {
var isSelected = selection === edge || rightSelection === edge;
if (isHighlightedActorEdge(edge)) {
if (isHighlightedActorEdge(edge as WF.WFEdge)) {
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;
} else if (selectedActor.value !== NO_ACTOR) {
return isSelected ? (darkMode ? edgeColourSubtleSelectedDarkMode : edgeColourSubtleSelected)
@ -996,13 +992,14 @@ function getEdgeColour(edge: WF.WFEdge) {
.linkColor(getEdgeColour)
.linkCurvature('curvature')
.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 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 target = edge.target;
const curvature = edge.curvature;
const source = wfEdge.source;
const target = wfEdge.target;
const curvature = wfEdge.curvature;
var textPos = (source === target)
? {x: source.x, y: source.y}
@ -1026,11 +1023,11 @@ function getEdgeColour(edge: WF.WFEdge) {
source.y > target.y && fromSource.x > source.x));
var offset = 0.5 * evLength * (isClockwise ? -curvature : curvature);
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
edgeVector.x = edge.__controlPoints[2] - edge.__controlPoints[0];
edgeVector.y = edge.__controlPoints[3] - edge.__controlPoints[1];
var ctrlCenter = {x: edge.__controlPoints[0] + (edge.__controlPoints[2] - edge.__controlPoints[0]) / 2,
y: edge.__controlPoints[1] + (edge.__controlPoints[3] - edge.__controlPoints[1]) / 2};
} else if (wfEdge.__controlPoints) { // Position label relative to the Bezier control points of the self loop
edgeVector.x = wfEdge.__controlPoints[2] - wfEdge.__controlPoints[0];
edgeVector.y = wfEdge.__controlPoints[3] - wfEdge.__controlPoints[1];
var ctrlCenter = {x: wfEdge.__controlPoints[0] + (wfEdge.__controlPoints[2] - wfEdge.__controlPoints[0]) / 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 fromSrcLen = Math.sqrt(Math.pow(fromSource.x, 2) + Math.pow(fromSource.y, 2));
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);
var label = edge.name;
var label = wfEdge.name;
// estimate fontSize to fit in link length
//context.font = '1px Sans-Serif';
@ -1058,7 +1055,7 @@ function getEdgeColour(edge: WF.WFEdge) {
if (textLen > maxTextLength) {
var allowedLen = maxTextLength * (label.length / textLen);
label = label.substring(0, allowedLen);
if (label !== edge.name) label += '...';
if (label !== wfEdge.name) label += '...';
textLen = context.measureText(label).width;
}
@ -1078,15 +1075,16 @@ function getEdgeColour(edge: WF.WFEdge) {
context.fillText(label, 0, 0);
context.restore();
})
.linkLineDash((edge: WF.WFEdge) => edge.actionData.mode == 'automatic' && [2, 3]) //[dash, gap]
.linkWidth((edge: WF.WFEdge) => ((edge === selection || edge === rightSelection) ? 2 : 0) + ((isHighlightedActorEdge(edge) || isHighlightedViewerEdge(edge)) ? 4 : 1))
.linkLineDash((edge: LinkObject) => (edge as WF.WFEdge).actionData.mode == 'automatic' ? [2, 3] : null) //[dash, gap]
.linkWidth((edge: LinkObject) => ((edge === selection || edge === rightSelection) ? 2 : 0) + ((isHighlightedActorEdge(edge as WF.WFEdge) || isHighlightedViewerEdge(edge as WF.WFEdge)) ? 4 : 1))
.linkDirectionalParticles(2)
.linkDirectionalParticleColor(() => darkMode ? '#ffffff55' : '#00000055')
.linkDirectionalParticleWidth((edge: WF.WFEdge) => (isHighlightedActorEdge(edge)) ? 3 : 0)
.nodeCanvasObject((node: WF.WFNode, ctx: CanvasRenderingContext2D) => {
ctx.fillStyle = getNodeColour(node);
.linkDirectionalParticleWidth((edge: LinkObject) => (isHighlightedActorEdge(edge as WF.WFEdge)) ? 3 : 0)
.nodeCanvasObject((node: NodeObject, ctx: CanvasRenderingContext2D) => {
const wfNode = node as WF.WFNode;
ctx.fillStyle = getNodeColour(wfNode);
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();
if (node === selection || node === rightSelection) {
ctx.strokeStyle = darkMode ? 'white' : 'black';
@ -1096,44 +1094,47 @@ function getEdgeColour(edge: WF.WFEdge) {
ctx.stroke();
}
if (! (node.stateData && node.stateData.abbreviation)) return;
if (! (wfNode.stateData && wfNode.stateData.abbreviation)) return;
ctx.fillStyle = 'white';
ctx.font = '4px Inter';
ctx.textAlign = 'center';
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.fy = node.y;
})
.onNodeClick((node: WF.WFNode, _: Event) => {
.onNodeClick((node: NodeObject, _: MouseEvent) => {
const wfNode = node as WF.WFNode;
if (edgeFrom) {
connect(edgeFrom, node);
connect(edgeFrom, wfNode);
edgeFrom = null;
} else if (edgeTo) {
connect(node, edgeTo);
connect(wfNode, edgeTo);
edgeTo = null;
} else select(node);
} else select(wfNode);
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);
closeContextMenus(contextMenuBg, contextMenuEd);
// contextMenuBg.style.display = contextMenuEd.style.display = 'none';
rightSelection = node;
rightSelection = node as WF.WFNode;
closeMenuItem();
})
.onLinkClick((edge: WF.WFEdge, _: Event) => {
select(edge);
.onLinkClick((edge: LinkObject, _: MouseEvent) => {
select(edge as WF.WFEdge);
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);
closeContextMenus(contextMenuBg, contextMenuSt);
// contextMenuBg.style.display = contextMenuSt.style.display = 'none';
rightSelection = edge;
rightSelection = edge as WF.WFEdge;
closeMenuItem()
})
.onBackgroundClick((_: Event) => {
@ -1142,8 +1143,10 @@ function getEdgeColour(edge: WF.WFEdge) {
edgeFrom = edgeTo = rightSelection = null;
closeMenuItem();
})
.onBackgroundRightClick((event: { layerX: number, layerY: number }) => {
.onBackgroundRightClick((event: MouseEvent) => {
//@ts-ignore TODO replace layerX/layerY
newStateCoords = wfGraph.screen2GraphCoords(event.layerX, event.layerY);
//@ts-ignore TODO replace layerX/layerY
openContextMenu(event.layerX, event.layerY, contextMenuBg);
closeContextMenus(contextMenuEd, contextMenuSt);
// 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",
"type": "module",
"scripts": {
"start": "./start.sh",
"start": "bash ./start.sh",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
@ -13,9 +13,18 @@
},
"author": "David Mosbach",
"license": "AGPL-3.0-or-later",
"private": true,
"dependencies": {
"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"
}
}

View File

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

View File

@ -27,7 +27,7 @@
/* Modules */
"module": "es6", /* Specify what module code is generated. */
// "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. */
// "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. */

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
import { LinkObject, NodeObject } from "force-graph";
export type WF = {
states : NodeFormat[],
actions : EdgeFormat[]
@ -111,7 +113,7 @@ export class Workflow {
}
}
export class WFNode {
export class WFNode implements NodeObject {
stateData: StateData;
x: number;
@ -153,7 +155,7 @@ export class StateData {
}
}
export class WFEdge {
export class WFEdge implements LinkObject {
actionData: ActionData;
source: WFNode;