From a45eefa742c3bc18b1e8efd27017fa53e089660f Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Sat, 18 Nov 2023 08:26:21 -0800 Subject: [PATCH] Add live camera & custom AR to Area card (#18643) --- src/panels/lovelace/cards/hui-area-card.ts | 39 +++++++-- src/panels/lovelace/cards/types.ts | 2 + .../config-elements/hui-area-card-editor.ts | 81 ++++++++++++++----- 3 files changed, 94 insertions(+), 28 deletions(-) diff --git a/src/panels/lovelace/cards/hui-area-card.ts b/src/panels/lovelace/cards/hui-area-card.ts index 70a0258867..eb80dbd2f5 100644 --- a/src/panels/lovelace/cards/hui-area-card.ts +++ b/src/panels/lovelace/cards/hui-area-card.ts @@ -21,6 +21,7 @@ import { } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; +import { styleMap } from "lit/directives/style-map"; import memoizeOne from "memoize-one"; import { STATES_OFF } from "../../../common/const"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; @@ -29,6 +30,7 @@ import { domainIcon } from "../../../common/entity/domain_icon"; import { navigate } from "../../../common/navigate"; import { formatNumber } from "../../../common/number/format_number"; import { subscribeOne } from "../../../common/util/subscribe-one"; +import parseAspectRatio from "../../../common/util/parse-aspect-ratio"; import "../../../components/entity/state-badge"; import "../../../components/ha-card"; import "../../../components/ha-icon-button"; @@ -55,6 +57,8 @@ import "../components/hui-warning"; import { LovelaceCard, LovelaceCardEditor } from "../types"; import { AreaCardConfig } from "./types"; +export const DEFAULT_ASPECT_RATIO = "16:9"; + const SENSOR_DOMAINS = ["sensor"]; const ALERT_DOMAINS = ["binary_sensor"]; @@ -109,6 +113,11 @@ export class HuiAreaCard @state() private _areas?: AreaRegistryEntry[]; + private _ratio: { + w: number; + h: number; + } | null = null; + private _entitiesByDomain = memoizeOne( ( areaId: string, @@ -319,6 +328,18 @@ export class HuiAreaCard return false; } + public willUpdate(changedProps: PropertyValues) { + if (changedProps.has("_config") || this._ratio === null) { + this._ratio = this._config?.aspect_ratio + ? parseAspectRatio(this._config?.aspect_ratio) + : null; + + if (this._ratio === null || this._ratio.w <= 0 || this._ratio.h <= 0) { + this._ratio = parseAspectRatio(DEFAULT_ASPECT_RATIO); + } + } + } + protected render() { if ( !this._config || @@ -374,15 +395,24 @@ export class HuiAreaCard cameraEntityId = entitiesByDomain.camera[0].entity_id; } + const imageClass = area.picture || cameraEntityId; return html` - + ${area.picture || cameraEntityId ? html`` : ""} @@ -488,14 +518,9 @@ export class HuiAreaCard ha-card { overflow: hidden; position: relative; - padding-bottom: 56.25%; background-size: cover; } - ha-card.image { - padding-bottom: 0; - } - .container { display: flex; flex-direction: column; diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index aa2c2b145d..d878e690d8 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -93,6 +93,8 @@ export interface AreaCardConfig extends LovelaceCardConfig { area: string; navigation_path?: string; show_camera?: boolean; + camera_view?: HuiImage["cameraView"]; + aspect_ratio?: string; } export interface ButtonCardConfig extends LovelaceCardConfig { diff --git a/src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts index 6a486565f3..c28567945a 100644 --- a/src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts @@ -1,8 +1,10 @@ import { html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; import { assert, assign, boolean, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-form/ha-form"; +import { DEFAULT_ASPECT_RATIO } from "../../cards/hui-area-card"; import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { AreaCardConfig } from "../../cards/types"; @@ -16,26 +18,10 @@ const cardConfigStruct = assign( navigation_path: optional(string()), theme: optional(string()), show_camera: optional(boolean()), + camera_view: optional(string()), + aspect_ratio: optional(string()), }) ); - -const SCHEMA = [ - { name: "area", selector: { area: {} } }, - { name: "show_camera", required: false, selector: { boolean: {} } }, - { - name: "", - type: "grid", - schema: [ - { - name: "navigation_path", - required: false, - selector: { navigation: {} }, - }, - { name: "theme", required: false, selector: { theme: {} } }, - ], - }, -] as const; - @customElement("hui-area-card-editor") export class HuiAreaCardEditor extends LitElement @@ -45,6 +31,39 @@ export class HuiAreaCardEditor @state() private _config?: AreaCardConfig; + private _schema = memoizeOne( + (showCamera: boolean) => + [ + { name: "area", selector: { area: {} } }, + { name: "show_camera", required: false, selector: { boolean: {} } }, + ...(showCamera + ? ([ + { + name: "camera_view", + selector: { select: { options: ["auto", "live"] } }, + }, + ] as const) + : []), + { + name: "", + type: "grid", + schema: [ + { + name: "navigation_path", + required: false, + selector: { navigation: {} }, + }, + { name: "theme", required: false, selector: { theme: {} } }, + { + name: "aspect_ratio", + default: DEFAULT_ASPECT_RATIO, + selector: { text: {} }, + }, + ], + }, + ] as const + ); + public setConfig(config: AreaCardConfig): void { assert(config, cardConfigStruct); this._config = config; @@ -55,11 +74,18 @@ export class HuiAreaCardEditor return nothing; } + const schema = this._schema(this._config.show_camera || false); + + const data = { + camera_view: "auto", + ...this._config, + }; + return html` @@ -68,10 +94,15 @@ export class HuiAreaCardEditor private _valueChanged(ev: CustomEvent): void { const config = ev.detail.value; + if (!config.show_camera) { + delete config.camera_view; + } fireEvent(this, "config-changed", { config }); } - private _computeLabelCallback = (schema: SchemaUnion) => { + private _computeLabelCallback = ( + schema: SchemaUnion> + ) => { switch (schema.name) { case "theme": return `${this.hass!.localize( @@ -85,6 +116,14 @@ export class HuiAreaCardEditor return this.hass!.localize( "ui.panel.lovelace.editor.action-editor.navigation_path" ); + case "aspect_ratio": + return this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.aspect_ratio" + ); + case "camera_view": + return this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.camera_view" + ); } return this.hass!.localize( `ui.panel.lovelace.editor.card.area.${schema.name}`