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, } } }