mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-01 13:37:47 +00:00
Selectors and stuff
This commit is contained in:
parent
a6abc88007
commit
8837eede9a
@ -74,6 +74,7 @@ export class HaDemo extends HomeAssistantAppEl {
|
|||||||
has_entity_name: false,
|
has_entity_name: false,
|
||||||
unique_id: "co2_intensity",
|
unique_id: "co2_intensity",
|
||||||
options: null,
|
options: null,
|
||||||
|
labels: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
config_entry_id: "co2signal",
|
config_entry_id: "co2signal",
|
||||||
@ -90,6 +91,7 @@ export class HaDemo extends HomeAssistantAppEl {
|
|||||||
has_entity_name: false,
|
has_entity_name: false,
|
||||||
unique_id: "grid_fossil_fuel_percentage",
|
unique_id: "grid_fossil_fuel_percentage",
|
||||||
options: null,
|
options: null,
|
||||||
|
labels: [],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
7
demo/src/stubs/label_registry.ts
Normal file
7
demo/src/stubs/label_registry.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { LabelRegistryEntry } from "../../../src/data/label_registry";
|
||||||
|
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
|
|
||||||
|
export const mockLabelRegistry = (
|
||||||
|
hass: MockHomeAssistant,
|
||||||
|
data: LabelRegistryEntry[] = []
|
||||||
|
) => hass.mockWS("config/label_registry/list", () => data);
|
@ -7,6 +7,7 @@ import "../../components/demo-black-white-row";
|
|||||||
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
||||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
||||||
|
import { mockLabelRegistry } from "../../../../demo/src/stubs/label_registry";
|
||||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||||
import "../../../../src/panels/config/automation/action/ha-automation-action";
|
import "../../../../src/panels/config/automation/action/ha-automation-action";
|
||||||
import { HaChooseAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-choose";
|
import { HaChooseAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-choose";
|
||||||
@ -59,6 +60,7 @@ class DemoHaAutomationEditorAction extends LitElement {
|
|||||||
mockEntityRegistry(hass);
|
mockEntityRegistry(hass);
|
||||||
mockDeviceRegistry(hass);
|
mockDeviceRegistry(hass);
|
||||||
mockAreaRegistry(hass);
|
mockAreaRegistry(hass);
|
||||||
|
mockLabelRegistry(hass);
|
||||||
mockHassioSupervisor(hass);
|
mockHassioSupervisor(hass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import "../../components/demo-black-white-row";
|
|||||||
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
||||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
||||||
|
import { mockLabelRegistry } from "../../../../demo/src/stubs/label_registry";
|
||||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||||
import type { ConditionWithShorthand } from "../../../../src/data/automation";
|
import type { ConditionWithShorthand } from "../../../../src/data/automation";
|
||||||
import "../../../../src/panels/config/automation/condition/ha-automation-condition";
|
import "../../../../src/panels/config/automation/condition/ha-automation-condition";
|
||||||
@ -95,6 +96,7 @@ class DemoHaAutomationEditorCondition extends LitElement {
|
|||||||
mockEntityRegistry(hass);
|
mockEntityRegistry(hass);
|
||||||
mockDeviceRegistry(hass);
|
mockDeviceRegistry(hass);
|
||||||
mockAreaRegistry(hass);
|
mockAreaRegistry(hass);
|
||||||
|
mockLabelRegistry(hass);
|
||||||
mockHassioSupervisor(hass);
|
mockHassioSupervisor(hass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import "../../components/demo-black-white-row";
|
|||||||
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
||||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
||||||
|
import { mockLabelRegistry } from "../../../../demo/src/stubs/label_registry";
|
||||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||||
import type { Trigger } from "../../../../src/data/automation";
|
import type { Trigger } from "../../../../src/data/automation";
|
||||||
import { HaGeolocationTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-geo_location";
|
import { HaGeolocationTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-geo_location";
|
||||||
@ -141,6 +142,7 @@ class DemoHaAutomationEditorTrigger extends LitElement {
|
|||||||
mockEntityRegistry(hass);
|
mockEntityRegistry(hass);
|
||||||
mockDeviceRegistry(hass);
|
mockDeviceRegistry(hass);
|
||||||
mockAreaRegistry(hass);
|
mockAreaRegistry(hass);
|
||||||
|
mockLabelRegistry(hass);
|
||||||
mockHassioSupervisor(hass);
|
mockHassioSupervisor(hass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
|||||||
import { mockConfigEntries } from "../../../../demo/src/stubs/config_entries";
|
import { mockConfigEntries } from "../../../../demo/src/stubs/config_entries";
|
||||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||||
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
||||||
|
import { mockLabelRegistry } from "../../../../demo/src/stubs/label_registry";
|
||||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||||
import { computeInitialHaFormData } from "../../../../src/components/ha-form/compute-initial-ha-form-data";
|
import { computeInitialHaFormData } from "../../../../src/components/ha-form/compute-initial-ha-form-data";
|
||||||
import "../../../../src/components/ha-form/ha-form";
|
import "../../../../src/components/ha-form/ha-form";
|
||||||
@ -58,6 +59,7 @@ const DEVICES = [
|
|||||||
hw_version: null,
|
hw_version: null,
|
||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
serial_number: null,
|
serial_number: null,
|
||||||
|
labels: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: "backyard",
|
area_id: "backyard",
|
||||||
@ -76,6 +78,7 @@ const DEVICES = [
|
|||||||
hw_version: null,
|
hw_version: null,
|
||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
serial_number: null,
|
serial_number: null,
|
||||||
|
labels: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: null,
|
area_id: null,
|
||||||
@ -94,6 +97,7 @@ const DEVICES = [
|
|||||||
hw_version: null,
|
hw_version: null,
|
||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
serial_number: null,
|
serial_number: null,
|
||||||
|
labels: [],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -118,6 +122,30 @@ const AREAS = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const LABELS = [
|
||||||
|
{
|
||||||
|
label_id: "romantic",
|
||||||
|
name: "Romantic",
|
||||||
|
icon: "mdi:heart",
|
||||||
|
color: "#ff0000",
|
||||||
|
description: "Lights that can create a romantic atmosphere",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label_id: "away",
|
||||||
|
name: "Away",
|
||||||
|
icon: "mdi:home-export-outline",
|
||||||
|
color: "#cccccc",
|
||||||
|
description: "All that can all be turned off when away from home",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label_id: "cleaning",
|
||||||
|
name: "Cleaning",
|
||||||
|
icon: "mdi:home-export-outline",
|
||||||
|
color: "#cccccc",
|
||||||
|
description: "Everything to turn on while cleaning the house",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const SCHEMAS: {
|
const SCHEMAS: {
|
||||||
title: string;
|
title: string;
|
||||||
translations?: Record<string, string>;
|
translations?: Record<string, string>;
|
||||||
@ -132,6 +160,7 @@ const SCHEMAS: {
|
|||||||
entity: "Entity",
|
entity: "Entity",
|
||||||
device: "Device",
|
device: "Device",
|
||||||
area: "Area",
|
area: "Area",
|
||||||
|
label: "Label",
|
||||||
target: "Target",
|
target: "Target",
|
||||||
number: "Number",
|
number: "Number",
|
||||||
boolean: "Boolean",
|
boolean: "Boolean",
|
||||||
@ -163,6 +192,7 @@ const SCHEMAS: {
|
|||||||
{ name: "Config entry", selector: { config_entry: {} } },
|
{ name: "Config entry", selector: { config_entry: {} } },
|
||||||
{ name: "Duration", selector: { duration: {} } },
|
{ name: "Duration", selector: { duration: {} } },
|
||||||
{ name: "area", selector: { area: {} } },
|
{ name: "area", selector: { area: {} } },
|
||||||
|
{ name: "label", selector: { label: {} } },
|
||||||
{ name: "target", selector: { target: {} } },
|
{ name: "target", selector: { target: {} } },
|
||||||
{ name: "number", selector: { number: { min: 0, max: 10 } } },
|
{ name: "number", selector: { number: { min: 0, max: 10 } } },
|
||||||
{ name: "boolean", selector: { boolean: {} } },
|
{ name: "boolean", selector: { boolean: {} } },
|
||||||
@ -444,6 +474,7 @@ class DemoHaForm extends LitElement {
|
|||||||
mockDeviceRegistry(hass, DEVICES);
|
mockDeviceRegistry(hass, DEVICES);
|
||||||
mockConfigEntries(hass);
|
mockConfigEntries(hass);
|
||||||
mockAreaRegistry(hass, AREAS);
|
mockAreaRegistry(hass, AREAS);
|
||||||
|
mockLabelRegistry(hass, LABELS);
|
||||||
mockHassioSupervisor(hass);
|
mockHassioSupervisor(hass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import { mockConfigEntries } from "../../../../demo/src/stubs/config_entries";
|
|||||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||||
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
||||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||||
|
import { mockLabelRegistry } from "../../../../demo/src/stubs/label_registry";
|
||||||
import "../../../../src/components/ha-selector/ha-selector";
|
import "../../../../src/components/ha-selector/ha-selector";
|
||||||
import "../../../../src/components/ha-settings-row";
|
import "../../../../src/components/ha-settings-row";
|
||||||
import { BlueprintInput } from "../../../../src/data/blueprint";
|
import { BlueprintInput } from "../../../../src/data/blueprint";
|
||||||
@ -54,6 +55,7 @@ const DEVICES = [
|
|||||||
hw_version: null,
|
hw_version: null,
|
||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
serial_number: null,
|
serial_number: null,
|
||||||
|
labels: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: "backyard",
|
area_id: "backyard",
|
||||||
@ -72,6 +74,7 @@ const DEVICES = [
|
|||||||
hw_version: null,
|
hw_version: null,
|
||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
serial_number: null,
|
serial_number: null,
|
||||||
|
labels: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: null,
|
area_id: null,
|
||||||
@ -90,9 +93,7 @@ const DEVICES = [
|
|||||||
hw_version: null,
|
hw_version: null,
|
||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
serial_number: null,
|
serial_number: null,
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const AREAS = [
|
const AREAS = [
|
||||||
{
|
{
|
||||||
area_id: "backyard",
|
area_id: "backyard",
|
||||||
@ -114,6 +115,30 @@ const AREAS = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const LABELS = [
|
||||||
|
{
|
||||||
|
label_id: "romantic",
|
||||||
|
name: "Romantic",
|
||||||
|
icon: "mdi:heart",
|
||||||
|
color: "#ff0000",
|
||||||
|
description: "Lights that can create a romantic atmosphere",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label_id: "away",
|
||||||
|
name: "Away",
|
||||||
|
icon: "mdi:home-export-outline",
|
||||||
|
color: "#cccccc",
|
||||||
|
description: "All that can all be turned off when away from home",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label_id: "cleaning",
|
||||||
|
name: "Cleaning",
|
||||||
|
icon: "mdi:home-export-outline",
|
||||||
|
color: "#cccccc",
|
||||||
|
description: "Everything to turn on while cleaning the house",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const SCHEMAS: {
|
const SCHEMAS: {
|
||||||
name: string;
|
name: string;
|
||||||
input: Record<string, (BlueprintInput & { required?: boolean }) | null>;
|
input: Record<string, (BlueprintInput & { required?: boolean }) | null>;
|
||||||
@ -138,6 +163,7 @@ const SCHEMAS: {
|
|||||||
duration: { name: "Duration", selector: { duration: {} } },
|
duration: { name: "Duration", selector: { duration: {} } },
|
||||||
addon: { name: "Addon", selector: { addon: {} } },
|
addon: { name: "Addon", selector: { addon: {} } },
|
||||||
area: { name: "Area", selector: { area: {} } },
|
area: { name: "Area", selector: { area: {} } },
|
||||||
|
label: { name: "Label", selector: { label: {} } },
|
||||||
target: { name: "Target", selector: { target: {} } },
|
target: { name: "Target", selector: { target: {} } },
|
||||||
number_box: {
|
number_box: {
|
||||||
name: "Number Box",
|
name: "Number Box",
|
||||||
@ -161,8 +187,8 @@ const SCHEMAS: {
|
|||||||
},
|
},
|
||||||
boolean: { name: "Boolean", selector: { boolean: {} } },
|
boolean: { name: "Boolean", selector: { boolean: {} } },
|
||||||
time: { name: "Time", selector: { time: {} } },
|
time: { name: "Time", selector: { time: {} } },
|
||||||
date: { name: "Date", selector: { date: {} } },
|
// date: { name: "Date", selector: { date: {} } },
|
||||||
datetime: { name: "Date Time", selector: { datetime: {} } },
|
// datetime: { name: "Date Time", selector: { datetime: {} } },
|
||||||
action: { name: "Action", selector: { action: {} } },
|
action: { name: "Action", selector: { action: {} } },
|
||||||
text: {
|
text: {
|
||||||
name: "Text",
|
name: "Text",
|
||||||
@ -279,6 +305,7 @@ const SCHEMAS: {
|
|||||||
entity: { name: "Entity", selector: { entity: { multiple: true } } },
|
entity: { name: "Entity", selector: { entity: { multiple: true } } },
|
||||||
device: { name: "Device", selector: { device: { multiple: true } } },
|
device: { name: "Device", selector: { device: { multiple: true } } },
|
||||||
area: { name: "Area", selector: { area: { multiple: true } } },
|
area: { name: "Area", selector: { area: { multiple: true } } },
|
||||||
|
label: { name: "Label", selector: { label: { multiple: true } } },
|
||||||
select: {
|
select: {
|
||||||
name: "Select Multiple",
|
name: "Select Multiple",
|
||||||
selector: {
|
selector: {
|
||||||
@ -335,6 +362,7 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
|||||||
mockDeviceRegistry(hass, DEVICES);
|
mockDeviceRegistry(hass, DEVICES);
|
||||||
mockConfigEntries(hass);
|
mockConfigEntries(hass);
|
||||||
mockAreaRegistry(hass, AREAS);
|
mockAreaRegistry(hass, AREAS);
|
||||||
|
mockLabelRegistry(hass, LABELS);
|
||||||
mockHassioSupervisor(hass);
|
mockHassioSupervisor(hass);
|
||||||
hass.mockWS("auth/sign_path", (params) => params);
|
hass.mockWS("auth/sign_path", (params) => params);
|
||||||
hass.mockWS("media_player/browse_media", this._browseMedia);
|
hass.mockWS("media_player/browse_media", this._browseMedia);
|
||||||
|
@ -198,6 +198,7 @@ const createEntityRegistryEntries = (
|
|||||||
has_entity_name: false,
|
has_entity_name: false,
|
||||||
unique_id: "updater",
|
unique_id: "updater",
|
||||||
options: null,
|
options: null,
|
||||||
|
labels: [],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -221,6 +222,7 @@ const createDeviceRegistryEntries = (
|
|||||||
name_by_user: null,
|
name_by_user: null,
|
||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
configuration_url: null,
|
configuration_url: null,
|
||||||
|
labels: [],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
465
src/components/ha-label-picker.ts
Normal file
465
src/components/ha-label-picker.ts
Normal file
@ -0,0 +1,465 @@
|
|||||||
|
import "@material/mwc-list/mwc-list-item";
|
||||||
|
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||||
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
|
import { classMap } from "lit/directives/class-map";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { computeDomain } from "../common/entity/compute_domain";
|
||||||
|
import {
|
||||||
|
LabelRegistryEntry,
|
||||||
|
createLabelRegistryEntry,
|
||||||
|
} from "../data/label_registry";
|
||||||
|
import {
|
||||||
|
DeviceEntityDisplayLookup,
|
||||||
|
DeviceRegistryEntry,
|
||||||
|
getDeviceEntityDisplayLookup,
|
||||||
|
} from "../data/device_registry";
|
||||||
|
import { EntityRegistryDisplayEntry } from "../data/entity_registry";
|
||||||
|
import {
|
||||||
|
showAlertDialog,
|
||||||
|
showPromptDialog,
|
||||||
|
} from "../dialogs/generic/show-dialog-box";
|
||||||
|
import { ValueChangedEvent, HomeAssistant } from "../types";
|
||||||
|
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||||
|
import "./ha-combo-box";
|
||||||
|
import type { HaComboBox } from "./ha-combo-box";
|
||||||
|
import "./ha-icon-button";
|
||||||
|
import "./ha-svg-icon";
|
||||||
|
|
||||||
|
const rowRenderer: ComboBoxLitRenderer<LabelRegistryEntry> = (
|
||||||
|
item
|
||||||
|
) => html`<mwc-list-item
|
||||||
|
class=${classMap({ "add-new": item.label_id === "add_new" })}
|
||||||
|
>
|
||||||
|
${item.name}
|
||||||
|
</mwc-list-item>`;
|
||||||
|
|
||||||
|
@customElement("ha-label-picker")
|
||||||
|
export class HaLabelPicker extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property() public value?: string;
|
||||||
|
|
||||||
|
@property() public helper?: string;
|
||||||
|
|
||||||
|
@property() public placeholder?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean, attribute: "no-add" })
|
||||||
|
public noAdd?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show only labels with entities from specific domains.
|
||||||
|
* @type {Array}
|
||||||
|
* @attr include-domains
|
||||||
|
*/
|
||||||
|
@property({ type: Array, attribute: "include-domains" })
|
||||||
|
public includeDomains?: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show no label with entities of these domains.
|
||||||
|
* @type {Array}
|
||||||
|
* @attr exclude-domains
|
||||||
|
*/
|
||||||
|
@property({ type: Array, attribute: "exclude-domains" })
|
||||||
|
public excludeDomains?: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show only labels with entities of these device classes.
|
||||||
|
* @type {Array}
|
||||||
|
* @attr include-device-classes
|
||||||
|
*/
|
||||||
|
@property({ type: Array, attribute: "include-device-classes" })
|
||||||
|
public includeDeviceClasses?: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of labels to be excluded.
|
||||||
|
* @type {Array}
|
||||||
|
* @attr exclude-labels
|
||||||
|
*/
|
||||||
|
@property({ type: Array, attribute: "exclude-labels" })
|
||||||
|
public excludeLabels?: string[];
|
||||||
|
|
||||||
|
@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
||||||
|
|
||||||
|
@property() public entityFilter?: (entity: HassEntity) => boolean;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled?: boolean;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public required?: boolean;
|
||||||
|
|
||||||
|
@state() private _opened?: boolean;
|
||||||
|
|
||||||
|
@query("ha-combo-box", true) public comboBox!: HaComboBox;
|
||||||
|
|
||||||
|
private _suggestion?: string;
|
||||||
|
|
||||||
|
private _init = false;
|
||||||
|
|
||||||
|
public async open() {
|
||||||
|
await this.updateComplete;
|
||||||
|
await this.comboBox?.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async focus() {
|
||||||
|
await this.updateComplete;
|
||||||
|
await this.comboBox?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getLabels = memoizeOne(
|
||||||
|
(
|
||||||
|
labels: LabelRegistryEntry[],
|
||||||
|
devices: DeviceRegistryEntry[],
|
||||||
|
entities: EntityRegistryDisplayEntry[],
|
||||||
|
includeDomains: this["includeDomains"],
|
||||||
|
excludeDomains: this["excludeDomains"],
|
||||||
|
includeDeviceClasses: this["includeDeviceClasses"],
|
||||||
|
deviceFilter: this["deviceFilter"],
|
||||||
|
entityFilter: this["entityFilter"],
|
||||||
|
noAdd: this["noAdd"],
|
||||||
|
excludeLabels: this["excludeLabels"]
|
||||||
|
): LabelRegistryEntry[] => {
|
||||||
|
if (!labels.length) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label_id: "no_labels",
|
||||||
|
name: this.hass.localize("ui.components.label-picker.no_labels"),
|
||||||
|
icon: null,
|
||||||
|
color: "#CCCCCC",
|
||||||
|
description: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
let deviceEntityLookup: DeviceEntityDisplayLookup = {};
|
||||||
|
let inputDevices: DeviceRegistryEntry[] | undefined;
|
||||||
|
let inputEntities: EntityRegistryDisplayEntry[] | undefined;
|
||||||
|
|
||||||
|
if (
|
||||||
|
includeDomains ||
|
||||||
|
excludeDomains ||
|
||||||
|
includeDeviceClasses ||
|
||||||
|
deviceFilter ||
|
||||||
|
entityFilter
|
||||||
|
) {
|
||||||
|
deviceEntityLookup = getDeviceEntityDisplayLookup(entities);
|
||||||
|
inputDevices = devices;
|
||||||
|
inputEntities = entities.filter((entity) => entity.labels);
|
||||||
|
|
||||||
|
if (includeDomains) {
|
||||||
|
inputDevices = inputDevices!.filter((device) => {
|
||||||
|
const devEntities = deviceEntityLookup[device.id];
|
||||||
|
if (!devEntities || !devEntities.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return deviceEntityLookup[device.id].some((entity) =>
|
||||||
|
includeDomains.includes(computeDomain(entity.entity_id))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
inputEntities = inputEntities!.filter((entity) =>
|
||||||
|
includeDomains.includes(computeDomain(entity.entity_id))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (excludeDomains) {
|
||||||
|
inputDevices = inputDevices!.filter((device) => {
|
||||||
|
const devEntities = deviceEntityLookup[device.id];
|
||||||
|
if (!devEntities || !devEntities.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return entities.every(
|
||||||
|
(entity) =>
|
||||||
|
!excludeDomains.includes(computeDomain(entity.entity_id))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
inputEntities = inputEntities!.filter(
|
||||||
|
(entity) =>
|
||||||
|
!excludeDomains.includes(computeDomain(entity.entity_id))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeDeviceClasses) {
|
||||||
|
inputDevices = inputDevices!.filter((device) => {
|
||||||
|
const devEntities = deviceEntityLookup[device.id];
|
||||||
|
if (!devEntities || !devEntities.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return deviceEntityLookup[device.id].some((entity) => {
|
||||||
|
const stateObj = this.hass.states[entity.entity_id];
|
||||||
|
if (!stateObj) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
stateObj.attributes.device_class &&
|
||||||
|
includeDeviceClasses.includes(stateObj.attributes.device_class)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
inputEntities = inputEntities!.filter((entity) => {
|
||||||
|
const stateObj = this.hass.states[entity.entity_id];
|
||||||
|
return (
|
||||||
|
stateObj.attributes.device_class &&
|
||||||
|
includeDeviceClasses.includes(stateObj.attributes.device_class)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deviceFilter) {
|
||||||
|
inputDevices = inputDevices!.filter((device) =>
|
||||||
|
deviceFilter!(device)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entityFilter) {
|
||||||
|
inputDevices = inputDevices!.filter((device) => {
|
||||||
|
const devEntities = deviceEntityLookup[device.id];
|
||||||
|
if (!devEntities || !devEntities.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return deviceEntityLookup[device.id].some((entity) => {
|
||||||
|
const stateObj = this.hass.states[entity.entity_id];
|
||||||
|
if (!stateObj) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return entityFilter(stateObj);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
inputEntities = inputEntities!.filter((entity) => {
|
||||||
|
const stateObj = this.hass.states[entity.entity_id];
|
||||||
|
if (!stateObj) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return entityFilter!(stateObj);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let outputLabels = labels;
|
||||||
|
|
||||||
|
let labelIds: string[] | undefined;
|
||||||
|
|
||||||
|
if (inputDevices) {
|
||||||
|
labelIds = inputDevices
|
||||||
|
.filter((device) => device.labels)
|
||||||
|
.map((device) => device.labels!)
|
||||||
|
.flat(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputEntities) {
|
||||||
|
labelIds = (labelIds ?? []).concat(
|
||||||
|
inputEntities
|
||||||
|
.filter((entity) => entity.labels)
|
||||||
|
.map((entity) => entity.labels!)
|
||||||
|
.flat(1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (labelIds) {
|
||||||
|
outputLabels = labels.filter((label) =>
|
||||||
|
labelIds!.includes(label.label_id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (excludeLabels) {
|
||||||
|
outputLabels = outputLabels.filter(
|
||||||
|
(label) => !excludeLabels!.includes(label.label_id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!outputLabels.length) {
|
||||||
|
outputLabels = [
|
||||||
|
{
|
||||||
|
label_id: "no_labels",
|
||||||
|
name: this.hass.localize("ui.components.label-picker.no_match"),
|
||||||
|
icon: null,
|
||||||
|
description: null,
|
||||||
|
color: "#CCCCCC",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return noAdd
|
||||||
|
? outputLabels
|
||||||
|
: [
|
||||||
|
...outputLabels,
|
||||||
|
{
|
||||||
|
label_id: "add_new",
|
||||||
|
name: this.hass.localize("ui.components.label-picker.add_new"),
|
||||||
|
icon: null,
|
||||||
|
description: null,
|
||||||
|
color: "#CCCCCC",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
protected updated(changedProps: PropertyValues) {
|
||||||
|
if (
|
||||||
|
(!this._init && this.hass) ||
|
||||||
|
(this._init && changedProps.has("_opened") && this._opened)
|
||||||
|
) {
|
||||||
|
this._init = true;
|
||||||
|
const labels = this._getLabels(
|
||||||
|
Object.values(this.hass.labels),
|
||||||
|
Object.values(this.hass.devices),
|
||||||
|
Object.values(this.hass.entities),
|
||||||
|
this.includeDomains,
|
||||||
|
this.excludeDomains,
|
||||||
|
this.includeDeviceClasses,
|
||||||
|
this.deviceFilter,
|
||||||
|
this.entityFilter,
|
||||||
|
this.noAdd,
|
||||||
|
this.excludeLabels
|
||||||
|
);
|
||||||
|
(this.comboBox as any).items = labels;
|
||||||
|
(this.comboBox as any).filteredItems = labels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<ha-combo-box
|
||||||
|
.hass=${this.hass}
|
||||||
|
.helper=${this.helper}
|
||||||
|
item-value-path="label_id"
|
||||||
|
item-id-path="label_id"
|
||||||
|
item-label-path="name"
|
||||||
|
.value=${this.value}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.required=${this.required}
|
||||||
|
.label=${this.label === undefined && this.hass
|
||||||
|
? this.hass.localize("ui.components.label-picker.label")
|
||||||
|
: this.label}
|
||||||
|
.placeholder=${this.placeholder
|
||||||
|
? this.hass.labels[this.placeholder]?.name
|
||||||
|
: undefined}
|
||||||
|
.renderer=${rowRenderer}
|
||||||
|
@filter-changed=${this._filterChanged}
|
||||||
|
@opened-changed=${this._openedChanged}
|
||||||
|
@value-changed=${this._labelChanged}
|
||||||
|
>
|
||||||
|
</ha-combo-box>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _filterChanged(ev: CustomEvent): void {
|
||||||
|
const filter = ev.detail.value;
|
||||||
|
if (!filter) {
|
||||||
|
this.comboBox.filteredItems = this.comboBox.items;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredItems = this.comboBox.items?.filter((item) =>
|
||||||
|
item.name.toLowerCase().includes(filter!.toLowerCase())
|
||||||
|
);
|
||||||
|
if (!this.noAdd && filteredItems?.length === 0) {
|
||||||
|
this._suggestion = filter;
|
||||||
|
this.comboBox.filteredItems = [
|
||||||
|
{
|
||||||
|
label_id: "add_new_suggestion",
|
||||||
|
name: this.hass.localize(
|
||||||
|
"ui.components.label-picker.add_new_sugestion",
|
||||||
|
{ name: this._suggestion }
|
||||||
|
),
|
||||||
|
icon: null,
|
||||||
|
description: null,
|
||||||
|
color: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
this.comboBox.filteredItems = filteredItems;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _value() {
|
||||||
|
return this.value || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private _openedChanged(ev: ValueChangedEvent<boolean>) {
|
||||||
|
this._opened = ev.detail.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _labelChanged(ev: ValueChangedEvent<string>) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
let newValue = ev.detail.value;
|
||||||
|
|
||||||
|
if (newValue === "no_labels") {
|
||||||
|
newValue = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!["add_new_suggestion", "add_new"].includes(newValue)) {
|
||||||
|
if (newValue !== this._value) {
|
||||||
|
this._setValue(newValue);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(ev.target as any).value = this._value;
|
||||||
|
showPromptDialog(this, {
|
||||||
|
title: this.hass.localize("ui.components.label-picker.add_dialog.title"),
|
||||||
|
text: this.hass.localize("ui.components.label-picker.add_dialog.text"),
|
||||||
|
confirmText: this.hass.localize(
|
||||||
|
"ui.components.label-picker.add_dialog.add"
|
||||||
|
),
|
||||||
|
inputLabel: this.hass.localize(
|
||||||
|
"ui.components.label-picker.add_dialog.name"
|
||||||
|
),
|
||||||
|
defaultValue:
|
||||||
|
newValue === "add_new_suggestion" ? this._suggestion : undefined,
|
||||||
|
confirm: async (name) => {
|
||||||
|
if (!name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const label = await createLabelRegistryEntry(this.hass, {
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
const labels = [...Object.values(this.hass.labels), label];
|
||||||
|
(this.comboBox as any).filteredItems = this._getLabels(
|
||||||
|
labels,
|
||||||
|
Object.values(this.hass.devices)!,
|
||||||
|
Object.values(this.hass.entities)!,
|
||||||
|
this.includeDomains,
|
||||||
|
this.excludeDomains,
|
||||||
|
this.includeDeviceClasses,
|
||||||
|
this.deviceFilter,
|
||||||
|
this.entityFilter,
|
||||||
|
this.noAdd,
|
||||||
|
this.excludeLabels
|
||||||
|
);
|
||||||
|
await this.updateComplete;
|
||||||
|
await this.comboBox.updateComplete;
|
||||||
|
this._setValue(label.label_id);
|
||||||
|
} catch (err: any) {
|
||||||
|
showAlertDialog(this, {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.components.label-picker.add_dialog.failed_create_label"
|
||||||
|
),
|
||||||
|
text: err.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cancel: () => {
|
||||||
|
this._setValue(undefined);
|
||||||
|
this._suggestion = undefined;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setValue(value?: string) {
|
||||||
|
this.value = value;
|
||||||
|
setTimeout(() => {
|
||||||
|
fireEvent(this, "value-changed", { value });
|
||||||
|
fireEvent(this, "change");
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-label-picker": HaLabelPicker;
|
||||||
|
}
|
||||||
|
}
|
166
src/components/ha-labels-picker.ts
Normal file
166
src/components/ha-labels-picker.ts
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||||
|
import type { HomeAssistant } from "../types";
|
||||||
|
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||||
|
import "./ha-label-picker";
|
||||||
|
|
||||||
|
@customElement("ha-labels-picker")
|
||||||
|
export class HaLabelsPicker extends SubscribeMixin(LitElement) {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property() public value?: string[];
|
||||||
|
|
||||||
|
@property() public helper?: string;
|
||||||
|
|
||||||
|
@property() public placeholder?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean, attribute: "no-add" })
|
||||||
|
public noAdd?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show only labels with entities from specific domains.
|
||||||
|
* @type {Array}
|
||||||
|
* @attr include-domains
|
||||||
|
*/
|
||||||
|
@property({ type: Array, attribute: "include-domains" })
|
||||||
|
public includeDomains?: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show no label with entities of these domains.
|
||||||
|
* @type {Array}
|
||||||
|
* @attr exclude-domains
|
||||||
|
*/
|
||||||
|
@property({ type: Array, attribute: "exclude-domains" })
|
||||||
|
public excludeDomains?: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show only label with entities of these device classes.
|
||||||
|
* @type {Array}
|
||||||
|
* @attr include-device-classes
|
||||||
|
*/
|
||||||
|
@property({ type: Array, attribute: "include-device-classes" })
|
||||||
|
public includeDeviceClasses?: string[];
|
||||||
|
|
||||||
|
@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
||||||
|
|
||||||
|
@property() public entityFilter?: (entity: HassEntity) => boolean;
|
||||||
|
|
||||||
|
@property({ attribute: "picked-label-label" })
|
||||||
|
public pickedLabelLabel?: string;
|
||||||
|
|
||||||
|
@property({ attribute: "pick-label-label" })
|
||||||
|
public pickLabelLabel?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled?: boolean;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public required?: boolean;
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this.hass) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentLabels = this._currentLabels;
|
||||||
|
return html`
|
||||||
|
${currentLabels.map(
|
||||||
|
(label) => html`
|
||||||
|
<div>
|
||||||
|
<ha-label-picker
|
||||||
|
.curValue=${label}
|
||||||
|
.noAdd=${this.noAdd}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${label}
|
||||||
|
.label=${this.pickedLabelLabel}
|
||||||
|
.includeDomains=${this.includeDomains}
|
||||||
|
.excludeDomains=${this.excludeDomains}
|
||||||
|
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||||
|
.deviceFilter=${this.deviceFilter}
|
||||||
|
.entityFilter=${this.entityFilter}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@value-changed=${this._labelChanged}
|
||||||
|
></ha-label-picker>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<ha-label-picker
|
||||||
|
.noAdd=${this.noAdd}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.label=${this.pickLabelLabel}
|
||||||
|
.helper=${this.helper}
|
||||||
|
.includeDomains=${this.includeDomains}
|
||||||
|
.excludeDomains=${this.excludeDomains}
|
||||||
|
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||||
|
.deviceFilter=${this.deviceFilter}
|
||||||
|
.entityFilter=${this.entityFilter}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.placeholder=${this.placeholder}
|
||||||
|
.required=${this.required && !currentLabels.length}
|
||||||
|
@value-changed=${this._addLabel}
|
||||||
|
></ha-label-picker>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _currentLabels(): string[] {
|
||||||
|
return this.value || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _updateLabels(labels) {
|
||||||
|
this.value = labels;
|
||||||
|
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: labels,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _labelChanged(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const curValue = (ev.currentTarget as any).curValue;
|
||||||
|
const newValue = ev.detail.value;
|
||||||
|
if (newValue === curValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const currentLabels = this._currentLabels;
|
||||||
|
if (!newValue || currentLabels.includes(newValue)) {
|
||||||
|
this._updateLabels(currentLabels.filter((ent) => ent !== curValue));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._updateLabels(
|
||||||
|
currentLabels.map((ent) => (ent === curValue ? newValue : ent))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addLabel(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
const toAdd = ev.detail.value;
|
||||||
|
if (!toAdd) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(ev.currentTarget as any).value = "";
|
||||||
|
const currentLabels = this._currentLabels;
|
||||||
|
if (currentLabels.includes(toAdd)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._updateLabels([...currentLabels, toAdd]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static override styles = css`
|
||||||
|
div {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-labels-picker": HaLabelsPicker;
|
||||||
|
}
|
||||||
|
}
|
132
src/components/ha-selector/ha-selector-label.ts
Normal file
132
src/components/ha-selector/ha-selector-label.ts
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
import { html, LitElement, PropertyValues, nothing } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { ensureArray } from "../../common/array/ensure-array";
|
||||||
|
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
||||||
|
import { getDeviceIntegrationLookup } from "../../data/device_registry";
|
||||||
|
import {
|
||||||
|
EntitySources,
|
||||||
|
fetchEntitySourcesWithCache,
|
||||||
|
} from "../../data/entity_sources";
|
||||||
|
import type { LabelSelector } from "../../data/selector";
|
||||||
|
import {
|
||||||
|
filterSelectorDevices,
|
||||||
|
filterSelectorEntities,
|
||||||
|
} from "../../data/selector";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
import "../ha-label-picker";
|
||||||
|
import "../ha-labels-picker";
|
||||||
|
|
||||||
|
@customElement("ha-selector-label")
|
||||||
|
export class HaLabelSelector extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public selector!: LabelSelector;
|
||||||
|
|
||||||
|
@property() public value?: any;
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property() public helper?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public required = true;
|
||||||
|
|
||||||
|
@state() private _entitySources?: EntitySources;
|
||||||
|
|
||||||
|
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
|
||||||
|
|
||||||
|
private _hasIntegration(selector: LabelSelector) {
|
||||||
|
return (
|
||||||
|
(selector.label?.entity &&
|
||||||
|
ensureArray(selector.label.entity).some(
|
||||||
|
(filter) => filter.integration
|
||||||
|
)) ||
|
||||||
|
(selector.label?.device &&
|
||||||
|
ensureArray(selector.label.device).some((device) => device.integration))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProperties: PropertyValues): void {
|
||||||
|
if (
|
||||||
|
changedProperties.has("selector") &&
|
||||||
|
this._hasIntegration(this.selector) &&
|
||||||
|
!this._entitySources
|
||||||
|
) {
|
||||||
|
fetchEntitySourcesWithCache(this.hass).then((sources) => {
|
||||||
|
this._entitySources = sources;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (this._hasIntegration(this.selector) && !this._entitySources) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.selector.label?.multiple) {
|
||||||
|
return html`
|
||||||
|
<ha-label-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this.value}
|
||||||
|
.label=${this.label}
|
||||||
|
.helper=${this.helper}
|
||||||
|
no-add
|
||||||
|
.deviceFilter=${this._filterDevices}
|
||||||
|
.entityFilter=${this._filterEntities}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.required=${this.required}
|
||||||
|
></ha-label-picker>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-labels-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this.value}
|
||||||
|
.helper=${this.helper}
|
||||||
|
.pickLabelLabel=${this.label}
|
||||||
|
no-add
|
||||||
|
.deviceFilter=${this._filterDevices}
|
||||||
|
.entityFilter=${this._filterEntities}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.required=${this.required}
|
||||||
|
></ha-labels-picker>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _filterEntities = (entity: HassEntity): boolean => {
|
||||||
|
if (!this.selector.label?.entity) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ensureArray(this.selector.label.entity).some((filter) =>
|
||||||
|
filterSelectorEntities(filter, entity, this._entitySources)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
private _filterDevices = (device: DeviceRegistryEntry): boolean => {
|
||||||
|
if (!this.selector.label?.device) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deviceIntegrations = this._entitySources
|
||||||
|
? this._deviceIntegrationLookup(
|
||||||
|
this._entitySources,
|
||||||
|
Object.values(this.hass.entities)
|
||||||
|
)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return ensureArray(this.selector.label.device).some((filter) =>
|
||||||
|
filterSelectorDevices(filter, device, deviceIntegrations)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-selector-label": HaLabelSelector;
|
||||||
|
}
|
||||||
|
}
|
@ -29,6 +29,7 @@ const LOAD_ELEMENTS = {
|
|||||||
entity: () => import("./ha-selector-entity"),
|
entity: () => import("./ha-selector-entity"),
|
||||||
statistic: () => import("./ha-selector-statistic"),
|
statistic: () => import("./ha-selector-statistic"),
|
||||||
file: () => import("./ha-selector-file"),
|
file: () => import("./ha-selector-file"),
|
||||||
|
label: () => import("./ha-selector-label"),
|
||||||
language: () => import("./ha-selector-language"),
|
language: () => import("./ha-selector-language"),
|
||||||
navigation: () => import("./ha-selector-navigation"),
|
navigation: () => import("./ha-selector-navigation"),
|
||||||
number: () => import("./ha-selector-number"),
|
number: () => import("./ha-selector-number"),
|
||||||
|
@ -19,6 +19,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
expandAreaTarget,
|
expandAreaTarget,
|
||||||
expandDeviceTarget,
|
expandDeviceTarget,
|
||||||
|
expandLabelTarget,
|
||||||
Selector,
|
Selector,
|
||||||
} from "../data/selector";
|
} from "../data/selector";
|
||||||
import { ValueChangedEvent, HomeAssistant } from "../types";
|
import { ValueChangedEvent, HomeAssistant } from "../types";
|
||||||
@ -127,7 +128,8 @@ export class HaServiceControl extends LitElement {
|
|||||||
"target" in serviceData &&
|
"target" in serviceData &&
|
||||||
(this.value?.data?.entity_id ||
|
(this.value?.data?.entity_id ||
|
||||||
this.value?.data?.area_id ||
|
this.value?.data?.area_id ||
|
||||||
this.value?.data?.device_id)
|
this.value?.data?.device_id ||
|
||||||
|
this.value?.data?.label_id)
|
||||||
) {
|
) {
|
||||||
const target = {
|
const target = {
|
||||||
...this.value.target,
|
...this.value.target,
|
||||||
@ -142,6 +144,9 @@ export class HaServiceControl extends LitElement {
|
|||||||
if (this.value.data.device_id && !this.value.target?.device_id) {
|
if (this.value.data.device_id && !this.value.target?.device_id) {
|
||||||
target.device_id = this.value.data.device_id;
|
target.device_id = this.value.data.device_id;
|
||||||
}
|
}
|
||||||
|
if (this.value.data.label_id && !this.value.target?.label_id) {
|
||||||
|
target.label_id = this.value.data.label_id;
|
||||||
|
}
|
||||||
|
|
||||||
this._value = {
|
this._value = {
|
||||||
...this.value,
|
...this.value,
|
||||||
@ -152,6 +157,7 @@ export class HaServiceControl extends LitElement {
|
|||||||
delete this._value.data!.entity_id;
|
delete this._value.data!.entity_id;
|
||||||
delete this._value.data!.device_id;
|
delete this._value.data!.device_id;
|
||||||
delete this._value.data!.area_id;
|
delete this._value.data!.area_id;
|
||||||
|
delete this._value.data!.label_id;
|
||||||
} else {
|
} else {
|
||||||
this._value = this.value;
|
this._value = this.value;
|
||||||
}
|
}
|
||||||
@ -263,6 +269,9 @@ export class HaServiceControl extends LitElement {
|
|||||||
const targetAreas = ensureArray(
|
const targetAreas = ensureArray(
|
||||||
value?.target?.area_id || value?.data?.area_id
|
value?.target?.area_id || value?.data?.area_id
|
||||||
)?.slice();
|
)?.slice();
|
||||||
|
const targetLabels = ensureArray(
|
||||||
|
value?.target?.label_id || value?.data?.label_id
|
||||||
|
)?.slice();
|
||||||
if (targetAreas) {
|
if (targetAreas) {
|
||||||
targetAreas.forEach((areaId) => {
|
targetAreas.forEach((areaId) => {
|
||||||
const expanded = expandAreaTarget(
|
const expanded = expandAreaTarget(
|
||||||
@ -288,6 +297,19 @@ export class HaServiceControl extends LitElement {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (targetLabels) {
|
||||||
|
targetLabels.forEach((labelId) => {
|
||||||
|
const expanded = expandLabelTarget(
|
||||||
|
this.hass,
|
||||||
|
labelId,
|
||||||
|
this.hass.devices,
|
||||||
|
this.hass.entities,
|
||||||
|
targetSelector
|
||||||
|
);
|
||||||
|
targetEntities.push(...expanded.entities);
|
||||||
|
targetDevices.push(...expanded.devices);
|
||||||
|
});
|
||||||
|
}
|
||||||
if (!targetEntities.length) {
|
if (!targetEntities.length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import "@material/mwc-menu/mwc-menu-surface";
|
|||||||
import {
|
import {
|
||||||
mdiClose,
|
mdiClose,
|
||||||
mdiDevices,
|
mdiDevices,
|
||||||
|
mdiLabelMultiple,
|
||||||
mdiPlus,
|
mdiPlus,
|
||||||
mdiSofa,
|
mdiSofa,
|
||||||
mdiUnfoldMoreVertical,
|
mdiUnfoldMoreVertical,
|
||||||
@ -34,6 +35,7 @@ import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker";
|
|||||||
import "./ha-area-picker";
|
import "./ha-area-picker";
|
||||||
import "./ha-icon-button";
|
import "./ha-icon-button";
|
||||||
import "./ha-input-helper-text";
|
import "./ha-input-helper-text";
|
||||||
|
import "./ha-label-picker";
|
||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
|
|
||||||
@customElement("ha-target-picker")
|
@customElement("ha-target-picker")
|
||||||
@ -70,7 +72,11 @@ export class HaTargetPicker extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public addOnTop = false;
|
@property({ type: Boolean }) public addOnTop = false;
|
||||||
|
|
||||||
@state() private _addMode?: "area_id" | "entity_id" | "device_id";
|
@state() private _addMode?:
|
||||||
|
| "area_id"
|
||||||
|
| "entity_id"
|
||||||
|
| "device_id"
|
||||||
|
| "label_id";
|
||||||
|
|
||||||
@query("#input") private _inputElement?;
|
@query("#input") private _inputElement?;
|
||||||
|
|
||||||
@ -123,6 +129,18 @@ export class HaTargetPicker extends LitElement {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
: ""}
|
: ""}
|
||||||
|
${this.value?.label_id
|
||||||
|
? ensureArray(this.value.label_id).map((labelId) => {
|
||||||
|
const label = this.hass.labels![labelId];
|
||||||
|
return this._renderChip(
|
||||||
|
"label_id",
|
||||||
|
labelId,
|
||||||
|
label?.name || labelId,
|
||||||
|
undefined,
|
||||||
|
mdiLabelMultiple
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -190,6 +208,26 @@ export class HaTargetPicker extends LitElement {
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="mdc-chip label_id add"
|
||||||
|
.type=${"label_id"}
|
||||||
|
@click=${this._showPicker}
|
||||||
|
>
|
||||||
|
<div class="mdc-chip__ripple"></div>
|
||||||
|
<ha-svg-icon
|
||||||
|
class="mdc-chip__icon mdc-chip__icon--leading"
|
||||||
|
.path=${mdiPlus}
|
||||||
|
></ha-svg-icon>
|
||||||
|
<span role="gridcell">
|
||||||
|
<span role="button" tabindex="0" class="mdc-chip__primary-action">
|
||||||
|
<span class="mdc-chip__text"
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.components.target-picker.add_label_id"
|
||||||
|
)}</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
${this._renderPicker()}
|
${this._renderPicker()}
|
||||||
</div>
|
</div>
|
||||||
${this.helper
|
${this.helper
|
||||||
@ -203,7 +241,7 @@ export class HaTargetPicker extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _renderChip(
|
private _renderChip(
|
||||||
type: "area_id" | "device_id" | "entity_id",
|
type: "area_id" | "device_id" | "entity_id" | "label_id",
|
||||||
id: string,
|
id: string,
|
||||||
name: string,
|
name: string,
|
||||||
entityState?: HassEntity,
|
entityState?: HassEntity,
|
||||||
@ -320,7 +358,8 @@ export class HaTargetPicker extends LitElement {
|
|||||||
@click=${this._preventDefault}
|
@click=${this._preventDefault}
|
||||||
></ha-device-picker>
|
></ha-device-picker>
|
||||||
`
|
`
|
||||||
: html`
|
: this._addMode === "entity_id"
|
||||||
|
? html`
|
||||||
<ha-entity-picker
|
<ha-entity-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
id="input"
|
id="input"
|
||||||
@ -336,6 +375,24 @@ export class HaTargetPicker extends LitElement {
|
|||||||
@click=${this._preventDefault}
|
@click=${this._preventDefault}
|
||||||
allow-custom-entity
|
allow-custom-entity
|
||||||
></ha-entity-picker>
|
></ha-entity-picker>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<ha-label-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
id="input"
|
||||||
|
.type=${"label_id"}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.components.target-picker.add_label_id"
|
||||||
|
)}
|
||||||
|
no-add
|
||||||
|
.deviceFilter=${this.deviceFilter}
|
||||||
|
.entityFilter=${this.entityFilter}
|
||||||
|
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||||
|
.includeDomains=${this.includeDomains}
|
||||||
|
.excludeLabels=${ensureArray(this.value?.label_id)}
|
||||||
|
@value-changed=${this._targetPicked}
|
||||||
|
@click=${this._preventDefault}
|
||||||
|
></ha-label-picker>
|
||||||
`}</mwc-menu-surface
|
`}</mwc-menu-surface
|
||||||
>`;
|
>`;
|
||||||
}
|
}
|
||||||
@ -405,6 +462,25 @@ export class HaTargetPicker extends LitElement {
|
|||||||
newEntities.push(entity.entity_id);
|
newEntities.push(entity.entity_id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else if (target.type === "label_id") {
|
||||||
|
Object.values(this.hass.devices).forEach((device) => {
|
||||||
|
if (
|
||||||
|
device.labels.includes(target.id) &&
|
||||||
|
!this.value!.device_id?.includes(device.id) &&
|
||||||
|
this._deviceMeetsFilter(device)
|
||||||
|
) {
|
||||||
|
newDevices.push(device.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.values(this.hass.entities).forEach((entity) => {
|
||||||
|
if (
|
||||||
|
entity.labels!.includes(target.id) &&
|
||||||
|
!this.value!.entity_id?.includes(entity.entity_id) &&
|
||||||
|
this._entityRegMeetsFilter(entity)
|
||||||
|
) {
|
||||||
|
newEntities.push(entity.entity_id);
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -662,6 +738,14 @@ export class HaTargetPicker extends LitElement {
|
|||||||
.mdc-chip.entity_id.add {
|
.mdc-chip.entity_id.add {
|
||||||
background: #d2e7b9;
|
background: #d2e7b9;
|
||||||
}
|
}
|
||||||
|
.mdc-chip.label_id:not(.add) {
|
||||||
|
border: 2px solid #eeefff;
|
||||||
|
background: var(--card-background-color);
|
||||||
|
}
|
||||||
|
.mdc-chip.label_id:not(.add) .mdc-chip__icon--leading,
|
||||||
|
.mdc-chip.label_id.add {
|
||||||
|
background: #eeefff;
|
||||||
|
}
|
||||||
.mdc-chip:hover {
|
.mdc-chip:hover {
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,7 @@ const targetStruct = object({
|
|||||||
entity_id: optional(union([string(), array(string())])),
|
entity_id: optional(union([string(), array(string())])),
|
||||||
device_id: optional(union([string(), array(string())])),
|
device_id: optional(union([string(), array(string())])),
|
||||||
area_id: optional(union([string(), array(string())])),
|
area_id: optional(union([string(), array(string())])),
|
||||||
|
label_id: optional(union([string(), array(string())])),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const serviceActionStruct: Describe<ServiceAction> = assign(
|
export const serviceActionStruct: Describe<ServiceAction> = assign(
|
||||||
|
@ -85,6 +85,7 @@ const tryDescribeAction = <T extends ActionType>(
|
|||||||
area_id: "areas",
|
area_id: "areas",
|
||||||
device_id: "devices",
|
device_id: "devices",
|
||||||
entity_id: "entities",
|
entity_id: "entities",
|
||||||
|
label_id: "labels",
|
||||||
})) {
|
})) {
|
||||||
if (!(key in config.target)) {
|
if (!(key in config.target)) {
|
||||||
continue;
|
continue;
|
||||||
@ -146,6 +147,13 @@ const tryDescribeAction = <T extends ActionType>(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} else if (key === "label_id") {
|
||||||
|
const label_ = hass.labels[targetThing];
|
||||||
|
if (label_?.name) {
|
||||||
|
targets.push(label_.name);
|
||||||
|
} else {
|
||||||
|
targets.push("unknown label");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
targets.push(targetThing);
|
targets.push(targetThing);
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ export type Selector =
|
|||||||
| LegacyEntitySelector
|
| LegacyEntitySelector
|
||||||
| FileSelector
|
| FileSelector
|
||||||
| IconSelector
|
| IconSelector
|
||||||
|
| LabelSelector
|
||||||
| LanguageSelector
|
| LanguageSelector
|
||||||
| LocationSelector
|
| LocationSelector
|
||||||
| MediaSelector
|
| MediaSelector
|
||||||
@ -157,6 +158,14 @@ export interface DeviceSelector {
|
|||||||
} | null;
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LabelSelector {
|
||||||
|
label: {
|
||||||
|
entity?: EntitySelectorFilter | readonly EntitySelectorFilter[];
|
||||||
|
device?: DeviceSelectorFilter | readonly DeviceSelectorFilter[];
|
||||||
|
multiple?: boolean;
|
||||||
|
} | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface LegacyDeviceSelector {
|
export interface LegacyDeviceSelector {
|
||||||
device: DeviceSelector["device"] & {
|
device: DeviceSelector["device"] & {
|
||||||
/**
|
/**
|
||||||
@ -463,6 +472,45 @@ export const expandDeviceTarget = (
|
|||||||
return { entities: newEntities };
|
return { entities: newEntities };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const expandLabelTarget = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
labelId: string,
|
||||||
|
devices: HomeAssistant["devices"],
|
||||||
|
entities: HomeAssistant["entities"],
|
||||||
|
targetSelector: TargetSelector,
|
||||||
|
entitySources?: EntitySources
|
||||||
|
) => {
|
||||||
|
const newEntities: string[] = [];
|
||||||
|
const newDevices: string[] = [];
|
||||||
|
Object.values(devices).forEach((device) => {
|
||||||
|
if (
|
||||||
|
device.labels.includes(labelId) &&
|
||||||
|
deviceMeetsTargetSelector(
|
||||||
|
hass,
|
||||||
|
Object.values(entities),
|
||||||
|
device,
|
||||||
|
targetSelector,
|
||||||
|
entitySources
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
newDevices.push(device.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.values(entities).forEach((entity) => {
|
||||||
|
if (
|
||||||
|
entity.labels!.includes(labelId) &&
|
||||||
|
entityMeetsTargetSelector(
|
||||||
|
hass.states[entity.entity_id],
|
||||||
|
targetSelector,
|
||||||
|
entitySources
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
newEntities.push(entity.entity_id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { devices: newDevices, entities: newEntities };
|
||||||
|
};
|
||||||
|
|
||||||
const deviceMeetsTargetSelector = (
|
const deviceMeetsTargetSelector = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entityRegistry: EntityRegistryDisplayEntry[],
|
entityRegistry: EntityRegistryDisplayEntry[],
|
||||||
|
@ -347,6 +347,7 @@ export const provideHass = (
|
|||||||
areas: {},
|
areas: {},
|
||||||
devices: {},
|
devices: {},
|
||||||
entities: {},
|
entities: {},
|
||||||
|
labels: {},
|
||||||
formatEntityState: (stateObj, state) =>
|
formatEntityState: (stateObj, state) =>
|
||||||
(state !== null ? state : stateObj.state) ?? "",
|
(state !== null ? state : stateObj.state) ?? "",
|
||||||
formatEntityAttributeName: (_stateObj, attribute) => attribute,
|
formatEntityAttributeName: (_stateObj, attribute) => attribute,
|
||||||
|
@ -404,13 +404,16 @@
|
|||||||
"expand": "Expand",
|
"expand": "Expand",
|
||||||
"expand_area_id": "Split this area into separate devices and entities.",
|
"expand_area_id": "Split this area into separate devices and entities.",
|
||||||
"expand_device_id": "Split this device into separate entities.",
|
"expand_device_id": "Split this device into separate entities.",
|
||||||
|
"expand_label_id": "Split this label into separate entities.",
|
||||||
"remove": "Remove",
|
"remove": "Remove",
|
||||||
"remove_area_id": "Remove area",
|
"remove_area_id": "Remove area",
|
||||||
"remove_device_id": "Remove device",
|
"remove_device_id": "Remove device",
|
||||||
"remove_entity_id": "Remove entity",
|
"remove_entity_id": "Remove entity",
|
||||||
|
"remove_label_id": "Remove label",
|
||||||
"add_area_id": "Choose area",
|
"add_area_id": "Choose area",
|
||||||
"add_device_id": "Choose device",
|
"add_device_id": "Choose device",
|
||||||
"add_entity_id": "Choose entity"
|
"add_entity_id": "Choose entity",
|
||||||
|
"add_label_id": "Choose label"
|
||||||
},
|
},
|
||||||
"config-entry-picker": {
|
"config-entry-picker": {
|
||||||
"config_entry": "Integration"
|
"config_entry": "Integration"
|
||||||
@ -476,6 +479,22 @@
|
|||||||
"failed_create_area": "Failed to create area."
|
"failed_create_area": "Failed to create area."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"label-picker": {
|
||||||
|
"clear": "Clear",
|
||||||
|
"show_labels": "Show labels",
|
||||||
|
"label": "Label",
|
||||||
|
"add_new_sugestion": "Add new label ''{name}''",
|
||||||
|
"add_new": "Add new label…",
|
||||||
|
"no_labels": "You don't have any labels",
|
||||||
|
"no_match": "No matching labels found",
|
||||||
|
"add_dialog": {
|
||||||
|
"title": "Add new label",
|
||||||
|
"text": "Enter the name of the new label.",
|
||||||
|
"name": "Name",
|
||||||
|
"add": "Add",
|
||||||
|
"failed_create_label": "Failed to create label."
|
||||||
|
}
|
||||||
|
},
|
||||||
"statistic-picker": {
|
"statistic-picker": {
|
||||||
"statistic": "Statistic",
|
"statistic": "Statistic",
|
||||||
"no_statistics": "You don't have any statistics",
|
"no_statistics": "You don't have any statistics",
|
||||||
@ -574,7 +593,7 @@
|
|||||||
"service-control": {
|
"service-control": {
|
||||||
"required": "This field is required",
|
"required": "This field is required",
|
||||||
"target": "Targets",
|
"target": "Targets",
|
||||||
"target_description": "What should this service use as targeted areas, devices or entities.",
|
"target_description": "What should this service use as targeted areas, devices, entities or labels.",
|
||||||
"data": "Service data",
|
"data": "Service data",
|
||||||
"integration_doc": "Integration documentation"
|
"integration_doc": "Integration documentation"
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user