mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-18 23:06:40 +00:00
20241105.0 (#22679)
This commit is contained in:
commit
a6971d61d1
@ -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);
|
||||
});
|
||||
|
||||
|
@ -1,16 +0,0 @@
|
||||
import { html } from "lit";
|
||||
import type { DemoConfig } from "../types";
|
||||
|
||||
export const demoLovelaceDescription: DemoConfig["description"] = (
|
||||
localize
|
||||
) => html`
|
||||
<p>
|
||||
${localize("ui.panel.page-demo.config.sections.description", {
|
||||
blog_post: html`<a
|
||||
href="https://www.home-assistant.io/blog/2024/03/04/dashboard-chapter-1/"
|
||||
target="_blank"
|
||||
>${localize("ui.panel.page-demo.config.sections.description_blog_post")}
|
||||
</a>`,
|
||||
})}
|
||||
</p>
|
||||
`;
|
@ -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: () => ({}),
|
||||
|
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -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`<img
|
||||
.src=${__DEMO__
|
||||
? this.stateObj.attributes.entity_picture!
|
||||
: this._connected
|
||||
? computeMJPEGStreamUrl(this.stateObj)
|
||||
: ""}
|
||||
: this._posterUrl || ""}
|
||||
alt=${`Preview of the ${computeStateName(this.stateObj)} camera.`}
|
||||
/>`;
|
||||
}
|
||||
return html`${this._streamType === STREAM_TYPE_HLS ||
|
||||
(!this._streamType &&
|
||||
this._capabilities?.frontend_stream_types.includes(STREAM_TYPE_HLS))
|
||||
? html`<ha-hls-player
|
||||
autoplay
|
||||
playsinline
|
||||
.allowExoPlayer=${this.allowExoPlayer}
|
||||
.muted=${this.muted}
|
||||
.controls=${this.controls}
|
||||
.hass=${this.hass}
|
||||
.entityid=${this.stateObj.entity_id}
|
||||
.posterUrl=${this._posterUrl}
|
||||
@streams=${this._handleHlsStreams}
|
||||
class=${!this._streamType && this._webRtcStreams?.hasVideo
|
||||
? "hidden"
|
||||
: ""}
|
||||
></ha-hls-player>`
|
||||
: nothing}
|
||||
${this._streamType === STREAM_TYPE_WEB_RTC ||
|
||||
(!this._streamType &&
|
||||
this._capabilities?.frontend_stream_types.includes(STREAM_TYPE_WEB_RTC))
|
||||
? html`<ha-web-rtc-player
|
||||
autoplay
|
||||
playsinline
|
||||
.muted=${this.muted}
|
||||
.controls=${this.controls}
|
||||
.hass=${this.hass}
|
||||
.entityid=${this.stateObj.entity_id}
|
||||
.posterUrl=${this._posterUrl}
|
||||
@streams=${this._handleWebRtcStreams}
|
||||
class=${this._streamType !== STREAM_TYPE_WEB_RTC &&
|
||||
!this._webRtcStreams
|
||||
? "hidden"
|
||||
: ""}
|
||||
></ha-web-rtc-player>`
|
||||
: nothing}`;
|
||||
|
||||
if (stream.type === STREAM_TYPE_HLS) {
|
||||
return html`<ha-hls-player
|
||||
autoplay
|
||||
playsinline
|
||||
.allowExoPlayer=${this.allowExoPlayer}
|
||||
.muted=${this.muted}
|
||||
.controls=${this.controls}
|
||||
.hass=${this.hass}
|
||||
.entityid=${this.stateObj.entity_id}
|
||||
.posterUrl=${this._posterUrl}
|
||||
@streams=${this._handleHlsStreams}
|
||||
class=${stream.visible ? "" : "hidden"}
|
||||
></ha-hls-player>`;
|
||||
}
|
||||
|
||||
if (stream.type === STREAM_TYPE_WEB_RTC) {
|
||||
return html`<ha-web-rtc-player
|
||||
autoplay
|
||||
playsinline
|
||||
.muted=${this.muted}
|
||||
.controls=${this.controls}
|
||||
.hass=${this.hass}
|
||||
.entityid=${this.stateObj.entity_id}
|
||||
.posterUrl=${this._posterUrl}
|
||||
@streams=${this._handleWebRtcStreams}
|
||||
class=${stream.visible ? "" : "hidden"}
|
||||
></ha-web-rtc-player>`;
|
||||
}
|
||||
|
||||
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<void> {
|
||||
@ -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`
|
||||
|
@ -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);
|
||||
}
|
||||
|
26
src/components/ha-md-select-option.ts
Normal file
26
src/components/ha-md-select-option.ts
Normal file
@ -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;
|
||||
}
|
||||
}
|
32
src/components/ha-md-select.ts
Normal file
32
src/components/ha-md-select.ts
Normal file
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
|
@ -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),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
)}:
|
||||
</div>
|
||||
<ha-select
|
||||
<ha-md-select
|
||||
.label=${this.hass.localize("ui.panel.config.logs.lines")}
|
||||
@selected=${this._setNumberOfLogs}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
@closed=${stopPropagation}
|
||||
@change=${this._setNumberOfLogs}
|
||||
.value=${String(this._lineCount)}
|
||||
>
|
||||
${numberOfLinesOptions.map(
|
||||
(option) => html`
|
||||
<ha-list-item .value=${String(option)}>
|
||||
<ha-md-select-option .value=${String(option)}>
|
||||
${option}
|
||||
</ha-list-item>
|
||||
</ha-md-select-option>
|
||||
`
|
||||
)}
|
||||
</ha-select>
|
||||
</ha-md-select>
|
||||
</div>
|
||||
<div slot="actions">
|
||||
<ha-button @click=${this.closeDialog}>
|
||||
@ -137,6 +133,7 @@ class DownloadLogsDialog extends LitElement {
|
||||
css`
|
||||
:host {
|
||||
direction: var(--direction);
|
||||
--dialog-content-overflow: visible;
|
||||
}
|
||||
.content {
|
||||
display: flex;
|
||||
|
@ -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",
|
||||
|
34
yarn.lock
34
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user