mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-23 09:16:38 +00:00
Group area per floor in the editor
This commit is contained in:
parent
976bf7c512
commit
b890ef2b01
213
src/components/ha-areas-floors-display-editor.ts
Normal file
213
src/components/ha-areas-floors-display-editor.ts
Normal file
@ -0,0 +1,213 @@
|
||||
import { mdiTextureBox } from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { computeFloorName } from "../common/entity/compute_floor_name";
|
||||
import { getAreaContext } from "../common/entity/context/get_area_context";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import { areaCompare } from "../data/area_registry";
|
||||
import type { FloorRegistryEntry } from "../data/floor_registry";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-expansion-panel";
|
||||
import "./ha-floor-icon";
|
||||
import "./ha-items-display-editor";
|
||||
import type { DisplayItem, DisplayValue } from "./ha-items-display-editor";
|
||||
import "./ha-svg-icon";
|
||||
import "./ha-textfield";
|
||||
|
||||
export interface AreasDisplayValue {
|
||||
hidden?: string[];
|
||||
order?: string[];
|
||||
}
|
||||
|
||||
const UNASSIGNED_FLOOR = "__unassigned__";
|
||||
|
||||
@customElement("ha-areas-floors-display-editor")
|
||||
export class HaAreasFloorsDisplayEditor extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ attribute: false }) public value?: AreasDisplayValue;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public expanded = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "show-navigation-button" })
|
||||
public showNavigationButton = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const compare = areaCompare(this.hass.areas);
|
||||
|
||||
const areas = Object.values(this.hass.areas).sort((areaA, areaB) =>
|
||||
compare(areaA.area_id, areaB.area_id)
|
||||
);
|
||||
|
||||
const groupedItems: Record<string, DisplayItem[]> = areas.reduce(
|
||||
(acc, area) => {
|
||||
const { floor } = getAreaContext(area, this.hass!);
|
||||
const floorId = floor?.floor_id ?? UNASSIGNED_FLOOR;
|
||||
|
||||
if (!acc[floorId]) {
|
||||
acc[floorId] = [];
|
||||
}
|
||||
acc[floorId].push({
|
||||
value: area.area_id,
|
||||
label: area.name,
|
||||
icon: area.icon ?? undefined,
|
||||
iconPath: mdiTextureBox,
|
||||
description: floor?.name,
|
||||
});
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, DisplayItem[]>
|
||||
);
|
||||
|
||||
const filteredFloors = this._sortedFloors(this.hass.floors).filter(
|
||||
(floor) =>
|
||||
// Only include floors that have areas assigned to them
|
||||
groupedItems[floor.floor_id]?.length > 0
|
||||
);
|
||||
|
||||
const value: DisplayValue = {
|
||||
order: this.value?.order ?? [],
|
||||
hidden: this.value?.hidden ?? [],
|
||||
};
|
||||
|
||||
return html`
|
||||
<ha-expansion-panel
|
||||
outlined
|
||||
.header=${this.label}
|
||||
.expanded=${this.expanded}
|
||||
>
|
||||
<ha-svg-icon slot="leading-icon" .path=${mdiTextureBox}></ha-svg-icon>
|
||||
${filteredFloors.map(
|
||||
(floor) => html`
|
||||
<div class="floor">
|
||||
<div class="header">
|
||||
<ha-floor-icon .floor=${floor}></ha-floor-icon>
|
||||
<p>${computeFloorName(floor)}</p>
|
||||
</div>
|
||||
<div class="areas">
|
||||
<ha-items-display-editor
|
||||
.hass=${this.hass}
|
||||
.items=${groupedItems[floor.floor_id] || []}
|
||||
.value=${value}
|
||||
.floorId=${floor.floor_id}
|
||||
@value-changed=${this._areaDisplayChanged}
|
||||
.showNavigationButton=${this.showNavigationButton}
|
||||
></ha-items-display-editor>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</ha-expansion-panel>
|
||||
`;
|
||||
}
|
||||
|
||||
private _sortedFloors = memoizeOne(
|
||||
(hassFloors: HomeAssistant["floors"]): FloorRegistryEntry[] => {
|
||||
const floors = Object.values(hassFloors).sort((floorA, floorB) => {
|
||||
if (floorA.level !== floorB.level) {
|
||||
return (floorA.level ?? 0) - (floorB.level ?? 0);
|
||||
}
|
||||
return stringCompare(floorA.name, floorB.name);
|
||||
});
|
||||
floors.push({
|
||||
floor_id: UNASSIGNED_FLOOR,
|
||||
name: this.hass.localize(
|
||||
"ui.panel.lovelace.strategy.areas.unassigned_areas"
|
||||
),
|
||||
icon: null,
|
||||
level: 999999,
|
||||
aliases: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
});
|
||||
return floors;
|
||||
}
|
||||
);
|
||||
|
||||
private async _areaDisplayChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
const value = ev.detail.value as DisplayValue;
|
||||
const currentFloorId = ev.currentTarget.floorId;
|
||||
|
||||
const floorIds = this._sortedFloors(this.hass.floors).map(
|
||||
(floor) => floor.floor_id
|
||||
);
|
||||
|
||||
const newHidden: string[] = [];
|
||||
const newOrder: string[] = [];
|
||||
|
||||
for (const floorId of floorIds) {
|
||||
if (currentFloorId === floorId) {
|
||||
newHidden.push(...(value.hidden ?? []));
|
||||
newOrder.push(...(value.order ?? []));
|
||||
continue;
|
||||
}
|
||||
const hidden = this.value?.hidden?.filter(
|
||||
(areaId) => this.hass.areas[areaId]?.floor_id === floorId
|
||||
);
|
||||
if (hidden) {
|
||||
newHidden.push(...hidden);
|
||||
}
|
||||
const order = this.value?.order?.filter(
|
||||
(areaId) => this.hass.areas[areaId]?.floor_id === floorId
|
||||
);
|
||||
if (order) {
|
||||
newOrder.push(...order);
|
||||
}
|
||||
}
|
||||
|
||||
const newValue: AreasDisplayValue = {
|
||||
hidden: newHidden,
|
||||
order: newOrder,
|
||||
};
|
||||
if (newValue.hidden?.length === 0) {
|
||||
delete newValue.hidden;
|
||||
}
|
||||
if (newValue.order?.length === 0) {
|
||||
delete newValue.order;
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", { value: newValue });
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
css`
|
||||
.floor .header p {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
flex: 1:
|
||||
}
|
||||
.floor .header {
|
||||
margin: 16px 0 8px 0;
|
||||
padding: 0 8px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-areas-floors-display-editor": HaAreasFloorsDisplayEditor;
|
||||
}
|
||||
}
|
@ -202,7 +202,7 @@ export class HaNumericStateTrigger extends LitElement {
|
||||
entity: { domain: ["input_number", "number", "sensor"] },
|
||||
},
|
||||
},
|
||||
] as const)
|
||||
] as const satisfies HaFormSchema[])
|
||||
: ([
|
||||
{
|
||||
name: "below",
|
||||
|
@ -11,6 +11,8 @@ import type { AreaCardConfig, HeadingCardConfig } from "../../cards/types";
|
||||
import type { EntitiesDisplay } from "./area-view-strategy";
|
||||
import { computeAreaPath, getAreas } from "./helpers/areas-strategy-helper";
|
||||
|
||||
const UNASSIGNED_FLOOR = "__unassigned__";
|
||||
|
||||
interface AreaOptions {
|
||||
groups_options?: Record<string, EntitiesDisplay>;
|
||||
}
|
||||
@ -46,13 +48,20 @@ export class AreasOverviewViewStrategy extends ReactiveElement {
|
||||
|
||||
const floorSections = [
|
||||
...floors,
|
||||
{ floor_id: "default", name: "Default", level: null, icon: null },
|
||||
{
|
||||
floor_id: UNASSIGNED_FLOOR,
|
||||
name: hass.localize(
|
||||
"ui.panel.lovelace.strategy.areas.unassigned_areas"
|
||||
),
|
||||
level: null,
|
||||
icon: null,
|
||||
},
|
||||
]
|
||||
.map<LovelaceSectionConfig | undefined>((floor) => {
|
||||
const areasInFloors = areas.filter(
|
||||
(area) =>
|
||||
area.floor_id === floor.floor_id ||
|
||||
(!area.floor_id && floor.floor_id === "default")
|
||||
(!area.floor_id && floor.floor_id === UNASSIGNED_FLOOR)
|
||||
);
|
||||
|
||||
if (areasInFloors.length === 0) {
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
type AreaRegistryEntry,
|
||||
} from "../../../../../data/area_registry";
|
||||
import { buttonLinkStyle } from "../../../../../resources/styles";
|
||||
import "../../../../../components/ha-areas-floors-display-editor";
|
||||
|
||||
@customElement("hui-areas-dashboard-strategy-editor")
|
||||
export class HuiAreasDashboardStrategyEditor
|
||||
@ -122,7 +123,7 @@ export class HuiAreasDashboardStrategyEditor
|
||||
const value = this._config.areas_display;
|
||||
|
||||
return html`
|
||||
<ha-areas-display-editor
|
||||
<ha-areas-floors-display-editor
|
||||
.hass=${this.hass}
|
||||
.value=${value}
|
||||
.label=${this.hass.localize(
|
||||
@ -132,7 +133,7 @@ export class HuiAreasDashboardStrategyEditor
|
||||
expanded
|
||||
show-navigation-button
|
||||
@item-display-navigate-clicked=${this._handleAreaNavigate}
|
||||
></ha-areas-display-editor>
|
||||
></ha-areas-floors-display-editor>
|
||||
`;
|
||||
}
|
||||
|
||||
|
@ -6618,7 +6618,8 @@
|
||||
"security": "Security",
|
||||
"actions": "Actions",
|
||||
"others": "Others"
|
||||
}
|
||||
},
|
||||
"unassigned_areas": "[%key:ui::panel::config::areas::picker::unassigned_areas%]"
|
||||
}
|
||||
},
|
||||
"cards": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user