263 lines
6.2 KiB
TypeScript
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,
|
|
}
|
|
}
|
|
|
|
}
|