Conversion functions script -> graph

This commit is contained in:
Thomas Lovén 2020-08-21 23:52:50 +02:00
parent a27da83c46
commit 6ffed6a69e
5 changed files with 338 additions and 134 deletions

View File

@ -25,7 +25,7 @@
max-width: 400px; max-width: 400px;
overflow: auto; overflow: auto;
} }
#code, #fullcode { .code {
max-height: 800px; max-height: 800px;
width: 400px; width: 400px;
overflow: auto; overflow: auto;
@ -37,12 +37,14 @@
</div> </div>
<div class="card" id="graph2"> <div class="card" id="graph2">
</div> </div>
<div class="card" id="code"> <div class="card code">
<code> <wc-codemirror mode="json" id="snippet">
</code> </wc-codemirror>
<button id="saveSnippet">Save</button>
<button id="deleteSnippet">Delete</button>
</div> </div>
<div class="card" id="fullcode"> <div class="card code">
<wc-codemirror mode="yaml"> <wc-codemirror mode="yaml" id="fullcode">
</wc-codemirror> </wc-codemirror>
<button id="updateButton">Update</button> <button id="updateButton">Update</button>
</div> </div>

View File

@ -1,4 +1,48 @@
export const demoConfig = [ export const demoConfig = [
{
condition: "state",
entity_id: "binary_sensor.dark_outside",
state: "on",
},
{
choose: [
{
conditions: [
{
condition: "state",
entity_id: "binary_sensor.door_open",
state: "on",
},
],
sequence: [
{
service: "light.turn_on",
entity_id: "light.outdoors",
}
],
},
{
conditions: [
{
condition: "state",
entity_id: "input_select.time_of_day",
state: "night",
},
],
sequence: [
{
service: "light.turn_off",
entity_id: "light.outdoors",
}
],
},
],
default: [
],
},
]
export const demoConfig2 = [
{ service: "light.turn_on", { service: "light.turn_on",
data: { data: {
entity_id: "group.bedroom", entity_id: "group.bedroom",

View File

@ -4,121 +4,18 @@ import "@vanillawc/wc-codemirror";
import "./script-graph"; import "./script-graph";
import "./script-graph2"; import "./script-graph2";
import { ActionHandler } from "./script-to-graph";
import { mdiAsterisk, mdiArrowUp, mdiArrowDown } from "@mdi/js"; import { mdiAsterisk, mdiArrowUp, mdiArrowDown } from "@mdi/js";
let index_counter = 0; let index_counter = 0;
let nodes = []; let nodes = [];
const structure_tree = (inp) => {
if(!inp) return null;
if(Array.isArray(inp)) return inp.map(structure_tree);
let data = {};
let type = "YAML";
const idx = index_counter++;
if("service" in inp) type = "service";
if("condition" in inp) type = "condition";
if("delay" in inp) type = "delay";
if("wait_template" in inp) type = "wait";
if("event" in inp) type = "event";
if("repeat" in inp) {
type = "loop";
data = {sequence: structure_tree(inp.repeat.sequence)};
}
if("choose" in inp) {
type = "choose";
let choices = [];
for (const [i,c] of inp.choose.entries()) {
const header = {
type: "chooseChoice",
idx: [idx, i],
}
choices.push([header].concat(structure_tree(c.sequence)));
}
choices.push([{
type: "chooseDefault",
idx: [idx, -1],
}].concat(structure_tree(inp.default)));
data = {
choices,
}
}
nodes[idx] = inp;
return {type,
idx,
...data};
}
const structure_tree2 = () => {
return [
{
icon: mdiAsterisk,
clickCallback: () => console.log("Number 1"),
},
{
icon: mdiAsterisk,
clickCallback: () => console.log("Number 2"),
},
{
icon: mdiAsterisk,
clickCallback: () => console.log("Number 3"),
},
{
icon: mdiAsterisk,
clickCallback: () => console.log("Number 4"),
children: [
[
{
icon: mdiAsterisk,
clickCallback: () => console.log("Number A1"),
},
{
icon: mdiAsterisk,
clickCallback: () => console.log("Number A2"),
},
],
{
icon: mdiAsterisk,
clickCallback: () => console.log("Number B"),
end: false,
},
{
icon: mdiAsterisk,
clickCallback: () => console.log("Number B"),
},
{
icon: mdiAsterisk,
clickCallback: () => console.log("Number B"),
},
]
},
{
icon: mdiAsterisk,
clickCallback: () => console.log("Number 4"),
children: [
{
icon: mdiArrowUp,
clickCallback: () => console.log("Return"),
},
{
icon: mdiArrowDown,
clickCallback: () => console.log("Return"),
},
],
},
{
icon: mdiAsterisk,
clickCallback: () => console.log("Number 1"),
},
];
}
window.onload = () => { window.onload = () => {
let src = demoConfig; let src = demoConfig;
const fullcode = document.querySelector("wc-codemirror"); const fullcode = document.querySelector("#fullcode");
fullcode.mode = "yaml"; fullcode.mode = "yaml";
window.setTimeout(()=> fullcode.value = jsyaml.safeDump(src), 100); window.setTimeout(()=> fullcode.value = jsyaml.safeDump(src), 100);
@ -127,29 +24,28 @@ window.onload = () => {
src = jsyaml.safeLoad(fullcode.value); src = jsyaml.safeLoad(fullcode.value);
index_counter = 0; index_counter = 0;
nodes = []; nodes = [];
graph.tree = structure_tree(src); // graph.tree = structure_tree(src);
}); });
const graph = document.createElement("script-graph");
graph.tree = structure_tree(src);
const graph2 = document.createElement("script-graph2"); const graph2 = document.createElement("script-graph2");
graph2.tree = structure_tree2(src); const tr = new ActionHandler();
window.tr = tr;
tr.actions = src;
tr.updateCallback = (actions) => {
graph2.tree = tr.graph;
fullcode.value = jsyaml.safeDump(tr.actions);
};
tr.selectCallback = (idx, action, update) => {
graph2.tree = tr.graph;
const code = document.querySelector("#snippet");
code.value = jsyaml.safeDump(action);
console.log(update);
document.querySelector("#saveSnippet").onclick = () => update(jsyaml.safeLoad(code.value));
document.querySelector("#deleteSnippet").onclick = () => update(jsyaml.safeLoad(null));
}
// graph2.tree = structure_tree2(src);
graph2.tree = tr.graph;
document.querySelector("#graph2").appendChild(graph2); document.querySelector("#graph2").appendChild(graph2);
graph.addEventListener("selected", (ev) => {
const idx = ev.detail;
const code = document.querySelector("code");
let c;
if(Array.isArray(idx)) {
c = nodes[idx[0]].choose[idx[1]];
} else {
c = nodes[idx];
}
code.innerHTML = JSON.stringify(c, null, ' ');
})
document.querySelector("#graph").appendChild(graph);
} }

View File

@ -34,6 +34,7 @@ class ScriptGraph2 extends LitElement {
r="${this.nodeSize/2}" r="${this.nodeSize/2}"
class="node" class="node"
@click=${node.clickCallback} @click=${node.clickCallback}
style=${node.styles}
/> />
<g style="pointer-events: none" transform="translate(${x - 12} ${y + this.nodeSize/2 - 12})"> <g style="pointer-events: none" transform="translate(${x - 12} ${y + this.nodeSize/2 - 12})">
<path d="${node.icon}"/> <path d="${node.icon}"/>
@ -177,13 +178,17 @@ class ScriptGraph2 extends LitElement {
let tree = this._draw_tree(this.tree); let tree = this._draw_tree(this.tree);
return html` return html`
<style> <style>
:host {
--stroke-clr: var(--stroke-color, rgb(3, 169, 244));
--hover-clr: var(--hover-color, rgb(255, 152, 0));
}
circle, line { circle, line {
stroke: rgb(3, 169, 244); stroke: var(--stroke-clr);
stroke-width: 5; stroke-width: 5;
fill: white; fill: white;
} }
.newnode:hover { .newnode:hover {
stroke: rgb(255, 152, 0); stroke: var(--hover-clr);
} }
</style> </style>
<svg width=${tree.width + 32} height=${tree.height + 32}> <svg width=${tree.width + 32} height=${tree.height + 32}>

257
src/script-to-graph.ts Normal file
View File

@ -0,0 +1,257 @@
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,
};
const OPTIONS = [
"condition",
"delay",
"device_id",
"event",
"scene",
"service",
"wait_template",
"repeat",
"choose",
];
const SPECIAL = {
condition: (action, selected, select, update) => {
return {
icon: ICONS["choose"],
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);
console.log(selected)
if(selected[0] === -2)
defaultHandler.selected = selected.slice(1);
defaultHandler.selectCallback = (i, a, u) => {
select( [-2].concat(i), a, u);
};
defaultHandler.updateCallback = (a) => {
console.log("UPDATE");
console.log(defaultHandler.actions);
console.log(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,
]
],
};
},
};
export class ActionHandler {
_actions = [];
updateCallback = null;
selectCallback = null;
selected = [];
constructor(actions = []) {
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((_, idx) => this._make_graph_node(idx));
}
_update_action(idx, action) {
if(action === null)
this.actions.splice(idx, 1);
else
this.actions[idx] = action;
if (this.updateCallback)
this.updateCallback(this.actions);
}
_add_action(idx) {
this.actions.splice(idx, 0, {});
if (this.updateCallback)
this.updateCallback(this.actions);
this._select_node([idx], {}, (a) =>this._update_action(idx, a));
}
_select_node(idx, action, update=null) {
this.selected = idx;
if (this.selectCallback)
this.selectCallback(idx, action, update);
}
_make_graph_node(idx) {
const action = this.actions[idx] || {};
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 = {};
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,
}
}
}