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";
|
||||
|
||||
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`
|
||||
<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
|
||||
cx="0"
|
||||
cy="${this.width / 2}"
|
||||
cy="0"
|
||||
r="${NODE_SIZE / 2}"
|
||||
/>
|
||||
<g
|
||||
style="pointer-events: none"
|
||||
transform="translate(${-12} ${this.width / 2 - 12})"
|
||||
transform="translate(${-12} ${-12})"
|
||||
>
|
||||
${this.iconPath ? svg`<path d="${this.iconPath}"/>` : ""}
|
||||
</g>
|
||||
</g>
|
||||
</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));
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@ -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`
|
||||
<svg width="${this.width}" height="${this.height}">
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="${this.width}"
|
||||
height="${this.height}"
|
||||
fill="white"
|
||||
/>
|
||||
<slot name="head" @slotchange=${this.updateChildren}> </slot>
|
||||
${this.branching !== undefined
|
||||
? svg`
|
||||
<svg
|
||||
id="top"
|
||||
width="${total_width}"
|
||||
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
|
||||
? branch_x.map((x) => {
|
||||
return svg`
|
||||
<path
|
||||
class="line"
|
||||
d="
|
||||
M ${this.width / 2} 0
|
||||
C ${this.width / 2} ${BRANCH_CURVATURE}
|
||||
${x} ${BRANCH_HEIGHT - BRANCH_CURVATURE}
|
||||
${x} ${BRANCH_HEIGHT}
|
||||
L ${x} ${line_end}
|
||||
C ${x} ${line_end + BRANCH_CURVATURE}
|
||||
${this.width / 2} ${this.height - BRANCH_CURVATURE}
|
||||
${this.width / 2} ${this.height}
|
||||
"
|
||||
/>
|
||||
`;
|
||||
})
|
||||
: svg`
|
||||
<path
|
||||
class="line"
|
||||
d="
|
||||
M ${this.width / 2} 0
|
||||
L ${this.width / 2} ${this.height}
|
||||
"
|
||||
/>
|
||||
`}
|
||||
</svg>
|
||||
<slot name="head"> </slot>
|
||||
<div
|
||||
id="branches"
|
||||
class="${this.querySelector(":scope > [slot='head']") ? "" : "no-head"}"
|
||||
>
|
||||
? svg`
|
||||
<svg
|
||||
id="lines"
|
||||
width="${total_width}"
|
||||
height="${max_height}"
|
||||
>
|
||||
${branches.map((branch, i) => {
|
||||
if (branch.end) return "";
|
||||
return svg`
|
||||
<path
|
||||
class="${classMap({
|
||||
line: true,
|
||||
marked: this.mark_end === i,
|
||||
})}"
|
||||
index=${i}
|
||||
d="
|
||||
M ${branch.x} ${branch.height}
|
||||
l 0 ${max_height - branch.height}
|
||||
"/>
|
||||
`;
|
||||
})}
|
||||
</svg>
|
||||
`
|
||||
: ""}
|
||||
<slot @slotchange=${this.updateChildren}></slot>
|
||||
</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 {
|
||||
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);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@ -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`
|
||||
<hat-graph-node slot="head" .iconPath=${mdiAbTesting} @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
|
||||
);
|
||||
@ -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 })
|
||||
|
Loading…
x
Reference in New Issue
Block a user