mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-07 17:36:35 +00:00
Cleanup of tracing graph (#9564)
This commit is contained in:
parent
dc50e54afc
commit
f686816c86
@ -5,7 +5,6 @@ import { classMap } from "lit/directives/class-map";
|
||||
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
|
||||
import "../ha-code-editor";
|
||||
import "../ha-icon-button";
|
||||
import type { NodeInfo } from "./hat-graph";
|
||||
import "./hat-logbook-note";
|
||||
import { LogbookEntry } from "../../data/logbook";
|
||||
import {
|
||||
@ -17,6 +16,7 @@ import {
|
||||
import "../../panels/logbook/ha-logbook";
|
||||
import { traceTabStyles } from "./trace-tab-styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import type { NodeInfo } from "./hat-script-graph";
|
||||
|
||||
@customElement("ha-trace-path-details")
|
||||
export class HaTracePathDetails extends LitElement {
|
||||
@ -30,7 +30,7 @@ export class HaTracePathDetails extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public selected!: NodeInfo;
|
||||
|
||||
@property() renderedNodes: Record<string, any> = {};
|
||||
@property() public renderedNodes: Record<string, any> = {};
|
||||
|
||||
@property() public trackedNodes!: Record<string, any>;
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import type { NodeInfo } from "./hat-graph";
|
||||
import "./hat-logbook-note";
|
||||
import "./hat-trace-timeline";
|
||||
import type { LogbookEntry } from "../../data/logbook";
|
||||
import type { TraceExtended } from "../../data/trace";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { NodeInfo } from "./hat-script-graph";
|
||||
|
||||
@customElement("ha-trace-timeline")
|
||||
export class HaTraceTimeline extends LitElement {
|
||||
@ -15,7 +15,7 @@ export class HaTraceTimeline extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public logbookEntries!: LogbookEntry[];
|
||||
|
||||
@property() public selected!: NodeInfo;
|
||||
@property({ attribute: false }) public selected!: NodeInfo;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
|
186
src/components/trace/hat-graph-branch.ts
Normal file
186
src/components/trace/hat-graph-branch.ts
Normal file
@ -0,0 +1,186 @@
|
||||
import { css, html, LitElement, svg } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { BRANCH_HEIGHT, SPACING } from "./hat-graph-const";
|
||||
|
||||
interface BranchConfig {
|
||||
x: number;
|
||||
height: number;
|
||||
start: boolean;
|
||||
end: boolean;
|
||||
track: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @attribute active
|
||||
* @attribute track
|
||||
*/
|
||||
@customElement("hat-graph-branch")
|
||||
export class HatGraphBranch extends LitElement {
|
||||
@property({ reflect: true, type: Boolean }) disabled?: boolean;
|
||||
|
||||
@property({ type: Boolean }) selected?: boolean;
|
||||
|
||||
@property({ type: Boolean }) start = false;
|
||||
|
||||
@property({ type: Boolean }) short = false;
|
||||
|
||||
@state() _branches: BranchConfig[] = [];
|
||||
|
||||
private _totalWidth = 0;
|
||||
|
||||
private _maxHeight = 0;
|
||||
|
||||
private _updateBranches(ev: Event) {
|
||||
let total_width = 0;
|
||||
const heights: number[] = [];
|
||||
const branches: BranchConfig[] = [];
|
||||
(ev.target as HTMLSlotElement).assignedElements().forEach((c) => {
|
||||
const width = c.clientWidth;
|
||||
const height = c.clientHeight;
|
||||
branches.push({
|
||||
x: width / 2 + total_width,
|
||||
height,
|
||||
start: c.hasAttribute("graphStart"),
|
||||
end: c.hasAttribute("graphEnd"),
|
||||
track: c.hasAttribute("track"),
|
||||
});
|
||||
total_width += width;
|
||||
heights.push(height);
|
||||
});
|
||||
this._totalWidth = total_width;
|
||||
this._maxHeight = Math.max(...heights);
|
||||
this._branches = branches.sort((a, b) => {
|
||||
if (a.track && !b.track) {
|
||||
return 1;
|
||||
}
|
||||
if (a.track && b.track) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<slot name="head"></slot>
|
||||
${!this.start
|
||||
? svg`
|
||||
<svg
|
||||
id="top"
|
||||
width="${this._totalWidth}"
|
||||
>
|
||||
${this._branches.map((branch) =>
|
||||
branch.start
|
||||
? ""
|
||||
: svg`
|
||||
<path
|
||||
class=${classMap({
|
||||
track: branch.track,
|
||||
})}
|
||||
d="
|
||||
M ${this._totalWidth / 2} 0
|
||||
L ${branch.x} ${BRANCH_HEIGHT}
|
||||
"/>
|
||||
`
|
||||
)}
|
||||
</svg>
|
||||
`
|
||||
: ""}
|
||||
<div id="branches">
|
||||
<svg id="lines" width="${this._totalWidth}" height="${this._maxHeight}">
|
||||
${this._branches.map((branch) => {
|
||||
if (branch.end) return "";
|
||||
return svg`
|
||||
<path
|
||||
class=${classMap({
|
||||
track: branch.track,
|
||||
})}
|
||||
d="
|
||||
M ${branch.x} ${branch.height}
|
||||
v ${this._maxHeight - branch.height}
|
||||
"/>
|
||||
`;
|
||||
})}
|
||||
</svg>
|
||||
<slot @slotchange=${this._updateBranches}></slot>
|
||||
</div>
|
||||
|
||||
${!this.short
|
||||
? svg`
|
||||
<svg
|
||||
id="bottom"
|
||||
width="${this._totalWidth}"
|
||||
>
|
||||
${this._branches.map((branch) => {
|
||||
if (branch.end) return "";
|
||||
return svg`
|
||||
<path
|
||||
class=${classMap({
|
||||
track: branch.track,
|
||||
})}
|
||||
d="
|
||||
M ${branch.x} 0
|
||||
V ${SPACING}
|
||||
L ${this._totalWidth / 2} ${BRANCH_HEIGHT + SPACING}
|
||||
"/>
|
||||
`;
|
||||
})}
|
||||
</svg>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
:host {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
:host(:focus) {
|
||||
outline: none;
|
||||
}
|
||||
#branches {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: start;
|
||||
}
|
||||
::slotted(*) {
|
||||
z-index: 1;
|
||||
}
|
||||
::slotted([slot="head"]) {
|
||||
margin-bottom: calc(var(--hat-graph-branch-height) / -2);
|
||||
}
|
||||
#lines {
|
||||
position: absolute;
|
||||
}
|
||||
#top {
|
||||
height: var(--hat-graph-branch-height);
|
||||
}
|
||||
#bottom {
|
||||
height: calc(var(--hat-graph-branch-height) + var(--hat-graph-spacing));
|
||||
}
|
||||
path {
|
||||
stroke: var(--stroke-clr);
|
||||
stroke-width: 2;
|
||||
fill: none;
|
||||
}
|
||||
path.track {
|
||||
stroke: var(--track-clr);
|
||||
}
|
||||
:host([disabled]) path {
|
||||
stroke: var(--disabled-clr);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hat-graph-branch": HatGraphBranch;
|
||||
}
|
||||
}
|
3
src/components/trace/hat-graph-const.ts
Normal file
3
src/components/trace/hat-graph-const.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const SPACING = 10;
|
||||
export const NODE_SIZE = 30;
|
||||
export const BRANCH_HEIGHT = 20;
|
@ -1,7 +1,18 @@
|
||||
import { css, LitElement, svg } from "lit";
|
||||
import {
|
||||
css,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
html,
|
||||
TemplateResult,
|
||||
svg,
|
||||
} from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { NODE_SIZE, SPACING } from "./hat-graph";
|
||||
import { NODE_SIZE, SPACING } from "./hat-graph-const";
|
||||
|
||||
/**
|
||||
* @attribute active
|
||||
* @attribute track
|
||||
*/
|
||||
@customElement("hat-graph-node")
|
||||
export class HatGraphNode extends LitElement {
|
||||
@property() iconPath?: string;
|
||||
@ -10,31 +21,33 @@ export class HatGraphNode extends LitElement {
|
||||
|
||||
@property({ reflect: true, type: Boolean }) graphStart?: boolean;
|
||||
|
||||
@property({ reflect: true, type: Boolean }) nofocus?: boolean;
|
||||
@property({ reflect: true, type: Boolean, attribute: "nofocus" })
|
||||
noFocus = false;
|
||||
|
||||
@property({ reflect: true, type: Number }) badge?: number;
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (!this.hasAttribute("tabindex") && !this.nofocus)
|
||||
this.setAttribute("tabindex", "0");
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (changedProps.has("noFocus")) {
|
||||
if (!this.hasAttribute("tabindex") && !this.noFocus) {
|
||||
this.setAttribute("tabindex", "0");
|
||||
} else if (changedProps.get("noFocus") !== undefined && this.noFocus) {
|
||||
this.removeAttribute("tabindex");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
protected render(): TemplateResult {
|
||||
const height = NODE_SIZE + (this.graphStart ? 2 : SPACING + 1);
|
||||
const width = SPACING + NODE_SIZE;
|
||||
return svg`
|
||||
<svg
|
||||
width="${width}px"
|
||||
height="${height}px"
|
||||
viewBox="-${Math.ceil(width / 2)} -${
|
||||
this.graphStart
|
||||
? Math.ceil(height / 2)
|
||||
: Math.ceil((NODE_SIZE + SPACING * 2) / 2)
|
||||
} ${width} ${height}"
|
||||
>
|
||||
${
|
||||
this.graphStart
|
||||
return html`
|
||||
<svg
|
||||
width="${width}px"
|
||||
height="${height}px"
|
||||
viewBox="-${Math.ceil(width / 2)} -${this.graphStart
|
||||
? Math.ceil(height / 2)
|
||||
: Math.ceil((NODE_SIZE + SPACING * 2) / 2)} ${width} ${height}"
|
||||
>
|
||||
${this.graphStart
|
||||
? ``
|
||||
: svg`
|
||||
<path
|
||||
@ -45,41 +58,31 @@ export class HatGraphNode extends LitElement {
|
||||
"
|
||||
line-caps="round"
|
||||
/>
|
||||
`
|
||||
}
|
||||
<g class="node">
|
||||
<circle
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="${NODE_SIZE / 2}"
|
||||
/>
|
||||
}
|
||||
${
|
||||
this.badge
|
||||
? svg`
|
||||
`}
|
||||
<g class="node">
|
||||
<circle cx="0" cy="0" r=${NODE_SIZE / 2} />
|
||||
}
|
||||
${this.badge
|
||||
? svg`
|
||||
<g class="number">
|
||||
<circle
|
||||
cx="8"
|
||||
cy="${-NODE_SIZE / 2}"
|
||||
cy=${-NODE_SIZE / 2}
|
||||
r="8"
|
||||
></circle>
|
||||
<text
|
||||
x="8"
|
||||
y="${-NODE_SIZE / 2}"
|
||||
y=${-NODE_SIZE / 2}
|
||||
text-anchor="middle"
|
||||
alignment-baseline="middle"
|
||||
>${this.badge > 9 ? "9+" : this.badge}</text>
|
||||
</g>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
<g
|
||||
style="pointer-events: none"
|
||||
transform="translate(${-12} ${-12})"
|
||||
>
|
||||
${this.iconPath ? svg`<path class="icon" d="${this.iconPath}"/>` : ""}
|
||||
</g>
|
||||
</g>
|
||||
: ""}
|
||||
<g style="pointer-events: none" transform="translate(${-12} ${-12})">
|
||||
${this.iconPath ? svg`<path class="icon" d=${this.iconPath}/>` : ""}
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
`;
|
||||
}
|
||||
@ -90,11 +93,11 @@ export class HatGraphNode extends LitElement {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
:host(.track) {
|
||||
:host([track]) {
|
||||
--stroke-clr: var(--track-clr);
|
||||
--icon-clr: var(--default-icon-clr);
|
||||
}
|
||||
:host(.active) circle {
|
||||
:host([active]) circle {
|
||||
--stroke-clr: var(--active-clr);
|
||||
--icon-clr: var(--default-icon-clr);
|
||||
}
|
||||
@ -111,7 +114,7 @@ export class HatGraphNode extends LitElement {
|
||||
:host-context([disabled]) {
|
||||
--stroke-clr: var(--disabled-clr);
|
||||
}
|
||||
:host([nofocus]):host-context(.active),
|
||||
:host([nofocus]):host-context([active]),
|
||||
:host([nofocus]):host-context(:focus) {
|
||||
--circle-clr: var(--active-clr);
|
||||
--icon-clr: var(--default-icon-clr);
|
||||
@ -137,24 +140,6 @@ export class HatGraphNode extends LitElement {
|
||||
path.icon {
|
||||
fill: var(--icon-clr);
|
||||
}
|
||||
|
||||
:host(.triggered) svg {
|
||||
overflow: visible;
|
||||
}
|
||||
:host(.triggered) circle {
|
||||
animation: glow 10s;
|
||||
}
|
||||
@keyframes glow {
|
||||
0% {
|
||||
filter: drop-shadow(0px 0px 5px rgba(var(--rgb-trigger-color), 0));
|
||||
}
|
||||
10% {
|
||||
filter: drop-shadow(0px 0px 10px rgba(var(--rgb-trigger-color), 1));
|
||||
}
|
||||
100% {
|
||||
filter: drop-shadow(0px 0px 5px rgba(var(--rgb-trigger-color), 0));
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,26 @@
|
||||
import { css, LitElement, svg } from "lit";
|
||||
import { css, LitElement, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { NODE_SIZE, SPACING } from "./hat-graph";
|
||||
import { SPACING, NODE_SIZE } from "./hat-graph-const";
|
||||
|
||||
/**
|
||||
* @attribute active
|
||||
* @attribute track
|
||||
*/
|
||||
@customElement("hat-graph-spacer")
|
||||
export class HatGraphSpacer extends LitElement {
|
||||
@property({ reflect: true, type: Boolean }) disabled?: boolean;
|
||||
|
||||
render() {
|
||||
return svg`
|
||||
<svg
|
||||
width="${SPACING}px"
|
||||
height="${SPACING + NODE_SIZE + 1}px"
|
||||
viewBox="-${SPACING / 2} 0 10 ${SPACING + NODE_SIZE + 1}"
|
||||
>
|
||||
<path
|
||||
class="connector"
|
||||
d="
|
||||
return html`
|
||||
<svg viewBox="-${SPACING / 2} 0 10 ${SPACING + NODE_SIZE + 1}">
|
||||
<path
|
||||
d="
|
||||
M 0 ${SPACING + NODE_SIZE + 1}
|
||||
L 0 0
|
||||
V 0
|
||||
"
|
||||
line-caps="round"
|
||||
/>
|
||||
}
|
||||
line-caps="round"
|
||||
/>
|
||||
}
|
||||
</svg>
|
||||
`;
|
||||
}
|
||||
@ -31,15 +30,21 @@ export class HatGraphSpacer extends LitElement {
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
:host(.track) {
|
||||
svg {
|
||||
width: var(--hat-graph-spacing);
|
||||
height: calc(
|
||||
var(--hat-graph-spacing) + var(--hat-graph-node-size) + 1px
|
||||
);
|
||||
}
|
||||
:host([track]) {
|
||||
--stroke-clr: var(--track-clr);
|
||||
--icon-clr: var(--default-icon-clr);
|
||||
}
|
||||
:host-context([disabled]) {
|
||||
--stroke-clr: var(--disabled-clr);
|
||||
}
|
||||
path.connector {
|
||||
path {
|
||||
stroke: var(--stroke-clr);
|
||||
stroke-width: 2;
|
||||
fill: none;
|
||||
|
@ -1,219 +0,0 @@
|
||||
import { css, html, LitElement, svg } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
|
||||
export const BRANCH_HEIGHT = 20;
|
||||
export const SPACING = 10;
|
||||
export const NODE_SIZE = 30;
|
||||
|
||||
const track_converter = {
|
||||
fromAttribute: (value) => value.split(",").map((v) => parseInt(v)),
|
||||
toAttribute: (value) =>
|
||||
value instanceof Array ? value.join(",") : `${value}`,
|
||||
};
|
||||
|
||||
export interface NodeInfo {
|
||||
path: string;
|
||||
config: any;
|
||||
}
|
||||
|
||||
interface BranchConfig {
|
||||
x: number;
|
||||
height: number;
|
||||
start: boolean;
|
||||
end: boolean;
|
||||
}
|
||||
|
||||
@customElement("hat-graph")
|
||||
export class HatGraph extends LitElement {
|
||||
@property({ type: Number }) _num_items = 0;
|
||||
|
||||
@property({ reflect: true, type: Boolean }) branching?: boolean;
|
||||
|
||||
@property({ converter: track_converter })
|
||||
track_start?: number[];
|
||||
|
||||
@property({ converter: track_converter }) track_end?: number[];
|
||||
|
||||
@property({ reflect: true, type: Boolean }) disabled?: boolean;
|
||||
|
||||
@property({ type: Boolean }) selected?: boolean;
|
||||
|
||||
@property({ type: Boolean }) short = false;
|
||||
|
||||
async updateChildren() {
|
||||
this._num_items = this.children.length;
|
||||
}
|
||||
|
||||
render() {
|
||||
const branches: BranchConfig[] = [];
|
||||
let total_width = 0;
|
||||
let max_height = 0;
|
||||
let min_height = Number.POSITIVE_INFINITY;
|
||||
if (this.branching) {
|
||||
for (const c of Array.from(this.children)) {
|
||||
if (c.slot === "head") continue;
|
||||
const rect = c.getBoundingClientRect();
|
||||
branches.push({
|
||||
x: rect.width / 2 + total_width,
|
||||
height: rect.height,
|
||||
start: c.getAttribute("graphStart") != null,
|
||||
end: c.getAttribute("graphEnd") != null,
|
||||
});
|
||||
total_width += rect.width;
|
||||
max_height = Math.max(max_height, rect.height);
|
||||
min_height = Math.min(min_height, rect.height);
|
||||
}
|
||||
}
|
||||
|
||||
return html`
|
||||
<slot name="head" @slotchange=${this.updateChildren}> </slot>
|
||||
${this.branching && branches.some((branch) => !branch.start)
|
||||
? 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,
|
||||
track: this.track_start?.includes(i) ?? false,
|
||||
})}"
|
||||
id="${this.track_start?.includes(i) ? "track-start" : ""}"
|
||||
index=${i}
|
||||
d="
|
||||
M ${total_width / 2} 0
|
||||
L ${branch.x} ${BRANCH_HEIGHT}
|
||||
"/>
|
||||
`;
|
||||
})}
|
||||
<use xlink:href="#track-start" />
|
||||
</svg>
|
||||
`
|
||||
: ""}
|
||||
<div id="branches">
|
||||
${this.branching
|
||||
? 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,
|
||||
track: this.track_end?.includes(i) ?? false,
|
||||
})}"
|
||||
index=${i}
|
||||
d="
|
||||
M ${branch.x} ${branch.height}
|
||||
l 0 ${max_height - branch.height}
|
||||
"/>
|
||||
`;
|
||||
})}
|
||||
</svg>
|
||||
`
|
||||
: ""}
|
||||
<slot @slotchange=${this.updateChildren}></slot>
|
||||
</div>
|
||||
|
||||
${this.branching && !this.short
|
||||
? svg`
|
||||
<svg
|
||||
id="bottom"
|
||||
width="${total_width}"
|
||||
height="${BRANCH_HEIGHT + SPACING}"
|
||||
>
|
||||
${branches.map((branch, i) => {
|
||||
if (branch.end) return "";
|
||||
return svg`
|
||||
<path
|
||||
class="${classMap({
|
||||
line: true,
|
||||
track: this.track_end?.includes(i) ?? false,
|
||||
})}"
|
||||
id="${this.track_end?.includes(i) ? "track-end" : ""}"
|
||||
index=${i}
|
||||
d="
|
||||
M ${branch.x} 0
|
||||
L ${branch.x} ${SPACING}
|
||||
L ${total_width / 2} ${BRANCH_HEIGHT + SPACING}
|
||||
"/>
|
||||
`;
|
||||
})}
|
||||
<use xlink:href="#track-end" />
|
||||
</svg>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
:host {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
--stroke-clr: var(--stroke-color, var(--secondary-text-color));
|
||||
--active-clr: var(--active-color, var(--primary-color));
|
||||
--track-clr: var(--track-color, var(--accent-color));
|
||||
--hover-clr: var(--hover-color, var(--primary-color));
|
||||
--disabled-clr: var(--disabled-color, var(--disabled-text-color));
|
||||
--default-trigger-color: 3, 169, 244;
|
||||
--rgb-trigger-color: var(--trigger-color, var(--default-trigger-color));
|
||||
--background-clr: var(--background-color, white);
|
||||
--default-icon-clr: var(--icon-color, black);
|
||||
--icon-clr: var(--stroke-clr);
|
||||
}
|
||||
:host(:focus) {
|
||||
outline: none;
|
||||
}
|
||||
#branches {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
:host([branching]) #branches {
|
||||
flex-direction: row;
|
||||
align-items: start;
|
||||
}
|
||||
:host([branching]) ::slotted(*) {
|
||||
z-index: 1;
|
||||
}
|
||||
:host([branching]) ::slotted([slot="head"]) {
|
||||
margin-bottom: ${-BRANCH_HEIGHT / 2}px;
|
||||
}
|
||||
|
||||
#lines {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
path.line {
|
||||
stroke: var(--stroke-clr);
|
||||
stroke-width: 2;
|
||||
fill: none;
|
||||
}
|
||||
path.line.track {
|
||||
stroke: var(--track-clr);
|
||||
}
|
||||
:host([disabled]) path.line {
|
||||
stroke: var(--disabled-clr);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hat-graph": HatGraph;
|
||||
}
|
||||
}
|
@ -19,7 +19,6 @@ import {
|
||||
} from "@mdi/js";
|
||||
import { css, html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { ensureArray } from "../../common/ensure-array";
|
||||
import { Condition, Trigger } from "../../data/automation";
|
||||
@ -41,9 +40,15 @@ import {
|
||||
TraceExtended,
|
||||
} from "../../data/trace";
|
||||
import "../ha-svg-icon";
|
||||
import { NodeInfo, NODE_SIZE, SPACING } from "./hat-graph";
|
||||
import "./hat-graph-node";
|
||||
import "./hat-graph-spacer";
|
||||
import "./hat-graph-branch";
|
||||
import { NODE_SIZE, SPACING, BRANCH_HEIGHT } from "./hat-graph-const";
|
||||
|
||||
export interface NodeInfo {
|
||||
path: string;
|
||||
config: any;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
@ -52,14 +57,14 @@ declare global {
|
||||
}
|
||||
|
||||
@customElement("hat-script-graph")
|
||||
class HatScriptGraph extends LitElement {
|
||||
export class HatScriptGraph extends LitElement {
|
||||
@property({ attribute: false }) public trace!: TraceExtended;
|
||||
|
||||
@property({ attribute: false }) public selected;
|
||||
@property({ attribute: false }) public selected?: string;
|
||||
|
||||
@property() renderedNodes: Record<string, any> = {};
|
||||
public renderedNodes: Record<string, NodeInfo> = {};
|
||||
|
||||
@property() trackedNodes: Record<string, any> = {};
|
||||
public trackedNodes: Record<string, NodeInfo> = {};
|
||||
|
||||
private selectNode(config, path) {
|
||||
return () => {
|
||||
@ -69,72 +74,54 @@ class HatScriptGraph extends LitElement {
|
||||
|
||||
private render_trigger(config: Trigger, i: number) {
|
||||
const path = `trigger/${i}`;
|
||||
const tracked = this.trace && path in this.trace.trace;
|
||||
const track = this.trace && path in this.trace.trace;
|
||||
this.renderedNodes[path] = { config, path };
|
||||
if (tracked) {
|
||||
if (track) {
|
||||
this.trackedNodes[path] = this.renderedNodes[path];
|
||||
}
|
||||
return html`
|
||||
<hat-graph-node
|
||||
graphStart
|
||||
?track=${track}
|
||||
@focus=${this.selectNode(config, path)}
|
||||
class=${classMap({
|
||||
track: tracked,
|
||||
active: this.selected === path,
|
||||
})}
|
||||
?active=${this.selected === path}
|
||||
.iconPath=${mdiAsterisk}
|
||||
tabindex=${tracked ? "0" : "-1"}
|
||||
tabindex=${track ? "0" : "-1"}
|
||||
></hat-graph-node>
|
||||
`;
|
||||
}
|
||||
|
||||
private render_condition(config: Condition, i: number) {
|
||||
const path = `condition/${i}`;
|
||||
const trace = this.trace.trace[path] as ConditionTraceStep[] | undefined;
|
||||
const track_path =
|
||||
trace?.[0].result === undefined ? 0 : trace[0].result.result ? 1 : 2;
|
||||
this.renderedNodes[path] = { config, path };
|
||||
if (trace) {
|
||||
if (this.trace && path in this.trace.trace) {
|
||||
this.trackedNodes[path] = this.renderedNodes[path];
|
||||
}
|
||||
return html`
|
||||
<hat-graph
|
||||
branching
|
||||
@focus=${this.selectNode(config, path)}
|
||||
class=${classMap({
|
||||
track: track_path,
|
||||
active: this.selected === path,
|
||||
})}
|
||||
.track_start=${[track_path]}
|
||||
.track_end=${[track_path]}
|
||||
tabindex=${trace ? "-1" : "0"}
|
||||
short
|
||||
>
|
||||
<hat-graph-node
|
||||
slot="head"
|
||||
class=${classMap({
|
||||
track: trace !== undefined,
|
||||
})}
|
||||
.iconPath=${mdiAbTesting}
|
||||
nofocus
|
||||
graphEnd
|
||||
></hat-graph-node>
|
||||
<div
|
||||
style=${`width: ${NODE_SIZE + SPACING}px;`}
|
||||
graphStart
|
||||
graphEnd
|
||||
></div>
|
||||
<div></div>
|
||||
<hat-graph-node
|
||||
.iconPath=${mdiClose}
|
||||
graphEnd
|
||||
nofocus
|
||||
class=${classMap({
|
||||
track: track_path === 2,
|
||||
})}
|
||||
></hat-graph-node>
|
||||
</hat-graph>
|
||||
`;
|
||||
return this.render_condition_node(config, path);
|
||||
}
|
||||
|
||||
private typeRenderers = {
|
||||
condition: this.render_condition_node,
|
||||
delay: this.render_delay_node,
|
||||
event: this.render_event_node,
|
||||
scene: this.render_scene_node,
|
||||
service: this.render_service_node,
|
||||
wait_template: this.render_wait_node,
|
||||
wait_for_trigger: this.render_wait_node,
|
||||
repeat: this.render_repeat_node,
|
||||
choose: this.render_choose_node,
|
||||
device_id: this.render_device_node,
|
||||
other: this.render_other_node,
|
||||
};
|
||||
|
||||
private render_action_node(node: Action, path: string, graphStart = false) {
|
||||
const type =
|
||||
Object.keys(this.typeRenderers).find((key) => key in node) || "other";
|
||||
this.renderedNodes[path] = { config: node, path };
|
||||
if (this.trace && path in this.trace.trace) {
|
||||
this.trackedNodes[path] = this.renderedNodes[path];
|
||||
}
|
||||
return this.typeRenderers[type].bind(this)(node, path, graphStart);
|
||||
}
|
||||
|
||||
private render_choose_node(
|
||||
@ -143,30 +130,25 @@ class HatScriptGraph extends LitElement {
|
||||
graphStart = false
|
||||
) {
|
||||
const trace = this.trace.trace[path] as ChooseActionTraceStep[] | undefined;
|
||||
const trace_path =
|
||||
trace !== undefined
|
||||
? trace[0].result === undefined || trace[0].result.choice === "default"
|
||||
? [Array.isArray(config.choose) ? config.choose.length : 0]
|
||||
: [trace[0].result.choice]
|
||||
: [];
|
||||
const trace_path = trace
|
||||
? trace.map((trc) =>
|
||||
trc.result === undefined || trc.result.choice === "default"
|
||||
? "default"
|
||||
: trc.result.choice
|
||||
)
|
||||
: [];
|
||||
const track_default = trace_path.includes("default");
|
||||
return html`
|
||||
<hat-graph
|
||||
<hat-graph-branch
|
||||
tabindex=${trace === undefined ? "-1" : "0"}
|
||||
branching
|
||||
.track_start=${trace_path}
|
||||
.track_end=${trace_path}
|
||||
@focus=${this.selectNode(config, path)}
|
||||
class=${classMap({
|
||||
track: trace !== undefined,
|
||||
active: this.selected === path,
|
||||
})}
|
||||
?track=${trace !== undefined}
|
||||
?active=${this.selected === path}
|
||||
>
|
||||
<hat-graph-node
|
||||
.graphStart=${graphStart}
|
||||
.iconPath=${mdiCallSplit}
|
||||
class=${classMap({
|
||||
track: trace !== undefined,
|
||||
})}
|
||||
?track=${trace !== undefined}
|
||||
slot="head"
|
||||
nofocus
|
||||
></hat-graph-node>
|
||||
@ -174,46 +156,39 @@ class HatScriptGraph extends LitElement {
|
||||
${config.choose
|
||||
? ensureArray(config.choose)?.map((branch, i) => {
|
||||
const branch_path = `${path}/choose/${i}`;
|
||||
const track_this =
|
||||
trace !== undefined && trace[0].result?.choice === i;
|
||||
const track_this = trace_path.includes(i);
|
||||
this.renderedNodes[branch_path] = { config, path: branch_path };
|
||||
if (track_this) {
|
||||
this.trackedNodes[branch_path] =
|
||||
this.renderedNodes[branch_path];
|
||||
}
|
||||
return html`
|
||||
<hat-graph>
|
||||
<div class="graph-container" ?track=${track_this}>
|
||||
<hat-graph-node
|
||||
.iconPath=${!trace || track_this
|
||||
? mdiCheckBoxOutline
|
||||
: mdiCheckboxBlankOutline}
|
||||
@focus=${this.selectNode(config, branch_path)}
|
||||
class=${classMap({
|
||||
active: this.selected === branch_path,
|
||||
track: track_this,
|
||||
})}
|
||||
?track=${track_this}
|
||||
?active=${this.selected === branch_path}
|
||||
></hat-graph-node>
|
||||
${ensureArray(branch.sequence).map((action, j) =>
|
||||
this.render_node(action, `${branch_path}/sequence/${j}`)
|
||||
this.render_action_node(
|
||||
action,
|
||||
`${branch_path}/sequence/${j}`
|
||||
)
|
||||
)}
|
||||
</hat-graph>
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
: ""}
|
||||
<hat-graph>
|
||||
<hat-graph-spacer
|
||||
class=${classMap({
|
||||
track:
|
||||
trace !== undefined &&
|
||||
(trace[0].result === undefined ||
|
||||
trace[0].result.choice === "default"),
|
||||
})}
|
||||
></hat-graph-spacer>
|
||||
<div ?track=${track_default}>
|
||||
<hat-graph-spacer ?track=${track_default}></hat-graph-spacer>
|
||||
${ensureArray(config.default)?.map((action, i) =>
|
||||
this.render_node(action, `${path}/default/${i}`)
|
||||
this.render_action_node(action, `${path}/default/${i}`)
|
||||
)}
|
||||
</hat-graph>
|
||||
</hat-graph>
|
||||
</div>
|
||||
</hat-graph-branch>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -222,41 +197,52 @@ class HatScriptGraph extends LitElement {
|
||||
path: string,
|
||||
graphStart = false
|
||||
) {
|
||||
const trace = (this.trace.trace[path] as ConditionTraceStep[]) || undefined;
|
||||
const track_path =
|
||||
trace?.[0].result === undefined ? 0 : trace[0].result.result ? 1 : 2;
|
||||
const trace = this.trace.trace[path] as ConditionTraceStep[] | undefined;
|
||||
let track = false;
|
||||
let trackPass = false;
|
||||
let trackFailed = false;
|
||||
if (trace) {
|
||||
for (const trc of trace) {
|
||||
if (trc.result) {
|
||||
track = true;
|
||||
if (trc.result.result) {
|
||||
trackPass = true;
|
||||
} else {
|
||||
trackFailed = true;
|
||||
}
|
||||
}
|
||||
if (trackPass && trackFailed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return html`
|
||||
<hat-graph
|
||||
branching
|
||||
<hat-graph-branch
|
||||
@focus=${this.selectNode(node, path)}
|
||||
class=${classMap({
|
||||
track: track_path,
|
||||
active: this.selected === path,
|
||||
})}
|
||||
.track_start=${[track_path]}
|
||||
.track_end=${[track_path]}
|
||||
?track=${track}
|
||||
?active=${this.selected === path}
|
||||
tabindex=${trace === undefined ? "-1" : "0"}
|
||||
short
|
||||
>
|
||||
<hat-graph-node
|
||||
.graphStart=${graphStart}
|
||||
slot="head"
|
||||
class=${classMap({
|
||||
track: Boolean(trace),
|
||||
})}
|
||||
?track=${track}
|
||||
.iconPath=${mdiAbTesting}
|
||||
nofocus
|
||||
></hat-graph-node>
|
||||
<div style=${`width: ${NODE_SIZE + SPACING}px;`}></div>
|
||||
<div></div>
|
||||
<div
|
||||
style=${`width: ${NODE_SIZE + SPACING}px;`}
|
||||
graphStart
|
||||
graphEnd
|
||||
></div>
|
||||
<div ?track=${trackPass}></div>
|
||||
<hat-graph-node
|
||||
.iconPath=${mdiClose}
|
||||
nofocus
|
||||
class=${classMap({
|
||||
track: track_path === 2,
|
||||
})}
|
||||
?track=${trackFailed}
|
||||
></hat-graph-node>
|
||||
</hat-graph>
|
||||
</hat-graph-branch>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -270,10 +256,8 @@ class HatScriptGraph extends LitElement {
|
||||
.graphStart=${graphStart}
|
||||
.iconPath=${mdiTimerOutline}
|
||||
@focus=${this.selectNode(node, path)}
|
||||
class=${classMap({
|
||||
track: path in this.trace.trace,
|
||||
active: this.selected === path,
|
||||
})}
|
||||
?track=${path in this.trace.trace}
|
||||
?active=${this.selected === path}
|
||||
tabindex=${this.trace && path in this.trace.trace ? "0" : "-1"}
|
||||
></hat-graph-node>
|
||||
`;
|
||||
@ -289,10 +273,8 @@ class HatScriptGraph extends LitElement {
|
||||
.graphStart=${graphStart}
|
||||
.iconPath=${mdiDevices}
|
||||
@focus=${this.selectNode(node, path)}
|
||||
class=${classMap({
|
||||
track: path in this.trace.trace,
|
||||
active: this.selected === path,
|
||||
})}
|
||||
?track=${path in this.trace.trace}
|
||||
?active=${this.selected === path}
|
||||
tabindex=${this.trace && path in this.trace.trace ? "0" : "-1"}
|
||||
></hat-graph-node>
|
||||
`;
|
||||
@ -308,10 +290,8 @@ class HatScriptGraph extends LitElement {
|
||||
.graphStart=${graphStart}
|
||||
.iconPath=${mdiExclamation}
|
||||
@focus=${this.selectNode(node, path)}
|
||||
class=${classMap({
|
||||
track: path in this.trace.trace,
|
||||
active: this.selected === path,
|
||||
})}
|
||||
?track=${path in this.trace.trace}
|
||||
?active=${this.selected === path}
|
||||
tabindex=${this.trace && path in this.trace.trace ? "0" : "-1"}
|
||||
></hat-graph-node>
|
||||
`;
|
||||
@ -323,43 +303,33 @@ class HatScriptGraph extends LitElement {
|
||||
graphStart = false
|
||||
) {
|
||||
const trace: any = this.trace.trace[path];
|
||||
const track_path = trace ? [0, 1] : [];
|
||||
const repeats = this.trace?.trace[`${path}/repeat/sequence/0`]?.length;
|
||||
return html`
|
||||
<hat-graph
|
||||
.track_start=${track_path}
|
||||
.track_end=${track_path}
|
||||
<hat-graph-branch
|
||||
tabindex=${trace === undefined ? "-1" : "0"}
|
||||
branching
|
||||
@focus=${this.selectNode(node, path)}
|
||||
class=${classMap({
|
||||
track: path in this.trace.trace,
|
||||
active: this.selected === path,
|
||||
})}
|
||||
?track=${path in this.trace.trace}
|
||||
?active=${this.selected === path}
|
||||
>
|
||||
<hat-graph-node
|
||||
.graphStart=${graphStart}
|
||||
.iconPath=${mdiRefresh}
|
||||
class=${classMap({
|
||||
track: trace,
|
||||
})}
|
||||
?track=${path in this.trace.trace}
|
||||
slot="head"
|
||||
nofocus
|
||||
></hat-graph-node>
|
||||
<hat-graph-node
|
||||
.iconPath=${mdiArrowUp}
|
||||
?track=${repeats > 1}
|
||||
nofocus
|
||||
class=${classMap({
|
||||
track: track_path.includes(1),
|
||||
})}
|
||||
.badge=${repeats}
|
||||
.badge=${repeats > 1 ? repeats : undefined}
|
||||
></hat-graph-node>
|
||||
<hat-graph>
|
||||
<div ?track=${trace}>
|
||||
${ensureArray(node.repeat.sequence).map((action, i) =>
|
||||
this.render_node(action, `${path}/repeat/sequence/${i}`)
|
||||
this.render_action_node(action, `${path}/repeat/sequence/${i}`)
|
||||
)}
|
||||
</hat-graph>
|
||||
</hat-graph>
|
||||
</div>
|
||||
</hat-graph-branch>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -373,10 +343,8 @@ class HatScriptGraph extends LitElement {
|
||||
.graphStart=${graphStart}
|
||||
.iconPath=${mdiExclamation}
|
||||
@focus=${this.selectNode(node, path)}
|
||||
class=${classMap({
|
||||
track: path in this.trace.trace,
|
||||
active: this.selected === path,
|
||||
})}
|
||||
?track=${path in this.trace.trace}
|
||||
?active=${this.selected === path}
|
||||
tabindex=${this.trace && path in this.trace.trace ? "0" : "-1"}
|
||||
></hat-graph-node>
|
||||
`;
|
||||
@ -392,10 +360,8 @@ class HatScriptGraph extends LitElement {
|
||||
.graphStart=${graphStart}
|
||||
.iconPath=${mdiChevronRight}
|
||||
@focus=${this.selectNode(node, path)}
|
||||
class=${classMap({
|
||||
track: path in this.trace.trace,
|
||||
active: this.selected === path,
|
||||
})}
|
||||
?track=${path in this.trace.trace}
|
||||
?active=${this.selected === path}
|
||||
tabindex=${this.trace && path in this.trace.trace ? "0" : "-1"}
|
||||
></hat-graph-node>
|
||||
`;
|
||||
@ -411,10 +377,8 @@ class HatScriptGraph extends LitElement {
|
||||
.graphStart=${graphStart}
|
||||
.iconPath=${mdiTrafficLight}
|
||||
@focus=${this.selectNode(node, path)}
|
||||
class=${classMap({
|
||||
track: path in this.trace.trace,
|
||||
active: this.selected === path,
|
||||
})}
|
||||
?track=${path in this.trace.trace}
|
||||
?active=${this.selected === path}
|
||||
tabindex=${this.trace && path in this.trace.trace ? "0" : "-1"}
|
||||
></hat-graph-node>
|
||||
`;
|
||||
@ -426,95 +390,55 @@ class HatScriptGraph extends LitElement {
|
||||
.graphStart=${graphStart}
|
||||
.iconPath=${mdiCodeBrackets}
|
||||
@focus=${this.selectNode(node, path)}
|
||||
class=${classMap({
|
||||
track: path in this.trace.trace,
|
||||
active: this.selected === path,
|
||||
})}
|
||||
?track=${path in this.trace.trace}
|
||||
?active=${this.selected === path}
|
||||
></hat-graph-node>
|
||||
`;
|
||||
}
|
||||
|
||||
private render_node(node: Action, path: string, graphStart = false) {
|
||||
const NODE_TYPES = {
|
||||
choose: this.render_choose_node,
|
||||
condition: this.render_condition_node,
|
||||
delay: this.render_delay_node,
|
||||
device_id: this.render_device_node,
|
||||
event: this.render_event_node,
|
||||
repeat: this.render_repeat_node,
|
||||
scene: this.render_scene_node,
|
||||
service: this.render_service_node,
|
||||
wait_template: this.render_wait_node,
|
||||
wait_for_trigger: this.render_wait_node,
|
||||
other: this.render_other_node,
|
||||
};
|
||||
|
||||
const type = Object.keys(NODE_TYPES).find((key) => key in node) || "other";
|
||||
const nodeEl = NODE_TYPES[type].bind(this)(node, path, graphStart);
|
||||
this.renderedNodes[path] = { config: node, path };
|
||||
if (this.trace && path in this.trace.trace) {
|
||||
this.trackedNodes[path] = this.renderedNodes[path];
|
||||
}
|
||||
return nodeEl;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const paths = Object.keys(this.trackedNodes);
|
||||
const manual_triggered = this.trace && "trigger" in this.trace.trace;
|
||||
let track_path = manual_triggered ? undefined : [0];
|
||||
const trigger_nodes =
|
||||
"trigger" in this.trace.config
|
||||
? ensureArray(this.trace.config.trigger).map((trigger, i) => {
|
||||
if (this.trace && `trigger/${i}` in this.trace.trace) {
|
||||
track_path = [i];
|
||||
}
|
||||
return this.render_trigger(trigger, i);
|
||||
})
|
||||
? ensureArray(this.trace.config.trigger).map((trigger, i) =>
|
||||
this.render_trigger(trigger, i)
|
||||
)
|
||||
: undefined;
|
||||
try {
|
||||
return html`
|
||||
<hat-graph class="parent">
|
||||
<div></div>
|
||||
<div class="parent graph-container">
|
||||
${trigger_nodes
|
||||
? html`<hat-graph
|
||||
branching
|
||||
id="trigger"
|
||||
.short=${trigger_nodes.length < 2}
|
||||
.track_start=${track_path}
|
||||
.track_end=${track_path}
|
||||
>
|
||||
? html`<hat-graph-branch start .short=${trigger_nodes.length < 2}>
|
||||
${trigger_nodes}
|
||||
</hat-graph>`
|
||||
</hat-graph-branch>`
|
||||
: ""}
|
||||
${"condition" in this.trace.config
|
||||
? html`<hat-graph id="condition">
|
||||
${ensureArray(this.trace.config.condition)?.map(
|
||||
(condition, i) => this.render_condition(condition!, i)
|
||||
)}
|
||||
</hat-graph>`
|
||||
? html`${ensureArray(this.trace.config.condition)?.map(
|
||||
(condition, i) => this.render_condition(condition, i)
|
||||
)}`
|
||||
: ""}
|
||||
${"action" in this.trace.config
|
||||
? html`${ensureArray(this.trace.config.action).map((action, i) =>
|
||||
this.render_node(action, `action/${i}`)
|
||||
this.render_action_node(action, `action/${i}`)
|
||||
)}`
|
||||
: ""}
|
||||
${"sequence" in this.trace.config
|
||||
? html`${ensureArray(this.trace.config.sequence).map((action, i) =>
|
||||
this.render_node(action, `sequence/${i}`, i === 0)
|
||||
this.render_action_node(action, `sequence/${i}`, i === 0)
|
||||
)}`
|
||||
: ""}
|
||||
</hat-graph>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<mwc-icon-button
|
||||
.disabled=${paths.length === 0 || paths[0] === this.selected}
|
||||
@click=${this.previousTrackedNode}
|
||||
@click=${this._previousTrackedNode}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiChevronUp}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-icon-button
|
||||
.disabled=${paths.length === 0 ||
|
||||
paths[paths.length - 1] === this.selected}
|
||||
@click=${this.nextTrackedNode}
|
||||
@click=${this._nextTrackedNode}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiChevronDown}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
@ -545,44 +469,39 @@ class HatScriptGraph extends LitElement {
|
||||
protected updated(changedProps: PropertyValues<this>) {
|
||||
super.updated(changedProps);
|
||||
|
||||
// Select first node if new trace loaded but no selection given.
|
||||
if (changedProps.has("trace")) {
|
||||
const tracked = this.trackedNodes;
|
||||
const paths = Object.keys(tracked);
|
||||
if (!changedProps.has("trace")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If trace changed and we have no or an invalid selection, select first option.
|
||||
if (this.selected === "" || !(this.selected in paths)) {
|
||||
// Find first tracked node with node info
|
||||
for (const path of paths) {
|
||||
if (tracked[path]) {
|
||||
fireEvent(this, "graph-node-selected", tracked[path]);
|
||||
break;
|
||||
}
|
||||
// If trace changed and we have no or an invalid selection, select first option.
|
||||
if (!this.selected || !(this.selected in this.trackedNodes)) {
|
||||
const firstNode = this.trackedNodes[Object.keys(this.trackedNodes)[0]];
|
||||
if (firstNode) {
|
||||
fireEvent(this, "graph-node-selected", firstNode);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.trace) {
|
||||
const sortKeys = Object.keys(this.trace.trace);
|
||||
const keys = Object.keys(this.renderedNodes).sort(
|
||||
(a, b) => sortKeys.indexOf(a) - sortKeys.indexOf(b)
|
||||
);
|
||||
const sortedTrackedNodes = {};
|
||||
const sortedRenderedNodes = {};
|
||||
for (const key of keys) {
|
||||
sortedRenderedNodes[key] = this.renderedNodes[key];
|
||||
if (key in this.trackedNodes) {
|
||||
sortedTrackedNodes[key] = this.trackedNodes[key];
|
||||
}
|
||||
}
|
||||
|
||||
if (this.trace) {
|
||||
const sortKeys = Object.keys(this.trace.trace);
|
||||
const keys = Object.keys(this.renderedNodes).sort(
|
||||
(a, b) => sortKeys.indexOf(a) - sortKeys.indexOf(b)
|
||||
);
|
||||
const sortedTrackedNodes = {};
|
||||
const sortedRenderedNodes = {};
|
||||
for (const key of keys) {
|
||||
sortedRenderedNodes[key] = this.renderedNodes[key];
|
||||
if (key in this.trackedNodes) {
|
||||
sortedTrackedNodes[key] = this.trackedNodes[key];
|
||||
}
|
||||
}
|
||||
this.renderedNodes = sortedRenderedNodes;
|
||||
this.trackedNodes = sortedTrackedNodes;
|
||||
}
|
||||
this.renderedNodes = sortedRenderedNodes;
|
||||
this.trackedNodes = sortedTrackedNodes;
|
||||
}
|
||||
}
|
||||
|
||||
public previousTrackedNode() {
|
||||
private _previousTrackedNode() {
|
||||
const nodes = Object.keys(this.trackedNodes);
|
||||
const prevIndex = nodes.indexOf(this.selected) - 1;
|
||||
const prevIndex = nodes.indexOf(this.selected!) - 1;
|
||||
if (prevIndex >= 0) {
|
||||
fireEvent(
|
||||
this,
|
||||
@ -592,9 +511,9 @@ class HatScriptGraph extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
public nextTrackedNode() {
|
||||
private _nextTrackedNode() {
|
||||
const nodes = Object.keys(this.trackedNodes);
|
||||
const nextIndex = nodes.indexOf(this.selected) + 1;
|
||||
const nextIndex = nodes.indexOf(this.selected!) + 1;
|
||||
if (nextIndex < nodes.length) {
|
||||
fireEvent(
|
||||
this,
|
||||
@ -608,6 +527,25 @@ class HatScriptGraph extends LitElement {
|
||||
return css`
|
||||
:host {
|
||||
display: flex;
|
||||
--stroke-clr: var(--stroke-color, var(--secondary-text-color));
|
||||
--active-clr: var(--active-color, var(--primary-color));
|
||||
--track-clr: var(--track-color, var(--accent-color));
|
||||
--hover-clr: var(--hover-color, var(--primary-color));
|
||||
--disabled-clr: var(--disabled-color, var(--disabled-text-color));
|
||||
--default-trigger-color: 3, 169, 244;
|
||||
--rgb-trigger-color: var(--trigger-color, var(--default-trigger-color));
|
||||
--background-clr: var(--background-color, white);
|
||||
--default-icon-clr: var(--icon-color, black);
|
||||
--icon-clr: var(--stroke-clr);
|
||||
|
||||
--hat-graph-spacing: ${SPACING}px;
|
||||
--hat-graph-node-size: ${NODE_SIZE}px;
|
||||
--hat-graph-branch-height: ${BRANCH_HEIGHT}px;
|
||||
}
|
||||
.graph-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
|
@ -6,12 +6,11 @@ import {
|
||||
mdiRefresh,
|
||||
} from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { formatDateTimeWithSeconds } from "../../../common/datetime/format_date_time";
|
||||
import type { NodeInfo } from "../../../components/trace/hat-graph";
|
||||
import "../../../components/trace/hat-script-graph";
|
||||
import { AutomationEntity } from "../../../data/automation";
|
||||
import { getLogbookDataForContext, LogbookEntry } from "../../../data/logbook";
|
||||
@ -31,6 +30,10 @@ import "../../../components/trace/ha-trace-logbook";
|
||||
import "../../../components/trace/ha-trace-path-details";
|
||||
import "../../../components/trace/ha-trace-timeline";
|
||||
import { traceTabStyles } from "../../../components/trace/trace-tab-styles";
|
||||
import type {
|
||||
HatScriptGraph,
|
||||
NodeInfo,
|
||||
} from "../../../components/trace/hat-script-graph";
|
||||
|
||||
@customElement("ha-automation-trace")
|
||||
export class HaAutomationTrace extends LitElement {
|
||||
@ -65,12 +68,14 @@ export class HaAutomationTrace extends LitElement {
|
||||
| "logbook"
|
||||
| "blueprint" = "details";
|
||||
|
||||
@query("hat-script-graph") private _graph?: HatScriptGraph;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const stateObj = this._entityId
|
||||
? this.hass.states[this._entityId]
|
||||
: undefined;
|
||||
|
||||
const graph = this.shadowRoot!.querySelector("hat-script-graph");
|
||||
const graph = this._graph;
|
||||
const trackedNodes = graph?.trackedNodes;
|
||||
const renderedNodes = graph?.renderedNodes;
|
||||
|
||||
@ -294,7 +299,6 @@ export class HaAutomationTrace extends LitElement {
|
||||
if (changedProps.has("_runId") && this._runId) {
|
||||
this._trace = undefined;
|
||||
this._logbookEntries = undefined;
|
||||
this.shadowRoot!.querySelector("select")!.value = this._runId;
|
||||
this._loadTrace();
|
||||
}
|
||||
|
||||
@ -427,14 +431,13 @@ export class HaAutomationTrace extends LitElement {
|
||||
this._logbookEntries = traceInfo.logbookEntries;
|
||||
}
|
||||
|
||||
private _showTab(ev) {
|
||||
private _showTab(ev: Event) {
|
||||
this._view = (ev.target as any).view;
|
||||
}
|
||||
|
||||
private _timelinePathPicked(ev) {
|
||||
private _timelinePathPicked(ev: CustomEvent) {
|
||||
const path = ev.detail.value;
|
||||
const nodes =
|
||||
this.shadowRoot!.querySelector("hat-script-graph")!.trackedNodes;
|
||||
const nodes = this._graph!.trackedNodes;
|
||||
if (nodes[path]) {
|
||||
this._selected = nodes[path];
|
||||
}
|
||||
|
@ -6,12 +6,11 @@ import {
|
||||
mdiRefresh,
|
||||
} from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { formatDateTimeWithSeconds } from "../../../common/datetime/format_date_time";
|
||||
import type { NodeInfo } from "../../../components/trace/hat-graph";
|
||||
import "../../../components/trace/hat-script-graph";
|
||||
import { getLogbookDataForContext, LogbookEntry } from "../../../data/logbook";
|
||||
import { ScriptEntity } from "../../../data/script";
|
||||
@ -31,6 +30,10 @@ import "../../../components/trace/ha-trace-config";
|
||||
import "../../../components/trace/ha-trace-logbook";
|
||||
import "../../../components/trace/ha-trace-path-details";
|
||||
import "../../../components/trace/ha-trace-timeline";
|
||||
import type {
|
||||
HatScriptGraph,
|
||||
NodeInfo,
|
||||
} from "../../../components/trace/hat-script-graph";
|
||||
|
||||
@customElement("ha-script-trace")
|
||||
export class HaScriptTrace extends LitElement {
|
||||
@ -63,12 +66,14 @@ export class HaScriptTrace extends LitElement {
|
||||
| "logbook"
|
||||
| "blueprint" = "details";
|
||||
|
||||
@query("hat-script-graph") private _graph?: HatScriptGraph;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const stateObj = this.scriptEntityId
|
||||
? this.hass.states[this.scriptEntityId]
|
||||
: undefined;
|
||||
|
||||
const graph = this.shadowRoot!.querySelector("hat-script-graph");
|
||||
const graph = this._graph;
|
||||
const trackedNodes = graph?.trackedNodes;
|
||||
const renderedNodes = graph?.renderedNodes;
|
||||
|
||||
@ -274,10 +279,10 @@ export class HaScriptTrace extends LitElement {
|
||||
this._loadTraces(params.get("run_id") || undefined);
|
||||
}
|
||||
|
||||
protected updated(changedProps) {
|
||||
super.updated(changedProps);
|
||||
public willUpdate(changedProps) {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
// Only reset if automationId has changed and we had one before.
|
||||
// Only reset if scriptEntityId has changed and we had one before.
|
||||
if (changedProps.get("scriptEntityId")) {
|
||||
this._traces = undefined;
|
||||
this._runId = undefined;
|
||||
@ -291,7 +296,6 @@ export class HaScriptTrace extends LitElement {
|
||||
if (changedProps.has("_runId") && this._runId) {
|
||||
this._trace = undefined;
|
||||
this._logbookEntries = undefined;
|
||||
this.shadowRoot!.querySelector("select")!.value = this._runId;
|
||||
this._loadTrace();
|
||||
}
|
||||
}
|
||||
@ -417,14 +421,13 @@ export class HaScriptTrace extends LitElement {
|
||||
this._logbookEntries = traceInfo.logbookEntries;
|
||||
}
|
||||
|
||||
private _showTab(ev) {
|
||||
private _showTab(ev: Event) {
|
||||
this._view = (ev.target as any).view;
|
||||
}
|
||||
|
||||
private _timelinePathPicked(ev) {
|
||||
private _timelinePathPicked(ev: CustomEvent) {
|
||||
const path = ev.detail.value;
|
||||
const nodes =
|
||||
this.shadowRoot!.querySelector("hat-script-graph")!.trackedNodes;
|
||||
const nodes = this._graph!.trackedNodes;
|
||||
if (nodes[path]) {
|
||||
this._selected = nodes[path];
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user