Merge branch 'typescript' into 'main'

Typescript

See merge request uni2work/workflows/workflow-visualiser!2
This commit is contained in:
David Mosbach 2023-08-24 03:07:49 +00:00
commit 7a2777df96
15 changed files with 3646 additions and 511 deletions

5
.gitignore vendored
View File

@ -7,4 +7,7 @@ CHANGELOG.md
test.json
server.py
/workflows
/spaß
/spaß
/node_modules
*.js
bundle.js.LICENSE.txt

View File

@ -7,30 +7,25 @@ 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>
<!-- <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>
<link rel="STYLESHEET" type="text/css" href="./editor.css">
</head>
<body>
<div id="editor">
<div id="curtain" onclick="closeFileDisplay()"></div> <!--Backdrop behind the file menu-->
<div id="curtain"></div> <!--Backdrop behind the file menu-->
<div id="submenu-backdrop"></div>
<div id="mainmenu" class="menu-lightmode"> <!--Horizontal main menu-->
<div class="menuitem" onclick="openFileMenu(this)">
<div id="file-menu-btn" class="menuitem">
<div class="menubutton">File</div>
<div id="filemenu" class="submenu" onclick="event.stopPropagation()">
<div class="menutop menu-lightmode">New Workflow</div>
<div class="menucenter menu-lightmode" onclick="openFileDisplay()">Open</div>
<div id="open-file" class="menucenter menu-lightmode">Open</div>
<div class="menucenter menu-lightmode">Save</div>
<div class="menucenter menu-lightmode">Save As</div>
<div class="menubottom menu-lightmode">Export</div>
</div>
</div>
<div class="menuitem" onclick="openEditMenu(this)">
<div id="edit-menu-btn" class="menuitem">
<div class="menubutton">Edit</div>
<div id="editmenu" class="submenu" onclick="event.stopPropagation()">
<div class="menutop menu-lightmode">Undo</div>
@ -39,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
<div class="menubottom menu-lightmode">Run Linter</div>
</div>
</div>
<div class="menuitem" onclick="openViewMenu(this)">
<div id="view-menu-btn" class="menuitem">
<div class="menubutton">View</div>
<div class="submenu menu-lightmode" onclick="event.stopPropagation()">
<table id="view-table">
@ -48,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
<label for="actor">Highlight actor:</label>
</td>
<td>
<select name="actor" id="actor" onchange="selectActor();"></select>
<select name="actor" id="actor"></select>
</td>
</tr>
<tr>
@ -56,13 +51,13 @@ SPDX-License-Identifier: AGPL-3.0-or-later
<label for="viewer">Highlight viewer:</label>
</td>
<td>
<select name="viewer" id="viewer" onchange="selectViewer();"></select>
<select name="viewer" id="viewer"></select>
</td>
</tr>
</table>
</div>
</div>
<div class="menuitem" onclick="openSettingsMenu(this)">
<div id="settings-menu-btn" class="menuitem">
<div class="menubutton">Settings</div> <!--Main settings menu-->
<div class="submenu menu-lightmode" onclick="event.stopPropagation()">
<table id="settings-table">
@ -75,7 +70,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
<td>
<label class="toggle" title="Dark Mode">
<input type="checkbox">
<span class="toggle-slider" onclick="toggleTheme()"></span>
<span id="theme-toggle" class="toggle-slider"></span>
</label>
</td>
<td>
@ -127,14 +122,14 @@ SPDX-License-Identifier: AGPL-3.0-or-later
</div>
</div>
</div>
<div class="menuitem" onclick="openAboutMenu(this)">
<div id="about-menu-btn" class="menuitem">
<div class="menubutton">About</div>
<div class="submenu menu-lightmode">Visualiser & editor for Uni2work workflows</div>
</div>
<div class="menuitem" onclick="openSearchMenu(this)">
<div id="search-menu-btn" class="menuitem">
<div id="search-container">
<input id="search-input" type="text" placeholder="Search" onclick="showSearchResults()" oninput="search(this.value)">
<span class="search-button" onclick="searchInput.focus()">
<input id="search-input" type="text" placeholder="Search">
<span class="search-button">
<svg id="search-icon" class="search-icon-lightmode" height="18" width="18" xmlns="http://www.w3.org/2000/svg">
<circle cx="11" cy="7" r="6" stroke-width="2" fill="none" />
<polyline points="6,12 2,16" style="fill:none;stroke-width:2" stroke-linecap="round" />
@ -182,7 +177,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
Node
</div>
<h1 id="sideheading">Hello</h1>
<svg class="close" height="15" width="15" xmlns="http://www.w3.org/2000/svg" onclick="deselect()">
<svg id="close-side-panel" class="close" height="15" width="15" xmlns="http://www.w3.org/2000/svg">
<polyline points="1,1 14,14" style="fill:none;stroke:rgb(120, 120, 120);stroke-width:2" stroke-linecap="round" />
<polyline points="14,1 1,14" style="fill:none;stroke:rgb(120, 120, 120);stroke-width:2" stroke-linecap="round" />
X
@ -190,16 +185,16 @@ SPDX-License-Identifier: AGPL-3.0-or-later
</div>
<div id="sidecontent"></div>
<div id="sidebuttons">
<button type="submit">Apply</button>
<button type="reset" onclick="deselect()">Cancel</button>
<button type="submit" onclick="focusSelection()">Focus</button>
<button type="submit" onclick="removeSelection()" style="position: absolute; right: 0px;">Delete</button>
<button id="side-panel-apply" type="submit">Apply</button>
<button id="side-panel-cancel" type="reset">Cancel</button>
<button id="side-panel-focus" type="submit">Focus</button>
<button id="side-panel-delete" type="submit" style="position: absolute; right: 0px;">Delete</button>
</div>
</div>
<div id="filepanel" class="menu-lightmode"> <!--Pop-up panel covering the center of the screen for file interactions-->
<div id="fileheader">
<h2 id="fileheading">Hello</h2>
<svg class="close" height="15" width="15" xmlns="http://www.w3.org/2000/svg" onclick="closeFileDisplay()">
<svg id="close-file-panel" class="close" height="15" width="15" xmlns="http://www.w3.org/2000/svg">
<polyline points="1,1 14,14" style="fill:none;stroke:rgb(120, 120, 120);stroke-width:2" stroke-linecap="round" />
<polyline points="14,1 1,14" style="fill:none;stroke:rgb(120, 120, 120);stroke-width:2" stroke-linecap="round" />
X
@ -208,11 +203,11 @@ SPDX-License-Identifier: AGPL-3.0-or-later
<div id="filecontent"></div>
<div id="filebuttons">
<!-- <button type="submit">Load</button> -->
<button type="reset" onclick="closeFileDisplay()">Cancel</button>
<button id="file-panel-cancel" type="reset">Cancel</button>
</div>
</div>
<div id="ctmenubg" class="contextmenu"> <!--Context menu displayed after right-clicking the background-->
<div class="menutop menu-lightmode" onclick="addState()">
<div id="add-state" class="menutop menu-lightmode">
<svg height="10" width="10" xmlns="http://www.w3.org/2000/svg">
<circle cx="5" cy="5" r="4" stroke-width="2" fill="none" />
</svg>
@ -226,19 +221,19 @@ SPDX-License-Identifier: AGPL-3.0-or-later
</div>
</div>
<div id="ctmenust" class="contextmenu"> <!--Context menu displayed after right-clicking a state-->
<div class="menutop menu-lightmode" onclick="markEdgeFrom()">
<div id="edge-from" class="menutop menu-lightmode">
<svg height="10" width="10" xmlns="http://www.w3.org/2000/svg">
<polyline points="1,1 9,9" style="fill:none;stroke-width:2" stroke-linecap="round" />
</svg>
&nbsp;&nbsp;New Edge from here
</div>
<div class="menucenter menu-lightmode" onclick="markEdgeTo()">
<div id="edge-to" class="menucenter menu-lightmode">
<svg height="10" width="10" xmlns="http://www.w3.org/2000/svg">
<polyline points="1,9 9,1" style="fill:none;stroke-width:2" stroke-linecap="round" />
</svg>
&nbsp;&nbsp;New Edge to here
</div>
<div class="menucenter menu-lightmode" onclick="rightSelect()">
<div class="edit-item menucenter menu-lightmode">
<svg height="10" width="10" xmlns="http://www.w3.org/2000/svg">
<polyline points="1,9 9,1" style="fill:none;stroke-width:4" />
<polyline points="1,9 8,2" style="fill:none;stroke-width:2" stroke-linecap="round" />
@ -253,7 +248,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
</svg>
&nbsp;&nbsp;Duplicate
</div>
<div class="menubottom menu-lightmode" onclick="removeRightSelection()">
<div class="delete-item menubottom menu-lightmode">
<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" />
@ -262,7 +257,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
</div>
</div>
<div id="ctmenued" class="contextmenu"> <!--Context menu displayed after right-clicking an edge-->
<div class="menutop menu-lightmode" onclick="rightSelect()">
<div class="edit-item menutop menu-lightmode">
<svg height="10" width="10" xmlns="http://www.w3.org/2000/svg">
<polyline points="1,9 9,1" style="fill:none;stroke-width:4" />
<polyline points="1,9 8,2" style="fill:none;stroke-width:2" stroke-linecap="round" />
@ -277,7 +272,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
</svg>
&nbsp;&nbsp;Duplicate
</div>
<div class="menubottom menu-lightmode" onclick="removeRightSelection()">
<div class="delete-item menubottom menu-lightmode">
<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" />
@ -288,7 +283,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
<div id="graph"></div> <!--The graph canvas-->
</div>
<script src="./workflow.js"></script>
<script src="./editor.js"></script>
<script src="./keyboard.js"></script>
<!-- <script src="./workflow.ts"></script> -->
<script type="module" src="bundle.js"></script>
<!-- <script src="./keyboard.ts"></script> -->
</body>

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
{
description = "Develop environment for the Workflow Visualiser";
description = "Development environment for the Workflow Visualiser";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
@ -43,17 +43,20 @@
devShell = {
enable = true;
mkShellArgs.shellHook = ''
if command -v zsh &> /dev/null; then
zsh && exit
fi
'';
mkShellArgs = {
shellHook = ''
if command -v zsh &> /dev/null; then
zsh && exit
fi
'';
buildInputs = with pkgs; [ nodejs_18 reuse ];
};
# Programs you want to make available in the shell.
# Default programs can be disabled by setting to 'null'
# tools = hp: { fourmolu = hp.fourmolu; ghcid = null; };
hlsCheck.enable = true;
hlsCheck.enable = true;
};
};

View File

@ -1,27 +0,0 @@
// SPDX-FileCopyrightText: 2023 David Mosbach <david.mosbach@campus.lmu.de>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
document.addEventListener('keydown', e => {
console.log(e.ctrlKey, e.key);
if (e.key === 'Escape') {
closeContextMenus(contextMenuEd, contextMenuSt, contextMenuBg);
closeMenuItem();
closeFileDisplay();
deselect();
rightSelection = null;
searchInput.blur();
} else if (!e.ctrlKey) return;
switch (e.key) {
case 'f':
e.preventDefault();
searchInput.focus();
openSearchMenu(searchContainer.parentElement);
break;
case 'o':
e.preventDefault();
openFileDisplay();
default:
break;
}
})

2618
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2023 David Mosbach <david.mosbach@campus.lmu.de>
SPDX-License-Identifier: AGPL-3.0-or-later

30
package.json Normal file
View File

@ -0,0 +1,30 @@
{
"name": "workflow-visualiser",
"version": "1.0.0",
"description": "Visualiser for Uni2work workflows",
"type": "module",
"scripts": {
"start": "bash ./start.sh",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://gitlab.uniworx.de/uni2work/workflows/workflow-visualiser"
},
"author": "David Mosbach",
"license": "AGPL-3.0-or-later",
"private": true,
"dependencies": {
"commonjs": "^0.0.1",
"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"
}
}

3
package.json.license Normal file
View File

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2023 David Mosbach <david.mosbach@campus.lmu.de>
SPDX-License-Identifier: AGPL-3.0-or-later

13
start.sh Executable file
View File

@ -0,0 +1,13 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: 2023 David Mosbach <david.mosbach@campus.lmu.de>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
echo 'Transpiling to JS...'
npx tsc
echo 'Generating Webpack bundle...'
npx webpack
echo 'Starting server...'
npx http-server --cors -o ./editor.html

109
tsconfig.json Normal file
View File

@ -0,0 +1,109 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
//"lib": ["es2022"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "es6", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
"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. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}

3
tsconfig.json.license Normal file
View File

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2023 David Mosbach <david.mosbach@campus.lmu.de>
SPDX-License-Identifier: AGPL-3.0-or-later

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

@ -1,152 +0,0 @@
// SPDX-FileCopyrightText: 2023 David Mosbach <david.mosbach@campus.lmu.de>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
class Role {
constructor(json) {
this.json = json;
if (json.tag == 'payload-reference') {
this.name = json['payload-label'];
} else if (json.authorized) {
this.name = json.authorized['dnf-terms'][0][0].var + ' (auth)';
} else if (json.user) {
this.name = json.user;
} else if (json.tag) {
this.name = json.tag + ' (tag)';
} else {
this.name = JSON.stringify(json);
}
}
format() {
return [document.createTextNode(this.name)];
}
}
class Roles {
constructor(json, roleName) {
this.roleName = roleName
this.anchor = json.anchor && new Anchor(json.anchor)
this[roleName] = [];
for (const role of json[roleName])
this[roleName].push(new Role(role));
this.comment = json.comment;
}
format() {
var r = document.createElement('h4');
var roles = document.createTextNode('Roles');
r.appendChild(roles);
var rolesList = document.createElement('ul');
this[this.roleName].forEach(r => {
var role = document.createElement('li');
role.appendChild(document.createTextNode(r.name));
rolesList.appendChild(role);
});
var result = [];
if (this.comment.length > 0) {
var c = document.createElement('h4');
c.innerText = 'Comment';
var comment = document.createElement('p');
comment.innerText = this.comment.join(' ');
result.push(c, comment);
}
if (this.anchor) {
var a = document.createElement('h4');
a.appendChild(this.anchor.format());
result.push(a);
} else result.push(r)
result.push(rolesList);
return result;
}
}
class Viewers extends Roles {
constructor(json) {
super(json, 'viewers');
}
}
class Actors extends Roles {
constructor(json) {
super(json, 'actors');
}
}
class Anchor {
constructor(json) {
this.name = json.name;
this.type = json.type;
}
format() {
return document.createTextNode(`${this.type == 'alias' ? '*' : '&'}${this.name}`);
}
}
class Message {
constructor(json) {
var content = json.content;
this.fallback = content.fallback;
this.fallbackLang = content['fallback-lang'];
this.translations = content.translations;
this.status = json.status;
this.viewers = new Viewers(json.viewers);
}
format() {
var v = document.createElement('h3');
var viewers = document.createTextNode('Viewers');
v.appendChild(viewers);
var viewerList = this.viewers.format();
var h = document.createElement('h3');
var heading = document.createTextNode('Status');
h.appendChild(heading);
var p = document.createElement('p');
var text = document.createTextNode(this.status);
p.appendChild(text);
var result = [v];
result = result.concat(viewerList);
result.push(h, p);
h = document.createElement('h3');
heading = document.createTextNode(this.fallbackLang);
h.appendChild(heading);
p = document.createElement('html');
p.setAttribute('lang', this.fallbackLang);
p.innerHTML = this.fallback;
result.push(h, p);
for (var t in this.translations) {
h = document.createElement('h3');
heading = document.createTextNode(t);
h.appendChild(heading);
p = document.createElement('html');
p.setAttribute('lang', this.translations[t]);
p.innerHTML = this.translations[t];
result.push(h, p);
}
return result;
}
}
class Payload {
constructor(json) {
this.fields = [];
if (json === null) return;
for (var f in json) {
this.fields.push(f);
}
}
format() {
var fieldList = document.createElement('ul');
this.fields.forEach(f => {
var field = document.createElement('li');
field.appendChild(document.createTextNode(f));
fieldList.appendChild(field);
});
return [fieldList];
}
}

398
workflow.ts Normal file
View File

@ -0,0 +1,398 @@
// SPDX-FileCopyrightText: 2023 David Mosbach <david.mosbach@campus.lmu.de>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
import { LinkObject, NodeObject } from "force-graph";
export type WF = {
states : NodeFormat[],
actions : EdgeFormat[]
}
export type NodeFormat = {
stateData: SDFormat,
x: number,
y: number,
fx: number,
fy: number,
id: string,
name: string,
val: number
}
export type SDFormat = {
abbreviation: string,
final: boolean | string,
messages?: MessageFormat[],
viewers?: RolesFormat,
payload?: string[]
}
export type EdgeFormat = {
actionData: ADFormat,
source: WFNode | string,
target: WFNode | string,
id: string,
name: string,
nodePairId : string
}
export type ActionMode = 'initial' | 'manual' | 'automatic';
export type ADFormat = {
messages?: MessageFormat[],
viewers?: RolesFormat,
actors?: RolesFormat,
['actor Viewers']?: RolesFormat,
mode?: ActionMode,
form?: string[]
}
export type RoleFormat = {
tag : string | null,
authorized : JSON | null,
user : string | null,
'payload-label': string | null
}
export type RoleName = 'actors' | 'viewers';
export type RolesFormat = {
[roleName: string]: any,
anchor: AnchorFormat,
comment: string[]
}
export type AnchorFormat = {
name: string,
type: AnchorType
} | 'NoAnchor' | null //TODO either NoAnchor or null in parser
export type AnchorType = 'anchor' | 'alias' | 'none'
export type MessageFormat = {
content: {
fallback: string,
'fallback-lang': string,
translations: JSON
},
status: string,
viewers: RolesFormat
}
export class Workflow {
states: WFNode[];
actions: WFEdge[];
constructor(json: WF) {
const stateMap: Map<string, WFNode> = new Map();
this.states = [];
for (const state of json.states) {
var node = new WFNode(state);
this.states.push(node);
stateMap.set(node.id, node);
}
// Resolve ID refs to nodes
var actions = json.actions.map(action => {
function transformRef(ref: string | WFNode) {
if (ref instanceof String)
stateMap.has(<string>ref)
&& (ref = <WFNode>stateMap.get(<string>ref))
|| console.error('Ref to unknown state: ' + ref);
return ref;
}
action.source = transformRef(action.source);
action.target = transformRef(action.target);
return action;
});
this.actions = [];
for (const action of actions)
this.actions.push(new WFEdge(action));
}
}
export class WFNode implements NodeObject {
stateData: StateData;
x: number;
y: number;
fx: number;
fy: number;
id: string;
name: string;
val: number;
constructor(json: NodeFormat) {
this.stateData = new StateData(json.stateData);
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;
}
}
export class StateData {
abbreviation: string;
messages: Message[];
viewers: Viewers;
payload: Payload;
viewerNames: string[];
final: boolean | string;
constructor(json: SDFormat) {
this.abbreviation = json.abbreviation;
this.messages = json.messages ? json.messages.map(message => { return new Message(message) }) : [];
this.viewers = json.viewers ? new Viewers(json.viewers) : Viewers.empty();
this.payload = new Payload(json.payload);
this.viewerNames = [];
this.final = json.final;
}
}
export class WFEdge implements LinkObject {
actionData: ActionData;
source: WFNode;
target: WFNode;
id: string;
name: string;
nodePairId : string;
curvature: number;
__controlPoints?: any;
constructor(json: EdgeFormat) {
this.actionData = new ActionData(json.actionData);
this.source = <WFNode>json.source;
this.target = <WFNode>json.target;
this.id = json.id;
this.name = json.name;
this.nodePairId = json.nodePairId;
this.curvature = 0;
}
}
export class ActionData {
messages: Message[];
viewers: Viewers;
actors: Actors;
'actor Viewers': Viewers;
form: Payload;
viewerNames: string[];
actorNames: string[];
mode: ActionMode | undefined;
constructor(json: ADFormat) {
this.messages = json.messages ? json.messages.map(message => { return new Message(message) }) : [];
this.viewers = json.viewers ? new Viewers(json.viewers) : Viewers.empty();
this.actors = json.actors ? new Actors(json.actors) : Actors.empty();
this['actor Viewers'] = json['actor Viewers'] ? new Viewers(json['actor Viewers']) : Viewers.empty();
this.form = new Payload(json.form);
this.viewerNames = [];
this.actorNames = [];
this.mode = json.mode ?? undefined;
}
}
export class Role {
json: RoleFormat;
name: string;
constructor(json: RoleFormat) {
this.json = json;
if (json.tag == 'payload-reference') {
this.name = <string>json['payload-label' as keyof RoleFormat];
} else if (json.authorized) {
this.name = (<any[]><unknown>json.authorized['dnf-terms' as keyof JSON])[0][0].var + ' (auth)'; //TODO ugly
} else if (json.user) {
this.name = json.user;
} else if (json.tag) {
this.name = json.tag + ' (tag)';
} else {
this.name = JSON.stringify(json);
}
}
format() {
return [document.createTextNode(this.name)];
}
}
export class Roles {
roleName: RoleName;
anchor: Anchor;
comment: string[];
roles: Role[];
constructor(json: RolesFormat, roleName: RoleName) {
this.roleName = roleName
this.anchor = json.anchor ? new Anchor(json.anchor) : new Anchor('NoAnchor');
this.roles = [];
for (const role of json[roleName as keyof RolesFormat])
this.roles.push(new Role(role));
this.comment = json.comment;
}
length() {
return this.roles.length;
}
format() {
var r = document.createElement('h4');
var roles = document.createTextNode('Roles');
r.appendChild(roles);
var rolesList = document.createElement('ul');
this.roles.forEach(r => {
var role = document.createElement('li');
role.appendChild(document.createTextNode(r.name));
rolesList.appendChild(role);
});
var result: HTMLElement[] = [];
if (this.comment.length > 0) {
var c = document.createElement('h4');
c.innerText = 'Comment';
var comment = document.createElement('p');
comment.innerText = this.comment.join(' ');
result.push(c, comment);
}
if (this.anchor) {
var a = document.createElement('h4');
a.appendChild(this.anchor.format());
result.push(a);
} else result.push(r)
result.push(rolesList);
return result;
}
}
export class Viewers extends Roles {
static empty() {
return new Viewers({
viewers: [],
anchor: 'NoAnchor',
comment: []
})
}
constructor(json: RolesFormat) {
super(json, 'viewers');
}
}
export class Actors extends Roles {
static empty() {
return new Actors({
actors: [],
anchor: 'NoAnchor',
comment: []
})
}
constructor(json: RolesFormat) {
super(json, 'actors');
}
}
export class Anchor {
name: string | undefined;
type: AnchorType;
constructor(json: AnchorFormat) {
if (!json || json === 'NoAnchor') {
this.name = undefined;
this.type = 'none';
} else {
this.name = json.name;
this.type = json.type;
}
}
format() {
return document.createTextNode(`${this.type == 'alias' ? '*' : '&'}${this.name}`);
}
}
export class Message {
fallback: string;
fallbackLang: string;
translations: JSON;
status: string;
viewers: Viewers;
constructor(json: MessageFormat) {
var content = json.content;
this.fallback = content.fallback;
this.fallbackLang = content['fallback-lang'];
this.translations = content.translations;
this.status = json.status;
this.viewers = new Viewers(json.viewers);
}
format() {
var v = document.createElement('h3');
var viewers = document.createTextNode('Viewers');
v.appendChild(viewers);
var viewerList = this.viewers.format();
var h = document.createElement('h3');
var heading = document.createTextNode('Status');
h.appendChild(heading);
var p: HTMLElement = document.createElement('p');
var text = document.createTextNode(this.status);
p.appendChild(text);
var result: HTMLElement[] = [v];
result = result.concat(viewerList);
result.push(h, p);
h = document.createElement('h3');
heading = document.createTextNode(this.fallbackLang);
h.appendChild(heading);
p = document.createElement('html');
p.setAttribute('lang', this.fallbackLang);
p.innerHTML = this.fallback;
result.push(h, p);
for (var t in this.translations) {
h = document.createElement('h3');
heading = document.createTextNode(t);
h.appendChild(heading);
p = document.createElement('html');
p.setAttribute('lang', <string>this.translations[t as keyof JSON]);
p.innerHTML = <string>this.translations[t as keyof JSON];
result.push(h, p);
}
return result;
}
}
export class Payload {
fields: string[]
constructor(json: string[] | undefined) {
this.fields = [];
if (json === undefined) return;
for (var f in json) {
this.fields.push(f);
}
}
format() {
var fieldList = document.createElement('ul');
this.fields.forEach(f => {
var field = document.createElement('li');
field.appendChild(document.createTextNode(f));
fieldList.appendChild(field);
});
return [fieldList];
}
}