Sort generated dashboard (#14577)

This commit is contained in:
Bram Kragten 2022-12-06 18:30:23 +01:00 committed by GitHub
parent 34dfaa5a0f
commit 06ee08db36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 88 additions and 52 deletions

View File

@ -188,6 +188,14 @@ export const DOMAINS_WITH_CARD = [
"water_heater", "water_heater",
]; ];
export const SENSOR_ENTITIES = [
"sensor",
"binary_sensor",
"camera",
"device_tracker",
"weather",
];
/** Domains that render an input element instead of a text value when displayed in a row. /** Domains that render an input element instead of a text value when displayed in a row.
* Those rows should then not show a cursor pointer when hovered (which would normally * Those rows should then not show a cursor pointer when hovered (which would normally
* be the default) unless the element itself enforces it (e.g. a button). Also those elements * be the default) unless the element itself enforces it (e.g. a button). Also those elements

View File

@ -75,6 +75,7 @@ import {
showDeviceRegistryDetailDialog, showDeviceRegistryDetailDialog,
} from "./device-registry-detail/show-dialog-device-registry-detail"; } from "./device-registry-detail/show-dialog-device-registry-detail";
import "../../../layouts/hass-subpage"; import "../../../layouts/hass-subpage";
import { SENSOR_ENTITIES } from "../../../common/const";
export interface EntityRegistryStateEntry extends EntityRegistryEntry { export interface EntityRegistryStateEntry extends EntityRegistryEntry {
stateName?: string | null; stateName?: string | null;
@ -172,13 +173,7 @@ export class HaConfigDevicePage extends LitElement {
const result = groupBy(entities, (entry) => const result = groupBy(entities, (entry) =>
entry.entity_category entry.entity_category
? entry.entity_category ? entry.entity_category
: [ : SENSOR_ENTITIES.includes(computeDomain(entry.entity_id))
"sensor",
"binary_sensor",
"camera",
"device_tracker",
"weather",
].includes(computeDomain(entry.entity_id))
? "sensor" ? "sensor"
: "control" : "control"
) as Record< ) as Record<

View File

@ -1,4 +1,5 @@
import { HassEntities, HassEntity } from "home-assistant-js-websocket"; import { HassEntities, HassEntity } from "home-assistant-js-websocket";
import { SENSOR_ENTITIES } from "../../../common/const";
import { computeDomain } from "../../../common/entity/compute_domain"; import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateDomain } from "../../../common/entity/compute_state_domain"; import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { computeStateName } from "../../../common/entity/compute_state_name"; import { computeStateName } from "../../../common/entity/compute_state_name";
@ -23,7 +24,7 @@ import {
PictureEntityCardConfig, PictureEntityCardConfig,
ThermostatCardConfig, ThermostatCardConfig,
} from "../cards/types"; } from "../cards/types";
import { LovelaceRowConfig } from "../entity-rows/types"; import { EntityConfig } from "../entity-rows/types";
import { ButtonsHeaderFooterConfig } from "../header-footer/types"; import { ButtonsHeaderFooterConfig } from "../header-footer/types";
const HIDE_DOMAIN = new Set([ const HIDE_DOMAIN = new Set([
@ -96,14 +97,15 @@ const splitByAreaDevice = (
}; };
export const computeCards = ( export const computeCards = (
states: Array<[string, HassEntity?]>, states: HassEntities,
entityIds: string[],
entityCardOptions: Partial<EntitiesCardConfig>, entityCardOptions: Partial<EntitiesCardConfig>,
renderFooterEntities = true renderFooterEntities = true
): LovelaceCardConfig[] => { ): LovelaceCardConfig[] => {
const cards: LovelaceCardConfig[] = []; const cards: LovelaceCardConfig[] = [];
// For entity card // For entity card
const entities: Array<string | LovelaceRowConfig> = []; const entitiesConf: Array<string | EntityConfig> = [];
const titlePrefix = entityCardOptions.title const titlePrefix = entityCardOptions.title
? entityCardOptions.title.toLowerCase() ? entityCardOptions.title.toLowerCase()
@ -111,7 +113,8 @@ export const computeCards = (
const footerEntities: ButtonsHeaderFooterConfig["entities"] = []; const footerEntities: ButtonsHeaderFooterConfig["entities"] = [];
for (const [entityId, stateObj] of states) { for (const entityId of entityIds) {
const stateObj = states[entityId];
const domain = computeDomain(entityId); const domain = computeDomain(entityId);
if (domain === "alarm_control_panel") { if (domain === "alarm_control_panel") {
@ -200,20 +203,49 @@ export const computeCards = (
} }
: entityId; : entityId;
entities.push(entityConf); entitiesConf.push(entityConf);
} }
} }
entitiesConf.sort((a, b) => {
const entityIdA = typeof a === "string" ? a : a.entity;
const entityIdB = typeof b === "string" ? b : b.entity;
const categoryA = SENSOR_ENTITIES.includes(computeDomain(entityIdA))
? "sensor"
: "control";
const categoryB = SENSOR_ENTITIES.includes(computeDomain(entityIdB))
? "sensor"
: "control";
if (categoryA !== categoryB) {
return categoryA === "sensor" ? 1 : -1;
}
return stringCompare(
typeof a === "string"
? states[a]
? computeStateName(states[a])
: ""
: a.name || "",
typeof b === "string"
? states[b]
? computeStateName(states[b])
: ""
: b.name || ""
);
});
// If we ended up with footer entities but no normal entities, // If we ended up with footer entities but no normal entities,
// render the footer entities as normal entities. // render the footer entities as normal entities.
if (entities.length === 0 && footerEntities.length > 0) { if (entitiesConf.length === 0 && footerEntities.length > 0) {
return computeCards(states, entityCardOptions, false); return computeCards(states, entityIds, entityCardOptions, false);
} }
if (entities.length > 0 || footerEntities.length > 0) { if (entitiesConf.length > 0 || footerEntities.length > 0) {
const card: EntitiesCardConfig = { const card: EntitiesCardConfig = {
type: "entities", type: "entities",
entities, entities: entitiesConf,
...entityCardOptions, ...entityCardOptions,
}; };
if (footerEntities.length > 0) { if (footerEntities.length > 0) {
@ -354,15 +386,10 @@ export const generateViewConfig = (
for (const groupEntity of splitted.groups) { for (const groupEntity of splitted.groups) {
cards.push( cards.push(
...computeCards( ...computeCards(entities, groupEntity.attributes.entity_id, {
groupEntity.attributes.entity_id.map( title: computeStateName(groupEntity),
(entityId): [string, HassEntity] => [entityId, entities[entityId]] show_header_toggle: groupEntity.attributes.control !== "hidden",
), })
{
title: computeStateName(groupEntity),
show_header_toggle: groupEntity.attributes.control !== "hidden",
}
)
); );
} }
@ -398,17 +425,13 @@ export const generateViewConfig = (
.forEach((domain) => { .forEach((domain) => {
cards.push( cards.push(
...computeCards( ...computeCards(
ungroupedEntitites[domain] entities,
.sort((a, b) => ungroupedEntitites[domain].sort((a, b) =>
stringCompare( stringCompare(
computeStateName(entities[a]), computeStateName(entities[a]),
computeStateName(entities[b]) computeStateName(entities[b])
)
) )
.map((entityId): [string, HassEntity] => [ ),
entityId,
entities[entityId],
]),
{ {
title: domainTranslations[domain], title: domainTranslations[domain],
} }
@ -466,28 +489,35 @@ export const generateDefaultViewConfig = (
groupOrders groupOrders
); );
const splittedCards: LovelaceCardConfig[] = []; const areaCards: LovelaceCardConfig[] = [];
for (const [areaId, areaEntities] of Object.entries( for (const [areaId, areaEntities] of Object.entries(
splittedByAreaDevice.areasWithEntities splittedByAreaDevice.areasWithEntities
)) { )) {
const area = areaEntries[areaId]; const area = areaEntries[areaId];
splittedCards.push( areaCards.push(
...computeCards( ...computeCards(
areaEntities.map((entity) => [entity.entity_id, entity]), entities,
areaEntities.map((entity) => entity.entity_id),
{ {
title: area.name, title: area.name,
} }
) )
); );
} }
areaCards.sort((a, b) => stringCompare(a.title || "", b.title || ""));
const deviceCards: LovelaceCardConfig[] = [];
for (const [deviceId, deviceEntities] of Object.entries( for (const [deviceId, deviceEntities] of Object.entries(
splittedByAreaDevice.devicesWithEntities splittedByAreaDevice.devicesWithEntities
)) { )) {
const device = deviceEntries[deviceId]; const device = deviceEntries[deviceId];
splittedCards.push( deviceCards.push(
...computeCards( ...computeCards(
deviceEntities.map((entity) => [entity.entity_id, entity]), entities,
deviceEntities.map((entity) => entity.entity_id),
{ {
title: title:
device.name_by_user || device.name_by_user ||
@ -503,6 +533,11 @@ export const generateDefaultViewConfig = (
) )
); );
} }
deviceCards.sort((a, b) => stringCompare(a.title || "", b.title || ""));
let energyCard: LovelaceCardConfig | undefined;
if (energyPrefs) { if (energyPrefs) {
// Distribution card requires the grid to be configured // Distribution card requires the grid to be configured
const grid = energyPrefs.energy_sources.find( const grid = energyPrefs.energy_sources.find(
@ -510,17 +545,21 @@ export const generateDefaultViewConfig = (
) as GridSourceTypeEnergyPreference | undefined; ) as GridSourceTypeEnergyPreference | undefined;
if (grid && grid.flow_from.length > 0) { if (grid && grid.flow_from.length > 0) {
splittedCards.push({ energyCard = {
title: localize( title: localize(
"ui.panel.lovelace.cards.energy.energy_distribution.title_today" "ui.panel.lovelace.cards.energy.energy_distribution.title_today"
), ),
type: "energy-distribution", type: "energy-distribution",
link_dashboard: true, link_dashboard: true,
}); };
} }
} }
config.cards!.unshift(...splittedCards); config.cards!.unshift(
...areaCards,
...(energyCard ? [energyCard] : []),
...deviceCards
);
return config; return config;
}; };

View File

@ -30,15 +30,9 @@ export class HuiDialogSuggestCard extends LitElement {
this._params = params; this._params = params;
this._cardConfig = this._cardConfig =
params.cardConfig || params.cardConfig ||
computeCards( computeCards(this.hass.states, params.entities, {
params.entities.map((entityId) => [ title: params.cardTitle,
entityId, });
this.hass.states[entityId],
]),
{
title: params.cardTitle,
}
);
if (!Object.isFrozen(this._cardConfig)) { if (!Object.isFrozen(this._cardConfig)) {
this._cardConfig = deepFreeze(this._cardConfig); this._cardConfig = deepFreeze(this._cardConfig);
} }