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",
];
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.
* 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

View File

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

View File

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

View File

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