mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-28 04:07:21 +00:00
Compare commits
2 Commits
fix-undo-r
...
unassigned
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ce6419cd8 | ||
|
|
ff28ca11c9 |
@@ -35,6 +35,7 @@ const COLORS: Record<HomeSummary, string> = {
|
||||
climate: "deep-orange",
|
||||
security: "blue-grey",
|
||||
media_players: "blue",
|
||||
unassigned_devices: "grey",
|
||||
};
|
||||
|
||||
@customElement("hui-home-summary-card")
|
||||
|
||||
@@ -50,6 +50,8 @@ const STRATEGIES: Record<LovelaceStrategyConfigType, Record<string, any>> = {
|
||||
"home-media-players": () =>
|
||||
import("./home/home-media-players-view-strategy"),
|
||||
"home-area": () => import("./home/home-area-view-strategy"),
|
||||
"home-unassigned-devices": () =>
|
||||
import("./home/home-unassigned-devices-view-strategy"),
|
||||
light: () => import("../../light/strategies/light-view-strategy"),
|
||||
security: () => import("../../security/strategies/security-view-strategy"),
|
||||
climate: () => import("../../climate/strategies/climate-view-strategy"),
|
||||
|
||||
@@ -9,6 +9,7 @@ export const HOME_SUMMARIES = [
|
||||
"climate",
|
||||
"security",
|
||||
"media_players",
|
||||
"unassigned_devices",
|
||||
] as const;
|
||||
|
||||
export type HomeSummary = (typeof HOME_SUMMARIES)[number];
|
||||
@@ -18,6 +19,7 @@ export const HOME_SUMMARIES_ICONS: Record<HomeSummary, string> = {
|
||||
climate: "mdi:home-thermometer",
|
||||
security: "mdi:security",
|
||||
media_players: "mdi:multimedia",
|
||||
unassigned_devices: "mdi:shape",
|
||||
};
|
||||
|
||||
export const HOME_SUMMARIES_FILTERS: Record<HomeSummary, EntityFilter[]> = {
|
||||
@@ -25,6 +27,19 @@ export const HOME_SUMMARIES_FILTERS: Record<HomeSummary, EntityFilter[]> = {
|
||||
climate: climateEntityFilters,
|
||||
security: securityEntityFilters,
|
||||
media_players: [{ domain: "media_player", entity_category: "none" }],
|
||||
unassigned_devices: [
|
||||
{
|
||||
area: null,
|
||||
hidden_platform: [
|
||||
"automation",
|
||||
"script",
|
||||
"hassio",
|
||||
"backup",
|
||||
"zone",
|
||||
"person",
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const getSummaryLabel = (
|
||||
|
||||
@@ -71,6 +71,16 @@ export class HomeDashboardStrategy extends ReactiveElement {
|
||||
icon: HOME_SUMMARIES_ICONS.media_players,
|
||||
} satisfies LovelaceViewRawConfig;
|
||||
|
||||
const unassignedDevicesView = {
|
||||
title: getSummaryLabel(hass.localize, "unassigned_devices"),
|
||||
path: "unassigned-devices",
|
||||
subview: true,
|
||||
strategy: {
|
||||
type: "home-unassigned-devices",
|
||||
},
|
||||
icon: HOME_SUMMARIES_ICONS.unassigned_devices,
|
||||
} satisfies LovelaceViewRawConfig;
|
||||
|
||||
return {
|
||||
views: [
|
||||
{
|
||||
@@ -83,6 +93,7 @@ export class HomeDashboardStrategy extends ReactiveElement {
|
||||
},
|
||||
...areaViews,
|
||||
mediaPlayersView,
|
||||
unassignedDevicesView,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -228,6 +228,19 @@ export class HomeMainViewStrategy extends ReactiveElement {
|
||||
columns: 4,
|
||||
},
|
||||
} satisfies HomeSummaryCard),
|
||||
{
|
||||
type: "home-summary",
|
||||
summary: "unassigned_devices",
|
||||
vertical: true,
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: "unassigned-devices",
|
||||
},
|
||||
grid_options: {
|
||||
rows: 2,
|
||||
columns: 4,
|
||||
},
|
||||
} satisfies HomeSummaryCard,
|
||||
].filter(Boolean) as LovelaceCardConfig[];
|
||||
|
||||
const summarySection: LovelaceSectionConfig = {
|
||||
@@ -297,6 +310,29 @@ export class HomeMainViewStrategy extends ReactiveElement {
|
||||
}
|
||||
}
|
||||
|
||||
const noAreaFilter = generateEntityFilter(hass, {
|
||||
area: null,
|
||||
});
|
||||
|
||||
const otherEntities = allEntities.filter(noAreaFilter);
|
||||
|
||||
if (otherEntities.length > 0) {
|
||||
widgetSection.cards!.push({
|
||||
type: "tile",
|
||||
entity: otherEntities[0],
|
||||
icon: "mdi:shape",
|
||||
name: "Unassigned devices",
|
||||
hide_state: true,
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: "unassigned-devices",
|
||||
},
|
||||
icon_tap_action: {
|
||||
action: "none",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const sections = (
|
||||
[
|
||||
favoriteSection.cards && favoriteSection,
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { computeDeviceName } from "../../../../common/entity/compute_device_name";
|
||||
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 { HOME_SUMMARIES_FILTERS } from "./helpers/home-summaries";
|
||||
|
||||
export interface HomeUnassignedDevicesViewStrategyConfig {
|
||||
type: "home-unassigned-devices";
|
||||
}
|
||||
|
||||
@customElement("home-unassigned-devices-view-strategy")
|
||||
export class HomeUnassignedDevicesViewStrategy extends ReactiveElement {
|
||||
static async generate(
|
||||
_config: HomeUnassignedDevicesViewStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
): Promise<LovelaceViewConfig> {
|
||||
const allEntities = Object.keys(hass.states);
|
||||
|
||||
const unassignedFilters = HOME_SUMMARIES_FILTERS.unassigned_devices.map(
|
||||
(filter) => generateEntityFilter(hass, filter)
|
||||
);
|
||||
|
||||
const unassignedEntities = findEntities(allEntities, unassignedFilters);
|
||||
|
||||
const sections: LovelaceSectionRawConfig[] = [];
|
||||
|
||||
const entitiesByDevice: Record<string, string[]> = {};
|
||||
const entitiesWithoutDevices: string[] = [];
|
||||
for (const entityId of unassignedEntities) {
|
||||
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 = entityId.split(".")[0];
|
||||
return isHelperDomain(domain);
|
||||
});
|
||||
|
||||
const otherEntities = entitiesWithoutDevices.filter((entityId) => {
|
||||
const domain = entityId.split(".")[0];
|
||||
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",
|
||||
},
|
||||
})),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
if (helpersEntities.length) {
|
||||
sections.push({
|
||||
type: "grid",
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading: hass.localize(
|
||||
"ui.panel.lovelace.strategy.unassigned_devices.unassigned_helpers"
|
||||
),
|
||||
} satisfies HeadingCardConfig,
|
||||
...helpersEntities.map((e) => ({
|
||||
type: "tile",
|
||||
entity: e,
|
||||
})),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
if (otherEntities.length) {
|
||||
sections.push({
|
||||
type: "grid",
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading: hass.localize(
|
||||
"ui.panel.lovelace.strategy.unassigned_devices.unassigned_entities"
|
||||
),
|
||||
} satisfies HeadingCardConfig,
|
||||
...otherEntities.map((e) => ({
|
||||
type: "tile",
|
||||
entity: e,
|
||||
})),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// 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-unassigned-devices-view-strategy": HomeUnassignedDevicesViewStrategy;
|
||||
}
|
||||
}
|
||||
@@ -7063,7 +7063,8 @@
|
||||
},
|
||||
"home": {
|
||||
"summary_list": {
|
||||
"media_players": "Media players"
|
||||
"media_players": "Media players",
|
||||
"unassigned_devices": "Unassigned devices"
|
||||
},
|
||||
"welcome_user": "Welcome {user}",
|
||||
"summaries": "Summaries",
|
||||
@@ -7093,6 +7094,10 @@
|
||||
"home_media_players": {
|
||||
"media_players": "Media players",
|
||||
"other_media_players": "Other media players"
|
||||
},
|
||||
"unassigned_devices": {
|
||||
"unassigned_helpers": "Unassigned helpers",
|
||||
"unassigned_entities": "Unassigned entities"
|
||||
}
|
||||
},
|
||||
"cards": {
|
||||
|
||||
Reference in New Issue
Block a user