mirror of
https://github.com/home-assistant/frontend.git
synced 2025-10-22 10:09:47 +00:00
288 lines
8.8 KiB
TypeScript
288 lines
8.8 KiB
TypeScript
import { mdiDragHorizontalVariant, mdiTextureBox } from "@mdi/js";
|
|
import type { TemplateResult } from "lit";
|
|
import { LitElement, css, html, nothing } from "lit";
|
|
import { customElement, property } from "lit/decorators";
|
|
import { repeat } from "lit/directives/repeat";
|
|
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 { areaCompare } from "../data/area_registry";
|
|
import type { FloorRegistryEntry } from "../data/floor_registry";
|
|
import { getFloors } from "../panels/lovelace/strategies/areas/helpers/areas-strategy-helper";
|
|
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 AreasFloorsDisplayValue {
|
|
areas_display?: {
|
|
hidden?: string[];
|
|
order?: string[];
|
|
};
|
|
floors_display?: {
|
|
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?: AreasFloorsDisplayValue;
|
|
|
|
@property() public helper?: string;
|
|
|
|
@property({ type: Boolean }) public disabled = false;
|
|
|
|
@property({ type: Boolean }) public required = false;
|
|
|
|
@property({ attribute: false }) public actionsRenderer?: () =>
|
|
| TemplateResult<1>
|
|
| typeof nothing;
|
|
|
|
@property({ type: Boolean, attribute: "show-navigation-button" })
|
|
public showNavigationButton = false;
|
|
|
|
protected render(): TemplateResult {
|
|
const groupedAreasItems = this._groupedAreasItems(
|
|
this.hass.areas,
|
|
this.hass.floors
|
|
);
|
|
|
|
const filteredFloors = this._sortedFloors(
|
|
this.hass.floors,
|
|
this.value?.floors_display?.order
|
|
).filter(
|
|
(floor) =>
|
|
// Only include floors that have areas assigned to them
|
|
groupedAreasItems[floor.floor_id]?.length > 0
|
|
);
|
|
|
|
const value: DisplayValue = {
|
|
order: this.value?.areas_display?.order ?? [],
|
|
hidden: this.value?.areas_display?.hidden ?? [],
|
|
};
|
|
|
|
const canReorderFloors =
|
|
filteredFloors.filter((floor) => floor.floor_id !== UNASSIGNED_FLOOR)
|
|
.length > 1;
|
|
|
|
return html`
|
|
${this.label ? html`<label>${this.label}</label>` : nothing}
|
|
<ha-sortable
|
|
draggable-selector=".draggable"
|
|
handle-selector=".handle"
|
|
@item-moved=${this._floorMoved}
|
|
.disabled=${this.disabled || !canReorderFloors}
|
|
invert-swap
|
|
>
|
|
<div>
|
|
${repeat(
|
|
filteredFloors,
|
|
(floor) => floor.floor_id,
|
|
(floor: FloorRegistryEntry) => html`
|
|
<ha-expansion-panel
|
|
outlined
|
|
.header=${computeFloorName(floor)}
|
|
left-chevron
|
|
class=${floor.floor_id === UNASSIGNED_FLOOR ? "" : "draggable"}
|
|
>
|
|
<ha-floor-icon
|
|
slot="leading-icon"
|
|
.floor=${floor}
|
|
></ha-floor-icon>
|
|
${floor.floor_id === UNASSIGNED_FLOOR || !canReorderFloors
|
|
? nothing
|
|
: html`
|
|
<ha-svg-icon
|
|
class="handle"
|
|
slot="icons"
|
|
.path=${mdiDragHorizontalVariant}
|
|
></ha-svg-icon>
|
|
`}
|
|
<ha-items-display-editor
|
|
.hass=${this.hass}
|
|
.items=${groupedAreasItems[floor.floor_id]}
|
|
.value=${value}
|
|
.floorId=${floor.floor_id}
|
|
.actionsRenderer=${this.actionsRenderer}
|
|
@value-changed=${this._areaDisplayChanged}
|
|
.showNavigationButton=${this.showNavigationButton}
|
|
></ha-items-display-editor>
|
|
</ha-expansion-panel>
|
|
`
|
|
)}
|
|
</div>
|
|
</ha-sortable>
|
|
`;
|
|
}
|
|
|
|
private _groupedAreasItems = memoizeOne(
|
|
(
|
|
hassAreas: HomeAssistant["areas"],
|
|
// update items if floors change
|
|
_hassFloors: HomeAssistant["floors"]
|
|
): Record<string, DisplayItem[]> => {
|
|
const compare = areaCompare(hassAreas);
|
|
|
|
const areas = Object.values(hassAreas).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.floors);
|
|
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,
|
|
});
|
|
|
|
return acc;
|
|
},
|
|
{} as Record<string, DisplayItem[]>
|
|
);
|
|
return groupedItems;
|
|
}
|
|
);
|
|
|
|
private _sortedFloors = memoizeOne(
|
|
(
|
|
hassFloors: HomeAssistant["floors"],
|
|
order: string[] | undefined
|
|
): FloorRegistryEntry[] => {
|
|
const floors = getFloors(hassFloors, order);
|
|
const noFloors = floors.length === 0;
|
|
floors.push({
|
|
floor_id: UNASSIGNED_FLOOR,
|
|
name: noFloors
|
|
? this.hass.localize("ui.panel.lovelace.strategy.areas.areas")
|
|
: this.hass.localize("ui.panel.lovelace.strategy.areas.other_areas"),
|
|
icon: null,
|
|
level: null,
|
|
aliases: [],
|
|
created_at: 0,
|
|
modified_at: 0,
|
|
});
|
|
return floors;
|
|
}
|
|
);
|
|
|
|
private _floorMoved(ev: CustomEvent<HASSDomEvents["item-moved"]>) {
|
|
ev.stopPropagation();
|
|
const newIndex = ev.detail.newIndex;
|
|
const oldIndex = ev.detail.oldIndex;
|
|
const floorIds = this._sortedFloors(
|
|
this.hass.floors,
|
|
this.value?.floors_display?.order
|
|
).map((floor) => floor.floor_id);
|
|
const newOrder = [...floorIds];
|
|
const movedFloorId = newOrder.splice(oldIndex, 1)[0];
|
|
newOrder.splice(newIndex, 0, movedFloorId);
|
|
const newValue: AreasFloorsDisplayValue = {
|
|
areas_display: this.value?.areas_display,
|
|
floors_display: {
|
|
order: newOrder,
|
|
},
|
|
};
|
|
if (newValue.floors_display?.order?.length === 0) {
|
|
delete newValue.floors_display.order;
|
|
}
|
|
fireEvent(this, "value-changed", { value: newValue });
|
|
}
|
|
|
|
private async _areaDisplayChanged(ev: CustomEvent<{ value: DisplayValue }>) {
|
|
ev.stopPropagation();
|
|
const value = ev.detail.value;
|
|
const currentFloorId = (ev.currentTarget as any).floorId;
|
|
|
|
const floorIds = this._sortedFloors(
|
|
this.hass.floors,
|
|
this.value?.floors_display?.order
|
|
).map((floor) => floor.floor_id);
|
|
|
|
const oldAreaDisplay = this.value?.areas_display ?? {};
|
|
|
|
const oldHidden = oldAreaDisplay?.hidden ?? [];
|
|
const oldOrder = oldAreaDisplay?.order ?? [];
|
|
|
|
const newHidden: string[] = [];
|
|
const newOrder: string[] = [];
|
|
|
|
for (const floorId of floorIds) {
|
|
if ((currentFloorId ?? UNASSIGNED_FLOOR) === floorId) {
|
|
newHidden.push(...(value.hidden ?? []));
|
|
newOrder.push(...(value.order ?? []));
|
|
continue;
|
|
}
|
|
const hidden = oldHidden.filter((areaId) => {
|
|
const id = this.hass.areas[areaId]?.floor_id ?? UNASSIGNED_FLOOR;
|
|
return id === floorId;
|
|
});
|
|
if (hidden?.length) {
|
|
newHidden.push(...hidden);
|
|
}
|
|
const order = oldOrder.filter((areaId) => {
|
|
const id = this.hass.areas[areaId]?.floor_id ?? UNASSIGNED_FLOOR;
|
|
return id === floorId;
|
|
});
|
|
if (order?.length) {
|
|
newOrder.push(...order);
|
|
}
|
|
}
|
|
|
|
const newValue: AreasFloorsDisplayValue = {
|
|
areas_display: {
|
|
hidden: newHidden,
|
|
order: newOrder,
|
|
},
|
|
floors_display: this.value?.floors_display,
|
|
};
|
|
if (newValue.areas_display?.hidden?.length === 0) {
|
|
delete newValue.areas_display.hidden;
|
|
}
|
|
if (newValue.areas_display?.order?.length === 0) {
|
|
delete newValue.areas_display.order;
|
|
}
|
|
if (newValue.floors_display?.order?.length === 0) {
|
|
delete newValue.floors_display.order;
|
|
}
|
|
|
|
fireEvent(this, "value-changed", { value: newValue });
|
|
}
|
|
|
|
static styles = css`
|
|
ha-expansion-panel {
|
|
margin-bottom: 8px;
|
|
--expansion-panel-summary-padding: 0 16px;
|
|
}
|
|
ha-expansion-panel [slot="leading-icon"] {
|
|
margin-inline-end: 16px;
|
|
}
|
|
label {
|
|
display: block;
|
|
font-weight: var(--ha-font-weight-bold);
|
|
margin-bottom: 8px;
|
|
}
|
|
`;
|
|
}
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
"ha-areas-floors-display-editor": HaAreasFloorsDisplayEditor;
|
|
}
|
|
}
|