mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-19 18:28:42 +00:00
209 lines
6.0 KiB
TypeScript
209 lines
6.0 KiB
TypeScript
import { ReactiveElement } from "lit";
|
|
import { customElement } from "lit/decorators";
|
|
import { computeDeviceName } from "../../../../common/entity/compute_device_name";
|
|
import { computeDomain } from "../../../../common/entity/compute_domain";
|
|
import { getEntityContext } from "../../../../common/entity/context/get_entity_context";
|
|
import {
|
|
findEntities,
|
|
generateEntityFilter,
|
|
} from "../../../../common/entity/entity_filter";
|
|
import { clamp } from "../../../../common/number/clamp";
|
|
import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
|
|
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
|
|
import type { HomeAssistant } from "../../../../types";
|
|
import { isHelperDomain } from "../../../config/helpers/const";
|
|
import type { HeadingCardConfig } from "../../cards/types";
|
|
import { OTHER_DEVICES_FILTERS } from "./helpers/other-devices-filters";
|
|
|
|
export interface HomeOtherDevicesViewStrategyConfig {
|
|
type: "home-other-devices";
|
|
}
|
|
|
|
@customElement("home-other-devices-view-strategy")
|
|
export class HomeOtherDevicesViewStrategy extends ReactiveElement {
|
|
static async generate(
|
|
_config: HomeOtherDevicesViewStrategyConfig,
|
|
hass: HomeAssistant
|
|
): Promise<LovelaceViewConfig> {
|
|
const allEntities = Object.keys(hass.states);
|
|
|
|
const otherDevicesFilters = OTHER_DEVICES_FILTERS.map((filter) =>
|
|
generateEntityFilter(hass, filter)
|
|
);
|
|
|
|
const otherDevicesEntities = findEntities(allEntities, otherDevicesFilters);
|
|
|
|
const sections: LovelaceSectionRawConfig[] = [];
|
|
|
|
const entitiesByDevice: Record<string, string[]> = {};
|
|
const entitiesWithoutDevices: string[] = [];
|
|
for (const entityId of otherDevicesEntities) {
|
|
const stateObj = hass.states[entityId];
|
|
if (!stateObj) continue;
|
|
const { device } = getEntityContext(
|
|
stateObj,
|
|
hass.entities,
|
|
hass.devices,
|
|
hass.areas,
|
|
hass.floors
|
|
);
|
|
if (!device) {
|
|
entitiesWithoutDevices.push(entityId);
|
|
continue;
|
|
}
|
|
if (!(device.id in entitiesByDevice)) {
|
|
entitiesByDevice[device.id] = [];
|
|
}
|
|
entitiesByDevice[device.id].push(entityId);
|
|
}
|
|
|
|
const devicesEntities = Object.entries(entitiesByDevice).map(
|
|
([deviceId, entities]) => ({
|
|
device_id: deviceId,
|
|
entities: entities,
|
|
})
|
|
);
|
|
|
|
const helpersEntities = entitiesWithoutDevices.filter((entityId) => {
|
|
const domain = computeDomain(entityId);
|
|
return isHelperDomain(domain);
|
|
});
|
|
|
|
const otherEntities = entitiesWithoutDevices.filter((entityId) => {
|
|
const domain = computeDomain(entityId);
|
|
return !isHelperDomain(domain);
|
|
});
|
|
|
|
const batteryFilter = generateEntityFilter(hass, {
|
|
domain: "sensor",
|
|
device_class: "battery",
|
|
});
|
|
|
|
const energyFilter = generateEntityFilter(hass, {
|
|
domain: "sensor",
|
|
device_class: ["energy", "power"],
|
|
});
|
|
|
|
const primaryFilter = generateEntityFilter(hass, {
|
|
entity_category: "none",
|
|
});
|
|
|
|
for (const deviceEntities of devicesEntities) {
|
|
if (deviceEntities.entities.length === 0) continue;
|
|
|
|
const batteryEntities = deviceEntities.entities.filter((e) =>
|
|
batteryFilter(e)
|
|
);
|
|
const entities = deviceEntities.entities.filter(
|
|
(e) => !batteryFilter(e) && !energyFilter(e) && primaryFilter(e)
|
|
);
|
|
|
|
if (entities.length === 0) {
|
|
continue;
|
|
}
|
|
|
|
const deviceId = deviceEntities.device_id;
|
|
const device = hass.devices[deviceId];
|
|
let heading = "";
|
|
if (device) {
|
|
heading =
|
|
computeDeviceName(device) ||
|
|
hass.localize("ui.panel.lovelace.strategy.home.unamed_device");
|
|
}
|
|
|
|
sections.push({
|
|
type: "grid",
|
|
cards: [
|
|
{
|
|
type: "heading",
|
|
heading: heading,
|
|
tap_action: device
|
|
? {
|
|
action: "navigate",
|
|
navigation_path: `/config/devices/device/${device.id}`,
|
|
}
|
|
: undefined,
|
|
badges: [
|
|
...batteryEntities.slice(0, 1).map((e) => ({
|
|
entity: e,
|
|
type: "entity",
|
|
tap_action: {
|
|
action: "more-info",
|
|
},
|
|
})),
|
|
],
|
|
} satisfies HeadingCardConfig,
|
|
...entities.map((e) => ({
|
|
type: "tile",
|
|
entity: e,
|
|
name: {
|
|
type: "entity",
|
|
},
|
|
})),
|
|
],
|
|
});
|
|
}
|
|
|
|
// Allow between 2 and 3 columns (the max should be set to define the width of the header)
|
|
const maxColumns = clamp(sections.length, 2, 3);
|
|
|
|
if (helpersEntities.length) {
|
|
sections.push({
|
|
type: "grid",
|
|
column_span: maxColumns,
|
|
cards: [
|
|
{
|
|
type: "heading",
|
|
heading: hass.localize(
|
|
"ui.panel.lovelace.strategy.other_devices.helpers"
|
|
),
|
|
} satisfies HeadingCardConfig,
|
|
...helpersEntities.map((e) => ({
|
|
type: "tile",
|
|
entity: e,
|
|
})),
|
|
],
|
|
});
|
|
}
|
|
|
|
if (otherEntities.length) {
|
|
sections.push({
|
|
type: "grid",
|
|
column_span: maxColumns,
|
|
cards: [
|
|
{
|
|
type: "heading",
|
|
heading: hass.localize(
|
|
"ui.panel.lovelace.strategy.other_devices.entities"
|
|
),
|
|
} satisfies HeadingCardConfig,
|
|
...otherEntities.map((e) => ({
|
|
type: "tile",
|
|
entity: e,
|
|
})),
|
|
],
|
|
});
|
|
}
|
|
|
|
// Take the full width if there is only one section to avoid narrow header on desktop
|
|
if (sections.length === 1) {
|
|
sections[0].column_span = 2;
|
|
}
|
|
|
|
return {
|
|
type: "sections",
|
|
header: {
|
|
badges_position: "bottom",
|
|
},
|
|
max_columns: maxColumns,
|
|
sections: sections,
|
|
};
|
|
}
|
|
}
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
"home-other-devices-view-strategy": HomeOtherDevicesViewStrategy;
|
|
}
|
|
}
|