mirror of
https://github.com/home-assistant/frontend.git
synced 2025-09-02 13:00:27 +00:00
Compare commits
74 Commits
fix-automa
...
20240131.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
22c3132638 | ||
![]() |
ce32de6e23 | ||
![]() |
b6bc88e460 | ||
![]() |
6e00806f1a | ||
![]() |
d9fa148c49 | ||
![]() |
939b3a8092 | ||
![]() |
95920ba710 | ||
![]() |
462ac79890 | ||
![]() |
601a165b2a | ||
![]() |
62bb9b1a87 | ||
![]() |
b60ba35a9f | ||
![]() |
c97c3f2fc4 | ||
![]() |
ed888200f9 | ||
![]() |
f4859320eb | ||
![]() |
b159f4c074 | ||
![]() |
b700e08d52 | ||
![]() |
c1bdd679ff | ||
![]() |
b728b9efc4 | ||
![]() |
8acae63939 | ||
![]() |
374f5ee1be | ||
![]() |
528533a2dd | ||
![]() |
2b18db8525 | ||
![]() |
6cd8ee9253 | ||
![]() |
e45709fffc | ||
![]() |
28c21b1041 | ||
![]() |
0919f0e89e | ||
![]() |
7ce9a937b1 | ||
![]() |
456c011f3e | ||
![]() |
a31b9f1b4d | ||
![]() |
a1cf18468b | ||
![]() |
f147a5e909 | ||
![]() |
8d541595b8 | ||
![]() |
32fd8270d7 | ||
![]() |
efddbfcfa0 | ||
![]() |
0b20725f5f | ||
![]() |
030566c1e8 | ||
![]() |
fef2c44cb8 | ||
![]() |
b99b13251f | ||
![]() |
288d173a4d | ||
![]() |
2c69fe8c53 | ||
![]() |
62dafac72b | ||
![]() |
22929672a0 | ||
![]() |
ae0eac3415 | ||
![]() |
4ea7d826bc | ||
![]() |
336214d97f | ||
![]() |
c9a0ae6e2d | ||
![]() |
0bc69fb9b3 | ||
![]() |
6e7366bf69 | ||
![]() |
8ee4aa9e63 | ||
![]() |
386c3ea1ca | ||
![]() |
c2f3e43ee5 | ||
![]() |
7354988ec9 | ||
![]() |
53dedc6c65 | ||
![]() |
de3b9a5bb2 | ||
![]() |
8d496e1511 | ||
![]() |
01bd88ce10 | ||
![]() |
18b5fd59a6 | ||
![]() |
750c1d5013 | ||
![]() |
f2226cdec2 | ||
![]() |
c125ec087a | ||
![]() |
f099f66065 | ||
![]() |
4fcf99faa7 | ||
![]() |
2add88ccc2 | ||
![]() |
aa94ec7949 | ||
![]() |
7b4ecfd30a | ||
![]() |
9d9e789f4b | ||
![]() |
6ce613acd2 | ||
![]() |
aa38e2d409 | ||
![]() |
fce4e5e382 | ||
![]() |
eb5e7ba3f3 | ||
![]() |
ae2e8e7402 | ||
![]() |
b854d23431 | ||
![]() |
ef735d65cf | ||
![]() |
2803e6aa95 |
@@ -1,39 +0,0 @@
|
||||
diff --git a/modular/sortable.complete.esm.js b/modular/sortable.complete.esm.js
|
||||
index 02e9f2d6bebeb430fe6e7c1cc3f9c3c9df051f14..bb8268b0844a1faa4108cc92c0be2a3dbaf23f83 100644
|
||||
--- a/modular/sortable.complete.esm.js
|
||||
+++ b/modular/sortable.complete.esm.js
|
||||
@@ -1657,7 +1657,7 @@ Sortable.prototype =
|
||||
target = parent; // store last element
|
||||
}
|
||||
/* jshint boss:true */
|
||||
- while (parent = parent.parentNode);
|
||||
+ while (parent = parent.parentNode || parent.getRootNode().host);
|
||||
}
|
||||
|
||||
_unhideGhostForTarget();
|
||||
diff --git a/modular/sortable.core.esm.js b/modular/sortable.core.esm.js
|
||||
index b04c8b4634f7c6b4ef1aadbb48afe6564306dea9..39a107163c8c336ebd669b5ea8a936af87e1c1e7 100644
|
||||
--- a/modular/sortable.core.esm.js
|
||||
+++ b/modular/sortable.core.esm.js
|
||||
@@ -1657,7 +1657,7 @@ Sortable.prototype =
|
||||
target = parent; // store last element
|
||||
}
|
||||
/* jshint boss:true */
|
||||
- while (parent = parent.parentNode);
|
||||
+ while (parent = parent.parentNode || parent.getRootNode().host);
|
||||
}
|
||||
|
||||
_unhideGhostForTarget();
|
||||
diff --git a/modular/sortable.esm.js b/modular/sortable.esm.js
|
||||
index 6ec7ed1bb557e21c2578200161e989c65d23150b..0a05475a22904472fac6c13f524c674da76584b0 100644
|
||||
--- a/modular/sortable.esm.js
|
||||
+++ b/modular/sortable.esm.js
|
||||
@@ -1657,7 +1657,7 @@ Sortable.prototype =
|
||||
target = parent; // store last element
|
||||
}
|
||||
/* jshint boss:true */
|
||||
- while (parent = parent.parentNode);
|
||||
+ while (parent = parent.parentNode || parent.getRootNode().host);
|
||||
}
|
||||
|
||||
_unhideGhostForTarget();
|
73
.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch
Normal file
73
.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch
Normal file
@@ -0,0 +1,73 @@
|
||||
diff --git a/modular/sortable.core.esm.js b/modular/sortable.core.esm.js
|
||||
index 93ba17509e2e8583ab241fea6845fbe714c584a2..de0651ddb5dced30d36f7d764da0dd0b441f523f 100644
|
||||
--- a/modular/sortable.core.esm.js
|
||||
+++ b/modular/sortable.core.esm.js
|
||||
@@ -1461,7 +1461,7 @@ Sortable.prototype = /** @lends Sortable.prototype */{
|
||||
}
|
||||
target = parent; // store last element
|
||||
}
|
||||
- /* jshint boss:true */ while (parent = parent.parentNode);
|
||||
+ /* jshint boss:true */ while (parent = parent.parentNode || parent.getRootNode().host);
|
||||
}
|
||||
_unhideGhostForTarget();
|
||||
}
|
||||
@@ -1781,11 +1781,16 @@ Sortable.prototype = /** @lends Sortable.prototype */{
|
||||
}
|
||||
if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, !!target) !== false) {
|
||||
capture();
|
||||
- if (elLastChild && elLastChild.nextSibling) {
|
||||
- // the last draggable element is not the last node
|
||||
- el.insertBefore(dragEl, elLastChild.nextSibling);
|
||||
- } else {
|
||||
- el.appendChild(dragEl);
|
||||
+ try {
|
||||
+ if (elLastChild && elLastChild.nextSibling) {
|
||||
+ // the last draggable element is not the last node
|
||||
+ el.insertBefore(dragEl, elLastChild.nextSibling);
|
||||
+ } else {
|
||||
+ el.appendChild(dragEl);
|
||||
+ }
|
||||
+ }
|
||||
+ catch(err) {
|
||||
+ return completed(false);
|
||||
}
|
||||
parentEl = el; // actualization
|
||||
|
||||
@@ -1802,7 +1807,13 @@ Sortable.prototype = /** @lends Sortable.prototype */{
|
||||
targetRect = getRect(target);
|
||||
if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, false) !== false) {
|
||||
capture();
|
||||
- el.insertBefore(dragEl, firstChild);
|
||||
+ try {
|
||||
+ el.insertBefore(dragEl, firstChild);
|
||||
+ }
|
||||
+ catch(err) {
|
||||
+ return completed(false);
|
||||
+ }
|
||||
+
|
||||
parentEl = el; // actualization
|
||||
|
||||
changed();
|
||||
@@ -1849,12 +1860,17 @@ Sortable.prototype = /** @lends Sortable.prototype */{
|
||||
_silent = true;
|
||||
setTimeout(_unsilent, 30);
|
||||
capture();
|
||||
- if (after && !nextSibling) {
|
||||
- el.appendChild(dragEl);
|
||||
- } else {
|
||||
- target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
|
||||
- }
|
||||
|
||||
+ try {
|
||||
+ if (after && !nextSibling) {
|
||||
+ el.appendChild(dragEl);
|
||||
+ } else {
|
||||
+ target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
|
||||
+ }
|
||||
+ }
|
||||
+ catch(err) {
|
||||
+ return completed(false);
|
||||
+ }
|
||||
// Undo chrome's scroll adjustment (has no effect on other browsers)
|
||||
if (scrolledPastTop) {
|
||||
scrollBy(scrolledPastTop, 0, scrollBefore - scrolledPastTop.scrollTop);
|
@@ -10,6 +10,7 @@ import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervis
|
||||
import { computeInitialHaFormData } from "../../../../src/components/ha-form/compute-initial-ha-form-data";
|
||||
import "../../../../src/components/ha-form/ha-form";
|
||||
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
||||
import type { AreaRegistryEntry } from "../../../../src/data/area_registry";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
@@ -97,22 +98,25 @@ const DEVICES = [
|
||||
},
|
||||
];
|
||||
|
||||
const AREAS = [
|
||||
const AREAS: AreaRegistryEntry[] = [
|
||||
{
|
||||
area_id: "backyard",
|
||||
name: "Backyard",
|
||||
icon: null,
|
||||
picture: null,
|
||||
aliases: [],
|
||||
},
|
||||
{
|
||||
area_id: "bedroom",
|
||||
name: "Bedroom",
|
||||
icon: "mdi:bed",
|
||||
picture: null,
|
||||
aliases: [],
|
||||
},
|
||||
{
|
||||
area_id: "livingroom",
|
||||
name: "Livingroom",
|
||||
icon: "mdi:sofa",
|
||||
picture: null,
|
||||
aliases: [],
|
||||
},
|
||||
|
@@ -9,6 +9,7 @@ import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||
import "../../../../src/components/ha-selector/ha-selector";
|
||||
import "../../../../src/components/ha-settings-row";
|
||||
import type { AreaRegistryEntry } from "../../../../src/data/area_registry";
|
||||
import { BlueprintInput } from "../../../../src/data/blueprint";
|
||||
import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
@@ -93,22 +94,25 @@ const DEVICES = [
|
||||
},
|
||||
];
|
||||
|
||||
const AREAS = [
|
||||
const AREAS: AreaRegistryEntry[] = [
|
||||
{
|
||||
area_id: "backyard",
|
||||
name: "Backyard",
|
||||
icon: null,
|
||||
picture: null,
|
||||
aliases: [],
|
||||
},
|
||||
{
|
||||
area_id: "bedroom",
|
||||
name: "Bedroom",
|
||||
icon: "mdi:bed",
|
||||
picture: null,
|
||||
aliases: [],
|
||||
},
|
||||
{
|
||||
area_id: "livingroom",
|
||||
name: "Livingroom",
|
||||
icon: "mdi:sofa",
|
||||
picture: null,
|
||||
aliases: [],
|
||||
},
|
||||
|
@@ -79,6 +79,18 @@ const CONFIGS = [
|
||||
color: pink
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Whole tile tap action",
|
||||
config: `
|
||||
- type: tile
|
||||
entity: switch.tv_outlet
|
||||
color: pink
|
||||
tap_action:
|
||||
action: toggle
|
||||
icon_tap_action:
|
||||
action: none
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Unknown entity",
|
||||
config: `
|
||||
|
@@ -255,7 +255,7 @@
|
||||
"lit": "2.8.0",
|
||||
"clean-css": "5.3.3",
|
||||
"@lit/reactive-element": "1.6.3",
|
||||
"sortablejs@1.15.0": "patch:sortablejs@npm%3A1.15.0#./.yarn/patches/sortablejs-npm-1.15.0-f3a393abcc.patch",
|
||||
"sortablejs@1.15.2": "patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch",
|
||||
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
||||
},
|
||||
"packageManager": "yarn@4.0.2"
|
||||
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20240125.0"
|
||||
version = "20240131.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
@@ -47,6 +47,12 @@ export class StateHistoryChartLine extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public logarithmicScale = false;
|
||||
|
||||
@property({ type: Number }) public minYAxis?: number;
|
||||
|
||||
@property({ type: Number }) public maxYAxis?: number;
|
||||
|
||||
@property({ type: Boolean }) public fitYData = false;
|
||||
|
||||
@state() private _chartData?: ChartData<"line">;
|
||||
|
||||
@state() private _entityIds: string[] = [];
|
||||
@@ -84,7 +90,10 @@ export class StateHistoryChartLine extends LitElement {
|
||||
changedProps.has("startTime") ||
|
||||
changedProps.has("endTime") ||
|
||||
changedProps.has("unit") ||
|
||||
changedProps.has("logarithmicScale")
|
||||
changedProps.has("logarithmicScale") ||
|
||||
changedProps.has("minYAxis") ||
|
||||
changedProps.has("maxYAxis") ||
|
||||
changedProps.has("fitYData")
|
||||
) {
|
||||
this._chartOptions = {
|
||||
parsing: false,
|
||||
@@ -121,6 +130,10 @@ export class StateHistoryChartLine extends LitElement {
|
||||
},
|
||||
},
|
||||
y: {
|
||||
suggestedMin: this.fitYData ? this.minYAxis : null,
|
||||
suggestedMax: this.fitYData ? this.maxYAxis : null,
|
||||
min: this.fitYData ? null : this.minYAxis,
|
||||
max: this.fitYData ? null : this.maxYAxis,
|
||||
ticks: {
|
||||
maxTicksLimit: 7,
|
||||
},
|
||||
|
@@ -74,6 +74,12 @@ export class StateHistoryCharts extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public logarithmicScale = false;
|
||||
|
||||
@property({ type: Number }) public minYAxis?: number;
|
||||
|
||||
@property({ type: Number }) public maxYAxis?: number;
|
||||
|
||||
@property({ type: Boolean }) public fitYData = false;
|
||||
|
||||
private _computedStartTime!: Date;
|
||||
|
||||
private _computedEndTime!: Date;
|
||||
@@ -161,6 +167,9 @@ export class StateHistoryCharts extends LitElement {
|
||||
.chartIndex=${index}
|
||||
.clickForMoreInfo=${this.clickForMoreInfo}
|
||||
.logarithmicScale=${this.logarithmicScale}
|
||||
.minYAxis=${this.minYAxis}
|
||||
.maxYAxis=${this.maxYAxis}
|
||||
.fitYData=${this.fitYData}
|
||||
@y-width-changed=${this._yWidthChanged}
|
||||
></state-history-chart-line>
|
||||
</div> `;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { html, LitElement, nothing, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -36,8 +36,12 @@ type ScorableAreaRegistryEntry = ScorableTextItem & AreaRegistryEntry;
|
||||
|
||||
const rowRenderer: ComboBoxLitRenderer<AreaRegistryEntry> = (item) =>
|
||||
html`<ha-list-item
|
||||
graphic="icon"
|
||||
class=${classMap({ "add-new": item.area_id === "add_new" })}
|
||||
>
|
||||
${item.icon
|
||||
? html`<ha-icon slot="graphic" .icon=${item.icon}></ha-icon>`
|
||||
: nothing}
|
||||
${item.name}
|
||||
</ha-list-item>`;
|
||||
|
||||
@@ -135,6 +139,7 @@ export class HaAreaPicker extends LitElement {
|
||||
area_id: "no_areas",
|
||||
name: this.hass.localize("ui.components.area-picker.no_areas"),
|
||||
picture: null,
|
||||
icon: null,
|
||||
aliases: [],
|
||||
},
|
||||
];
|
||||
@@ -262,7 +267,9 @@ export class HaAreaPicker extends LitElement {
|
||||
}
|
||||
|
||||
if (areaIds) {
|
||||
outputAreas = areas.filter((area) => areaIds!.includes(area.area_id));
|
||||
outputAreas = outputAreas.filter((area) =>
|
||||
areaIds!.includes(area.area_id)
|
||||
);
|
||||
}
|
||||
|
||||
if (excludeAreas) {
|
||||
@@ -277,6 +284,7 @@ export class HaAreaPicker extends LitElement {
|
||||
area_id: "no_areas",
|
||||
name: this.hass.localize("ui.components.area-picker.no_match"),
|
||||
picture: null,
|
||||
icon: null,
|
||||
aliases: [],
|
||||
},
|
||||
];
|
||||
@@ -290,6 +298,7 @@ export class HaAreaPicker extends LitElement {
|
||||
area_id: "add_new",
|
||||
name: this.hass.localize("ui.components.area-picker.add_new"),
|
||||
picture: null,
|
||||
icon: "mdi:plus",
|
||||
aliases: [],
|
||||
},
|
||||
];
|
||||
|
@@ -67,7 +67,8 @@ export class HaQrCode extends LitElement {
|
||||
const computedStyles = getComputedStyle(this);
|
||||
|
||||
QRCode.toCanvas(canvas, this.data, {
|
||||
errorCorrectionLevel: this.errorCorrectionLevel,
|
||||
errorCorrectionLevel:
|
||||
this.errorCorrectionLevel || (this.centerImage ? "Q" : "M"),
|
||||
width: this.width,
|
||||
scale: this.scale,
|
||||
margin: this.margin,
|
||||
|
30
src/components/ha-selector/ha-selector-qr-code.ts
Normal file
30
src/components/ha-selector/ha-selector-qr-code.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { QRCodeSelector } from "../../data/selector";
|
||||
import "../ha-qr-code";
|
||||
|
||||
@customElement("ha-selector-qr_code")
|
||||
export class HaSelectorQRCode extends LitElement {
|
||||
@property({ attribute: false }) public selector!: QRCodeSelector;
|
||||
|
||||
protected render() {
|
||||
return html`<ha-qr-code
|
||||
.data=${this.selector.qr_code?.data}
|
||||
.scale=${this.selector.qr_code?.scale}
|
||||
.errorCorrectionLevel=${this.selector.qr_code?.error_correction_level}
|
||||
.centerImage=${this.selector.qr_code?.center_image}
|
||||
></ha-qr-code>`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-qr-code {
|
||||
text-align: center;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-selector-qr_code": HaSelectorQRCode;
|
||||
}
|
||||
}
|
@@ -34,6 +34,7 @@ const LOAD_ELEMENTS = {
|
||||
navigation: () => import("./ha-selector-navigation"),
|
||||
number: () => import("./ha-selector-number"),
|
||||
object: () => import("./ha-selector-object"),
|
||||
qr_code: () => import("./ha-selector-qr-code"),
|
||||
select: () => import("./ha-selector-select"),
|
||||
selector: () => import("./ha-selector-selector"),
|
||||
state: () => import("./ha-selector-state"),
|
||||
|
@@ -98,6 +98,7 @@ export class HaTargetPicker extends LitElement {
|
||||
area_id,
|
||||
area?.name || area_id,
|
||||
undefined,
|
||||
area?.icon,
|
||||
mdiSofa
|
||||
);
|
||||
})
|
||||
@@ -110,6 +111,7 @@ export class HaTargetPicker extends LitElement {
|
||||
device_id,
|
||||
device ? computeDeviceName(device, this.hass) : device_id,
|
||||
undefined,
|
||||
undefined,
|
||||
mdiDevices
|
||||
);
|
||||
})
|
||||
@@ -209,7 +211,8 @@ export class HaTargetPicker extends LitElement {
|
||||
id: string,
|
||||
name: string,
|
||||
entityState?: HassEntity,
|
||||
iconPath?: string
|
||||
icon?: string | null,
|
||||
fallbackIconPath?: string
|
||||
) {
|
||||
return html`
|
||||
<div
|
||||
@@ -217,12 +220,17 @@ export class HaTargetPicker extends LitElement {
|
||||
[type]: true,
|
||||
})}"
|
||||
>
|
||||
${iconPath
|
||||
? html`<ha-svg-icon
|
||||
${icon
|
||||
? html`<ha-icon
|
||||
class="mdc-chip__icon mdc-chip__icon--leading"
|
||||
.path=${iconPath}
|
||||
></ha-svg-icon>`
|
||||
: ""}
|
||||
.icon=${icon}
|
||||
></ha-icon>`
|
||||
: fallbackIconPath
|
||||
? html`<ha-svg-icon
|
||||
class="mdc-chip__icon mdc-chip__icon--leading"
|
||||
.path=${fallbackIconPath}
|
||||
></ha-svg-icon>`
|
||||
: ""}
|
||||
${entityState
|
||||
? html`<ha-state-icon
|
||||
class="mdc-chip__icon mdc-chip__icon--leading"
|
||||
|
@@ -133,7 +133,7 @@ export class HaLocationsEditor extends LitElement {
|
||||
.layers=${this._getLayers(this._circles, this._locationMarkers)}
|
||||
.zoom=${this.zoom}
|
||||
.autoFit=${this.autoFit}
|
||||
.darkMode=${this.darkMode}
|
||||
?darkMode=${this.darkMode}
|
||||
></ha-map>
|
||||
${this.helper
|
||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
||||
|
@@ -156,9 +156,9 @@ export class HaMap extends ReactiveElement {
|
||||
}
|
||||
|
||||
private _updateMapStyle(): void {
|
||||
const darkMode = this.darkMode ?? this.hass.themes.darkMode ?? false;
|
||||
const forcedDark = this.darkMode ?? false;
|
||||
const map = this.shadowRoot!.getElementById("map");
|
||||
const darkMode = this.darkMode || (this.hass.themes.darkMode ?? false);
|
||||
const forcedDark = this.darkMode;
|
||||
const map = this.renderRoot.querySelector("#map");
|
||||
map!.classList.toggle("dark", darkMode);
|
||||
map!.classList.toggle("forced-dark", forcedDark);
|
||||
}
|
||||
@@ -361,7 +361,7 @@ export class HaMap extends ReactiveElement {
|
||||
);
|
||||
|
||||
const className =
|
||||
this.darkMode ?? this.hass.themes.darkMode ? "dark" : "light";
|
||||
this.darkMode || this.hass.themes.darkMode ? "dark" : "light";
|
||||
|
||||
for (const entity of this.entities) {
|
||||
const stateObj = hass.states[getEntityId(entity)];
|
||||
|
@@ -24,6 +24,8 @@ class HaUsersPickerLight extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public users?: User[];
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
if (this.users === undefined) {
|
||||
@@ -57,6 +59,7 @@ class HaUsersPickerLight extends LitElement {
|
||||
this.users,
|
||||
notSelectedUsers
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._userChanged}
|
||||
></ha-user-picker>
|
||||
<ha-icon-button
|
||||
@@ -78,7 +81,7 @@ class HaUsersPickerLight extends LitElement {
|
||||
this.hass!.localize("ui.components.user-picker.add_user")}
|
||||
.hass=${this.hass}
|
||||
.users=${notSelectedUsers}
|
||||
.disabled=${!notSelectedUsers?.length}
|
||||
.disabled=${this.disabled || !notSelectedUsers?.length}
|
||||
@value-changed=${this._addUser}
|
||||
></ha-user-picker>
|
||||
`;
|
||||
|
@@ -9,6 +9,7 @@ export interface AreaRegistryEntry {
|
||||
area_id: string;
|
||||
name: string;
|
||||
picture: string | null;
|
||||
icon: string | null;
|
||||
aliases: string[];
|
||||
}
|
||||
|
||||
@@ -23,6 +24,7 @@ export interface AreaDeviceLookup {
|
||||
export interface AreaRegistryEntryMutableParams {
|
||||
name: string;
|
||||
picture?: string | null;
|
||||
icon?: string | null;
|
||||
aliases?: string[];
|
||||
}
|
||||
|
||||
|
@@ -74,8 +74,8 @@ export interface StateTrigger extends BaseTrigger {
|
||||
platform: "state";
|
||||
entity_id: string | string[];
|
||||
attribute?: string;
|
||||
from?: string | number;
|
||||
to?: string | string[] | number;
|
||||
from?: string | string[];
|
||||
to?: string | string[];
|
||||
for?: string | number | ForDict;
|
||||
}
|
||||
|
||||
|
@@ -199,57 +199,46 @@ const tryDescribeTrigger = (
|
||||
|
||||
// State Trigger
|
||||
if (trigger.platform === "state") {
|
||||
let base = "When";
|
||||
const entities: string[] = [];
|
||||
const states = hass.states;
|
||||
|
||||
let attribute = "";
|
||||
if (trigger.attribute) {
|
||||
const stateObj = Array.isArray(trigger.entity_id)
|
||||
? hass.states[trigger.entity_id[0]]
|
||||
: hass.states[trigger.entity_id];
|
||||
base += ` ${computeAttributeNameDisplay(
|
||||
attribute = computeAttributeNameDisplay(
|
||||
hass.localize,
|
||||
stateObj,
|
||||
hass.entities,
|
||||
trigger.attribute
|
||||
)} of`;
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(trigger.entity_id)) {
|
||||
for (const entity of trigger.entity_id.values()) {
|
||||
const entityArray: string[] = ensureArray(trigger.entity_id);
|
||||
if (entityArray) {
|
||||
for (const entity of entityArray) {
|
||||
if (states[entity]) {
|
||||
entities.push(computeStateName(states[entity]) || entity);
|
||||
}
|
||||
}
|
||||
} else if (trigger.entity_id) {
|
||||
entities.push(
|
||||
states[trigger.entity_id]
|
||||
? computeStateName(states[trigger.entity_id])
|
||||
: trigger.entity_id
|
||||
);
|
||||
}
|
||||
|
||||
if (entities.length === 0) {
|
||||
// no entity_id or empty array
|
||||
entities.push("something");
|
||||
}
|
||||
const stateObj = hass.states[entityArray[0]];
|
||||
|
||||
base += ` ${entities} changes`;
|
||||
|
||||
const stateObj =
|
||||
hass.states[
|
||||
Array.isArray(trigger.entity_id)
|
||||
? trigger.entity_id[0]
|
||||
: trigger.entity_id
|
||||
];
|
||||
let fromChoice = "other";
|
||||
let fromString = "";
|
||||
if (trigger.from !== undefined) {
|
||||
let fromArray: string[] = [];
|
||||
if (trigger.from === null) {
|
||||
if (!trigger.attribute) {
|
||||
base += " from any state";
|
||||
fromChoice = "null";
|
||||
}
|
||||
} else if (Array.isArray(trigger.from)) {
|
||||
} else {
|
||||
fromArray = ensureArray(trigger.from);
|
||||
|
||||
const from: string[] = [];
|
||||
for (const state of trigger.from.values()) {
|
||||
for (const state of fromArray) {
|
||||
from.push(
|
||||
trigger.attribute
|
||||
? hass
|
||||
@@ -263,34 +252,25 @@ const tryDescribeTrigger = (
|
||||
);
|
||||
}
|
||||
if (from.length !== 0) {
|
||||
const fromString = formatListWithOrs(hass.locale, from);
|
||||
base += ` from ${fromString}`;
|
||||
fromString = formatListWithOrs(hass.locale, from);
|
||||
fromChoice = "fromUsed";
|
||||
}
|
||||
} else {
|
||||
base += ` from ${
|
||||
trigger.attribute
|
||||
? hass
|
||||
.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
trigger.attribute,
|
||||
trigger.from
|
||||
)
|
||||
.toString()
|
||||
: hass
|
||||
.formatEntityState(stateObj, trigger.from.toString())
|
||||
.toString()
|
||||
}`;
|
||||
}
|
||||
}
|
||||
|
||||
let toChoice = "other";
|
||||
let toString = "";
|
||||
if (trigger.to !== undefined) {
|
||||
let toArray: string[] = [];
|
||||
if (trigger.to === null) {
|
||||
if (!trigger.attribute) {
|
||||
base += " to any state";
|
||||
toChoice = "null";
|
||||
}
|
||||
} else if (Array.isArray(trigger.to)) {
|
||||
} else {
|
||||
toArray = ensureArray(trigger.to);
|
||||
|
||||
const to: string[] = [];
|
||||
for (const state of trigger.to.values()) {
|
||||
for (const state of toArray) {
|
||||
to.push(
|
||||
trigger.attribute
|
||||
? hass
|
||||
@@ -304,21 +284,9 @@ const tryDescribeTrigger = (
|
||||
);
|
||||
}
|
||||
if (to.length !== 0) {
|
||||
const toString = formatListWithOrs(hass.locale, to);
|
||||
base += ` to ${toString}`;
|
||||
toString = formatListWithOrs(hass.locale, to);
|
||||
toChoice = "toUsed";
|
||||
}
|
||||
} else {
|
||||
base += ` to ${
|
||||
trigger.attribute
|
||||
? hass
|
||||
.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
trigger.attribute,
|
||||
trigger.to
|
||||
)
|
||||
.toString()
|
||||
: hass.formatEntityState(stateObj, trigger.to.toString())
|
||||
}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,17 +295,29 @@ const tryDescribeTrigger = (
|
||||
trigger.from === undefined &&
|
||||
trigger.to === undefined
|
||||
) {
|
||||
base += " state or any attributes";
|
||||
toChoice = "special";
|
||||
}
|
||||
|
||||
let duration = "";
|
||||
if (trigger.for) {
|
||||
const duration = describeDuration(hass.locale, trigger.for);
|
||||
if (duration) {
|
||||
base += ` for ${duration}`;
|
||||
}
|
||||
duration = describeDuration(hass.locale, trigger.for) ?? "";
|
||||
}
|
||||
|
||||
return base;
|
||||
return hass.localize(
|
||||
`${triggerTranslationBaseKey}.state.description.full`,
|
||||
{
|
||||
hasAttribute: attribute !== "" ? "true" : "false",
|
||||
attribute: attribute,
|
||||
hasEntity: entities.length !== 0 ? "true" : "false",
|
||||
entity: formatListWithOrs(hass.locale, entities),
|
||||
fromChoice: fromChoice,
|
||||
fromString: fromString,
|
||||
toChoice: toChoice,
|
||||
toString: toString,
|
||||
hasDuration: duration !== "" ? "true" : "false",
|
||||
duration: duration,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Sun Trigger
|
||||
|
@@ -72,6 +72,8 @@ export const enum ClimateEntityFeature {
|
||||
PRESET_MODE = 16,
|
||||
SWING_MODE = 32,
|
||||
AUX_HEAT = 64,
|
||||
TURN_OFF = 128,
|
||||
TURN_ON = 256,
|
||||
}
|
||||
|
||||
const hvacModeOrdering = HVAC_MODES.reduce(
|
||||
|
@@ -470,9 +470,15 @@ export const computeHistory = (
|
||||
}[domain];
|
||||
}
|
||||
|
||||
const deviceClass: string | undefined = (
|
||||
currentState?.attributes || numericStateFromHistory?.a
|
||||
)?.device_class;
|
||||
const specialDomainClasses = {
|
||||
climate: "temperature",
|
||||
humidifier: "humidity",
|
||||
water_heater: "temperature",
|
||||
};
|
||||
|
||||
const deviceClass: string | undefined =
|
||||
specialDomainClasses[domain] ||
|
||||
(currentState?.attributes || numericStateFromHistory?.a)?.device_class;
|
||||
|
||||
const key = computeGroupKey(unit, deviceClass, splitDeviceClasses);
|
||||
|
||||
|
@@ -3,6 +3,50 @@ import { navigate } from "../common/navigate";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { subscribeDeviceRegistry } from "./device_registry";
|
||||
|
||||
export enum NetworkType {
|
||||
THREAD = "thread",
|
||||
WIFI = "wifi",
|
||||
ETHERNET = "ethernet",
|
||||
UNKNOWN = "unknown",
|
||||
}
|
||||
|
||||
export enum NodeType {
|
||||
END_DEVICE = "end_device",
|
||||
SLEEPY_END_DEVICE = "sleepy_end_device",
|
||||
ROUTING_END_DEVICE = "routing_end_device",
|
||||
BRIDGE = "bridge",
|
||||
UNKNOWN = "unknown",
|
||||
}
|
||||
|
||||
export interface MatterFabricData {
|
||||
fabric_id: number;
|
||||
vendor_id: number;
|
||||
fabric_index: number;
|
||||
fabric_label?: string;
|
||||
vendor_name?: string;
|
||||
}
|
||||
|
||||
export interface MatterNodeDiagnostics {
|
||||
node_id: number;
|
||||
network_type: NetworkType;
|
||||
node_type: NodeType;
|
||||
network_name?: string;
|
||||
ip_adresses: string[];
|
||||
mac_address?: string;
|
||||
available: boolean;
|
||||
active_fabrics: MatterFabricData[];
|
||||
}
|
||||
|
||||
export interface MatterPingResult {
|
||||
[ip_address: string]: boolean;
|
||||
}
|
||||
|
||||
export interface MatterCommissioningParameters {
|
||||
setup_pin_code: number;
|
||||
setup_manual_code: string;
|
||||
setup_qr_code: string;
|
||||
}
|
||||
|
||||
export const canCommissionMatterExternal = (hass: HomeAssistant) =>
|
||||
hass.auth.external?.config.canCommissionMatter;
|
||||
|
||||
@@ -86,3 +130,50 @@ export const matterSetThread = (
|
||||
type: "matter/set_thread",
|
||||
thread_operation_dataset,
|
||||
});
|
||||
|
||||
export const getMatterNodeDiagnostics = (
|
||||
hass: HomeAssistant,
|
||||
device_id: string
|
||||
): Promise<MatterNodeDiagnostics> =>
|
||||
hass.callWS({
|
||||
type: "matter/node_diagnostics",
|
||||
device_id,
|
||||
});
|
||||
|
||||
export const pingMatterNode = (
|
||||
hass: HomeAssistant,
|
||||
device_id: string
|
||||
): Promise<MatterPingResult> =>
|
||||
hass.callWS({
|
||||
type: "matter/ping_node",
|
||||
device_id,
|
||||
});
|
||||
|
||||
export const openMatterCommissioningWindow = (
|
||||
hass: HomeAssistant,
|
||||
device_id: string
|
||||
): Promise<MatterCommissioningParameters> =>
|
||||
hass.callWS({
|
||||
type: "matter/open_commissioning_window",
|
||||
device_id,
|
||||
});
|
||||
|
||||
export const removeMatterFabric = (
|
||||
hass: HomeAssistant,
|
||||
device_id: string,
|
||||
fabric_index: number
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "matter/remove_matter_fabric",
|
||||
device_id,
|
||||
fabric_index,
|
||||
});
|
||||
|
||||
export const interviewMatterNode = (
|
||||
hass: HomeAssistant,
|
||||
device_id: string
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "matter/interview_node",
|
||||
device_id,
|
||||
});
|
||||
|
@@ -32,6 +32,13 @@ export const fetchRepairsIssues = (conn: Connection) =>
|
||||
type: "repairs/list_issues",
|
||||
});
|
||||
|
||||
export const fetchRepairsIssueData = (conn: Connection, domain, issue_id) =>
|
||||
conn.sendMessagePromise<{ issue_data: { string: any } }>({
|
||||
type: "repairs/get_issue_data",
|
||||
domain,
|
||||
issue_id,
|
||||
});
|
||||
|
||||
export const ignoreRepairsIssue = async (
|
||||
hass: HomeAssistant,
|
||||
issue: RepairsIssue,
|
||||
|
@@ -41,6 +41,7 @@ export type Selector =
|
||||
| NumberSelector
|
||||
| ObjectSelector
|
||||
| AssistPipelineSelector
|
||||
| QRCodeSelector
|
||||
| SelectSelector
|
||||
| SelectorSelector
|
||||
| StateSelector
|
||||
@@ -340,6 +341,15 @@ export interface BackupLocationSelector {
|
||||
backup_location: {} | null;
|
||||
}
|
||||
|
||||
export interface QRCodeSelector {
|
||||
qr_code: {
|
||||
data: string;
|
||||
scale?: number;
|
||||
error_correction_level?: "low" | "medium" | "quartile" | "high";
|
||||
center_image?: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface StringSelector {
|
||||
text: {
|
||||
multiline?: boolean;
|
||||
|
@@ -22,6 +22,7 @@ export interface ThreadDataSet {
|
||||
network_name: string;
|
||||
pan_id: string | null;
|
||||
preferred_border_agent_id: string | null;
|
||||
preferred_extended_address: string | null;
|
||||
preferred: boolean;
|
||||
source: string;
|
||||
}
|
||||
@@ -107,10 +108,12 @@ export const setPreferredThreadDataSet = (
|
||||
export const setPreferredBorderAgent = (
|
||||
hass: HomeAssistant,
|
||||
dataset_id: string,
|
||||
border_agent_id: string
|
||||
border_agent_id: string | null,
|
||||
extended_address: string
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "thread/set_preferred_border_agent_id",
|
||||
type: "thread/set_preferred_border_agent",
|
||||
dataset_id,
|
||||
border_agent_id,
|
||||
extended_address,
|
||||
});
|
||||
|
@@ -1,19 +1,12 @@
|
||||
import { Connection, createCollection } from "home-assistant-js-websocket";
|
||||
import { Store } from "home-assistant-js-websocket/dist/store";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import { AreaRegistryEntry } from "./area_registry";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import { AreaRegistryEntry } from "./area_registry";
|
||||
|
||||
const fetchAreaRegistry = (conn: Connection) =>
|
||||
conn
|
||||
.sendMessagePromise({
|
||||
type: "config/area_registry/list",
|
||||
})
|
||||
.then((areas) =>
|
||||
(areas as AreaRegistryEntry[]).sort((ent1, ent2) =>
|
||||
stringCompare(ent1.name, ent2.name)
|
||||
)
|
||||
);
|
||||
conn.sendMessagePromise<AreaRegistryEntry[]>({
|
||||
type: "config/area_registry/list",
|
||||
});
|
||||
|
||||
const subscribeAreaRegistryUpdates = (
|
||||
conn: Connection,
|
||||
|
@@ -73,7 +73,7 @@ export interface ClusterAttributeData {
|
||||
export interface AttributeConfigurationStatus {
|
||||
id: number;
|
||||
name: string;
|
||||
success: boolean | undefined;
|
||||
status: string;
|
||||
min: number;
|
||||
max: number;
|
||||
change: number;
|
||||
|
@@ -35,6 +35,13 @@ interface EMOutgoingMessageConfigGet extends EMMessage {
|
||||
type: "config/get";
|
||||
}
|
||||
|
||||
interface EMOutgoingMessageScanQRCode extends EMMessage {
|
||||
type: "qr_code/scan";
|
||||
title: string;
|
||||
description: string;
|
||||
alternative_option_label?: string;
|
||||
}
|
||||
|
||||
interface EMOutgoingMessageMatterCommission extends EMMessage {
|
||||
type: "matter/commission";
|
||||
}
|
||||
@@ -48,6 +55,13 @@ type EMOutgoingMessageWithAnswer = {
|
||||
request: EMOutgoingMessageConfigGet;
|
||||
response: ExternalConfig;
|
||||
};
|
||||
"qr_code/scan": {
|
||||
request: EMOutgoingMessageScanQRCode;
|
||||
response:
|
||||
| EMIncomingMessageQRCodeResponseCanceled
|
||||
| EMIncomingMessageQRCodeResponseAlternativeOptions
|
||||
| EMIncomingMessageQRCodeResponseScanResult;
|
||||
};
|
||||
};
|
||||
|
||||
interface EMOutgoingMessageExoplayerPlayHLS extends EMMessage {
|
||||
@@ -158,6 +172,19 @@ interface EMIncomingMessageShowAutomationEditor {
|
||||
};
|
||||
}
|
||||
|
||||
export interface EMIncomingMessageQRCodeResponseCanceled {
|
||||
action: "canceled";
|
||||
}
|
||||
|
||||
export interface EMIncomingMessageQRCodeResponseAlternativeOptions {
|
||||
action: "alternative_options";
|
||||
}
|
||||
|
||||
export interface EMIncomingMessageQRCodeResponseScanResult {
|
||||
action: "scan_result";
|
||||
result: string;
|
||||
}
|
||||
|
||||
export type EMIncomingMessageCommands =
|
||||
| EMIncomingMessageRestart
|
||||
| EMIncomingMessageShowNotifications
|
||||
@@ -180,6 +207,7 @@ export interface ExternalConfig {
|
||||
canCommissionMatter: boolean;
|
||||
canImportThreadCredentials: boolean;
|
||||
hasAssist: boolean;
|
||||
hasQRScanner: number;
|
||||
}
|
||||
|
||||
export class ExternalMessaging {
|
||||
|
@@ -9,6 +9,7 @@ import { createCloseHeading } from "../../../components/ha-dialog";
|
||||
import "../../../components/ha-picture-upload";
|
||||
import type { HaPictureUpload } from "../../../components/ha-picture-upload";
|
||||
import "../../../components/ha-settings-row";
|
||||
import "../../../components/ha-icon-picker";
|
||||
import "../../../components/ha-textfield";
|
||||
import { AreaRegistryEntryMutableParams } from "../../../data/area_registry";
|
||||
import { CropOptions } from "../../../dialogs/image-cropper-dialog/show-image-cropper-dialog";
|
||||
@@ -32,6 +33,8 @@ class DialogAreaDetail extends LitElement {
|
||||
|
||||
@state() private _picture!: string | null;
|
||||
|
||||
@state() private _icon!: string | null;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _params?: AreaRegistryDetailDialogParams;
|
||||
@@ -46,6 +49,7 @@ class DialogAreaDetail extends LitElement {
|
||||
this._name = this._params.entry ? this._params.entry.name : "";
|
||||
this._aliases = this._params.entry ? this._params.entry.aliases : [];
|
||||
this._picture = this._params.entry?.picture || null;
|
||||
this._icon = this._params.entry?.icon || null;
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
@@ -101,6 +105,13 @@ class DialogAreaDetail extends LitElement {
|
||||
dialogInitialFocus
|
||||
></ha-textfield>
|
||||
|
||||
<ha-icon-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._icon}
|
||||
@value-changed=${this._iconChanged}
|
||||
.label=${this.hass.localize("ui.panel.config.areas.editor.icon")}
|
||||
></ha-icon-picker>
|
||||
|
||||
<ha-picture-upload
|
||||
.hass=${this.hass}
|
||||
.value=${this._picture}
|
||||
@@ -152,23 +163,30 @@ class DialogAreaDetail extends LitElement {
|
||||
this._name = ev.target.value;
|
||||
}
|
||||
|
||||
private _iconChanged(ev) {
|
||||
this._error = undefined;
|
||||
this._icon = ev.detail.value;
|
||||
}
|
||||
|
||||
private _pictureChanged(ev: ValueChangedEvent<string | null>) {
|
||||
this._error = undefined;
|
||||
this._picture = (ev.target as HaPictureUpload).value;
|
||||
}
|
||||
|
||||
private async _updateEntry() {
|
||||
const create = !this._params!.entry;
|
||||
this._submitting = true;
|
||||
try {
|
||||
const values: AreaRegistryEntryMutableParams = {
|
||||
name: this._name.trim(),
|
||||
picture: this._picture,
|
||||
picture: this._picture || (create ? undefined : null),
|
||||
icon: this._icon || (create ? undefined : null),
|
||||
aliases: this._aliases,
|
||||
};
|
||||
if (this._params!.entry) {
|
||||
await this._params!.updateEntry!(values);
|
||||
} else {
|
||||
if (create) {
|
||||
await this._params!.createEntry!(values);
|
||||
} else {
|
||||
await this._params!.updateEntry!(values);
|
||||
}
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
@@ -189,6 +207,7 @@ class DialogAreaDetail extends LitElement {
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-textfield,
|
||||
ha-icon-picker,
|
||||
ha-picture-upload {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
|
@@ -1,11 +1,9 @@
|
||||
import { consume } from "@lit-labs/context";
|
||||
import "@material/mwc-button";
|
||||
import "@material/mwc-list";
|
||||
import { mdiDelete, mdiDotsVertical, mdiImagePlus, mdiPencil } from "@mdi/js";
|
||||
import {
|
||||
HassEntity,
|
||||
UnsubscribeFunc,
|
||||
} from "home-assistant-js-websocket/dist/types";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { HassEntity } from "home-assistant-js-websocket/dist/types";
|
||||
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -18,33 +16,31 @@ import { afterNextRender } from "../../../common/util/render-status";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../../components/ha-list-item";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
deleteAreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
updateAreaRegistryEntry,
|
||||
} from "../../../data/area_registry";
|
||||
import { AutomationEntity } from "../../../data/automation";
|
||||
import { fullEntitiesContext } from "../../../data/context";
|
||||
import {
|
||||
computeDeviceName,
|
||||
DeviceRegistryEntry,
|
||||
computeDeviceName,
|
||||
sortDeviceRegistryByName,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../../../data/device_registry";
|
||||
import {
|
||||
computeEntityRegistryName,
|
||||
EntityRegistryEntry,
|
||||
computeEntityRegistryName,
|
||||
sortEntityRegistryByName,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../../data/entity_registry";
|
||||
import { SceneEntity } from "../../../data/scene";
|
||||
import { ScriptEntity } from "../../../data/script";
|
||||
import { findRelated, RelatedResult } from "../../../data/search";
|
||||
import { RelatedResult, findRelated } from "../../../data/search";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog";
|
||||
import "../../../layouts/hass-error-screen";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "../../logbook/ha-logbook";
|
||||
@@ -52,7 +48,6 @@ import {
|
||||
loadAreaRegistryDetailDialog,
|
||||
showAreaRegistryDetailDialog,
|
||||
} from "./show-dialog-area-registry-detail";
|
||||
import "../../../components/ha-list-item";
|
||||
|
||||
declare type NameAndEntity<EntityType extends HassEntity> = {
|
||||
name: string;
|
||||
@@ -60,7 +55,7 @@ declare type NameAndEntity<EntityType extends HassEntity> = {
|
||||
};
|
||||
|
||||
@customElement("ha-config-area-page")
|
||||
class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
class HaConfigAreaPage extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public areaId!: string;
|
||||
@@ -71,24 +66,14 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property({ type: Boolean }) public showAdvanced = false;
|
||||
|
||||
@state() public _areas!: AreaRegistryEntry[];
|
||||
|
||||
@state() public _devices!: DeviceRegistryEntry[];
|
||||
|
||||
@state() public _entities!: EntityRegistryEntry[];
|
||||
@state()
|
||||
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||
_entityReg!: EntityRegistryEntry[];
|
||||
|
||||
@state() private _related?: RelatedResult;
|
||||
|
||||
private _logbookTime = { recent: 86400 };
|
||||
|
||||
private _area = memoizeOne(
|
||||
(
|
||||
areaId: string,
|
||||
areas: AreaRegistryEntry[]
|
||||
): AreaRegistryEntry | undefined =>
|
||||
areas.find((area) => area.area_id === areaId)
|
||||
);
|
||||
|
||||
private _memberships = memoizeOne(
|
||||
(
|
||||
areaId: string,
|
||||
@@ -150,26 +135,12 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
|
||||
return [
|
||||
subscribeAreaRegistry(this.hass.connection, (areas) => {
|
||||
this._areas = areas;
|
||||
}),
|
||||
subscribeDeviceRegistry(this.hass.connection, (entries) => {
|
||||
this._devices = entries;
|
||||
}),
|
||||
subscribeEntityRegistry(this.hass.connection, (entries) => {
|
||||
this._entities = entries;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._areas || !this._devices || !this._entities) {
|
||||
if (!this.hass.areas || !this.hass.devices || !this.hass.entities) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const area = this._area(this.areaId, this._areas);
|
||||
const area = this.hass.areas[this.areaId];
|
||||
|
||||
if (!area) {
|
||||
return html`
|
||||
@@ -182,8 +153,8 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
|
||||
const memberships = this._memberships(
|
||||
this.areaId,
|
||||
this._devices,
|
||||
this._entities
|
||||
Object.values(this.hass.devices),
|
||||
this._entityReg
|
||||
);
|
||||
const { devices, entities } = memberships;
|
||||
|
||||
@@ -617,7 +588,7 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _renderScript(name: string, entityState: ScriptEntity) {
|
||||
const entry = this._entities.find(
|
||||
const entry = this._entityReg.find(
|
||||
(e) => e.entity_id === entityState.entity_id
|
||||
);
|
||||
let url = `/config/script/show/${entityState.entity_id}`;
|
||||
@@ -657,7 +628,7 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private async _deleteConfirm() {
|
||||
const area = this._area(this.areaId, this._areas);
|
||||
const area = this.hass.areas[this.areaId];
|
||||
showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.areas.delete.confirmation_title",
|
||||
@@ -686,7 +657,6 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
font-weight: 500;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
img {
|
||||
border-radius: var(--ha-card-border-radius, 12px);
|
||||
width: 100%;
|
||||
|
@@ -1,7 +1,13 @@
|
||||
import { mdiHelpCircle, mdiPlus } from "@mdi/js";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { formatListWithAnds } from "../../../common/string/format-list";
|
||||
@@ -11,19 +17,9 @@ import "../../../components/ha-svg-icon";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
createAreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
} from "../../../data/area_registry";
|
||||
import {
|
||||
DeviceRegistryEntry,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../../../data/device_registry";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../../data/entity_registry";
|
||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import "../ha-config-section";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
@@ -33,7 +29,7 @@ import {
|
||||
} from "./show-dialog-area-registry-detail";
|
||||
|
||||
@customElement("ha-config-areas-dashboard")
|
||||
export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
||||
export class HaConfigAreasDashboard extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public isWide = false;
|
||||
@@ -42,24 +38,18 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@state() private _areas!: AreaRegistryEntry[];
|
||||
|
||||
@state() private _devices!: DeviceRegistryEntry[];
|
||||
|
||||
@state() private _entities!: EntityRegistryEntry[];
|
||||
|
||||
private _processAreas = memoizeOne(
|
||||
(
|
||||
areas: AreaRegistryEntry[],
|
||||
devices: DeviceRegistryEntry[],
|
||||
entities: EntityRegistryEntry[]
|
||||
) =>
|
||||
areas.map((area) => {
|
||||
areas: HomeAssistant["areas"],
|
||||
devices: HomeAssistant["devices"],
|
||||
entities: HomeAssistant["entities"]
|
||||
) => {
|
||||
const processArea = (area: AreaRegistryEntry) => {
|
||||
let noDevicesInArea = 0;
|
||||
let noServicesInArea = 0;
|
||||
let noEntitiesInArea = 0;
|
||||
|
||||
for (const device of devices) {
|
||||
for (const device of Object.values(devices)) {
|
||||
if (device.area_id === area.area_id) {
|
||||
if (device.entry_type === "service") {
|
||||
noServicesInArea++;
|
||||
@@ -69,7 +59,7 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
for (const entity of entities) {
|
||||
for (const entity of Object.values(entities)) {
|
||||
if (entity.area_id === area.area_id) {
|
||||
noEntitiesInArea++;
|
||||
}
|
||||
@@ -81,24 +71,22 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
||||
services: noServicesInArea,
|
||||
entities: noEntitiesInArea,
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
return Object.values(areas).map(processArea);
|
||||
}
|
||||
);
|
||||
|
||||
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
|
||||
return [
|
||||
subscribeAreaRegistry(this.hass.connection, (areas) => {
|
||||
this._areas = areas;
|
||||
}),
|
||||
subscribeDeviceRegistry(this.hass.connection, (entries) => {
|
||||
this._devices = entries;
|
||||
}),
|
||||
subscribeEntityRegistry(this.hass.connection, (entries) => {
|
||||
this._entities = entries;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const areas =
|
||||
!this.hass.areas || !this.hass.devices || !this.hass.entities
|
||||
? undefined
|
||||
: this._processAreas(
|
||||
this.hass.areas,
|
||||
this.hass.devices,
|
||||
this.hass.entities
|
||||
);
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
@@ -115,52 +103,11 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
||||
@click=${this._showHelp}
|
||||
></ha-icon-button>
|
||||
<div class="container">
|
||||
${!this._areas || !this._devices || !this._entities
|
||||
? ""
|
||||
: this._processAreas(
|
||||
this._areas,
|
||||
this._devices,
|
||||
this._entities
|
||||
).map(
|
||||
(area) =>
|
||||
html`<a href=${`/config/areas/area/${area.area_id}`}
|
||||
><ha-card outlined>
|
||||
<div
|
||||
style=${styleMap({
|
||||
backgroundImage: area.picture
|
||||
? `url(${area.picture})`
|
||||
: undefined,
|
||||
})}
|
||||
class="picture ${!area.picture ? "placeholder" : ""}"
|
||||
></div>
|
||||
<h1 class="card-header">${area.name}</h1>
|
||||
<div class="card-content">
|
||||
<div>
|
||||
${formatListWithAnds(
|
||||
this.hass.locale,
|
||||
[
|
||||
area.devices &&
|
||||
this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.devices",
|
||||
{ count: area.devices }
|
||||
),
|
||||
area.services &&
|
||||
this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.services",
|
||||
{ count: area.services }
|
||||
),
|
||||
area.entities &&
|
||||
this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.entities",
|
||||
{ count: area.entities }
|
||||
),
|
||||
].filter((v): v is string => Boolean(v))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ha-card></a
|
||||
>`
|
||||
)}
|
||||
${areas?.length
|
||||
? html`<div class="areas">
|
||||
${areas.map((area) => this._renderArea(area))}
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
@@ -176,13 +123,55 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderArea(area) {
|
||||
return html`<a href=${`/config/areas/area/${area.area_id}`}>
|
||||
<ha-card outlined>
|
||||
<div
|
||||
style=${styleMap({
|
||||
backgroundImage: area.picture ? `url(${area.picture})` : undefined,
|
||||
})}
|
||||
class="picture ${!area.picture ? "placeholder" : ""}"
|
||||
>
|
||||
${!area.picture && area.icon
|
||||
? html`<ha-icon .icon=${area.icon}></ha-icon>`
|
||||
: ""}
|
||||
</div>
|
||||
<h1 class="card-header">${area.name}</h1>
|
||||
<div class="card-content">
|
||||
<div>
|
||||
${formatListWithAnds(
|
||||
this.hass.locale,
|
||||
[
|
||||
area.devices &&
|
||||
this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.devices",
|
||||
{ count: area.devices }
|
||||
),
|
||||
area.services &&
|
||||
this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.services",
|
||||
{ count: area.services }
|
||||
),
|
||||
area.entities &&
|
||||
this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.entities",
|
||||
{ count: area.entities }
|
||||
),
|
||||
].filter((v): v is string => Boolean(v))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
</a>`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
loadAreaRegistryDetailDialog();
|
||||
}
|
||||
|
||||
private _createArea() {
|
||||
this._openDialog();
|
||||
this._openAreaDialog();
|
||||
}
|
||||
|
||||
private _showHelp() {
|
||||
@@ -202,7 +191,7 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
||||
});
|
||||
}
|
||||
|
||||
private _openDialog(entry?: AreaRegistryEntry) {
|
||||
private _openAreaDialog(entry?: AreaRegistryEntry) {
|
||||
showAreaRegistryDetailDialog(this, {
|
||||
entry,
|
||||
createEntry: async (values) =>
|
||||
@@ -213,14 +202,17 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
.container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
grid-gap: 16px 16px;
|
||||
padding: 8px 16px 16px;
|
||||
margin: 0 auto 64px auto;
|
||||
max-width: 2000px;
|
||||
}
|
||||
.container > * {
|
||||
.areas {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
grid-gap: 16px 16px;
|
||||
max-width: 2000px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.areas > * {
|
||||
max-width: 500px;
|
||||
}
|
||||
ha-card {
|
||||
@@ -239,6 +231,12 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
||||
background-position: center;
|
||||
position: relative;
|
||||
}
|
||||
.placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
--mdc-icon-size: 48px;
|
||||
}
|
||||
.picture.placeholder::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
|
@@ -77,7 +77,7 @@ export default class HaAutomationAction extends LitElement {
|
||||
return html`
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
.disabled=${!this._showReorder}
|
||||
.disabled=${!this._showReorder || this.disabled}
|
||||
@item-moved=${this._actionMoved}
|
||||
group="actions"
|
||||
.path=${this.path}
|
||||
@@ -101,7 +101,7 @@ export default class HaAutomationAction extends LitElement {
|
||||
@value-changed=${this._actionChanged}
|
||||
.hass=${this.hass}
|
||||
>
|
||||
${this._showReorder
|
||||
${this._showReorder && !this.disabled
|
||||
? html`
|
||||
<div class="handle" slot="icons">
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
|
@@ -122,7 +122,7 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
return html`
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
.disabled=${!this._showReorder}
|
||||
.disabled=${!this._showReorder || this.disabled}
|
||||
group="choose-options"
|
||||
.path=${[...(this.path ?? []), "choose"]}
|
||||
>
|
||||
@@ -148,7 +148,7 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
? ""
|
||||
: this._getDescription(option))}
|
||||
</h3>
|
||||
${this._showReorder
|
||||
${this._showReorder && !this.disabled
|
||||
? html`
|
||||
<div class="handle" slot="icons">
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
|
@@ -117,7 +117,7 @@ export default class HaAutomationCondition extends LitElement {
|
||||
return html`
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
.disabled=${!this._showReorder}
|
||||
.disabled=${!this._showReorder || this.disabled}
|
||||
@item-moved=${this._conditionMoved}
|
||||
group="conditions"
|
||||
.path=${this.path}
|
||||
@@ -141,7 +141,7 @@ export default class HaAutomationCondition extends LitElement {
|
||||
@value-changed=${this._conditionChanged}
|
||||
.hass=${this.hass}
|
||||
>
|
||||
${this._showReorder
|
||||
${this._showReorder && !this.disabled
|
||||
? html`
|
||||
<div class="handle" slot="icons">
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
|
@@ -74,7 +74,7 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
return html`
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
.disabled=${!this._showReorder}
|
||||
.disabled=${!this._showReorder || this.disabled}
|
||||
@item-moved=${this._triggerMoved}
|
||||
group="triggers"
|
||||
.path=${this.path}
|
||||
@@ -97,7 +97,7 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this._showReorder
|
||||
${this._showReorder && !this.disabled
|
||||
? html`
|
||||
<div class="handle" slot="icons">
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
|
@@ -0,0 +1,88 @@
|
||||
import {
|
||||
mdiAccessPoint,
|
||||
mdiChatProcessing,
|
||||
mdiChatQuestion,
|
||||
mdiExportVariant,
|
||||
} from "@mdi/js";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import {
|
||||
NetworkType,
|
||||
getMatterNodeDiagnostics,
|
||||
} from "../../../../../../data/matter";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import { showMatterReinterviewNodeDialog } from "../../../../integrations/integration-panels/matter/show-dialog-matter-reinterview-node";
|
||||
import { showMatterPingNodeDialog } from "../../../../integrations/integration-panels/matter/show-dialog-matter-ping-node";
|
||||
import { showMatterOpenCommissioningWindowDialog } from "../../../../integrations/integration-panels/matter/show-dialog-matter-open-commissioning-window";
|
||||
import type { DeviceAction } from "../../../ha-config-device-page";
|
||||
import { showMatterManageFabricsDialog } from "../../../../integrations/integration-panels/matter/show-dialog-matter-manage-fabrics";
|
||||
import { navigate } from "../../../../../../common/navigate";
|
||||
|
||||
export const getMatterDeviceActions = async (
|
||||
el: HTMLElement,
|
||||
hass: HomeAssistant,
|
||||
device: DeviceRegistryEntry
|
||||
): Promise<DeviceAction[]> => {
|
||||
if (device.via_device_id !== null) {
|
||||
// only show device actions for top level nodes (so not bridged)
|
||||
return [];
|
||||
}
|
||||
|
||||
const nodeDiagnostics = await getMatterNodeDiagnostics(hass, device.id);
|
||||
|
||||
const actions: DeviceAction[] = [];
|
||||
|
||||
if (nodeDiagnostics.available) {
|
||||
// actions that can only be performed if the device is alive
|
||||
actions.push({
|
||||
label: hass.localize(
|
||||
"ui.panel.config.matter.device_actions.open_commissioning_window"
|
||||
),
|
||||
icon: mdiExportVariant,
|
||||
action: () =>
|
||||
showMatterOpenCommissioningWindowDialog(el, {
|
||||
device_id: device.id,
|
||||
}),
|
||||
});
|
||||
actions.push({
|
||||
label: hass.localize(
|
||||
"ui.panel.config.matter.device_actions.manage_fabrics"
|
||||
),
|
||||
icon: mdiExportVariant,
|
||||
action: () =>
|
||||
showMatterManageFabricsDialog(el, {
|
||||
device_id: device.id,
|
||||
}),
|
||||
});
|
||||
actions.push({
|
||||
label: hass.localize(
|
||||
"ui.panel.config.matter.device_actions.reinterview_device"
|
||||
),
|
||||
icon: mdiChatProcessing,
|
||||
action: () =>
|
||||
showMatterReinterviewNodeDialog(el, {
|
||||
device_id: device.id,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
if (nodeDiagnostics.network_type === NetworkType.THREAD) {
|
||||
actions.push({
|
||||
label: hass.localize(
|
||||
"ui.panel.config.matter.device_actions.view_thread_network"
|
||||
),
|
||||
icon: mdiAccessPoint,
|
||||
action: () => navigate("/config/thread"),
|
||||
});
|
||||
}
|
||||
|
||||
actions.push({
|
||||
label: hass.localize("ui.panel.config.matter.device_actions.ping_device"),
|
||||
icon: mdiChatQuestion,
|
||||
action: () =>
|
||||
showMatterPingNodeDialog(el, {
|
||||
device_id: device.id,
|
||||
}),
|
||||
});
|
||||
|
||||
return actions;
|
||||
};
|
@@ -0,0 +1,174 @@
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../../../components/ha-expansion-panel";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import {
|
||||
getMatterNodeDiagnostics,
|
||||
MatterNodeDiagnostics,
|
||||
} from "../../../../../../data/matter";
|
||||
import "@material/mwc-list";
|
||||
import "../../../../../../components/ha-list-item";
|
||||
import { SubscribeMixin } from "../../../../../../mixins/subscribe-mixin";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
|
||||
@customElement("ha-device-info-matter")
|
||||
export class HaDeviceInfoMatter extends SubscribeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public device!: DeviceRegistryEntry;
|
||||
|
||||
@state() private _nodeDiagnostics?: MatterNodeDiagnostics;
|
||||
|
||||
public willUpdate(changedProperties: PropertyValues) {
|
||||
super.willUpdate(changedProperties);
|
||||
if (changedProperties.has("device")) {
|
||||
this._fetchNodeDetails();
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchNodeDetails() {
|
||||
if (!this.device) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.device.via_device_id !== null) {
|
||||
// only show device details for top level nodes (so not bridged)
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this._nodeDiagnostics = await getMatterNodeDiagnostics(
|
||||
this.hass,
|
||||
this.device.id
|
||||
);
|
||||
} catch (err: any) {
|
||||
this._nodeDiagnostics = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._nodeDiagnostics) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<ha-expansion-panel
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.matter.device_info.device_info"
|
||||
)}
|
||||
>
|
||||
<div class="row">
|
||||
<span class="name"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.matter.device_info.node_id"
|
||||
)}:</span
|
||||
>
|
||||
<span class="value">${this._nodeDiagnostics.node_id}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="name"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.matter.device_info.network_type"
|
||||
)}:</span
|
||||
>
|
||||
<span class="value"
|
||||
>${this.hass.localize(
|
||||
`ui.panel.config.matter.network_type.${this._nodeDiagnostics.network_type}`
|
||||
)}</span
|
||||
>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="name"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.matter.device_info.node_type"
|
||||
)}:</span
|
||||
>
|
||||
<span class="value"
|
||||
>${this.hass.localize(
|
||||
`ui.panel.config.matter.node_type.${this._nodeDiagnostics.node_type}`
|
||||
)}</span
|
||||
>
|
||||
</div>
|
||||
${this._nodeDiagnostics.network_name
|
||||
? html`
|
||||
<div class="row">
|
||||
<span class="name"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.matter.device_info.network_name"
|
||||
)}:</span
|
||||
>
|
||||
<span class="value">${this._nodeDiagnostics.network_name}</span>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
${this._nodeDiagnostics.mac_address
|
||||
? html`
|
||||
<div class="row">
|
||||
<span class="name"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.matter.device_info.mac_address"
|
||||
)}:</span
|
||||
>
|
||||
<span class="value">${this._nodeDiagnostics.mac_address}</span>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<div class="row">
|
||||
<span class="name"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.matter.device_info.ip_adresses"
|
||||
)}:</span
|
||||
>
|
||||
<span class="value"
|
||||
>${this._nodeDiagnostics.ip_adresses.map(
|
||||
(ip) => html`${ip}<br />`
|
||||
)}</span
|
||||
>
|
||||
</div>
|
||||
</ha-expansion-panel>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
h4 {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
div {
|
||||
word-break: break-all;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
.value {
|
||||
text-align: right;
|
||||
}
|
||||
ha-expansion-panel {
|
||||
margin: 8px -16px 0;
|
||||
--expansion-panel-summary-padding: 0 16px;
|
||||
--expansion-panel-content-padding: 0 16px;
|
||||
--ha-card-border-radius: 0px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-device-info-matter": HaDeviceInfoMatter;
|
||||
}
|
||||
}
|
@@ -1099,6 +1099,17 @@ export class HaConfigDevicePage extends LitElement {
|
||||
);
|
||||
deviceActions.push(...actions);
|
||||
}
|
||||
if (domains.includes("matter")) {
|
||||
const matter = await import(
|
||||
"./device-detail/integration-elements/matter/device-actions"
|
||||
);
|
||||
const actions = await matter.getMatterDeviceActions(
|
||||
this,
|
||||
this.hass,
|
||||
device
|
||||
);
|
||||
deviceActions.push(...actions);
|
||||
}
|
||||
|
||||
this._deviceActions = deviceActions;
|
||||
}
|
||||
@@ -1204,6 +1215,17 @@ export class HaConfigDevicePage extends LitElement {
|
||||
></ha-device-info-zwave_js>
|
||||
`);
|
||||
}
|
||||
if (domains.includes("matter")) {
|
||||
import(
|
||||
"./device-detail/integration-elements/matter/ha-device-info-matter"
|
||||
);
|
||||
deviceInfo.push(html`
|
||||
<ha-device-info-matter
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-info-matter>
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
private async _showSettings() {
|
||||
|
@@ -0,0 +1,169 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-circular-progress";
|
||||
import { createCloseHeading } from "../../../../../components/ha-dialog";
|
||||
import "../../../../../components/ha-qr-code";
|
||||
import {
|
||||
MatterFabricData,
|
||||
MatterNodeDiagnostics,
|
||||
getMatterNodeDiagnostics,
|
||||
removeMatterFabric,
|
||||
} from "../../../../../data/matter";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyleDialog } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { MatterManageFabricsDialogParams } from "./show-dialog-matter-manage-fabrics";
|
||||
|
||||
const NABUCASA_FABRIC = 4939;
|
||||
|
||||
@customElement("dialog-matter-manage-fabrics")
|
||||
class DialogMatterManageFabrics extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private device_id?: string;
|
||||
|
||||
@state() private _nodeDiagnostics?: MatterNodeDiagnostics;
|
||||
|
||||
public async showDialog(
|
||||
params: MatterManageFabricsDialogParams
|
||||
): Promise<void> {
|
||||
this.device_id = params.device_id;
|
||||
this._fetchNodeDetails();
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.device_id) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
hideActions
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass.localize("ui.panel.config.matter.manage_fabrics.title")
|
||||
)}
|
||||
>
|
||||
<p>
|
||||
${this.hass.localize("ui.panel.config.matter.manage_fabrics.fabrics")}
|
||||
</p>
|
||||
${this._nodeDiagnostics
|
||||
? html`<mwc-list>
|
||||
${this._nodeDiagnostics.active_fabrics.map(
|
||||
(fabric) =>
|
||||
html`<ha-list-item
|
||||
noninteractive
|
||||
.hasMeta=${this._nodeDiagnostics?.available &&
|
||||
fabric.vendor_id !== NABUCASA_FABRIC}
|
||||
>${fabric.vendor_name ||
|
||||
fabric.fabric_label ||
|
||||
fabric.vendor_id}
|
||||
<ha-icon-button
|
||||
@click=${this._removeFabric}
|
||||
slot="meta"
|
||||
.fabric=${fabric}
|
||||
.path=${mdiClose}
|
||||
></ha-icon-button>
|
||||
</ha-list-item>`
|
||||
)}
|
||||
</mwc-list>`
|
||||
: html`<div class="center">
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
</div>`}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _fetchNodeDetails() {
|
||||
if (!this.device_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this._nodeDiagnostics = await getMatterNodeDiagnostics(
|
||||
this.hass,
|
||||
this.device_id
|
||||
);
|
||||
} catch (err: any) {
|
||||
this._nodeDiagnostics = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private async _removeFabric(ev) {
|
||||
const fabric: MatterFabricData = ev.target.fabric;
|
||||
const fabricName =
|
||||
fabric.vendor_name || fabric.fabric_label || fabric.vendor_id.toString();
|
||||
const confirm = await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.matter.manage_fabrics.remove_fabric_confirm_header",
|
||||
{ fabric: fabricName }
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.matter.manage_fabrics.remove_fabric_confirm_text",
|
||||
{ fabric: fabricName }
|
||||
),
|
||||
warning: true,
|
||||
});
|
||||
|
||||
if (!confirm) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await removeMatterFabric(this.hass, this.device_id!, fabric.fabric_index);
|
||||
this._fetchNodeDetails();
|
||||
} catch (err: any) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.matter.manage_fabrics.remove_fabric_failed_header",
|
||||
{ fabric: fabricName }
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.matter.manage_fabrics.remove_fabric_failed_text"
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this.device_id = undefined;
|
||||
this._nodeDiagnostics = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-dialog {
|
||||
--dialog-content-padding: 0;
|
||||
--mdc-list-side-padding: 24px;
|
||||
--mdc-list-side-padding-right: 16px;
|
||||
--mdc-list-item-meta-size: 48px;
|
||||
}
|
||||
p {
|
||||
margin: 8px 24px;
|
||||
}
|
||||
.center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-matter-manage-fabrics": DialogMatterManageFabrics;
|
||||
}
|
||||
}
|
@@ -0,0 +1,200 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { mdiCheckCircle, mdiCloseCircle } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-circular-progress";
|
||||
import "../../../../../components/ha-qr-code";
|
||||
import { createCloseHeading } from "../../../../../components/ha-dialog";
|
||||
import {
|
||||
openMatterCommissioningWindow,
|
||||
MatterCommissioningParameters,
|
||||
} from "../../../../../data/matter";
|
||||
import { haStyleDialog } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { MatterOpenCommissioningWindowDialogParams } from "./show-dialog-matter-open-commissioning-window";
|
||||
|
||||
@customElement("dialog-matter-open-commissioning-window")
|
||||
class DialogMatterOpenCommissioningWindow extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private device_id?: string;
|
||||
|
||||
@state() private _status?: string;
|
||||
|
||||
@state() private _commissionParams?: MatterCommissioningParameters;
|
||||
|
||||
public async showDialog(
|
||||
params: MatterOpenCommissioningWindowDialogParams
|
||||
): Promise<void> {
|
||||
this.device_id = params.device_id;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.device_id) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass.localize(
|
||||
"ui.panel.config.matter.open_commissioning_window.title"
|
||||
)
|
||||
)}
|
||||
>
|
||||
${this._commissionParams
|
||||
? html`
|
||||
<div class="flex-container">
|
||||
<ha-svg-icon
|
||||
.path=${mdiCheckCircle}
|
||||
class="success"
|
||||
></ha-svg-icon>
|
||||
<div class="status">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.open_commissioning_window.sharing_code"
|
||||
)}: <b>${this._commissionParams.setup_manual_code}</b>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<ha-qr-code
|
||||
.data=${this._commissionParams.setup_qr_code}
|
||||
errorCorrectionLevel="quartile"
|
||||
scale="6"
|
||||
></ha-qr-code>
|
||||
<div></div>
|
||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
${this.hass.localize("ui.common.close")}
|
||||
</mwc-button>
|
||||
`
|
||||
: this._status === "started"
|
||||
? html`
|
||||
<div class="flex-container">
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
<div class="status">
|
||||
<p>
|
||||
<b>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.open_commissioning_window.in_progress"
|
||||
)}
|
||||
</b>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
${this.hass.localize("ui.common.close")}
|
||||
</mwc-button>
|
||||
`
|
||||
: this._status === "failed"
|
||||
? html`
|
||||
<div class="flex-container">
|
||||
<ha-svg-icon
|
||||
.path=${mdiCloseCircle}
|
||||
class="failed"
|
||||
></ha-svg-icon>
|
||||
<div class="status">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.open_commissioning_window.failed"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
${this.hass.localize("ui.common.close")}
|
||||
</mwc-button>
|
||||
`
|
||||
: html`
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.open_commissioning_window.introduction"
|
||||
)}
|
||||
</p>
|
||||
<mwc-button slot="primaryAction" @click=${this._start}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.open_commissioning_window.start_commissioning"
|
||||
)}
|
||||
</mwc-button>
|
||||
`}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _start(): Promise<void> {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
this._status = "started";
|
||||
this._commissionParams = undefined;
|
||||
try {
|
||||
this._commissionParams = await openMatterCommissioningWindow(
|
||||
this.hass,
|
||||
this.device_id!
|
||||
);
|
||||
} catch (e) {
|
||||
this._status = "failed";
|
||||
}
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this.device_id = undefined;
|
||||
this._status = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
.success {
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.failed {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
.flex-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stages {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.stage ha-svg-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.stage {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
ha-svg-icon {
|
||||
width: 68px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
ha-qr-code {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.flex-container ha-circular-progress,
|
||||
.flex-container ha-svg-icon {
|
||||
margin-right: 20px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-matter-open-commissioning-window": DialogMatterOpenCommissioningWindow;
|
||||
}
|
||||
}
|
@@ -0,0 +1,199 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { mdiCheckCircle, mdiCloseCircle } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-circular-progress";
|
||||
import { createCloseHeading } from "../../../../../components/ha-dialog";
|
||||
import { pingMatterNode, MatterPingResult } from "../../../../../data/matter";
|
||||
import { haStyleDialog } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { MatterPingNodeDialogParams } from "./show-dialog-matter-ping-node";
|
||||
|
||||
@customElement("dialog-matter-ping-node")
|
||||
class DialogMatterPingNode extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private device_id?: string;
|
||||
|
||||
@state() private _status?: string;
|
||||
|
||||
@state() private _pingResult?: MatterPingResult;
|
||||
|
||||
public async showDialog(params: MatterPingNodeDialogParams): Promise<void> {
|
||||
this.device_id = params.device_id;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.device_id) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass.localize("ui.panel.config.matter.ping_node.title")
|
||||
)}
|
||||
>
|
||||
${this._pingResult
|
||||
? html`
|
||||
<div class="flex-container">
|
||||
<ha-svg-icon
|
||||
.path=${mdiCheckCircle}
|
||||
class="success"
|
||||
></ha-svg-icon>
|
||||
<div class="status">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.ping_node.ping_complete"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<mwc-list>
|
||||
${Object.entries(this._pingResult).map(
|
||||
([ip, success]) =>
|
||||
html`<ha-list-item hasMeta
|
||||
>${ip}
|
||||
<ha-icon
|
||||
slot="meta"
|
||||
icon=${success ? "mdi:check" : "mdi:close"}
|
||||
></ha-icon>
|
||||
</ha-list-item>`
|
||||
)}
|
||||
</mwc-list>
|
||||
</div>
|
||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
${this.hass.localize("ui.common.close")}
|
||||
</mwc-button>
|
||||
`
|
||||
: this._status === "started"
|
||||
? html`
|
||||
<div class="flex-container">
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
<div class="status">
|
||||
<p>
|
||||
<b>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.ping_node.in_progress"
|
||||
)}
|
||||
</b>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
${this.hass.localize("ui.common.close")}
|
||||
</mwc-button>
|
||||
`
|
||||
: this._status === "failed"
|
||||
? html`
|
||||
<div class="flex-container">
|
||||
<ha-svg-icon
|
||||
.path=${mdiCloseCircle}
|
||||
class="failed"
|
||||
></ha-svg-icon>
|
||||
<div class="status">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.ping_node.ping_failed"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
${this.hass.localize("ui.common.close")}
|
||||
</mwc-button>
|
||||
`
|
||||
: html`
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.ping_node.introduction"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<em>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.ping_node.battery_device_warning"
|
||||
)}
|
||||
</em>
|
||||
</p>
|
||||
<mwc-button slot="primaryAction" @click=${this._startPing}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.ping_node.start_ping"
|
||||
)}
|
||||
</mwc-button>
|
||||
`}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _startPing(): Promise<void> {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
this._status = "started";
|
||||
try {
|
||||
this._pingResult = await pingMatterNode(this.hass, this.device_id!);
|
||||
} catch (err) {
|
||||
this._status = "failed";
|
||||
}
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this.device_id = undefined;
|
||||
this._status = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
.success {
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.failed {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
.flex-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stages {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.stage ha-svg-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.stage {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
ha-svg-icon {
|
||||
width: 68px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.flex-container ha-circular-progress,
|
||||
.flex-container ha-svg-icon {
|
||||
margin-right: 20px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-matter-ping-node": DialogMatterPingNode;
|
||||
}
|
||||
}
|
@@ -0,0 +1,193 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { mdiCheckCircle, mdiCloseCircle } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-circular-progress";
|
||||
import { createCloseHeading } from "../../../../../components/ha-dialog";
|
||||
import { interviewMatterNode } from "../../../../../data/matter";
|
||||
import { haStyleDialog } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { MatterReinterviewNodeDialogParams } from "./show-dialog-matter-reinterview-node";
|
||||
|
||||
@customElement("dialog-matter-reinterview-node")
|
||||
class DialogMatterReinterviewNode extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private device_id?: string;
|
||||
|
||||
@state() private _status?: string;
|
||||
|
||||
public async showDialog(
|
||||
params: MatterReinterviewNodeDialogParams
|
||||
): Promise<void> {
|
||||
this.device_id = params.device_id;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.device_id) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass.localize("ui.panel.config.matter.reinterview_node.title")
|
||||
)}
|
||||
>
|
||||
${!this._status
|
||||
? html`
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.reinterview_node.introduction"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<em>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.reinterview_node.battery_device_warning"
|
||||
)}
|
||||
</em>
|
||||
</p>
|
||||
<mwc-button slot="primaryAction" @click=${this._startReinterview}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.reinterview_node.start_reinterview"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: this._status === "started"
|
||||
? html`
|
||||
<div class="flex-container">
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
<div class="status">
|
||||
<p>
|
||||
<b>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.reinterview_node.in_progress"
|
||||
)}
|
||||
</b>
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.reinterview_node.run_in_background"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
${this.hass.localize("ui.common.close")}
|
||||
</mwc-button>
|
||||
`
|
||||
: this._status === "failed"
|
||||
? html`
|
||||
<div class="flex-container">
|
||||
<ha-svg-icon
|
||||
.path=${mdiCloseCircle}
|
||||
class="failed"
|
||||
></ha-svg-icon>
|
||||
<div class="status">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.reinterview_node.interview_failed"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
${this.hass.localize("ui.common.close")}
|
||||
</mwc-button>
|
||||
`
|
||||
: this._status === "finished"
|
||||
? html`
|
||||
<div class="flex-container">
|
||||
<ha-svg-icon
|
||||
.path=${mdiCheckCircle}
|
||||
class="success"
|
||||
></ha-svg-icon>
|
||||
<div class="status">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.reinterview_node.interview_complete"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
${this.hass.localize("ui.common.close")}
|
||||
</mwc-button>
|
||||
`
|
||||
: nothing}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _startReinterview(): Promise<void> {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
this._status = "started";
|
||||
try {
|
||||
await interviewMatterNode(this.hass, this.device_id!);
|
||||
this._status = "finished";
|
||||
} catch (err) {
|
||||
this._status = "failed";
|
||||
}
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this.device_id = undefined;
|
||||
this._status = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
.success {
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.failed {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
.flex-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stages {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.stage ha-svg-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.stage {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
ha-svg-icon {
|
||||
width: 68px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.flex-container ha-circular-progress,
|
||||
.flex-container ha-svg-icon {
|
||||
margin-right: 20px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-matter-reinterview-node": DialogMatterReinterviewNode;
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
|
||||
export interface MatterManageFabricsDialogParams {
|
||||
device_id: string;
|
||||
}
|
||||
|
||||
export const loadManageFabricsDialog = () =>
|
||||
import("./dialog-matter-manage-fabrics");
|
||||
|
||||
export const showMatterManageFabricsDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: MatterManageFabricsDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-matter-manage-fabrics",
|
||||
dialogImport: loadManageFabricsDialog,
|
||||
dialogParams,
|
||||
});
|
||||
};
|
@@ -0,0 +1,19 @@
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
|
||||
export interface MatterOpenCommissioningWindowDialogParams {
|
||||
device_id: string;
|
||||
}
|
||||
|
||||
export const loadOpenCommissioningWindowDialog = () =>
|
||||
import("./dialog-matter-open-commissioning-window");
|
||||
|
||||
export const showMatterOpenCommissioningWindowDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: MatterOpenCommissioningWindowDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-matter-open-commissioning-window",
|
||||
dialogImport: loadOpenCommissioningWindowDialog,
|
||||
dialogParams,
|
||||
});
|
||||
};
|
@@ -0,0 +1,18 @@
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
|
||||
export interface MatterPingNodeDialogParams {
|
||||
device_id: string;
|
||||
}
|
||||
|
||||
export const loadPingNodeDialog = () => import("./dialog-matter-ping-node");
|
||||
|
||||
export const showMatterPingNodeDialog = (
|
||||
element: HTMLElement,
|
||||
pingNodeDialogParams: MatterPingNodeDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-matter-ping-node",
|
||||
dialogImport: loadPingNodeDialog,
|
||||
dialogParams: pingNodeDialogParams,
|
||||
});
|
||||
};
|
@@ -0,0 +1,19 @@
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
|
||||
export interface MatterReinterviewNodeDialogParams {
|
||||
device_id: string;
|
||||
}
|
||||
|
||||
export const loadReinterviewNodeDialog = () =>
|
||||
import("./dialog-matter-reinterview-node");
|
||||
|
||||
export const showMatterReinterviewNodeDialog = (
|
||||
element: HTMLElement,
|
||||
reinterviewNodeDialogParams: MatterReinterviewNodeDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-matter-reinterview-node",
|
||||
dialogImport: loadReinterviewNodeDialog,
|
||||
dialogParams: reinterviewNodeDialogParams,
|
||||
});
|
||||
};
|
@@ -210,8 +210,8 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
|
||||
<span slot="secondary">${router.server}</span>
|
||||
${showOverflow
|
||||
? html`${network.dataset &&
|
||||
router.border_agent_id ===
|
||||
network.dataset.preferred_border_agent_id
|
||||
router.extended_address ===
|
||||
network.dataset.preferred_extended_address
|
||||
? html`<ha-svg-icon
|
||||
.path=${mdiCellphoneKey}
|
||||
.title=${this.hass.localize(
|
||||
@@ -524,12 +524,12 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
|
||||
dataset: ThreadDataSet,
|
||||
router: ThreadRouter
|
||||
) {
|
||||
const datasetId = dataset.dataset_id;
|
||||
const borderAgentId = router.border_agent_id;
|
||||
if (!borderAgentId) {
|
||||
return;
|
||||
}
|
||||
await setPreferredBorderAgent(this.hass, datasetId, borderAgentId);
|
||||
await setPreferredBorderAgent(
|
||||
this.hass,
|
||||
dataset.dataset_id,
|
||||
router.border_agent_id,
|
||||
router.extended_address
|
||||
);
|
||||
this._refresh();
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
||||
import { mdiCheckCircle, mdiCloseCircle } from "@mdi/js";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
@@ -56,6 +57,7 @@ class DialogZHAReconfigureDevice extends LitElement {
|
||||
this._stages = undefined;
|
||||
this._clusterConfigurationStatuses = undefined;
|
||||
this._showDetails = false;
|
||||
this._allSuccessful = true;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
@@ -278,7 +280,7 @@ class DialogZHAReconfigureDevice extends LitElement {
|
||||
(attribute) => html`
|
||||
<span class="grid-item">
|
||||
${attribute.name}:
|
||||
${attribute.success
|
||||
${attribute.status === "SUCCESS"
|
||||
? html`
|
||||
<span class="stage">
|
||||
<ha-svg-icon
|
||||
@@ -289,6 +291,12 @@ class DialogZHAReconfigureDevice extends LitElement {
|
||||
`
|
||||
: html`
|
||||
<span class="stage">
|
||||
<simple-tooltip
|
||||
animation-delay="0"
|
||||
position="top"
|
||||
>
|
||||
${attribute.status}
|
||||
</simple-tooltip>
|
||||
<ha-svg-icon
|
||||
.path=${mdiCloseCircle}
|
||||
class="failed"
|
||||
@@ -361,7 +369,12 @@ class DialogZHAReconfigureDevice extends LitElement {
|
||||
Object.keys(attributes).forEach((name) => {
|
||||
const attribute = attributes[name];
|
||||
clusterConfigurationStatus!.attributes.set(attribute.id, attribute);
|
||||
this._allSuccessful = this._allSuccessful && attribute.success;
|
||||
this._allSuccessful =
|
||||
this._allSuccessful &&
|
||||
!(
|
||||
attribute.status in
|
||||
["FAILURE", "UNSUPPORTED_ATTRIBUTE", "UNREPORTABLE_ATTRIBUTE"]
|
||||
);
|
||||
});
|
||||
}
|
||||
this.requestUpdate();
|
||||
|
@@ -8,11 +8,15 @@ import "../../../components/ha-card";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { domainToName } from "../../../data/integration";
|
||||
import type { RepairsIssue } from "../../../data/repairs";
|
||||
import {
|
||||
fetchRepairsIssueData,
|
||||
type RepairsIssue,
|
||||
} from "../../../data/repairs";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { brandsUrl } from "../../../util/brands-url";
|
||||
import { showRepairsFlowDialog } from "./show-dialog-repair-flow";
|
||||
import { showRepairsIssueDialog } from "./show-repair-issue-dialog";
|
||||
import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow";
|
||||
|
||||
@customElement("ha-config-repairs")
|
||||
class HaConfigRepairs extends LitElement {
|
||||
@@ -107,10 +111,24 @@ class HaConfigRepairs extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _openShowMoreDialog(ev): void {
|
||||
private async _openShowMoreDialog(ev): Promise<void> {
|
||||
const issue = ev.currentTarget.issue as RepairsIssue;
|
||||
if (issue.is_fixable) {
|
||||
showRepairsFlowDialog(this, issue);
|
||||
} else if (
|
||||
issue.domain === "homeassistant" &&
|
||||
issue.translation_key === "config_entry_reauth"
|
||||
) {
|
||||
const data = await fetchRepairsIssueData(
|
||||
this.hass.connection,
|
||||
issue.domain,
|
||||
issue.issue_id
|
||||
);
|
||||
if ("flow_id" in data.issue_data) {
|
||||
showConfigFlowDialog(this, {
|
||||
continueFlowId: data.issue_data.flow_id as string,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
showRepairsIssueDialog(this, {
|
||||
issue,
|
||||
|
@@ -191,6 +191,8 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
@blur=${this.handleRippleBlur}
|
||||
@mousedown=${this.handleRippleActivate}
|
||||
@mouseup=${this.handleRippleDeactivate}
|
||||
@mouseenter=${this.handleRippleMouseEnter}
|
||||
@mouseleave=${this.handleRippleMouseLeave}
|
||||
@touchstart=${this.handleRippleActivate}
|
||||
@touchend=${this.handleRippleDeactivate}
|
||||
@touchcancel=${this.handleRippleDeactivate}
|
||||
@@ -204,6 +206,9 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
tabindex=${ifDefined(
|
||||
hasAction(this._config.tap_action) ? "0" : undefined
|
||||
)}
|
||||
style=${styleMap({
|
||||
"--state-color": colored ? this._computeColor(stateObj) : undefined,
|
||||
})}
|
||||
>
|
||||
${this._config.show_icon
|
||||
? html`
|
||||
@@ -217,7 +222,6 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObj}
|
||||
style=${styleMap({
|
||||
color: colored ? this._computeColor(stateObj) : undefined,
|
||||
filter: colored ? stateColorBrightness(stateObj) : undefined,
|
||||
height: this._config.icon_height
|
||||
? this._config.icon_height
|
||||
@@ -280,23 +284,37 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
this._rippleHandlers.startPress(evt);
|
||||
}
|
||||
|
||||
@eventOptions({ passive: true })
|
||||
private handleRippleDeactivate() {
|
||||
this._rippleHandlers.endPress();
|
||||
}
|
||||
|
||||
@eventOptions({ passive: true })
|
||||
private handleRippleFocus() {
|
||||
this._rippleHandlers.startFocus();
|
||||
}
|
||||
|
||||
@eventOptions({ passive: true })
|
||||
private handleRippleBlur() {
|
||||
this._rippleHandlers.endFocus();
|
||||
}
|
||||
|
||||
@eventOptions({ passive: true })
|
||||
private handleRippleMouseEnter() {
|
||||
this._rippleHandlers.startHover();
|
||||
}
|
||||
|
||||
@eventOptions({ passive: true })
|
||||
private handleRippleMouseLeave() {
|
||||
this._rippleHandlers.endHover();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
iconColorCSS,
|
||||
css`
|
||||
ha-card {
|
||||
--mdc-ripple-color: var(--state-color);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -318,9 +336,11 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
ha-state-icon {
|
||||
width: 40%;
|
||||
height: auto;
|
||||
color: var(--paper-item-icon-color, #44739e);
|
||||
max-height: 80%;
|
||||
color: var(--state-color, var(--paper-item-icon-color, #44739e));
|
||||
--mdc-icon-size: 100%;
|
||||
--state-inactive-color: var(--paper-item-icon-color, #44739e);
|
||||
transition: transform 180ms ease-in-out;
|
||||
}
|
||||
|
||||
ha-state-icon + span {
|
||||
@@ -332,6 +352,11 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
:host(:focus-visible) ha-state-icon,
|
||||
:host(:active) ha-state-icon {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.state {
|
||||
font-size: 0.9rem;
|
||||
color: var(--secondary-text-color);
|
||||
|
@@ -238,6 +238,9 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
|
||||
? this._config.show_names
|
||||
: true}
|
||||
.logarithmicScale=${this._config.logarithmic_scale || false}
|
||||
.minYAxis=${this._config.min_y_axis}
|
||||
.maxYAxis=${this._config.max_y_axis}
|
||||
.fitYData=${this._config.fit_y_data || false}
|
||||
></state-history-charts>
|
||||
`}
|
||||
</div>
|
||||
|
@@ -166,7 +166,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
.paths=${this._getHistoryPaths(this._config, this._stateHistory)}
|
||||
.autoFit=${this._config.auto_fit || false}
|
||||
.fitZones=${this._config.fit_zones}
|
||||
.darkMode=${this._config.dark_mode}
|
||||
?darkMode=${this._config.dark_mode}
|
||||
interactiveZones
|
||||
renderPassive
|
||||
></ha-map>
|
||||
|
@@ -525,6 +525,9 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
top: -3px;
|
||||
right: -3px;
|
||||
}
|
||||
.icon-container:not([role="button"]) {
|
||||
pointer-events: none;
|
||||
}
|
||||
.icon-container[role="button"]:focus-visible,
|
||||
.icon-container[role="button"]:active {
|
||||
transform: scale(1.2);
|
||||
|
@@ -324,6 +324,9 @@ export interface HistoryGraphCardConfig extends LovelaceCardConfig {
|
||||
title?: string;
|
||||
show_names?: boolean;
|
||||
logarithmic_scale?: boolean;
|
||||
min_y_axis?: number;
|
||||
max_y_axis?: number;
|
||||
fit_y_data?: boolean;
|
||||
split_device_classes?: boolean;
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import {
|
||||
array,
|
||||
assert,
|
||||
@@ -32,29 +33,12 @@ const cardConfigStruct = assign(
|
||||
refresh_interval: optional(number()), // deprecated
|
||||
show_names: optional(boolean()),
|
||||
logarithmic_scale: optional(boolean()),
|
||||
min_y_axis: optional(number()),
|
||||
max_y_axis: optional(number()),
|
||||
fit_y_data: optional(boolean()),
|
||||
})
|
||||
);
|
||||
|
||||
const SCHEMA = [
|
||||
{ name: "title", selector: { text: {} } },
|
||||
{
|
||||
name: "",
|
||||
type: "grid",
|
||||
schema: [
|
||||
{
|
||||
name: "hours_to_show",
|
||||
default: DEFAULT_HOURS_TO_SHOW,
|
||||
selector: { number: { min: 1, mode: "box" } },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "logarithmic_scale",
|
||||
required: false,
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
] as const;
|
||||
|
||||
@customElement("hui-history-graph-card-editor")
|
||||
export class HuiHistoryGraphCardEditor
|
||||
extends LitElement
|
||||
@@ -72,16 +56,69 @@ export class HuiHistoryGraphCardEditor
|
||||
this._configEntities = processEditorEntities(config.entities);
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(showFitOption: boolean) =>
|
||||
[
|
||||
{ name: "title", selector: { text: {} } },
|
||||
{
|
||||
name: "",
|
||||
type: "grid",
|
||||
schema: [
|
||||
{
|
||||
name: "hours_to_show",
|
||||
default: DEFAULT_HOURS_TO_SHOW,
|
||||
selector: { number: { min: 1, mode: "box" } },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "logarithmic_scale",
|
||||
required: false,
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "grid",
|
||||
schema: [
|
||||
{
|
||||
name: "min_y_axis",
|
||||
required: false,
|
||||
selector: { number: { mode: "box", step: "any" } },
|
||||
},
|
||||
{
|
||||
name: "max_y_axis",
|
||||
required: false,
|
||||
selector: { number: { mode: "box", step: "any" } },
|
||||
},
|
||||
],
|
||||
},
|
||||
...(showFitOption
|
||||
? [
|
||||
{
|
||||
name: "fit_y_data",
|
||||
required: false,
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
]
|
||||
: []),
|
||||
] as const
|
||||
);
|
||||
|
||||
protected render() {
|
||||
if (!this.hass || !this._config) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const schema = this._schema(
|
||||
this._config!.min_y_axis !== undefined ||
|
||||
this._config!.max_y_axis !== undefined
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this._config}
|
||||
.schema=${SCHEMA}
|
||||
.schema=${schema}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
@@ -106,9 +143,14 @@ export class HuiHistoryGraphCardEditor
|
||||
fireEvent(this, "config-changed", { config });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (schema: SchemaUnion<typeof SCHEMA>) => {
|
||||
private _computeLabelCallback = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
) => {
|
||||
switch (schema.name) {
|
||||
case "logarithmic_scale":
|
||||
case "min_y_axis":
|
||||
case "max_y_axis":
|
||||
case "fit_y_data":
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.history-graph.${schema.name}`
|
||||
);
|
||||
|
@@ -7,7 +7,6 @@ import {
|
||||
mdiDotsVertical,
|
||||
mdiFileMultiple,
|
||||
mdiFormatListBulletedTriangle,
|
||||
mdiHelp,
|
||||
mdiHelpCircle,
|
||||
mdiMagnify,
|
||||
mdiPencil,
|
||||
@@ -75,6 +74,7 @@ import {
|
||||
} from "../../data/lovelace/config/types";
|
||||
import { showSaveDialog } from "./editor/show-save-config-dialog";
|
||||
import { isLegacyStrategyConfig } from "./strategies/legacy-strategy";
|
||||
import { LocalizeKeys } from "../../common/translations/localize";
|
||||
|
||||
@customElement("hui-root")
|
||||
class HUIRoot extends LitElement {
|
||||
@@ -110,6 +110,168 @@ class HUIRoot extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
private _renderActionItems(): TemplateResult {
|
||||
const result: TemplateResult[] = [];
|
||||
if (this._editMode) {
|
||||
result.push(
|
||||
html`<mwc-button
|
||||
outlined
|
||||
class="exit-edit-mode"
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.menu.exit_edit_mode"
|
||||
)}
|
||||
@click=${this._editModeDisable}
|
||||
></mwc-button>
|
||||
<a
|
||||
href=${documentationUrl(this.hass, "/dashboards/")}
|
||||
rel="noreferrer"
|
||||
class="menu-link"
|
||||
target="_blank"
|
||||
>
|
||||
<ha-icon-button
|
||||
.label=${this.hass!.localize("ui.panel.lovelace.menu.help")}
|
||||
.path=${mdiHelpCircle}
|
||||
></ha-icon-button>
|
||||
</a>`
|
||||
);
|
||||
}
|
||||
|
||||
const items: {
|
||||
icon: string;
|
||||
key: LocalizeKeys;
|
||||
overflowAction?: any;
|
||||
buttonAction?: any;
|
||||
visible: boolean | undefined;
|
||||
overflow: boolean;
|
||||
overflow_can_promote?: boolean;
|
||||
}[] = [
|
||||
{
|
||||
icon: mdiFormatListBulletedTriangle,
|
||||
key: "ui.panel.lovelace.unused_entities.title",
|
||||
overflowAction: this._handleUnusedEntities,
|
||||
visible: this._editMode && !__DEMO__,
|
||||
overflow: true,
|
||||
},
|
||||
{
|
||||
icon: mdiCodeBraces,
|
||||
key: "ui.panel.lovelace.editor.menu.raw_editor",
|
||||
overflowAction: this._handleRawEditor,
|
||||
visible: this._editMode,
|
||||
overflow: true,
|
||||
},
|
||||
{
|
||||
icon: mdiViewDashboard,
|
||||
key: "ui.panel.lovelace.editor.menu.manage_dashboards",
|
||||
overflowAction: this._handleManageDashboards,
|
||||
visible: this._editMode && !__DEMO__,
|
||||
overflow: true,
|
||||
},
|
||||
{
|
||||
icon: mdiFileMultiple,
|
||||
key: "ui.panel.lovelace.editor.menu.manage_resources",
|
||||
overflowAction: this._handleManageResources,
|
||||
visible: this._editMode && this.hass.userData?.showAdvanced,
|
||||
overflow: true,
|
||||
},
|
||||
{
|
||||
icon: mdiMagnify,
|
||||
key: "ui.panel.lovelace.menu.search",
|
||||
buttonAction: this._showQuickBar,
|
||||
overflowAction: this._handleShowQuickBar,
|
||||
visible: !this._editMode,
|
||||
overflow: this.narrow,
|
||||
},
|
||||
{
|
||||
icon: mdiCommentProcessingOutline,
|
||||
key: "ui.panel.lovelace.menu.assist",
|
||||
buttonAction: this._showVoiceCommandDialog,
|
||||
overflowAction: this._handleShowVoiceCommandDialog,
|
||||
visible:
|
||||
!this._editMode && this._conversation(this.hass.config.components),
|
||||
overflow: this.narrow,
|
||||
},
|
||||
{
|
||||
icon: mdiRefresh,
|
||||
key: "ui.common.refresh",
|
||||
overflowAction: this._handleRefresh,
|
||||
visible: !this._editMode && this._yamlMode,
|
||||
overflow: true,
|
||||
},
|
||||
{
|
||||
icon: mdiShape,
|
||||
key: "ui.panel.lovelace.unused_entities.title",
|
||||
overflowAction: this._handleUnusedEntities,
|
||||
visible: !this._editMode && this._yamlMode,
|
||||
overflow: true,
|
||||
},
|
||||
{
|
||||
icon: mdiRefresh,
|
||||
key: "ui.panel.lovelace.menu.reload_resources",
|
||||
overflowAction: this._handleReloadResources,
|
||||
visible:
|
||||
!this._editMode &&
|
||||
(this.hass.panels.lovelace?.config as LovelacePanelConfig)?.mode ===
|
||||
"yaml",
|
||||
overflow: true,
|
||||
},
|
||||
{
|
||||
icon: mdiPencil,
|
||||
key: "ui.panel.lovelace.menu.configure_ui",
|
||||
overflowAction: this._handleEnableEditMode,
|
||||
buttonAction: this._enableEditMode,
|
||||
visible:
|
||||
!this._editMode &&
|
||||
this.hass!.user?.is_admin &&
|
||||
!this.hass!.config.recovery_mode,
|
||||
overflow: true,
|
||||
overflow_can_promote: true,
|
||||
},
|
||||
];
|
||||
|
||||
const overflowItems = items.filter((i) => i.visible && i.overflow);
|
||||
const overflowCanPromote =
|
||||
overflowItems.length === 1 && overflowItems[0].overflow_can_promote;
|
||||
const buttonItems = items.filter(
|
||||
(i) => i.visible && (!i.overflow || overflowCanPromote)
|
||||
);
|
||||
|
||||
buttonItems.forEach((i) => {
|
||||
result.push(
|
||||
html`<ha-icon-button
|
||||
slot="actionItems"
|
||||
.label=${this.hass!.localize(i.key)}
|
||||
.path=${i.icon}
|
||||
@click=${i.buttonAction}
|
||||
></ha-icon-button>`
|
||||
);
|
||||
});
|
||||
if (overflowItems.length && !overflowCanPromote) {
|
||||
const listItems: TemplateResult[] = [];
|
||||
overflowItems.forEach((i) => {
|
||||
listItems.push(
|
||||
html`<mwc-list-item
|
||||
graphic="icon"
|
||||
@request-selected=${i.overflowAction}
|
||||
>
|
||||
${this.hass!.localize(i.key)}
|
||||
<ha-svg-icon slot="graphic" .path=${i.icon}></ha-svg-icon>
|
||||
</mwc-list-item>`
|
||||
);
|
||||
});
|
||||
result.push(
|
||||
html`<ha-button-menu slot="actionItems">
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass!.localize("ui.panel.lovelace.editor.menu.open")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
${listItems}
|
||||
</ha-button-menu>`
|
||||
);
|
||||
}
|
||||
return html`${result}`;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const views = this.lovelace?.config.views ?? [];
|
||||
|
||||
@@ -139,96 +301,7 @@ class HUIRoot extends LitElement {
|
||||
@click=${this._editLovelace}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
<div class="action-items">
|
||||
<mwc-button
|
||||
outlined
|
||||
class="exit-edit-mode"
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.menu.exit_edit_mode"
|
||||
)}
|
||||
@click=${this._editModeDisable}
|
||||
></mwc-button>
|
||||
<a
|
||||
href=${documentationUrl(this.hass, "/dashboards/")}
|
||||
rel="noreferrer"
|
||||
class="menu-link"
|
||||
target="_blank"
|
||||
>
|
||||
<ha-icon-button
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.menu.help"
|
||||
)}
|
||||
.path=${mdiHelpCircle}
|
||||
></ha-icon-button>
|
||||
</a>
|
||||
<ha-button-menu>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.menu.open"
|
||||
)}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
${__DEMO__ /* No unused entities available in the demo */
|
||||
? ""
|
||||
: html`
|
||||
<mwc-list-item
|
||||
graphic="icon"
|
||||
@request-selected=${this._handleUnusedEntities}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiFormatListBulletedTriangle}
|
||||
>
|
||||
</ha-svg-icon>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.unused_entities.title"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
`}
|
||||
<mwc-list-item
|
||||
graphic="icon"
|
||||
@request-selected=${this._handleRawEditor}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiCodeBraces}
|
||||
></ha-svg-icon>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.menu.raw_editor"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
${__DEMO__ /* No config available in the demo */
|
||||
? ""
|
||||
: html`<mwc-list-item
|
||||
graphic="icon"
|
||||
@request-selected=${this._handleManageDashboards}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiViewDashboard}
|
||||
></ha-svg-icon>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.menu.manage_dashboards"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`<mwc-list-item
|
||||
graphic="icon"
|
||||
@request-selected=${this
|
||||
._handleManageResources}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiFileMultiple}
|
||||
></ha-svg-icon>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.menu.manage_resources"
|
||||
)}
|
||||
</mwc-list-item>`
|
||||
: ""} `}
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
<div class="action-items">${this._renderActionItems()}</div>
|
||||
`
|
||||
: html`
|
||||
${curViewConfig?.subview
|
||||
@@ -289,174 +362,7 @@ class HUIRoot extends LitElement {
|
||||
: html`<div class="main-title">
|
||||
${this.config.title}
|
||||
</div>`}
|
||||
<div class="action-items">
|
||||
${!this.narrow
|
||||
? html`
|
||||
<ha-icon-button
|
||||
slot="actionItems"
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.menu.search"
|
||||
)}
|
||||
.path=${mdiMagnify}
|
||||
@click=${this._showQuickBar}
|
||||
></ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
${!this.narrow &&
|
||||
this._conversation(this.hass.config.components)
|
||||
? html`
|
||||
<ha-icon-button
|
||||
slot="actionItems"
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.menu.assist"
|
||||
)}
|
||||
.path=${mdiCommentProcessingOutline}
|
||||
@click=${this._showVoiceCommandDialog}
|
||||
></ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
${this._showButtonMenu
|
||||
? html`
|
||||
<ha-button-menu slot="actionItems">
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.menu.open"
|
||||
)}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
${this.narrow
|
||||
? html`
|
||||
<mwc-list-item
|
||||
graphic="icon"
|
||||
@request-selected=${this
|
||||
._handleShowQuickBar}
|
||||
>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.menu.search"
|
||||
)}
|
||||
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiMagnify}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
`
|
||||
: ""}
|
||||
${this.narrow &&
|
||||
this._conversation(this.hass.config.components)
|
||||
? html`
|
||||
<mwc-list-item
|
||||
graphic="icon"
|
||||
@request-selected=${this
|
||||
._handleShowVoiceCommandDialog}
|
||||
>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.menu.assist"
|
||||
)}
|
||||
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiCommentProcessingOutline}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
`
|
||||
: ""}
|
||||
${this._yamlMode
|
||||
? html`
|
||||
<mwc-list-item
|
||||
graphic="icon"
|
||||
@request-selected=${this._handleRefresh}
|
||||
>
|
||||
${this.hass!.localize("ui.common.refresh")}
|
||||
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiRefresh}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item
|
||||
graphic="icon"
|
||||
@request-selected=${this
|
||||
._handleUnusedEntities}
|
||||
>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.unused_entities.title"
|
||||
)}
|
||||
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiShape}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
`
|
||||
: ""}
|
||||
${(
|
||||
this.hass.panels.lovelace
|
||||
?.config as LovelacePanelConfig
|
||||
)?.mode === "yaml"
|
||||
? html`
|
||||
<mwc-list-item
|
||||
graphic="icon"
|
||||
@request-selected=${this
|
||||
._handleReloadResources}
|
||||
>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.menu.reload_resources"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiRefresh}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
`
|
||||
: ""}
|
||||
${this.hass!.user?.is_admin &&
|
||||
!this.hass!.config.recovery_mode
|
||||
? html`
|
||||
<mwc-list-item
|
||||
graphic="icon"
|
||||
@request-selected=${this
|
||||
._handleEnableEditMode}
|
||||
>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.menu.configure_ui"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiPencil}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
`
|
||||
: ""}
|
||||
${this._editMode
|
||||
? html`
|
||||
<a
|
||||
href=${documentationUrl(
|
||||
this.hass,
|
||||
"/lovelace/"
|
||||
)}
|
||||
rel="noreferrer"
|
||||
class="menu-link"
|
||||
target="_blank"
|
||||
>
|
||||
<mwc-list-item graphic="icon">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.menu.help"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiHelp}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
</ha-button-menu>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="action-items">${this._renderActionItems()}</div>
|
||||
`}
|
||||
</div>
|
||||
${this._editMode
|
||||
@@ -698,17 +604,6 @@ class HUIRoot extends LitElement {
|
||||
return this.shadowRoot!.getElementById("view") as HTMLDivElement;
|
||||
}
|
||||
|
||||
private get _showButtonMenu(): boolean {
|
||||
return (
|
||||
(this.narrow && this._conversation(this.hass.config.components)) ||
|
||||
this._editMode ||
|
||||
(this.hass!.user?.is_admin && !this.hass!.config.recovery_mode) ||
|
||||
(this.hass.panels.lovelace?.config as LovelacePanelConfig)?.mode ===
|
||||
"yaml" ||
|
||||
this._yamlMode
|
||||
);
|
||||
}
|
||||
|
||||
private _handleRefresh(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
@@ -811,6 +706,10 @@ class HUIRoot extends LitElement {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
this._enableEditMode();
|
||||
}
|
||||
|
||||
private _enableEditMode(): void {
|
||||
if (this._yamlMode) {
|
||||
showAlertDialog(this, {
|
||||
text: this.hass!.localize("ui.panel.lovelace.editor.yaml_unsupported"),
|
||||
|
@@ -166,6 +166,15 @@ class HaMfaModuleSetupFlow extends LitElement {
|
||||
ha-markdown a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
ha-markdown-element p {
|
||||
text-align: center;
|
||||
}
|
||||
ha-markdown-element code {
|
||||
background-color: transparent;
|
||||
}
|
||||
ha-markdown-element > *:last-child {
|
||||
margin-bottom: revert;
|
||||
}
|
||||
.init-spinner {
|
||||
padding: 10px 100px 34px;
|
||||
text-align: center;
|
||||
|
@@ -73,13 +73,11 @@ export const stateControlCircularSliderStyle = css`
|
||||
.primary-state {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
.buttons ha-outlined-icon-button {
|
||||
--md-outlined-icon-button-container-width: 48px;
|
||||
--md-outlined-icon-button-container-height: 48px;
|
||||
--md-outlined-icon-button-icon-size: 24px;
|
||||
}
|
||||
|
||||
.container.md ha-big-number {
|
||||
font-size: 44px;
|
||||
}
|
||||
|
@@ -1771,6 +1771,7 @@
|
||||
"update_area": "Update area",
|
||||
"delete": "Delete",
|
||||
"name": "Name",
|
||||
"icon": "Icon",
|
||||
"name_required": "Name is required",
|
||||
"area_id": "Area ID",
|
||||
"unknown_error": "Unknown error",
|
||||
@@ -2569,7 +2570,8 @@
|
||||
"to": "To (optional)",
|
||||
"any_state_ignore_attributes": "Any state (ignoring attribute changes)",
|
||||
"description": {
|
||||
"picker": "When the state of an entity (or attribute) changes."
|
||||
"picker": "When the state of an entity (or attribute) changes.",
|
||||
"full": "When{hasAttribute, select, \n true { {attribute} of} \n other {}\n} {hasEntity, select, \n true {{entity}} \n other {something}\n} changes{fromChoice, select, \n fromUsed { from {fromString}}\n null { from any state} \n other {}\n}{toChoice, select, \n toUsed { to {toString}} \n null { to any state} \n special { state or any attributes} \n other {}\n}{hasDuration, select, \n true { for {duration}} \n other {}\n}"
|
||||
}
|
||||
},
|
||||
"homeassistant": {
|
||||
@@ -4596,6 +4598,73 @@
|
||||
"download_logs": "Download logs"
|
||||
}
|
||||
},
|
||||
"matter": {
|
||||
"network_type": {
|
||||
"thread": "Thread",
|
||||
"wifi": "Wi-Fi",
|
||||
"ethernet": "Ethernet",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"node_type": {
|
||||
"end_device": "End-device",
|
||||
"sleepy_end_device": "Sleepy end device",
|
||||
"routing_end_device": "Routing end device",
|
||||
"bridge": "Bridge",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"device_info": {
|
||||
"device_info": "Device info",
|
||||
"node_id": "Node ID",
|
||||
"network_type": "Network Type",
|
||||
"node_type": "Device type",
|
||||
"network_name": "Network name",
|
||||
"ip_adresses": "IP Address(es)",
|
||||
"mac_address": "MAC address",
|
||||
"available": "Available?"
|
||||
},
|
||||
"device_actions": {
|
||||
"reinterview_device": "Re-interview device",
|
||||
"ping_device": "Ping device",
|
||||
"open_commissioning_window": "Enable commisisioning mode",
|
||||
"manage_fabrics": "Manage fabrics",
|
||||
"view_thread_network": "View Thread network"
|
||||
},
|
||||
"manage_fabrics": {
|
||||
"title": "Connected fabrics",
|
||||
"fabrics": "Manage the fabrics that have access to this device.",
|
||||
"remove_fabric_confirm_header": "Remove {fabric} fabric from device",
|
||||
"remove_fabric_confirm_text": "Are you sure you want to remove the {fabric} from the device? You will not be able to control/access the device from that ecosystem/fabric after this action!",
|
||||
"remove_fabric_failed_header": "Remove {fabric} fabric failed",
|
||||
"remove_fabric_failed_text": "The action did not succeed, check the logs for more information."
|
||||
},
|
||||
"reinterview_node": {
|
||||
"title": "Re-interview a Matter device",
|
||||
"introduction": "Perform a full re-interview of a Matter device. Use this feature only if your device has missing or incorrect functionality.",
|
||||
"battery_device_warning": "You will need to wake battery powered devices before starting the re-interview. Refer to your device's manual for instructions on how to wake the device.",
|
||||
"run_in_background": "You can close this dialog and the interview will continue in the background.",
|
||||
"start_reinterview": "Start re-interview",
|
||||
"in_progress": "The device is being interviewed. This may take some time.",
|
||||
"interview_failed": "The device interview failed. Additional information may be available in the logs.",
|
||||
"interview_complete": "Device interview complete."
|
||||
},
|
||||
"ping_node": {
|
||||
"title": "Ping a Matter device",
|
||||
"introduction": "Perform a (server-side) ping on your Matter device on all its (known) IP-addresses.",
|
||||
"battery_device_warning": "Note that especially for battery powered devices this can take a a while. You may need to up powered devices before starting the pinging to speed up the process. Refer to your device's manual for instructions on how to wake the device.",
|
||||
"start_ping": "Start ping",
|
||||
"in_progress": "The device is being pinged. This may take some time.",
|
||||
"ping_failed": "The device ping failed. Additional information may be available in the logs.",
|
||||
"ping_complete": "Ping device complete."
|
||||
},
|
||||
"open_commissioning_window": {
|
||||
"title": "Enable commissioning mode",
|
||||
"introduction": "Enable commissioning mode on the device to pair it to another Matter controller.",
|
||||
"start_commissioning": "Enable commissioning mode",
|
||||
"in_progress": "We're communicating with the device. This may take some time.",
|
||||
"failed": "The command failed. Additional information may be available in the logs.",
|
||||
"sharing_code": "Sharing code"
|
||||
}
|
||||
},
|
||||
"tips": {
|
||||
"tip": "Tip!",
|
||||
"join": "Join the community on our {forums}, {twitter}, {discord}, {blog} or {newsletter}",
|
||||
@@ -5253,7 +5322,10 @@
|
||||
"history-graph": {
|
||||
"name": "History graph",
|
||||
"description": "The History graph card allows you to display a graph for each of the entities listed.",
|
||||
"logarithmic_scale": "Logarithmic scale"
|
||||
"logarithmic_scale": "Logarithmic scale",
|
||||
"min_y_axis": "Y axis minimum",
|
||||
"max_y_axis": "Y axis maximum",
|
||||
"fit_y_data": "Extend Y axis limits to fit data"
|
||||
},
|
||||
"statistics-graph": {
|
||||
"name": "Statistics graph",
|
||||
|
@@ -14312,6 +14312,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sortablejs@patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch":
|
||||
version: 1.15.2
|
||||
resolution: "sortablejs@patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch::version=1.15.2&hash=1591ab"
|
||||
checksum: d44399e9ca660157c76b13705eaa26191f71c4bd025e2d47b9f7e50a8f9bdb7deaaa2783a8032e55f39627fa4007042bcfd62cb4bbeb2931f6a5d6ee06047e2e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"source-list-map@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "source-list-map@npm:2.0.1"
|
||||
|
Reference in New Issue
Block a user