script-graph/src/script-to-graph.ts

263 lines
6.2 KiB
TypeScript

import {
mdiCallSplit,
mdiAbTesting,
mdiCheck,
mdiClose,
mdiChevronRight,
mdiExclamation,
mdiTimerOutline,
mdiTrafficLight,
mdiRefresh,
mdiArrowUp,
mdiCodeJson,
mdiCheckBoxOutline,
mdiCheckboxBlankOutline,
mdiAsterisk,
} from "@mdi/js";
const ICONS = {
new: mdiAsterisk,
service: mdiChevronRight,
condition: mdiAbTesting,
TRUE: mdiCheck,
FALSE: mdiClose,
delay: mdiTimerOutline,
wait_template: mdiTrafficLight,
event: mdiExclamation,
repeat: mdiRefresh,
repeatReturn: mdiArrowUp,
choose: mdiCallSplit,
chooseChoice: mdiCheckBoxOutline,
chooseDefault: mdiCheckboxBlankOutline,
YAML: mdiCodeJson,
};
import { TreeNode} from "./script-graph";
import { Action } from "./types";
const OPTIONS = [
"condition",
"delay",
"device_id",
"event",
"scene",
"service",
"wait_template",
"repeat",
"choose",
];
interface NodeHandler {
(action, selected: any[], select, update): TreeNode;
}
const SPECIAL = {
condition: (action, selected, select, update) => {
return {
icon: ICONS["condition"],
clickCallback: () => select([1], action, update),
children: [
{
icon: ICONS["TRUE"],
clickCallback: () => select([2], action, update),
styles: selected[0]
? "stroke: orange;"
: undefined,
},
{
icon: ICONS["FALSE"],
end: false,
clickCallback: () => select([3], action, update),
styles: selected[0]
? "stroke: orange;"
: undefined,
},
],
}
},
repeat: (action, selected, select, update) => {
let seq = action.repeat.sequence;
if(!seq || !seq.length) seq = [{}];
const seqHandler = new ActionHandler(seq);
if(selected[0] !== undefined && selected[0] !== -1)
seqHandler.selected = selected;
seqHandler.selectCallback = select;
seqHandler.updateCallback = (a) => {
action.repeat["sequence"] = a;
update(action);
}
return {
icon: ICONS["repeat"],
clickCallback: () => select([-1], action, update),
children: [
{
icon: ICONS["repeatReturn"],
clickCallback: () => select([-1], action, update),
styles: selected[0] === -1
? "stroke: orange;"
: undefined,
},
seqHandler.graph,
],
};
},
choose: (action, selected, select, update) => {
let def = action.default || [{}];
const defaultHandler = new ActionHandler(def);
if(selected[0] === -2)
defaultHandler.selected = selected.slice(1);
defaultHandler.selectCallback = (i, a, u) => {
select( [-2].concat(i), a, u);
};
defaultHandler.updateCallback = (a) => {
action.default = defaultHandler.actions;
update(action);
};
return {
icon: ICONS["choose"],
clickCallback: () => select([-1], action, update),
children: [
...action.choose.map((b,idx) => {
const handler = new ActionHandler(b.sequence || [{}]);
if(selected[0] === idx)
handler.selected = selected.slice(1);
handler.selectCallback = (i, a, u) => {
select([idx].concat(i), a, u);
};
handler.updateCallback = (a) => {
b.sequence = handler.actions;
action.choose[idx] = b;
update(action);
};
return [
{
icon: ICONS["chooseChoice"],
clickCallback: () => select([idx], b, (a) => {
action.choose[idx] = a;
update(action);
}),
styles: selected[0] === idx
? "stroke: orange;"
: undefined,
},
handler.graph,
];
}),
[
{
icon: ICONS["chooseDefault"],
clickCallback: () => select([-2], def, (a) => {
action.default = a;
update(action);
}),
styles: selected[0] === -2
? "stroke: orange;"
: undefined,
},
defaultHandler.graph,
]
],
};
},
};
interface NoAction {
}
export class ActionHandler {
_actions: (Action | NoAction)[] = [];
updateCallback = null;
selectCallback = null;
selected: number[] = [];
constructor(actions: (Action | NoAction)[] = []) {
this._actions = actions;
}
set actions(actions) {
this._actions = actions;
}
get actions() {
if(!this._actions.length) this._actions = [{}];
return this._actions;
}
get graph() {
return this.actions.map((action, idx) => this._make_graph_node(idx, action));
}
_update_action(idx: number, action) {
if(action === null)
this.actions.splice(idx, 1);
else
this.actions[idx] = action;
if (this.updateCallback)
this.updateCallback(this.actions);
}
_add_action(idx: number) {
this.actions.splice(idx, 0, {});
if (this.updateCallback)
this.updateCallback(this.actions);
this._select_node([idx], {}, (a) =>this._update_action(idx, a));
}
_select_node(path: number[], action, update=null) {
this.selected = path;
if (this.selectCallback)
this.selectCallback(path, action, update);
}
_make_graph_node(idx: number, action): TreeNode {
let _type = "yaml";
if (Object.keys(action).length === 0)
_type = "new";
else
_type = OPTIONS.find((option) => option in action) || "YAML";
const selected = this.selected.length >= 1 && this.selected[0] == idx;
let node: TreeNode = {icon: ""};
if (_type in SPECIAL) {
node = SPECIAL[_type](
action,
selected ? this.selected.slice(1): [],
(i, a, u) => this._select_node([idx].concat(i), a, u),
(a) => this._update_action(idx, a),
);
} else {
node = {
icon: ICONS[_type],
clickCallback: () => {
this._select_node(
[idx],
action,
(a) => this._update_action(idx, a)
)
}
}
}
return {
...node,
addCallback: () => this._add_action(idx+1),
styles: selected
? "stroke: orange"
: _type === "new"
? "stroke: lightgreen;"
: undefined,
}
}
}