mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
Add area filter selector for default dashboard (#18779)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
9b20e1cf56
commit
7727f34e8f
96
src/components/ha-area-filter.ts
Normal file
96
src/components/ha-area-filter.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { mdiChevronRight, mdiSofa } from "@mdi/js";
|
||||||
|
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { showAreaFilterDialog } from "../dialogs/area-filter/show-area-filter-dialog";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
import "./ha-svg-icon";
|
||||||
|
import "./ha-textfield";
|
||||||
|
|
||||||
|
export type AreaFilterValue = {
|
||||||
|
hidden?: string[];
|
||||||
|
order?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
@customElement("ha-area-filter")
|
||||||
|
export class HaAreaPicker extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public value?: AreaFilterValue;
|
||||||
|
|
||||||
|
@property() public helper?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public required = false;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
const allAreasCount = Object.keys(this.hass.areas).length;
|
||||||
|
const hiddenAreasCount = this.value?.hidden?.length ?? 0;
|
||||||
|
|
||||||
|
const description =
|
||||||
|
hiddenAreasCount === 0
|
||||||
|
? this.hass.localize("ui.components.area-filter.all_areas")
|
||||||
|
: allAreasCount === hiddenAreasCount
|
||||||
|
? this.hass.localize("ui.components.area-filter.no_areas")
|
||||||
|
: this.hass.localize("ui.components.area-filter.area_count", {
|
||||||
|
count: allAreasCount - hiddenAreasCount,
|
||||||
|
});
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-list-item
|
||||||
|
tabindex="0"
|
||||||
|
role="button"
|
||||||
|
hasMeta
|
||||||
|
twoline
|
||||||
|
graphic="icon"
|
||||||
|
@click=${this._edit}
|
||||||
|
@keydown=${this._edit}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
>
|
||||||
|
<ha-svg-icon slot="graphic" .path=${mdiSofa}></ha-svg-icon>
|
||||||
|
<span>${this.label}</span>
|
||||||
|
<span slot="secondary">${description}</span>
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="meta"
|
||||||
|
.label=${this.hass.localize("ui.common.edit")}
|
||||||
|
.path=${mdiChevronRight}
|
||||||
|
></ha-svg-icon>
|
||||||
|
</ha-list-item>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _edit(ev) {
|
||||||
|
if (ev.defaultPrevented) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ev.type === "keydown" && ev.key !== "Enter" && ev.key !== " ") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
const value = await showAreaFilterDialog(this, {
|
||||||
|
title: this.label,
|
||||||
|
initialValue: this.value,
|
||||||
|
});
|
||||||
|
if (!value) return;
|
||||||
|
fireEvent(this, "value-changed", { value });
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
ha-list-item {
|
||||||
|
--mdc-list-side-padding-left: 8px;
|
||||||
|
--mdc-list-side-padding-right: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-area-filter": HaAreaPicker;
|
||||||
|
}
|
||||||
|
}
|
41
src/components/ha-selector/ha-selector-area-filter.ts
Normal file
41
src/components/ha-selector/ha-selector-area-filter.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { LitElement, html } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import type { AreaFilterSelector } from "../../data/selector";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
import "../ha-area-filter";
|
||||||
|
|
||||||
|
@customElement("ha-selector-area_filter")
|
||||||
|
export class HaAreaFilterSelector extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public selector!: AreaFilterSelector;
|
||||||
|
|
||||||
|
@property() public value?: any;
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property() public helper?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public required = true;
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`
|
||||||
|
<ha-area-filter
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this.value}
|
||||||
|
.label=${this.label}
|
||||||
|
.helper=${this.helper}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.required=${this.required}
|
||||||
|
></ha-area-filter>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-selector-area_filter": HaAreaFilterSelector;
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,7 @@ const LOAD_ELEMENTS = {
|
|||||||
action: () => import("./ha-selector-action"),
|
action: () => import("./ha-selector-action"),
|
||||||
addon: () => import("./ha-selector-addon"),
|
addon: () => import("./ha-selector-addon"),
|
||||||
area: () => import("./ha-selector-area"),
|
area: () => import("./ha-selector-area"),
|
||||||
|
area_filter: () => import("./ha-selector-area-filter"),
|
||||||
attribute: () => import("./ha-selector-attribute"),
|
attribute: () => import("./ha-selector-attribute"),
|
||||||
assist_pipeline: () => import("./ha-selector-assist-pipeline"),
|
assist_pipeline: () => import("./ha-selector-assist-pipeline"),
|
||||||
boolean: () => import("./ha-selector-boolean"),
|
boolean: () => import("./ha-selector-boolean"),
|
||||||
|
@ -123,3 +123,22 @@ export const getAreaDeviceLookup = (
|
|||||||
}
|
}
|
||||||
return areaDeviceLookup;
|
return areaDeviceLookup;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const areaCompare =
|
||||||
|
(entries?: HomeAssistant["areas"], order?: string[]) =>
|
||||||
|
(a: string, b: string) => {
|
||||||
|
const indexA = order ? order.indexOf(a) : -1;
|
||||||
|
const indexB = order ? order.indexOf(b) : 1;
|
||||||
|
if (indexA === -1 && indexB === -1) {
|
||||||
|
const nameA = entries?.[a].name ?? a;
|
||||||
|
const nameB = entries?.[b].name ?? b;
|
||||||
|
return stringCompare(nameA, nameB);
|
||||||
|
}
|
||||||
|
if (indexA === -1) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (indexB === -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return indexA - indexB;
|
||||||
|
};
|
||||||
|
@ -15,6 +15,7 @@ export type Selector =
|
|||||||
| ActionSelector
|
| ActionSelector
|
||||||
| AddonSelector
|
| AddonSelector
|
||||||
| AreaSelector
|
| AreaSelector
|
||||||
|
| AreaFilterSelector
|
||||||
| AttributeSelector
|
| AttributeSelector
|
||||||
| BooleanSelector
|
| BooleanSelector
|
||||||
| ColorRGBSelector
|
| ColorRGBSelector
|
||||||
@ -77,6 +78,11 @@ export interface AreaSelector {
|
|||||||
} | null;
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AreaFilterSelector {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
area_filter: {} | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AttributeSelector {
|
export interface AttributeSelector {
|
||||||
attribute: {
|
attribute: {
|
||||||
entity_id?: string;
|
entity_id?: string;
|
||||||
|
218
src/dialogs/area-filter/area-filter-dialog.ts
Normal file
218
src/dialogs/area-filter/area-filter-dialog.ts
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
import "@material/mwc-list/mwc-list";
|
||||||
|
import { mdiDrag, mdiEye, mdiEyeOff } from "@mdi/js";
|
||||||
|
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { classMap } from "lit/directives/class-map";
|
||||||
|
import { repeat } from "lit/directives/repeat";
|
||||||
|
import type { SortableEvent } from "sortablejs";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import type { AreaFilterValue } from "../../components/ha-area-filter";
|
||||||
|
import "../../components/ha-button";
|
||||||
|
import "../../components/ha-icon-button";
|
||||||
|
import "../../components/ha-list-item";
|
||||||
|
import { areaCompare } from "../../data/area_registry";
|
||||||
|
import { sortableStyles } from "../../resources/ha-sortable-style";
|
||||||
|
import type { SortableInstance } from "../../resources/sortable";
|
||||||
|
import { haStyleDialog } from "../../resources/styles";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
import { HassDialog } from "../make-dialog-manager";
|
||||||
|
import { AreaFilterDialogParams } from "./show-area-filter-dialog";
|
||||||
|
|
||||||
|
@customElement("dialog-area-filter")
|
||||||
|
export class DialogAreaFilter
|
||||||
|
extends LitElement
|
||||||
|
implements HassDialog<AreaFilterDialogParams>
|
||||||
|
{
|
||||||
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||||
|
|
||||||
|
@state() private _dialogParams?: AreaFilterDialogParams;
|
||||||
|
|
||||||
|
@state() private _hidden: string[] = [];
|
||||||
|
|
||||||
|
@state() private _areas: string[] = [];
|
||||||
|
|
||||||
|
private _sortable?: SortableInstance;
|
||||||
|
|
||||||
|
public async showDialog(dialogParams: AreaFilterDialogParams): Promise<void> {
|
||||||
|
this._dialogParams = dialogParams;
|
||||||
|
this._hidden = dialogParams.initialValue?.hidden ?? [];
|
||||||
|
const order = dialogParams.initialValue?.order ?? [];
|
||||||
|
const allAreas = Object.keys(this.hass!.areas);
|
||||||
|
this._areas = allAreas.concat().sort(areaCompare(this.hass!.areas, order));
|
||||||
|
await this.updateComplete;
|
||||||
|
this._createSortable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeDialog(): void {
|
||||||
|
this._dialogParams = undefined;
|
||||||
|
this._hidden = [];
|
||||||
|
this._areas = [];
|
||||||
|
this._destroySortable();
|
||||||
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _submit(): void {
|
||||||
|
const order = this._areas.filter((area) => !this._hidden.includes(area));
|
||||||
|
const value: AreaFilterValue = {
|
||||||
|
hidden: this._hidden,
|
||||||
|
order,
|
||||||
|
};
|
||||||
|
this._dialogParams?.submit?.(value);
|
||||||
|
this.closeDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _cancel(): void {
|
||||||
|
this._dialogParams?.cancel?.();
|
||||||
|
this.closeDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _createSortable() {
|
||||||
|
const Sortable = (await import("../../resources/sortable")).default;
|
||||||
|
if (this._sortable) return;
|
||||||
|
this._sortable = new Sortable(this.shadowRoot!.querySelector(".areas")!, {
|
||||||
|
animation: 150,
|
||||||
|
fallbackClass: "sortable-fallback",
|
||||||
|
handle: ".handle",
|
||||||
|
onChoose: (evt: SortableEvent) => {
|
||||||
|
(evt.item as any).placeholder =
|
||||||
|
document.createComment("sort-placeholder");
|
||||||
|
evt.item.after((evt.item as any).placeholder);
|
||||||
|
},
|
||||||
|
onEnd: (evt: SortableEvent) => {
|
||||||
|
// put back in original location
|
||||||
|
if ((evt.item as any).placeholder) {
|
||||||
|
(evt.item as any).placeholder.replaceWith(evt.item);
|
||||||
|
delete (evt.item as any).placeholder;
|
||||||
|
}
|
||||||
|
this._dragged(evt);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _destroySortable() {
|
||||||
|
this._sortable?.destroy();
|
||||||
|
this._sortable = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _dragged(ev: SortableEvent): void {
|
||||||
|
if (ev.oldIndex === ev.newIndex) return;
|
||||||
|
|
||||||
|
const areas = this._areas.concat();
|
||||||
|
|
||||||
|
const option = areas.splice(ev.oldIndex!, 1)[0];
|
||||||
|
areas.splice(ev.newIndex!, 0, option);
|
||||||
|
|
||||||
|
this._areas = areas;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this._dialogParams || !this.hass) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allAreas = this._areas;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-dialog
|
||||||
|
open
|
||||||
|
@closed=${this._cancel}
|
||||||
|
.heading=${this._dialogParams.title ??
|
||||||
|
this.hass.localize("ui.components.area-filter.title")}
|
||||||
|
>
|
||||||
|
<mwc-list class="areas">
|
||||||
|
${repeat(
|
||||||
|
allAreas,
|
||||||
|
(area) => area,
|
||||||
|
(area, _idx) => {
|
||||||
|
const isVisible = !this._hidden.includes(area);
|
||||||
|
const name = this.hass!.areas[area]?.name || area;
|
||||||
|
return html`
|
||||||
|
<ha-list-item
|
||||||
|
class=${classMap({ hidden: !isVisible })}
|
||||||
|
hasMeta
|
||||||
|
graphic="icon"
|
||||||
|
noninteractive
|
||||||
|
>
|
||||||
|
<ha-svg-icon
|
||||||
|
class="handle"
|
||||||
|
.path=${mdiDrag}
|
||||||
|
slot="graphic"
|
||||||
|
></ha-svg-icon>
|
||||||
|
${name}
|
||||||
|
<ha-icon-button
|
||||||
|
tabindex="0"
|
||||||
|
class="action"
|
||||||
|
.path=${isVisible ? mdiEye : mdiEyeOff}
|
||||||
|
slot="meta"
|
||||||
|
.label=${this.hass!.localize(
|
||||||
|
`ui.components.area-filter.${
|
||||||
|
isVisible ? "hide" : "show"
|
||||||
|
}`,
|
||||||
|
{ area: name }
|
||||||
|
)}
|
||||||
|
.area=${area}
|
||||||
|
@click=${this._toggle}
|
||||||
|
></ha-icon-button>
|
||||||
|
</ha-list-item>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</mwc-list>
|
||||||
|
<ha-button slot="secondaryAction" dialogAction="cancel">
|
||||||
|
${this.hass.localize("ui.common.cancel")}
|
||||||
|
</ha-button>
|
||||||
|
<ha-button @click=${this._submit} slot="primaryAction">
|
||||||
|
${this.hass.localize("ui.common.submit")}
|
||||||
|
</ha-button>
|
||||||
|
</ha-dialog>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
_toggle(ev) {
|
||||||
|
const area = ev.target.area;
|
||||||
|
const hidden = [...(this._hidden ?? [])];
|
||||||
|
if (hidden.includes(area)) {
|
||||||
|
hidden.splice(hidden.indexOf(area), 1);
|
||||||
|
} else {
|
||||||
|
hidden.push(area);
|
||||||
|
}
|
||||||
|
this._hidden = hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
sortableStyles,
|
||||||
|
haStyleDialog,
|
||||||
|
css`
|
||||||
|
ha-dialog {
|
||||||
|
/* Place above other dialogs */
|
||||||
|
--dialog-z-index: 104;
|
||||||
|
--dialog-content-padding: 0;
|
||||||
|
}
|
||||||
|
ha-list-item {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
.hidden {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
.handle {
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
ha-icon-button {
|
||||||
|
display: block;
|
||||||
|
margin: -12px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"dialog-area-filter": DialogAreaFilter;
|
||||||
|
}
|
||||||
|
}
|
38
src/dialogs/area-filter/show-area-filter-dialog.ts
Normal file
38
src/dialogs/area-filter/show-area-filter-dialog.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import type { AreaFilterValue } from "../../components/ha-area-filter";
|
||||||
|
|
||||||
|
export interface AreaFilterDialogParams {
|
||||||
|
title?: string;
|
||||||
|
initialValue?: AreaFilterValue;
|
||||||
|
submit?: (value?: AreaFilterValue) => void;
|
||||||
|
cancel?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const showAreaFilterDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
dialogParams: AreaFilterDialogParams
|
||||||
|
) =>
|
||||||
|
new Promise<AreaFilterValue | null>((resolve) => {
|
||||||
|
const origCancel = dialogParams.cancel;
|
||||||
|
const origSubmit = dialogParams.submit;
|
||||||
|
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "dialog-area-filter",
|
||||||
|
dialogImport: () => import("./area-filter-dialog"),
|
||||||
|
dialogParams: {
|
||||||
|
...dialogParams,
|
||||||
|
cancel: () => {
|
||||||
|
resolve(null);
|
||||||
|
if (origCancel) {
|
||||||
|
origCancel();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submit: (code: AreaFilterValue) => {
|
||||||
|
resolve(code);
|
||||||
|
if (origSubmit) {
|
||||||
|
origSubmit(code);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
@ -7,6 +7,7 @@ import { splitByGroups } from "../../../common/entity/split_by_groups";
|
|||||||
import { stripPrefixFromEntityName } from "../../../common/entity/strip_prefix_from_entity_name";
|
import { stripPrefixFromEntityName } from "../../../common/entity/strip_prefix_from_entity_name";
|
||||||
import { stringCompare } from "../../../common/string/compare";
|
import { stringCompare } from "../../../common/string/compare";
|
||||||
import { LocalizeFunc } from "../../../common/translations/localize";
|
import { LocalizeFunc } from "../../../common/translations/localize";
|
||||||
|
import type { AreaFilterValue } from "../../../components/ha-area-filter";
|
||||||
import {
|
import {
|
||||||
EnergyPreferences,
|
EnergyPreferences,
|
||||||
GridSourceTypeEnergyPreference,
|
GridSourceTypeEnergyPreference,
|
||||||
@ -27,6 +28,7 @@ import {
|
|||||||
} from "../cards/types";
|
} from "../cards/types";
|
||||||
import { EntityConfig } from "../entity-rows/types";
|
import { EntityConfig } from "../entity-rows/types";
|
||||||
import { ButtonsHeaderFooterConfig } from "../header-footer/types";
|
import { ButtonsHeaderFooterConfig } from "../header-footer/types";
|
||||||
|
import { areaCompare } from "../../../data/area_registry";
|
||||||
|
|
||||||
const HIDE_DOMAIN = new Set([
|
const HIDE_DOMAIN = new Set([
|
||||||
"automation",
|
"automation",
|
||||||
@ -447,9 +449,7 @@ export const generateDefaultViewConfig = (
|
|||||||
entities: HassEntities,
|
entities: HassEntities,
|
||||||
localize: LocalizeFunc,
|
localize: LocalizeFunc,
|
||||||
energyPrefs?: EnergyPreferences,
|
energyPrefs?: EnergyPreferences,
|
||||||
areasPrefs?: {
|
areasPrefs?: AreaFilterValue,
|
||||||
hidden?: string[];
|
|
||||||
},
|
|
||||||
hideEntitiesWithoutAreas?: boolean,
|
hideEntitiesWithoutAreas?: boolean,
|
||||||
hideEnergy?: boolean
|
hideEnergy?: boolean
|
||||||
): LovelaceViewConfig => {
|
): LovelaceViewConfig => {
|
||||||
@ -511,15 +511,12 @@ export const generateDefaultViewConfig = (
|
|||||||
|
|
||||||
const areaCards: LovelaceCardConfig[] = [];
|
const areaCards: LovelaceCardConfig[] = [];
|
||||||
|
|
||||||
const sortedAreas = Object.entries(
|
const sortedAreas = Object.keys(splittedByAreaDevice.areasWithEntities).sort(
|
||||||
splittedByAreaDevice.areasWithEntities
|
areaCompare(areaEntries, areasPrefs?.order)
|
||||||
).sort((a, b) => {
|
);
|
||||||
const areaA = areaEntries[a[0]];
|
|
||||||
const areaB = areaEntries[b[0]];
|
|
||||||
return stringCompare(areaA.name, areaB.name);
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const [areaId, areaEntities] of sortedAreas) {
|
for (const areaId of sortedAreas) {
|
||||||
|
const areaEntities = splittedByAreaDevice.areasWithEntities[areaId];
|
||||||
const area = areaEntries[areaId];
|
const area = areaEntries[areaId];
|
||||||
areaCards.push(
|
areaCards.push(
|
||||||
...computeCards(
|
...computeCards(
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
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 { 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 type {
|
import type {
|
||||||
@ -13,11 +12,9 @@ import { LovelaceStrategyEditor } from "../../strategies/types";
|
|||||||
|
|
||||||
const SCHEMA = [
|
const SCHEMA = [
|
||||||
{
|
{
|
||||||
name: "hidden_areas",
|
name: "areas",
|
||||||
selector: {
|
selector: {
|
||||||
area: {
|
area_filter: {},
|
||||||
multiple: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -40,12 +37,6 @@ const SCHEMA = [
|
|||||||
},
|
},
|
||||||
] as const satisfies readonly HaFormSchema[];
|
] as const satisfies readonly HaFormSchema[];
|
||||||
|
|
||||||
type FormData = {
|
|
||||||
hidden_areas: string[];
|
|
||||||
hide_energy?: boolean;
|
|
||||||
hide_entities_without_area?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
@customElement("hui-original-states-dashboard-strategy-editor")
|
@customElement("hui-original-states-dashboard-strategy-editor")
|
||||||
export class HuiOriginalStatesDashboarStrategyEditor
|
export class HuiOriginalStatesDashboarStrategyEditor
|
||||||
extends LitElement
|
extends LitElement
|
||||||
@ -60,44 +51,15 @@ export class HuiOriginalStatesDashboarStrategyEditor
|
|||||||
this._config = config;
|
this._config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _configToFormData = memoizeOne(
|
|
||||||
(config: OriginalStatesDashboardStrategyConfig): FormData => {
|
|
||||||
const { areas, ...rest } = config;
|
|
||||||
return {
|
|
||||||
...rest,
|
|
||||||
hidden_areas: areas?.hidden || [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
private _formDataToConfig = memoizeOne(
|
|
||||||
(data: FormData): OriginalStatesDashboardStrategyConfig => {
|
|
||||||
const { hidden_areas, ...rest } = data;
|
|
||||||
const areas =
|
|
||||||
hidden_areas.length > 0
|
|
||||||
? {
|
|
||||||
hidden: hidden_areas,
|
|
||||||
}
|
|
||||||
: undefined;
|
|
||||||
return {
|
|
||||||
type: "original-states",
|
|
||||||
...rest,
|
|
||||||
areas,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!this.hass || !this._config) {
|
if (!this.hass || !this._config) {
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = this._configToFormData(this._config);
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-form
|
<ha-form
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.data=${data}
|
.data=${this._config}
|
||||||
.schema=${SCHEMA}
|
.schema=${SCHEMA}
|
||||||
.computeLabel=${this._computeLabelCallback}
|
.computeLabel=${this._computeLabelCallback}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
@ -106,14 +68,13 @@ export class HuiOriginalStatesDashboarStrategyEditor
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: CustomEvent): void {
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
const data = ev.detail.value as FormData;
|
const data = ev.detail.value;
|
||||||
const config = this._formDataToConfig(data);
|
fireEvent(this, "config-changed", { config: data });
|
||||||
fireEvent(this, "config-changed", { config });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computeLabelCallback = (schema: SchemaUnion<typeof SCHEMA>) => {
|
private _computeLabelCallback = (schema: SchemaUnion<typeof SCHEMA>) => {
|
||||||
switch (schema.name) {
|
switch (schema.name) {
|
||||||
case "hidden_areas":
|
case "areas":
|
||||||
case "hide_energy":
|
case "hide_energy":
|
||||||
case "hide_entities_without_area":
|
case "hide_entities_without_area":
|
||||||
return this.hass?.localize(
|
return this.hass?.localize(
|
||||||
|
@ -2,6 +2,7 @@ import { STATE_NOT_RUNNING } from "home-assistant-js-websocket";
|
|||||||
import { ReactiveElement } from "lit";
|
import { ReactiveElement } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
|
import type { AreaFilterValue } from "../../../components/ha-area-filter";
|
||||||
import { getEnergyPreferences } from "../../../data/energy";
|
import { getEnergyPreferences } from "../../../data/energy";
|
||||||
import { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
import { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
@ -9,9 +10,7 @@ import { generateDefaultViewConfig } from "../common/generate-lovelace-config";
|
|||||||
|
|
||||||
export type OriginalStatesViewStrategyConfig = {
|
export type OriginalStatesViewStrategyConfig = {
|
||||||
type: "original-states";
|
type: "original-states";
|
||||||
areas?: {
|
areas?: AreaFilterValue;
|
||||||
hidden?: string[];
|
|
||||||
};
|
|
||||||
hide_entities_without_area?: boolean;
|
hide_entities_without_area?: boolean;
|
||||||
hide_energy?: boolean;
|
hide_energy?: boolean;
|
||||||
};
|
};
|
||||||
|
@ -512,6 +512,14 @@
|
|||||||
"failed_create_area": "Failed to create area."
|
"failed_create_area": "Failed to create area."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"area-filter": {
|
||||||
|
"title": "Areas",
|
||||||
|
"no_areas": "No areas",
|
||||||
|
"area_count": "{count} {count, plural,\n one {area}\n other {areas}\n}",
|
||||||
|
"all_areas": "All areas",
|
||||||
|
"show": "Show {area}",
|
||||||
|
"hide": "Hide {area}"
|
||||||
|
},
|
||||||
"statistic-picker": {
|
"statistic-picker": {
|
||||||
"statistic": "Statistic",
|
"statistic": "Statistic",
|
||||||
"no_statistics": "You don't have any statistics",
|
"no_statistics": "You don't have any statistics",
|
||||||
@ -5269,7 +5277,7 @@
|
|||||||
},
|
},
|
||||||
"strategy": {
|
"strategy": {
|
||||||
"original-states": {
|
"original-states": {
|
||||||
"hidden_areas": "Hidden Areas",
|
"areas": "Areas",
|
||||||
"hide_entities_without_area": "Hide entities without area",
|
"hide_entities_without_area": "Hide entities without area",
|
||||||
"hide_energy": "Hide energy"
|
"hide_energy": "Hide energy"
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user