diff --git a/build-scripts/gulp/gather-static.js b/build-scripts/gulp/gather-static.js
index 8902c33dc6..c02511abff 100644
--- a/build-scripts/gulp/gather-static.js
+++ b/build-scripts/gulp/gather-static.js
@@ -106,6 +106,14 @@ function copyMapPanel(staticDir) {
);
}
+function copyZXingWasm(staticDir) {
+ const staticPath = genStaticPath(staticDir);
+ copyFileDir(
+ npmPath("zxing-wasm/dist/reader/zxing_reader.wasm"),
+ staticPath("js")
+ );
+}
+
gulp.task("copy-locale-data", async () => {
const staticDir = paths.app_output_static;
copyLocaleData(staticDir);
@@ -143,6 +151,7 @@ gulp.task("copy-static-app", async () => {
copyMapPanel(staticDir);
// Qr Scanner assets
+ copyZXingWasm(staticDir);
copyQrScannerWorker(staticDir);
});
diff --git a/demo/src/configs/sections/description.ts b/demo/src/configs/sections/description.ts
deleted file mode 100644
index e90b78b173..0000000000
--- a/demo/src/configs/sections/description.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { html } from "lit";
-import type { DemoConfig } from "../types";
-
-export const demoLovelaceDescription: DemoConfig["description"] = (
- localize
-) => html`
-
- ${localize("ui.panel.page-demo.config.sections.description", {
- blog_post: html`${localize("ui.panel.page-demo.config.sections.description_blog_post")}
- `,
- })}
-
-`;
diff --git a/demo/src/configs/sections/index.ts b/demo/src/configs/sections/index.ts
index 248a053cd7..c25df44ebc 100644
--- a/demo/src/configs/sections/index.ts
+++ b/demo/src/configs/sections/index.ts
@@ -1,5 +1,4 @@
import type { DemoConfig } from "../types";
-import { demoLovelaceDescription } from "./description";
import { demoEntitiesSections } from "./entities";
import { demoLovelaceSections } from "./lovelace";
@@ -7,7 +6,6 @@ export const demoSections: DemoConfig = {
authorName: "Home Assistant",
authorUrl: "https://github.com/home-assistant/frontend/",
name: "Home Demo",
- description: demoLovelaceDescription,
lovelace: demoLovelaceSections,
entities: demoEntitiesSections,
theme: () => ({}),
diff --git a/package.json b/package.json
index 956b2bf945..6bc912e9ae 100644
--- a/package.json
+++ b/package.json
@@ -98,6 +98,7 @@
"@webcomponents/scoped-custom-element-registry": "0.0.9",
"@webcomponents/webcomponentsjs": "2.8.0",
"app-datepicker": "5.1.1",
+ "barcode-detector": "2.2.11",
"chart.js": "4.4.6",
"color-name": "2.0.0",
"comlink": "4.4.1",
diff --git a/pyproject.toml b/pyproject.toml
index 86a0d55242..ad1d211e00 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
-version = "20241104.0"
+version = "20241105.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"
diff --git a/src/components/data-table/ha-data-table.ts b/src/components/data-table/ha-data-table.ts
index f698f4f3ec..de2b3a9a44 100644
--- a/src/components/data-table/ha-data-table.ts
+++ b/src/components/data-table/ha-data-table.ts
@@ -1185,6 +1185,7 @@ export class HaDataTable extends LitElement {
.group-header {
padding-top: 12px;
+ height: var(--data-table-row-height, 52px);
padding-left: 12px;
padding-inline-start: 12px;
padding-inline-end: initial;
diff --git a/src/components/ha-camera-stream.ts b/src/components/ha-camera-stream.ts
index 29bf3a7860..4fe9b9d302 100644
--- a/src/components/ha-camera-stream.ts
+++ b/src/components/ha-camera-stream.ts
@@ -7,6 +7,8 @@ import {
type PropertyValues,
} from "lit";
import { customElement, property, state } from "lit/decorators";
+import { repeat } from "lit/directives/repeat";
+import memoizeOne from "memoize-one";
import { computeStateName } from "../common/entity/compute_state_name";
import { supportsFeature } from "../common/entity/supports-feature";
import {
@@ -24,6 +26,13 @@ import type { HomeAssistant } from "../types";
import "./ha-hls-player";
import "./ha-web-rtc-player";
+const MJPEG_STREAM = "mjpeg";
+
+type Stream = {
+ type: StreamType | typeof MJPEG_STREAM;
+ visible: boolean;
+};
+
@customElement("ha-camera-stream")
export class HaCameraStream extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@@ -46,8 +55,6 @@ export class HaCameraStream extends LitElement {
@state() private _capabilities?: CameraCapabilities;
- @state() private _streamType?: StreamType;
-
@state() private _hlsStreams?: { hasAudio: boolean; hasVideo: boolean };
@state() private _webRtcStreams?: { hasAudio: boolean; hasVideo: boolean };
@@ -55,7 +62,6 @@ export class HaCameraStream extends LitElement {
public willUpdate(changedProps: PropertyValues): void {
if (
changedProps.has("stateObj") &&
- !this._shouldRenderMJPEG &&
this.stateObj &&
(changedProps.get("stateObj") as CameraEntity | undefined)?.entity_id !==
this.stateObj.entity_id
@@ -79,52 +85,63 @@ export class HaCameraStream extends LitElement {
if (!this.stateObj) {
return nothing;
}
- if (__DEMO__ || this._shouldRenderMJPEG) {
+ const streams = this._streams(
+ this._capabilities?.frontend_stream_types,
+ this._hlsStreams,
+ this._webRtcStreams
+ );
+ return html`${repeat(
+ streams,
+ (stream) => stream.type + this.stateObj!.entity_id,
+ (stream) => this._renderStream(stream)
+ )}`;
+ }
+
+ private _renderStream(stream: Stream) {
+ if (!this.stateObj) {
+ return nothing;
+ }
+ if (stream.type === MJPEG_STREAM) {
return html`
`;
}
- return html`${this._streamType === STREAM_TYPE_HLS ||
- (!this._streamType &&
- this._capabilities?.frontend_stream_types.includes(STREAM_TYPE_HLS))
- ? html``
- : nothing}
- ${this._streamType === STREAM_TYPE_WEB_RTC ||
- (!this._streamType &&
- this._capabilities?.frontend_stream_types.includes(STREAM_TYPE_WEB_RTC))
- ? html``
- : nothing}`;
+
+ if (stream.type === STREAM_TYPE_HLS) {
+ return html``;
+ }
+
+ if (stream.type === STREAM_TYPE_WEB_RTC) {
+ return html``;
+ }
+
+ return nothing;
}
private async _getCapabilities() {
@@ -132,35 +149,13 @@ export class HaCameraStream extends LitElement {
this._hlsStreams = undefined;
this._webRtcStreams = undefined;
if (!supportsFeature(this.stateObj!, CAMERA_SUPPORT_STREAM)) {
+ this._capabilities = { frontend_stream_types: [] };
return;
}
this._capabilities = await fetchCameraCapabilities(
this.hass!,
this.stateObj!.entity_id
);
- if (this._capabilities.frontend_stream_types.length === 1) {
- this._streamType = this._capabilities.frontend_stream_types[0];
- }
- }
-
- private get _shouldRenderMJPEG() {
- if (!supportsFeature(this.stateObj!, CAMERA_SUPPORT_STREAM)) {
- // Steaming is not supported by the camera so fallback to MJPEG stream
- return true;
- }
- if (
- this._capabilities &&
- (!this._capabilities.frontend_stream_types.includes(STREAM_TYPE_HLS) ||
- this._hlsStreams?.hasVideo === false) &&
- (!this._capabilities.frontend_stream_types.includes(
- STREAM_TYPE_WEB_RTC
- ) ||
- this._webRtcStreams?.hasVideo === false)
- ) {
- // No video in HLS stream and no video in WebRTC stream
- return true;
- }
- return false;
}
private async _getPosterUrl(): Promise {
@@ -179,28 +174,87 @@ export class HaCameraStream extends LitElement {
private _handleHlsStreams(ev: CustomEvent) {
this._hlsStreams = ev.detail;
- this._pickStreamType();
}
private _handleWebRtcStreams(ev: CustomEvent) {
this._webRtcStreams = ev.detail;
- this._pickStreamType();
}
- private _pickStreamType() {
- if (!this._hlsStreams || !this._webRtcStreams) {
- return;
+ private _streams = memoizeOne(
+ (
+ supportedTypes?: StreamType[],
+ hlsStreams?: { hasAudio: boolean; hasVideo: boolean },
+ webRtcStreams?: { hasAudio: boolean; hasVideo: boolean }
+ ): Stream[] => {
+ if (__DEMO__) {
+ return [{ type: MJPEG_STREAM, visible: true }];
+ }
+ if (!supportedTypes) {
+ return [];
+ }
+ if (supportedTypes.length === 0) {
+ // doesn't support any stream type, fallback to mjpeg
+ return [{ type: MJPEG_STREAM, visible: true }];
+ }
+ if (supportedTypes.length === 1) {
+ // only 1 stream type, no need to choose
+ if (
+ (supportedTypes[0] === STREAM_TYPE_HLS &&
+ hlsStreams?.hasVideo === false) ||
+ (supportedTypes[0] === STREAM_TYPE_WEB_RTC &&
+ webRtcStreams?.hasVideo === false)
+ ) {
+ // stream failed to load, fallback to mjpeg
+ return [{ type: MJPEG_STREAM, visible: true }];
+ }
+ return [{ type: supportedTypes[0], visible: true }];
+ }
+ if (hlsStreams && webRtcStreams) {
+ // fully loaded
+ if (
+ hlsStreams.hasVideo &&
+ hlsStreams.hasAudio &&
+ !webRtcStreams.hasAudio
+ ) {
+ // webRTC stream is missing audio, use HLS
+ return [{ type: STREAM_TYPE_HLS, visible: true }];
+ }
+ if (webRtcStreams.hasVideo) {
+ return [{ type: STREAM_TYPE_WEB_RTC, visible: true }];
+ }
+ // both streams failed to load, fallback to mjpeg
+ return [{ type: MJPEG_STREAM, visible: true }];
+ }
+
+ if (hlsStreams?.hasVideo !== webRtcStreams?.hasVideo) {
+ // one of the two streams is loaded, or errored
+ // choose the one that has video or is still loading
+ if (hlsStreams?.hasVideo) {
+ return [
+ { type: STREAM_TYPE_HLS, visible: true },
+ { type: STREAM_TYPE_WEB_RTC, visible: false },
+ ];
+ }
+ if (hlsStreams?.hasVideo === false) {
+ return [{ type: STREAM_TYPE_WEB_RTC, visible: true }];
+ }
+ if (webRtcStreams?.hasVideo) {
+ return [
+ { type: STREAM_TYPE_WEB_RTC, visible: true },
+ { type: STREAM_TYPE_HLS, visible: false },
+ ];
+ }
+ if (webRtcStreams?.hasVideo === false) {
+ return [{ type: STREAM_TYPE_HLS, visible: true }];
+ }
+ }
+
+ return [
+ { type: STREAM_TYPE_HLS, visible: true },
+ { type: STREAM_TYPE_WEB_RTC, visible: false },
+ ];
}
- if (
- this._hlsStreams.hasVideo &&
- this._hlsStreams.hasAudio &&
- !this._webRtcStreams.hasAudio
- ) {
- this._streamType = STREAM_TYPE_HLS;
- } else if (this._webRtcStreams.hasVideo) {
- this._streamType = STREAM_TYPE_WEB_RTC;
- }
- }
+ );
static get styles(): CSSResultGroup {
return css`
diff --git a/src/components/ha-md-dialog.ts b/src/components/ha-md-dialog.ts
index 0637817f7c..dde855e016 100644
--- a/src/components/ha-md-dialog.ts
+++ b/src/components/ha-md-dialog.ts
@@ -182,6 +182,10 @@ export class HaMdDialog extends MdDialog {
display: contents;
}
+ .scroller {
+ overflow: var(--dialog-content-overflow, auto);
+ }
+
slot[name="content"]::slotted(*) {
padding: var(--dialog-content-padding, 24px);
}
diff --git a/src/components/ha-md-select-option.ts b/src/components/ha-md-select-option.ts
new file mode 100644
index 0000000000..4cf8062e21
--- /dev/null
+++ b/src/components/ha-md-select-option.ts
@@ -0,0 +1,26 @@
+import { MdSelectOption } from "@material/web/select/select-option";
+import { css } from "lit";
+import { customElement } from "lit/decorators";
+
+@customElement("ha-md-select-option")
+export class HaMdSelectOption extends MdSelectOption {
+ static override styles = [
+ ...super.styles,
+ css`
+ :host {
+ --ha-icon-display: block;
+ --md-sys-color-primary: var(--primary-text-color);
+ --md-sys-color-secondary: var(--secondary-text-color);
+ --md-sys-color-surface: var(--card-background-color);
+ --md-sys-color-on-surface: var(--primary-text-color);
+ --md-sys-color-on-surface-variant: var(--secondary-text-color);
+ }
+ `,
+ ];
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-md-select-option": HaMdSelectOption;
+ }
+}
diff --git a/src/components/ha-md-select.ts b/src/components/ha-md-select.ts
new file mode 100644
index 0000000000..924fc21635
--- /dev/null
+++ b/src/components/ha-md-select.ts
@@ -0,0 +1,32 @@
+import { MdFilledSelect } from "@material/web/select/filled-select";
+import { css } from "lit";
+import { customElement } from "lit/decorators";
+
+@customElement("ha-md-select")
+export class HaMdSelect extends MdFilledSelect {
+ static override styles = [
+ ...super.styles,
+ css`
+ :host {
+ --ha-icon-display: block;
+ --md-sys-color-primary: var(--primary-text-color);
+ --md-sys-color-secondary: var(--secondary-text-color);
+ --md-sys-color-surface: var(--card-background-color);
+ --md-sys-color-on-surface-variant: var(--secondary-text-color);
+
+ --md-sys-color-surface-container-highest: var(--input-fill-color);
+ --md-sys-color-on-surface: var(--input-ink-color);
+
+ --md-sys-color-surface-container: var(--input-fill-color);
+ --md-sys-color-secondary-container: var(--input-fill-color);
+ --md-menu-container-color: var(--card-background-color);
+ }
+ `,
+ ];
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-md-select": HaMdSelect;
+ }
+}
diff --git a/src/components/ha-qr-scanner.ts b/src/components/ha-qr-scanner.ts
index b38d24ff7c..2869b1986d 100644
--- a/src/components/ha-qr-scanner.ts
+++ b/src/components/ha-qr-scanner.ts
@@ -4,6 +4,11 @@ import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
+// The BarcodeDetector Web API is not yet supported in all browsers,
+// and "qr-scanner" defaults to a suboptimal implementation if it is not available.
+// The following import makes a better implementation available that is based on a
+// WebAssembly port of ZXing:
+import { setZXingModuleOverrides } from "barcode-detector";
import type QrScanner from "qr-scanner";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
@@ -16,6 +21,15 @@ import "./ha-list-item";
import "./ha-textfield";
import type { HaTextField } from "./ha-textfield";
+setZXingModuleOverrides({
+ locateFile: (path: string, prefix: string) => {
+ if (path.endsWith(".wasm")) {
+ return "/static/js/zxing_reader.wasm";
+ }
+ return prefix + path;
+ },
+});
+
@customElement("ha-qr-scanner")
class HaQrScanner extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -174,7 +188,7 @@ class HaQrScanner extends LitElement {
}
private _qrCodeError = (err: any) => {
- if (err === "No QR code found") {
+ if (err.endsWith("No QR code found")) {
this._qrNotFoundCount++;
if (this._qrNotFoundCount === 250) {
this._reportError(err);
diff --git a/src/components/ha-web-rtc-player.ts b/src/components/ha-web-rtc-player.ts
index b2467e2804..91f2c71cd2 100644
--- a/src/components/ha-web-rtc-player.ts
+++ b/src/components/ha-web-rtc-player.ts
@@ -1,4 +1,3 @@
-/* eslint-disable no-console */
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, html, LitElement } from "lit";
@@ -108,18 +107,18 @@ class HaWebRtcPlayer extends LitElement {
return;
}
- console.time("WebRTC");
-
this._error = undefined;
- console.timeLog("WebRTC", "start clientConfig");
+ this._startTimer();
+
+ this._logEvent("start clientConfig");
this._clientConfig = await fetchWebRtcClientConfiguration(
this.hass,
this.entityid
);
- console.timeLog("WebRTC", "end clientConfig", this._clientConfig);
+ this._logEvent("end clientConfig", this._clientConfig);
this._peerConnection = new RTCPeerConnection(
this._clientConfig.configuration
@@ -141,11 +140,10 @@ class HaWebRtcPlayer extends LitElement {
this._peerConnection.onsignalingstatechange = (ev) => {
switch ((ev.target as RTCPeerConnection).signalingState) {
case "stable":
- console.timeLog("WebRTC", "ICE negotiation complete");
+ this._logEvent("ICE negotiation complete");
break;
default:
- console.timeLog(
- "WebRTC",
+ this._logEvent(
"Signaling state changed",
(ev.target as RTCPeerConnection).signalingState
);
@@ -170,7 +168,7 @@ class HaWebRtcPlayer extends LitElement {
offerToReceiveVideo: true,
};
- console.timeLog("WebRTC", "start createOffer", offerOptions);
+ this._logEvent("start createOffer", offerOptions);
const offer: RTCSessionDescriptionInit =
await this._peerConnection.createOffer(offerOptions);
@@ -179,9 +177,9 @@ class HaWebRtcPlayer extends LitElement {
return;
}
- console.timeLog("WebRTC", "end createOffer", offer);
+ this._logEvent("end createOffer", offer);
- console.timeLog("WebRTC", "start setLocalDescription");
+ this._logEvent("start setLocalDescription");
await this._peerConnection.setLocalDescription(offer);
@@ -189,7 +187,7 @@ class HaWebRtcPlayer extends LitElement {
return;
}
- console.timeLog("WebRTC", "end setLocalDescription");
+ this._logEvent("end setLocalDescription");
let candidates = "";
@@ -203,11 +201,7 @@ class HaWebRtcPlayer extends LitElement {
resolve();
}
- console.timeLog(
- "WebRTC",
- "Ice gathering state changed",
- iceGatheringState
- );
+ this._logEvent("Ice gathering state changed", iceGatheringState);
};
});
@@ -225,7 +219,7 @@ class HaWebRtcPlayer extends LitElement {
const offer_sdp = offer.sdp! + candidates;
- console.timeLog("WebRTC", "start webRtcOffer", offer_sdp);
+ this._logEvent("start webRtcOffer", offer_sdp);
try {
this._unsub = webRtcOffer(this.hass, this.entityid, offer_sdp, (event) =>
@@ -238,8 +232,7 @@ class HaWebRtcPlayer extends LitElement {
};
private _iceConnectionStateChanged = () => {
- console.timeLog(
- "WebRTC",
+ this._logEvent(
"ice connection state change",
this._peerConnection?.iceConnectionState
);
@@ -265,18 +258,19 @@ class HaWebRtcPlayer extends LitElement {
this._candidatesList = [];
}
if (event.type === "answer") {
- console.timeLog("WebRTC", "answer", event.answer);
+ this._logEvent("answer", event.answer);
this._handleAnswer(event);
}
if (event.type === "candidate") {
- console.timeLog("WebRTC", "remote ice candidate", event.candidate);
+ this._logEvent("remote ice candidate", event.candidate);
try {
await this._peerConnection?.addIceCandidate(
new RTCIceCandidate({ candidate: event.candidate, sdpMid: "0" })
);
} catch (err: any) {
+ // eslint-disable-next-line no-console
console.error(err);
}
}
@@ -291,11 +285,7 @@ class HaWebRtcPlayer extends LitElement {
return;
}
- console.timeLog(
- "WebRTC",
- "local ice candidate",
- event.candidate?.candidate
- );
+ this._logEvent("local ice candidate", event.candidate?.candidate);
if (this._sessionId) {
addWebRtcCandidate(
@@ -334,19 +324,16 @@ class HaWebRtcPlayer extends LitElement {
sdp: event.answer,
});
try {
- console.timeLog("WebRTC", "start setRemoteDescription", remoteDesc);
+ this._logEvent("start setRemoteDescription", remoteDesc);
await this._peerConnection.setRemoteDescription(remoteDesc);
} catch (err: any) {
this._error = "Failed to connect WebRTC stream: " + err.message;
this._cleanUp();
}
- console.timeLog("WebRTC", "end setRemoteDescription");
+ this._logEvent("end setRemoteDescription");
}
private _cleanUp() {
- console.timeLog("WebRTC", "stopped");
- console.timeEnd("WebRTC");
-
if (this._remoteStream) {
this._remoteStream.getTracks().forEach((track) => {
track.stop();
@@ -372,6 +359,9 @@ class HaWebRtcPlayer extends LitElement {
this._peerConnection.onsignalingstatechange = null;
this._peerConnection = undefined;
+
+ this._logEvent("stopped");
+ this._stopTimer();
}
this._unsub?.then((unsub) => unsub());
this._unsub = undefined;
@@ -380,17 +370,43 @@ class HaWebRtcPlayer extends LitElement {
}
private _loadedData() {
- console.timeLog("WebRTC", "loadedData");
- console.timeEnd("WebRTC");
-
const video = this._videoEl;
const stream = video.srcObject as MediaStream;
- fireEvent(this, "load");
- fireEvent(this, "streams", {
+ const data = {
hasAudio: Boolean(stream?.getAudioTracks().length),
hasVideo: Boolean(stream?.getVideoTracks().length),
- });
+ };
+
+ fireEvent(this, "load");
+ fireEvent(this, "streams", data);
+
+ this._logEvent("loadedData", data);
+ this._stopTimer();
+ }
+
+ private _startTimer() {
+ if (!__DEV__) {
+ return;
+ }
+ // eslint-disable-next-line no-console
+ console.time("WebRTC");
+ }
+
+ private _stopTimer() {
+ if (!__DEV__) {
+ return;
+ }
+ // eslint-disable-next-line no-console
+ console.timeEnd("WebRTC");
+ }
+
+ private _logEvent(msg: string, ...args: unknown[]) {
+ if (!__DEV__) {
+ return;
+ }
+ // eslint-disable-next-line no-console
+ console.timeLog("WebRTC", msg, ...args);
}
static get styles(): CSSResultGroup {
diff --git a/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-add-node.ts b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-add-node.ts
index ad0d87aaf5..565478d982 100644
--- a/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-add-node.ts
+++ b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-add-node.ts
@@ -690,9 +690,6 @@ class DialogZWaveJSAddNode extends LitElement {
provisioningInfo
);
this._status = "provisioned";
- if (this._params?.addedCallback) {
- this._params.addedCallback();
- }
} catch (err: any) {
this._error = err.message;
this._status = "failed";
@@ -831,9 +828,6 @@ class DialogZWaveJSAddNode extends LitElement {
if (message.event === "interview completed") {
this._unsubscribe();
this._status = "finished";
- if (this._params?.addedCallback) {
- this._params.addedCallback();
- }
}
if (message.event === "interview stage completed") {
@@ -874,6 +868,9 @@ class DialogZWaveJSAddNode extends LitElement {
}
if (this._entryId) {
stopZwaveInclusion(this.hass, this._entryId);
+ if (this._params?.onStop) {
+ this._params.onStop();
+ }
}
this._requestedGrant = undefined;
this._dsk = undefined;
diff --git a/src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-add-node.ts b/src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-add-node.ts
index d956d79d78..6c4c3b38f8 100644
--- a/src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-add-node.ts
+++ b/src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-add-node.ts
@@ -2,7 +2,7 @@ import { fireEvent } from "../../../../../common/dom/fire_event";
export interface ZWaveJSAddNodeDialogParams {
entry_id: string;
- addedCallback?: () => void;
+ onStop?: () => void;
}
export const loadAddNodeDialog = () => import("./dialog-zwave_js-add-node");
diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts
index 286cbd2adb..f6232ca43c 100644
--- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts
+++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts
@@ -564,7 +564,8 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
private async _addNodeClicked() {
showZWaveJSAddNodeDialog(this, {
entry_id: this.configEntryId!,
- addedCallback: () => this._fetchData(),
+ // refresh the data after the dialog is closed. add a small delay for the inclusion state to update
+ onStop: () => setTimeout(() => this._fetchData(), 100),
});
}
diff --git a/src/panels/config/logs/dialog-download-logs.ts b/src/panels/config/logs/dialog-download-logs.ts
index fdd67e5a31..0a7e9d965f 100644
--- a/src/panels/config/logs/dialog-download-logs.ts
+++ b/src/panels/config/logs/dialog-download-logs.ts
@@ -2,21 +2,20 @@ import { mdiClose } from "@mdi/js";
import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
-import "../../../components/ha-md-dialog";
+import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-button";
import "../../../components/ha-dialog-header";
import "../../../components/ha-icon-button";
+import "../../../components/ha-md-dialog";
import type { HaMdDialog } from "../../../components/ha-md-dialog";
-import type { HomeAssistant } from "../../../types";
-import { haStyle, haStyleDialog } from "../../../resources/styles";
-import { fireEvent } from "../../../common/dom/fire_event";
-import type { DownloadLogsDialogParams } from "./show-dialog-download-logs";
-import "../../../components/ha-select";
-import "../../../components/ha-list-item";
-import { stopPropagation } from "../../../common/dom/stop_propagation";
-import { getHassioLogDownloadLinesUrl } from "../../../data/hassio/supervisor";
+import "../../../components/ha-md-select";
+import "../../../components/ha-md-select-option";
import { getSignedPath } from "../../../data/auth";
+import { getHassioLogDownloadLinesUrl } from "../../../data/hassio/supervisor";
+import { haStyle, haStyleDialog } from "../../../resources/styles";
+import type { HomeAssistant } from "../../../types";
import { fileDownload } from "../../../util/file_download";
+import type { DownloadLogsDialogParams } from "./show-dialog-download-logs";
@customElement("dialog-download-logs")
class DownloadLogsDialog extends LitElement {
@@ -78,22 +77,19 @@ class DownloadLogsDialog extends LitElement {
"ui.panel.config.logs.select_number_of_lines"
)}:
-
${numberOfLinesOptions.map(
(option) => html`
-
+
${option}
-
+
`
)}
-
+
@@ -137,6 +133,7 @@ class DownloadLogsDialog extends LitElement {
css`
:host {
direction: var(--direction);
+ --dialog-content-overflow: visible;
}
.content {
display: flex;
diff --git a/src/translations/en.json b/src/translations/en.json
index 80c7faebff..09c4d2b1a8 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -6887,8 +6887,6 @@
}
},
"sections": {
- "description": "This dashboard is using the sections view released in Home Assistant 2024.3. Learn more about it in this {blog_post}.",
- "description_blog_post": "blog post",
"titles": {
"welcome": "Welcome",
"living_room": "Living room",
diff --git a/yarn.lock b/yarn.lock
index 190ad621bb..3ac463b311 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3944,6 +3944,20 @@ __metadata:
languageName: node
linkType: hard
+"@types/dom-webcodecs@npm:^0.1.13":
+ version: 0.1.13
+ resolution: "@types/dom-webcodecs@npm:0.1.13"
+ checksum: 10/99cb227416725efd4b22175ef18988ae3fc728480fe6ed2192777d7dba52d18af540b4df49fcfa3cf73753d1dcf9d5399b089702bde215d78679284848b078f7
+ languageName: node
+ linkType: hard
+
+"@types/emscripten@npm:^1.39.13":
+ version: 1.39.13
+ resolution: "@types/emscripten@npm:1.39.13"
+ checksum: 10/02c0446150f9cc2c74dc3a551f86ce13df266c33d8b98d11d9f17263e2d98a6a6b4d36bdd15066c4e1547ae1ed2d52eed9420116b4935d119009e0f53ddbb041
+ languageName: node
+ linkType: hard
+
"@types/eslint-scope@npm:^3.7.7":
version: 3.7.7
resolution: "@types/eslint-scope@npm:3.7.7"
@@ -5742,6 +5756,16 @@ __metadata:
languageName: node
linkType: hard
+"barcode-detector@npm:2.2.11":
+ version: 2.2.11
+ resolution: "barcode-detector@npm:2.2.11"
+ dependencies:
+ "@types/dom-webcodecs": "npm:^0.1.13"
+ zxing-wasm: "npm:1.2.14"
+ checksum: 10/91f04ac8a73a5fccf15d08c2b148e3f9584448956de9b2506f5c5c3213ba133c504cd6890926f4fde2b1294e42b9943f979820440bc8373388d5a86cb6c764c5
+ languageName: node
+ linkType: hard
+
"bare-events@npm:^2.2.0":
version: 2.5.0
resolution: "bare-events@npm:2.5.0"
@@ -8833,6 +8857,7 @@ __metadata:
app-datepicker: "npm:5.1.1"
babel-loader: "npm:9.2.1"
babel-plugin-template-html-minifier: "npm:4.1.0"
+ barcode-detector: "npm:2.2.11"
browserslist-useragent-regexp: "npm:4.1.3"
chai: "npm:5.1.2"
chart.js: "npm:4.4.6"
@@ -15465,3 +15490,12 @@ __metadata:
checksum: 10/f2e05b767ed3141e6372a80af9caa4715d60969227f38b1a4370d60bffe153c9c5b33a862905609afc9b375ec57cd40999810d20e5e10229a204e8bde7ef255c
languageName: node
linkType: hard
+
+"zxing-wasm@npm:1.2.14":
+ version: 1.2.14
+ resolution: "zxing-wasm@npm:1.2.14"
+ dependencies:
+ "@types/emscripten": "npm:^1.39.13"
+ checksum: 10/02ea0408553f1aebb412a97c5e11887c58da1b2adff636302232535e752b91bdb3f358411259b6cddd34b341df565336bc03f48895e362f61316018d9bbff8c3
+ languageName: node
+ linkType: hard