mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-12 04:20:28 +00:00
Compare commits
3 Commits
copilot/fi
...
Area-card-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c3fd73738 | ||
|
|
8b81174395 | ||
|
|
f3d018f047 |
@@ -86,10 +86,22 @@ const splitByAreas = (
|
|||||||
|
|
||||||
export const computeCards = (
|
export const computeCards = (
|
||||||
states: Array<[string, HassEntity?]>,
|
states: Array<[string, HassEntity?]>,
|
||||||
entityCardOptions: Partial<EntitiesCardConfig>
|
entityCardOptions: Partial<EntitiesCardConfig>,
|
||||||
|
dedicated?: boolean
|
||||||
): LovelaceCardConfig[] => {
|
): LovelaceCardConfig[] => {
|
||||||
const cards: LovelaceCardConfig[] = [];
|
const cards: LovelaceCardConfig[] = [];
|
||||||
|
const sensorGrid: { type: string; cards: LovelaceCardConfig[] } = {
|
||||||
|
type: "grid",
|
||||||
|
columns: 2,
|
||||||
|
square: false,
|
||||||
|
cards: [],
|
||||||
|
};
|
||||||
|
const toggleGrid: { type: string; cards: LovelaceCardConfig[] } = {
|
||||||
|
type: "grid",
|
||||||
|
columns: 2,
|
||||||
|
square: false,
|
||||||
|
cards: [],
|
||||||
|
};
|
||||||
// For entity card
|
// For entity card
|
||||||
const entities: Array<string | LovelaceRowConfig> = [];
|
const entities: Array<string | LovelaceRowConfig> = [];
|
||||||
|
|
||||||
@@ -148,6 +160,39 @@ export const computeCards = (
|
|||||||
stateObj?.attributes.device_class === SENSOR_DEVICE_CLASS_BATTERY
|
stateObj?.attributes.device_class === SENSOR_DEVICE_CLASS_BATTERY
|
||||||
) {
|
) {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
|
} else if (dedicated) {
|
||||||
|
if (domain === "binary_sensor") {
|
||||||
|
const cardConfig = {
|
||||||
|
type: "entity",
|
||||||
|
entity: entityId,
|
||||||
|
};
|
||||||
|
sensorGrid.cards.push(cardConfig);
|
||||||
|
} else if (domain === "sensor") {
|
||||||
|
const cardConfig = {
|
||||||
|
type: "sensor",
|
||||||
|
entity: entityId,
|
||||||
|
graph: "line",
|
||||||
|
};
|
||||||
|
sensorGrid.cards.push(cardConfig);
|
||||||
|
} else if (domain === "switch") {
|
||||||
|
const cardConfig = {
|
||||||
|
type: "button",
|
||||||
|
entity: entityId,
|
||||||
|
};
|
||||||
|
toggleGrid.cards.push(cardConfig);
|
||||||
|
} else if (domain === "fan") {
|
||||||
|
const cardConfig = {
|
||||||
|
type: "button",
|
||||||
|
entity: entityId,
|
||||||
|
};
|
||||||
|
toggleGrid.cards.push(cardConfig);
|
||||||
|
} else if (domain === "light") {
|
||||||
|
const cardConfig = {
|
||||||
|
type: "light",
|
||||||
|
entity: entityId,
|
||||||
|
};
|
||||||
|
toggleGrid.cards.push(cardConfig);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let name: string | undefined;
|
let name: string | undefined;
|
||||||
const entityConf =
|
const entityConf =
|
||||||
@@ -176,6 +221,14 @@ export const computeCards = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sensorGrid.cards.length > 0) {
|
||||||
|
cards.push(sensorGrid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toggleGrid.cards.length > 0) {
|
||||||
|
cards.push(toggleGrid);
|
||||||
|
}
|
||||||
|
|
||||||
return cards;
|
return cards;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -409,3 +462,59 @@ export const generateDefaultViewConfig = (
|
|||||||
|
|
||||||
return config;
|
return config;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const generateAreaViewConfig = (
|
||||||
|
area: AreaRegistryEntry,
|
||||||
|
deviceEntries: DeviceRegistryEntry[],
|
||||||
|
entityEntries: EntityRegistryEntry[],
|
||||||
|
entities: HassEntities,
|
||||||
|
localize: LocalizeFunc
|
||||||
|
): LovelaceViewConfig => {
|
||||||
|
const states = computeDefaultViewStates(entities, entityEntries);
|
||||||
|
const path = "default_view";
|
||||||
|
const title = "Home";
|
||||||
|
const icon = undefined;
|
||||||
|
|
||||||
|
// In the case of a default view, we want to use the group order attribute
|
||||||
|
const groupOrders = {};
|
||||||
|
Object.keys(states).forEach((entityId) => {
|
||||||
|
const stateObj = states[entityId];
|
||||||
|
if (stateObj.attributes.order) {
|
||||||
|
groupOrders[entityId] = stateObj.attributes.order;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const splittedByAreas = splitByAreas(
|
||||||
|
[area],
|
||||||
|
deviceEntries,
|
||||||
|
entityEntries,
|
||||||
|
states
|
||||||
|
);
|
||||||
|
|
||||||
|
const config = generateViewConfig(
|
||||||
|
localize,
|
||||||
|
path,
|
||||||
|
title,
|
||||||
|
icon,
|
||||||
|
{},
|
||||||
|
groupOrders
|
||||||
|
);
|
||||||
|
|
||||||
|
const areaCards: LovelaceCardConfig[] = [];
|
||||||
|
|
||||||
|
splittedByAreas.areasWithEntities.forEach(([a, areaEntities]) => {
|
||||||
|
areaCards.push(
|
||||||
|
...computeCards(
|
||||||
|
areaEntities.map((entity) => [entity.entity_id, entity]),
|
||||||
|
{
|
||||||
|
title: a.name,
|
||||||
|
},
|
||||||
|
true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
config.cards!.unshift(...areaCards);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|||||||
80
src/panels/lovelace/strategies/area-overview-strategy.ts
Normal file
80
src/panels/lovelace/strategies/area-overview-strategy.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { STATE_NOT_RUNNING } from "home-assistant-js-websocket";
|
||||||
|
import { subscribeOne } from "../../../common/util/subscribe-one";
|
||||||
|
import { subscribeAreaRegistry } from "../../../data/area_registry";
|
||||||
|
import { subscribeDeviceRegistry } from "../../../data/device_registry";
|
||||||
|
import { subscribeEntityRegistry } from "../../../data/entity_registry";
|
||||||
|
import { LovelaceViewConfig } from "../../../data/lovelace";
|
||||||
|
import {
|
||||||
|
LovelaceDashboardStrategy,
|
||||||
|
LovelaceViewStrategy,
|
||||||
|
} from "./get-strategy";
|
||||||
|
|
||||||
|
let subscribedRegistries = false;
|
||||||
|
|
||||||
|
export class AreaOverviewStrategy {
|
||||||
|
static async generateView(
|
||||||
|
info: Parameters<LovelaceViewStrategy["generateView"]>[0]
|
||||||
|
): ReturnType<LovelaceViewStrategy["generateView"]> {
|
||||||
|
const hass = info.hass;
|
||||||
|
const view: LovelaceViewConfig = { cards: [] };
|
||||||
|
|
||||||
|
if (hass.config.state === STATE_NOT_RUNNING) {
|
||||||
|
return {
|
||||||
|
cards: [{ type: "starting" }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hass.config.safe_mode) {
|
||||||
|
return {
|
||||||
|
cards: [{ type: "safe-mode" }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// We leave this here so we always have the freshest data.
|
||||||
|
if (!subscribedRegistries) {
|
||||||
|
subscribedRegistries = true;
|
||||||
|
subscribeAreaRegistry(hass.connection, () => undefined);
|
||||||
|
subscribeDeviceRegistry(hass.connection, () => undefined);
|
||||||
|
subscribeEntityRegistry(hass.connection, () => undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [areaEntries] = await Promise.all([
|
||||||
|
subscribeOne(hass.connection, subscribeAreaRegistry),
|
||||||
|
]);
|
||||||
|
|
||||||
|
areaEntries.forEach((area) => {
|
||||||
|
view.cards?.push({
|
||||||
|
type: "area",
|
||||||
|
area: area.area_id,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async generateDashboard(
|
||||||
|
info: Parameters<LovelaceDashboardStrategy["generateDashboard"]>[0]
|
||||||
|
): ReturnType<LovelaceDashboardStrategy["generateDashboard"]> {
|
||||||
|
const [areaEntries] = await Promise.all([
|
||||||
|
subscribeOne(info.hass.connection, subscribeAreaRegistry),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const areaViews = areaEntries.map((area) => ({
|
||||||
|
strategy: {
|
||||||
|
type: "area",
|
||||||
|
options: { area_id: area.area_id },
|
||||||
|
},
|
||||||
|
title: area.name,
|
||||||
|
}));
|
||||||
|
return {
|
||||||
|
title: info.hass.config.location_name,
|
||||||
|
views: [
|
||||||
|
{
|
||||||
|
strategy: { type: "area-overview" },
|
||||||
|
title: "Overview",
|
||||||
|
},
|
||||||
|
...areaViews,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
100
src/panels/lovelace/strategies/area-strategy.ts
Normal file
100
src/panels/lovelace/strategies/area-strategy.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import { STATE_NOT_RUNNING } from "home-assistant-js-websocket";
|
||||||
|
import { subscribeOne } from "../../../common/util/subscribe-one";
|
||||||
|
import {
|
||||||
|
AreaRegistryEntry,
|
||||||
|
subscribeAreaRegistry,
|
||||||
|
} from "../../../data/area_registry";
|
||||||
|
import {
|
||||||
|
DeviceRegistryEntry,
|
||||||
|
subscribeDeviceRegistry,
|
||||||
|
} from "../../../data/device_registry";
|
||||||
|
import { subscribeEntityRegistry } from "../../../data/entity_registry";
|
||||||
|
import { generateAreaViewConfig } from "../common/generate-lovelace-config";
|
||||||
|
import {
|
||||||
|
LovelaceDashboardStrategy,
|
||||||
|
LovelaceViewStrategy,
|
||||||
|
} from "./get-strategy";
|
||||||
|
|
||||||
|
let subscribedRegistries = false;
|
||||||
|
|
||||||
|
export class AreaStrategy {
|
||||||
|
static async generateView(
|
||||||
|
info: Parameters<LovelaceViewStrategy["generateView"]>[0]
|
||||||
|
): ReturnType<LovelaceViewStrategy["generateView"]> {
|
||||||
|
const hass = info.hass;
|
||||||
|
const areaId = info.view.strategy?.options?.area_id;
|
||||||
|
|
||||||
|
if (hass.config.state === STATE_NOT_RUNNING) {
|
||||||
|
return {
|
||||||
|
cards: [{ type: "starting" }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hass.config.safe_mode) {
|
||||||
|
return {
|
||||||
|
cards: [{ type: "safe-mode" }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let areaEntry: AreaRegistryEntry | undefined;
|
||||||
|
let deviceEntries: DeviceRegistryEntry[] | undefined;
|
||||||
|
|
||||||
|
// We leave this here so we always have the freshest data.
|
||||||
|
if (!subscribedRegistries) {
|
||||||
|
subscribedRegistries = true;
|
||||||
|
subscribeAreaRegistry(hass.connection, (areas) => {
|
||||||
|
areaEntry = areas.find((area) => area.area_id === areaId);
|
||||||
|
});
|
||||||
|
subscribeDeviceRegistry(hass.connection, (devices) => {
|
||||||
|
deviceEntries = devices.filter((device) => device.area_id === areaId);
|
||||||
|
});
|
||||||
|
subscribeEntityRegistry(hass.connection, () => undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||||
|
const [localize, entityEntries] = await Promise.all([
|
||||||
|
hass.loadBackendTranslation("title"),
|
||||||
|
subscribeOne(hass.connection, subscribeEntityRegistry),
|
||||||
|
subscribeOne(hass.connection, subscribeAreaRegistry),
|
||||||
|
subscribeOne(hass.connection, subscribeDeviceRegistry),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!areaEntry) {
|
||||||
|
return {
|
||||||
|
cards: [{ type: "error" }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// User can override default view. If they didn't, we will add one
|
||||||
|
// that contains all entities.
|
||||||
|
const view = generateAreaViewConfig(
|
||||||
|
areaEntry,
|
||||||
|
deviceEntries || [],
|
||||||
|
entityEntries,
|
||||||
|
hass.states,
|
||||||
|
localize
|
||||||
|
);
|
||||||
|
|
||||||
|
// User has no entities
|
||||||
|
if (view.cards!.length === 0) {
|
||||||
|
view.cards!.push({
|
||||||
|
type: "empty-state",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async generateDashboard(
|
||||||
|
info: Parameters<LovelaceDashboardStrategy["generateDashboard"]>[0]
|
||||||
|
): ReturnType<LovelaceDashboardStrategy["generateDashboard"]> {
|
||||||
|
return {
|
||||||
|
title: info.hass.config.location_name,
|
||||||
|
views: [
|
||||||
|
{
|
||||||
|
strategy: { type: "area" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,9 @@ const strategies: Record<
|
|||||||
(await import("./original-states-strategy")).OriginalStatesStrategy,
|
(await import("./original-states-strategy")).OriginalStatesStrategy,
|
||||||
energy: async () =>
|
energy: async () =>
|
||||||
(await import("../../energy/strategies/energy-strategy")).EnergyStrategy,
|
(await import("../../energy/strategies/energy-strategy")).EnergyStrategy,
|
||||||
|
"area-overview": async () =>
|
||||||
|
(await import("./area-overview-strategy")).AreaOverviewStrategy,
|
||||||
|
area: async () => (await import("./area-strategy")).AreaStrategy,
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLovelaceStrategy = async <
|
const getLovelaceStrategy = async <
|
||||||
|
|||||||
Reference in New Issue
Block a user