mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 01:36:49 +00:00
Area Card (#10141)
Co-authored-by: Philip Allgaier <mail@spacegaier.de> Co-authored-by: Bram Kragten <mail@bramkragten.nl> Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
a567312bdb
commit
4684979ae7
BIN
gallery/public/images/office.jpg
Normal file
BIN
gallery/public/images/office.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 147 KiB |
156
gallery/src/demos/demo-hui-area-card.ts
Normal file
156
gallery/src/demos/demo-hui-area-card.ts
Normal file
@ -0,0 +1,156 @@
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, query } from "lit/decorators";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("light", "bed_light", "on", {
|
||||
friendly_name: "Bed Light",
|
||||
}),
|
||||
getEntity("switch", "bed_ac", "on", {
|
||||
friendly_name: "Ecobee",
|
||||
}),
|
||||
getEntity("sensor", "bed_temp", "72", {
|
||||
friendly_name: "Bedroom Temp",
|
||||
device_class: "temperature",
|
||||
unit_of_measurement: "°F",
|
||||
}),
|
||||
getEntity("light", "living_room_light", "off", {
|
||||
friendly_name: "Living Room Light",
|
||||
}),
|
||||
getEntity("fan", "living_room", "on", {
|
||||
friendly_name: "Living Room Fan",
|
||||
}),
|
||||
getEntity("sensor", "office_humidity", "73", {
|
||||
friendly_name: "Office Humidity",
|
||||
device_class: "humidity",
|
||||
unit_of_measurement: "%",
|
||||
}),
|
||||
getEntity("light", "office", "on", {
|
||||
friendly_name: "Office Light",
|
||||
}),
|
||||
getEntity("fan", "kitchen", "on", {
|
||||
friendly_name: "Second Office Fan",
|
||||
}),
|
||||
getEntity("binary_sensor", "kitchen_door", "on", {
|
||||
friendly_name: "Office Door",
|
||||
device_class: "door",
|
||||
}),
|
||||
];
|
||||
|
||||
// TODO: Update image here
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: "Bedroom",
|
||||
config: `
|
||||
- type: area
|
||||
area: bedroom
|
||||
image: "/images/bed.png"
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Living Room",
|
||||
config: `
|
||||
- type: area
|
||||
area: living_room
|
||||
image: "/images/living_room.png"
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Office",
|
||||
config: `
|
||||
- type: area
|
||||
area: office
|
||||
image: "/images/office.jpg"
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Kitchen",
|
||||
config: `
|
||||
- type: area
|
||||
area: kitchen
|
||||
image: "/images/kitchen.png"
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-hui-area-card")
|
||||
class DemoArea extends LitElement {
|
||||
@query("#demos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
hass.mockWS("config/area_registry/list", () => [
|
||||
{
|
||||
name: "Bedroom",
|
||||
area_id: "bedroom",
|
||||
},
|
||||
{
|
||||
name: "Living Room",
|
||||
area_id: "living_room",
|
||||
},
|
||||
{
|
||||
name: "Office",
|
||||
area_id: "office",
|
||||
},
|
||||
{
|
||||
name: "Second Office",
|
||||
area_id: "kitchen",
|
||||
},
|
||||
]);
|
||||
hass.mockWS("config/device_registry/list", () => []);
|
||||
hass.mockWS("config/entity_registry/list", () => [
|
||||
{
|
||||
area_id: "bedroom",
|
||||
entity_id: "light.bed_light",
|
||||
},
|
||||
{
|
||||
area_id: "bedroom",
|
||||
entity_id: "switch.bed_ac",
|
||||
},
|
||||
{
|
||||
area_id: "bedroom",
|
||||
entity_id: "sensor.bed_temp",
|
||||
},
|
||||
{
|
||||
area_id: "living_room",
|
||||
entity_id: "light.living_room_light",
|
||||
},
|
||||
{
|
||||
area_id: "living_room",
|
||||
entity_id: "fan.living_room",
|
||||
},
|
||||
{
|
||||
area_id: "office",
|
||||
entity_id: "light.office",
|
||||
},
|
||||
{
|
||||
area_id: "office",
|
||||
entity_id: "sensor.office_humidity",
|
||||
},
|
||||
{
|
||||
area_id: "kitchen",
|
||||
entity_id: "fan.kitchen",
|
||||
},
|
||||
{
|
||||
area_id: "kitchen",
|
||||
entity_id: "binary_sensor.kitchen_door",
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-hui-area-card": DemoArea;
|
||||
}
|
||||
}
|
@ -172,6 +172,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
{
|
||||
area_id: "",
|
||||
name: this.hass.localize("ui.components.area-picker.no_areas"),
|
||||
picture: null,
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -295,6 +296,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
{
|
||||
area_id: "",
|
||||
name: this.hass.localize("ui.components.area-picker.no_match"),
|
||||
picture: null,
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -306,6 +308,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
{
|
||||
area_id: "add_new",
|
||||
name: this.hass.localize("ui.components.area-picker.add_new"),
|
||||
picture: null,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import { HomeAssistant } from "../types";
|
||||
export interface AreaRegistryEntry {
|
||||
area_id: string;
|
||||
name: string;
|
||||
picture?: string;
|
||||
picture: string | null;
|
||||
}
|
||||
|
||||
export interface AreaRegistryEntryMutableParams {
|
||||
|
431
src/panels/lovelace/cards/hui-area-card.ts
Normal file
431
src/panels/lovelace/cards/hui-area-card.ts
Normal file
@ -0,0 +1,431 @@
|
||||
import "@material/mwc-ripple";
|
||||
import type { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} 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 { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import "../../../components/entity/state-badge";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-state-icon";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
} from "../../../data/area_registry";
|
||||
import {
|
||||
DeviceRegistryEntry,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../../../data/device_registry";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../../data/entity_registry";
|
||||
import { forwardHaptic } from "../../../data/haptics";
|
||||
import { ActionHandlerEvent } from "../../../data/lovelace";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { toggleEntity } from "../common/entity/toggle-entity";
|
||||
import "../components/hui-warning";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { AreaCardConfig } from "./types";
|
||||
|
||||
const SENSOR_DOMAINS = new Set(["sensor", "binary_sensor"]);
|
||||
|
||||
const SENSOR_DEVICE_CLASSES = new Set([
|
||||
"temperature",
|
||||
"humidity",
|
||||
"motion",
|
||||
"door",
|
||||
"aqi",
|
||||
]);
|
||||
|
||||
const TOGGLE_DOMAINS = new Set(["light", "fan", "switch"]);
|
||||
|
||||
@customElement("hui-area-card")
|
||||
export class HuiAreaCard
|
||||
extends SubscribeMixin(LitElement)
|
||||
implements LovelaceCard
|
||||
{
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
await import("../editor/config-elements/hui-area-card-editor");
|
||||
return document.createElement("hui-area-card-editor");
|
||||
}
|
||||
|
||||
public static getStubConfig(): AreaCardConfig {
|
||||
return { type: "area", area: "" };
|
||||
}
|
||||
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _config?: AreaCardConfig;
|
||||
|
||||
@state() private _entities?: EntityRegistryEntry[];
|
||||
|
||||
@state() private _devices?: DeviceRegistryEntry[];
|
||||
|
||||
@state() private _areas?: AreaRegistryEntry[];
|
||||
|
||||
private _memberships = memoizeOne(
|
||||
(
|
||||
areaId: string,
|
||||
devicesInArea: Set<string>,
|
||||
registryEntities: EntityRegistryEntry[],
|
||||
states: HomeAssistant["states"]
|
||||
) => {
|
||||
const entitiesInArea = registryEntities
|
||||
.filter(
|
||||
(entry) =>
|
||||
!entry.entity_category &&
|
||||
(entry.area_id
|
||||
? entry.area_id === areaId
|
||||
: entry.device_id && devicesInArea.has(entry.device_id))
|
||||
)
|
||||
.map((entry) => entry.entity_id);
|
||||
|
||||
const sensorEntities: HassEntity[] = [];
|
||||
const entitiesToggle: HassEntity[] = [];
|
||||
|
||||
for (const entity of entitiesInArea) {
|
||||
const domain = computeDomain(entity);
|
||||
if (!TOGGLE_DOMAINS.has(domain) && !SENSOR_DOMAINS.has(domain)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const stateObj: HassEntity | undefined = states[entity];
|
||||
|
||||
if (!stateObj) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entitiesToggle.length < 3 && TOGGLE_DOMAINS.has(domain)) {
|
||||
entitiesToggle.push(stateObj);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
sensorEntities.length < 3 &&
|
||||
SENSOR_DOMAINS.has(domain) &&
|
||||
stateObj.attributes.device_class &&
|
||||
SENSOR_DEVICE_CLASSES.has(stateObj.attributes.device_class)
|
||||
) {
|
||||
sensorEntities.push(stateObj);
|
||||
}
|
||||
|
||||
if (sensorEntities.length === 3 && entitiesToggle.length === 3) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { sensorEntities, entitiesToggle };
|
||||
}
|
||||
);
|
||||
|
||||
private _area = memoizeOne(
|
||||
(areaId: string | undefined, areas: AreaRegistryEntry[]) =>
|
||||
areas.find((area) => area.area_id === areaId) || null
|
||||
);
|
||||
|
||||
private _devicesInArea = memoizeOne(
|
||||
(areaId: string | undefined, devices: DeviceRegistryEntry[]) =>
|
||||
new Set(
|
||||
areaId
|
||||
? devices
|
||||
.filter((device) => device.area_id === areaId)
|
||||
.map((device) => device.id)
|
||||
: []
|
||||
)
|
||||
);
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeAreaRegistry(this.hass!.connection, (areas) => {
|
||||
this._areas = areas;
|
||||
}),
|
||||
subscribeDeviceRegistry(this.hass!.connection, (devices) => {
|
||||
this._devices = devices;
|
||||
}),
|
||||
subscribeEntityRegistry(this.hass!.connection, (entries) => {
|
||||
this._entities = entries;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
public getCardSize(): number {
|
||||
return 3;
|
||||
}
|
||||
|
||||
public setConfig(config: AreaCardConfig): void {
|
||||
if (!config.area) {
|
||||
throw new Error("Area Required");
|
||||
}
|
||||
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
if (changedProps.has("_config") || !this._config) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
changedProps.has("_devicesInArea") ||
|
||||
changedProps.has("_area") ||
|
||||
changedProps.has("_entities")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!changedProps.has("hass")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
|
||||
if (
|
||||
!oldHass ||
|
||||
oldHass.themes !== this.hass!.themes ||
|
||||
oldHass.locale !== this.hass!.locale
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
!this._devices ||
|
||||
!this._devicesInArea(this._config.area, this._devices) ||
|
||||
!this._entities
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { sensorEntities, entitiesToggle } = this._memberships(
|
||||
this._config.area,
|
||||
this._devicesInArea(this._config.area, this._devices),
|
||||
this._entities,
|
||||
this.hass.states
|
||||
);
|
||||
|
||||
for (const stateObj of sensorEntities) {
|
||||
if (oldHass!.states[stateObj.entity_id] !== stateObj) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (const stateObj of entitiesToggle) {
|
||||
if (oldHass!.states[stateObj.entity_id] !== stateObj) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (
|
||||
!this._config ||
|
||||
!this.hass ||
|
||||
!this._areas ||
|
||||
!this._devices ||
|
||||
!this._entities
|
||||
) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const { sensorEntities, entitiesToggle } = this._memberships(
|
||||
this._config.area,
|
||||
this._devicesInArea(this._config.area, this._devices),
|
||||
this._entities,
|
||||
this.hass.states
|
||||
);
|
||||
|
||||
const area = this._area(this._config.area, this._areas);
|
||||
|
||||
if (area === null) {
|
||||
return html`
|
||||
<hui-warning>
|
||||
${this.hass.localize("ui.card.area.area_not_found")}
|
||||
</hui-warning>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
style=${styleMap({
|
||||
"background-image": `url(${this.hass.hassUrl(area.picture)})`,
|
||||
})}
|
||||
>
|
||||
<div class="container">
|
||||
<div class="sensors">
|
||||
${sensorEntities.map(
|
||||
(stateObj) => html`
|
||||
<span
|
||||
.entity=${stateObj.entity_id}
|
||||
@click=${this._handleMoreInfo}
|
||||
>
|
||||
<ha-state-icon .state=${stateObj}></ha-state-icon>
|
||||
${computeDomain(stateObj.entity_id) === "binary_sensor"
|
||||
? ""
|
||||
: html`
|
||||
${computeStateDisplay(
|
||||
this.hass!.localize,
|
||||
stateObj,
|
||||
this.hass!.locale
|
||||
)}
|
||||
`}
|
||||
</span>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div
|
||||
class="name ${this._config.navigation_path ? "navigate" : ""}"
|
||||
@click=${this._handleNavigation}
|
||||
>
|
||||
${area.name}
|
||||
</div>
|
||||
<div class="buttons">
|
||||
${entitiesToggle.map(
|
||||
(stateObj) => html`
|
||||
<ha-icon-button
|
||||
class=${classMap({
|
||||
off: stateObj.state === "off",
|
||||
})}
|
||||
.entity=${stateObj.entity_id}
|
||||
.actionHandler=${actionHandler({
|
||||
hasHold: true,
|
||||
})}
|
||||
@action=${this._handleAction}
|
||||
>
|
||||
<state-badge
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObj}
|
||||
stateColor
|
||||
></state-badge>
|
||||
</ha-icon-button>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
const oldConfig = changedProps.get("_config") as AreaCardConfig | undefined;
|
||||
|
||||
if (
|
||||
(changedProps.has("hass") &&
|
||||
(!oldHass || oldHass.themes !== this.hass.themes)) ||
|
||||
(changedProps.has("_config") &&
|
||||
(!oldConfig || oldConfig.theme !== this._config.theme))
|
||||
) {
|
||||
applyThemesOnElement(this, this.hass.themes, this._config.theme);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleMoreInfo(ev) {
|
||||
const entity = (ev.currentTarget as any).entity;
|
||||
fireEvent(this, "hass-more-info", { entityId: entity });
|
||||
}
|
||||
|
||||
private _handleNavigation() {
|
||||
if (this._config!.navigation_path) {
|
||||
navigate(this._config!.navigation_path);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleAction(ev: ActionHandlerEvent) {
|
||||
const entity = (ev.currentTarget as any).entity as string;
|
||||
if (ev.detail.action === "hold") {
|
||||
fireEvent(this, "hass-more-info", { entityId: entity });
|
||||
} else if (ev.detail.action === "tap") {
|
||||
toggleEntity(this.hass, entity);
|
||||
forwardHaptic("light");
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-card {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
padding-bottom: 56.25%;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.sensors {
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
--mdc-icon-size: 28px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.name {
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 8px 8px 16px;
|
||||
}
|
||||
|
||||
.name.navigate {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
state-badge {
|
||||
--ha-icon-display: inline;
|
||||
}
|
||||
|
||||
ha-icon-button {
|
||||
color: white;
|
||||
background-color: var(--area-button-color, rgb(175, 175, 175, 0.5));
|
||||
border-radius: 50%;
|
||||
margin-left: 8px;
|
||||
--mdc-icon-button-size: 44px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-area-card": HuiAreaCard;
|
||||
}
|
||||
}
|
@ -76,6 +76,11 @@ export interface EntitiesCardConfig extends LovelaceCardConfig {
|
||||
state_color?: boolean;
|
||||
}
|
||||
|
||||
export interface AreaCardConfig extends LovelaceCardConfig {
|
||||
area: string;
|
||||
navigation_path?: string;
|
||||
}
|
||||
|
||||
export interface ButtonCardConfig extends LovelaceCardConfig {
|
||||
entity?: string;
|
||||
name?: string;
|
||||
|
@ -33,6 +33,7 @@ const ALWAYS_LOADED_TYPES = new Set([
|
||||
|
||||
const LAZY_LOAD_TYPES = {
|
||||
"alarm-panel": () => import("../cards/hui-alarm-panel-card"),
|
||||
area: () => import("../cards/hui-area-card"),
|
||||
error: () => import("../cards/hui-error-card"),
|
||||
"empty-state": () => import("../cards/hui-empty-state-card"),
|
||||
"energy-usage-graph": () =>
|
||||
|
@ -0,0 +1,119 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { assert, assign, object, optional, string } from "superstruct";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-area-picker";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { AreaCardConfig } from "../../cards/types";
|
||||
import "../../components/hui-theme-select-editor";
|
||||
import { LovelaceCardEditor } from "../../types";
|
||||
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||
import { EditorTarget } from "../types";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
|
||||
const cardConfigStruct = assign(
|
||||
baseLovelaceCardConfig,
|
||||
object({
|
||||
area: optional(string()),
|
||||
navigation_path: optional(string()),
|
||||
theme: optional(string()),
|
||||
})
|
||||
);
|
||||
|
||||
@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 {
|
||||
assert(config, cardConfigStruct);
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
get _area(): string {
|
||||
return this._config!.area || "";
|
||||
}
|
||||
|
||||
get _navigation_path(): string {
|
||||
return this._config!.navigation_path || "";
|
||||
}
|
||||
|
||||
get _theme(): string {
|
||||
return this._config!.theme || "";
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || !this._config) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="card-config">
|
||||
<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._area}
|
||||
.placeholder=${this._area}
|
||||
.configValue=${"area"}
|
||||
.label=${this.hass.localize("ui.dialogs.entity_registry.editor.area")}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-area-picker>
|
||||
<paper-input
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.action-editor.navigation_path"
|
||||
)}
|
||||
.value=${this._navigation_path}
|
||||
.configValue=${"navigation_path"}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</paper-input>
|
||||
<hui-theme-select-editor
|
||||
.hass=${this.hass}
|
||||
.value=${this._theme}
|
||||
.configValue=${"theme"}
|
||||
@value-changed=${this._valueChanged}
|
||||
></hui-theme-select-editor>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
const value = ev.detail.value;
|
||||
|
||||
if (this[`_${target.configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newConfig;
|
||||
if (target.configValue) {
|
||||
if (!value) {
|
||||
newConfig = { ...this._config };
|
||||
delete newConfig[target.configValue!];
|
||||
} else {
|
||||
newConfig = {
|
||||
...this._config,
|
||||
[target.configValue!]: value,
|
||||
};
|
||||
}
|
||||
}
|
||||
fireEvent(this, "config-changed", { config: newConfig });
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return configElementStyle;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-area-card-editor": HuiAreaCardEditor;
|
||||
}
|
||||
}
|
@ -89,6 +89,9 @@ export const coreCards: Card[] = [
|
||||
type: "weather-forecast",
|
||||
showElement: true,
|
||||
},
|
||||
{
|
||||
type: "area",
|
||||
},
|
||||
{
|
||||
type: "conditional",
|
||||
},
|
||||
|
@ -116,6 +116,9 @@
|
||||
"arm_vacation": "Arm vacation",
|
||||
"arm_custom_bypass": "Custom bypass"
|
||||
},
|
||||
"area": {
|
||||
"area_not_found": "Area not found."
|
||||
},
|
||||
"automation": {
|
||||
"last_triggered": "Last triggered",
|
||||
"trigger": "Run Actions"
|
||||
@ -3212,6 +3215,10 @@
|
||||
"available_states": "Available States",
|
||||
"description": "The Alarm Panel card allows you to Arm and Disarm your alarm control panel integrations."
|
||||
},
|
||||
"area": {
|
||||
"name": "Area",
|
||||
"description": "The Area card automatically displays entities of a specific area."
|
||||
},
|
||||
"calendar": {
|
||||
"name": "Calendar",
|
||||
"description": "The Calendar card displays a calendar including day, week and list views",
|
||||
|
Loading…
x
Reference in New Issue
Block a user