Compare commits

..

8 Commits

Author SHA1 Message Date
Wendelin
928ac33f94 Add touch area to control-switch 2026-04-30 11:19:51 +02:00
Bram Kragten
4d75ea5198 Add inline YAML linting to the yaml code editor (#51791) 2026-04-30 08:42:12 +00:00
Wendelin
ba3a63f856 Fix ha-select undefined value (#51800)
Fix ha-select undefined

Co-authored-by: Copilot <copilot@github.com>
2026-04-30 10:25:26 +03:00
renovate[bot]
fd25d38be6 Update dependency jsdom to v29.1.0 (#51798)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-30 10:18:18 +03:00
Wendelin
ac22374a00 Hide tooltip on mobile clients in ha-sidebar component (#51799) 2026-04-30 10:17:44 +03:00
AlCalzone
de529cc26b Expose Z-Wave exclusion instructions when removing device (#51788)
* Expose Z-Wave exclusion instructions when removing device

* text tweaks

* Apply suggestion from @MindFreeze

* Apply suggestions from code review

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>

* bring back comment

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-04-30 06:06:21 +00:00
Aidan Timson
126db3e8df Refactor events devtools tab layout and events output card (#51789)
* Only show events output when there are any, types and margin

* Refactor to use pagination

* Fix

* Simplify, remove pinning and auto-follow, stay on event 1 and allow the user to move around manually

* Show info why only 30 events are keps

* Increase bufffer limit to 100, add explainer and tip when rolls over

* Update disclaimer

* Use buffer position and total instead of event id + total in counter

* Use fixed height and constrain editor

* Cleanup

* Cleanup

* Fix narrow layouts
2026-04-30 08:42:30 +03:00
Matthias de Baat
ed6fd59968 Move preview device analytics button to card (#51787)
* Move preview device analytics button to card

* Add icon back
2026-04-29 17:36:06 +02:00
23 changed files with 730 additions and 2019 deletions

View File

@@ -33,6 +33,7 @@
"@codemirror/lang-jinja": "6.0.1",
"@codemirror/lang-yaml": "6.1.3",
"@codemirror/language": "6.12.3",
"@codemirror/lint": "6.9.5",
"@codemirror/search": "6.7.0",
"@codemirror/state": "6.6.0",
"@codemirror/view": "6.41.1",
@@ -184,7 +185,7 @@
"gulp-rename": "2.1.0",
"html-minifier-terser": "7.2.0",
"husky": "9.1.7",
"jsdom": "29.0.2",
"jsdom": "29.1.0",
"jszip": "3.10.1",
"lint-staged": "16.4.0",
"lit-analyzer": "2.0.3",

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20260429.1"
version = "20260429.0"
license = "Apache-2.0"
license-files = ["LICENSE*"]
description = "The Home Assistant frontend"

View File

@@ -166,6 +166,7 @@ export class HaEntityToggle extends LitElement {
ha-control-switch {
--control-switch-thickness: 20px;
--control-switch-off-color: var(--state-inactive-color);
--control-switch-touch-area-size: var(--ha-space-2);
}
ha-icon-button {
--ha-icon-button-size: 40px;

View File

@@ -1,42 +0,0 @@
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import type { CompletionItem } from "./ha-code-editor-completion-items";
import "./ha-code-editor-completion-items";
@customElement("ha-code-editor-jinja-arg-hover")
export class HaCodeEditorJinjaArgHover extends LitElement {
/** Bold heading shown above the items grid (e.g. entity/device/area name). */
@property({ attribute: false }) public heading?: string;
@property({ attribute: false }) public items: CompletionItem[] = [];
render() {
return html`
${this.heading
? html`<div class="heading">${this.heading}</div>`
: nothing}
<ha-code-editor-completion-items
.items=${this.items}
></ha-code-editor-completion-items>
`;
}
static styles = css`
:host {
display: block;
padding: 6px 10px;
max-width: 360px;
}
.heading {
font-weight: var(--ha-font-weight-bold);
margin-bottom: 4px;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-code-editor-jinja-arg-hover": HaCodeEditorJinjaArgHover;
}
}

View File

@@ -1,101 +0,0 @@
import type { Completion } from "@codemirror/autocomplete";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { mdiHelpCircleOutline } from "@mdi/js";
import "./ha-svg-icon";
@customElement("ha-code-editor-jinja-hover")
export class HaCodeEditorJinjaHover extends LitElement {
@property({ attribute: false }) public completion!: Completion;
@property({ attribute: false }) public docUrl?: string;
@property({ attribute: false }) public openDocumentation =
"Open documentation";
render() {
const info =
typeof this.completion.info === "string"
? this.completion.info
: undefined;
return html`
<div class="header">
<div class="sig">
<strong>${this.completion.label}</strong>
${this.completion.detail
? html`<span class="detail">(${this.completion.detail})</span>`
: nothing}
</div>
${this.docUrl
? html`<a
class="doc-link"
href=${this.docUrl}
target="_blank"
rel="noreferrer"
title=${this.openDocumentation}
><ha-svg-icon .path=${mdiHelpCircleOutline}></ha-svg-icon
></a>`
: nothing}
</div>
${info ? html`<div class="desc">${info}</div>` : nothing}
`;
}
static styles = css`
:host {
display: block;
padding: 6px 10px;
max-width: 360px;
line-height: 1.5;
}
.header {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 4px;
}
.sig {
font-family: var(--ha-font-family-code);
font-size: 0.9em;
flex: 1;
min-width: 0;
}
.detail {
color: var(--secondary-text-color);
}
.doc-link {
flex-shrink: 0;
display: inline-flex;
align-items: center;
color: var(--secondary-text-color);
opacity: 0.7;
line-height: 1;
}
.doc-link:hover {
opacity: 1;
color: var(--primary-color);
}
.doc-link ha-svg-icon {
width: 16px;
height: 16px;
}
.desc {
font-size: 0.9em;
color: var(--secondary-text-color);
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-code-editor-jinja-hover": HaCodeEditorJinjaHover;
}
}

View File

@@ -36,13 +36,9 @@ import { computeAreaName } from "../common/entity/compute_area_name";
import { computeFloorName } from "../common/entity/compute_floor_name";
import { copyToClipboard } from "../common/util/copy-clipboard";
import { haStyleScrollbar } from "../resources/styles";
import type {
JinjaArgType,
HassArgHoverContext,
} from "../resources/jinja_ha_completions";
import type { JinjaArgType } from "../resources/jinja_ha_completions";
import type { HomeAssistant } from "../types";
import { showToast } from "../util/toast";
import { documentationUrl } from "../util/documentation-url";
import { labelsContext } from "../data/context";
import type { LabelRegistryEntry } from "../data/label/label_registry";
import "./ha-code-editor-completion-items";
@@ -95,6 +91,8 @@ export class HaCodeEditor extends ReactiveElement {
@property({ type: Boolean }) public error = false;
@property({ type: Boolean }) public lint = false;
@property({ type: Boolean, attribute: "disable-fullscreen" })
public disableFullscreen = false;
@@ -163,6 +161,40 @@ export class HaCodeEditor extends ReactiveElement {
return !!this.renderRoot.querySelector(`span.${className}`);
}
/**
* Push a YAML parse error (or null to clear) into the lint gutter as a
* diagnostic. Avoids re-parsing the document — the caller (ha-yaml-editor)
* already has the error from its own js-yaml load() call.
*/
public setYamlError(
err: {
mark?: { position: number; line: number; column: number };
reason?: string;
} | null
): void {
if (!this.codemirror || !this._loadedCodeMirror) return;
let diagnostics: {
from: number;
to: number;
severity: "error";
message: string;
}[] = [];
if (err) {
const doc = this.codemirror.state.doc;
const pos = err.mark ? Math.min(err.mark.position, doc.length) : 0;
const line = doc.lineAt(pos);
const message = `${
err.reason ||
this.hass?.localize("ui.components.yaml-editor.error") ||
"YAML syntax error"
}${err.mark ? ` (${this.hass?.localize("ui.components.yaml-editor.error_location", { line: err.mark.line + 1, column: err.mark.column + 1 })})` : ""}`;
diagnostics = [{ from: pos, to: line.to, severity: "error", message }];
}
this.codemirror.dispatch(
this._loadedCodeMirror.setDiagnostics(this.codemirror.state, diagnostics)
);
}
public connectedCallback() {
super.connectedCallback();
this.classList.toggle("in-dialog", this.inDialog);
@@ -220,17 +252,38 @@ export class HaCodeEditor extends ReactiveElement {
transactions.push({
effects: [
this._loadedCodeMirror!.langCompartment!.reconfigure(this._mode),
this._loadedCodeMirror!.yamlLintCompartment!.reconfigure(
this.lint && !this.readOnly
? [this._loadedCodeMirror!.lintGutter()]
: []
),
],
});
}
if (changedProps.has("readOnly")) {
transactions.push({
effects: this._loadedCodeMirror!.readonlyCompartment!.reconfigure(
this._loadedCodeMirror!.EditorView!.editable.of(!this.readOnly)
),
effects: [
this._loadedCodeMirror!.readonlyCompartment!.reconfigure(
this._loadedCodeMirror!.EditorView!.editable.of(!this.readOnly)
),
this._loadedCodeMirror!.yamlLintCompartment!.reconfigure(
this.lint && !this.readOnly
? [this._loadedCodeMirror!.lintGutter()]
: []
),
],
});
this._updateToolbarButtons();
}
if (changedProps.has("lint")) {
transactions.push({
effects: this._loadedCodeMirror!.yamlLintCompartment!.reconfigure(
this.lint && !this.readOnly
? [this._loadedCodeMirror!.lintGutter()]
: []
),
});
}
if (changedProps.has("linewrap")) {
transactions.push({
effects: this._loadedCodeMirror!.linewrapCompartment!.reconfigure(
@@ -312,6 +365,7 @@ export class HaCodeEditor extends ReactiveElement {
...this._loadedCodeMirror.searchKeymap,
...this._loadedCodeMirror.historyKeymap,
...this._loadedCodeMirror.tabKeyBindings,
...this._loadedCodeMirror.lintKeymap,
saveKeyBinding,
]),
this._loadedCodeMirror.search({ top: true }),
@@ -326,20 +380,13 @@ export class HaCodeEditor extends ReactiveElement {
this._loadedCodeMirror.linewrapCompartment.of(
this.linewrap ? this._loadedCodeMirror.EditorView.lineWrapping : []
),
this._loadedCodeMirror.yamlLintCompartment.of(
this.lint && !this.readOnly ? [this._loadedCodeMirror.lintGutter()] : []
),
this._loadedCodeMirror.EditorView.updateListener.of(this._onUpdate),
this._loadedCodeMirror.tooltips({
position: "absolute",
}),
this._loadedCodeMirror.hoverTooltip(
(view, pos) =>
this._loadedCodeMirror!.haJinjaHoverSource(
view,
pos,
this.hass ? documentationUrl(this.hass, "") : undefined,
this.hass ? this._hassArgHoverContext() : undefined
),
{ hoverTime: 300 }
),
...(this.placeholder ? [placeholder(this.placeholder)] : []),
];
@@ -589,48 +636,6 @@ export class HaCodeEditor extends ReactiveElement {
}
};
/**
* Builds a HassArgHoverContext from the current hass object so that
* haJinjaHoverSource can resolve entity / device / area friendly names
* without importing the full HomeAssistant type into the resource file.
*/
private _hassArgHoverContext(): HassArgHoverContext {
const hass = this.hass!;
const labelMap: Record<
string,
{ name: string; description?: string | null }
> = {};
for (const label of this._labels ?? []) {
labelMap[label.label_id] = {
name: label.name,
description: label.description,
};
}
return {
states: hass.states as HassArgHoverContext["states"],
devices: hass.devices as HassArgHoverContext["devices"],
areas: hass.areas as HassArgHoverContext["areas"],
floors: hass.floors as HassArgHoverContext["floors"],
entities: hass.entities as HassArgHoverContext["entities"],
labels: labelMap,
formatEntityState: (entityId) =>
hass.formatEntityState(hass.states[entityId]),
formatEntityName: (entityId) => {
const stateObj = hass.states[entityId];
return (
(stateObj?.attributes.friendly_name as string | undefined) ??
hass.entities[entityId]?.name ??
undefined
);
},
formatAttributeName: (entityId, attribute) =>
hass.formatEntityAttributeName(hass.states[entityId], attribute),
formatAttributeValue: (entityId, attribute) =>
hass.formatEntityAttributeValue(hass.states[entityId], attribute),
localize: (key) => hass.localize(key as never),
};
}
private _renderInfo = (completion: Completion): CompletionInfo => {
const key =
typeof completion.apply === "string"

View File

@@ -8,7 +8,7 @@ import {
} from "@egjs/hammerjs";
import type { PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import { customElement, property } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { fireEvent } from "../common/dom/fire_event";
import { mainWindow } from "../common/dom/get_main_window";
@@ -71,16 +71,13 @@ export class HaControlSwitch extends LitElement {
this.destroyListeners();
}
@query("#switch")
private switch!: HTMLDivElement;
setupSwipeListeners() {
if (this.disabled) {
return;
}
if (this.switch && !this._mc) {
this._mc = new Manager(this.switch, {
if (!this._mc) {
this._mc = new Manager(this, {
touchAction: this.touchAction ?? (this.vertical ? "pan-x" : "pan-y"),
});
this._mc.add(
@@ -191,6 +188,7 @@ export class HaControlSwitch extends LitElement {
static styles = css`
:host {
display: block;
position: relative;
--control-switch-on-color: var(--primary-color);
--control-switch-off-color: var(--disabled-color);
--control-switch-background-opacity: 0.2;
@@ -198,6 +196,7 @@ export class HaControlSwitch extends LitElement {
--control-switch-thickness: 40px;
--control-switch-border-radius: var(--ha-border-radius-lg);
--control-switch-padding: 4px;
--control-switch-touch-area-size: 0px;
--mdc-icon-size: 20px;
height: var(--control-switch-thickness);
width: 100%;
@@ -206,6 +205,11 @@ export class HaControlSwitch extends LitElement {
transition: box-shadow 180ms ease-in-out;
-webkit-tap-highlight-color: transparent;
}
:host::before {
content: "";
position: absolute;
inset: calc(-1 * var(--control-switch-touch-area-size));
}
.switch:not([disabled]):focus-visible {
box-shadow: 0 0 0 2px var(--control-switch-off-color);
}

View File

@@ -8,7 +8,6 @@ import { copyToClipboard } from "../common/util/copy-clipboard";
import { haStyle } from "../resources/styles";
import type { HomeAssistant } from "../types";
import { showToast } from "../util/toast";
import "./ha-alert";
import "./ha-button";
import "./ha-code-editor";
import type { HaCodeEditor } from "./ha-code-editor";
@@ -58,15 +57,8 @@ export class HaYamlEditor extends LitElement {
@property({ attribute: "has-extra-actions", type: Boolean })
public hasExtraActions = false;
@property({ attribute: "show-errors", type: Boolean })
public showErrors = true;
@state() private _yaml = "";
@state() private _error = "";
@state() private _showingError = false;
@query("ha-code-editor") _codeEditor?: HaCodeEditor;
public setValue(value): void {
@@ -126,16 +118,14 @@ export class HaYamlEditor extends LitElement {
.disableFullscreen=${this.disableFullscreen}
.inDialog=${this.inDialog}
mode="yaml"
lint
autocomplete-entities
autocomplete-icons
.error=${this.isValid === false}
@value-changed=${this._onChange}
@blur=${this._onBlur}
@editor-save=${this._onEditorSave}
dir="ltr"
></ha-code-editor>
${this._showingError
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: nothing}
${this.copyClipboard || this.hasExtraActions
? html`
<div class="card-actions">
@@ -158,9 +148,13 @@ export class HaYamlEditor extends LitElement {
private _onChange(ev: CustomEvent): void {
ev.stopPropagation();
this._yaml = ev.detail.value;
let parsed;
let parsed: unknown;
let isValid = true;
let errorMsg;
let errorMsg: string | undefined;
let yamlError: {
mark?: { position: number; line: number; column: number };
message?: string;
} | null = null;
if (this._yaml) {
try {
@@ -168,15 +162,13 @@ export class HaYamlEditor extends LitElement {
} catch (err: any) {
// Invalid YAML
isValid = false;
yamlError = err;
errorMsg = `${this.hass.localize("ui.components.yaml-editor.error", { reason: err.reason })}${err.mark ? ` (${this.hass.localize("ui.components.yaml-editor.error_location", { line: err.mark.line + 1, column: err.mark.column + 1 })})` : ""}`;
}
} else {
parsed = {};
}
this._error = errorMsg ?? "";
if (isValid) {
this._showingError = false;
}
this._codeEditor?.setYamlError(yamlError);
this.value = parsed;
this.isValid = isValid;
@@ -188,16 +180,23 @@ export class HaYamlEditor extends LitElement {
} as any);
}
private _onBlur(): void {
if (this.showErrors && this._error) {
this._showingError = true;
}
}
get yaml() {
return this._yaml;
}
get codemirror() {
return this._codeEditor?.codemirror;
}
get hasComments(): boolean {
return this._codeEditor?.hasComments ?? false;
}
private _onEditorSave(ev: CustomEvent): void {
fireEvent(this, "editor-save");
ev.stopPropagation();
}
private async _copyYaml(): Promise<void> {
if (this.yaml) {
await copyToClipboard(this.yaml);

View File

@@ -546,7 +546,6 @@ export class HaAutomationEditor extends AutomationScriptEditorMixin<AutomationCo
.readOnly=${this.readOnly}
@value-changed=${this._yamlChanged}
@editor-save=${this._handleSaveAutomation}
.showErrors=${false}
disable-fullscreen
></ha-yaml-editor>
<ha-button

View File

@@ -1,14 +1,18 @@
import { mdiDownload } from "@mdi/js";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { extractSearchParam } from "../../../common/url/search-params";
import "../../../components/ha-analytics";
import "../../../components/ha-button";
import "../../../components/ha-card";
import "../../../components/ha-md-list";
import "../../../components/ha-md-list-item";
import "../../../components/ha-spinner";
import "../../../components/ha-svg-icon";
import "../../../components/ha-switch";
import { getSignedPath } from "../../../data/auth";
import type { HaSwitch } from "../../../components/ha-switch";
import type { Analytics } from "../../../data/analytics";
import {
@@ -26,6 +30,7 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
import { fileDownload } from "../../../util/file_download";
@customElement("ha-config-analytics")
class ConfigAnalytics extends SubscribeMixin(LitElement) {
@@ -119,6 +124,18 @@ class ConfigAnalytics extends SubscribeMixin(LitElement) {
</ha-md-list-item>
</ha-md-list>
</div>
<div class="card-actions">
<ha-button
size="small"
appearance="plain"
@click=${this._downloadDeviceInfo}
>
<ha-svg-icon slot="start" .path=${mdiDownload}></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.analytics.download_device_info"
)}
</ha-button>
</div>
</ha-card>`
: nothing}
${this._zwaveEntryId !== undefined
@@ -290,6 +307,11 @@ class ConfigAnalytics extends SubscribeMixin(LitElement) {
this._save();
}
private async _downloadDeviceInfo(): Promise<void> {
const signedPath = await getSignedPath(this.hass, "/api/analytics/devices");
fileDownload(signedPath.path);
}
static get styles(): CSSResultGroup {
return [
haStyle,

View File

@@ -1,17 +1,9 @@
import { mdiDotsVertical, mdiDownload } from "@mdi/js";
import type { TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import "../../../components/ha-icon-button";
import "../../../components/ha-svg-icon";
import { getSignedPath } from "../../../data/auth";
import "../../../layouts/hass-subpage";
import type { HomeAssistant, Route } from "../../../types";
import { fileDownload } from "../../../util/file_download";
import "./ha-config-analytics";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
@customElement("ha-config-section-analytics")
class HaConfigSectionAnalytics extends LitElement {
@@ -29,19 +21,6 @@ class HaConfigSectionAnalytics extends LitElement {
.narrow=${this.narrow}
.header=${this.hass.localize("ui.panel.config.analytics.caption")}
>
<ha-dropdown
@wa-select=${this._handleOverflowAction}
slot="toolbar-icon"
>
<ha-icon-button slot="trigger" .path=${mdiDotsVertical}>
</ha-icon-button>
<ha-dropdown-item .value=${"download_device_info"}>
<ha-svg-icon slot="icon" .path=${mdiDownload}></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.analytics.download_device_info"
)}
</ha-dropdown-item>
</ha-dropdown>
<div class="content">
<ha-config-analytics .hass=${this.hass}></ha-config-analytics>
</div>
@@ -49,18 +28,6 @@ class HaConfigSectionAnalytics extends LitElement {
`;
}
private async _handleOverflowAction(
ev: HaDropdownSelectEvent
): Promise<void> {
if (ev.detail.item.value === "download_device_info") {
const signedPath = await getSignedPath(
this.hass,
"/api/analytics/devices"
);
fileDownload(signedPath.path);
}
}
static styles = css`
.content {
padding: 28px 20px 0;

View File

@@ -18,7 +18,7 @@ import "./events-list";
class HaPanelDevEvent extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public narrow = false;
@property({ type: Boolean, reflect: true }) public narrow = false;
@state() private _eventType = "";
@@ -94,6 +94,7 @@ class HaPanelDevEvent extends LitElement {
<event-subscribe-card
.hass=${this.hass}
.narrow=${this.narrow}
.selectedEventType=${this._selectedEventType}
></event-subscribe-card>
</div>
@@ -158,6 +159,8 @@ class HaPanelDevEvent extends LitElement {
padding: var(--ha-space-4);
max-width: 1200px;
margin: auto;
height: 100%;
box-sizing: border-box;
}
:host {
@@ -165,10 +168,26 @@ class HaPanelDevEvent extends LitElement {
-webkit-user-select: initial;
-moz-user-select: initial;
display: block;
height: 100%;
}
:host([narrow]) {
height: auto;
}
:host([narrow]) .content {
height: auto;
}
.flex {
min-width: 0;
min-height: 0;
display: flex;
flex-direction: column;
}
:host([narrow]) .flex {
min-height: auto;
}
.inputs {
@@ -180,11 +199,19 @@ class HaPanelDevEvent extends LitElement {
}
event-subscribe-card {
display: block;
display: flex;
flex-direction: column;
min-height: 0;
flex: 1;
margin-top: var(--ha-space-4);
direction: var(--direction);
}
:host([narrow]) event-subscribe-card {
flex: none;
min-height: auto;
}
a {
color: var(--primary-color);
}

View File

@@ -1,21 +1,39 @@
import {
mdiChevronDoubleLeft,
mdiChevronDoubleRight,
mdiChevronLeft,
mdiChevronRight,
mdiInformationOutline,
} from "@mdi/js";
import type { HassEvent } from "home-assistant-js-websocket";
import type { TemplateResult, PropertyValues } from "lit";
import { css, html, LitElement } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { repeat } from "lit/directives/repeat";
import { formatTime } from "../../../../common/datetime/format_time";
import { formatTimeWithSeconds } from "../../../../common/datetime/format_time";
import "../../../../components/ha-alert";
import "../../../../components/ha-button";
import "../../../../components/ha-card";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-svg-icon";
import "../../../../components/ha-tooltip";
import "../../../../components/ha-yaml-editor";
import "../../../../components/input/ha-input";
import type { HaInput } from "../../../../components/input/ha-input";
import type { HomeAssistant } from "../../../../types";
const MAX_BUFFERED_EVENTS = 100;
interface SubscribedEvent {
id: number;
event: HassEvent;
}
@customElement("event-subscribe-card")
class EventSubscribeCard extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean, reflect: true }) public narrow = false;
@property({ attribute: false }) public selectedEventType = "";
@state() private _eventType = "";
@@ -24,13 +42,12 @@ class EventSubscribeCard extends LitElement {
@state() private _eventFilter = "";
@state() private _events: {
id: number;
event: HassEvent;
}[] = [];
@state() private _events: SubscribedEvent[] = [];
@state() private _error?: string;
@state() private _viewedEventId?: number;
private _eventCount = 0;
@state() _ignoredEventsCount = 0;
@@ -113,43 +130,161 @@ class EventSubscribeCard extends LitElement {
</ha-button>
</div>
</ha-card>
<ha-card>
<div class="card-content">
<div class="events">
${repeat(
this._events,
(event) => event.id,
(event) => html`
<div class="event">
${this.hass!.localize(
"ui.panel.config.developer-tools.tabs.events.event_fired",
{ name: event.id }
)}
${formatTime(
new Date(event.event.time_fired),
this.hass!.locale,
this.hass!.config
)}:
<ha-yaml-editor
.hass=${this.hass}
.defaultValue=${event.event}
read-only
></ha-yaml-editor>
</div>
`
${this._renderEventsCard()}
`;
}
private _renderEventsCard(): TemplateResult {
if (!this._events.length) {
const message = this._subscribed
? this.hass!.localize(
"ui.panel.config.developer-tools.tabs.events.waiting_for_events"
)
: this.hass!.localize(
"ui.panel.config.developer-tools.tabs.events.subscribe_prompt"
);
return html`
<ha-card class="events-card">
<div class="empty-state">${message}</div>
</ha-card>
`;
}
const bufferTotal = this._events.length;
const index = this._resolveViewedIndex();
const event = this._events[index];
const position = event.id + 1;
const bufferPosition = bufferTotal - index;
const atNewest = index === 0;
const hasRolledOver = this._events[bufferTotal - 1].id > 0;
return html`
<ha-card class="events-card">
<div class="events-toolbar">
<ha-icon-button
.path=${mdiChevronDoubleLeft}
.disabled=${index >= bufferTotal - 1}
.label=${this.hass!.localize(
"ui.panel.config.developer-tools.tabs.events.oldest_event"
)}
@click=${this._showOldest}
></ha-icon-button>
<ha-icon-button
.path=${mdiChevronLeft}
.disabled=${index >= bufferTotal - 1}
.label=${this.hass!.localize(
"ui.panel.config.developer-tools.tabs.events.older_event"
)}
@click=${this._showOlder}
></ha-icon-button>
<div class="event-info">
${this.hass!.localize(
"ui.panel.config.developer-tools.tabs.events.event_fired",
{
name: position,
time: formatTimeWithSeconds(
new Date(event.event.time_fired),
this.hass!.locale,
this.hass!.config
),
}
)}
<span class="counter">(${bufferPosition} / ${bufferTotal})</span>
${hasRolledOver
? html`
<ha-svg-icon
id="buffer-info"
class="buffer-info"
.path=${mdiInformationOutline}
></ha-svg-icon>
<ha-tooltip for="buffer-info" placement="bottom">
<span class="buffer-tooltip">
${this.hass!.localize(
"ui.panel.config.developer-tools.tabs.events.buffer_disclaimer",
{ count: MAX_BUFFERED_EVENTS }
)}
</span>
</ha-tooltip>
`
: nothing}
</div>
<ha-icon-button
.path=${mdiChevronRight}
.disabled=${atNewest}
.label=${this.hass!.localize(
"ui.panel.config.developer-tools.tabs.events.newer_event"
)}
@click=${this._showNewer}
></ha-icon-button>
<ha-icon-button
.path=${mdiChevronDoubleRight}
.disabled=${atNewest}
.label=${this.hass!.localize(
"ui.panel.config.developer-tools.tabs.events.newest_event"
)}
@click=${this._showNewest}
></ha-icon-button>
</div>
<ha-yaml-editor
.hass=${this.hass}
.value=${event.event}
auto-update
read-only
></ha-yaml-editor>
</ha-card>
`;
}
private _valueChanged(ev: InputEvent): void {
private _resolveViewedIndex(): number {
if (this._viewedEventId === undefined) {
return 0;
}
const found = this._events.findIndex((e) => e.id === this._viewedEventId);
// Fall back to the oldest available event when the viewed one has aged out.
return found === -1 ? this._events.length - 1 : found;
}
private _showOldest() {
if (!this._events.length) {
return;
}
this._viewedEventId = this._events[this._events.length - 1].id;
}
private _showOlder() {
if (!this._events.length) {
return;
}
const next = Math.min(
this._resolveViewedIndex() + 1,
this._events.length - 1
);
this._viewedEventId = this._events[next].id;
}
private _showNewest() {
if (!this._events.length) {
return;
}
this._viewedEventId = this._events[0].id;
}
private _showNewer() {
if (!this._events.length) {
return;
}
const next = Math.max(this._resolveViewedIndex() - 1, 0);
this._viewedEventId = this._events[next].id;
}
private _valueChanged(ev: InputEvent) {
this._eventType = (ev.target as HaInput).value ?? "";
this._error = undefined;
}
private _filterChanged(ev: InputEvent): void {
private _filterChanged(ev: InputEvent) {
this._eventFilter = (ev.target as HaInput).value ?? "";
}
@@ -160,7 +295,7 @@ class EventSubscribeCard extends LitElement {
const searchStr = this._eventFilter;
function visit(node) {
function visit(node: unknown) {
// Handle primitives directly
if (node === null || typeof node !== "object") {
return String(node).includes(searchStr);
@@ -203,55 +338,116 @@ class EventSubscribeCard extends LitElement {
return;
}
const tail =
this._events.length > 30
? this._events.slice(0, 29)
this._events.length >= MAX_BUFFERED_EVENTS
? this._events.slice(0, MAX_BUFFERED_EVENTS - 1)
: this._events;
const id = this._eventCount++;
this._events = [
{
event,
id: this._eventCount++,
id,
},
...tail,
];
if (this._viewedEventId === undefined) {
this._viewedEventId = id;
}
}, this._eventType);
} catch (error: any) {
} catch (error) {
this._error = this.hass!.localize(
"ui.panel.config.developer-tools.tabs.events.subscribe_failed",
{ error: error.message || "Unknown error" }
{
error:
error instanceof Error
? error.message
: this.hass!.localize(
"ui.panel.config.developer-tools.tabs.events.unknown_error"
),
}
);
}
}
}
private _clearEvents(): void {
private _clearEvents() {
this._events = [];
this._eventCount = 0;
this._ignoredEventsCount = 0;
this._error = undefined;
this._viewedEventId = undefined;
}
static styles = css`
:host {
display: flex;
flex-direction: column;
min-height: 0;
}
ha-input {
margin-bottom: var(--ha-space-2);
}
.error-message {
margin-top: var(--ha-space-2);
}
.event {
border-top: 1px solid var(--divider-color);
padding-top: var(--ha-space-2);
padding-bottom: var(--ha-space-2);
margin: var(--ha-space-4) 0;
}
.event:last-child {
border-bottom: 0;
margin-bottom: 0;
}
pre {
font-family: var(--ha-font-family-code);
}
ha-card {
margin-bottom: var(--ha-space-1);
margin-bottom: var(--ha-space-2);
}
.events-card {
display: flex;
flex-direction: column;
height: 620px;
padding: var(--ha-space-2);
}
:host([narrow]) .events-card {
height: auto;
min-height: 360px;
}
.events-toolbar {
display: flex;
align-items: center;
gap: var(--ha-space-2);
}
.empty-state {
display: flex;
flex: 1;
align-items: center;
justify-content: center;
padding: var(--ha-space-8);
color: var(--primary-text-color);
text-align: center;
font-size: var(--ha-font-size-xl);
line-height: var(--ha-line-height-normal);
}
.event-info {
flex: 1;
text-align: center;
font-size: var(--ha-font-size-m);
}
.counter {
color: var(--secondary-text-color);
margin-left: var(--ha-space-2);
}
.buffer-info {
color: var(--secondary-text-color);
margin-left: var(--ha-space-1);
vertical-align: middle;
--mdc-icon-size: 16px;
}
.buffer-tooltip {
white-space: pre-line;
display: block;
max-width: 320px;
}
ha-yaml-editor {
display: flex;
flex-direction: column;
min-height: 0;
flex: 1;
margin-top: var(--ha-space-2);
--code-mirror-height: 100%;
}
`;
}

View File

@@ -9,7 +9,6 @@ import "../../../../components/ha-button";
import "../../../../components/ha-card";
import "../../../../components/ha-code-editor";
import "../../../../components/ha-spinner";
import "../../../../components/ha-tip";
import type { RenderTemplateResult } from "../../../../data/ws-templates";
import { subscribeRenderTemplate } from "../../../../data/ws-templates";
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
@@ -159,14 +158,6 @@ class HaPanelDevTemplate extends LitElement {
${this.hass.localize("ui.common.clear")}
</ha-button>
</div>
<ha-tip .hass=${this.hass}>
${this.hass.localize(
"ui.panel.config.developer-tools.tabs.templates.keyboard_tip",
{
autocomplete: html`<kbd>Ctrl</kbd>+<kbd>Space</kbd>`,
}
)}
</ha-tip>
</ha-card>
<ha-card
@@ -370,22 +361,6 @@ ${type === "object"
color: var(--warning-color);
}
ha-tip {
padding: 0 var(--ha-space-4) var(--ha-space-4);
display: block;
}
kbd {
display: inline-block;
font-family: var(--ha-font-family-code);
font-size: 0.85em;
padding: 1px 5px;
border: 1px solid var(--divider-color);
border-radius: var(--ha-border-radius-xs);
background-color: var(--secondary-background-color);
white-space: nowrap;
}
@media all and (max-width: 870px) {
.content ha-card {
max-width: 100%;

View File

@@ -329,7 +329,6 @@ export class HaSceneEditor extends PreventUnsavedMixin(
.defaultValue=${this._config}
@value-changed=${this._yamlChanged}
@editor-save=${this._saveScene}
.showErrors=${false}
disable-fullscreen
></ha-yaml-editor>`;
}

View File

@@ -464,7 +464,6 @@ export class HaScriptEditor extends SubscribeMixin(
disable-fullscreen
@value-changed=${this._yamlChanged}
@editor-save=${this._handleSaveScript}
.showErrors=${false}
></ha-yaml-editor>
<ha-button
slot="fab"

View File

@@ -55,6 +55,7 @@ class HuiEntitiesToggle extends LitElement {
ha-control-switch {
--control-switch-thickness: 20px;
--control-switch-off-color: var(--state-inactive-color);
--control-switch-touch-area-size: var(--ha-space-2);
}
`;

View File

@@ -5,7 +5,6 @@ import { cache } from "lit/directives/cache";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { fireEvent } from "../../../common/dom/fire_event";
import { handleStructError } from "../../../common/structs/handle-errors";
import { debounce } from "../../../common/util/debounce";
import { deepEqual } from "../../../common/util/deep-equal";
import "../../../components/ha-alert";
import "../../../components/ha-spinner";
@@ -71,11 +70,6 @@ export abstract class HuiElementEditor<
// Error: Configuration broken - do not save
@state() private _errors?: string[];
// Error from unparseable YAML, but don't show it immediately to prevent showing immediately on every keystroke
@state() private _pendingYamlError?: string;
@state() private _yamlError = false;
// Warning: GUI editor can't handle configuration - ok to save
@state() private _warnings?: string[];
@@ -83,6 +77,8 @@ export abstract class HuiElementEditor<
@state() private _loading = false;
@state() private _yamlError = false;
@query("ha-yaml-editor") _yamlEditor?: HaYamlEditor;
private _loadCount = 0;
@@ -105,7 +101,7 @@ export abstract class HuiElementEditor<
}
private _setConfig(): void {
if (!this._errors) {
if (!this._errors && !this._yamlError) {
try {
this._updateConfigElement();
} catch (err: any) {
@@ -131,7 +127,9 @@ export abstract class HuiElementEditor<
}
public get hasError(): boolean {
return this._errors !== undefined && this._errors.length > 0;
return (
this._yamlError || (this._errors !== undefined && this._errors.length > 0)
);
}
public get GUImode(): boolean {
@@ -251,10 +249,8 @@ export abstract class HuiElementEditor<
.hass=${this.hass}
.inDialog=${this.inDialog}
@value-changed=${this._handleYAMLChanged}
@blur=${this._onBlurYaml}
@keydown=${this._ignoreKeydown}
dir="ltr"
.showErrors=${false}
></ha-yaml-editor>
</div>
`}
@@ -274,7 +270,7 @@ export abstract class HuiElementEditor<
</ha-alert>
`
: nothing}
${this.hasError
${this._errors?.length
? html`
<ha-alert
alert-type="error"
@@ -283,7 +279,7 @@ export abstract class HuiElementEditor<
)}
>
<ul>
${this._errors!.map((error) => html`<li>${error}</li>`)}
${this._errors.map((error) => html`<li>${error}</li>`)}
</ul>
</ha-alert>
`
@@ -339,40 +335,14 @@ export abstract class HuiElementEditor<
private _handleYAMLChanged(ev: CustomEvent) {
ev.stopPropagation();
const config = ev.detail.value;
if (ev.detail.isValid) {
this._config = config;
this._config = ev.detail.value;
this._errors = undefined;
this._pendingYamlError = undefined;
this._yamlError = false;
this._debounceYamlError.cancel();
this._setConfig();
} else if (this._yamlError) {
// If we're already showing a yaml error, don't bother to debounce, just update immediately.
this._errors = [ev.detail.errorMsg];
} else {
this._pendingYamlError = ev.detail.errorMsg;
this._debounceYamlError();
}
}
private _debounceYamlError = debounce(() => {
if (this._pendingYamlError) {
this._yamlError = true;
this._errors = [this._pendingYamlError];
this._pendingYamlError = undefined;
this._setConfig();
}
}, 2000);
private _onBlurYaml() {
this._debounceYamlError.cancel();
if (this._pendingYamlError) {
this._yamlError = true;
this._errors = [this._pendingYamlError];
this._pendingYamlError = undefined;
this._setConfig();
}
this._setConfig();
}
protected async unloadConfigElement(): Promise<void> {

View File

@@ -1,6 +1,5 @@
import { undoDepth } from "@codemirror/commands";
import { mdiClose } from "@mdi/js";
import { dump, load } from "js-yaml";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
@@ -8,8 +7,8 @@ import { classMap } from "lit/directives/class-map";
import { array, assert, object, optional, string, type } from "superstruct";
import { deepEqual } from "../../common/util/deep-equal";
import "../../components/ha-button";
import "../../components/ha-code-editor";
import type { HaCodeEditor } from "../../components/ha-code-editor";
import "../../components/ha-yaml-editor";
import type { HaYamlEditor } from "../../components/ha-yaml-editor";
import "../../components/ha-icon-button";
import "../../components/ha-top-app-bar-fixed";
import type { LovelaceRawConfig } from "../../data/lovelace/config/types";
@@ -47,6 +46,10 @@ class LovelaceFullConfigEditor extends LitElement {
@state() private _changed?: boolean;
private _config?: LovelaceRawConfig;
private _yamlError?: string;
protected render(): TemplateResult | undefined {
return html`
<ha-top-app-bar-fixed .narrow=${this.narrow}>
@@ -81,18 +84,14 @@ class LovelaceFullConfigEditor extends LitElement {
)}</ha-button
>
<div class="content">
<ha-code-editor
mode="yaml"
<ha-yaml-editor
autofocus
autocomplete-entities
autocomplete-icons
.hass=${this.hass}
@value-changed=${this._yamlChanged}
@editor-save=${this._handleSave}
disable-fullscreen
dir="ltr"
>
</ha-code-editor>
</ha-yaml-editor>
</div>
</ha-top-app-bar-fixed>
`;
@@ -100,7 +99,7 @@ class LovelaceFullConfigEditor extends LitElement {
protected firstUpdated(changedProps: PropertyValues<this>) {
super.firstUpdated(changedProps);
this.yamlEditor.value = dump(this.lovelace!.rawConfig);
this.yamlEditor.setValue(this.lovelace!.rawConfig);
}
protected updated(changedProps: PropertyValues<this>) {
@@ -112,7 +111,7 @@ class LovelaceFullConfigEditor extends LitElement {
oldLovelace.rawConfig !== this.lovelace.rawConfig &&
!deepEqual(oldLovelace.rawConfig, this.lovelace.rawConfig)
) {
this.yamlEditor.value = dump(this.lovelace!.rawConfig);
this.yamlEditor.setValue(this.lovelace!.rawConfig);
}
}
@@ -137,7 +136,7 @@ class LovelaceFullConfigEditor extends LitElement {
font-size: var(--ha-font-size-l);
}
ha-code-editor {
ha-yaml-editor {
height: 100%;
}
@@ -154,7 +153,9 @@ class LovelaceFullConfigEditor extends LitElement {
];
}
private _yamlChanged() {
private _yamlChanged(ev: CustomEvent) {
this._config = ev.detail.isValid ? ev.detail.value : undefined;
this._yamlError = ev.detail.errorMsg;
this._changed = undoDepth(this.yamlEditor.codemirror!.state) > 0;
if (this._changed && !window.onbeforeunload) {
window.onbeforeunload = () => true;
@@ -204,9 +205,7 @@ class LovelaceFullConfigEditor extends LitElement {
private async _handleSave() {
this._saving = true;
const value = this.yamlEditor.value;
if (!value) {
if (!this.yamlEditor.yaml) {
showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.lovelace.editor.raw_editor.confirm_reset_config_title"
@@ -222,6 +221,14 @@ class LovelaceFullConfigEditor extends LitElement {
return;
}
if (this._yamlError) {
showAlertDialog(this, {
text: this._yamlError,
});
this._saving = false;
return;
}
if (this.yamlEditor.hasComments) {
if (
!confirm(
@@ -234,19 +241,8 @@ class LovelaceFullConfigEditor extends LitElement {
}
}
let config: LovelaceRawConfig;
try {
config = load(value) as LovelaceRawConfig;
} catch (err: any) {
showAlertDialog(this, {
text: this.hass.localize(
"ui.panel.lovelace.editor.raw_editor.error_parse_yaml",
{ error: err }
),
});
this._saving = false;
return;
}
const config: LovelaceRawConfig = this._config!;
try {
if (isStrategyDashboard(config)) {
assert(config, strategyStruct);
@@ -285,8 +281,8 @@ class LovelaceFullConfigEditor extends LitElement {
this._saving = false;
}
private get yamlEditor(): HaCodeEditor {
return this.shadowRoot!.querySelector("ha-code-editor")! as HaCodeEditor;
private get yamlEditor(): HaYamlEditor {
return this.shadowRoot!.querySelector("ha-yaml-editor")! as HaYamlEditor;
}
}

View File

@@ -37,13 +37,13 @@ export {
search,
searchKeymap,
} from "@codemirror/search";
export { lintGutter, lintKeymap, setDiagnostics } from "@codemirror/lint";
export { EditorState } from "@codemirror/state";
export {
crosshairCursor,
drawSelection,
EditorView,
highlightActiveLine,
hoverTooltip,
keymap,
lineNumbers,
rectangularSelection,
@@ -71,15 +71,15 @@ export const closeBracketsOverride = Prec.highest(
export {
haJinjaCompletionSource,
haJinjaHoverSource,
JINJA_FUNCTION_ARG_TYPES,
} from "./jinja_ha_completions";
export type { HassArgHoverContext, JinjaArgType } from "./jinja_ha_completions";
export type { JinjaArgType } from "./jinja_ha_completions";
export { closePercentBrace };
export const langCompartment = new Compartment();
export const readonlyCompartment = new Compartment();
export const linewrapCompartment = new Compartment();
export const yamlLintCompartment = new Compartment();
// ---------------------------------------------------------------------------
// YAML scalar type highlighter
@@ -369,6 +369,20 @@ export const haTheme = EditorView.theme({
paddingRight: "0",
},
".cm-gutterElement.lineNumber": { color: "inherit" },
// Lint gutter
".cm-lint-marker-error": { color: "var(--error-color)" },
".cm-lint-marker-warning": { color: "var(--warning-color)" },
".cm-lint-marker-info": { color: "var(--info-color, var(--primary-color))" },
".cm-diagnostic": {
fontFamily: "var(--mdc-typography-font-family, var(--ha-font-family-body))",
},
".cm-diagnostic.cm-diagnostic-error": {
borderLeft: "3px solid var(--error-color)",
},
".cm-diagnostic.cm-diagnostic-warning": {
borderLeft: "3px solid var(--warning-color)",
},
});
const haHighlightStyle = HighlightStyle.define([

File diff suppressed because it is too large Load Diff

View File

@@ -1427,6 +1427,7 @@
},
"yaml-editor": {
"copy_to_clipboard": "[%key:ui::panel::config::automation::editor::copy_to_clipboard%]",
"syntax_error": "YAML syntax error",
"error": "Error in parsing YAML: {reason}",
"error_location": "line: {line}, column: {column}",
"enter_fullscreen": "Enter fullscreen",
@@ -1497,9 +1498,6 @@
"area_label": "Area",
"description": "Configure which areas correspond to each vacuum segment"
},
"codemirror": {
"open_documentation": "Open documentation"
},
"safe_mode": {
"title": "Safe mode",
"text": "Home Assistant is running in safe mode, custom integrations and community frontend modules are not available. Restart Home Assistant to exit safe mode."
@@ -3784,7 +3782,7 @@
"type": "Event type",
"data": "Event data (YAML, optional)",
"fire_event": "Fire event",
"event_fired": "Event {name} fired",
"event_fired": "Event {name} fired at {time}",
"active_listeners": "Active listeners",
"count_listeners": "({count} {count, plural,\n one {listener}\n other {listeners}\n})",
"listen_to_events": "Listen to events",
@@ -3798,7 +3796,15 @@
"clear_events": "Clear events",
"alert_event_type": "Event type is a mandatory field",
"notification_event_fired": "Event {type} successfully fired!",
"subscribe_failed": "Failed to subscribe to event: {error}"
"subscribe_failed": "Failed to subscribe to event: {error}",
"unknown_error": "Unknown error",
"oldest_event": "Oldest event",
"older_event": "Older event",
"newer_event": "Newer event",
"newest_event": "Newest event",
"waiting_for_events": "Waiting for events…",
"subscribe_prompt": "Subscribe to an event type above to see events here.",
"buffer_disclaimer": "Showing the latest {count} events. Older events are discarded as new ones arrive.\n\nTip: Subscribe to a more specific event type, or use the filter to narrow results."
},
"actions": {
"title": "Actions",
@@ -3870,8 +3876,7 @@
"no_listeners": "This template does not listen for any events and will not update automatically.",
"listeners": "This template listens for the following state changed events:",
"entity": "Entity",
"domain": "Domain",
"keyboard_tip": "Press {autocomplete} to trigger autocomplete, when your cursor is inside a function that supports it."
"domain": "Domain"
},
"statistics": {
"title": "Statistics",
@@ -8687,7 +8692,6 @@
"confirm_reset_config_text": "Your dashboard will be reset to an empty state. You can start fresh and build your dashboard from scratch.",
"confirm_unsaved_changes": "You have unsaved changes, are you sure you want to exit?",
"confirm_unsaved_comments": "Your configuration might contain comments, these will not be saved. Do you want to continue?",
"error_parse_yaml": "Unable to parse YAML: {error}",
"error_invalid_config": "Your configuration is not valid: {error}",
"error_save_yaml": "Unable to save YAML: {error}",
"resources_moved": "Resources should no longer be added to the dashboard configuration but can be added in the dashboard config panel."

113
yarn.lock
View File

@@ -18,27 +18,36 @@ __metadata:
languageName: node
linkType: hard
"@asamuzakjp/css-color@npm:^5.1.5":
version: 5.1.6
resolution: "@asamuzakjp/css-color@npm:5.1.6"
"@asamuzakjp/css-color@npm:^5.1.11":
version: 5.1.11
resolution: "@asamuzakjp/css-color@npm:5.1.11"
dependencies:
"@csstools/css-calc": "npm:^3.1.1"
"@csstools/css-color-parser": "npm:^4.0.2"
"@asamuzakjp/generational-cache": "npm:^1.0.1"
"@csstools/css-calc": "npm:^3.2.0"
"@csstools/css-color-parser": "npm:^4.1.0"
"@csstools/css-parser-algorithms": "npm:^4.0.0"
"@csstools/css-tokenizer": "npm:^4.0.0"
checksum: 10/5151369d9369e478e03c0eee0f171b8f86306ebbdf5b352544cd745c360d97343f437bdd0690ff658e47d2876b466bffc8811fcef7f0347cb243c6483a7e95a0
checksum: 10/2e337cc94b5a3f9741a27f92b4e4b7dc467a76b1dcf66c40e71808fed71695f10c8cf07c8b13313cbb637154314ca1d8626bb9a045fe94b404b242a390cf3bd3
languageName: node
linkType: hard
"@asamuzakjp/dom-selector@npm:^7.0.6":
version: 7.0.7
resolution: "@asamuzakjp/dom-selector@npm:7.0.7"
"@asamuzakjp/dom-selector@npm:^7.1.1":
version: 7.1.1
resolution: "@asamuzakjp/dom-selector@npm:7.1.1"
dependencies:
"@asamuzakjp/generational-cache": "npm:^1.0.1"
"@asamuzakjp/nwsapi": "npm:^2.3.9"
bidi-js: "npm:^1.0.3"
css-tree: "npm:^3.2.1"
is-potential-custom-element-name: "npm:^1.0.1"
checksum: 10/18f40def8c775c6008c8fcd75d7d049ff92d99a494929ab2bf742341b348c78cbf4808d29c13b9cd87ca4fd272773cf5aa9e58fee48603c286df48148be8cb67
checksum: 10/49a065a64db5f53a3008c231d09606e4b67f509fa20148a67419451c2dc91a421202ed17bfc4bc679ad2f0432d7260720d602c1d5c9c5e165931fff5199c3f12
languageName: node
linkType: hard
"@asamuzakjp/generational-cache@npm:^1.0.1":
version: 1.0.1
resolution: "@asamuzakjp/generational-cache@npm:1.0.1"
checksum: 10/e1cf3f1916a334c6153f624982f0eb3d50fa3048435ea5c5b0f441f8f1ab74a0fe992dac214b612d22c0acafad3cd1a1f6b45d99c7b6e3b63cfdf7f6ca5fc144
languageName: node
linkType: hard
@@ -1331,7 +1340,7 @@ __metadata:
languageName: node
linkType: hard
"@codemirror/lint@npm:^6.0.0":
"@codemirror/lint@npm:6.9.5, @codemirror/lint@npm:^6.0.0":
version: 6.9.5
resolution: "@codemirror/lint@npm:6.9.5"
dependencies:
@@ -1381,26 +1390,26 @@ __metadata:
languageName: node
linkType: hard
"@csstools/css-calc@npm:^3.1.1":
version: 3.1.1
resolution: "@csstools/css-calc@npm:3.1.1"
"@csstools/css-calc@npm:^3.2.0":
version: 3.2.0
resolution: "@csstools/css-calc@npm:3.2.0"
peerDependencies:
"@csstools/css-parser-algorithms": ^4.0.0
"@csstools/css-tokenizer": ^4.0.0
checksum: 10/faa3aa2736b20757ceafd76e3d2841e8726ec9e7ae78e387684eb462aba73d533ba384039338685c3a52196196300ccdfecb051e59864b1d3b457fe927b7f53b
checksum: 10/7eec51a21945a74aa6a407d1e6290d0f4c5d01829a42c01a56ce2055216398540cc3120837b15a0db38601bcb40cf97f1d991fefb3ee9d00d9cec03d67beba4c
languageName: node
linkType: hard
"@csstools/css-color-parser@npm:^4.0.2":
version: 4.0.2
resolution: "@csstools/css-color-parser@npm:4.0.2"
"@csstools/css-color-parser@npm:^4.1.0":
version: 4.1.0
resolution: "@csstools/css-color-parser@npm:4.1.0"
dependencies:
"@csstools/color-helpers": "npm:^6.0.2"
"@csstools/css-calc": "npm:^3.1.1"
"@csstools/css-calc": "npm:^3.2.0"
peerDependencies:
"@csstools/css-parser-algorithms": ^4.0.0
"@csstools/css-tokenizer": ^4.0.0
checksum: 10/6418bfadc8c15d3a65c1e80278df383b542f0437446c0ba21d591dd564bcc19ab0b11243edf62672f4c62cc778f9b386fa4349e9a8d1de2b414148ea8a1ac775
checksum: 10/794508011a95ebac3856e67e0333ca4174604d2dfddc101d991f2ebfd52b3c99cd36a08462675c2a07d057ca3787187fcd7eac98bced2eefdd9040b37853426d
languageName: node
linkType: hard
@@ -1413,15 +1422,15 @@ __metadata:
languageName: node
linkType: hard
"@csstools/css-syntax-patches-for-csstree@npm:^1.1.1":
version: 1.1.1
resolution: "@csstools/css-syntax-patches-for-csstree@npm:1.1.1"
"@csstools/css-syntax-patches-for-csstree@npm:^1.1.3":
version: 1.1.3
resolution: "@csstools/css-syntax-patches-for-csstree@npm:1.1.3"
peerDependencies:
css-tree: ^3.2.1
peerDependenciesMeta:
css-tree:
optional: true
checksum: 10/745ec0f6f7d1c3707af9661d5dcc7e29c12c0416da46e10dda7518c872fef38446d39e13557b3d134e16eb1c78899fa6a712a27fd8ab544813e25a4cd0913cdc
checksum: 10/1c91dc03b64ca913eed5064ca0e434da1c0be8def6ce20f932d1db10f9b478ac3830c99a033b0edf75954cf9164c7c267b220ed9faffbc3342bf320870c3bb4b
languageName: node
linkType: hard
@@ -6712,6 +6721,13 @@ __metadata:
languageName: node
linkType: hard
"entities@npm:^8.0.0":
version: 8.0.0
resolution: "entities@npm:8.0.0"
checksum: 10/d6e2ba75e444fb101ee2fbb07c839e687306c8a509426b75186619c19196f97c1db9932ca083f823c03e4a20e7407b654aa34de8cbb7770468e20fb2d4573a0e
languageName: node
linkType: hard
"env-paths@npm:^2.2.0":
version: 2.2.1
resolution: "env-paths@npm:2.2.1"
@@ -8106,6 +8122,7 @@ __metadata:
"@codemirror/lang-jinja": "npm:6.0.1"
"@codemirror/lang-yaml": "npm:6.1.3"
"@codemirror/language": "npm:6.12.3"
"@codemirror/lint": "npm:6.9.5"
"@codemirror/search": "npm:6.7.0"
"@codemirror/state": "npm:6.6.0"
"@codemirror/view": "npm:6.41.1"
@@ -8223,7 +8240,7 @@ __metadata:
idb-keyval: "npm:6.2.2"
intl-messageformat: "npm:11.2.2"
js-yaml: "npm:4.1.1"
jsdom: "npm:29.0.2"
jsdom: "npm:29.1.0"
jszip: "npm:3.10.1"
leaflet: "npm:1.9.4"
leaflet-draw: "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
@@ -9098,26 +9115,26 @@ __metadata:
languageName: node
linkType: hard
"jsdom@npm:29.0.2":
version: 29.0.2
resolution: "jsdom@npm:29.0.2"
"jsdom@npm:29.1.0":
version: 29.1.0
resolution: "jsdom@npm:29.1.0"
dependencies:
"@asamuzakjp/css-color": "npm:^5.1.5"
"@asamuzakjp/dom-selector": "npm:^7.0.6"
"@asamuzakjp/css-color": "npm:^5.1.11"
"@asamuzakjp/dom-selector": "npm:^7.1.1"
"@bramus/specificity": "npm:^2.4.2"
"@csstools/css-syntax-patches-for-csstree": "npm:^1.1.1"
"@csstools/css-syntax-patches-for-csstree": "npm:^1.1.3"
"@exodus/bytes": "npm:^1.15.0"
css-tree: "npm:^3.2.1"
data-urls: "npm:^7.0.0"
decimal.js: "npm:^10.6.0"
html-encoding-sniffer: "npm:^6.0.0"
is-potential-custom-element-name: "npm:^1.0.1"
lru-cache: "npm:^11.2.7"
parse5: "npm:^8.0.0"
lru-cache: "npm:^11.3.5"
parse5: "npm:^8.0.1"
saxes: "npm:^6.0.0"
symbol-tree: "npm:^3.2.4"
tough-cookie: "npm:^6.0.1"
undici: "npm:^7.24.5"
undici: "npm:^7.25.0"
w3c-xmlserializer: "npm:^5.0.0"
webidl-conversions: "npm:^8.0.1"
whatwg-mimetype: "npm:^5.0.0"
@@ -9128,7 +9145,7 @@ __metadata:
peerDependenciesMeta:
canvas:
optional: true
checksum: 10/3ad1d9a5b6aba067427bc43be98e1c51fab489bf689a6530e596278c6326fe053c94fc47a9c133f126fbe914f421283ae723fb92214dfe4959ca6cf2ee1666f6
checksum: 10/91194be30c10b518c8e21c3a15cf1ef0e6c74722fb8fe402f201efed3e5de55f13005f2a4108d02ccde75d1e16719ca898690be93cd22d498a4c062d035adae5
languageName: node
linkType: hard
@@ -9652,10 +9669,10 @@ __metadata:
languageName: node
linkType: hard
"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1, lru-cache@npm:^11.2.7":
version: 11.2.7
resolution: "lru-cache@npm:11.2.7"
checksum: 10/fbff4b8dee8189dde9b52cdfb3ea89b4c9cec094c1538cd30d1f47299477ff312efdb35f7994477ec72328f8e754e232b26a143feda1bd1f79ff22da6664d2c5
"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1, lru-cache@npm:^11.3.5":
version: 11.3.5
resolution: "lru-cache@npm:11.3.5"
checksum: 10/3701b77e87765a3aea453402a7850bdbf7e02445210f35bd5ba1561f601f605f488bf9932be4a3851a6664073924f671a1ec99c4a1a98c457e0d126872a3e04f
languageName: node
linkType: hard
@@ -10445,12 +10462,12 @@ __metadata:
languageName: node
linkType: hard
"parse5@npm:^8.0.0":
version: 8.0.0
resolution: "parse5@npm:8.0.0"
"parse5@npm:^8.0.1":
version: 8.0.1
resolution: "parse5@npm:8.0.1"
dependencies:
entities: "npm:^6.0.0"
checksum: 10/1973850932bb1cbd52ab64502761489fbe1bb43a52dee7ce41aac0b6c33a51a92aaee04661590b0912b739ae9ee316bce4c78c8ea34af42a7e522c983c3c6cf5
entities: "npm:^8.0.0"
checksum: 10/671dedfe7cbf4714414317bc8c6b2a14c61ef44f8fd90c983b5b1870653af5aa2e3b4e25e38e9538a7120ea2b688c50908830da2bd0930d8fd4bce34aed024eb
languageName: node
linkType: hard
@@ -12665,10 +12682,10 @@ __metadata:
languageName: node
linkType: hard
"undici@npm:^7.24.5":
version: 7.24.5
resolution: "undici@npm:7.24.5"
checksum: 10/1eef66817e5bd1ee6ba908553452e8e23c6b743221f8435e838a87fcec84db901ad9bca5853e7a6e123520abd8d3dd03215b19bf57c2ffb114bfeb0b7c206027
"undici@npm:^7.25.0":
version: 7.25.0
resolution: "undici@npm:7.25.0"
checksum: 10/038d3568c72bb976e3cc389284f7f1cc64cd70d578300e4676a449fbcb624a35fe99ac127b5f3729f18b8246d6c090444ab61b1b67736bb88f52a3e913d76bf8
languageName: node
linkType: hard