mirror of
https://github.com/home-assistant/frontend.git
synced 2026-05-22 17:17:34 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6745a97f86 | |||
| a8286e31a0 | |||
| 4fa7274fbe | |||
| 414c1315a4 | |||
| 04bfc350ea | |||
| 75ec9404ce |
@@ -56,5 +56,4 @@ test/coverage/
|
||||
|
||||
# AI tooling
|
||||
.claude
|
||||
.cursor
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { DeviceRegistryEntry } from "../../../src/data/device/device_registry";
|
||||
import type { DeviceRegistryEntry } from "../../../src/data/device_registry";
|
||||
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
|
||||
export const mockDeviceRegistry = (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { EntityRegistryEntry } from "../../../src/data/entity/entity_registry";
|
||||
import type { EntityRegistryEntry } from "../../../src/data/entity_registry";
|
||||
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
|
||||
export const mockEntityRegistry = (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { LabelRegistryEntry } from "../../../src/data/label/label_registry";
|
||||
import type { LabelRegistryEntry } from "../../../src/data/label_registry";
|
||||
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
|
||||
export const mockLabelRegistry = (
|
||||
|
||||
@@ -11,11 +11,11 @@ import { computeInitialHaFormData } from "../../../../src/components/ha-form/com
|
||||
import "../../../../src/components/ha-form/ha-form";
|
||||
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
||||
import type { AreaRegistryEntry } from "../../../../src/data/area_registry";
|
||||
import type { DeviceRegistryEntry } from "../../../../src/data/device/device_registry";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import "../../components/demo-black-white-row";
|
||||
import type { DeviceRegistryEntry } from "../../../../src/data/device_registry";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("alarm_control_panel", "alarm", "disarmed", {
|
||||
|
||||
@@ -13,9 +13,9 @@ import "../../../../src/components/ha-selector/ha-selector";
|
||||
import "../../../../src/components/ha-settings-row";
|
||||
import type { AreaRegistryEntry } from "../../../../src/data/area_registry";
|
||||
import type { BlueprintInput } from "../../../../src/data/blueprint";
|
||||
import type { DeviceRegistryEntry } from "../../../../src/data/device/device_registry";
|
||||
import type { DeviceRegistryEntry } from "../../../../src/data/device_registry";
|
||||
import type { FloorRegistryEntry } from "../../../../src/data/floor_registry";
|
||||
import type { LabelRegistryEntry } from "../../../../src/data/label/label_registry";
|
||||
import type { LabelRegistryEntry } from "../../../../src/data/label_registry";
|
||||
import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
|
||||
@@ -6,8 +6,8 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import type { IntegrationManifest } from "../../../../src/data/integration";
|
||||
|
||||
import type { DeviceRegistryEntry } from "../../../../src/data/device/device_registry";
|
||||
import type { EntityRegistryEntry } from "../../../../src/data/entity/entity_registry";
|
||||
import type { DeviceRegistryEntry } from "../../../../src/data/device_registry";
|
||||
import type { EntityRegistryEntry } from "../../../../src/data/entity_registry";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import "../../../../src/panels/config/integrations/ha-config-flow-card";
|
||||
import type {
|
||||
|
||||
+2
-2
@@ -52,7 +52,7 @@
|
||||
"@fullcalendar/list": "6.1.19",
|
||||
"@fullcalendar/luxon3": "6.1.19",
|
||||
"@fullcalendar/timegrid": "6.1.19",
|
||||
"@home-assistant/webawesome": "3.0.0-ha.2",
|
||||
"@home-assistant/webawesome": "3.0.0-ha.1",
|
||||
"@lezer/highlight": "1.2.3",
|
||||
"@lit-labs/motion": "1.0.9",
|
||||
"@lit-labs/observers": "2.0.6",
|
||||
@@ -201,7 +201,7 @@
|
||||
"gulp-rename": "2.1.0",
|
||||
"html-minifier-terser": "7.2.0",
|
||||
"husky": "9.1.7",
|
||||
"jsdom": "27.3.0",
|
||||
"jsdom": "27.2.0",
|
||||
"jszip": "3.10.1",
|
||||
"lint-staged": "16.2.7",
|
||||
"lit-analyzer": "2.0.3",
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
DOMAIN_ATTRIBUTES_FORMATERS,
|
||||
DOMAIN_ATTRIBUTES_UNITS,
|
||||
TEMPERATURE_ATTRIBUTES,
|
||||
} from "../../data/entity/entity_attributes";
|
||||
import type { EntityRegistryDisplayEntry } from "../../data/entity/entity_registry";
|
||||
} from "../../data/entity_attributes";
|
||||
import type { EntityRegistryDisplayEntry } from "../../data/entity_registry";
|
||||
import type { FrontendLocaleData } from "../../data/translation";
|
||||
import type { WeatherEntity } from "../../data/weather";
|
||||
import { getWeatherUnit } from "../../data/weather";
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import memoizeOne from "memoize-one";
|
||||
import type { DeviceRegistryEntry } from "../../data/device/device_registry";
|
||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
||||
import type {
|
||||
EntityRegistryDisplayEntry,
|
||||
EntityRegistryEntry,
|
||||
} from "../../data/entity/entity_registry";
|
||||
} from "../../data/entity_registry";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { getDuplicates } from "../string/get_duplicates";
|
||||
import { computeStateName } from "./compute_state_name";
|
||||
import { getDuplicates } from "../string/get_duplicates";
|
||||
|
||||
export const computeDeviceName = (
|
||||
device: DeviceRegistryEntry
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type {
|
||||
EntityRegistryDisplayEntry,
|
||||
EntityRegistryEntry,
|
||||
} from "../../data/entity/entity_registry";
|
||||
} from "../../data/entity_registry";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { computeDeviceName } from "./compute_device_name";
|
||||
import { computeStateName } from "./compute_state_name";
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { HassConfig, HassEntity } from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity/entity";
|
||||
import type { EntityRegistryDisplayEntry } from "../../data/entity/entity_registry";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
||||
import type { EntityRegistryDisplayEntry } from "../../data/entity_registry";
|
||||
import type { FrontendLocaleData } from "../../data/translation";
|
||||
import { TimeZone } from "../../data/translation";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { DURATION_UNITS, formatDuration } from "../datetime/format_duration";
|
||||
import { formatDate } from "../datetime/format_date";
|
||||
import { formatDateTime } from "../datetime/format_date_time";
|
||||
import { DURATION_UNITS, formatDuration } from "../datetime/format_duration";
|
||||
import { formatTime } from "../datetime/format_time";
|
||||
import {
|
||||
formatNumber,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { AreaRegistryEntry } from "../../../data/area_registry";
|
||||
import type { DeviceRegistryEntry } from "../../../data/device/device_registry";
|
||||
import type { DeviceRegistryEntry } from "../../../data/device_registry";
|
||||
import type { FloorRegistryEntry } from "../../../data/floor_registry";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { AreaRegistryEntry } from "../../../data/area_registry";
|
||||
import type { DeviceRegistryEntry } from "../../../data/device/device_registry";
|
||||
import type { DeviceRegistryEntry } from "../../../data/device_registry";
|
||||
import type {
|
||||
EntityRegistryDisplayEntry,
|
||||
EntityRegistryEntry,
|
||||
ExtEntityRegistryEntry,
|
||||
} from "../../../data/entity/entity_registry";
|
||||
} from "../../../data/entity_registry";
|
||||
import type { FloorRegistryEntry } from "../../../data/floor_registry";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import type { ConfigEntry } from "../../data/config_entries";
|
||||
import { deleteConfigEntry } from "../../data/config_entries";
|
||||
import type { EntityRegistryEntry } from "../../data/entity/entity_registry";
|
||||
import { removeEntityRegistryEntry } from "../../data/entity/entity_registry";
|
||||
import { HELPERS_CRUD } from "../../data/helpers_crud";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { IntegrationManifest } from "../../data/integration";
|
||||
import { computeDomain } from "./compute_domain";
|
||||
import { HELPERS_CRUD } from "../../data/helpers_crud";
|
||||
import type { Helper } from "../../panels/config/helpers/const";
|
||||
import { isHelperDomain } from "../../panels/config/helpers/const";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { isComponentLoaded } from "../config/is_component_loaded";
|
||||
import { computeDomain } from "./compute_domain";
|
||||
import type { EntityRegistryEntry } from "../../data/entity_registry";
|
||||
import { removeEntityRegistryEntry } from "../../data/entity_registry";
|
||||
import type { ConfigEntry } from "../../data/config_entries";
|
||||
import { deleteConfigEntry } from "../../data/config_entries";
|
||||
|
||||
export const isDeletableEntity = (
|
||||
hass: HomeAssistant,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE_STATES } from "../../data/entity/entity";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { stringCompare } from "../string/compare";
|
||||
import { computeDomain } from "./compute_domain";
|
||||
import { computeStateDomain } from "./compute_state_domain";
|
||||
import { UNAVAILABLE_STATES } from "../../data/entity";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { computeDomain } from "./compute_domain";
|
||||
import { stringCompare } from "../string/compare";
|
||||
|
||||
export const FIXED_DOMAIN_STATES = {
|
||||
alarm_control_panel: [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { isUnavailableState, UNAVAILABLE } from "../../data/entity/entity";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { computeStateDomain } from "./compute_state_domain";
|
||||
import { isUnavailableState, UNAVAILABLE } from "../../data/entity";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
|
||||
export const computeGroupEntitiesState = (states: HassEntity[]): string => {
|
||||
if (!states.length) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { isUnavailableState, OFF, UNAVAILABLE } from "../../data/entity/entity";
|
||||
import { isUnavailableState, OFF, UNAVAILABLE } from "../../data/entity";
|
||||
import { computeDomain } from "./compute_domain";
|
||||
|
||||
export function stateActive(stateObj: HassEntity, state?: string): boolean {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE } from "../../data/entity/entity";
|
||||
import { UNAVAILABLE } from "../../data/entity";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { DOMAINS_WITH_CARD } from "../const";
|
||||
import { canToggleState } from "./can_toggle_state";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** Return a color representing a state. */
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE } from "../../data/entity/entity";
|
||||
import { UNAVAILABLE } from "../../data/entity";
|
||||
import type { GroupEntity } from "../../data/group";
|
||||
import { computeGroupDomain } from "../../data/group";
|
||||
import { computeCssVariable } from "../../resources/css-variables";
|
||||
|
||||
@@ -2,7 +2,7 @@ import type {
|
||||
HassEntity,
|
||||
HassEntityAttributeBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
import type { EntityRegistryDisplayEntry } from "../../data/entity/entity_registry";
|
||||
import type { EntityRegistryDisplayEntry } from "../../data/entity_registry";
|
||||
import type { FrontendLocaleData } from "../../data/translation";
|
||||
import { NumberFormat } from "../../data/translation";
|
||||
import { round } from "./round";
|
||||
|
||||
@@ -3,11 +3,11 @@ import { getGraphColorByIndex } from "../../common/color/colors";
|
||||
import { hex2rgb, lab2hex, rgb2lab } from "../../common/color/convert-color";
|
||||
import { labBrighten } from "../../common/color/lab";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { stateColorProperties } from "../../common/entity/state_color";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
||||
import { computeCssValue } from "../../resources/css-variables";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { FIXED_DOMAIN_STATES } from "../../common/entity/get_states";
|
||||
import { stateColorProperties } from "../../common/entity/state_color";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity/entity";
|
||||
import { computeCssValue } from "../../resources/css-variables";
|
||||
|
||||
const DOMAIN_STATE_SHADES: Record<string, Record<string, number>> = {
|
||||
media_player: {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { computeCssColor } from "../../common/color/compute-color";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
import type { LabelRegistryEntry } from "../../data/label/label_registry";
|
||||
import type { LabelRegistryEntry } from "../../data/label_registry";
|
||||
import "../chips/ha-chip-set";
|
||||
import "../ha-dropdown";
|
||||
import "../ha-dropdown-item";
|
||||
|
||||
@@ -16,10 +16,8 @@ import memoizeOne from "memoize-one";
|
||||
import { restoreScroll } from "../../common/decorators/restore-scroll";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import { groupBy } from "../../common/util/group-by";
|
||||
import { nextRender } from "../../common/util/render-status";
|
||||
import { haStyleScrollbar } from "../../resources/styles";
|
||||
import { loadVirtualizer } from "../../resources/virtualizer";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
@@ -28,6 +26,8 @@ import type { HaCheckbox } from "../ha-checkbox";
|
||||
import "../ha-svg-icon";
|
||||
import "../search-input";
|
||||
import { filterData, sortData } from "./sort-filter";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import { nextRender } from "../../common/util/render-status";
|
||||
|
||||
export interface RowClickedEvent {
|
||||
id: string;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { expose } from "comlink";
|
||||
import Fuse, { type FuseOptionKey } from "fuse.js";
|
||||
import Fuse from "fuse.js";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ipCompare, stringCompare } from "../../common/string/compare";
|
||||
import { stripDiacritics } from "../../common/string/strip-diacritics";
|
||||
import { multiTermSearch } from "../../resources/fuseMultiTerm";
|
||||
import { HaFuse } from "../../resources/fuse";
|
||||
import type {
|
||||
ClonedDataTableColumnData,
|
||||
DataTableRowData,
|
||||
@@ -11,10 +11,9 @@ import type {
|
||||
SortingDirection,
|
||||
} from "./ha-data-table";
|
||||
|
||||
const getSearchKeys = memoizeOne(
|
||||
(columns: SortableColumnContainer): FuseOptionKey<DataTableRowData>[] => {
|
||||
const fuseIndex = memoizeOne(
|
||||
(data: DataTableRowData[], columns: SortableColumnContainer) => {
|
||||
const searchKeys = new Set<string>();
|
||||
|
||||
Object.entries(columns).forEach(([key, column]) => {
|
||||
if (column.filterable) {
|
||||
searchKeys.add(
|
||||
@@ -24,15 +23,10 @@ const getSearchKeys = memoizeOne(
|
||||
);
|
||||
}
|
||||
});
|
||||
return Array.from(searchKeys);
|
||||
return Fuse.createIndex([...searchKeys], data);
|
||||
}
|
||||
);
|
||||
|
||||
const fuseIndex = memoizeOne(
|
||||
(data: DataTableRowData[], keys: FuseOptionKey<DataTableRowData>[]) =>
|
||||
Fuse.createIndex(keys, data)
|
||||
);
|
||||
|
||||
const filterData = (
|
||||
data: DataTableRowData[],
|
||||
columns: SortableColumnContainer,
|
||||
@@ -44,13 +38,21 @@ const filterData = (
|
||||
return data;
|
||||
}
|
||||
|
||||
const keys = getSearchKeys(columns);
|
||||
const index = fuseIndex(data, columns);
|
||||
|
||||
const index = fuseIndex(data, keys);
|
||||
const fuse = new HaFuse(
|
||||
data,
|
||||
{ shouldSort: false, minMatchCharLength: 1 },
|
||||
index
|
||||
);
|
||||
|
||||
return multiTermSearch<DataTableRowData>(data, filter, keys, index, {
|
||||
threshold: 0.2, // reduce fuzzy matches in data tables
|
||||
});
|
||||
const searchResults = fuse.multiTermsSearch(filter);
|
||||
|
||||
if (searchResults) {
|
||||
return searchResults.map((result) => result.item);
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const sortData = (
|
||||
|
||||
@@ -101,6 +101,10 @@ const Component = Vue.extend({
|
||||
type: String,
|
||||
default: "en",
|
||||
},
|
||||
opensVertical: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
render(createElement) {
|
||||
// @ts-expect-error
|
||||
@@ -129,6 +133,11 @@ const Component = Vue.extend({
|
||||
},
|
||||
expression: "dateRange",
|
||||
},
|
||||
on: {
|
||||
toggle: (open: boolean) => {
|
||||
fireEvent(this.$el as HTMLElement, "toggle", { open });
|
||||
},
|
||||
},
|
||||
scopedSlots: {
|
||||
input() {
|
||||
return createElement("slot", {
|
||||
@@ -309,6 +318,10 @@ class DateRangePickerElement extends WrappedElement {
|
||||
min-width: unset !important;
|
||||
display: block !important;
|
||||
}
|
||||
:host([opens-vertical="up"]) .daterangepicker {
|
||||
bottom: 100%;
|
||||
top: auto !important;
|
||||
}
|
||||
`;
|
||||
if (mainWindow.document.dir === "rtl") {
|
||||
style.innerHTML += `
|
||||
@@ -340,4 +353,7 @@ declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"date-range-picker": DateRangePickerElement;
|
||||
}
|
||||
interface HASSDomEvents {
|
||||
toggle: { open: boolean };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { customElement } from "lit/decorators";
|
||||
import type { DeviceAction } from "../../data/device/device_automation";
|
||||
import type { DeviceAction } from "../../data/device_automation";
|
||||
import {
|
||||
fetchDeviceActions,
|
||||
localizeDeviceAutomationAction,
|
||||
} from "../../data/device/device_automation";
|
||||
} from "../../data/device_automation";
|
||||
import { HaDeviceAutomationPicker } from "./ha-device-automation-picker";
|
||||
|
||||
@customElement("ha-device-action-picker")
|
||||
|
||||
@@ -2,17 +2,17 @@ import { consume } from "@lit/context";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||
import { fullEntitiesContext } from "../../data/context";
|
||||
import type { DeviceAutomation } from "../../data/device/device_automation";
|
||||
import type { DeviceAutomation } from "../../data/device_automation";
|
||||
import {
|
||||
deviceAutomationsEqual,
|
||||
sortDeviceAutomations,
|
||||
} from "../../data/device/device_automation";
|
||||
import type { EntityRegistryEntry } from "../../data/entity/entity_registry";
|
||||
} from "../../data/device_automation";
|
||||
import type { EntityRegistryEntry } from "../../data/entity_registry";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-md-select";
|
||||
import "../ha-md-select-option";
|
||||
import "../ha-md-select";
|
||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||
|
||||
const NO_AUTOMATION_KEY = "NO_AUTOMATION";
|
||||
const UNKNOWN_AUTOMATION_KEY = "UNKNOWN_AUTOMATION";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { customElement } from "lit/decorators";
|
||||
import type { DeviceCondition } from "../../data/device/device_automation";
|
||||
import type { DeviceCondition } from "../../data/device_automation";
|
||||
import {
|
||||
fetchDeviceConditions,
|
||||
localizeDeviceAutomationCondition,
|
||||
} from "../../data/device/device_automation";
|
||||
} from "../../data/device_automation";
|
||||
import { HaDeviceAutomationPicker } from "./ha-device-automation-picker";
|
||||
|
||||
@customElement("ha-device-condition-picker")
|
||||
|
||||
@@ -9,11 +9,10 @@ import { computeDeviceName } from "../../common/entity/compute_device_name";
|
||||
import { getDeviceContext } from "../../common/entity/context/get_device_context";
|
||||
import { getConfigEntries, type ConfigEntry } from "../../data/config_entries";
|
||||
import {
|
||||
deviceComboBoxKeys,
|
||||
getDevices,
|
||||
type DevicePickerItem,
|
||||
} from "../../data/device/device_picker";
|
||||
import type { DeviceRegistryEntry } from "../../data/device/device_registry";
|
||||
type DeviceRegistryEntry,
|
||||
} from "../../data/device_registry";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { brandsUrl } from "../../util/brands-url";
|
||||
import "../ha-generic-picker";
|
||||
@@ -217,10 +216,6 @@ export class HaDevicePicker extends LitElement {
|
||||
.getItems=${this._getItems}
|
||||
.hideClearIcon=${this.hideClearIcon}
|
||||
.valueRenderer=${valueRenderer}
|
||||
.searchKeys=${deviceComboBoxKeys}
|
||||
.unknownItemText=${this.hass.localize(
|
||||
"ui.components.device-picker.unknown"
|
||||
)}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { customElement } from "lit/decorators";
|
||||
import type { DeviceTrigger } from "../../data/device/device_automation";
|
||||
import type { DeviceTrigger } from "../../data/device_automation";
|
||||
import {
|
||||
fetchDeviceTriggers,
|
||||
localizeDeviceAutomationTrigger,
|
||||
} from "../../data/device/device_automation";
|
||||
} from "../../data/device_automation";
|
||||
import { HaDeviceAutomationPicker } from "./ha-device-automation-picker";
|
||||
|
||||
@customElement("ha-device-trigger-picker")
|
||||
|
||||
@@ -4,7 +4,7 @@ import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { isValidEntityId } from "../../common/entity/valid_entity_id";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../../data/entity/entity";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../../data/entity";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import "../ha-sortable";
|
||||
import "./ha-entity-picker";
|
||||
|
||||
@@ -7,12 +7,11 @@ import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeEntityNameList } from "../../common/entity/compute_entity_name_display";
|
||||
import { isValidEntityId } from "../../common/entity/valid_entity_id";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../../data/entity/entity";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../../data/entity";
|
||||
import {
|
||||
entityComboBoxKeys,
|
||||
getEntities,
|
||||
type EntityComboBoxItem,
|
||||
} from "../../data/entity/entity_picker";
|
||||
} from "../../data/entity_registry";
|
||||
import { domainToName } from "../../data/integration";
|
||||
import {
|
||||
isHelperDomain,
|
||||
@@ -228,7 +227,7 @@ export class HaEntityPicker extends LitElement {
|
||||
if (!createDomains?.length) {
|
||||
return [];
|
||||
}
|
||||
this.hass.loadFragmentTranslation("config");
|
||||
|
||||
return createDomains.map((domain) => {
|
||||
const primary = localize(
|
||||
"ui.components.entity.entity-picker.create_helper",
|
||||
@@ -236,7 +235,7 @@ export class HaEntityPicker extends LitElement {
|
||||
domain: isHelperDomain(domain)
|
||||
? localize(
|
||||
`ui.panel.config.helpers.types.${domain as HelperDomain}`
|
||||
) || domain
|
||||
)
|
||||
: domainToName(localize, domain),
|
||||
}
|
||||
);
|
||||
@@ -289,14 +288,10 @@ export class HaEntityPicker extends LitElement {
|
||||
.hideClearIcon=${this.hideClearIcon}
|
||||
.searchFn=${this._searchFn}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
.searchKeys=${entityComboBoxKeys}
|
||||
@value-changed=${this._valueChanged}
|
||||
.addButtonLabel=${this.addButton
|
||||
? this.hass.localize("ui.components.entity.entity-picker.add")
|
||||
: undefined}
|
||||
.unknownItemText=${this.hass.localize(
|
||||
"ui.components.entity.entity-picker.unknown"
|
||||
)}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
`;
|
||||
|
||||
@@ -6,11 +6,7 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { STATES_OFF } from "../../common/const";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import {
|
||||
UNAVAILABLE,
|
||||
UNKNOWN,
|
||||
isUnavailableState,
|
||||
} from "../../data/entity/entity";
|
||||
import { UNAVAILABLE, UNKNOWN, isUnavailableState } from "../../data/entity";
|
||||
import { forwardHaptic } from "../../data/haptics";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-formfield";
|
||||
|
||||
@@ -14,12 +14,8 @@ import {
|
||||
getNumberFormatOptions,
|
||||
isNumericState,
|
||||
} from "../../common/number/format_number";
|
||||
import {
|
||||
isUnavailableState,
|
||||
UNAVAILABLE,
|
||||
UNKNOWN,
|
||||
} from "../../data/entity/entity";
|
||||
import type { EntityRegistryDisplayEntry } from "../../data/entity/entity_registry";
|
||||
import { isUnavailableState, UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
||||
import type { EntityRegistryDisplayEntry } from "../../data/entity_registry";
|
||||
import { timerTimeRemaining } from "../../data/timer";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-label-badge";
|
||||
|
||||
@@ -38,21 +38,9 @@ type StatisticItemType = "entity" | "external" | "no_state";
|
||||
interface StatisticComboBoxItem extends PickerComboBoxItem {
|
||||
statistic_id?: string;
|
||||
stateObj?: HassEntity;
|
||||
domainName?: string;
|
||||
type?: StatisticItemType;
|
||||
}
|
||||
|
||||
const SEARCH_KEYS = [
|
||||
{ name: "label", weight: 10 },
|
||||
{ name: "search_labels.entityName", weight: 10 },
|
||||
{ name: "search_labels.friendlyName", weight: 9 },
|
||||
{ name: "search_labels.deviceName", weight: 8 },
|
||||
{ name: "search_labels.areaName", weight: 6 },
|
||||
{ name: "search_labels.domainName", weight: 4 },
|
||||
{ name: "statisticId", weight: 3 },
|
||||
{ name: "id", weight: 2 },
|
||||
];
|
||||
|
||||
@customElement("ha-statistic-picker")
|
||||
export class HaStatisticPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -245,6 +233,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
),
|
||||
type,
|
||||
sorting_label: [sortingPrefix, label].join("_"),
|
||||
search_labels: [label, id],
|
||||
icon_path: mdiShape,
|
||||
});
|
||||
} else if (type === "external") {
|
||||
@@ -257,7 +246,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
secondary: domainName,
|
||||
type,
|
||||
sorting_label: [sortingPrefix, label].join("_"),
|
||||
search_labels: { label, domainName },
|
||||
search_labels: [label, domainName, id],
|
||||
icon_path: mdiChartLine,
|
||||
});
|
||||
}
|
||||
@@ -291,12 +280,13 @@ export class HaStatisticPicker extends LitElement {
|
||||
stateObj: stateObj,
|
||||
type: "entity",
|
||||
sorting_label: [sortingPrefix, deviceName, entityName].join("_"),
|
||||
search_labels: {
|
||||
entityName: entityName || null,
|
||||
deviceName: deviceName || null,
|
||||
areaName: areaName || null,
|
||||
search_labels: [
|
||||
entityName,
|
||||
deviceName,
|
||||
areaName,
|
||||
friendlyName,
|
||||
},
|
||||
id,
|
||||
].filter(Boolean) as string[],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -371,13 +361,13 @@ export class HaStatisticPicker extends LitElement {
|
||||
stateObj: stateObj,
|
||||
type: "entity",
|
||||
sorting_label: [sortingPrefix, deviceName, entityName].join("_"),
|
||||
search_labels: {
|
||||
entityName: entityName || null,
|
||||
deviceName: deviceName || null,
|
||||
areaName: areaName || null,
|
||||
search_labels: [
|
||||
entityName,
|
||||
deviceName,
|
||||
areaName,
|
||||
friendlyName,
|
||||
statisticId,
|
||||
},
|
||||
].filter(Boolean) as string[],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -404,7 +394,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
secondary: domainName,
|
||||
type: "external",
|
||||
sorting_label: [sortingPrefix, label].join("_"),
|
||||
search_labels: { label, domainName, statisticId },
|
||||
search_labels: [label, domainName, statisticId],
|
||||
icon_path: mdiChartLine,
|
||||
};
|
||||
}
|
||||
@@ -419,7 +409,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
secondary: this.hass.localize("ui.components.statistic-picker.no_state"),
|
||||
type: "no_state",
|
||||
sorting_label: [sortingPrefix, label].join("_"),
|
||||
search_labels: { label, statisticId },
|
||||
search_labels: [label, statisticId],
|
||||
icon_path: mdiShape,
|
||||
};
|
||||
}
|
||||
@@ -485,10 +475,6 @@ export class HaStatisticPicker extends LitElement {
|
||||
.searchFn=${this._searchFn}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
.helper=${this.helper}
|
||||
.searchKeys=${SEARCH_KEYS}
|
||||
.unknownItemText=${this.hass.localize(
|
||||
"ui.components.statistic-picker.unknown"
|
||||
)}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
import { mdiTextureBox } from "@mdi/js";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { LitElement, html, nothing } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { computeAreaName } from "../common/entity/compute_area_name";
|
||||
import { computeFloorName } from "../common/entity/compute_floor_name";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import {
|
||||
getAreasAndFloors,
|
||||
type AreaFloorValue,
|
||||
type FloorComboBoxItem,
|
||||
} from "../data/area_floor";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../types";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||
import "./ha-combo-box-item";
|
||||
import "./ha-floor-icon";
|
||||
import "./ha-generic-picker";
|
||||
import type { HaGenericPicker } from "./ha-generic-picker";
|
||||
import "./ha-icon-button";
|
||||
import type { PickerValueRenderer } from "./ha-picker-field";
|
||||
import "./ha-svg-icon";
|
||||
import "./ha-tree-indicator";
|
||||
|
||||
const SEPARATOR = "________";
|
||||
|
||||
@customElement("ha-area-floor-picker")
|
||||
export class HaAreaFloorPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ attribute: false }) public value?: AreaFloorValue;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property() public placeholder?: string;
|
||||
|
||||
@property({ type: String, attribute: "search-label" })
|
||||
public searchLabel?: string;
|
||||
|
||||
/**
|
||||
* Show only areas with entities from specific domains.
|
||||
* @type {Array}
|
||||
* @attr include-domains
|
||||
*/
|
||||
@property({ type: Array, attribute: "include-domains" })
|
||||
public includeDomains?: string[];
|
||||
|
||||
/**
|
||||
* Show no areas with entities of these domains.
|
||||
* @type {Array}
|
||||
* @attr exclude-domains
|
||||
*/
|
||||
@property({ type: Array, attribute: "exclude-domains" })
|
||||
public excludeDomains?: string[];
|
||||
|
||||
/**
|
||||
* Show only areas with entities of these device classes.
|
||||
* @type {Array}
|
||||
* @attr include-device-classes
|
||||
*/
|
||||
@property({ type: Array, attribute: "include-device-classes" })
|
||||
public includeDeviceClasses?: string[];
|
||||
|
||||
/**
|
||||
* List of areas to be excluded.
|
||||
* @type {Array}
|
||||
* @attr exclude-areas
|
||||
*/
|
||||
@property({ type: Array, attribute: "exclude-areas" })
|
||||
public excludeAreas?: string[];
|
||||
|
||||
/**
|
||||
* List of floors to be excluded.
|
||||
* @type {Array}
|
||||
* @attr exclude-floors
|
||||
*/
|
||||
@property({ type: Array, attribute: "exclude-floors" })
|
||||
public excludeFloors?: string[];
|
||||
|
||||
@property({ attribute: false })
|
||||
public deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
||||
|
||||
@property({ attribute: false })
|
||||
public entityFilter?: (entity: HassEntity) => boolean;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@query("ha-generic-picker") private _picker?: HaGenericPicker;
|
||||
|
||||
public async open() {
|
||||
await this.updateComplete;
|
||||
await this._picker?.open();
|
||||
}
|
||||
|
||||
private _valueRenderer: PickerValueRenderer = (value: string) => {
|
||||
const item = this._parseValue(value);
|
||||
|
||||
const area = item.type === "area" && this.hass.areas[value];
|
||||
|
||||
if (area) {
|
||||
const areaName = computeAreaName(area);
|
||||
return html`
|
||||
${area.icon
|
||||
? html`<ha-icon slot="start" .icon=${area.icon}></ha-icon>`
|
||||
: html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiTextureBox}
|
||||
></ha-svg-icon>`}
|
||||
<slot name="headline">${areaName}</slot>
|
||||
`;
|
||||
}
|
||||
|
||||
const floor = item.type === "floor" && this.hass.floors[value];
|
||||
|
||||
if (floor) {
|
||||
const floorName = computeFloorName(floor);
|
||||
return html`
|
||||
<ha-floor-icon slot="start" .floor=${floor}></ha-floor-icon>
|
||||
<span slot="headline">${floorName}</span>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-svg-icon slot="start" .path=${mdiTextureBox}></ha-svg-icon>
|
||||
<span slot="headline">${value}</span>
|
||||
`;
|
||||
};
|
||||
|
||||
private _rowRenderer: ComboBoxLitRenderer<FloorComboBoxItem> = (
|
||||
item,
|
||||
{ index },
|
||||
combobox
|
||||
) => {
|
||||
const nextItem = combobox.filteredItems?.[index + 1];
|
||||
const isLastArea =
|
||||
!nextItem ||
|
||||
nextItem.type === "floor" ||
|
||||
(nextItem.type === "area" && !nextItem.area?.floor_id);
|
||||
|
||||
const rtl = computeRTL(this.hass);
|
||||
|
||||
const hasFloor = item.type === "area" && item.area?.floor_id;
|
||||
|
||||
return html`
|
||||
<ha-combo-box-item
|
||||
type="button"
|
||||
style=${item.type === "area" && hasFloor
|
||||
? "--md-list-item-leading-space: 48px;"
|
||||
: ""}
|
||||
>
|
||||
${item.type === "area" && hasFloor
|
||||
? html`
|
||||
<ha-tree-indicator
|
||||
style=${styleMap({
|
||||
width: "48px",
|
||||
position: "absolute",
|
||||
top: "0px",
|
||||
left: rtl ? undefined : "4px",
|
||||
right: rtl ? "4px" : undefined,
|
||||
transform: rtl ? "scaleX(-1)" : "",
|
||||
})}
|
||||
.end=${isLastArea}
|
||||
slot="start"
|
||||
></ha-tree-indicator>
|
||||
`
|
||||
: nothing}
|
||||
${item.type === "floor" && item.floor
|
||||
? html`<ha-floor-icon
|
||||
slot="start"
|
||||
.floor=${item.floor}
|
||||
></ha-floor-icon>`
|
||||
: item.icon
|
||||
? html`<ha-icon slot="start" .icon=${item.icon}></ha-icon>`
|
||||
: html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${item.icon_path || mdiTextureBox}
|
||||
></ha-svg-icon>`}
|
||||
${item.primary}
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
};
|
||||
|
||||
private _getAreasAndFloorsMemoized = memoizeOne(getAreasAndFloors);
|
||||
|
||||
private _getItems = () =>
|
||||
this._getAreasAndFloorsMemoized(
|
||||
this.hass.states,
|
||||
this.hass.floors,
|
||||
this.hass.areas,
|
||||
this.hass.devices,
|
||||
this.hass.entities,
|
||||
this._formatValue,
|
||||
this.includeDomains,
|
||||
this.excludeDomains,
|
||||
this.includeDeviceClasses,
|
||||
this.deviceFilter,
|
||||
this.entityFilter,
|
||||
this.excludeAreas,
|
||||
this.excludeFloors
|
||||
);
|
||||
|
||||
private _formatValue = memoizeOne((value: AreaFloorValue): string =>
|
||||
[value.type, value.id].join(SEPARATOR)
|
||||
);
|
||||
|
||||
private _parseValue = memoizeOne((value: string): AreaFloorValue => {
|
||||
const [type, id] = value.split(SEPARATOR);
|
||||
|
||||
return { id, type: type as "floor" | "area" };
|
||||
});
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const placeholder =
|
||||
this.placeholder ?? this.hass.localize("ui.components.area-picker.area");
|
||||
|
||||
const value = this.value ? this._formatValue(this.value) : undefined;
|
||||
|
||||
return html`
|
||||
<ha-generic-picker
|
||||
.hass=${this.hass}
|
||||
.autofocus=${this.autofocus}
|
||||
.label=${this.label}
|
||||
.searchLabel=${this.searchLabel}
|
||||
.notFoundLabel=${this.hass.localize(
|
||||
"ui.components.area-picker.no_match"
|
||||
)}
|
||||
.placeholder=${placeholder}
|
||||
.value=${value}
|
||||
.getItems=${this._getItems}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
.rowRenderer=${this._rowRenderer}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: ValueChangedEvent<string>) {
|
||||
ev.stopPropagation();
|
||||
const value = ev.detail.value;
|
||||
|
||||
if (!value) {
|
||||
this._setValue(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
const selected = this._parseValue(value);
|
||||
this._setValue(selected);
|
||||
}
|
||||
|
||||
private _setValue(value?: AreaFloorValue) {
|
||||
this.value = value;
|
||||
fireEvent(this, "value-changed", { value });
|
||||
fireEvent(this, "change");
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-area-floor-picker": HaAreaFloorPicker;
|
||||
}
|
||||
}
|
||||
@@ -13,9 +13,9 @@ import { createAreaRegistryEntry } from "../data/area_registry";
|
||||
import type {
|
||||
DeviceEntityDisplayLookup,
|
||||
DeviceRegistryEntry,
|
||||
} from "../data/device/device_registry";
|
||||
import { getDeviceEntityDisplayLookup } from "../data/device/device_registry";
|
||||
import type { EntityRegistryDisplayEntry } from "../data/entity/entity_registry";
|
||||
} from "../data/device_registry";
|
||||
import { getDeviceEntityDisplayLookup } from "../data/device_registry";
|
||||
import type { EntityRegistryDisplayEntry } from "../data/entity_registry";
|
||||
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
||||
import { showAreaRegistryDetailDialog } from "../panels/config/areas/show-dialog-area-registry-detail";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../types";
|
||||
@@ -30,12 +30,6 @@ import "./ha-svg-icon";
|
||||
|
||||
const ADD_NEW_ID = "___ADD_NEW___";
|
||||
|
||||
const SEARCH_KEYS = [
|
||||
{ name: "search_labels.areaName", weight: 10 },
|
||||
{ name: "search_labels.aliases", weight: 8 },
|
||||
{ name: "search_labels.floorName", weight: 6 },
|
||||
{ name: "search_labels.id", weight: 3 },
|
||||
];
|
||||
@customElement("ha-area-picker")
|
||||
export class HaAreaPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -296,12 +290,13 @@ export class HaAreaPicker extends LitElement {
|
||||
secondary: floorName,
|
||||
icon: area.icon || undefined,
|
||||
icon_path: area.icon ? undefined : mdiTextureBox,
|
||||
search_labels: {
|
||||
areaName: areaName || null,
|
||||
floorName: floorName || null,
|
||||
id: area.area_id,
|
||||
aliases: area.aliases.join(" "),
|
||||
},
|
||||
sorting_label: areaName,
|
||||
search_labels: [
|
||||
areaName,
|
||||
floorName,
|
||||
area.area_id,
|
||||
...area.aliases,
|
||||
].filter((v): v is string => Boolean(v)),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -384,10 +379,6 @@ export class HaAreaPicker extends LitElement {
|
||||
.getAdditionalItems=${this._getAdditionalItems}
|
||||
.valueRenderer=${valueRenderer}
|
||||
.addButtonLabel=${this.addButtonLabel}
|
||||
.searchKeys=${SEARCH_KEYS}
|
||||
.unknownItemText=${this.hass.localize(
|
||||
"ui.components.area-picker.unknown"
|
||||
)}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
|
||||
@@ -659,7 +659,7 @@ export class HaAssistChat extends LitElement {
|
||||
--markdown-table-border-color: var(--divider-color);
|
||||
--markdown-code-background-color: var(--primary-background-color);
|
||||
--markdown-code-text-color: var(--primary-text-color);
|
||||
--markdown-list-indent: 1.15em;
|
||||
--markdown-list-indent: 1rem;
|
||||
&:not(:has(ha-markdown-element)) {
|
||||
min-height: 1lh;
|
||||
min-width: 1lh;
|
||||
|
||||
@@ -3,15 +3,15 @@ import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { computeAttributeNameDisplay } from "../common/entity/compute_attribute_display";
|
||||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||
import {
|
||||
STATE_ATTRIBUTES,
|
||||
STATE_ATTRIBUTES_DOMAIN_CLASS,
|
||||
} from "../data/entity/entity_attributes";
|
||||
} from "../data/entity_attributes";
|
||||
import { haStyle } from "../resources/styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-attribute-value";
|
||||
import "./ha-expansion-panel";
|
||||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||
|
||||
@customElement("ha-attributes")
|
||||
class HaAttributes extends LitElement {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import type { ClimateEntity } from "../data/climate";
|
||||
import { CLIMATE_PRESET_NONE } from "../data/climate";
|
||||
import { isUnavailableState, OFF } from "../data/entity/entity";
|
||||
import { isUnavailableState, OFF } from "../data/entity";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
@customElement("ha-climate-state")
|
||||
|
||||
@@ -9,14 +9,14 @@ import type { ConfigEntry, SubEntry } from "../data/config_entries";
|
||||
import { getConfigEntry, getSubEntries } from "../data/config_entries";
|
||||
import type { Agent } from "../data/conversation";
|
||||
import { listAgents } from "../data/conversation";
|
||||
import { getExtendedEntityRegistryEntry } from "../data/entity/entity_registry";
|
||||
import { fetchIntegrationManifest } from "../data/integration";
|
||||
import { showOptionsFlowDialog } from "../dialogs/config-flow/show-dialog-options-flow";
|
||||
import { showSubConfigFlowDialog } from "../dialogs/config-flow/show-dialog-sub-config-flow";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-list-item";
|
||||
import "./ha-select";
|
||||
import type { HaSelect } from "./ha-select";
|
||||
import { getExtendedEntityRegistryEntry } from "../data/entity_registry";
|
||||
import { showSubConfigFlowDialog } from "../dialogs/config-flow/show-dialog-sub-config-flow";
|
||||
|
||||
const NONE = "__NONE_OPTION__";
|
||||
|
||||
|
||||
@@ -74,6 +74,9 @@ export class HaDateRangePicker extends LitElement {
|
||||
@property({ attribute: "extended-presets", type: Boolean })
|
||||
public extendedPresets = false;
|
||||
|
||||
@property({ attribute: "vertical-opening-direction" })
|
||||
public verticalOpeningDirection?: "up" | "down";
|
||||
|
||||
@property({ attribute: false }) public openingDirection?:
|
||||
| "right"
|
||||
| "left"
|
||||
@@ -127,6 +130,7 @@ export class HaDateRangePicker extends LitElement {
|
||||
opening-direction=${ifDefined(
|
||||
this.openingDirection || this._calcedOpeningDirection
|
||||
)}
|
||||
opens-vertical=${ifDefined(this.verticalOpeningDirection)}
|
||||
first-day=${firstWeekdayIndex(this.hass.locale)}
|
||||
language=${this.hass.locale.language}
|
||||
@change=${this._handleChange}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import DropdownItem from "@home-assistant/webawesome/dist/components/dropdown-item/dropdown-item";
|
||||
import "@home-assistant/webawesome/dist/components/icon/icon";
|
||||
import { css, type CSSResultGroup, html } from "lit";
|
||||
import { css, type CSSResultGroup } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import "./ha-svg-icon";
|
||||
import { mdiCheckboxBlankOutline, mdiCheckboxMarked } from "@mdi/js";
|
||||
|
||||
/**
|
||||
* Home Assistant dropdown item component
|
||||
@@ -17,16 +14,6 @@ import { mdiCheckboxBlankOutline, mdiCheckboxMarked } from "@mdi/js";
|
||||
*/
|
||||
@customElement("ha-dropdown-item")
|
||||
export class HaDropdownItem extends DropdownItem {
|
||||
protected renderCheckboxIcon() {
|
||||
return html`
|
||||
<ha-svg-icon
|
||||
id="check"
|
||||
part="checkmark"
|
||||
.path=${this.checked ? mdiCheckboxMarked : mdiCheckboxBlankOutline}
|
||||
></ha-svg-icon>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
DropdownItem.styles,
|
||||
@@ -35,10 +22,6 @@ export class HaDropdownItem extends DropdownItem {
|
||||
min-height: var(--ha-space-10);
|
||||
}
|
||||
|
||||
#check {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#icon ::slotted(*) {
|
||||
color: var(--ha-color-on-neutral-normal);
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ import { computeCssColor } from "../common/color/compute-color";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { navigate } from "../common/navigate";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import type { LabelRegistryEntry } from "../data/label/label_registry";
|
||||
import { subscribeLabelRegistry } from "../data/label/label_registry";
|
||||
import type { LabelRegistryEntry } from "../data/label_registry";
|
||||
import { subscribeLabelRegistry } from "../data/label_registry";
|
||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
@@ -12,9 +12,9 @@ import { updateAreaRegistryEntry } from "../data/area_registry";
|
||||
import type {
|
||||
DeviceEntityDisplayLookup,
|
||||
DeviceRegistryEntry,
|
||||
} from "../data/device/device_registry";
|
||||
import { getDeviceEntityDisplayLookup } from "../data/device/device_registry";
|
||||
import type { EntityRegistryDisplayEntry } from "../data/entity/entity_registry";
|
||||
} from "../data/device_registry";
|
||||
import { getDeviceEntityDisplayLookup } from "../data/device_registry";
|
||||
import type { EntityRegistryDisplayEntry } from "../data/entity_registry";
|
||||
import {
|
||||
createFloorRegistryEntry,
|
||||
getFloorAreaLookup,
|
||||
@@ -35,12 +35,6 @@ import "./ha-svg-icon";
|
||||
|
||||
const ADD_NEW_ID = "___ADD_NEW___";
|
||||
|
||||
const SEARCH_KEYS = [
|
||||
{ name: "search_labels.floorName", weight: 10 },
|
||||
{ name: "search_labels.aliases", weight: 8 },
|
||||
{ name: "search_labels.floor_id", weight: 3 },
|
||||
];
|
||||
|
||||
interface FloorComboBoxItem extends PickerComboBoxItem {
|
||||
floor?: FloorRegistryEntry;
|
||||
}
|
||||
@@ -291,11 +285,10 @@ export class HaFloorPicker extends LitElement {
|
||||
id: floor.floor_id,
|
||||
primary: floorName,
|
||||
floor: floor,
|
||||
search_labels: {
|
||||
floorName,
|
||||
floor_id: floor.floor_id,
|
||||
aliases: floor.aliases.join(" "),
|
||||
},
|
||||
sorting_label: floor.level?.toString() || "zzzzz",
|
||||
search_labels: [floorName, floor.floor_id, ...floor.aliases].filter(
|
||||
(v): v is string => Boolean(v)
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -400,10 +393,6 @@ export class HaFloorPicker extends LitElement {
|
||||
.getAdditionalItems=${this._getAdditionalItems}
|
||||
.valueRenderer=${valueRenderer}
|
||||
.rowRenderer=${this._rowRenderer}
|
||||
.searchKeys=${SEARCH_KEYS}
|
||||
.unknownItemText=${this.hass.localize(
|
||||
"ui.components.floor-picker.unknown"
|
||||
)}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
|
||||
@@ -4,10 +4,8 @@ import { mdiPlaylistPlus } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing, type CSSResultGroup } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { tinykeys } from "tinykeys";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { FuseWeightedKey } from "../resources/fuseMultiTerm";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-bottom-sheet";
|
||||
import "./ha-button";
|
||||
@@ -48,9 +46,8 @@ export class HaGenericPicker extends LitElement {
|
||||
@property({ attribute: "hide-clear-icon", type: Boolean })
|
||||
public hideClearIcon = false;
|
||||
|
||||
/** To prevent lags, getItems needs to be memoized */
|
||||
@property({ attribute: false })
|
||||
public getItems!: (
|
||||
public getItems?: (
|
||||
searchString?: string,
|
||||
section?: string
|
||||
) => (PickerComboBoxItem | string)[];
|
||||
@@ -67,9 +64,6 @@ export class HaGenericPicker extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public searchFn?: PickerComboBoxSearchFn<PickerComboBoxItem>;
|
||||
|
||||
@property({ attribute: false })
|
||||
public searchKeys?: FuseWeightedKey[];
|
||||
|
||||
@property({ attribute: false })
|
||||
public notFoundLabel?: string | ((search: string) => string);
|
||||
|
||||
@@ -113,8 +107,6 @@ export class HaGenericPicker extends LitElement {
|
||||
|
||||
@property({ attribute: "selected-section" }) public selectedSection?: string;
|
||||
|
||||
@property({ attribute: "unknown-item-text" }) public unknownItemText?: string;
|
||||
|
||||
@query(".container") private _containerElement?: HTMLDivElement;
|
||||
|
||||
@query("ha-picker-combo-box") private _comboBox?: HaPickerComboBox;
|
||||
@@ -164,8 +156,6 @@ export class HaGenericPicker extends LitElement {
|
||||
type="button"
|
||||
class=${this._opened ? "opened" : ""}
|
||||
compact
|
||||
.unknown=${this._unknownValue(this.value, this.getItems())}
|
||||
.unknownItemText=${this.unknownItemText}
|
||||
aria-label=${ifDefined(this.label)}
|
||||
@click=${this.open}
|
||||
@clear=${this._clear}
|
||||
@@ -239,23 +229,10 @@ export class HaGenericPicker extends LitElement {
|
||||
.sections=${this.sections}
|
||||
.sectionTitleFunction=${this.sectionTitleFunction}
|
||||
.selectedSection=${this.selectedSection}
|
||||
.searchKeys=${this.searchKeys}
|
||||
></ha-picker-combo-box>
|
||||
`;
|
||||
}
|
||||
|
||||
private _unknownValue = memoizeOne(
|
||||
(value?: string, items?: (PickerComboBoxItem | string)[]) => {
|
||||
if (value === undefined || value === null || value === "" || !items) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !items.some(
|
||||
(item) => typeof item !== "string" && item.id === value
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
private _renderHelper() {
|
||||
return this.helper
|
||||
? html`<ha-input-helper-text .disabled=${this.disabled}
|
||||
@@ -367,10 +344,7 @@ export class HaGenericPicker extends LitElement {
|
||||
|
||||
wa-popover::part(body) {
|
||||
width: max(var(--body-width), 250px);
|
||||
max-width: var(
|
||||
--ha-generic-picker-max-width,
|
||||
max(var(--body-width), 250px)
|
||||
);
|
||||
max-width: max(var(--body-width), 250px);
|
||||
max-height: 500px;
|
||||
height: 70vh;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { isUnavailableState, OFF } from "../data/entity/entity";
|
||||
import { isUnavailableState, OFF } from "../data/entity";
|
||||
import type { HumidifierEntity } from "../data/humidifier";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
|
||||
@@ -11,12 +11,12 @@ import {
|
||||
} from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { getLabels, labelComboBoxKeys } from "../data/label/label_picker";
|
||||
import type { LabelRegistryEntry } from "../data/label_registry";
|
||||
import {
|
||||
createLabelRegistryEntry,
|
||||
getLabels,
|
||||
subscribeLabelRegistry,
|
||||
type LabelRegistryEntry,
|
||||
} from "../data/label/label_registry";
|
||||
} from "../data/label_registry";
|
||||
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||
import { showLabelDetailDialog } from "../panels/config/labels/show-dialog-label-detail";
|
||||
@@ -237,7 +237,6 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
|
||||
.getItems=${this._getItems}
|
||||
.getAdditionalItems=${this._getAdditionalItems}
|
||||
.valueRenderer=${valueRenderer}
|
||||
.searchKeys=${labelComboBoxKeys}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
<slot .slot=${this._slotNodes?.length ? "field" : undefined}></slot>
|
||||
|
||||
@@ -8,11 +8,11 @@ import memoizeOne from "memoize-one";
|
||||
import { computeCssColor } from "../common/color/compute-color";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import type { LabelRegistryEntry } from "../data/label/label_registry";
|
||||
import type { LabelRegistryEntry } from "../data/label_registry";
|
||||
import {
|
||||
subscribeLabelRegistry,
|
||||
updateLabelRegistryEntry,
|
||||
} from "../data/label/label_registry";
|
||||
} from "../data/label_registry";
|
||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||
import { showLabelDetailDialog } from "../panels/config/labels/show-dialog-label-detail";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../types";
|
||||
|
||||
@@ -40,12 +40,14 @@ export const getLanguageOptions = (
|
||||
return {
|
||||
id: lang,
|
||||
primary,
|
||||
search_labels: [primary],
|
||||
};
|
||||
});
|
||||
} else if (locale) {
|
||||
options = languages.map((lang) => ({
|
||||
id: lang,
|
||||
primary: formatLanguageCode(lang, locale),
|
||||
search_labels: [formatLanguageCode(lang, locale)],
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -74,6 +74,9 @@ export class HaMarkdown extends LitElement {
|
||||
background-color: var(--markdown-image-background-color);
|
||||
border-radius: var(--markdown-image-border-radius);
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
width: auto;
|
||||
transition: height 0.2s ease-in-out;
|
||||
}
|
||||
p:first-child > img:first-child {
|
||||
vertical-align: top;
|
||||
@@ -81,7 +84,8 @@ export class HaMarkdown extends LitElement {
|
||||
p:first-child > img:last-child {
|
||||
vertical-align: top;
|
||||
}
|
||||
ha-markdown-element > :is(ol, ul) {
|
||||
:host > ul,
|
||||
:host > ol {
|
||||
padding-inline-start: var(--markdown-list-indent, revert);
|
||||
}
|
||||
li {
|
||||
@@ -132,18 +136,6 @@ export class HaMarkdown extends LitElement {
|
||||
border-bottom: none;
|
||||
margin: var(--ha-space-4) 0;
|
||||
}
|
||||
table[role="presentation"] {
|
||||
--markdown-table-border-collapse: separate;
|
||||
--markdown-table-border-width: attr(border, 0);
|
||||
--markdown-table-padding-inline: 0;
|
||||
--markdown-table-padding-block: 0;
|
||||
th {
|
||||
vertical-align: attr(align, center);
|
||||
}
|
||||
td {
|
||||
vertical-align: attr(align, left);
|
||||
}
|
||||
}
|
||||
table {
|
||||
border-collapse: var(--markdown-table-border-collapse, collapse);
|
||||
}
|
||||
@@ -151,15 +143,14 @@ export class HaMarkdown extends LitElement {
|
||||
overflow: auto;
|
||||
}
|
||||
th {
|
||||
text-align: var(--markdown-table-text-align, start);
|
||||
text-align: start;
|
||||
}
|
||||
td,
|
||||
th {
|
||||
border-width: var(--markdown-table-border-width, 1px);
|
||||
border-style: var(--markdown-table-border-style, solid);
|
||||
border-color: var(--markdown-table-border-color, var(--divider-color));
|
||||
padding-inline: var(--markdown-table-padding-inline, 0.5em);
|
||||
padding-block: var(--markdown-table-padding-block, 0.25em);
|
||||
padding: 0.25em 0.5em;
|
||||
}
|
||||
blockquote {
|
||||
border-left: 4px solid var(--divider-color);
|
||||
|
||||
@@ -15,10 +15,7 @@ import { tinykeys } from "tinykeys";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||
import { ScrollableFadeMixin } from "../mixins/scrollable-fade-mixin";
|
||||
import {
|
||||
multiTermSortedSearch,
|
||||
type FuseWeightedKey,
|
||||
} from "../resources/fuseMultiTerm";
|
||||
import { HaFuse } from "../resources/fuse";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import { loadVirtualizer } from "../resources/virtualizer";
|
||||
import type { HomeAssistant } from "../types";
|
||||
@@ -29,26 +26,11 @@ import "./ha-icon";
|
||||
import "./ha-textfield";
|
||||
import type { HaTextField } from "./ha-textfield";
|
||||
|
||||
export const DEFAULT_SEARCH_KEYS: FuseWeightedKey[] = [
|
||||
{
|
||||
name: "primary",
|
||||
weight: 10,
|
||||
},
|
||||
{
|
||||
name: "secondary",
|
||||
weight: 7,
|
||||
},
|
||||
{
|
||||
name: "id",
|
||||
weight: 3,
|
||||
},
|
||||
];
|
||||
|
||||
export interface PickerComboBoxItem {
|
||||
id: string;
|
||||
primary: string;
|
||||
secondary?: string;
|
||||
search_labels?: Record<string, string | null>;
|
||||
search_labels?: string[];
|
||||
sorting_label?: string;
|
||||
icon_path?: string;
|
||||
icon?: string;
|
||||
@@ -95,13 +77,10 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property({ attribute: false })
|
||||
public searchKeys?: FuseWeightedKey[];
|
||||
|
||||
@state() private _listScrolled = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
public getItems!: (
|
||||
public getItems?: (
|
||||
searchString?: string,
|
||||
section?: string
|
||||
) => (PickerComboBoxItem | string)[];
|
||||
@@ -154,8 +133,6 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
|
||||
@state() private _sectionTitle?: string;
|
||||
|
||||
@state() private _valuePinned = true;
|
||||
|
||||
private _allItems: (PickerComboBoxItem | string)[] = [];
|
||||
|
||||
private _selectedItemIndex = -1;
|
||||
@@ -217,15 +194,6 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
.renderItem=${this._renderItem}
|
||||
style="min-height: 36px;"
|
||||
class=${this._listScrolled ? "scrolled" : ""}
|
||||
.layout=${this.value && this._valuePinned
|
||||
? {
|
||||
pin: {
|
||||
index: this._getInitialSelectedIndex(),
|
||||
block: "center",
|
||||
},
|
||||
}
|
||||
: undefined}
|
||||
@unpinned=${this._handleUnpinned}
|
||||
@scroll=${this._onScrollList}
|
||||
@focus=${this._focusList}
|
||||
@visibilityChanged=${this._visibilityChanged}
|
||||
@@ -276,42 +244,24 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
@eventOptions({ passive: true })
|
||||
private _handleUnpinned() {
|
||||
this._valuePinned = false;
|
||||
}
|
||||
|
||||
private _getAdditionalItems = (searchString?: string) =>
|
||||
this.getAdditionalItems?.(searchString) || [];
|
||||
|
||||
private _getItems = () => {
|
||||
let items = [...this.getItems(this._search, this.selectedSection)];
|
||||
let items = [
|
||||
...(this.getItems
|
||||
? this.getItems(this._search, this.selectedSection)
|
||||
: []),
|
||||
];
|
||||
|
||||
if (!this.sections?.length) {
|
||||
items = items.sort((entityA, entityB) => {
|
||||
const sortLabelA =
|
||||
typeof entityA === "string" ? entityA : entityA.sorting_label;
|
||||
const sortLabelB =
|
||||
typeof entityB === "string" ? entityB : entityB.sorting_label;
|
||||
|
||||
if (!sortLabelA || !sortLabelB) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!sortLabelB) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!sortLabelA) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return caseInsensitiveStringCompare(
|
||||
sortLabelA,
|
||||
sortLabelB,
|
||||
items = items.sort((entityA, entityB) =>
|
||||
caseInsensitiveStringCompare(
|
||||
(entityA as PickerComboBoxItem).sorting_label!,
|
||||
(entityB as PickerComboBoxItem).sorting_label!,
|
||||
this.hass?.locale.language ?? navigator.language
|
||||
);
|
||||
});
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!items.length) {
|
||||
@@ -329,9 +279,6 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
};
|
||||
|
||||
private _renderItem = (item: PickerComboBoxItem | string, index: number) => {
|
||||
if (!item) {
|
||||
return nothing;
|
||||
}
|
||||
if (item === "padding") {
|
||||
return html`<div class="bottom-padding"></div>`;
|
||||
}
|
||||
@@ -392,9 +339,8 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
fireEvent(this, "value-changed", { value: newValue });
|
||||
};
|
||||
|
||||
private _fuseIndex = memoizeOne(
|
||||
(states: PickerComboBoxItem[], searchKeys?: FuseWeightedKey[]) =>
|
||||
Fuse.createIndex(searchKeys || DEFAULT_SEARCH_KEYS, states)
|
||||
private _fuseIndex = memoizeOne((states: PickerComboBoxItem[]) =>
|
||||
Fuse.createIndex(["search_labels"], states)
|
||||
);
|
||||
|
||||
private _filterChanged = (ev: Event) => {
|
||||
@@ -410,26 +356,34 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = this._fuseIndex(
|
||||
const index = this._fuseIndex(this._allItems as PickerComboBoxItem[]);
|
||||
const fuse = new HaFuse(
|
||||
this._allItems as PickerComboBoxItem[],
|
||||
this.searchKeys
|
||||
{
|
||||
shouldSort: false,
|
||||
minMatchCharLength: Math.min(searchString.length, 2),
|
||||
},
|
||||
index
|
||||
);
|
||||
|
||||
let filteredItems = multiTermSortedSearch<PickerComboBoxItem>(
|
||||
this._allItems as PickerComboBoxItem[],
|
||||
searchString,
|
||||
this.searchKeys || DEFAULT_SEARCH_KEYS,
|
||||
(item) => item.id,
|
||||
index
|
||||
) as (PickerComboBoxItem | string)[];
|
||||
const results = fuse.multiTermsSearch(searchString);
|
||||
let filteredItems = [...this._allItems];
|
||||
|
||||
if (!filteredItems.length) {
|
||||
filteredItems.push(NO_ITEMS_AVAILABLE_ID);
|
||||
if (results) {
|
||||
const items: (PickerComboBoxItem | string)[] = results.map(
|
||||
(result) => result.item
|
||||
);
|
||||
|
||||
if (!items.length) {
|
||||
filteredItems.push(NO_ITEMS_AVAILABLE_ID);
|
||||
}
|
||||
|
||||
const additionalItems = this._getAdditionalItems();
|
||||
items.push(...additionalItems);
|
||||
|
||||
filteredItems = items;
|
||||
}
|
||||
|
||||
const additionalItems = this._getAdditionalItems();
|
||||
filteredItems.push(...additionalItems);
|
||||
|
||||
if (this.searchFn) {
|
||||
filteredItems = this.searchFn(
|
||||
searchString,
|
||||
@@ -636,25 +590,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _keyFunction = (item: PickerComboBoxItem | string) =>
|
||||
typeof item === "string" ? item : item?.id;
|
||||
|
||||
private _getInitialSelectedIndex() {
|
||||
if (!this._virtualizerElement || !this.value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const index = this._virtualizerElement.items.findIndex(
|
||||
(item) =>
|
||||
typeof item !== "string" &&
|
||||
(item as PickerComboBoxItem).id === this.value
|
||||
);
|
||||
|
||||
if (index === -1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
typeof item === "string" ? item : item.id;
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { consume } from "@lit/context";
|
||||
import { mdiClose, mdiMenuDown } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
@@ -8,10 +7,8 @@ import {
|
||||
type CSSResultGroup,
|
||||
type TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { localizeContext } from "../data/context";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-combo-box-item";
|
||||
import type { HaComboBoxItem } from "./ha-combo-box-item";
|
||||
import "./ha-icon-button";
|
||||
@@ -36,10 +33,6 @@ export class HaPickerField extends LitElement {
|
||||
|
||||
@property() public placeholder?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public unknown = false;
|
||||
|
||||
@property({ attribute: "unknown-item-text" }) public unknownItemText?: string;
|
||||
|
||||
@property({ attribute: "hide-clear-icon", type: Boolean })
|
||||
public hideClearIcon = false;
|
||||
|
||||
@@ -48,10 +41,6 @@ export class HaPickerField extends LitElement {
|
||||
|
||||
@query("ha-combo-box-item", true) public item!: HaComboBoxItem;
|
||||
|
||||
@state()
|
||||
@consume({ context: localizeContext, subscribe: true })
|
||||
private localize!: HomeAssistant["localize"];
|
||||
|
||||
public async focus() {
|
||||
await this.updateComplete;
|
||||
await this.item?.focus();
|
||||
@@ -72,12 +61,6 @@ export class HaPickerField extends LitElement {
|
||||
${this.placeholder}
|
||||
</span>
|
||||
`}
|
||||
${this.unknown
|
||||
? html`<div slot="supporting-text" class="unknown">
|
||||
${this.unknownItemText ||
|
||||
this.localize("ui.components.combo-box.unknown_item")}
|
||||
</div>`
|
||||
: nothing}
|
||||
${showClearIcon
|
||||
? html`
|
||||
<ha-icon-button
|
||||
@@ -159,10 +142,6 @@ export class HaPickerField extends LitElement {
|
||||
background-color: var(--mdc-theme-primary);
|
||||
}
|
||||
|
||||
:host([unknown]) ha-combo-box-item {
|
||||
background-color: var(--ha-color-fill-warning-quiet-resting);
|
||||
}
|
||||
|
||||
.clear {
|
||||
margin: 0 -8px;
|
||||
--mdc-icon-button-size: 32px;
|
||||
@@ -177,10 +156,6 @@ export class HaPickerField extends LitElement {
|
||||
color: var(--secondary-text-color);
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.unknown {
|
||||
color: var(--ha-color-on-warning-normal);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { fullEntitiesContext } from "../../data/context";
|
||||
import {
|
||||
subscribeEntityRegistry,
|
||||
type EntityRegistryEntry,
|
||||
} from "../../data/entity/entity_registry";
|
||||
} from "../../data/entity_registry";
|
||||
import type { Action } from "../../data/script";
|
||||
import { migrateAutomationAction } from "../../data/script";
|
||||
import type { ActionSelector } from "../../data/selector";
|
||||
|
||||
@@ -4,14 +4,14 @@ import { html, LitElement, 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 { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { EntitySources } from "../../data/entity_sources";
|
||||
import { fetchEntitySourcesWithCache } from "../../data/entity_sources";
|
||||
import type { AreaSelector } from "../../data/selector";
|
||||
import type { ConfigEntry } from "../../data/config_entries";
|
||||
import { getConfigEntries } from "../../data/config_entries";
|
||||
import type { DeviceRegistryEntry } from "../../data/device/device_registry";
|
||||
import { getDeviceIntegrationLookup } from "../../data/device/device_registry";
|
||||
import type { EntitySources } from "../../data/entity/entity_sources";
|
||||
import { fetchEntitySourcesWithCache } from "../../data/entity/entity_sources";
|
||||
import type { AreaSelector } from "../../data/selector";
|
||||
import {
|
||||
filterSelectorDevices,
|
||||
filterSelectorEntities,
|
||||
|
||||
@@ -5,13 +5,13 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
||||
import { getDeviceIntegrationLookup } from "../../data/device_registry";
|
||||
import type { EntitySources } from "../../data/entity_sources";
|
||||
import { fetchEntitySourcesWithCache } from "../../data/entity_sources";
|
||||
import type { DeviceSelector } from "../../data/selector";
|
||||
import type { ConfigEntry } from "../../data/config_entries";
|
||||
import { getConfigEntries } from "../../data/config_entries";
|
||||
import type { DeviceRegistryEntry } from "../../data/device/device_registry";
|
||||
import { getDeviceIntegrationLookup } from "../../data/device/device_registry";
|
||||
import type { EntitySources } from "../../data/entity/entity_sources";
|
||||
import { fetchEntitySourcesWithCache } from "../../data/entity/entity_sources";
|
||||
import type { DeviceSelector } from "../../data/selector";
|
||||
import {
|
||||
filterSelectorDevices,
|
||||
filterSelectorEntities,
|
||||
|
||||
@@ -4,12 +4,12 @@ import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { EntitySources } from "../../data/entity/entity_sources";
|
||||
import { fetchEntitySourcesWithCache } from "../../data/entity/entity_sources";
|
||||
import type { EntitySources } from "../../data/entity_sources";
|
||||
import { fetchEntitySourcesWithCache } from "../../data/entity_sources";
|
||||
import type { EntitySelector } from "../../data/selector";
|
||||
import {
|
||||
computeCreateDomains,
|
||||
filterSelectorEntities,
|
||||
computeCreateDomains,
|
||||
} from "../../data/selector";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../entity/ha-entities-picker";
|
||||
|
||||
@@ -4,14 +4,14 @@ import { html, LitElement, 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 { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { EntitySources } from "../../data/entity_sources";
|
||||
import { fetchEntitySourcesWithCache } from "../../data/entity_sources";
|
||||
import type { FloorSelector } from "../../data/selector";
|
||||
import type { ConfigEntry } from "../../data/config_entries";
|
||||
import { getConfigEntries } from "../../data/config_entries";
|
||||
import type { DeviceRegistryEntry } from "../../data/device/device_registry";
|
||||
import { getDeviceIntegrationLookup } from "../../data/device/device_registry";
|
||||
import type { EntitySources } from "../../data/entity/entity_sources";
|
||||
import { fetchEntitySourcesWithCache } from "../../data/entity/entity_sources";
|
||||
import type { FloorSelector } from "../../data/selector";
|
||||
import {
|
||||
filterSelectorDevices,
|
||||
filterSelectorEntities,
|
||||
|
||||
@@ -7,15 +7,15 @@ import { css, html, LitElement, 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/device_registry";
|
||||
import { getDeviceIntegrationLookup } from "../../data/device/device_registry";
|
||||
import type { EntitySources } from "../../data/entity/entity_sources";
|
||||
import { fetchEntitySourcesWithCache } from "../../data/entity/entity_sources";
|
||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
||||
import { getDeviceIntegrationLookup } from "../../data/device_registry";
|
||||
import type { EntitySources } from "../../data/entity_sources";
|
||||
import { fetchEntitySourcesWithCache } from "../../data/entity_sources";
|
||||
import type { TargetSelector } from "../../data/selector";
|
||||
import {
|
||||
computeCreateDomains,
|
||||
filterSelectorDevices,
|
||||
filterSelectorEntities,
|
||||
computeCreateDomains,
|
||||
} from "../../data/selector";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-target-picker";
|
||||
|
||||
@@ -21,13 +21,6 @@ interface ServiceComboBoxItem extends PickerComboBoxItem {
|
||||
service_id?: string;
|
||||
}
|
||||
|
||||
const SEARCH_KEYS = [
|
||||
{ name: "search_labels.name", weight: 10 },
|
||||
{ name: "search_labels.description", weight: 8 },
|
||||
{ name: "search_labels.domainName", weight: 6 },
|
||||
{ name: "search_labels.serviceId", weight: 3 },
|
||||
];
|
||||
|
||||
@customElement("ha-service-picker")
|
||||
class HaServicePicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -148,10 +141,6 @@ class HaServicePicker extends LitElement {
|
||||
this.hass.localize,
|
||||
this.hass.services
|
||||
)}
|
||||
.searchKeys=${SEARCH_KEYS}
|
||||
.unknownItemText=${this.hass.localize(
|
||||
"ui.components.service-picker.unknown"
|
||||
)}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
@@ -205,7 +194,9 @@ class HaServicePicker extends LitElement {
|
||||
secondary: description,
|
||||
domain_name: domainName,
|
||||
service_id: serviceId,
|
||||
search_labels: { serviceId, domainName, name, description },
|
||||
search_labels: [serviceId, domainName, name, description].filter(
|
||||
Boolean
|
||||
),
|
||||
sorting_label: serviceId,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,30 +13,19 @@ import memoizeOne from "memoize-one";
|
||||
import { ensureArray } from "../common/array/ensure-array";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { isValidEntityId } from "../common/entity/valid_entity_id";
|
||||
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import {
|
||||
areaFloorComboBoxKeys,
|
||||
getAreasAndFloors,
|
||||
type AreaFloorValue,
|
||||
type FloorComboBoxItem,
|
||||
} from "../data/area_floor_picker";
|
||||
} from "../data/area_floor";
|
||||
import { getConfigEntries, type ConfigEntry } from "../data/config_entries";
|
||||
import { labelsContext } from "../data/context";
|
||||
import {
|
||||
deviceComboBoxKeys,
|
||||
getDevices,
|
||||
type DevicePickerItem,
|
||||
} from "../data/device/device_picker";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../data/entity/entity";
|
||||
import {
|
||||
entityComboBoxKeys,
|
||||
getEntities,
|
||||
type EntityComboBoxItem,
|
||||
} from "../data/entity/entity_picker";
|
||||
import { getDevices, type DevicePickerItem } from "../data/device_registry";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../data/entity";
|
||||
import { getEntities, type EntityComboBoxItem } from "../data/entity_registry";
|
||||
import { domainToName } from "../data/integration";
|
||||
import { getLabels, labelComboBoxKeys } from "../data/label/label_picker";
|
||||
import type { LabelRegistryEntry } from "../data/label/label_registry";
|
||||
import { getLabels, type LabelRegistryEntry } from "../data/label_registry";
|
||||
import {
|
||||
areaMeetsFilter,
|
||||
deviceMeetsFilter,
|
||||
@@ -48,11 +37,7 @@ import {
|
||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||
import { isHelperDomain } from "../panels/config/helpers/const";
|
||||
import { showHelperDetailDialog } from "../panels/config/helpers/show-dialog-helper-detail";
|
||||
import {
|
||||
multiTermSearch,
|
||||
multiTermSortedSearch,
|
||||
type FuseWeightedKey,
|
||||
} from "../resources/fuseMultiTerm";
|
||||
import { HaFuse } from "../resources/fuse";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { brandsUrl } from "../util/brands-url";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||
@@ -128,16 +113,16 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _fuseIndexes = {
|
||||
area: memoizeOne((states: FloorComboBoxItem[]) =>
|
||||
this._createFuseIndex(states, areaFloorComboBoxKeys)
|
||||
this._createFuseIndex(states)
|
||||
),
|
||||
entity: memoizeOne((states: EntityComboBoxItem[]) =>
|
||||
this._createFuseIndex(states, entityComboBoxKeys)
|
||||
this._createFuseIndex(states)
|
||||
),
|
||||
device: memoizeOne((states: DevicePickerItem[]) =>
|
||||
this._createFuseIndex(states, deviceComboBoxKeys)
|
||||
this._createFuseIndex(states)
|
||||
),
|
||||
label: memoizeOne((states: PickerComboBoxItem[]) =>
|
||||
this._createFuseIndex(states, labelComboBoxKeys)
|
||||
this._createFuseIndex(states)
|
||||
),
|
||||
};
|
||||
|
||||
@@ -149,8 +134,8 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
private _createFuseIndex = (states, keys: FuseWeightedKey[]) =>
|
||||
Fuse.createIndex(keys, states);
|
||||
private _createFuseIndex = (states) =>
|
||||
Fuse.createIndex(["search_labels"], states);
|
||||
|
||||
protected render() {
|
||||
if (this.addOnTop) {
|
||||
@@ -743,14 +728,15 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
: undefined,
|
||||
undefined,
|
||||
`entity${SEPARATOR}`
|
||||
).sort(this._sortBySortingLabel);
|
||||
);
|
||||
|
||||
if (searchTerm) {
|
||||
entityItems = this._filterGroup(
|
||||
"entity",
|
||||
entityItems,
|
||||
searchTerm,
|
||||
entityComboBoxKeys
|
||||
(item: EntityComboBoxItem) =>
|
||||
item.stateObj?.entity_id === searchTerm
|
||||
) as EntityComboBoxItem[];
|
||||
}
|
||||
|
||||
@@ -776,15 +762,10 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
: undefined,
|
||||
undefined,
|
||||
`device${SEPARATOR}`
|
||||
).sort(this._sortBySortingLabel);
|
||||
);
|
||||
|
||||
if (searchTerm) {
|
||||
deviceItems = this._filterGroup(
|
||||
"device",
|
||||
deviceItems,
|
||||
searchTerm,
|
||||
deviceComboBoxKeys
|
||||
);
|
||||
deviceItems = this._filterGroup("device", deviceItems, searchTerm);
|
||||
}
|
||||
|
||||
if (!filterType && deviceItems.length) {
|
||||
@@ -818,9 +799,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
areasAndFloors = this._filterGroup(
|
||||
"area",
|
||||
areasAndFloors,
|
||||
searchTerm,
|
||||
areaFloorComboBoxKeys,
|
||||
false
|
||||
searchTerm
|
||||
) as FloorComboBoxItem[];
|
||||
}
|
||||
|
||||
@@ -862,15 +841,10 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
entityFilter,
|
||||
targetValue?.label_id ? ensureArray(targetValue.label_id) : undefined,
|
||||
`label${SEPARATOR}`
|
||||
).sort(this._sortBySortingLabel);
|
||||
);
|
||||
|
||||
if (searchTerm) {
|
||||
labels = this._filterGroup(
|
||||
"label",
|
||||
labels,
|
||||
searchTerm,
|
||||
labelComboBoxKeys
|
||||
);
|
||||
labels = this._filterGroup("label", labels, searchTerm);
|
||||
}
|
||||
|
||||
if (!filterType && labels.length) {
|
||||
@@ -889,24 +863,40 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
type: TargetType,
|
||||
items: (FloorComboBoxItem | PickerComboBoxItem | EntityComboBoxItem)[],
|
||||
searchTerm: string,
|
||||
weightedKeys: FuseWeightedKey[],
|
||||
sort = true
|
||||
checkExact?: (
|
||||
item: FloorComboBoxItem | PickerComboBoxItem | EntityComboBoxItem
|
||||
) => boolean
|
||||
) {
|
||||
const fuseIndex = this._fuseIndexes[type](items);
|
||||
const fuse = new HaFuse(
|
||||
items,
|
||||
{
|
||||
shouldSort: false,
|
||||
minMatchCharLength: Math.min(searchTerm.length, 2),
|
||||
},
|
||||
fuseIndex
|
||||
);
|
||||
|
||||
if (sort) {
|
||||
return multiTermSortedSearch(
|
||||
items,
|
||||
searchTerm,
|
||||
weightedKeys,
|
||||
(item) => item.id,
|
||||
fuseIndex
|
||||
);
|
||||
const results = fuse.multiTermsSearch(searchTerm);
|
||||
let filteredItems = items;
|
||||
if (results) {
|
||||
filteredItems = results.map((result) => result.item);
|
||||
}
|
||||
|
||||
return multiTermSearch(items, searchTerm, weightedKeys, fuseIndex, {
|
||||
ignoreLocation: true,
|
||||
});
|
||||
if (!checkExact) {
|
||||
return filteredItems;
|
||||
}
|
||||
|
||||
// If there is exact match for entity id, put it first
|
||||
const index = filteredItems.findIndex((item) => checkExact(item));
|
||||
if (index === -1) {
|
||||
return filteredItems;
|
||||
}
|
||||
|
||||
const [exactMatch] = filteredItems.splice(index, 1);
|
||||
filteredItems.unshift(exactMatch);
|
||||
|
||||
return filteredItems;
|
||||
}
|
||||
|
||||
private _getAdditionalItems = () => this._getCreateItems(this.createDomains);
|
||||
@@ -962,7 +952,10 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
let hasFloor = false;
|
||||
let rtl = false;
|
||||
let showEntityId = false;
|
||||
|
||||
if (type === "area" || type === "floor") {
|
||||
item.id = item[type]?.[`${type}_id`];
|
||||
|
||||
rtl = computeRTL(this.hass);
|
||||
hasFloor =
|
||||
type === "area" && !!(item as FloorComboBoxItem).area?.floor_id;
|
||||
@@ -1068,13 +1061,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
term: html`<b>‘${search}’</b>`,
|
||||
});
|
||||
|
||||
private _sortBySortingLabel = (entityA, entityB) =>
|
||||
caseInsensitiveStringCompare(
|
||||
(entityA as PickerComboBoxItem).sorting_label!,
|
||||
(entityB as PickerComboBoxItem).sorting_label!,
|
||||
this.hass?.locale.language ?? navigator.language
|
||||
);
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
.add-target-wrapper {
|
||||
|
||||
@@ -13,7 +13,6 @@ export class HaToast extends Snackbar {
|
||||
}
|
||||
|
||||
.mdc-snackbar {
|
||||
z-index: 10;
|
||||
margin: 8px;
|
||||
right: calc(8px + var(--safe-area-inset-right));
|
||||
bottom: calc(8px + var(--safe-area-inset-bottom));
|
||||
|
||||
@@ -7,7 +7,7 @@ import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { supportsFeature } from "../../common/entity/supports-feature";
|
||||
import type { EntityRegistryDisplayEntry } from "../../data/entity/entity_registry";
|
||||
import type { EntityRegistryDisplayEntry } from "../../data/entity_registry";
|
||||
import { extractApiErrorMessage } from "../../data/hassio/common";
|
||||
import {
|
||||
type MediaPlayerEntity,
|
||||
|
||||
@@ -17,7 +17,7 @@ import { until } from "lit/directives/until";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { slugify } from "../../common/string/slugify";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import { isUnavailableState } from "../../data/entity/entity";
|
||||
import { isUnavailableState } from "../../data/entity";
|
||||
import type {
|
||||
MediaPickedEvent,
|
||||
MediaPlayerBrowseAction,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../../../data/entity/entity";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../../../data/entity";
|
||||
import type { TargetType } from "../../../data/target";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "../../device/ha-device-picker";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../../data/entity/entity";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../../data/entity";
|
||||
import type { TargetType, TargetTypeFloorless } from "../../data/target";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "../device/ha-device-picker";
|
||||
|
||||
@@ -23,11 +23,11 @@ import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import type { AreaRegistryEntry } from "../../data/area_registry";
|
||||
import { getConfigEntry } from "../../data/config_entries";
|
||||
import { labelsContext } from "../../data/context";
|
||||
import type { DeviceRegistryEntry } from "../../data/device/device_registry";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../../data/entity/entity";
|
||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../../data/entity";
|
||||
import type { FloorRegistryEntry } from "../../data/floor_registry";
|
||||
import { domainToName } from "../../data/integration";
|
||||
import type { LabelRegistryEntry } from "../../data/label/label_registry";
|
||||
import type { LabelRegistryEntry } from "../../data/label_registry";
|
||||
import {
|
||||
areaMeetsFilter,
|
||||
deviceMeetsFilter,
|
||||
|
||||
@@ -23,7 +23,7 @@ import { slugify } from "../../common/string/slugify";
|
||||
import { getConfigEntry } from "../../data/config_entries";
|
||||
import { labelsContext } from "../../data/context";
|
||||
import { domainToName } from "../../data/integration";
|
||||
import type { LabelRegistryEntry } from "../../data/label/label_registry";
|
||||
import type { LabelRegistryEntry } from "../../data/label_registry";
|
||||
import type { TargetType } from "../../data/target";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { brandsUrl } from "../../util/brands-url";
|
||||
|
||||
@@ -17,7 +17,6 @@ export class HaTraceBlueprintConfig extends LitElement {
|
||||
return html`
|
||||
<ha-code-editor
|
||||
.value=${dump(this.trace.blueprint_inputs || "").trimRight()}
|
||||
.hass=${this.hass}
|
||||
read-only
|
||||
dir="ltr"
|
||||
></ha-code-editor>
|
||||
|
||||
@@ -17,7 +17,6 @@ export class HaTraceConfig extends LitElement {
|
||||
return html`
|
||||
<ha-code-editor
|
||||
.value=${dump(this.trace.config).trimRight()}
|
||||
.hass=${this.hass}
|
||||
read-only
|
||||
dir="ltr"
|
||||
></ha-code-editor>
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
import { consume } from "@lit/context";
|
||||
import { dump } from "js-yaml";
|
||||
import { consume } from "@lit/context";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
|
||||
import { describeCondition, describeTrigger } from "../../data/automation_i18n";
|
||||
import {
|
||||
floorsContext,
|
||||
fullEntitiesContext,
|
||||
labelsContext,
|
||||
} from "../../data/context";
|
||||
import type { EntityRegistryEntry } from "../../data/entity/entity_registry";
|
||||
import type { FloorRegistryEntry } from "../../data/floor_registry";
|
||||
import type { LabelRegistryEntry } from "../../data/label/label_registry";
|
||||
import "../ha-code-editor";
|
||||
import "../ha-icon-button";
|
||||
import "./hat-logbook-note";
|
||||
import type { LogbookEntry } from "../../data/logbook";
|
||||
import { describeAction } from "../../data/script_i18n";
|
||||
import type {
|
||||
ActionTraceStep,
|
||||
ChooseActionTraceStep,
|
||||
@@ -23,12 +16,19 @@ import type {
|
||||
} from "../../data/trace";
|
||||
import { getDataFromPath } from "../../data/trace";
|
||||
import "../../panels/logbook/ha-logbook-renderer";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-code-editor";
|
||||
import "../ha-icon-button";
|
||||
import "./hat-logbook-note";
|
||||
import type { NodeInfo } from "./hat-script-graph";
|
||||
import { traceTabStyles } from "./trace-tab-styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { NodeInfo } from "./hat-script-graph";
|
||||
import { describeCondition, describeTrigger } from "../../data/automation_i18n";
|
||||
import type { EntityRegistryEntry } from "../../data/entity_registry";
|
||||
import type { LabelRegistryEntry } from "../../data/label_registry";
|
||||
import type { FloorRegistryEntry } from "../../data/floor_registry";
|
||||
import {
|
||||
floorsContext,
|
||||
fullEntitiesContext,
|
||||
labelsContext,
|
||||
} from "../../data/context";
|
||||
import { describeAction } from "../../data/script_i18n";
|
||||
|
||||
const TRACE_PATH_TABS = [
|
||||
"step_config",
|
||||
@@ -278,7 +278,6 @@ export class HaTracePathDetails extends LitElement {
|
||||
return config
|
||||
? html`<ha-code-editor
|
||||
.value=${dump(config).trimEnd()}
|
||||
.hass=${this.hass}
|
||||
read-only
|
||||
dir="ltr"
|
||||
></ha-code-editor>`
|
||||
|
||||
@@ -20,14 +20,14 @@ import {
|
||||
fullEntitiesContext,
|
||||
labelsContext,
|
||||
} from "../../data/context";
|
||||
import type { EntityRegistryEntry } from "../../data/entity/entity_registry";
|
||||
import type { EntityRegistryEntry } from "../../data/entity_registry";
|
||||
import type { FloorRegistryEntry } from "../../data/floor_registry";
|
||||
import type { LabelRegistryEntry } from "../../data/label/label_registry";
|
||||
import type { LabelRegistryEntry } from "../../data/label_registry";
|
||||
import type { LogbookEntry } from "../../data/logbook";
|
||||
import type {
|
||||
ChooseAction,
|
||||
IfAction,
|
||||
Option,
|
||||
IfAction,
|
||||
ParallelAction,
|
||||
RepeatAction,
|
||||
SequenceAction,
|
||||
|
||||
@@ -17,12 +17,6 @@ interface UserComboBoxItem extends PickerComboBoxItem {
|
||||
user?: User;
|
||||
}
|
||||
|
||||
const SEARCH_KEYS = [
|
||||
{ name: "primary", weight: 10 },
|
||||
{ name: "search_labels.username", weight: 6 },
|
||||
{ name: "id", weight: 3 },
|
||||
];
|
||||
|
||||
@customElement("ha-user-picker")
|
||||
class HaUserPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -115,7 +109,9 @@ class HaUserPicker extends LitElement {
|
||||
id: user.id,
|
||||
primary: user.name,
|
||||
domain_name: user.name,
|
||||
search_labels: { username: user.username },
|
||||
search_labels: [user.name, user.id, user.username].filter(
|
||||
Boolean
|
||||
) as string[],
|
||||
sorting_label: user.name,
|
||||
user,
|
||||
}));
|
||||
@@ -138,10 +134,6 @@ class HaUserPicker extends LitElement {
|
||||
.getItems=${this._getItems}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
.rowRenderer=${this._rowRenderer}
|
||||
.searchKeys=${SEARCH_KEYS}
|
||||
.unknownItemText=${this.hass.localize(
|
||||
"ui.components.user-picker.unknown"
|
||||
)}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
|
||||
@@ -13,7 +13,7 @@ import type {
|
||||
import { supportsFeature } from "../common/entity/supports-feature";
|
||||
import { showEnterCodeDialog } from "../dialogs/enter-code/show-enter-code-dialog";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { getExtendedEntityRegistryEntry } from "./entity/entity_registry";
|
||||
import { getExtendedEntityRegistryEntry } from "./entity_registry";
|
||||
|
||||
export const FORMAT_TEXT = "text";
|
||||
export const FORMAT_NUMBER = "number";
|
||||
|
||||
@@ -4,16 +4,15 @@ import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { computeFloorName } from "../common/entity/compute_floor_name";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "../components/device/ha-device-picker";
|
||||
import type { PickerComboBoxItem } from "../components/ha-picker-combo-box";
|
||||
import type { FuseWeightedKey } from "../resources/fuseMultiTerm";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { AreaRegistryEntry } from "./area_registry";
|
||||
import {
|
||||
getDeviceEntityDisplayLookup,
|
||||
type DeviceEntityDisplayLookup,
|
||||
type DeviceRegistryEntry,
|
||||
} from "./device/device_registry";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "./entity/entity";
|
||||
import type { EntityRegistryDisplayEntry } from "./entity/entity_registry";
|
||||
} from "./device_registry";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "./entity";
|
||||
import type { EntityRegistryDisplayEntry } from "./entity_registry";
|
||||
import type { FloorRegistryEntry } from "./floor_registry";
|
||||
|
||||
export interface FloorComboBoxItem extends PickerComboBoxItem {
|
||||
@@ -27,8 +26,7 @@ export interface FloorNestedComboBoxItem extends PickerComboBoxItem {
|
||||
areas: FloorComboBoxItem[];
|
||||
}
|
||||
|
||||
export interface UnassignedAreasFloorComboBoxItem {
|
||||
id: undefined;
|
||||
export interface UnassignedAreasFloorComboBoxItem extends PickerComboBoxItem {
|
||||
areas: FloorComboBoxItem[];
|
||||
}
|
||||
|
||||
@@ -100,29 +98,6 @@ export const getAreasAndFloors = (
|
||||
excludeFloors
|
||||
) as FloorComboBoxItem[];
|
||||
|
||||
export const areaFloorComboBoxKeys: FuseWeightedKey[] = [
|
||||
{
|
||||
name: "search_labels.name",
|
||||
weight: 10,
|
||||
},
|
||||
{
|
||||
name: "search_labels.aliases",
|
||||
weight: 8,
|
||||
},
|
||||
{
|
||||
name: "search_labels.floorName",
|
||||
weight: 6,
|
||||
},
|
||||
{
|
||||
name: "search_labels.relatedAreas",
|
||||
weight: 4,
|
||||
},
|
||||
{
|
||||
name: "search_labels.id",
|
||||
weight: 3,
|
||||
},
|
||||
];
|
||||
|
||||
const getAreasAndFloorsItems = (
|
||||
states: HomeAssistant["states"],
|
||||
haFloors: HomeAssistant["floors"],
|
||||
@@ -329,12 +304,12 @@ const getAreasAndFloorsItems = (
|
||||
primary: floorName,
|
||||
floor: floor,
|
||||
icon: floor.icon || undefined,
|
||||
search_labels: {
|
||||
id: floor.floor_id,
|
||||
name: floorName || null,
|
||||
aliases: floor.aliases.join(", ") || null,
|
||||
relatedAreas: areaSearchLabels.join(" ") || null,
|
||||
},
|
||||
search_labels: [
|
||||
floor.floor_id,
|
||||
floorName,
|
||||
...floor.aliases,
|
||||
...areaSearchLabels,
|
||||
],
|
||||
};
|
||||
|
||||
items.push(floorItem);
|
||||
@@ -347,12 +322,11 @@ const getAreasAndFloorsItems = (
|
||||
primary: areaName || area.area_id,
|
||||
area: area,
|
||||
icon: area.icon || undefined,
|
||||
search_labels: {
|
||||
id: area.area_id,
|
||||
name: areaName || null,
|
||||
aliases: area.aliases.join(", ") || null,
|
||||
floorName: floorName || null,
|
||||
},
|
||||
search_labels: [
|
||||
area.area_id,
|
||||
...(areaName ? [areaName] : []),
|
||||
...area.aliases,
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
@@ -365,24 +339,19 @@ const getAreasAndFloorsItems = (
|
||||
|
||||
const unassignedAreaItems = hierarchy.areas.map((areaId) => {
|
||||
const area = haAreas[areaId];
|
||||
const areaName = computeAreaName(area);
|
||||
const areaName = computeAreaName(area) || area.area_id;
|
||||
return {
|
||||
id: formatId({ id: area.area_id, type: "area" }),
|
||||
type: "area" as const,
|
||||
primary: areaName || area.area_id,
|
||||
primary: areaName,
|
||||
area: area,
|
||||
icon: area.icon || undefined,
|
||||
search_labels: {
|
||||
id: area.area_id,
|
||||
name: areaName || null,
|
||||
aliases: area.aliases.join(", ") || null,
|
||||
},
|
||||
search_labels: [area.area_id, areaName, ...area.aliases],
|
||||
};
|
||||
});
|
||||
|
||||
if (nested && unassignedAreaItems.length) {
|
||||
items.push({
|
||||
id: undefined,
|
||||
areas: unassignedAreaItems,
|
||||
} as UnassignedAreasFloorComboBoxItem);
|
||||
} else {
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { DeviceRegistryEntry } from "./device/device_registry";
|
||||
import type { DeviceRegistryEntry } from "./device_registry";
|
||||
import type {
|
||||
EntityRegistryDisplayEntry,
|
||||
EntityRegistryEntry,
|
||||
} from "./entity/entity_registry";
|
||||
} from "./entity_registry";
|
||||
import type { RegistryEntry } from "./registry";
|
||||
|
||||
export { subscribeAreaRegistry } from "./ws-area_registry";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { supportsFeature } from "../common/entity/supports-feature";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { UNAVAILABLE } from "./entity/entity";
|
||||
import { supportsFeature } from "../common/entity/supports-feature";
|
||||
import { UNAVAILABLE } from "./entity";
|
||||
|
||||
export const enum AssistSatelliteEntityFeature {
|
||||
ANNOUNCE = 1,
|
||||
|
||||
@@ -12,10 +12,7 @@ import type { Context, HomeAssistant } from "../types";
|
||||
import type { BlueprintInput } from "./blueprint";
|
||||
import type { ConditionDescription } from "./condition";
|
||||
import { CONDITION_BUILDING_BLOCKS } from "./condition";
|
||||
import type {
|
||||
DeviceCondition,
|
||||
DeviceTrigger,
|
||||
} from "./device/device_automation";
|
||||
import type { DeviceCondition, DeviceTrigger } from "./device_automation";
|
||||
import type { Action, Field, MODES } from "./script";
|
||||
import { migrateAutomationAction } from "./script";
|
||||
import type { TriggerDescription } from "./trigger";
|
||||
|
||||
@@ -26,15 +26,12 @@ import type {
|
||||
Trigger,
|
||||
} from "./automation";
|
||||
import { getConditionDomain, getConditionObjectId } from "./condition";
|
||||
import type {
|
||||
DeviceCondition,
|
||||
DeviceTrigger,
|
||||
} from "./device/device_automation";
|
||||
import type { DeviceCondition, DeviceTrigger } from "./device_automation";
|
||||
import {
|
||||
localizeDeviceAutomationCondition,
|
||||
localizeDeviceAutomationTrigger,
|
||||
} from "./device/device_automation";
|
||||
import type { EntityRegistryEntry } from "./entity/entity_registry";
|
||||
} from "./device_automation";
|
||||
import type { EntityRegistryEntry } from "./entity_registry";
|
||||
import type { FrontendLocaleData } from "./translation";
|
||||
import { getTriggerDomain, getTriggerObjectId, isTriggerList } from "./trigger";
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { getColorByIndex } from "../common/color/colors";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { isUnavailableState } from "./entity/entity";
|
||||
import { isUnavailableState } from "./entity";
|
||||
|
||||
export interface Calendar {
|
||||
entity_id: string;
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import { createContext } from "@lit/context";
|
||||
import type { HassConfig } from "home-assistant-js-websocket";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { EntityRegistryEntry } from "./entity/entity_registry";
|
||||
import type { LabelRegistryEntry } from "./label/label_registry";
|
||||
import type { EntityRegistryEntry } from "./entity_registry";
|
||||
import type { LabelRegistryEntry } from "./label_registry";
|
||||
|
||||
export const connectionContext =
|
||||
createContext<HomeAssistant["connection"]>("connection");
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import type {
|
||||
import { stateActive } from "../common/entity/state_active";
|
||||
import { supportsFeature } from "../common/entity/supports-feature";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { UNAVAILABLE } from "./entity/entity";
|
||||
import { UNAVAILABLE } from "./entity";
|
||||
|
||||
export const enum CoverEntityFeature {
|
||||
OPEN = 1,
|
||||
|
||||
@@ -1,182 +0,0 @@
|
||||
import { computeAreaName } from "../../common/entity/compute_area_name";
|
||||
import { computeDeviceNameDisplay } from "../../common/entity/compute_device_name";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { getDeviceContext } from "../../common/entity/context/get_device_context";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "../../components/device/ha-device-picker";
|
||||
import type { PickerComboBoxItem } from "../../components/ha-picker-combo-box";
|
||||
import type { FuseWeightedKey } from "../../resources/fuseMultiTerm";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { ConfigEntry } from "../config_entries";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../entity/entity";
|
||||
import { domainToName } from "../integration";
|
||||
import {
|
||||
getDeviceEntityDisplayLookup,
|
||||
type DeviceEntityDisplayLookup,
|
||||
} from "./device_registry";
|
||||
|
||||
export interface DevicePickerItem extends PickerComboBoxItem {
|
||||
domain?: string;
|
||||
domain_name?: string;
|
||||
}
|
||||
|
||||
export const deviceComboBoxKeys: FuseWeightedKey[] = [
|
||||
{
|
||||
name: "search_labels.deviceName",
|
||||
weight: 10,
|
||||
},
|
||||
{
|
||||
name: "search_labels.areaName",
|
||||
weight: 8,
|
||||
},
|
||||
{
|
||||
name: "search_labels.domainName",
|
||||
weight: 4,
|
||||
},
|
||||
{
|
||||
name: "search_labels.domain",
|
||||
weight: 4,
|
||||
},
|
||||
];
|
||||
|
||||
export const getDevices = (
|
||||
hass: HomeAssistant,
|
||||
configEntryLookup: Record<string, ConfigEntry>,
|
||||
includeDomains?: string[],
|
||||
excludeDomains?: string[],
|
||||
includeDeviceClasses?: string[],
|
||||
deviceFilter?: HaDevicePickerDeviceFilterFunc,
|
||||
entityFilter?: HaEntityPickerEntityFilterFunc,
|
||||
excludeDevices?: string[],
|
||||
value?: string,
|
||||
idPrefix = ""
|
||||
): DevicePickerItem[] => {
|
||||
const devices = Object.values(hass.devices);
|
||||
const entities = Object.values(hass.entities);
|
||||
|
||||
let deviceEntityLookup: DeviceEntityDisplayLookup = {};
|
||||
|
||||
if (
|
||||
includeDomains ||
|
||||
excludeDomains ||
|
||||
includeDeviceClasses ||
|
||||
entityFilter
|
||||
) {
|
||||
deviceEntityLookup = getDeviceEntityDisplayLookup(entities);
|
||||
}
|
||||
|
||||
let inputDevices = devices.filter(
|
||||
(device) => device.id === value || !device.disabled_by
|
||||
);
|
||||
|
||||
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))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
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))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (excludeDevices) {
|
||||
inputDevices = inputDevices.filter(
|
||||
(device) => !excludeDevices!.includes(device.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 = hass.states[entity.entity_id];
|
||||
if (!stateObj) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
stateObj.attributes.device_class &&
|
||||
includeDeviceClasses.includes(stateObj.attributes.device_class)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (entityFilter) {
|
||||
inputDevices = inputDevices.filter((device) => {
|
||||
const devEntities = deviceEntityLookup[device.id];
|
||||
if (!devEntities || !devEntities.length) {
|
||||
return false;
|
||||
}
|
||||
return devEntities.some((entity) => {
|
||||
const stateObj = hass.states[entity.entity_id];
|
||||
if (!stateObj) {
|
||||
return false;
|
||||
}
|
||||
return entityFilter(stateObj);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (deviceFilter) {
|
||||
inputDevices = inputDevices.filter(
|
||||
(device) =>
|
||||
// We always want to include the device of the current value
|
||||
device.id === value || deviceFilter!(device)
|
||||
);
|
||||
}
|
||||
|
||||
const outputDevices = inputDevices.map<DevicePickerItem>((device) => {
|
||||
const deviceName = computeDeviceNameDisplay(
|
||||
device,
|
||||
hass,
|
||||
deviceEntityLookup[device.id]
|
||||
);
|
||||
|
||||
const { area } = getDeviceContext(device, hass);
|
||||
|
||||
const areaName = area ? computeAreaName(area) : undefined;
|
||||
|
||||
const configEntry = device.primary_config_entry
|
||||
? configEntryLookup?.[device.primary_config_entry]
|
||||
: undefined;
|
||||
|
||||
const domain = configEntry?.domain;
|
||||
const domainName = domain ? domainToName(hass.localize, domain) : undefined;
|
||||
const primary =
|
||||
deviceName || hass.localize("ui.components.device-picker.unnamed_device");
|
||||
|
||||
return {
|
||||
id: `${idPrefix}${device.id}`,
|
||||
label: "",
|
||||
primary,
|
||||
secondary: areaName,
|
||||
domain: configEntry?.domain,
|
||||
domain_name: domainName,
|
||||
search_labels: {
|
||||
deviceName,
|
||||
areaName: areaName || null,
|
||||
domain: domain || null,
|
||||
domainName: domainName || null,
|
||||
},
|
||||
sorting_label: [primary, areaName, domainName].filter(Boolean).join("_"),
|
||||
};
|
||||
});
|
||||
|
||||
return outputDevices;
|
||||
};
|
||||
@@ -1,169 +0,0 @@
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { ConfigEntry } from "../config_entries";
|
||||
import type {
|
||||
EntityRegistryDisplayEntry,
|
||||
EntityRegistryEntry,
|
||||
} from "../entity/entity_registry";
|
||||
import type { EntitySources } from "../entity/entity_sources";
|
||||
import type { RegistryEntry } from "../registry";
|
||||
|
||||
export {
|
||||
fetchDeviceRegistry,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../ws-device_registry";
|
||||
|
||||
export interface DeviceRegistryEntry extends RegistryEntry {
|
||||
id: string;
|
||||
config_entries: string[];
|
||||
config_entries_subentries: Record<string, (string | null)[]>;
|
||||
connections: [string, string][];
|
||||
identifiers: [string, string][];
|
||||
manufacturer: string | null;
|
||||
model: string | null;
|
||||
model_id: string | null;
|
||||
name: string | null;
|
||||
labels: string[];
|
||||
sw_version: string | null;
|
||||
hw_version: string | null;
|
||||
serial_number: string | null;
|
||||
via_device_id: string | null;
|
||||
area_id: string | null;
|
||||
name_by_user: string | null;
|
||||
entry_type: "service" | null;
|
||||
disabled_by: "user" | "integration" | "config_entry" | null;
|
||||
configuration_url: string | null;
|
||||
primary_config_entry: string | null;
|
||||
}
|
||||
|
||||
export type DeviceEntityDisplayLookup = Record<
|
||||
string,
|
||||
EntityRegistryDisplayEntry[]
|
||||
>;
|
||||
|
||||
export type DeviceEntityLookup<
|
||||
T extends EntityRegistryEntry | EntityRegistryDisplayEntry =
|
||||
| EntityRegistryEntry
|
||||
| EntityRegistryDisplayEntry,
|
||||
> = Record<string, T[]>;
|
||||
|
||||
export interface DeviceRegistryEntryMutableParams {
|
||||
area_id?: string | null;
|
||||
name_by_user?: string | null;
|
||||
disabled_by?: string | null;
|
||||
labels?: string[];
|
||||
}
|
||||
|
||||
export const fallbackDeviceName = (
|
||||
hass: HomeAssistant,
|
||||
entities: EntityRegistryEntry[] | EntityRegistryDisplayEntry[] | string[]
|
||||
) => {
|
||||
for (const entity of entities || []) {
|
||||
const entityId = typeof entity === "string" ? entity : entity.entity_id;
|
||||
const stateObj = hass.states[entityId];
|
||||
if (stateObj) {
|
||||
return computeStateName(stateObj);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const devicesInArea = (devices: DeviceRegistryEntry[], areaId: string) =>
|
||||
devices.filter((device) => device.area_id === areaId);
|
||||
|
||||
export const updateDeviceRegistryEntry = (
|
||||
hass: HomeAssistant,
|
||||
deviceId: string,
|
||||
updates: Partial<DeviceRegistryEntryMutableParams>
|
||||
) =>
|
||||
hass.callWS<DeviceRegistryEntry>({
|
||||
type: "config/device_registry/update",
|
||||
device_id: deviceId,
|
||||
...updates,
|
||||
});
|
||||
|
||||
export const removeConfigEntryFromDevice = (
|
||||
hass: HomeAssistant,
|
||||
deviceId: string,
|
||||
configEntryId: string
|
||||
) =>
|
||||
hass.callWS<DeviceRegistryEntry>({
|
||||
type: "config/device_registry/remove_config_entry",
|
||||
device_id: deviceId,
|
||||
config_entry_id: configEntryId,
|
||||
});
|
||||
|
||||
export const sortDeviceRegistryByName = (
|
||||
entries: DeviceRegistryEntry[],
|
||||
language: string
|
||||
) =>
|
||||
entries.sort((entry1, entry2) =>
|
||||
caseInsensitiveStringCompare(entry1.name || "", entry2.name || "", language)
|
||||
);
|
||||
|
||||
export const getDeviceEntityLookup = (
|
||||
entities: (EntityRegistryEntry | EntityRegistryDisplayEntry)[]
|
||||
): DeviceEntityLookup => {
|
||||
const deviceEntityLookup: DeviceEntityLookup = {};
|
||||
for (const entity of entities) {
|
||||
if (!entity.device_id) {
|
||||
continue;
|
||||
}
|
||||
if (!(entity.device_id in deviceEntityLookup)) {
|
||||
deviceEntityLookup[entity.device_id] = [];
|
||||
}
|
||||
deviceEntityLookup[entity.device_id].push(entity);
|
||||
}
|
||||
return deviceEntityLookup;
|
||||
};
|
||||
|
||||
export const getDeviceEntityDisplayLookup = (
|
||||
entities: EntityRegistryDisplayEntry[]
|
||||
): DeviceEntityDisplayLookup => {
|
||||
const deviceEntityLookup: DeviceEntityDisplayLookup = {};
|
||||
for (const entity of entities) {
|
||||
if (!entity.device_id) {
|
||||
continue;
|
||||
}
|
||||
if (!(entity.device_id in deviceEntityLookup)) {
|
||||
deviceEntityLookup[entity.device_id] = [];
|
||||
}
|
||||
deviceEntityLookup[entity.device_id].push(entity);
|
||||
}
|
||||
return deviceEntityLookup;
|
||||
};
|
||||
|
||||
export const getDeviceIntegrationLookup = (
|
||||
entitySources: EntitySources,
|
||||
entities: EntityRegistryDisplayEntry[] | EntityRegistryEntry[],
|
||||
devices?: DeviceRegistryEntry[],
|
||||
configEntries?: ConfigEntry[]
|
||||
): Record<string, Set<string>> => {
|
||||
const deviceIntegrations: Record<string, Set<string>> = {};
|
||||
|
||||
for (const entity of entities) {
|
||||
const source = entitySources[entity.entity_id];
|
||||
if (!source?.domain || entity.device_id === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
deviceIntegrations[entity.device_id!] =
|
||||
deviceIntegrations[entity.device_id!] || new Set<string>();
|
||||
deviceIntegrations[entity.device_id!].add(source.domain);
|
||||
}
|
||||
// Lookup devices that have no entities
|
||||
if (devices && configEntries) {
|
||||
for (const device of devices) {
|
||||
for (const config_entry_id of device.config_entries) {
|
||||
const entry = configEntries.find((e) => e.entry_id === config_entry_id);
|
||||
if (entry?.domain) {
|
||||
deviceIntegrations[device.id] =
|
||||
deviceIntegrations[device.id] || new Set<string>();
|
||||
deviceIntegrations[device.id].add(entry.domain);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return deviceIntegrations;
|
||||
};
|
||||
@@ -1,14 +1,14 @@
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import type { HaFormSchema } from "../../components/ha-form/types";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { BaseTrigger } from "../automation";
|
||||
import { migrateAutomationTrigger } from "../automation";
|
||||
import type { EntityRegistryEntry } from "../entity/entity_registry";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import type { HaFormSchema } from "../components/ha-form/types";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { BaseTrigger } from "./automation";
|
||||
import { migrateAutomationTrigger } from "./automation";
|
||||
import type { EntityRegistryEntry } from "./entity_registry";
|
||||
import {
|
||||
computeEntityRegistryName,
|
||||
entityRegistryByEntityId,
|
||||
entityRegistryById,
|
||||
} from "../entity/entity_registry";
|
||||
} from "./entity_registry";
|
||||
|
||||
export interface DeviceAutomation {
|
||||
alias?: string;
|
||||
@@ -0,0 +1,322 @@
|
||||
import { computeAreaName } from "../common/entity/compute_area_name";
|
||||
import { computeDeviceNameDisplay } from "../common/entity/compute_device_name";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import { getDeviceContext } from "../common/entity/context/get_device_context";
|
||||
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "../components/device/ha-device-picker";
|
||||
import type { PickerComboBoxItem } from "../components/ha-picker-combo-box";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { ConfigEntry } from "./config_entries";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "./entity";
|
||||
import type {
|
||||
EntityRegistryDisplayEntry,
|
||||
EntityRegistryEntry,
|
||||
} from "./entity_registry";
|
||||
import type { EntitySources } from "./entity_sources";
|
||||
import { domainToName } from "./integration";
|
||||
import type { RegistryEntry } from "./registry";
|
||||
|
||||
export {
|
||||
fetchDeviceRegistry,
|
||||
subscribeDeviceRegistry,
|
||||
} from "./ws-device_registry";
|
||||
|
||||
export interface DeviceRegistryEntry extends RegistryEntry {
|
||||
id: string;
|
||||
config_entries: string[];
|
||||
config_entries_subentries: Record<string, (string | null)[]>;
|
||||
connections: [string, string][];
|
||||
identifiers: [string, string][];
|
||||
manufacturer: string | null;
|
||||
model: string | null;
|
||||
model_id: string | null;
|
||||
name: string | null;
|
||||
labels: string[];
|
||||
sw_version: string | null;
|
||||
hw_version: string | null;
|
||||
serial_number: string | null;
|
||||
via_device_id: string | null;
|
||||
area_id: string | null;
|
||||
name_by_user: string | null;
|
||||
entry_type: "service" | null;
|
||||
disabled_by: "user" | "integration" | "config_entry" | null;
|
||||
configuration_url: string | null;
|
||||
primary_config_entry: string | null;
|
||||
}
|
||||
|
||||
export type DeviceEntityDisplayLookup = Record<
|
||||
string,
|
||||
EntityRegistryDisplayEntry[]
|
||||
>;
|
||||
|
||||
export type DeviceEntityLookup<
|
||||
T extends EntityRegistryEntry | EntityRegistryDisplayEntry =
|
||||
| EntityRegistryEntry
|
||||
| EntityRegistryDisplayEntry,
|
||||
> = Record<string, T[]>;
|
||||
|
||||
export interface DeviceRegistryEntryMutableParams {
|
||||
area_id?: string | null;
|
||||
name_by_user?: string | null;
|
||||
disabled_by?: string | null;
|
||||
labels?: string[];
|
||||
}
|
||||
|
||||
export const fallbackDeviceName = (
|
||||
hass: HomeAssistant,
|
||||
entities: EntityRegistryEntry[] | EntityRegistryDisplayEntry[] | string[]
|
||||
) => {
|
||||
for (const entity of entities || []) {
|
||||
const entityId = typeof entity === "string" ? entity : entity.entity_id;
|
||||
const stateObj = hass.states[entityId];
|
||||
if (stateObj) {
|
||||
return computeStateName(stateObj);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const devicesInArea = (devices: DeviceRegistryEntry[], areaId: string) =>
|
||||
devices.filter((device) => device.area_id === areaId);
|
||||
|
||||
export const updateDeviceRegistryEntry = (
|
||||
hass: HomeAssistant,
|
||||
deviceId: string,
|
||||
updates: Partial<DeviceRegistryEntryMutableParams>
|
||||
) =>
|
||||
hass.callWS<DeviceRegistryEntry>({
|
||||
type: "config/device_registry/update",
|
||||
device_id: deviceId,
|
||||
...updates,
|
||||
});
|
||||
|
||||
export const removeConfigEntryFromDevice = (
|
||||
hass: HomeAssistant,
|
||||
deviceId: string,
|
||||
configEntryId: string
|
||||
) =>
|
||||
hass.callWS<DeviceRegistryEntry>({
|
||||
type: "config/device_registry/remove_config_entry",
|
||||
device_id: deviceId,
|
||||
config_entry_id: configEntryId,
|
||||
});
|
||||
|
||||
export const sortDeviceRegistryByName = (
|
||||
entries: DeviceRegistryEntry[],
|
||||
language: string
|
||||
) =>
|
||||
entries.sort((entry1, entry2) =>
|
||||
caseInsensitiveStringCompare(entry1.name || "", entry2.name || "", language)
|
||||
);
|
||||
|
||||
export const getDeviceEntityLookup = (
|
||||
entities: (EntityRegistryEntry | EntityRegistryDisplayEntry)[]
|
||||
): DeviceEntityLookup => {
|
||||
const deviceEntityLookup: DeviceEntityLookup = {};
|
||||
for (const entity of entities) {
|
||||
if (!entity.device_id) {
|
||||
continue;
|
||||
}
|
||||
if (!(entity.device_id in deviceEntityLookup)) {
|
||||
deviceEntityLookup[entity.device_id] = [];
|
||||
}
|
||||
deviceEntityLookup[entity.device_id].push(entity);
|
||||
}
|
||||
return deviceEntityLookup;
|
||||
};
|
||||
|
||||
export const getDeviceEntityDisplayLookup = (
|
||||
entities: EntityRegistryDisplayEntry[]
|
||||
): DeviceEntityDisplayLookup => {
|
||||
const deviceEntityLookup: DeviceEntityDisplayLookup = {};
|
||||
for (const entity of entities) {
|
||||
if (!entity.device_id) {
|
||||
continue;
|
||||
}
|
||||
if (!(entity.device_id in deviceEntityLookup)) {
|
||||
deviceEntityLookup[entity.device_id] = [];
|
||||
}
|
||||
deviceEntityLookup[entity.device_id].push(entity);
|
||||
}
|
||||
return deviceEntityLookup;
|
||||
};
|
||||
|
||||
export const getDeviceIntegrationLookup = (
|
||||
entitySources: EntitySources,
|
||||
entities: EntityRegistryDisplayEntry[] | EntityRegistryEntry[],
|
||||
devices?: DeviceRegistryEntry[],
|
||||
configEntries?: ConfigEntry[]
|
||||
): Record<string, Set<string>> => {
|
||||
const deviceIntegrations: Record<string, Set<string>> = {};
|
||||
|
||||
for (const entity of entities) {
|
||||
const source = entitySources[entity.entity_id];
|
||||
if (!source?.domain || entity.device_id === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
deviceIntegrations[entity.device_id!] =
|
||||
deviceIntegrations[entity.device_id!] || new Set<string>();
|
||||
deviceIntegrations[entity.device_id!].add(source.domain);
|
||||
}
|
||||
// Lookup devices that have no entities
|
||||
if (devices && configEntries) {
|
||||
for (const device of devices) {
|
||||
for (const config_entry_id of device.config_entries) {
|
||||
const entry = configEntries.find((e) => e.entry_id === config_entry_id);
|
||||
if (entry?.domain) {
|
||||
deviceIntegrations[device.id] =
|
||||
deviceIntegrations[device.id] || new Set<string>();
|
||||
deviceIntegrations[device.id].add(entry.domain);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return deviceIntegrations;
|
||||
};
|
||||
|
||||
export interface DevicePickerItem extends PickerComboBoxItem {
|
||||
domain?: string;
|
||||
domain_name?: string;
|
||||
}
|
||||
|
||||
export const getDevices = (
|
||||
hass: HomeAssistant,
|
||||
configEntryLookup: Record<string, ConfigEntry>,
|
||||
includeDomains?: string[],
|
||||
excludeDomains?: string[],
|
||||
includeDeviceClasses?: string[],
|
||||
deviceFilter?: HaDevicePickerDeviceFilterFunc,
|
||||
entityFilter?: HaEntityPickerEntityFilterFunc,
|
||||
excludeDevices?: string[],
|
||||
value?: string,
|
||||
idPrefix = ""
|
||||
): DevicePickerItem[] => {
|
||||
const devices = Object.values(hass.devices);
|
||||
const entities = Object.values(hass.entities);
|
||||
|
||||
let deviceEntityLookup: DeviceEntityDisplayLookup = {};
|
||||
|
||||
if (
|
||||
includeDomains ||
|
||||
excludeDomains ||
|
||||
includeDeviceClasses ||
|
||||
entityFilter
|
||||
) {
|
||||
deviceEntityLookup = getDeviceEntityDisplayLookup(entities);
|
||||
}
|
||||
|
||||
let inputDevices = devices.filter(
|
||||
(device) => device.id === value || !device.disabled_by
|
||||
);
|
||||
|
||||
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))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
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))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (excludeDevices) {
|
||||
inputDevices = inputDevices.filter(
|
||||
(device) => !excludeDevices!.includes(device.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 = hass.states[entity.entity_id];
|
||||
if (!stateObj) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
stateObj.attributes.device_class &&
|
||||
includeDeviceClasses.includes(stateObj.attributes.device_class)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (entityFilter) {
|
||||
inputDevices = inputDevices.filter((device) => {
|
||||
const devEntities = deviceEntityLookup[device.id];
|
||||
if (!devEntities || !devEntities.length) {
|
||||
return false;
|
||||
}
|
||||
return devEntities.some((entity) => {
|
||||
const stateObj = hass.states[entity.entity_id];
|
||||
if (!stateObj) {
|
||||
return false;
|
||||
}
|
||||
return entityFilter(stateObj);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (deviceFilter) {
|
||||
inputDevices = inputDevices.filter(
|
||||
(device) =>
|
||||
// We always want to include the device of the current value
|
||||
device.id === value || deviceFilter!(device)
|
||||
);
|
||||
}
|
||||
|
||||
const outputDevices = inputDevices.map<DevicePickerItem>((device) => {
|
||||
const deviceName = computeDeviceNameDisplay(
|
||||
device,
|
||||
hass,
|
||||
deviceEntityLookup[device.id]
|
||||
);
|
||||
|
||||
const { area } = getDeviceContext(device, hass);
|
||||
|
||||
const areaName = area ? computeAreaName(area) : undefined;
|
||||
|
||||
const configEntry = device.primary_config_entry
|
||||
? configEntryLookup?.[device.primary_config_entry]
|
||||
: undefined;
|
||||
|
||||
const domain = configEntry?.domain;
|
||||
const domainName = domain ? domainToName(hass.localize, domain) : undefined;
|
||||
|
||||
return {
|
||||
id: `${idPrefix}${device.id}`,
|
||||
label: "",
|
||||
primary:
|
||||
deviceName ||
|
||||
hass.localize("ui.components.device-picker.unnamed_device"),
|
||||
secondary: areaName,
|
||||
domain: configEntry?.domain,
|
||||
domain_name: domainName,
|
||||
search_labels: [deviceName, areaName, domain, domainName].filter(
|
||||
Boolean
|
||||
) as string[],
|
||||
sorting_label: deviceName || "zzz",
|
||||
};
|
||||
});
|
||||
|
||||
return outputDevices;
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { arrayLiteralIncludes } from "../../common/array/literal-includes";
|
||||
import { arrayLiteralIncludes } from "../common/array/literal-includes";
|
||||
|
||||
export const UNAVAILABLE = "unavailable";
|
||||
export const UNKNOWN = "unknown";
|
||||
@@ -1,155 +0,0 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { computeEntityNameList } from "../../common/entity/compute_entity_name_display";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import type { PickerComboBoxItem } from "../../components/ha-picker-combo-box";
|
||||
import type { FuseWeightedKey } from "../../resources/fuseMultiTerm";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { domainToName } from "../integration";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "./entity";
|
||||
|
||||
export interface EntityComboBoxItem extends PickerComboBoxItem {
|
||||
domain_name?: string;
|
||||
stateObj?: HassEntity;
|
||||
}
|
||||
|
||||
export const entityComboBoxKeys: FuseWeightedKey[] = [
|
||||
{
|
||||
name: "search_labels.entityName",
|
||||
weight: 10,
|
||||
},
|
||||
{
|
||||
name: "search_labels.friendlyName",
|
||||
weight: 8,
|
||||
},
|
||||
{
|
||||
name: "search_labels.deviceName",
|
||||
weight: 7,
|
||||
},
|
||||
{
|
||||
name: "search_labels.areaName",
|
||||
weight: 6,
|
||||
},
|
||||
{
|
||||
name: "search_labels.domainName",
|
||||
weight: 6,
|
||||
},
|
||||
{
|
||||
name: "search_labels.entityId",
|
||||
weight: 3,
|
||||
},
|
||||
];
|
||||
|
||||
export const getEntities = (
|
||||
hass: HomeAssistant,
|
||||
includeDomains?: string[],
|
||||
excludeDomains?: string[],
|
||||
entityFilter?: HaEntityPickerEntityFilterFunc,
|
||||
includeDeviceClasses?: string[],
|
||||
includeUnitOfMeasurement?: string[],
|
||||
includeEntities?: string[],
|
||||
excludeEntities?: string[],
|
||||
value?: string,
|
||||
idPrefix = ""
|
||||
): EntityComboBoxItem[] => {
|
||||
let items: EntityComboBoxItem[] = [];
|
||||
|
||||
let entityIds = Object.keys(hass.states);
|
||||
|
||||
if (includeEntities) {
|
||||
entityIds = entityIds.filter((entityId) =>
|
||||
includeEntities.includes(entityId)
|
||||
);
|
||||
}
|
||||
|
||||
if (excludeEntities) {
|
||||
entityIds = entityIds.filter(
|
||||
(entityId) => !excludeEntities.includes(entityId)
|
||||
);
|
||||
}
|
||||
|
||||
if (includeDomains) {
|
||||
entityIds = entityIds.filter((eid) =>
|
||||
includeDomains.includes(computeDomain(eid))
|
||||
);
|
||||
}
|
||||
|
||||
if (excludeDomains) {
|
||||
entityIds = entityIds.filter(
|
||||
(eid) => !excludeDomains.includes(computeDomain(eid))
|
||||
);
|
||||
}
|
||||
|
||||
items = entityIds.map<EntityComboBoxItem>((entityId) => {
|
||||
const stateObj = hass.states[entityId];
|
||||
|
||||
const friendlyName = computeStateName(stateObj); // Keep this for search
|
||||
const [entityName, deviceName, areaName] = computeEntityNameList(
|
||||
stateObj,
|
||||
[{ type: "entity" }, { type: "device" }, { type: "area" }],
|
||||
hass.entities,
|
||||
hass.devices,
|
||||
hass.areas,
|
||||
hass.floors
|
||||
);
|
||||
|
||||
const domainName = domainToName(hass.localize, computeDomain(entityId));
|
||||
|
||||
const isRTL = computeRTL(hass);
|
||||
|
||||
const primary = entityName || deviceName || entityId;
|
||||
const secondary = [areaName, entityName ? deviceName : undefined]
|
||||
.filter(Boolean)
|
||||
.join(isRTL ? " ◂ " : " ▸ ");
|
||||
|
||||
return {
|
||||
id: `${idPrefix}${entityId}`,
|
||||
primary: primary,
|
||||
secondary: secondary,
|
||||
domain_name: domainName,
|
||||
sorting_label: [primary, secondary].filter(Boolean).join("_"),
|
||||
search_labels: {
|
||||
entityName: entityName || null,
|
||||
deviceName: deviceName || null,
|
||||
areaName: areaName || null,
|
||||
domainName: domainName || null,
|
||||
friendlyName: friendlyName || null,
|
||||
entityId: entityId,
|
||||
},
|
||||
stateObj: stateObj,
|
||||
};
|
||||
});
|
||||
|
||||
if (includeDeviceClasses) {
|
||||
items = items.filter(
|
||||
(item) =>
|
||||
// We always want to include the entity of the current value
|
||||
item.id === value ||
|
||||
(item.stateObj?.attributes.device_class &&
|
||||
includeDeviceClasses.includes(item.stateObj.attributes.device_class))
|
||||
);
|
||||
}
|
||||
|
||||
if (includeUnitOfMeasurement) {
|
||||
items = items.filter(
|
||||
(item) =>
|
||||
// We always want to include the entity of the current value
|
||||
item.id === value ||
|
||||
(item.stateObj?.attributes.unit_of_measurement &&
|
||||
includeUnitOfMeasurement.includes(
|
||||
item.stateObj.attributes.unit_of_measurement
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
if (entityFilter) {
|
||||
items = items.filter(
|
||||
(item) =>
|
||||
// We always want to include the entity of the current value
|
||||
item.id === value || (item.stateObj && entityFilter!(item.stateObj))
|
||||
);
|
||||
}
|
||||
|
||||
return items;
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { formatDurationDigital } from "../../common/datetime/format_duration";
|
||||
import type { FrontendLocaleData } from "../translation";
|
||||
import { formatDurationDigital } from "../common/datetime/format_duration";
|
||||
import type { FrontendLocaleData } from "./translation";
|
||||
|
||||
// These attributes are hidden from the more-info window for all entities.
|
||||
export const STATE_ATTRIBUTES = [
|
||||
@@ -1,14 +1,19 @@
|
||||
import type { Connection } from "home-assistant-js-websocket";
|
||||
import type { Connection, HassEntity } from "home-assistant-js-websocket";
|
||||
import { createCollection } from "home-assistant-js-websocket";
|
||||
import type { Store } from "home-assistant-js-websocket/dist/store";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { LightColor } from "../light";
|
||||
import type { RegistryEntry } from "../registry";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { computeEntityNameList } from "../common/entity/compute_entity_name_display";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import type { PickerComboBoxItem } from "../components/ha-picker-combo-box";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "./entity";
|
||||
import { domainToName } from "./integration";
|
||||
import type { LightColor } from "./light";
|
||||
import type { RegistryEntry } from "./registry";
|
||||
|
||||
type EntityCategory = "config" | "diagnostic";
|
||||
|
||||
@@ -324,3 +329,121 @@ export const getAutomaticEntityIds = (
|
||||
type: "config/entity_registry/get_automatic_entity_ids",
|
||||
entity_ids,
|
||||
});
|
||||
|
||||
export interface EntityComboBoxItem extends PickerComboBoxItem {
|
||||
domain_name?: string;
|
||||
stateObj?: HassEntity;
|
||||
}
|
||||
|
||||
export const getEntities = (
|
||||
hass: HomeAssistant,
|
||||
includeDomains?: string[],
|
||||
excludeDomains?: string[],
|
||||
entityFilter?: HaEntityPickerEntityFilterFunc,
|
||||
includeDeviceClasses?: string[],
|
||||
includeUnitOfMeasurement?: string[],
|
||||
includeEntities?: string[],
|
||||
excludeEntities?: string[],
|
||||
value?: string,
|
||||
idPrefix = ""
|
||||
): EntityComboBoxItem[] => {
|
||||
let items: EntityComboBoxItem[] = [];
|
||||
|
||||
let entityIds = Object.keys(hass.states);
|
||||
|
||||
if (includeEntities) {
|
||||
entityIds = entityIds.filter((entityId) =>
|
||||
includeEntities.includes(entityId)
|
||||
);
|
||||
}
|
||||
|
||||
if (excludeEntities) {
|
||||
entityIds = entityIds.filter(
|
||||
(entityId) => !excludeEntities.includes(entityId)
|
||||
);
|
||||
}
|
||||
|
||||
if (includeDomains) {
|
||||
entityIds = entityIds.filter((eid) =>
|
||||
includeDomains.includes(computeDomain(eid))
|
||||
);
|
||||
}
|
||||
|
||||
if (excludeDomains) {
|
||||
entityIds = entityIds.filter(
|
||||
(eid) => !excludeDomains.includes(computeDomain(eid))
|
||||
);
|
||||
}
|
||||
|
||||
items = entityIds.map<EntityComboBoxItem>((entityId) => {
|
||||
const stateObj = hass.states[entityId];
|
||||
|
||||
const friendlyName = computeStateName(stateObj); // Keep this for search
|
||||
const [entityName, deviceName, areaName] = computeEntityNameList(
|
||||
stateObj,
|
||||
[{ type: "entity" }, { type: "device" }, { type: "area" }],
|
||||
hass.entities,
|
||||
hass.devices,
|
||||
hass.areas,
|
||||
hass.floors
|
||||
);
|
||||
|
||||
const domainName = domainToName(hass.localize, computeDomain(entityId));
|
||||
|
||||
const isRTL = computeRTL(hass);
|
||||
|
||||
const primary = entityName || deviceName || entityId;
|
||||
const secondary = [areaName, entityName ? deviceName : undefined]
|
||||
.filter(Boolean)
|
||||
.join(isRTL ? " ◂ " : " ▸ ");
|
||||
|
||||
return {
|
||||
id: `${idPrefix}${entityId}`,
|
||||
primary: primary,
|
||||
secondary: secondary,
|
||||
domain_name: domainName,
|
||||
sorting_label: [deviceName, entityName].filter(Boolean).join("_"),
|
||||
search_labels: [
|
||||
entityName,
|
||||
deviceName,
|
||||
areaName,
|
||||
domainName,
|
||||
friendlyName,
|
||||
entityId,
|
||||
].filter(Boolean) as string[],
|
||||
stateObj: stateObj,
|
||||
};
|
||||
});
|
||||
|
||||
if (includeDeviceClasses) {
|
||||
items = items.filter(
|
||||
(item) =>
|
||||
// We always want to include the entity of the current value
|
||||
item.id === value ||
|
||||
(item.stateObj?.attributes.device_class &&
|
||||
includeDeviceClasses.includes(item.stateObj.attributes.device_class))
|
||||
);
|
||||
}
|
||||
|
||||
if (includeUnitOfMeasurement) {
|
||||
items = items.filter(
|
||||
(item) =>
|
||||
// We always want to include the entity of the current value
|
||||
item.id === value ||
|
||||
(item.stateObj?.attributes.unit_of_measurement &&
|
||||
includeUnitOfMeasurement.includes(
|
||||
item.stateObj.attributes.unit_of_measurement
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
if (entityFilter) {
|
||||
items = items.filter(
|
||||
(item) =>
|
||||
// We always want to include the entity of the current value
|
||||
item.id === value || (item.stateObj && entityFilter!(item.stateObj))
|
||||
);
|
||||
}
|
||||
|
||||
return items;
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { timeCachePromiseFunc } from "../../common/util/time-cache-function-promise";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { timeCachePromiseFunc } from "../common/util/time-cache-function-promise";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
interface EntitySource {
|
||||
domain: string;
|
||||
+2
-2
@@ -56,11 +56,11 @@ import type { HomeAssistant } from "../types";
|
||||
import type {
|
||||
EntityRegistryDisplayEntry,
|
||||
EntityRegistryEntry,
|
||||
} from "./entity/entity_registry";
|
||||
} from "./entity_registry";
|
||||
|
||||
import { mdiHomeAssistant } from "../resources/home-assistant-logo-svg";
|
||||
import { getConditionDomain, getConditionObjectId } from "./condition";
|
||||
import { getTriggerDomain, getTriggerObjectId } from "./trigger";
|
||||
import { getConditionDomain, getConditionObjectId } from "./condition";
|
||||
|
||||
/** Icon to use when no icon specified for service. */
|
||||
export const DEFAULT_SERVICE_ICON = mdiRoomService;
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
import type { Connection } from "home-assistant-js-websocket";
|
||||
import { createCollection } from "home-assistant-js-websocket";
|
||||
import type { Store } from "home-assistant-js-websocket/dist/store";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { RegistryEntry } from "../registry";
|
||||
|
||||
export interface LabelRegistryEntry extends RegistryEntry {
|
||||
label_id: string;
|
||||
name: string;
|
||||
icon: string | null;
|
||||
color: string | null;
|
||||
description: string | null;
|
||||
}
|
||||
|
||||
export interface LabelRegistryEntryMutableParams {
|
||||
name: string;
|
||||
icon?: string | null;
|
||||
color?: string | null;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export const fetchLabelRegistry = (conn: Connection) =>
|
||||
conn
|
||||
.sendMessagePromise({
|
||||
type: "config/label_registry/list",
|
||||
})
|
||||
.then((labels) =>
|
||||
(labels as LabelRegistryEntry[]).sort((ent1, ent2) =>
|
||||
stringCompare(ent1.name, ent2.name)
|
||||
)
|
||||
);
|
||||
|
||||
export const subscribeLabelRegistryUpdates = (
|
||||
conn: Connection,
|
||||
store: Store<LabelRegistryEntry[]>
|
||||
) =>
|
||||
conn.subscribeEvents(
|
||||
debounce(
|
||||
() =>
|
||||
fetchLabelRegistry(conn).then((labels: LabelRegistryEntry[]) =>
|
||||
store.setState(labels, true)
|
||||
),
|
||||
500,
|
||||
true
|
||||
),
|
||||
"label_registry_updated"
|
||||
);
|
||||
|
||||
export const subscribeLabelRegistry = (
|
||||
conn: Connection,
|
||||
onChange: (labels: LabelRegistryEntry[]) => void
|
||||
) =>
|
||||
createCollection<LabelRegistryEntry[]>(
|
||||
"_labelRegistry",
|
||||
fetchLabelRegistry,
|
||||
subscribeLabelRegistryUpdates,
|
||||
conn,
|
||||
onChange
|
||||
);
|
||||
|
||||
export const createLabelRegistryEntry = (
|
||||
hass: HomeAssistant,
|
||||
values: LabelRegistryEntryMutableParams
|
||||
) =>
|
||||
hass.callWS<LabelRegistryEntry>({
|
||||
type: "config/label_registry/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
export const updateLabelRegistryEntry = (
|
||||
hass: HomeAssistant,
|
||||
labelId: string,
|
||||
updates: Partial<LabelRegistryEntryMutableParams>
|
||||
) =>
|
||||
hass.callWS<LabelRegistryEntry>({
|
||||
type: "config/label_registry/update",
|
||||
label_id: labelId,
|
||||
...updates,
|
||||
});
|
||||
|
||||
export const deleteLabelRegistryEntry = (
|
||||
hass: HomeAssistant,
|
||||
labelId: string
|
||||
) =>
|
||||
hass.callWS({
|
||||
type: "config/label_registry/delete",
|
||||
label_id: labelId,
|
||||
});
|
||||
@@ -1,32 +1,104 @@
|
||||
import { mdiLabel } from "@mdi/js";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "../../components/device/ha-device-picker";
|
||||
import type { PickerComboBoxItem } from "../../components/ha-picker-combo-box";
|
||||
import type { FuseWeightedKey } from "../../resources/fuseMultiTerm";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { Connection } from "home-assistant-js-websocket";
|
||||
import { createCollection } from "home-assistant-js-websocket";
|
||||
import type { Store } from "home-assistant-js-websocket/dist/store";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "../components/device/ha-device-picker";
|
||||
import type { PickerComboBoxItem } from "../components/ha-picker-combo-box";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import {
|
||||
getDeviceEntityDisplayLookup,
|
||||
type DeviceEntityDisplayLookup,
|
||||
type DeviceRegistryEntry,
|
||||
} from "../device/device_registry";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../entity/entity";
|
||||
import type { EntityRegistryDisplayEntry } from "../entity/entity_registry";
|
||||
import type { LabelRegistryEntry } from "./label_registry";
|
||||
} from "./device_registry";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "./entity";
|
||||
import type { EntityRegistryDisplayEntry } from "./entity_registry";
|
||||
import type { RegistryEntry } from "./registry";
|
||||
|
||||
export const labelComboBoxKeys: FuseWeightedKey[] = [
|
||||
{
|
||||
name: "search_labels.name",
|
||||
weight: 10,
|
||||
},
|
||||
{
|
||||
name: "search_labels.description",
|
||||
weight: 5,
|
||||
},
|
||||
{
|
||||
name: "search_labels.id",
|
||||
weight: 4,
|
||||
},
|
||||
];
|
||||
export interface LabelRegistryEntry extends RegistryEntry {
|
||||
label_id: string;
|
||||
name: string;
|
||||
icon: string | null;
|
||||
color: string | null;
|
||||
description: string | null;
|
||||
}
|
||||
|
||||
export interface LabelRegistryEntryMutableParams {
|
||||
name: string;
|
||||
icon?: string | null;
|
||||
color?: string | null;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export const fetchLabelRegistry = (conn: Connection) =>
|
||||
conn
|
||||
.sendMessagePromise({
|
||||
type: "config/label_registry/list",
|
||||
})
|
||||
.then((labels) =>
|
||||
(labels as LabelRegistryEntry[]).sort((ent1, ent2) =>
|
||||
stringCompare(ent1.name, ent2.name)
|
||||
)
|
||||
);
|
||||
|
||||
export const subscribeLabelRegistryUpdates = (
|
||||
conn: Connection,
|
||||
store: Store<LabelRegistryEntry[]>
|
||||
) =>
|
||||
conn.subscribeEvents(
|
||||
debounce(
|
||||
() =>
|
||||
fetchLabelRegistry(conn).then((labels: LabelRegistryEntry[]) =>
|
||||
store.setState(labels, true)
|
||||
),
|
||||
500,
|
||||
true
|
||||
),
|
||||
"label_registry_updated"
|
||||
);
|
||||
|
||||
export const subscribeLabelRegistry = (
|
||||
conn: Connection,
|
||||
onChange: (labels: LabelRegistryEntry[]) => void
|
||||
) =>
|
||||
createCollection<LabelRegistryEntry[]>(
|
||||
"_labelRegistry",
|
||||
fetchLabelRegistry,
|
||||
subscribeLabelRegistryUpdates,
|
||||
conn,
|
||||
onChange
|
||||
);
|
||||
|
||||
export const createLabelRegistryEntry = (
|
||||
hass: HomeAssistant,
|
||||
values: LabelRegistryEntryMutableParams
|
||||
) =>
|
||||
hass.callWS<LabelRegistryEntry>({
|
||||
type: "config/label_registry/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
export const updateLabelRegistryEntry = (
|
||||
hass: HomeAssistant,
|
||||
labelId: string,
|
||||
updates: Partial<LabelRegistryEntryMutableParams>
|
||||
) =>
|
||||
hass.callWS<LabelRegistryEntry>({
|
||||
type: "config/label_registry/update",
|
||||
label_id: labelId,
|
||||
...updates,
|
||||
});
|
||||
|
||||
export const deleteLabelRegistryEntry = (
|
||||
hass: HomeAssistant,
|
||||
labelId: string
|
||||
) =>
|
||||
hass.callWS({
|
||||
type: "config/label_registry/delete",
|
||||
label_id: labelId,
|
||||
});
|
||||
|
||||
export const getLabels = (
|
||||
hassStates: HomeAssistant["states"],
|
||||
@@ -201,11 +273,9 @@ export const getLabels = (
|
||||
icon: label.icon || undefined,
|
||||
icon_path: label.icon ? undefined : mdiLabel,
|
||||
sorting_label: label.name,
|
||||
search_labels: {
|
||||
name: label.name,
|
||||
description: label.description,
|
||||
id: label.label_id,
|
||||
},
|
||||
search_labels: [label.name, label.label_id, label.description].filter(
|
||||
(v): v is string => Boolean(v)
|
||||
),
|
||||
}));
|
||||
|
||||
return items;
|
||||
@@ -2,7 +2,7 @@ import type {
|
||||
HassEntityAttributeBase,
|
||||
HassEntityBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE } from "./entity/entity";
|
||||
import { UNAVAILABLE } from "./entity";
|
||||
|
||||
export type LawnMowerEntityState =
|
||||
| "paused"
|
||||
|
||||
+2
-2
@@ -2,10 +2,10 @@ import type {
|
||||
HassEntityAttributeBase,
|
||||
HassEntityBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { getExtendedEntityRegistryEntry } from "./entity_registry";
|
||||
import { showEnterCodeDialog } from "../dialogs/enter-code/show-enter-code-dialog";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { UNAVAILABLE } from "./entity/entity";
|
||||
import { getExtendedEntityRegistryEntry } from "./entity/entity_registry";
|
||||
import { UNAVAILABLE } from "./entity";
|
||||
|
||||
export const enum LockEntityFeature {
|
||||
OPEN = 1,
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||
import { autoCaseNoun } from "../common/translations/auto_case_noun";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { UNAVAILABLE, UNKNOWN } from "./entity/entity";
|
||||
import { UNAVAILABLE, UNKNOWN } from "./entity";
|
||||
import { isNumericEntity } from "./history";
|
||||
|
||||
const LOGBOOK_LOCALIZE_PATH = "ui.components.logbook.messages";
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
import { navigate } from "../common/navigate";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { subscribeDeviceRegistry } from "./device/device_registry";
|
||||
import { subscribeDeviceRegistry } from "./device_registry";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
import { getThreadDataSetTLV, listThreadDataSets } from "./thread";
|
||||
|
||||
export enum NetworkType {
|
||||
|
||||
@@ -32,11 +32,11 @@ import type {
|
||||
HassEntityAttributeBase,
|
||||
HassEntityBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { stateActive } from "../common/entity/state_active";
|
||||
import { supportsFeature } from "../common/entity/supports-feature";
|
||||
import { stateActive } from "../common/entity/state_active";
|
||||
import type { MediaPlayerItemId } from "../components/media-player/ha-media-player-browse";
|
||||
import type { HomeAssistant, TranslationDict } from "../types";
|
||||
import { isUnavailableState } from "./entity/entity";
|
||||
import { isUnavailableState } from "./entity";
|
||||
import { isTTSMediaSource } from "./tts";
|
||||
|
||||
interface MediaPlayerEntityAttributes extends HassEntityAttributeBase {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user