mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
Add icon to areas (#19585)
* Add icon to areas * Fix gallery --------- Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
This commit is contained in:
parent
b159f4c074
commit
f4859320eb
@ -10,6 +10,7 @@ import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervis
|
|||||||
import { computeInitialHaFormData } from "../../../../src/components/ha-form/compute-initial-ha-form-data";
|
import { computeInitialHaFormData } from "../../../../src/components/ha-form/compute-initial-ha-form-data";
|
||||||
import "../../../../src/components/ha-form/ha-form";
|
import "../../../../src/components/ha-form/ha-form";
|
||||||
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
||||||
|
import type { AreaRegistryEntry } from "../../../../src/data/area_registry";
|
||||||
import { getEntity } from "../../../../src/fake_data/entity";
|
import { getEntity } from "../../../../src/fake_data/entity";
|
||||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||||
import { HomeAssistant } from "../../../../src/types";
|
import { HomeAssistant } from "../../../../src/types";
|
||||||
@ -97,22 +98,25 @@ const DEVICES = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const AREAS = [
|
const AREAS: AreaRegistryEntry[] = [
|
||||||
{
|
{
|
||||||
area_id: "backyard",
|
area_id: "backyard",
|
||||||
name: "Backyard",
|
name: "Backyard",
|
||||||
|
icon: null,
|
||||||
picture: null,
|
picture: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: "bedroom",
|
area_id: "bedroom",
|
||||||
name: "Bedroom",
|
name: "Bedroom",
|
||||||
|
icon: "mdi:bed",
|
||||||
picture: null,
|
picture: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: "livingroom",
|
area_id: "livingroom",
|
||||||
name: "Livingroom",
|
name: "Livingroom",
|
||||||
|
icon: "mdi:sofa",
|
||||||
picture: null,
|
picture: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
},
|
},
|
||||||
|
@ -9,6 +9,7 @@ import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
|||||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||||
import "../../../../src/components/ha-selector/ha-selector";
|
import "../../../../src/components/ha-selector/ha-selector";
|
||||||
import "../../../../src/components/ha-settings-row";
|
import "../../../../src/components/ha-settings-row";
|
||||||
|
import type { AreaRegistryEntry } from "../../../../src/data/area_registry";
|
||||||
import { BlueprintInput } from "../../../../src/data/blueprint";
|
import { BlueprintInput } from "../../../../src/data/blueprint";
|
||||||
import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
|
import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||||
import { getEntity } from "../../../../src/fake_data/entity";
|
import { getEntity } from "../../../../src/fake_data/entity";
|
||||||
@ -93,22 +94,25 @@ const DEVICES = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const AREAS = [
|
const AREAS: AreaRegistryEntry[] = [
|
||||||
{
|
{
|
||||||
area_id: "backyard",
|
area_id: "backyard",
|
||||||
name: "Backyard",
|
name: "Backyard",
|
||||||
|
icon: null,
|
||||||
picture: null,
|
picture: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: "bedroom",
|
area_id: "bedroom",
|
||||||
name: "Bedroom",
|
name: "Bedroom",
|
||||||
|
icon: "mdi:bed",
|
||||||
picture: null,
|
picture: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: "livingroom",
|
area_id: "livingroom",
|
||||||
name: "Livingroom",
|
name: "Livingroom",
|
||||||
|
icon: "mdi:sofa",
|
||||||
picture: null,
|
picture: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
import { html, LitElement, nothing, PropertyValues, TemplateResult } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
@ -36,8 +36,12 @@ type ScorableAreaRegistryEntry = ScorableTextItem & AreaRegistryEntry;
|
|||||||
|
|
||||||
const rowRenderer: ComboBoxLitRenderer<AreaRegistryEntry> = (item) =>
|
const rowRenderer: ComboBoxLitRenderer<AreaRegistryEntry> = (item) =>
|
||||||
html`<ha-list-item
|
html`<ha-list-item
|
||||||
|
graphic="icon"
|
||||||
class=${classMap({ "add-new": item.area_id === "add_new" })}
|
class=${classMap({ "add-new": item.area_id === "add_new" })}
|
||||||
>
|
>
|
||||||
|
${item.icon
|
||||||
|
? html`<ha-icon slot="graphic" .icon=${item.icon}></ha-icon>`
|
||||||
|
: nothing}
|
||||||
${item.name}
|
${item.name}
|
||||||
</ha-list-item>`;
|
</ha-list-item>`;
|
||||||
|
|
||||||
@ -135,6 +139,7 @@ export class HaAreaPicker extends LitElement {
|
|||||||
area_id: "no_areas",
|
area_id: "no_areas",
|
||||||
name: this.hass.localize("ui.components.area-picker.no_areas"),
|
name: this.hass.localize("ui.components.area-picker.no_areas"),
|
||||||
picture: null,
|
picture: null,
|
||||||
|
icon: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -262,7 +267,9 @@ export class HaAreaPicker extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (areaIds) {
|
if (areaIds) {
|
||||||
outputAreas = areas.filter((area) => areaIds!.includes(area.area_id));
|
outputAreas = outputAreas.filter((area) =>
|
||||||
|
areaIds!.includes(area.area_id)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (excludeAreas) {
|
if (excludeAreas) {
|
||||||
@ -277,6 +284,7 @@ export class HaAreaPicker extends LitElement {
|
|||||||
area_id: "no_areas",
|
area_id: "no_areas",
|
||||||
name: this.hass.localize("ui.components.area-picker.no_match"),
|
name: this.hass.localize("ui.components.area-picker.no_match"),
|
||||||
picture: null,
|
picture: null,
|
||||||
|
icon: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -290,6 +298,7 @@ export class HaAreaPicker extends LitElement {
|
|||||||
area_id: "add_new",
|
area_id: "add_new",
|
||||||
name: this.hass.localize("ui.components.area-picker.add_new"),
|
name: this.hass.localize("ui.components.area-picker.add_new"),
|
||||||
picture: null,
|
picture: null,
|
||||||
|
icon: "mdi:plus",
|
||||||
aliases: [],
|
aliases: [],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -98,6 +98,7 @@ export class HaTargetPicker extends LitElement {
|
|||||||
area_id,
|
area_id,
|
||||||
area?.name || area_id,
|
area?.name || area_id,
|
||||||
undefined,
|
undefined,
|
||||||
|
area?.icon,
|
||||||
mdiSofa
|
mdiSofa
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@ -110,6 +111,7 @@ export class HaTargetPicker extends LitElement {
|
|||||||
device_id,
|
device_id,
|
||||||
device ? computeDeviceName(device, this.hass) : device_id,
|
device ? computeDeviceName(device, this.hass) : device_id,
|
||||||
undefined,
|
undefined,
|
||||||
|
undefined,
|
||||||
mdiDevices
|
mdiDevices
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@ -209,7 +211,8 @@ export class HaTargetPicker extends LitElement {
|
|||||||
id: string,
|
id: string,
|
||||||
name: string,
|
name: string,
|
||||||
entityState?: HassEntity,
|
entityState?: HassEntity,
|
||||||
iconPath?: string
|
icon?: string | null,
|
||||||
|
fallbackIconPath?: string
|
||||||
) {
|
) {
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
@ -217,12 +220,17 @@ export class HaTargetPicker extends LitElement {
|
|||||||
[type]: true,
|
[type]: true,
|
||||||
})}"
|
})}"
|
||||||
>
|
>
|
||||||
${iconPath
|
${icon
|
||||||
? html`<ha-svg-icon
|
? html`<ha-icon
|
||||||
class="mdc-chip__icon mdc-chip__icon--leading"
|
class="mdc-chip__icon mdc-chip__icon--leading"
|
||||||
.path=${iconPath}
|
.icon=${icon}
|
||||||
></ha-svg-icon>`
|
></ha-icon>`
|
||||||
: ""}
|
: fallbackIconPath
|
||||||
|
? html`<ha-svg-icon
|
||||||
|
class="mdc-chip__icon mdc-chip__icon--leading"
|
||||||
|
.path=${fallbackIconPath}
|
||||||
|
></ha-svg-icon>`
|
||||||
|
: ""}
|
||||||
${entityState
|
${entityState
|
||||||
? html`<ha-state-icon
|
? html`<ha-state-icon
|
||||||
class="mdc-chip__icon mdc-chip__icon--leading"
|
class="mdc-chip__icon mdc-chip__icon--leading"
|
||||||
|
@ -9,6 +9,7 @@ export interface AreaRegistryEntry {
|
|||||||
area_id: string;
|
area_id: string;
|
||||||
name: string;
|
name: string;
|
||||||
picture: string | null;
|
picture: string | null;
|
||||||
|
icon: string | null;
|
||||||
aliases: string[];
|
aliases: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,6 +24,7 @@ export interface AreaDeviceLookup {
|
|||||||
export interface AreaRegistryEntryMutableParams {
|
export interface AreaRegistryEntryMutableParams {
|
||||||
name: string;
|
name: string;
|
||||||
picture?: string | null;
|
picture?: string | null;
|
||||||
|
icon?: string | null;
|
||||||
aliases?: string[];
|
aliases?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,19 +1,12 @@
|
|||||||
import { Connection, createCollection } from "home-assistant-js-websocket";
|
import { Connection, createCollection } from "home-assistant-js-websocket";
|
||||||
import { Store } from "home-assistant-js-websocket/dist/store";
|
import { Store } from "home-assistant-js-websocket/dist/store";
|
||||||
import { stringCompare } from "../common/string/compare";
|
|
||||||
import { AreaRegistryEntry } from "./area_registry";
|
|
||||||
import { debounce } from "../common/util/debounce";
|
import { debounce } from "../common/util/debounce";
|
||||||
|
import { AreaRegistryEntry } from "./area_registry";
|
||||||
|
|
||||||
const fetchAreaRegistry = (conn: Connection) =>
|
const fetchAreaRegistry = (conn: Connection) =>
|
||||||
conn
|
conn.sendMessagePromise<AreaRegistryEntry[]>({
|
||||||
.sendMessagePromise({
|
type: "config/area_registry/list",
|
||||||
type: "config/area_registry/list",
|
});
|
||||||
})
|
|
||||||
.then((areas) =>
|
|
||||||
(areas as AreaRegistryEntry[]).sort((ent1, ent2) =>
|
|
||||||
stringCompare(ent1.name, ent2.name)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const subscribeAreaRegistryUpdates = (
|
const subscribeAreaRegistryUpdates = (
|
||||||
conn: Connection,
|
conn: Connection,
|
||||||
|
@ -9,6 +9,7 @@ import { createCloseHeading } from "../../../components/ha-dialog";
|
|||||||
import "../../../components/ha-picture-upload";
|
import "../../../components/ha-picture-upload";
|
||||||
import type { HaPictureUpload } from "../../../components/ha-picture-upload";
|
import type { HaPictureUpload } from "../../../components/ha-picture-upload";
|
||||||
import "../../../components/ha-settings-row";
|
import "../../../components/ha-settings-row";
|
||||||
|
import "../../../components/ha-icon-picker";
|
||||||
import "../../../components/ha-textfield";
|
import "../../../components/ha-textfield";
|
||||||
import { AreaRegistryEntryMutableParams } from "../../../data/area_registry";
|
import { AreaRegistryEntryMutableParams } from "../../../data/area_registry";
|
||||||
import { CropOptions } from "../../../dialogs/image-cropper-dialog/show-image-cropper-dialog";
|
import { CropOptions } from "../../../dialogs/image-cropper-dialog/show-image-cropper-dialog";
|
||||||
@ -32,6 +33,8 @@ class DialogAreaDetail extends LitElement {
|
|||||||
|
|
||||||
@state() private _picture!: string | null;
|
@state() private _picture!: string | null;
|
||||||
|
|
||||||
|
@state() private _icon!: string | null;
|
||||||
|
|
||||||
@state() private _error?: string;
|
@state() private _error?: string;
|
||||||
|
|
||||||
@state() private _params?: AreaRegistryDetailDialogParams;
|
@state() private _params?: AreaRegistryDetailDialogParams;
|
||||||
@ -46,6 +49,7 @@ class DialogAreaDetail extends LitElement {
|
|||||||
this._name = this._params.entry ? this._params.entry.name : "";
|
this._name = this._params.entry ? this._params.entry.name : "";
|
||||||
this._aliases = this._params.entry ? this._params.entry.aliases : [];
|
this._aliases = this._params.entry ? this._params.entry.aliases : [];
|
||||||
this._picture = this._params.entry?.picture || null;
|
this._picture = this._params.entry?.picture || null;
|
||||||
|
this._icon = this._params.entry?.icon || null;
|
||||||
await this.updateComplete;
|
await this.updateComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,6 +105,13 @@ class DialogAreaDetail extends LitElement {
|
|||||||
dialogInitialFocus
|
dialogInitialFocus
|
||||||
></ha-textfield>
|
></ha-textfield>
|
||||||
|
|
||||||
|
<ha-icon-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this._icon}
|
||||||
|
@value-changed=${this._iconChanged}
|
||||||
|
.label=${this.hass.localize("ui.panel.config.areas.editor.icon")}
|
||||||
|
></ha-icon-picker>
|
||||||
|
|
||||||
<ha-picture-upload
|
<ha-picture-upload
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this._picture}
|
.value=${this._picture}
|
||||||
@ -152,23 +163,30 @@ class DialogAreaDetail extends LitElement {
|
|||||||
this._name = ev.target.value;
|
this._name = ev.target.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _iconChanged(ev) {
|
||||||
|
this._error = undefined;
|
||||||
|
this._icon = ev.detail.value;
|
||||||
|
}
|
||||||
|
|
||||||
private _pictureChanged(ev: ValueChangedEvent<string | null>) {
|
private _pictureChanged(ev: ValueChangedEvent<string | null>) {
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
this._picture = (ev.target as HaPictureUpload).value;
|
this._picture = (ev.target as HaPictureUpload).value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _updateEntry() {
|
private async _updateEntry() {
|
||||||
|
const create = !this._params!.entry;
|
||||||
this._submitting = true;
|
this._submitting = true;
|
||||||
try {
|
try {
|
||||||
const values: AreaRegistryEntryMutableParams = {
|
const values: AreaRegistryEntryMutableParams = {
|
||||||
name: this._name.trim(),
|
name: this._name.trim(),
|
||||||
picture: this._picture,
|
picture: this._picture || (create ? undefined : null),
|
||||||
|
icon: this._icon || (create ? undefined : null),
|
||||||
aliases: this._aliases,
|
aliases: this._aliases,
|
||||||
};
|
};
|
||||||
if (this._params!.entry) {
|
if (create) {
|
||||||
await this._params!.updateEntry!(values);
|
|
||||||
} else {
|
|
||||||
await this._params!.createEntry!(values);
|
await this._params!.createEntry!(values);
|
||||||
|
} else {
|
||||||
|
await this._params!.updateEntry!(values);
|
||||||
}
|
}
|
||||||
this.closeDialog();
|
this.closeDialog();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@ -189,6 +207,7 @@ class DialogAreaDetail extends LitElement {
|
|||||||
haStyleDialog,
|
haStyleDialog,
|
||||||
css`
|
css`
|
||||||
ha-textfield,
|
ha-textfield,
|
||||||
|
ha-icon-picker,
|
||||||
ha-picture-upload {
|
ha-picture-upload {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
|
import { consume } from "@lit-labs/context";
|
||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@material/mwc-list";
|
import "@material/mwc-list";
|
||||||
import { mdiDelete, mdiDotsVertical, mdiImagePlus, mdiPencil } from "@mdi/js";
|
import { mdiDelete, mdiDotsVertical, mdiImagePlus, mdiPencil } from "@mdi/js";
|
||||||
import {
|
import { HassEntity } from "home-assistant-js-websocket/dist/types";
|
||||||
HassEntity,
|
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||||
UnsubscribeFunc,
|
|
||||||
} from "home-assistant-js-websocket/dist/types";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
@ -18,33 +16,31 @@ import { afterNextRender } from "../../../common/util/render-status";
|
|||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import "../../../components/ha-icon-next";
|
import "../../../components/ha-icon-next";
|
||||||
|
import "../../../components/ha-list-item";
|
||||||
import {
|
import {
|
||||||
AreaRegistryEntry,
|
AreaRegistryEntry,
|
||||||
deleteAreaRegistryEntry,
|
deleteAreaRegistryEntry,
|
||||||
subscribeAreaRegistry,
|
|
||||||
updateAreaRegistryEntry,
|
updateAreaRegistryEntry,
|
||||||
} from "../../../data/area_registry";
|
} from "../../../data/area_registry";
|
||||||
import { AutomationEntity } from "../../../data/automation";
|
import { AutomationEntity } from "../../../data/automation";
|
||||||
|
import { fullEntitiesContext } from "../../../data/context";
|
||||||
import {
|
import {
|
||||||
computeDeviceName,
|
|
||||||
DeviceRegistryEntry,
|
DeviceRegistryEntry,
|
||||||
|
computeDeviceName,
|
||||||
sortDeviceRegistryByName,
|
sortDeviceRegistryByName,
|
||||||
subscribeDeviceRegistry,
|
|
||||||
} from "../../../data/device_registry";
|
} from "../../../data/device_registry";
|
||||||
import {
|
import {
|
||||||
computeEntityRegistryName,
|
|
||||||
EntityRegistryEntry,
|
EntityRegistryEntry,
|
||||||
|
computeEntityRegistryName,
|
||||||
sortEntityRegistryByName,
|
sortEntityRegistryByName,
|
||||||
subscribeEntityRegistry,
|
|
||||||
} from "../../../data/entity_registry";
|
} from "../../../data/entity_registry";
|
||||||
import { SceneEntity } from "../../../data/scene";
|
import { SceneEntity } from "../../../data/scene";
|
||||||
import { ScriptEntity } from "../../../data/script";
|
import { ScriptEntity } from "../../../data/script";
|
||||||
import { findRelated, RelatedResult } from "../../../data/search";
|
import { RelatedResult, findRelated } from "../../../data/search";
|
||||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||||
import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog";
|
import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog";
|
||||||
import "../../../layouts/hass-error-screen";
|
import "../../../layouts/hass-error-screen";
|
||||||
import "../../../layouts/hass-subpage";
|
import "../../../layouts/hass-subpage";
|
||||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
|
||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import "../../logbook/ha-logbook";
|
import "../../logbook/ha-logbook";
|
||||||
@ -52,7 +48,6 @@ import {
|
|||||||
loadAreaRegistryDetailDialog,
|
loadAreaRegistryDetailDialog,
|
||||||
showAreaRegistryDetailDialog,
|
showAreaRegistryDetailDialog,
|
||||||
} from "./show-dialog-area-registry-detail";
|
} from "./show-dialog-area-registry-detail";
|
||||||
import "../../../components/ha-list-item";
|
|
||||||
|
|
||||||
declare type NameAndEntity<EntityType extends HassEntity> = {
|
declare type NameAndEntity<EntityType extends HassEntity> = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -60,7 +55,7 @@ declare type NameAndEntity<EntityType extends HassEntity> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
@customElement("ha-config-area-page")
|
@customElement("ha-config-area-page")
|
||||||
class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
class HaConfigAreaPage extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public areaId!: string;
|
@property() public areaId!: string;
|
||||||
@ -71,24 +66,14 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public showAdvanced = false;
|
@property({ type: Boolean }) public showAdvanced = false;
|
||||||
|
|
||||||
@state() public _areas!: AreaRegistryEntry[];
|
@state()
|
||||||
|
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||||
@state() public _devices!: DeviceRegistryEntry[];
|
_entityReg!: EntityRegistryEntry[];
|
||||||
|
|
||||||
@state() public _entities!: EntityRegistryEntry[];
|
|
||||||
|
|
||||||
@state() private _related?: RelatedResult;
|
@state() private _related?: RelatedResult;
|
||||||
|
|
||||||
private _logbookTime = { recent: 86400 };
|
private _logbookTime = { recent: 86400 };
|
||||||
|
|
||||||
private _area = memoizeOne(
|
|
||||||
(
|
|
||||||
areaId: string,
|
|
||||||
areas: AreaRegistryEntry[]
|
|
||||||
): AreaRegistryEntry | undefined =>
|
|
||||||
areas.find((area) => area.area_id === areaId)
|
|
||||||
);
|
|
||||||
|
|
||||||
private _memberships = memoizeOne(
|
private _memberships = memoizeOne(
|
||||||
(
|
(
|
||||||
areaId: string,
|
areaId: string,
|
||||||
@ -150,26 +135,12 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
|
|
||||||
return [
|
|
||||||
subscribeAreaRegistry(this.hass.connection, (areas) => {
|
|
||||||
this._areas = areas;
|
|
||||||
}),
|
|
||||||
subscribeDeviceRegistry(this.hass.connection, (entries) => {
|
|
||||||
this._devices = entries;
|
|
||||||
}),
|
|
||||||
subscribeEntityRegistry(this.hass.connection, (entries) => {
|
|
||||||
this._entities = entries;
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!this._areas || !this._devices || !this._entities) {
|
if (!this.hass.areas || !this.hass.devices || !this.hass.entities) {
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
const area = this._area(this.areaId, this._areas);
|
const area = this.hass.areas[this.areaId];
|
||||||
|
|
||||||
if (!area) {
|
if (!area) {
|
||||||
return html`
|
return html`
|
||||||
@ -182,8 +153,8 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
const memberships = this._memberships(
|
const memberships = this._memberships(
|
||||||
this.areaId,
|
this.areaId,
|
||||||
this._devices,
|
Object.values(this.hass.devices),
|
||||||
this._entities
|
this._entityReg
|
||||||
);
|
);
|
||||||
const { devices, entities } = memberships;
|
const { devices, entities } = memberships;
|
||||||
|
|
||||||
@ -617,7 +588,7 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _renderScript(name: string, entityState: ScriptEntity) {
|
private _renderScript(name: string, entityState: ScriptEntity) {
|
||||||
const entry = this._entities.find(
|
const entry = this._entityReg.find(
|
||||||
(e) => e.entity_id === entityState.entity_id
|
(e) => e.entity_id === entityState.entity_id
|
||||||
);
|
);
|
||||||
let url = `/config/script/show/${entityState.entity_id}`;
|
let url = `/config/script/show/${entityState.entity_id}`;
|
||||||
@ -657,7 +628,7 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _deleteConfirm() {
|
private async _deleteConfirm() {
|
||||||
const area = this._area(this.areaId, this._areas);
|
const area = this.hass.areas[this.areaId];
|
||||||
showConfirmationDialog(this, {
|
showConfirmationDialog(this, {
|
||||||
title: this.hass.localize(
|
title: this.hass.localize(
|
||||||
"ui.panel.config.areas.delete.confirmation_title",
|
"ui.panel.config.areas.delete.confirmation_title",
|
||||||
@ -686,7 +657,6 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
border-radius: var(--ha-card-border-radius, 12px);
|
border-radius: var(--ha-card-border-radius, 12px);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
import { mdiHelpCircle, mdiPlus } from "@mdi/js";
|
import { mdiHelpCircle, mdiPlus } from "@mdi/js";
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import {
|
||||||
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
|
CSSResultGroup,
|
||||||
import { customElement, property, state } from "lit/decorators";
|
LitElement,
|
||||||
|
TemplateResult,
|
||||||
|
css,
|
||||||
|
html,
|
||||||
|
nothing,
|
||||||
|
} from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { formatListWithAnds } from "../../../common/string/format-list";
|
import { formatListWithAnds } from "../../../common/string/format-list";
|
||||||
@ -11,19 +17,9 @@ import "../../../components/ha-svg-icon";
|
|||||||
import {
|
import {
|
||||||
AreaRegistryEntry,
|
AreaRegistryEntry,
|
||||||
createAreaRegistryEntry,
|
createAreaRegistryEntry,
|
||||||
subscribeAreaRegistry,
|
|
||||||
} from "../../../data/area_registry";
|
} from "../../../data/area_registry";
|
||||||
import {
|
|
||||||
DeviceRegistryEntry,
|
|
||||||
subscribeDeviceRegistry,
|
|
||||||
} from "../../../data/device_registry";
|
|
||||||
import {
|
|
||||||
EntityRegistryEntry,
|
|
||||||
subscribeEntityRegistry,
|
|
||||||
} from "../../../data/entity_registry";
|
|
||||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||||
import "../../../layouts/hass-tabs-subpage";
|
import "../../../layouts/hass-tabs-subpage";
|
||||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
|
||||||
import { HomeAssistant, Route } from "../../../types";
|
import { HomeAssistant, Route } from "../../../types";
|
||||||
import "../ha-config-section";
|
import "../ha-config-section";
|
||||||
import { configSections } from "../ha-panel-config";
|
import { configSections } from "../ha-panel-config";
|
||||||
@ -33,7 +29,7 @@ import {
|
|||||||
} from "./show-dialog-area-registry-detail";
|
} from "./show-dialog-area-registry-detail";
|
||||||
|
|
||||||
@customElement("ha-config-areas-dashboard")
|
@customElement("ha-config-areas-dashboard")
|
||||||
export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
export class HaConfigAreasDashboard extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ type: Boolean }) public isWide = false;
|
@property({ type: Boolean }) public isWide = false;
|
||||||
@ -42,24 +38,18 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@property({ attribute: false }) public route!: Route;
|
@property({ attribute: false }) public route!: Route;
|
||||||
|
|
||||||
@state() private _areas!: AreaRegistryEntry[];
|
|
||||||
|
|
||||||
@state() private _devices!: DeviceRegistryEntry[];
|
|
||||||
|
|
||||||
@state() private _entities!: EntityRegistryEntry[];
|
|
||||||
|
|
||||||
private _processAreas = memoizeOne(
|
private _processAreas = memoizeOne(
|
||||||
(
|
(
|
||||||
areas: AreaRegistryEntry[],
|
areas: HomeAssistant["areas"],
|
||||||
devices: DeviceRegistryEntry[],
|
devices: HomeAssistant["devices"],
|
||||||
entities: EntityRegistryEntry[]
|
entities: HomeAssistant["entities"]
|
||||||
) =>
|
) => {
|
||||||
areas.map((area) => {
|
const processArea = (area: AreaRegistryEntry) => {
|
||||||
let noDevicesInArea = 0;
|
let noDevicesInArea = 0;
|
||||||
let noServicesInArea = 0;
|
let noServicesInArea = 0;
|
||||||
let noEntitiesInArea = 0;
|
let noEntitiesInArea = 0;
|
||||||
|
|
||||||
for (const device of devices) {
|
for (const device of Object.values(devices)) {
|
||||||
if (device.area_id === area.area_id) {
|
if (device.area_id === area.area_id) {
|
||||||
if (device.entry_type === "service") {
|
if (device.entry_type === "service") {
|
||||||
noServicesInArea++;
|
noServicesInArea++;
|
||||||
@ -69,7 +59,7 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const entity of entities) {
|
for (const entity of Object.values(entities)) {
|
||||||
if (entity.area_id === area.area_id) {
|
if (entity.area_id === area.area_id) {
|
||||||
noEntitiesInArea++;
|
noEntitiesInArea++;
|
||||||
}
|
}
|
||||||
@ -81,24 +71,22 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
|||||||
services: noServicesInArea,
|
services: noServicesInArea,
|
||||||
entities: noEntitiesInArea,
|
entities: noEntitiesInArea,
|
||||||
};
|
};
|
||||||
})
|
};
|
||||||
|
|
||||||
|
return Object.values(areas).map(processArea);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
|
|
||||||
return [
|
|
||||||
subscribeAreaRegistry(this.hass.connection, (areas) => {
|
|
||||||
this._areas = areas;
|
|
||||||
}),
|
|
||||||
subscribeDeviceRegistry(this.hass.connection, (entries) => {
|
|
||||||
this._devices = entries;
|
|
||||||
}),
|
|
||||||
subscribeEntityRegistry(this.hass.connection, (entries) => {
|
|
||||||
this._entities = entries;
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
|
const areas =
|
||||||
|
!this.hass.areas || !this.hass.devices || !this.hass.entities
|
||||||
|
? undefined
|
||||||
|
: this._processAreas(
|
||||||
|
this.hass.areas,
|
||||||
|
this.hass.devices,
|
||||||
|
this.hass.entities
|
||||||
|
);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<hass-tabs-subpage
|
<hass-tabs-subpage
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@ -115,52 +103,11 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
|||||||
@click=${this._showHelp}
|
@click=${this._showHelp}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
${!this._areas || !this._devices || !this._entities
|
${areas?.length
|
||||||
? ""
|
? html`<div class="areas">
|
||||||
: this._processAreas(
|
${areas.map((area) => this._renderArea(area))}
|
||||||
this._areas,
|
</div>`
|
||||||
this._devices,
|
: nothing}
|
||||||
this._entities
|
|
||||||
).map(
|
|
||||||
(area) =>
|
|
||||||
html`<a href=${`/config/areas/area/${area.area_id}`}
|
|
||||||
><ha-card outlined>
|
|
||||||
<div
|
|
||||||
style=${styleMap({
|
|
||||||
backgroundImage: area.picture
|
|
||||||
? `url(${area.picture})`
|
|
||||||
: undefined,
|
|
||||||
})}
|
|
||||||
class="picture ${!area.picture ? "placeholder" : ""}"
|
|
||||||
></div>
|
|
||||||
<h1 class="card-header">${area.name}</h1>
|
|
||||||
<div class="card-content">
|
|
||||||
<div>
|
|
||||||
${formatListWithAnds(
|
|
||||||
this.hass.locale,
|
|
||||||
[
|
|
||||||
area.devices &&
|
|
||||||
this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.config_entry.devices",
|
|
||||||
{ count: area.devices }
|
|
||||||
),
|
|
||||||
area.services &&
|
|
||||||
this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.config_entry.services",
|
|
||||||
{ count: area.services }
|
|
||||||
),
|
|
||||||
area.entities &&
|
|
||||||
this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.config_entry.entities",
|
|
||||||
{ count: area.entities }
|
|
||||||
),
|
|
||||||
].filter((v): v is string => Boolean(v))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ha-card></a
|
|
||||||
>`
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<ha-fab
|
<ha-fab
|
||||||
slot="fab"
|
slot="fab"
|
||||||
@ -176,13 +123,55 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _renderArea(area) {
|
||||||
|
return html`<a href=${`/config/areas/area/${area.area_id}`}>
|
||||||
|
<ha-card outlined>
|
||||||
|
<div
|
||||||
|
style=${styleMap({
|
||||||
|
backgroundImage: area.picture ? `url(${area.picture})` : undefined,
|
||||||
|
})}
|
||||||
|
class="picture ${!area.picture ? "placeholder" : ""}"
|
||||||
|
>
|
||||||
|
${!area.picture && area.icon
|
||||||
|
? html`<ha-icon .icon=${area.icon}></ha-icon>`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
<h1 class="card-header">${area.name}</h1>
|
||||||
|
<div class="card-content">
|
||||||
|
<div>
|
||||||
|
${formatListWithAnds(
|
||||||
|
this.hass.locale,
|
||||||
|
[
|
||||||
|
area.devices &&
|
||||||
|
this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.devices",
|
||||||
|
{ count: area.devices }
|
||||||
|
),
|
||||||
|
area.services &&
|
||||||
|
this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.services",
|
||||||
|
{ count: area.services }
|
||||||
|
),
|
||||||
|
area.entities &&
|
||||||
|
this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.entities",
|
||||||
|
{ count: area.entities }
|
||||||
|
),
|
||||||
|
].filter((v): v is string => Boolean(v))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
</a>`;
|
||||||
|
}
|
||||||
|
|
||||||
protected firstUpdated(changedProps) {
|
protected firstUpdated(changedProps) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
loadAreaRegistryDetailDialog();
|
loadAreaRegistryDetailDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createArea() {
|
private _createArea() {
|
||||||
this._openDialog();
|
this._openAreaDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _showHelp() {
|
private _showHelp() {
|
||||||
@ -202,7 +191,7 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _openDialog(entry?: AreaRegistryEntry) {
|
private _openAreaDialog(entry?: AreaRegistryEntry) {
|
||||||
showAreaRegistryDetailDialog(this, {
|
showAreaRegistryDetailDialog(this, {
|
||||||
entry,
|
entry,
|
||||||
createEntry: async (values) =>
|
createEntry: async (values) =>
|
||||||
@ -213,14 +202,17 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
|||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
.container {
|
.container {
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
||||||
grid-gap: 16px 16px;
|
|
||||||
padding: 8px 16px 16px;
|
padding: 8px 16px 16px;
|
||||||
margin: 0 auto 64px auto;
|
margin: 0 auto 64px auto;
|
||||||
max-width: 2000px;
|
|
||||||
}
|
}
|
||||||
.container > * {
|
.areas {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||||
|
grid-gap: 16px 16px;
|
||||||
|
max-width: 2000px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.areas > * {
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
}
|
}
|
||||||
ha-card {
|
ha-card {
|
||||||
@ -239,6 +231,12 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
|||||||
background-position: center;
|
background-position: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
.placeholder {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
--mdc-icon-size: 48px;
|
||||||
|
}
|
||||||
.picture.placeholder::before {
|
.picture.placeholder::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
content: "";
|
content: "";
|
||||||
|
@ -1771,6 +1771,7 @@
|
|||||||
"update_area": "Update area",
|
"update_area": "Update area",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
|
"icon": "Icon",
|
||||||
"name_required": "Name is required",
|
"name_required": "Name is required",
|
||||||
"area_id": "Area ID",
|
"area_id": "Area ID",
|
||||||
"unknown_error": "Unknown error",
|
"unknown_error": "Unknown error",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user