uni2work.workflows.visualiser/workflow.ts
2023-08-24 04:18:07 +02:00

398 lines
9.4 KiB
TypeScript

// 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];
}
}