diff --git a/src/hat-graph/hat-graph-node.ts b/src/hat-graph/hat-graph-node.ts index d251c2a..5a01e69 100644 --- a/src/hat-graph/hat-graph-node.ts +++ b/src/hat-graph/hat-graph-node.ts @@ -1,60 +1,59 @@ import { css, LitElement, property, svg } from "lit-element"; const NODE_SIZE = 24; +const VERTICAL_SPACING = 10; export class HatGraphNode extends LitElement { @property() iconPath?; - config = undefined; + @property({ reflect: true }) marked?; + @property({ reflect: true }) selected?; + @property({ reflect: true }) disabled?; connectedCallback() { super.connectedCallback(); if (!this.hasAttribute("tabindex")) this.setAttribute("tabindex", "0"); + this.addEventListener("focusin", () => this.setAttribute("selected", "")); + this.addEventListener("focusout", () => this.removeAttribute("selected")); } - updateNode(config) { - this.dispatchEvent( - new CustomEvent("update-node", { - detail: { config }, - bubbles: true, - composed: true, - }) + updated() { + const svg = this.shadowRoot.querySelector("svg"); + const bbox = svg.getBBox(); + svg.setAttribute("width", `${bbox.width + 2}px`); + svg.setAttribute("height", `${bbox.height + 1}px`); + svg.setAttribute( + "viewBox", + `${bbox.x - 1} ${bbox.y} ${bbox.width + 2} ${bbox.height + 1}` ); } - deleteNode() { - this.dispatchEvent(new CustomEvent("delete-node", { bubbles: true })); - } - - placeNode(config) { - this.dispatchEvent( - new CustomEvent("place-node", { detail: { config }, bubbles: true }) - ); - } - - get width() { - return NODE_SIZE + 5; - } - get height() { - return NODE_SIZE + 5; - } render() { return svg` + + ${this.iconPath ? svg`` : ""} + `; } @@ -63,28 +62,40 @@ export class HatGraphNode extends LitElement { return css` :host { display: flex; + flex-direction: column; --stroke-clr: var(--stroke-color, rgb(3, 169, 244)); - --hover-clr: var(--hover-color, rgb(255, 152, 0)); - } - :host(:hover) { - --stroke-clr: var(--hover-clr); + --selected-clr: var(--selected-color, rgb(255, 152, 0)); + --marked-clr: var(--marked-color, green); + --hover-clr: var(--hover-color, red); + --disabled-clr: var(--disabled-color, gray); } + :host([selected]), :host(:focus) { - --stroke-clr: green; + --circle-clr: var(--selected-clr); outline: none; } - :host(.dragging) { - --stroke-clr: gray; - color: gray; + :host([marked]) { + --stroke-clr: var(--marked-clr); } - :host(.dragging) path { - stroke: gray; - fill: gray; + :host(:hover) circle { + --stroke-clr: var(--hover-clr); } - circle { + :host([disabled]) circle { + stroke: var(--disabled-clr); + } + :host-context([disabled]) { + --stroke-clr: var(--disabled-clr); + } + + circle, + path.connector { stroke: var(--stroke-clr); stroke-width: 2; + fill: none; + } + circle { fill: white; + stroke: var(--circle-clr, var(--stroke-clr)); } `; } diff --git a/src/hat-graph/hat-graph.ts b/src/hat-graph/hat-graph.ts index 7ce25f7..8dd9c06 100644 --- a/src/hat-graph/hat-graph.ts +++ b/src/hat-graph/hat-graph.ts @@ -1,131 +1,131 @@ import { css, html, LitElement, property, svg } from "lit-element"; - -import "./hat-graph-node"; +import { classMap } from "lit-html/directives/class-map"; const BRANCH_HEIGHT = 30; const BRANCH_CURVATURE = 25; -const VERTICAL_SPACING = 10; -const NODE_SIZE = 24; export class HatGraph extends LitElement { @property() _num_items = 0; @property({ reflect: true }) branching?; - config?; - - updateNode(config) { - this.dispatchEvent( - new CustomEvent("update-node", { - detail: { config }, - bubbles: true, - composed: true, - }) - ); - } - - deleteNode() { - this.dispatchEvent(new CustomEvent("delete-node", { bubbles: true })); - } - - placeNode(config) { - this.dispatchEvent( - new CustomEvent("place-node", { detail: { config }, bubbles: true }) - ); - } - - get width() { - let w = 0; - if (this.branching !== undefined) { - for (const c of this.children) { - if (c.slot === "head") continue; - w += (c as any).width; - } - } else { - for (const c of this.children) { - w = Math.max(w, (c as any).width); - } - } - return w; - } - get height() { - let h = 0; - if (this.branching !== undefined) { - for (const c of this.children) { - if (c.slot === "head") continue; - h = Math.max(h, (c as any).height); - } - h += 2 * BRANCH_HEIGHT; - } else { - for (const c of this.children) { - h += (c as any).height + VERTICAL_SPACING; - } - h; - } - return h; - } + @property({ reflect: true }) mark_start?; + @property({ reflect: true }) mark_end?; + @property({ reflect: true }) disabled?; async updateChildren() { this._num_items = this.children.length; } render() { - let branch_x = []; - let total = 0; + let branches = []; + let total_width = 0; + let max_height = 0; + let min_height = Number.POSITIVE_INFINITY; if (this.branching !== undefined) { for (const c of Array.from(this.children)) { if (c.slot === "head") continue; const rect = c.getBoundingClientRect(); - branch_x.push(rect.width / 2 + total); - total += rect.width; + branches.push({ + x: rect.width / 2 + total_width, + height: rect.height, + start: c.getAttribute("graph-start") != null, + end: c.getAttribute("graph-end") != null, + }); + total_width += rect.width; + max_height = Math.max(max_height, rect.height); + min_height = Math.min(min_height, rect.height); } } - const line_end = this.height - BRANCH_HEIGHT; - return html` - - + + ${this.branching !== undefined + ? svg` + + ${branches.map((branch, i) => { + if (branch.start) return ""; + return svg` + + `; + })} + + + ` + : ""} +
${this.branching !== undefined - ? branch_x.map((x) => { - return svg` - - `; - }) - : svg` - - `} - - -
[slot='head']") ? "" : "no-head"}" - > + ? svg` + + ${branches.map((branch, i) => { + if (branch.end) return ""; + return svg` + + `; + })} + + ` + : ""}
+ + ${this.branching !== undefined + ? svg` + + ${branches.map((branch, i) => { + if (branch.end) return ""; + return svg` + + `; + })} + + + ` + : ""} `; } @@ -134,47 +134,38 @@ export class HatGraph extends LitElement { :host { position: relative; display: flex; + flex-direction: column; + align-items: center; --stroke-clr: var(--stroke-color, rgb(3, 169, 244)); - --hover-clr: var(--hover-color, rgb(255, 152, 0)); + --marked-clr: var(--marked-color, green); + --disabled-clr: var(--disabled-color, gray); } #branches { - position: absolute; - left: 0; + position: relative; display: flex; - flex-direction: row; + flex-direction: column; align-items: center; } :host([branching]) #branches { - top: ${BRANCH_HEIGHT}px; flex-direction: row; align-items: start; } - :host(:not([branching])) #branches { - top: ${VERTICAL_SPACING + NODE_SIZE}px; /* SHould be something else*/ - flex-direction: column; - } - :host(:not([branching])) #branches.no-head { - top: 0; + + #lines { + position: absolute; + z-index: -1; } + path.line { stroke: var(--stroke-clr); stroke-width: 2; fill: none; } - :host(:not([branching])) ::slotted(*) { - margin-bottom: 10px; + path.line.marked { + stroke: var(--marked-clr); } - ::slotted(:last-child) { - margin-bottom: 0; - } - ::slotted([slot="head"]) { - position: absolute; - top: ${BRANCH_HEIGHT / 2}px; - left: 50%; - transform: translate(-50%, -50%); - } - :host(:focus-within) ::slotted([slot="head"]) { - --stroke-color: green; + :host([disabled]) path.line { + stroke: var(--disabled-clr); } `; } diff --git a/src/hat-graph/make-graph.ts b/src/hat-graph/make-graph.ts index b1057fa..2e29f34 100644 --- a/src/hat-graph/make-graph.ts +++ b/src/hat-graph/make-graph.ts @@ -49,6 +49,7 @@ const ICONS = { chooseChoice: mdiCheckBoxOutline, chooseDefault: mdiCheckboxBlankOutline, YAML: mdiCodeJson, + trigger: mdiAsterisk, }; const SPECIAL_NODES = { @@ -69,8 +70,13 @@ function makeConditionNode(config) { html` + - + `, graph ); @@ -79,7 +85,6 @@ function makeConditionNode(config) { function makeChooseNode(config) { const graph = document.createElement("hat-graph") as HatGraph; - graph.config = config; graph.branching = true; const focused = () => @@ -143,8 +148,6 @@ function makeNode(config) { node.iconPath = ICONS[type]; - node.config = config; - node.addEventListener("focus", (ev) => { node.dispatchEvent( new CustomEvent("node-selected", { detail: { config }, bubbles: true })