Stability improvements and cleanup
This commit is contained in:
parent
9dead0c87d
commit
24954fddd2
@ -1,60 +1,59 @@
|
|||||||
import { css, LitElement, property, svg } from "lit-element";
|
import { css, LitElement, property, svg } from "lit-element";
|
||||||
|
|
||||||
const NODE_SIZE = 24;
|
const NODE_SIZE = 24;
|
||||||
|
const VERTICAL_SPACING = 10;
|
||||||
|
|
||||||
export class HatGraphNode extends LitElement {
|
export class HatGraphNode extends LitElement {
|
||||||
@property() iconPath?;
|
@property() iconPath?;
|
||||||
config = undefined;
|
@property({ reflect: true }) marked?;
|
||||||
|
@property({ reflect: true }) selected?;
|
||||||
|
@property({ reflect: true }) disabled?;
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
if (!this.hasAttribute("tabindex")) this.setAttribute("tabindex", "0");
|
if (!this.hasAttribute("tabindex")) this.setAttribute("tabindex", "0");
|
||||||
|
this.addEventListener("focusin", () => this.setAttribute("selected", ""));
|
||||||
|
this.addEventListener("focusout", () => this.removeAttribute("selected"));
|
||||||
}
|
}
|
||||||
|
|
||||||
updateNode(config) {
|
updated() {
|
||||||
this.dispatchEvent(
|
const svg = this.shadowRoot.querySelector("svg");
|
||||||
new CustomEvent("update-node", {
|
const bbox = svg.getBBox();
|
||||||
detail: { config },
|
svg.setAttribute("width", `${bbox.width + 2}px`);
|
||||||
bubbles: true,
|
svg.setAttribute("height", `${bbox.height + 1}px`);
|
||||||
composed: true,
|
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() {
|
render() {
|
||||||
return svg`
|
return svg`
|
||||||
<svg
|
<svg
|
||||||
width="${this.width}"
|
|
||||||
height="${this.height}"
|
|
||||||
viewBox="${-this.width / 2} 0 ${this.width} ${this.height}"
|
|
||||||
>
|
>
|
||||||
|
<path
|
||||||
|
class="connector"
|
||||||
|
d="
|
||||||
|
M 0 ${-VERTICAL_SPACING}
|
||||||
|
L 0 0
|
||||||
|
"
|
||||||
|
line-caps="round"
|
||||||
|
/>
|
||||||
|
<g class="node"
|
||||||
|
transform="translate(0 ${VERTICAL_SPACING})"
|
||||||
|
>
|
||||||
<circle
|
<circle
|
||||||
cx="0"
|
cx="0"
|
||||||
cy="${this.width / 2}"
|
cy="0"
|
||||||
r="${NODE_SIZE / 2}"
|
r="${NODE_SIZE / 2}"
|
||||||
/>
|
/>
|
||||||
<g
|
<g
|
||||||
style="pointer-events: none"
|
style="pointer-events: none"
|
||||||
transform="translate(${-12} ${this.width / 2 - 12})"
|
transform="translate(${-12} ${-12})"
|
||||||
>
|
>
|
||||||
${this.iconPath ? svg`<path d="${this.iconPath}"/>` : ""}
|
${this.iconPath ? svg`<path d="${this.iconPath}"/>` : ""}
|
||||||
</g>
|
</g>
|
||||||
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -63,28 +62,40 @@ export class HatGraphNode extends LitElement {
|
|||||||
return css`
|
return css`
|
||||||
:host {
|
:host {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
--stroke-clr: var(--stroke-color, rgb(3, 169, 244));
|
--stroke-clr: var(--stroke-color, rgb(3, 169, 244));
|
||||||
--hover-clr: var(--hover-color, rgb(255, 152, 0));
|
--selected-clr: var(--selected-color, rgb(255, 152, 0));
|
||||||
}
|
--marked-clr: var(--marked-color, green);
|
||||||
:host(:hover) {
|
--hover-clr: var(--hover-color, red);
|
||||||
--stroke-clr: var(--hover-clr);
|
--disabled-clr: var(--disabled-color, gray);
|
||||||
}
|
}
|
||||||
|
:host([selected]),
|
||||||
:host(:focus) {
|
:host(:focus) {
|
||||||
--stroke-clr: green;
|
--circle-clr: var(--selected-clr);
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
:host(.dragging) {
|
:host([marked]) {
|
||||||
--stroke-clr: gray;
|
--stroke-clr: var(--marked-clr);
|
||||||
color: gray;
|
|
||||||
}
|
}
|
||||||
:host(.dragging) path {
|
:host(:hover) circle {
|
||||||
stroke: gray;
|
--stroke-clr: var(--hover-clr);
|
||||||
fill: gray;
|
|
||||||
}
|
}
|
||||||
circle {
|
:host([disabled]) circle {
|
||||||
|
stroke: var(--disabled-clr);
|
||||||
|
}
|
||||||
|
:host-context([disabled]) {
|
||||||
|
--stroke-clr: var(--disabled-clr);
|
||||||
|
}
|
||||||
|
|
||||||
|
circle,
|
||||||
|
path.connector {
|
||||||
stroke: var(--stroke-clr);
|
stroke: var(--stroke-clr);
|
||||||
stroke-width: 2;
|
stroke-width: 2;
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
|
circle {
|
||||||
fill: white;
|
fill: white;
|
||||||
|
stroke: var(--circle-clr, var(--stroke-clr));
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -1,131 +1,131 @@
|
|||||||
import { css, html, LitElement, property, svg } from "lit-element";
|
import { css, html, LitElement, property, svg } from "lit-element";
|
||||||
|
import { classMap } from "lit-html/directives/class-map";
|
||||||
import "./hat-graph-node";
|
|
||||||
|
|
||||||
const BRANCH_HEIGHT = 30;
|
const BRANCH_HEIGHT = 30;
|
||||||
const BRANCH_CURVATURE = 25;
|
const BRANCH_CURVATURE = 25;
|
||||||
const VERTICAL_SPACING = 10;
|
|
||||||
const NODE_SIZE = 24;
|
|
||||||
|
|
||||||
export class HatGraph extends LitElement {
|
export class HatGraph extends LitElement {
|
||||||
@property() _num_items = 0;
|
@property() _num_items = 0;
|
||||||
|
|
||||||
@property({ reflect: true }) branching?;
|
@property({ reflect: true }) branching?;
|
||||||
config?;
|
@property({ reflect: true }) mark_start?;
|
||||||
|
@property({ reflect: true }) mark_end?;
|
||||||
updateNode(config) {
|
@property({ reflect: true }) disabled?;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateChildren() {
|
async updateChildren() {
|
||||||
this._num_items = this.children.length;
|
this._num_items = this.children.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let branch_x = [];
|
let branches = [];
|
||||||
let total = 0;
|
let total_width = 0;
|
||||||
|
let max_height = 0;
|
||||||
|
let min_height = Number.POSITIVE_INFINITY;
|
||||||
if (this.branching !== undefined) {
|
if (this.branching !== undefined) {
|
||||||
for (const c of Array.from(this.children)) {
|
for (const c of Array.from(this.children)) {
|
||||||
if (c.slot === "head") continue;
|
if (c.slot === "head") continue;
|
||||||
const rect = c.getBoundingClientRect();
|
const rect = c.getBoundingClientRect();
|
||||||
branch_x.push(rect.width / 2 + total);
|
branches.push({
|
||||||
total += rect.width;
|
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`
|
return html`
|
||||||
<svg width="${this.width}" height="${this.height}">
|
<slot name="head" @slotchange=${this.updateChildren}> </slot>
|
||||||
<rect
|
${this.branching !== undefined
|
||||||
x="0"
|
? svg`
|
||||||
y="0"
|
<svg
|
||||||
width="${this.width}"
|
id="top"
|
||||||
height="${this.height}"
|
width="${total_width}"
|
||||||
fill="white"
|
height="${BRANCH_HEIGHT}"
|
||||||
/>
|
>
|
||||||
|
${branches.map((branch, i) => {
|
||||||
|
if (branch.start) return "";
|
||||||
|
return svg`
|
||||||
|
<path
|
||||||
|
class="${classMap({
|
||||||
|
line: true,
|
||||||
|
marked: this.mark_start === i,
|
||||||
|
})}"
|
||||||
|
id="${this.mark_start === i ? "mark-start" : ""}"
|
||||||
|
index=${i}
|
||||||
|
d="
|
||||||
|
M ${total_width / 2} 0
|
||||||
|
C ${total_width / 2} ${BRANCH_CURVATURE}
|
||||||
|
${branch.x} ${BRANCH_HEIGHT - BRANCH_CURVATURE}
|
||||||
|
${branch.x} ${BRANCH_HEIGHT}
|
||||||
|
"/>
|
||||||
|
`;
|
||||||
|
})}
|
||||||
|
<use xlink:href="#mark-start" />
|
||||||
|
</svg>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<div id="branches">
|
||||||
${this.branching !== undefined
|
${this.branching !== undefined
|
||||||
? branch_x.map((x) => {
|
? svg`
|
||||||
return svg`
|
<svg
|
||||||
<path
|
id="lines"
|
||||||
class="line"
|
width="${total_width}"
|
||||||
d="
|
height="${max_height}"
|
||||||
M ${this.width / 2} 0
|
>
|
||||||
C ${this.width / 2} ${BRANCH_CURVATURE}
|
${branches.map((branch, i) => {
|
||||||
${x} ${BRANCH_HEIGHT - BRANCH_CURVATURE}
|
if (branch.end) return "";
|
||||||
${x} ${BRANCH_HEIGHT}
|
return svg`
|
||||||
L ${x} ${line_end}
|
<path
|
||||||
C ${x} ${line_end + BRANCH_CURVATURE}
|
class="${classMap({
|
||||||
${this.width / 2} ${this.height - BRANCH_CURVATURE}
|
line: true,
|
||||||
${this.width / 2} ${this.height}
|
marked: this.mark_end === i,
|
||||||
"
|
})}"
|
||||||
/>
|
index=${i}
|
||||||
`;
|
d="
|
||||||
})
|
M ${branch.x} ${branch.height}
|
||||||
: svg`
|
l 0 ${max_height - branch.height}
|
||||||
<path
|
"/>
|
||||||
class="line"
|
`;
|
||||||
d="
|
})}
|
||||||
M ${this.width / 2} 0
|
</svg>
|
||||||
L ${this.width / 2} ${this.height}
|
`
|
||||||
"
|
: ""}
|
||||||
/>
|
|
||||||
`}
|
|
||||||
</svg>
|
|
||||||
<slot name="head"> </slot>
|
|
||||||
<div
|
|
||||||
id="branches"
|
|
||||||
class="${this.querySelector(":scope > [slot='head']") ? "" : "no-head"}"
|
|
||||||
>
|
|
||||||
<slot @slotchange=${this.updateChildren}></slot>
|
<slot @slotchange=${this.updateChildren}></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
${this.branching !== undefined
|
||||||
|
? svg`
|
||||||
|
<svg
|
||||||
|
id="bottom"
|
||||||
|
width="${total_width}"
|
||||||
|
height="${BRANCH_HEIGHT}"
|
||||||
|
>
|
||||||
|
${branches.map((branch, i) => {
|
||||||
|
if (branch.end) return "";
|
||||||
|
return svg`
|
||||||
|
<path
|
||||||
|
class="${classMap({
|
||||||
|
line: true,
|
||||||
|
marked: this.mark_end === i,
|
||||||
|
})}"
|
||||||
|
id="${this.mark_end === i ? "mark-end" : ""}"
|
||||||
|
index=${i}
|
||||||
|
d="
|
||||||
|
M ${branch.x} 0
|
||||||
|
C ${branch.x} ${BRANCH_CURVATURE}
|
||||||
|
${total_width / 2} ${BRANCH_HEIGHT - BRANCH_CURVATURE}
|
||||||
|
${total_width / 2} ${BRANCH_HEIGHT}
|
||||||
|
"/>
|
||||||
|
`;
|
||||||
|
})}
|
||||||
|
<use xlink:href="#mark-end" />
|
||||||
|
</svg>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,47 +134,38 @@ export class HatGraph extends LitElement {
|
|||||||
:host {
|
:host {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
--stroke-clr: var(--stroke-color, rgb(3, 169, 244));
|
--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 {
|
#branches {
|
||||||
position: absolute;
|
position: relative;
|
||||||
left: 0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
:host([branching]) #branches {
|
:host([branching]) #branches {
|
||||||
top: ${BRANCH_HEIGHT}px;
|
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
}
|
}
|
||||||
:host(:not([branching])) #branches {
|
|
||||||
top: ${VERTICAL_SPACING + NODE_SIZE}px; /* SHould be something else*/
|
#lines {
|
||||||
flex-direction: column;
|
position: absolute;
|
||||||
}
|
z-index: -1;
|
||||||
:host(:not([branching])) #branches.no-head {
|
|
||||||
top: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
path.line {
|
path.line {
|
||||||
stroke: var(--stroke-clr);
|
stroke: var(--stroke-clr);
|
||||||
stroke-width: 2;
|
stroke-width: 2;
|
||||||
fill: none;
|
fill: none;
|
||||||
}
|
}
|
||||||
:host(:not([branching])) ::slotted(*) {
|
path.line.marked {
|
||||||
margin-bottom: 10px;
|
stroke: var(--marked-clr);
|
||||||
}
|
}
|
||||||
::slotted(:last-child) {
|
:host([disabled]) path.line {
|
||||||
margin-bottom: 0;
|
stroke: var(--disabled-clr);
|
||||||
}
|
|
||||||
::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;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,7 @@ const ICONS = {
|
|||||||
chooseChoice: mdiCheckBoxOutline,
|
chooseChoice: mdiCheckBoxOutline,
|
||||||
chooseDefault: mdiCheckboxBlankOutline,
|
chooseDefault: mdiCheckboxBlankOutline,
|
||||||
YAML: mdiCodeJson,
|
YAML: mdiCodeJson,
|
||||||
|
trigger: mdiAsterisk,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SPECIAL_NODES = {
|
const SPECIAL_NODES = {
|
||||||
@ -69,8 +70,13 @@ function makeConditionNode(config) {
|
|||||||
html`
|
html`
|
||||||
<hat-graph-node slot="head" .iconPath=${mdiAbTesting} @focus=${focused}>
|
<hat-graph-node slot="head" .iconPath=${mdiAbTesting} @focus=${focused}>
|
||||||
</hat-graph-node>
|
</hat-graph-node>
|
||||||
|
|
||||||
<hat-graph-node .iconPath=${mdiCheck} @focus=${focused}></hat-graph-node>
|
<hat-graph-node .iconPath=${mdiCheck} @focus=${focused}></hat-graph-node>
|
||||||
<hat-graph-node .iconPath=${mdiClose} @focus=${focused}></hat-graph-node>
|
<hat-graph-node
|
||||||
|
.iconPath=${mdiClose}
|
||||||
|
@focus=${focused}
|
||||||
|
graph-end
|
||||||
|
></hat-graph-node>
|
||||||
`,
|
`,
|
||||||
graph
|
graph
|
||||||
);
|
);
|
||||||
@ -79,7 +85,6 @@ function makeConditionNode(config) {
|
|||||||
|
|
||||||
function makeChooseNode(config) {
|
function makeChooseNode(config) {
|
||||||
const graph = document.createElement("hat-graph") as HatGraph;
|
const graph = document.createElement("hat-graph") as HatGraph;
|
||||||
graph.config = config;
|
|
||||||
graph.branching = true;
|
graph.branching = true;
|
||||||
|
|
||||||
const focused = () =>
|
const focused = () =>
|
||||||
@ -143,8 +148,6 @@ function makeNode(config) {
|
|||||||
|
|
||||||
node.iconPath = ICONS[type];
|
node.iconPath = ICONS[type];
|
||||||
|
|
||||||
node.config = config;
|
|
||||||
|
|
||||||
node.addEventListener("focus", (ev) => {
|
node.addEventListener("focus", (ev) => {
|
||||||
node.dispatchEvent(
|
node.dispatchEvent(
|
||||||
new CustomEvent("node-selected", { detail: { config }, bubbles: true })
|
new CustomEvent("node-selected", { detail: { config }, bubbles: true })
|
||||||
|
Loading…
x
Reference in New Issue
Block a user