Sort all elements on the area page (#11338)

This commit is contained in:
Philip Allgaier 2022-01-26 18:06:12 +01:00 committed by GitHub
parent 7d335d7d85
commit a4ae1bee79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 162 additions and 89 deletions

View File

@ -1,5 +1,6 @@
import { Connection, createCollection } from "home-assistant-js-websocket"; import { Connection, createCollection } from "home-assistant-js-websocket";
import { computeStateName } from "../common/entity/compute_state_name"; import { computeStateName } from "../common/entity/compute_state_name";
import { caseInsensitiveStringCompare } from "../common/string/compare";
import { debounce } from "../common/util/debounce"; import { debounce } from "../common/util/debounce";
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
import { EntityRegistryEntry } from "./entity_registry"; import { EntityRegistryEntry } from "./entity_registry";
@ -99,3 +100,8 @@ export const subscribeDeviceRegistry = (
conn, conn,
onChange onChange
); );
export const sortDeviceRegistryByName = (entries: DeviceRegistryEntry[]) =>
entries.sort((entry1, entry2) =>
caseInsensitiveStringCompare(entry1.name || "", entry2.name || "")
);

View File

@ -1,6 +1,7 @@
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 { computeStateName } from "../common/entity/compute_state_name"; import { computeStateName } from "../common/entity/compute_state_name";
import { caseInsensitiveStringCompare } from "../common/string/compare";
import { debounce } from "../common/util/debounce"; import { debounce } from "../common/util/debounce";
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
@ -133,3 +134,8 @@ export const subscribeEntityRegistry = (
conn, conn,
onChange onChange
); );
export const sortEntityRegistryByName = (entries: EntityRegistryEntry[]) =>
entries.sort((entry1, entry2) =>
caseInsensitiveStringCompare(entry1.name || "", entry2.name || "")
);

View File

@ -1,13 +1,17 @@
import "@material/mwc-button"; import "@material/mwc-button";
import { mdiImagePlus, mdiPencil } from "@mdi/js";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-item/paper-item-body";
import { mdiImagePlus, mdiPencil } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket/dist/types";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } 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";
import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateName } from "../../../common/entity/compute_state_name"; import { computeStateName } from "../../../common/entity/compute_state_name";
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
import { groupBy } from "../../../common/util/group-by";
import { afterNextRender } from "../../../common/util/render-status"; 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";
@ -17,14 +21,19 @@ import {
deleteAreaRegistryEntry, deleteAreaRegistryEntry,
updateAreaRegistryEntry, updateAreaRegistryEntry,
} from "../../../data/area_registry"; } from "../../../data/area_registry";
import { AutomationEntity } from "../../../data/automation";
import { import {
computeDeviceName, computeDeviceName,
DeviceRegistryEntry, DeviceRegistryEntry,
sortDeviceRegistryByName,
} from "../../../data/device_registry"; } from "../../../data/device_registry";
import { import {
computeEntityRegistryName, computeEntityRegistryName,
EntityRegistryEntry, EntityRegistryEntry,
sortEntityRegistryByName,
} from "../../../data/entity_registry"; } from "../../../data/entity_registry";
import { SceneEntity } from "../../../data/scene";
import { ScriptEntity } from "../../../data/script";
import { findRelated, RelatedResult } from "../../../data/search"; import { findRelated, RelatedResult } from "../../../data/search";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import { haStyle } from "../../../resources/styles"; import { haStyle } from "../../../resources/styles";
@ -35,11 +44,11 @@ import {
loadAreaRegistryDetailDialog, loadAreaRegistryDetailDialog,
showAreaRegistryDetailDialog, showAreaRegistryDetailDialog,
} from "./show-dialog-area-registry-detail"; } from "./show-dialog-area-registry-detail";
import { computeDomain } from "../../../common/entity/compute_domain";
import { SceneEntity } from "../../../data/scene"; declare type NameAndEntity<EntityType extends HassEntity> = {
import { ScriptEntity } from "../../../data/script"; name: string;
import { AutomationEntity } from "../../../data/automation"; entity: EntityType;
import { groupBy } from "../../../common/util/group-by"; };
@customElement("ha-config-area-page") @customElement("ha-config-area-page")
class HaConfigAreaPage extends LitElement { class HaConfigAreaPage extends LitElement {
@ -136,10 +145,59 @@ class HaConfigAreaPage extends LitElement {
this.entities this.entities
); );
const grouped = groupBy(entities, (entity) => // Pre-compute the entity and device names, so we can sort by them
if (devices) {
devices.forEach((entry) => {
entry.name = computeDeviceName(entry, this.hass);
});
sortDeviceRegistryByName(devices);
}
if (entities) {
entities.forEach((entry) => {
entry.name = computeEntityRegistryName(this.hass, entry);
});
sortEntityRegistryByName(entities);
}
// Group entities by domain
const groupedEntities = groupBy(entities, (entity) =>
computeDomain(entity.entity_id) computeDomain(entity.entity_id)
); );
// Pre-compute the name also for the grouped and related entities so we can sort by them
let groupedAutomations: NameAndEntity<AutomationEntity>[] = [];
let groupedScenes: NameAndEntity<SceneEntity>[] = [];
let groupedScripts: NameAndEntity<ScriptEntity>[] = [];
let relatedAutomations: NameAndEntity<AutomationEntity>[] = [];
let relatedScenes: NameAndEntity<SceneEntity>[] = [];
let relatedScripts: NameAndEntity<ScriptEntity>[] = [];
if (isComponentLoaded(this.hass, "automation")) {
({
groupedEntities: groupedAutomations,
relatedEntities: relatedAutomations,
} = this._prepareEntities<AutomationEntity>(
groupedEntities.automation,
this._related?.automation
));
}
if (isComponentLoaded(this.hass, "scene")) {
({ groupedEntities: groupedScenes, relatedEntities: relatedScenes } =
this._prepareEntities<SceneEntity>(
groupedEntities.scene,
this._related?.scene
));
}
if (isComponentLoaded(this.hass, "script")) {
({ groupedEntities: groupedScripts, relatedEntities: relatedScripts } =
this._prepareEntities<ScriptEntity>(
groupedEntities.script,
this._related?.script
));
}
return html` return html`
<hass-tabs-subpage <hass-tabs-subpage
.hass=${this.hass} .hass=${this.hass}
@ -208,9 +266,7 @@ class HaConfigAreaPage extends LitElement {
html` html`
<a href="/config/devices/device/${device.id}"> <a href="/config/devices/device/${device.id}">
<paper-item> <paper-item>
<paper-item-body> <paper-item-body> ${device.name} </paper-item-body>
${computeDeviceName(device, this.hass)}
</paper-item-body>
<ha-icon-next></ha-icon-next> <ha-icon-next></ha-icon-next>
</paper-item> </paper-item>
</a> </a>
@ -240,9 +296,7 @@ class HaConfigAreaPage extends LitElement {
@click=${this._openEntity} @click=${this._openEntity}
.entity=${entity} .entity=${entity}
> >
<paper-item-body> <paper-item-body> ${entity.name} </paper-item-body>
${computeEntityRegistryName(this.hass, entity)}
</paper-item-body>
<ha-icon-next></ha-icon-next> <ha-icon-next></ha-icon-next>
</paper-item> </paper-item>
` `
@ -264,43 +318,33 @@ class HaConfigAreaPage extends LitElement {
"ui.panel.config.devices.automation.automations" "ui.panel.config.devices.automation.automations"
)} )}
> >
${grouped.automation?.length ${groupedAutomations?.length
? html`<h3> ? html`<h3>
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.areas.assigned_to_area" "ui.panel.config.areas.assigned_to_area"
)}: )}:
</h3> </h3>
${grouped.automation.map((entity) => { ${groupedAutomations.map((automation) =>
const entityState = this.hass.states[ this._renderAutomation(
entity.entity_id automation.name,
] as AutomationEntity | undefined; automation.entity
return entityState )
? this._renderAutomation(entityState) )}`
: "";
})}`
: ""} : ""}
${this._related?.automation?.filter( ${relatedAutomations?.length
(entityId) =>
!grouped.automation?.find(
(entity) => entity.entity_id === entityId
)
).length
? html`<h3> ? html`<h3>
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.areas.targeting_area" "ui.panel.config.areas.targeting_area"
)}: )}:
</h3> </h3>
${this._related.automation.map((scene) => { ${relatedAutomations.map((automation) =>
const entityState = this.hass.states[scene] as this._renderAutomation(
| AutomationEntity automation.name,
| undefined; automation.entity
return entityState )
? this._renderAutomation(entityState) )}`
: "";
})}`
: ""} : ""}
${!grouped.automation?.length && ${!groupedAutomations?.length && !relatedAutomations?.length
!this._related?.automation?.length
? html` ? html`
<paper-item class="no-link" <paper-item class="no-link"
>${this.hass.localize( >${this.hass.localize(
@ -321,39 +365,27 @@ class HaConfigAreaPage extends LitElement {
"ui.panel.config.devices.scene.scenes" "ui.panel.config.devices.scene.scenes"
)} )}
> >
${grouped.scene?.length ${groupedScenes?.length
? html`<h3> ? html`<h3>
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.areas.assigned_to_area" "ui.panel.config.areas.assigned_to_area"
)}: )}:
</h3> </h3>
${grouped.scene.map((entity) => { ${groupedScenes.map((scene) =>
const entityState = this._renderScene(scene.name, scene.entity)
this.hass.states[entity.entity_id]; )}`
return entityState
? this._renderScene(entityState)
: "";
})}`
: ""} : ""}
${this._related?.scene?.filter( ${relatedScenes?.length
(entityId) =>
!grouped.scene?.find(
(entity) => entity.entity_id === entityId
)
).length
? html`<h3> ? html`<h3>
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.areas.targeting_area" "ui.panel.config.areas.targeting_area"
)}: )}:
</h3> </h3>
${this._related.scene.map((scene) => { ${relatedScenes.map((scene) =>
const entityState = this.hass.states[scene]; this._renderScene(scene.name, scene.entity)
return entityState )}`
? this._renderScene(entityState)
: "";
})}`
: ""} : ""}
${!grouped.scene?.length && !this._related?.scene?.length ${!groupedScenes?.length && !relatedScenes?.length
? html` ? html`
<paper-item class="no-link" <paper-item class="no-link"
>${this.hass.localize( >${this.hass.localize(
@ -372,42 +404,27 @@ class HaConfigAreaPage extends LitElement {
"ui.panel.config.devices.script.scripts" "ui.panel.config.devices.script.scripts"
)} )}
> >
${grouped.script?.length ${groupedScripts?.length
? html`<h3> ? html`<h3>
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.areas.assigned_to_area" "ui.panel.config.areas.assigned_to_area"
)}: )}:
</h3> </h3>
${grouped.script.map((entity) => { ${groupedScripts.map((script) =>
const entityState = this.hass.states[ this._renderScript(script.name, script.entity)
entity.entity_id )}`
] as ScriptEntity | undefined;
return entityState
? this._renderScript(entityState)
: "";
})}`
: ""} : ""}
${this._related?.script?.filter( ${relatedScripts?.length
(entityId) =>
!grouped.script?.find(
(entity) => entity.entity_id === entityId
)
).length
? html`<h3> ? html`<h3>
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.areas.targeting_area" "ui.panel.config.areas.targeting_area"
)}: )}:
</h3> </h3>
${this._related.script.map((scene) => { ${relatedScripts.map((script) =>
const entityState = this.hass.states[scene] as this._renderScript(script.name, script.entity)
| ScriptEntity )}`
| undefined;
return entityState
? this._renderScript(entityState)
: "";
})}`
: ""} : ""}
${!grouped.script?.length && !this._related?.script?.length ${!groupedScripts?.length && !relatedScripts?.length
? html` ? html`
<paper-item class="no-link" <paper-item class="no-link"
>${this.hass.localize( >${this.hass.localize(
@ -425,7 +442,51 @@ class HaConfigAreaPage extends LitElement {
`; `;
} }
private _renderScene(entityState: SceneEntity) { private _prepareEntities<EntityType extends HassEntity>(
entries?: EntityRegistryEntry[],
relatedEntityIds?: string[]
): {
groupedEntities: NameAndEntity<EntityType>[];
relatedEntities: NameAndEntity<EntityType>[];
} {
const groupedEntities: NameAndEntity<EntityType>[] = [];
const relatedEntities: NameAndEntity<EntityType>[] = [];
if (entries?.length) {
entries.forEach((entity) => {
const entityState = this.hass.states[
entity.entity_id
] as unknown as EntityType;
if (entityState) {
groupedEntities.push({
name: computeStateName(entityState),
entity: entityState,
});
}
});
groupedEntities.sort((entry1, entry2) =>
caseInsensitiveStringCompare(entry1.name!, entry2.name!)
);
}
if (relatedEntityIds?.length) {
relatedEntityIds.forEach((entity) => {
const entityState = this.hass.states[entity] as EntityType;
if (entityState) {
relatedEntities.push({
name: entityState ? computeStateName(entityState) : "",
entity: entityState,
});
}
});
relatedEntities.sort((entry1, entry2) =>
caseInsensitiveStringCompare(entry1.name!, entry2.name!)
);
}
return { groupedEntities, relatedEntities };
}
private _renderScene(name: string, entityState: SceneEntity) {
return html`<div> return html`<div>
<a <a
href=${ifDefined( href=${ifDefined(
@ -435,7 +496,7 @@ class HaConfigAreaPage extends LitElement {
)} )}
> >
<paper-item .disabled=${!entityState.attributes.id}> <paper-item .disabled=${!entityState.attributes.id}>
<paper-item-body> ${computeStateName(entityState)} </paper-item-body> <paper-item-body> ${name} </paper-item-body>
<ha-icon-next></ha-icon-next> <ha-icon-next></ha-icon-next>
</paper-item> </paper-item>
</a> </a>
@ -449,7 +510,7 @@ class HaConfigAreaPage extends LitElement {
</div>`; </div>`;
} }
private _renderAutomation(entityState: AutomationEntity) { private _renderAutomation(name: string, entityState: AutomationEntity) {
return html`<div> return html`<div>
<a <a
href=${ifDefined( href=${ifDefined(
@ -459,7 +520,7 @@ class HaConfigAreaPage extends LitElement {
)} )}
> >
<paper-item .disabled=${!entityState.attributes.id}> <paper-item .disabled=${!entityState.attributes.id}>
<paper-item-body> ${computeStateName(entityState)} </paper-item-body> <paper-item-body> ${name} </paper-item-body>
<ha-icon-next></ha-icon-next> <ha-icon-next></ha-icon-next>
</paper-item> </paper-item>
</a> </a>
@ -473,10 +534,10 @@ class HaConfigAreaPage extends LitElement {
</div>`; </div>`;
} }
private _renderScript(entityState: ScriptEntity) { private _renderScript(name: string, entityState: ScriptEntity) {
return html`<a href=${`/config/script/edit/${entityState.entity_id}`}> return html`<a href=${`/config/script/edit/${entityState.entity_id}`}>
<paper-item> <paper-item>
<paper-item-body> ${computeStateName(entityState)} </paper-item-body> <paper-item-body> ${name} </paper-item-body>
<ha-icon-next></ha-icon-next> <ha-icon-next></ha-icon-next>
</paper-item> </paper-item>
</a>`; </a>`;