Use tree for nested floor instead of icon (#20363)

This commit is contained in:
Paul Bottein 2024-04-03 11:27:30 +02:00 committed by GitHub
parent 3de985a3b8
commit 82a3b9d80f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 136 additions and 63 deletions

View File

@ -1,8 +1,9 @@
import { mdiSubdirectoryArrowRight, mdiTextureBox } from "@mdi/js"; import { mdiTextureBox } from "@mdi/js";
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit"; import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import { LitElement, PropertyValues, TemplateResult, html, nothing } from "lit"; import { LitElement, PropertyValues, TemplateResult, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { computeDomain } from "../common/entity/compute_domain"; import { computeDomain } from "../common/entity/compute_domain";
@ -11,6 +12,7 @@ import {
ScorableTextItem, ScorableTextItem,
fuzzyFilterSort, fuzzyFilterSort,
} from "../common/string/filter/sequence-matching"; } from "../common/string/filter/sequence-matching";
import { computeRTL } from "../common/util/compute_rtl";
import { AreaRegistryEntry } from "../data/area_registry"; import { AreaRegistryEntry } from "../data/area_registry";
import { import {
DeviceEntityDisplayLookup, DeviceEntityDisplayLookup,
@ -32,6 +34,7 @@ import "./ha-floor-icon";
import "./ha-icon-button"; import "./ha-icon-button";
import "./ha-list-item"; import "./ha-list-item";
import "./ha-svg-icon"; import "./ha-svg-icon";
import "./ha-tree-indicator";
type ScorableAreaFloorEntry = ScorableTextItem & FloorAreaEntry; type ScorableAreaFloorEntry = ScorableTextItem & FloorAreaEntry;
@ -41,35 +44,11 @@ interface FloorAreaEntry {
icon: string | null; icon: string | null;
strings: string[]; strings: string[];
type: "floor" | "area"; type: "floor" | "area";
hasFloor?: boolean;
level: number | null; level: number | null;
hasFloor?: boolean;
lastArea?: boolean;
} }
const rowRenderer: ComboBoxLitRenderer<FloorAreaEntry> = (item) =>
html`<ha-list-item
graphic="icon"
style=${item.type === "area" && item.hasFloor
? "padding-left: 42px; padding-inline-start: 42px; --mdc-list-item-graphic-margin: 32px;"
: ""}
>
${item.type === "area" && item.hasFloor
? html`<ha-svg-icon
style="margin-inline-end: 8px; opacity: .6;"
slot="graphic"
.path=${mdiSubdirectoryArrowRight}
></ha-svg-icon>`
: nothing}
${item.type === "floor"
? html`<ha-floor-icon slot="graphic" .floor=${item}></ha-floor-icon>`
: item.icon
? html`<ha-icon slot="graphic" .icon=${item.icon}></ha-icon>`
: html`<ha-svg-icon
slot="graphic"
.path=${mdiTextureBox}
></ha-svg-icon>`}
${item.name}
</ha-list-item>`;
@customElement("ha-area-floor-picker") @customElement("ha-area-floor-picker")
export class HaAreaFloorPicker extends SubscribeMixin(LitElement) { export class HaAreaFloorPicker extends SubscribeMixin(LitElement) {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@ -158,6 +137,44 @@ export class HaAreaFloorPicker extends SubscribeMixin(LitElement) {
await this.comboBox?.focus(); await this.comboBox?.focus();
} }
private _rowRenderer: ComboBoxLitRenderer<FloorAreaEntry> = (item) => {
const rtl = computeRTL(this.hass);
return html`
<ha-list-item
graphic="icon"
style=${item.type === "area" && item.hasFloor
? rtl
? "--mdc-list-side-padding-right: 48px;"
: "--mdc-list-side-padding-left: 48px;"
: ""}
>
${item.type === "area" && item.hasFloor
? html`<ha-tree-indicator
style=${styleMap({
width: "48px",
position: "absolute",
top: "0px",
left: rtl ? undefined : "8px",
right: rtl ? "8px" : undefined,
transform: rtl ? "scaleX(-1)" : "",
})}
.end=${item.lastArea}
slot="graphic"
></ha-tree-indicator>`
: nothing}
${item.type === "floor"
? html`<ha-floor-icon slot="graphic" .floor=${item}></ha-floor-icon>`
: item.icon
? html`<ha-icon slot="graphic" .icon=${item.icon}></ha-icon>`
: html`<ha-svg-icon
slot="graphic"
.path=${mdiTextureBox}
></ha-svg-icon>`}
${item.name}
</ha-list-item>
`;
};
private _getAreas = memoizeOne( private _getAreas = memoizeOne(
( (
floors: FloorRegistryEntry[], floors: FloorRegistryEntry[],
@ -371,7 +388,7 @@ export class HaAreaFloorPicker extends SubscribeMixin(LitElement) {
}); });
} }
output.push( output.push(
...floorAreas.map((area) => ({ ...floorAreas.map((area, index, array) => ({
id: area.area_id, id: area.area_id,
type: "area" as const, type: "area" as const,
name: area.name, name: area.name,
@ -379,6 +396,7 @@ export class HaAreaFloorPicker extends SubscribeMixin(LitElement) {
strings: [area.area_id, ...area.aliases, area.name], strings: [area.area_id, ...area.aliases, area.name],
hasFloor: true, hasFloor: true,
level: null, level: null,
lastArea: index === array.length - 1,
})) }))
); );
}); });
@ -452,7 +470,7 @@ export class HaAreaFloorPicker extends SubscribeMixin(LitElement) {
.placeholder=${this.placeholder .placeholder=${this.placeholder
? this.hass.areas[this.placeholder]?.name ? this.hass.areas[this.placeholder]?.name
: undefined} : undefined}
.renderer=${rowRenderer} .renderer=${this._rowRenderer}
@filter-changed=${this._filterChanged} @filter-changed=${this._filterChanged}
@opened-changed=${this._openedChanged} @opened-changed=${this._openedChanged}
@value-changed=${this._areaChanged} @value-changed=${this._areaChanged}

View File

@ -1,15 +1,13 @@
import "@material/mwc-menu/mwc-menu-surface"; import "@material/mwc-menu/mwc-menu-surface";
import { import { mdiFilterVariantRemove, mdiTextureBox } from "@mdi/js";
mdiFilterVariantRemove,
mdiSubdirectoryArrowRight,
mdiTextureBox,
} from "@mdi/js";
import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { repeat } from "lit/directives/repeat"; import { repeat } from "lit/directives/repeat";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { computeRTL } from "../common/util/compute_rtl";
import { import {
FloorRegistryEntry, FloorRegistryEntry,
getFloorAreaLookup, getFloorAreaLookup,
@ -23,6 +21,7 @@ import "./ha-check-list-item";
import "./ha-floor-icon"; import "./ha-floor-icon";
import "./ha-icon"; import "./ha-icon";
import "./ha-svg-icon"; import "./ha-svg-icon";
import "./ha-tree-indicator";
@customElement("ha-filter-floor-areas") @customElement("ha-filter-floor-areas")
export class HaFilterFloorAreas extends SubscribeMixin(LitElement) { export class HaFilterFloorAreas extends SubscribeMixin(LitElement) {
@ -90,8 +89,10 @@ export class HaFilterFloorAreas extends SubscribeMixin(LitElement) {
</ha-check-list-item> </ha-check-list-item>
${repeat( ${repeat(
floor.areas, floor.areas,
(area) => area.area_id, (area, index) =>
(area) => this._renderArea(area) `${area.area_id}${index === floor.areas.length - 1 ? "___last" : ""}`,
(area, index) =>
this._renderArea(area, index === floor.areas.length - 1)
)} )}
` `
)} )}
@ -107,30 +108,37 @@ export class HaFilterFloorAreas extends SubscribeMixin(LitElement) {
`; `;
} }
private _renderArea(area) { private _renderArea(area, last: boolean = false) {
return html`<ha-check-list-item const hasFloor = !!area.floor_id;
.value=${area.area_id} return html`
.selected=${this.value?.areas?.includes(area.area_id) || false} <ha-check-list-item
.type=${"areas"} .value=${area.area_id}
graphic="icon" .selected=${this.value?.areas?.includes(area.area_id) || false}
class=${area.floor_id ? "floor" : ""} .type=${"areas"}
@request-selected=${this._handleItemClick} graphic="icon"
> @request-selected=${this._handleItemClick}
${area.floor_id class=${classMap({
? html`<ha-svg-icon rtl: computeRTL(this.hass),
class="subdir" floor: hasFloor,
slot="graphic" })}
.path=${mdiSubdirectoryArrowRight} >
></ha-svg-icon>` ${hasFloor
: nothing} ? html`
${area.icon <ha-tree-indicator
? html`<ha-icon slot="graphic" .icon=${area.icon}></ha-icon>` .end=${last}
: html`<ha-svg-icon slot="graphic"
slot="graphic" ></ha-tree-indicator>
.path=${mdiTextureBox} `
></ha-svg-icon>`} : nothing}
${area.name} ${area.icon
</ha-check-list-item>`; ? html`<ha-icon slot="graphic" .icon=${area.icon}></ha-icon>`
: html`<ha-svg-icon
slot="graphic"
.path=${mdiTextureBox}
></ha-svg-icon>`}
${area.name}
</ha-check-list-item>
`;
} }
private _handleItemClick(ev) { private _handleItemClick(ev) {
@ -305,9 +313,20 @@ export class HaFilterFloorAreas extends SubscribeMixin(LitElement) {
--mdc-list-item-graphic-margin: 16px; --mdc-list-item-graphic-margin: 16px;
} }
.floor { .floor {
padding-left: 38px; padding-left: 48px;
padding-inline-start: 38px; padding-inline-start: 48px;
--mdc-list-item-graphic-margin: 32px; padding-inline-end: 16px;
}
ha-tree-indicator {
width: 56px;
position: absolute;
top: 0px;
left: 0px;
}
.rtl ha-tree-indicator {
right: 0px;
left: initial;
transform: scaleX(-1);
} }
.subdir { .subdir {
margin-inline-end: 8px; margin-inline-end: 8px;

View File

@ -0,0 +1,36 @@
import { LitElement, TemplateResult, css, html } from "lit";
import { customElement, property } from "lit/decorators";
@customElement("ha-tree-indicator")
export class HaTreeIndicator extends LitElement {
@property({ type: Boolean, reflect: true })
public end?: boolean = false;
protected render(): TemplateResult {
return html`
<svg width="100%" height="100%" viewBox="0 0 48 48">
<line x1="24" y1="0" x2="24" y2=${this.end ? "24" : "48"}></line>
<line x1="24" y1="24" x2="36" y2="24"></line>
</svg>
`;
}
static styles = css`
:host {
display: block;
width: 48px;
height: 48px;
}
line {
stroke: var(--divider-color);
stroke-width: 2;
stroke-dasharray: 2;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-tree-indicator": HaTreeIndicator;
}
}