Add live camera & custom AR to Area card (#18643)

This commit is contained in:
karwosts 2023-11-18 08:26:21 -08:00 committed by GitHub
parent a1236924aa
commit a45eefa742
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 94 additions and 28 deletions

View File

@ -21,6 +21,7 @@ import {
} from "lit"; } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { STATES_OFF } from "../../../common/const"; import { STATES_OFF } from "../../../common/const";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; 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 { navigate } from "../../../common/navigate";
import { formatNumber } from "../../../common/number/format_number"; import { formatNumber } from "../../../common/number/format_number";
import { subscribeOne } from "../../../common/util/subscribe-one"; import { subscribeOne } from "../../../common/util/subscribe-one";
import parseAspectRatio from "../../../common/util/parse-aspect-ratio";
import "../../../components/entity/state-badge"; import "../../../components/entity/state-badge";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button";
@ -55,6 +57,8 @@ import "../components/hui-warning";
import { LovelaceCard, LovelaceCardEditor } from "../types"; import { LovelaceCard, LovelaceCardEditor } from "../types";
import { AreaCardConfig } from "./types"; import { AreaCardConfig } from "./types";
export const DEFAULT_ASPECT_RATIO = "16:9";
const SENSOR_DOMAINS = ["sensor"]; const SENSOR_DOMAINS = ["sensor"];
const ALERT_DOMAINS = ["binary_sensor"]; const ALERT_DOMAINS = ["binary_sensor"];
@ -109,6 +113,11 @@ export class HuiAreaCard
@state() private _areas?: AreaRegistryEntry[]; @state() private _areas?: AreaRegistryEntry[];
private _ratio: {
w: number;
h: number;
} | null = null;
private _entitiesByDomain = memoizeOne( private _entitiesByDomain = memoizeOne(
( (
areaId: string, areaId: string,
@ -319,6 +328,18 @@ export class HuiAreaCard
return false; 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() { protected render() {
if ( if (
!this._config || !this._config ||
@ -374,15 +395,24 @@ export class HuiAreaCard
cameraEntityId = entitiesByDomain.camera[0].entity_id; cameraEntityId = entitiesByDomain.camera[0].entity_id;
} }
const imageClass = area.picture || cameraEntityId;
return html` return html`
<ha-card class=${area.picture || cameraEntityId ? "image" : ""}> <ha-card
class=${imageClass ? "image" : ""}
style=${styleMap({
paddingBottom: imageClass
? "0"
: `${((100 * this._ratio!.h) / this._ratio!.w).toFixed(2)}%`,
})}
>
${area.picture || cameraEntityId ${area.picture || cameraEntityId
? html`<hui-image ? html`<hui-image
.config=${this._config} .config=${this._config}
.hass=${this.hass} .hass=${this.hass}
.image=${area.picture ? area.picture : undefined} .image=${area.picture ? area.picture : undefined}
.cameraImage=${cameraEntityId} .cameraImage=${cameraEntityId}
aspectRatio="16:9" .cameraView=${this._config.camera_view}
.aspectRatio=${this._config.aspect_ratio || DEFAULT_ASPECT_RATIO}
></hui-image>` ></hui-image>`
: ""} : ""}
@ -488,14 +518,9 @@ export class HuiAreaCard
ha-card { ha-card {
overflow: hidden; overflow: hidden;
position: relative; position: relative;
padding-bottom: 56.25%;
background-size: cover; background-size: cover;
} }
ha-card.image {
padding-bottom: 0;
}
.container { .container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -93,6 +93,8 @@ export interface AreaCardConfig extends LovelaceCardConfig {
area: string; area: string;
navigation_path?: string; navigation_path?: string;
show_camera?: boolean; show_camera?: boolean;
camera_view?: HuiImage["cameraView"];
aspect_ratio?: string;
} }
export interface ButtonCardConfig extends LovelaceCardConfig { export interface ButtonCardConfig extends LovelaceCardConfig {

View File

@ -1,8 +1,10 @@
import { html, LitElement, nothing } from "lit"; import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { assert, assign, boolean, object, optional, string } from "superstruct"; import { assert, assign, boolean, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-form/ha-form"; 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 { SchemaUnion } from "../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import type { AreaCardConfig } from "../../cards/types"; import type { AreaCardConfig } from "../../cards/types";
@ -16,12 +18,32 @@ const cardConfigStruct = assign(
navigation_path: optional(string()), navigation_path: optional(string()),
theme: optional(string()), theme: optional(string()),
show_camera: optional(boolean()), show_camera: optional(boolean()),
camera_view: optional(string()),
aspect_ratio: optional(string()),
}) })
); );
@customElement("hui-area-card-editor")
export class HuiAreaCardEditor
extends LitElement
implements LovelaceCardEditor
{
@property({ attribute: false }) public hass?: HomeAssistant;
const SCHEMA = [ @state() private _config?: AreaCardConfig;
private _schema = memoizeOne(
(showCamera: boolean) =>
[
{ name: "area", selector: { area: {} } }, { name: "area", selector: { area: {} } },
{ name: "show_camera", required: false, selector: { boolean: {} } }, { name: "show_camera", required: false, selector: { boolean: {} } },
...(showCamera
? ([
{
name: "camera_view",
selector: { select: { options: ["auto", "live"] } },
},
] as const)
: []),
{ {
name: "", name: "",
type: "grid", type: "grid",
@ -32,18 +54,15 @@ const SCHEMA = [
selector: { navigation: {} }, selector: { navigation: {} },
}, },
{ name: "theme", required: false, selector: { theme: {} } }, { name: "theme", required: false, selector: { theme: {} } },
{
name: "aspect_ratio",
default: DEFAULT_ASPECT_RATIO,
selector: { text: {} },
},
], ],
}, },
] as const; ] as const
);
@customElement("hui-area-card-editor")
export class HuiAreaCardEditor
extends LitElement
implements LovelaceCardEditor
{
@property({ attribute: false }) public hass?: HomeAssistant;
@state() private _config?: AreaCardConfig;
public setConfig(config: AreaCardConfig): void { public setConfig(config: AreaCardConfig): void {
assert(config, cardConfigStruct); assert(config, cardConfigStruct);
@ -55,11 +74,18 @@ export class HuiAreaCardEditor
return nothing; return nothing;
} }
const schema = this._schema(this._config.show_camera || false);
const data = {
camera_view: "auto",
...this._config,
};
return html` return html`
<ha-form <ha-form
.hass=${this.hass} .hass=${this.hass}
.data=${this._config} .data=${data}
.schema=${SCHEMA} .schema=${schema}
.computeLabel=${this._computeLabelCallback} .computeLabel=${this._computeLabelCallback}
@value-changed=${this._valueChanged} @value-changed=${this._valueChanged}
></ha-form> ></ha-form>
@ -68,10 +94,15 @@ export class HuiAreaCardEditor
private _valueChanged(ev: CustomEvent): void { private _valueChanged(ev: CustomEvent): void {
const config = ev.detail.value; const config = ev.detail.value;
if (!config.show_camera) {
delete config.camera_view;
}
fireEvent(this, "config-changed", { config }); fireEvent(this, "config-changed", { config });
} }
private _computeLabelCallback = (schema: SchemaUnion<typeof SCHEMA>) => { private _computeLabelCallback = (
schema: SchemaUnion<ReturnType<typeof this._schema>>
) => {
switch (schema.name) { switch (schema.name) {
case "theme": case "theme":
return `${this.hass!.localize( return `${this.hass!.localize(
@ -85,6 +116,14 @@ export class HuiAreaCardEditor
return this.hass!.localize( return this.hass!.localize(
"ui.panel.lovelace.editor.action-editor.navigation_path" "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( return this.hass!.localize(
`ui.panel.lovelace.editor.card.area.${schema.name}` `ui.panel.lovelace.editor.card.area.${schema.name}`