Conversion functions script -> graph
This commit is contained in:
parent
a27da83c46
commit
6ffed6a69e
14
example.html
14
example.html
@ -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>
|
||||||
|
@ -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",
|
||||||
|
148
src/main.js
148
src/main.js
@ -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);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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
257
src/script-to-graph.ts
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user