diff --git a/public/static/icons/spinner-48x48.svg b/public/static/icons/spinner-48x48.svg new file mode 100644 index 000000000000..a83f127af0b9 --- /dev/null +++ b/public/static/icons/spinner-48x48.svg @@ -0,0 +1 @@ + diff --git a/src/components/ha-hls-player.ts b/src/components/ha-hls-player.ts index 11280c3833e1..387d0c83b33d 100644 --- a/src/components/ha-hls-player.ts +++ b/src/components/ha-hls-player.ts @@ -180,7 +180,15 @@ class HaHLSPlayer extends LitElement { let playlist_url: string; if (match !== null && matchTwice === null) { // Only send the regular playlist url if we match exactly once - playlist_url = new URL(match[3], this._url).href; + // In case we arrive here with a relative URL, we need to provide a valid + // base/absolute URL to avoid the URL() constructor throwing an error. + let base_url: string; + try { + base_url = new URL(this._url).href; + } catch (error) { + base_url = new URL(this._url, window.location.href).href; + } + playlist_url = new URL(match[3], base_url).href; } else { playlist_url = this._url; } diff --git a/src/data/preview.ts b/src/data/preview.ts index 0018f1fe1c46..a26b030b8f2c 100644 --- a/src/data/preview.ts +++ b/src/data/preview.ts @@ -1,7 +1,7 @@ import type { UnsubscribeFunc } from "home-assistant-js-websocket"; import type { HomeAssistant } from "../types"; -const HAS_CUSTOM_PREVIEW = ["template"]; +const HAS_CUSTOM_PREVIEW = ["generic_camera", "template"]; export interface GenericPreview { state: string; diff --git a/src/dialogs/config-flow/previews/flow-preview-generic.ts b/src/dialogs/config-flow/previews/flow-preview-generic.ts index 436bf1f35cd6..baa839895a47 100644 --- a/src/dialogs/config-flow/previews/flow-preview-generic.ts +++ b/src/dialogs/config-flow/previews/flow-preview-generic.ts @@ -11,7 +11,7 @@ import { fireEvent } from "../../../common/dom/fire_event"; import "../../../components/ha-alert"; @customElement("flow-preview-generic") -class FlowPreviewGeneric extends LitElement { +export class FlowPreviewGeneric extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @property() public flowType!: FlowType; @@ -26,9 +26,9 @@ class FlowPreviewGeneric extends LitElement { @property() public stepData!: Record; - @state() private _preview?: HassEntity; + @state() protected _preview?: HassEntity; - @state() private _error?: string; + @state() protected _error?: string; private _unsub?: Promise; diff --git a/src/dialogs/config-flow/previews/flow-preview-generic_camera.ts b/src/dialogs/config-flow/previews/flow-preview-generic_camera.ts new file mode 100644 index 000000000000..ece1af723cc6 --- /dev/null +++ b/src/dialogs/config-flow/previews/flow-preview-generic_camera.ts @@ -0,0 +1,39 @@ +import { html } from "lit"; +import { customElement } from "lit/decorators"; +import { FlowPreviewGeneric } from "./flow-preview-generic"; +import "../../../components/ha-hls-player"; + +@customElement("flow-preview-generic_camera") +class FlowPreviewGenericCamera extends FlowPreviewGeneric { + protected override render() { + if (!this._preview) { + return html`${this._error}`; + } + + const stillUrl = this._preview.attributes.still_url; + const streamUrl = this._preview.attributes.stream_url; + + return html` ${stillUrl + ? html`

Still image:

+

+ Still preview +

` + : ""} + ${streamUrl + ? html`

Stream:

+ ` + : ""}`; + } +} + +declare global { + interface HTMLElementTagNameMap { + "flow-preview-generic_camera": FlowPreviewGenericCamera; + } +}