mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-26 02:36:37 +00:00
Add live camera & custom AR to Area card (#18643)
This commit is contained in:
parent
a1236924aa
commit
a45eefa742
@ -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;
|
||||||
|
@ -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 {
|
||||||
|
@ -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,26 +18,10 @@ 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()),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
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")
|
@customElement("hui-area-card-editor")
|
||||||
export class HuiAreaCardEditor
|
export class HuiAreaCardEditor
|
||||||
extends LitElement
|
extends LitElement
|
||||||
@ -45,6 +31,39 @@ export class HuiAreaCardEditor
|
|||||||
|
|
||||||
@state() private _config?: AreaCardConfig;
|
@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 {
|
public setConfig(config: AreaCardConfig): void {
|
||||||
assert(config, cardConfigStruct);
|
assert(config, cardConfigStruct);
|
||||||
this._config = config;
|
this._config = config;
|
||||||
@ -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}`
|
||||||
|
Loading…
x
Reference in New Issue
Block a user