mirror of
https://github.com/home-assistant/frontend.git
synced 2026-10-14 17:43:16 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49c22a97a1 |
No files matched your search
@@ -0,0 +1,40 @@
|
||||
import { ASSIST_ENTITIES, SENSOR_ENTITIES } from "../../common/const";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import type { EntityCategory } from "../entity/entity_registry";
|
||||
|
||||
export const DEVICE_ENTITY_CATEGORIES = [
|
||||
"control",
|
||||
"sensor",
|
||||
"notify",
|
||||
"event",
|
||||
"assist",
|
||||
"config",
|
||||
"diagnostic",
|
||||
] as const;
|
||||
|
||||
export type DeviceEntityCategory = (typeof DEVICE_ENTITY_CATEGORIES)[number];
|
||||
|
||||
export const computeDeviceEntityCategory = (entry: {
|
||||
entity_id: string;
|
||||
entity_category?: EntityCategory | null;
|
||||
}): DeviceEntityCategory => {
|
||||
const domain = computeDomain(entry.entity_id);
|
||||
|
||||
if (ASSIST_ENTITIES.includes(domain)) {
|
||||
return "assist";
|
||||
}
|
||||
|
||||
if (domain === "event" || domain === "notify") {
|
||||
return domain;
|
||||
}
|
||||
|
||||
if (entry.entity_category) {
|
||||
return entry.entity_category;
|
||||
}
|
||||
|
||||
if (SENSOR_ENTITIES.includes(domain)) {
|
||||
return "sensor";
|
||||
}
|
||||
|
||||
return "control";
|
||||
};
|
||||
@@ -10,7 +10,7 @@ import type { LightColor } from "../light";
|
||||
import type { NextNamePart, RegistryEntry } from "../registry";
|
||||
import type { Segment } from "../vacuum";
|
||||
|
||||
type EntityCategory = "config" | "diagnostic";
|
||||
export type EntityCategory = "config" | "diagnostic";
|
||||
|
||||
export interface EntityRegistryDisplayEntry {
|
||||
entity_id: string;
|
||||
|
||||
@@ -25,7 +25,6 @@ import { ifDefined } from "lit/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { consume } from "../../../common/decorators/consume";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { ASSIST_ENTITIES, SENSOR_ENTITIES } from "../../../common/const";
|
||||
import type { HASSDomCurrentTargetEvent } from "../../../common/dom/fire_event";
|
||||
import { computeDeviceNameDisplay } from "../../../common/entity/compute_device_name";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
@@ -65,6 +64,11 @@ import {
|
||||
sortConfigEntries,
|
||||
} from "../../../data/config_entries";
|
||||
import { fireRelatedContext, fullEntitiesContext } from "../../../data/context";
|
||||
import {
|
||||
DEVICE_ENTITY_CATEGORIES,
|
||||
computeDeviceEntityCategory,
|
||||
type DeviceEntityCategory,
|
||||
} from "../../../data/device/device_entity_category";
|
||||
import type { DeviceRegistryEntry } from "../../../data/device/device_registry";
|
||||
import {
|
||||
removeDeviceFromRegistry,
|
||||
@@ -305,44 +309,11 @@ export class HaConfigDevicePage extends LitElement {
|
||||
|
||||
private _entitiesByCategory = memoizeOne(
|
||||
(entities: EntityRegistryEntry[]) => {
|
||||
const result = groupBy(entities, (entry) => {
|
||||
const domain = computeDomain(entry.entity_id);
|
||||
|
||||
if (ASSIST_ENTITIES.includes(domain)) {
|
||||
return "assist";
|
||||
}
|
||||
|
||||
if (domain === "event" || domain === "notify") {
|
||||
return domain;
|
||||
}
|
||||
|
||||
if (entry.entity_category) {
|
||||
return entry.entity_category;
|
||||
}
|
||||
|
||||
if (SENSOR_ENTITIES.includes(domain)) {
|
||||
return "sensor";
|
||||
}
|
||||
|
||||
return "control";
|
||||
}) as Record<
|
||||
| "control"
|
||||
| "event"
|
||||
| "sensor"
|
||||
| "assist"
|
||||
| "notify"
|
||||
| NonNullable<EntityRegistryEntry["entity_category"]>,
|
||||
const result = groupBy(entities, computeDeviceEntityCategory) as Record<
|
||||
DeviceEntityCategory,
|
||||
EntityRegistryStateEntry[]
|
||||
>;
|
||||
for (const key of [
|
||||
"assist",
|
||||
"config",
|
||||
"control",
|
||||
"diagnostic",
|
||||
"event",
|
||||
"notify",
|
||||
"sensor",
|
||||
]) {
|
||||
for (const key of DEVICE_ENTITY_CATEGORIES) {
|
||||
if (!(key in result)) {
|
||||
result[key] = [];
|
||||
}
|
||||
@@ -909,17 +880,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
`;
|
||||
|
||||
const entitiesColumn = html`
|
||||
${(
|
||||
[
|
||||
"control",
|
||||
"sensor",
|
||||
"notify",
|
||||
"event",
|
||||
"assist",
|
||||
"config",
|
||||
"diagnostic",
|
||||
] as const
|
||||
).map((category) =>
|
||||
${DEVICE_ENTITY_CATEGORIES.map((category) =>
|
||||
// Make sure we render controls if no other cards will be rendered
|
||||
entitiesByCategory[category].length > 0 ||
|
||||
(entities.length === 0 && category === "control")
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { STRINGS_SEPARATOR_DOT } from "../../../../common/const";
|
||||
import { computeAreaName } from "../../../../common/entity/compute_area_name";
|
||||
import { computeDeviceName } from "../../../../common/entity/compute_device_name";
|
||||
import { computeEntityEntryName } from "../../../../common/entity/compute_entity_name_display";
|
||||
import { stringCompare } from "../../../../common/string/compare";
|
||||
import {
|
||||
computeDeviceEntityCategory,
|
||||
type DeviceEntityCategory,
|
||||
} from "../../../../data/device/device_entity_category";
|
||||
import type { DeviceRegistryEntry } from "../../../../data/device/device_registry";
|
||||
import { findBatteryEntity } from "../../../../data/entity/entity_registry";
|
||||
import type { LovelaceSectionConfig } from "../../../../data/lovelace/config/section";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type {
|
||||
EntitiesCardConfig,
|
||||
HeadingCardConfig,
|
||||
MarkdownCardConfig,
|
||||
TileCardConfig,
|
||||
} from "../../cards/types";
|
||||
import type { EntityConfig, SectionConfig } from "../../entity-rows/types";
|
||||
import type {
|
||||
LovelaceStrategyDependency,
|
||||
LovelaceStrategyEditor,
|
||||
} from "../types";
|
||||
|
||||
export const DEVICE_SECTION_CATEGORIES = [
|
||||
"control",
|
||||
"sensor",
|
||||
"event",
|
||||
"config",
|
||||
"diagnostic",
|
||||
] as const satisfies readonly DeviceEntityCategory[];
|
||||
|
||||
export type DeviceSectionCategory = (typeof DEVICE_SECTION_CATEGORIES)[number];
|
||||
|
||||
export const DEFAULT_DEVICE_SECTION_CATEGORIES: DeviceSectionCategory[] = [
|
||||
"control",
|
||||
"sensor",
|
||||
];
|
||||
|
||||
export const DEVICE_SECTION_HEADINGS = ["none", "device", "area"] as const;
|
||||
|
||||
export type DeviceSectionHeading = (typeof DEVICE_SECTION_HEADINGS)[number];
|
||||
|
||||
export interface DeviceSectionStrategyConfig {
|
||||
type: "device";
|
||||
device_id?: string;
|
||||
categories?: DeviceSectionCategory[];
|
||||
display?: "tiles" | "list";
|
||||
group_by_category?: boolean;
|
||||
heading?: DeviceSectionHeading;
|
||||
exclude_entities?: string[];
|
||||
}
|
||||
|
||||
const computeTileConfig = (entityId: string): TileCardConfig => ({
|
||||
type: "tile",
|
||||
entity: entityId,
|
||||
name: { type: "entity" },
|
||||
});
|
||||
|
||||
const computeRowConfig = (entityId: string): EntityConfig => ({
|
||||
entity: entityId,
|
||||
name: { type: "entity" },
|
||||
});
|
||||
|
||||
const computeDeviceSectionHeading = (
|
||||
device: DeviceRegistryEntry,
|
||||
heading: Exclude<DeviceSectionHeading, "none">,
|
||||
devices: HomeAssistant["devices"],
|
||||
areas: HomeAssistant["areas"],
|
||||
localize: HomeAssistant["localize"]
|
||||
): string => {
|
||||
const area =
|
||||
heading === "area" && device.area_id ? areas[device.area_id] : undefined;
|
||||
const parentDevice =
|
||||
device.next_name_part === "parent_device" && device.parent_device_id
|
||||
? devices[device.parent_device_id]
|
||||
: undefined;
|
||||
|
||||
const parts = [
|
||||
area ? computeAreaName(area) : undefined,
|
||||
parentDevice ? computeDeviceName(parentDevice) : undefined,
|
||||
computeDeviceName(device) ||
|
||||
localize("ui.components.device-picker.unnamed_device"),
|
||||
].filter(Boolean);
|
||||
|
||||
return parts.join(STRINGS_SEPARATOR_DOT);
|
||||
};
|
||||
|
||||
@customElement("device-section-strategy")
|
||||
export class DeviceSectionStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [
|
||||
"entities",
|
||||
"devices",
|
||||
"areas",
|
||||
];
|
||||
|
||||
public static async getConfigElement(): Promise<LovelaceStrategyEditor> {
|
||||
await import("./hui-device-section-strategy-editor");
|
||||
return document.createElement("hui-device-section-strategy-editor");
|
||||
}
|
||||
|
||||
static async generate(
|
||||
config: DeviceSectionStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
): Promise<LovelaceSectionConfig> {
|
||||
const section: LovelaceSectionConfig = {
|
||||
type: "grid",
|
||||
cards: [],
|
||||
};
|
||||
|
||||
const device = config.device_id
|
||||
? hass.devices[config.device_id]
|
||||
: undefined;
|
||||
|
||||
if (!device) {
|
||||
section.cards!.push({
|
||||
type: "markdown",
|
||||
content: hass.localize(
|
||||
config.device_id
|
||||
? "ui.panel.lovelace.strategy.device.device_not_found"
|
||||
: "ui.panel.lovelace.strategy.device.no_device"
|
||||
),
|
||||
} satisfies MarkdownCardConfig);
|
||||
return section;
|
||||
}
|
||||
|
||||
const heading = config.heading ?? "device";
|
||||
|
||||
const categories: readonly DeviceEntityCategory[] =
|
||||
config.categories ?? DEFAULT_DEVICE_SECTION_CATEGORIES;
|
||||
|
||||
const entries = Object.values(hass.entities)
|
||||
.filter(
|
||||
(entry) =>
|
||||
entry.device_id === device.id &&
|
||||
!entry.hidden &&
|
||||
entry.entity_id in hass.states &&
|
||||
!config.exclude_entities?.includes(entry.entity_id)
|
||||
)
|
||||
.sort((entry1, entry2) => {
|
||||
const name1 = computeEntityEntryName(entry1);
|
||||
const name2 = computeEntityEntryName(entry2);
|
||||
if (!name1 || !name2) {
|
||||
return Number(!!name1) - Number(!!name2);
|
||||
}
|
||||
return stringCompare(name1, name2, hass.locale.language);
|
||||
});
|
||||
|
||||
const batteryEntry =
|
||||
heading !== "none" ? findBatteryEntity(hass.states, entries) : undefined;
|
||||
|
||||
if (heading !== "none") {
|
||||
section.cards!.push({
|
||||
type: "heading",
|
||||
heading: computeDeviceSectionHeading(
|
||||
device,
|
||||
heading,
|
||||
hass.devices,
|
||||
hass.areas,
|
||||
hass.localize
|
||||
),
|
||||
tap_action: hass.user?.is_admin
|
||||
? {
|
||||
action: "navigate",
|
||||
navigation_path: `/config/devices/device/${device.id}`,
|
||||
}
|
||||
: { action: "none" },
|
||||
badges: batteryEntry
|
||||
? [
|
||||
{
|
||||
type: "entity",
|
||||
entity: batteryEntry.entity_id,
|
||||
tap_action: { action: "more-info" },
|
||||
},
|
||||
]
|
||||
: [],
|
||||
} satisfies HeadingCardConfig);
|
||||
}
|
||||
|
||||
const entitiesByCategory = new Map<DeviceEntityCategory, string[]>();
|
||||
for (const entry of entries) {
|
||||
if (entry === batteryEntry) continue;
|
||||
const category = computeDeviceEntityCategory(entry);
|
||||
if (!categories.includes(category)) continue;
|
||||
if (!entitiesByCategory.has(category)) {
|
||||
entitiesByCategory.set(category, []);
|
||||
}
|
||||
entitiesByCategory.get(category)!.push(entry.entity_id);
|
||||
}
|
||||
|
||||
const shownCategories = DEVICE_SECTION_CATEGORIES.filter((category) =>
|
||||
entitiesByCategory.has(category)
|
||||
);
|
||||
const showCategoryNames =
|
||||
config.group_by_category && shownCategories.length > 1;
|
||||
const computeCategoryName = (category: DeviceSectionCategory) =>
|
||||
hass.localize(`ui.panel.lovelace.strategy.device.categories.${category}`);
|
||||
|
||||
if (config.display === "list") {
|
||||
if (shownCategories.length === 0) {
|
||||
return section;
|
||||
}
|
||||
section.cards!.push({
|
||||
type: "entities",
|
||||
entities: shownCategories.flatMap((category) => [
|
||||
...(showCategoryNames
|
||||
? [
|
||||
{
|
||||
type: "section",
|
||||
label: computeCategoryName(category),
|
||||
} satisfies SectionConfig,
|
||||
]
|
||||
: []),
|
||||
...entitiesByCategory.get(category)!.map(computeRowConfig),
|
||||
]),
|
||||
} satisfies EntitiesCardConfig);
|
||||
return section;
|
||||
}
|
||||
|
||||
for (const category of shownCategories) {
|
||||
if (showCategoryNames) {
|
||||
section.cards!.push({
|
||||
type: "heading",
|
||||
heading_style: "subtitle",
|
||||
heading: computeCategoryName(category),
|
||||
} satisfies HeadingCardConfig);
|
||||
}
|
||||
section.cards!.push(
|
||||
...entitiesByCategory.get(category)!.map(computeTileConfig)
|
||||
);
|
||||
}
|
||||
|
||||
return section;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"device-section-strategy": DeviceSectionStrategy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
import { mdiFormatListBulleted } from "@mdi/js";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { consumeLocalize } from "../../../../common/decorators/consume-context-entry";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import type {
|
||||
HaFormSchema,
|
||||
SchemaUnion,
|
||||
} from "../../../../components/ha-form/types";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../../../types";
|
||||
import type { LovelaceStrategyEditor } from "../types";
|
||||
import {
|
||||
DEFAULT_DEVICE_SECTION_CATEGORIES,
|
||||
DEVICE_SECTION_CATEGORIES,
|
||||
DEVICE_SECTION_HEADINGS,
|
||||
type DeviceSectionStrategyConfig,
|
||||
} from "./device-section-strategy";
|
||||
|
||||
@customElement("hui-device-section-strategy-editor")
|
||||
export class HuiDeviceSectionStrategyEditor
|
||||
extends LitElement
|
||||
implements LovelaceStrategyEditor
|
||||
{
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@state() @consumeLocalize() private _localize!: LocalizeFunc;
|
||||
|
||||
@state() private _config?: DeviceSectionStrategyConfig;
|
||||
|
||||
public setConfig(config: DeviceSectionStrategyConfig): void {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
private _deviceEntities = memoizeOne(
|
||||
(deviceId: string | undefined, entities: HomeAssistant["entities"]) =>
|
||||
deviceId
|
||||
? Object.values(entities)
|
||||
.filter((entry) => entry.device_id === deviceId && !entry.hidden)
|
||||
.map((entry) => entry.entity_id)
|
||||
: []
|
||||
);
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(localize: LocalizeFunc, deviceEntities: string[]) =>
|
||||
[
|
||||
{
|
||||
name: "device_id",
|
||||
required: true,
|
||||
selector: { device: {} },
|
||||
},
|
||||
{
|
||||
name: "heading",
|
||||
selector: {
|
||||
select: {
|
||||
mode: "dropdown",
|
||||
options: DEVICE_SECTION_HEADINGS.map((heading) => ({
|
||||
value: heading,
|
||||
label: localize(
|
||||
`ui.panel.lovelace.editor.strategy.device.heading_options.${heading}`
|
||||
),
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "categories",
|
||||
selector: {
|
||||
select: {
|
||||
multiple: true,
|
||||
mode: "list",
|
||||
options: DEVICE_SECTION_CATEGORIES.map((category) => ({
|
||||
value: category,
|
||||
label: localize(
|
||||
`ui.panel.lovelace.strategy.device.categories.${category}`
|
||||
),
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "display",
|
||||
selector: {
|
||||
select: {
|
||||
mode: "box",
|
||||
options: (["tiles", "list"] as const).map((display) => ({
|
||||
value: display,
|
||||
label: localize(
|
||||
`ui.panel.lovelace.editor.strategy.device.display_options.${display}`
|
||||
),
|
||||
description: localize(
|
||||
`ui.panel.lovelace.editor.strategy.device.display_options.${display}_description`
|
||||
),
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "group_by_category",
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
...(deviceEntities.length
|
||||
? ([
|
||||
{
|
||||
name: "entities",
|
||||
type: "expandable",
|
||||
flatten: true,
|
||||
iconPath: mdiFormatListBulleted,
|
||||
schema: [
|
||||
{
|
||||
name: "exclude_entities",
|
||||
selector: {
|
||||
entity: {
|
||||
multiple: true,
|
||||
include_entities: deviceEntities,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
] as const)
|
||||
: []),
|
||||
] as const satisfies readonly HaFormSchema[]
|
||||
);
|
||||
|
||||
protected render() {
|
||||
if (!this._config) return nothing;
|
||||
|
||||
const data = {
|
||||
categories: DEFAULT_DEVICE_SECTION_CATEGORIES,
|
||||
display: "tiles",
|
||||
heading: "device",
|
||||
...this._config,
|
||||
};
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${data}
|
||||
.schema=${this._schema(
|
||||
this._localize,
|
||||
this._deviceEntities(
|
||||
this._config.device_id,
|
||||
this.hass?.entities ?? {}
|
||||
)
|
||||
)}
|
||||
.computeLabel=${this._computeLabel}
|
||||
.computeHelper=${this._computeHelper}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
`;
|
||||
}
|
||||
|
||||
private _computeLabel = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
) =>
|
||||
this._localize(`ui.panel.lovelace.editor.strategy.device.${schema.name}`);
|
||||
|
||||
private _computeHelper = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
) =>
|
||||
schema.name === "group_by_category" ||
|
||||
schema.name === "heading" ||
|
||||
schema.name === "exclude_entities"
|
||||
? this._localize(
|
||||
`ui.panel.lovelace.editor.strategy.device.${schema.name}_helper`
|
||||
)
|
||||
: "";
|
||||
|
||||
private _valueChanged(ev: ValueChangedEvent<DeviceSectionStrategyConfig>) {
|
||||
ev.stopPropagation();
|
||||
const config = { ...this._config, ...ev.detail.value };
|
||||
if (config.device_id !== this._config?.device_id) {
|
||||
delete config.exclude_entities;
|
||||
}
|
||||
fireEvent(this, "config-changed", { config });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-device-section-strategy-editor": HuiDeviceSectionStrategyEditor;
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,7 @@ const STRATEGIES: Record<LovelaceStrategyConfigType, Record<string, any>> = {
|
||||
section: {
|
||||
"common-controls": () =>
|
||||
import("./usage_prediction/common-controls-section-strategy"),
|
||||
device: () => import("./device/device-section-strategy"),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -9563,6 +9563,17 @@
|
||||
"not_loaded": "Usage Prediction integration is not loaded.",
|
||||
"no_data": "This place will soon fill up with the entities you use most often, based on your activity."
|
||||
},
|
||||
"device": {
|
||||
"no_device": "Select a device to show its entities.",
|
||||
"device_not_found": "This device no longer exists.",
|
||||
"categories": {
|
||||
"control": "[%key:ui::panel::config::devices::entities::control%]",
|
||||
"sensor": "[%key:ui::panel::config::devices::entities::sensor%]",
|
||||
"event": "[%key:ui::panel::config::devices::entities::event%]",
|
||||
"config": "[%key:ui::panel::config::devices::entities::config%]",
|
||||
"diagnostic": "[%key:ui::panel::config::devices::entities::diagnostic%]"
|
||||
}
|
||||
},
|
||||
"light": {
|
||||
"lights": "Lights",
|
||||
"other_lights": "Other lights",
|
||||
@@ -11556,6 +11567,29 @@
|
||||
"exclude_entities_helper": "These entities will not be suggested based on your activity",
|
||||
"hide_empty": "Hide when empty"
|
||||
},
|
||||
"device": {
|
||||
"device_id": "Device",
|
||||
"categories": "Categories",
|
||||
"display": "Display",
|
||||
"display_options": {
|
||||
"tiles": "Tiles",
|
||||
"tiles_description": "One card per entity",
|
||||
"list": "List",
|
||||
"list_description": "All entities in a single card"
|
||||
},
|
||||
"group_by_category": "Group by category",
|
||||
"group_by_category_helper": "Adds a subtitle above each category",
|
||||
"heading": "Heading",
|
||||
"heading_helper": "When shown, the heading includes the battery level",
|
||||
"heading_options": {
|
||||
"none": "No heading",
|
||||
"device": "Device name",
|
||||
"area": "Area and device name"
|
||||
},
|
||||
"entities": "Entities",
|
||||
"exclude_entities": "Exclude entities",
|
||||
"exclude_entities_helper": "Not shown in this section, even if their category is selected"
|
||||
},
|
||||
"original-states": {
|
||||
"areas": "Areas to display",
|
||||
"hide_entities_without_area": "Hide entities without area",
|
||||
|
||||
Reference in new issue
Block a user