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`${`Preview`; } - 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