398 lines
9.4 KiB
TypeScript
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];
|
|
}
|
|
} |