mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
Sort generated dashboard (#14577)
This commit is contained in:
parent
34dfaa5a0f
commit
06ee08db36
@ -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
|
||||||
|
@ -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<
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user