mirror of
https://github.com/home-assistant/frontend.git
synced 2026-10-21 13:13:39 +00:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
794c322160 | ||
|
|
8557297ef4 | ||
|
|
2d659acfc4 | ||
|
|
150d6284c2 | ||
|
|
9dcda20bdc | ||
|
|
7aa42753f5 | ||
|
|
7030e08bd5 | ||
|
|
cbf3238d35 | ||
|
|
2bc4074188 |
No files matched your search
@@ -71,6 +71,9 @@ export class QueryParamsController<
|
||||
mainWindow.removeEventListener("location-changed", this._navigated);
|
||||
mainWindow.removeEventListener("popstate", this._navigated);
|
||||
this._cancelPending();
|
||||
// A detached host can still get late events, like a filter that finished
|
||||
// a lookup, and must not write over the page that is open now
|
||||
this._path = undefined;
|
||||
}
|
||||
|
||||
/** The current search params, or undefined when not on the host's page. */
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import type {
|
||||
DataTableFiltersValue,
|
||||
DataTableFiltersValues,
|
||||
} from "../../data/data_table_filters";
|
||||
import { dataTableQueryParamConfig } from "./data-table-query-params";
|
||||
import {
|
||||
decodeQueryParams,
|
||||
hasQueryParams,
|
||||
type QueryParamConfig,
|
||||
type QueryParamValues,
|
||||
type SearchParamsSource,
|
||||
} from "./query-params";
|
||||
|
||||
// The filters of the entities table. Search, sorting and grouping are kept
|
||||
// by the data table, see data-table-query-params.
|
||||
export const entitiesFilterQueryParamConfig = {
|
||||
list: [
|
||||
"status",
|
||||
"floor",
|
||||
"area",
|
||||
"device",
|
||||
"domain",
|
||||
"entity_domain",
|
||||
"label",
|
||||
"assistants",
|
||||
"config_entry",
|
||||
"sub_entry",
|
||||
// Read until 2027.5, replaced with `assistants` on the next write
|
||||
"voice_assistant",
|
||||
],
|
||||
} as const satisfies QueryParamConfig;
|
||||
|
||||
export type EntitiesFilterQueryParams = QueryParamValues<
|
||||
typeof entitiesFilterQueryParamConfig
|
||||
>;
|
||||
|
||||
const list = (value: DataTableFiltersValue): string[] | undefined =>
|
||||
Array.isArray(value) ? value : undefined;
|
||||
|
||||
const floorAreas = (
|
||||
value: DataTableFiltersValue
|
||||
): Record<string, string[]> | undefined =>
|
||||
value && !Array.isArray(value) ? value : undefined;
|
||||
|
||||
export const entitiesFilterQueryParams = (
|
||||
filters: DataTableFiltersValues
|
||||
): EntitiesFilterQueryParams => ({
|
||||
status: list(filters["ha-filter-states"]),
|
||||
floor: floorAreas(filters["ha-filter-floor-areas"])?.floors,
|
||||
area: floorAreas(filters["ha-filter-floor-areas"])?.areas,
|
||||
device: list(filters["ha-filter-devices"]),
|
||||
domain: list(filters["ha-filter-integrations"]),
|
||||
entity_domain: list(filters["ha-filter-domains"]),
|
||||
label: list(filters["ha-filter-labels"]),
|
||||
assistants: list(filters["ha-filter-voice-assistants"]),
|
||||
config_entry: list(filters.config_entry),
|
||||
sub_entry: list(filters.sub_entry),
|
||||
});
|
||||
|
||||
// What a link says to show, undefined when it has no table params so what was
|
||||
// picked before stays. A link replaces the search and the filters, ones left
|
||||
// behind would hide part of what it shows. The table applies its sorting and
|
||||
// grouping.
|
||||
export interface EntitiesLink {
|
||||
search: string;
|
||||
filters: DataTableFiltersValues;
|
||||
}
|
||||
|
||||
export const entitiesLinkFromUrl = (
|
||||
searchParams: SearchParamsSource
|
||||
): EntitiesLink | undefined => {
|
||||
if (
|
||||
!hasQueryParams(searchParams, entitiesFilterQueryParamConfig) &&
|
||||
!hasQueryParams(searchParams, dataTableQueryParamConfig)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const params = decodeQueryParams(
|
||||
searchParams,
|
||||
entitiesFilterQueryParamConfig
|
||||
);
|
||||
|
||||
const { search } = decodeQueryParams(searchParams, dataTableQueryParamConfig);
|
||||
|
||||
const values = (key: keyof EntitiesFilterQueryParams): string[] =>
|
||||
params[key]?.filter(Boolean) ?? [];
|
||||
|
||||
const floors = values("floor");
|
||||
const areas = values("area");
|
||||
const assistants = values("assistants");
|
||||
|
||||
return {
|
||||
search: search ?? "",
|
||||
filters: {
|
||||
"ha-filter-states": values("status"),
|
||||
"ha-filter-floor-areas":
|
||||
floors.length || areas.length ? { floors, areas } : undefined,
|
||||
"ha-filter-devices": values("device"),
|
||||
"ha-filter-integrations": values("domain"),
|
||||
"ha-filter-domains": values("entity_domain"),
|
||||
"ha-filter-labels": values("label"),
|
||||
"ha-filter-voice-assistants": assistants.length
|
||||
? assistants
|
||||
: values("voice_assistant"),
|
||||
config_entry: values("config_entry"),
|
||||
sub_entry: values("sub_entry"),
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -38,6 +38,7 @@ export function getDomainTableColumn<T>(
|
||||
title: localize("ui.panel.config.generic.headers.domain"),
|
||||
hidden: true,
|
||||
groupable: true,
|
||||
urlName: "entity_domain",
|
||||
filterable: true,
|
||||
sortable: false,
|
||||
};
|
||||
@@ -49,6 +50,7 @@ export function getAreaTableColumn<T>(
|
||||
return {
|
||||
title: localize("ui.panel.config.generic.headers.area"),
|
||||
groupable: true,
|
||||
urlName: "area",
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
minWidth: "120px",
|
||||
@@ -62,6 +64,7 @@ export function getFloorTableColumn<T>(
|
||||
title: localize("ui.panel.config.generic.headers.floor"),
|
||||
defaultHidden: true,
|
||||
groupable: true,
|
||||
urlName: "floor",
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
minWidth: "120px",
|
||||
@@ -75,6 +78,7 @@ export function getCategoryTableColumn<T>(
|
||||
title: localize("ui.panel.config.generic.headers.category"),
|
||||
defaultHidden: true,
|
||||
groupable: true,
|
||||
urlName: "category",
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
};
|
||||
|
||||
@@ -19,6 +19,7 @@ import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoize from "memoize-one";
|
||||
import { QueryParamsController } from "../../../common/controllers/query-params-controller";
|
||||
import { consume } from "../../../common/decorators/consume";
|
||||
import { storage } from "../../../common/decorators/storage";
|
||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
@@ -37,9 +38,13 @@ import {
|
||||
PROTOCOL_INTEGRATIONS,
|
||||
protocolIntegrationPicked,
|
||||
} from "../../../common/integrations/protocolIntegrationPicked";
|
||||
import { getHistoryState, updateHistoryState } from "../../../common/navigate";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import {
|
||||
entitiesFilterQueryParamConfig,
|
||||
entitiesFilterQueryParams,
|
||||
entitiesLinkFromUrl,
|
||||
} from "../../../common/url/entities-query-params";
|
||||
import {
|
||||
hasRejectedItems,
|
||||
rejectedItems,
|
||||
@@ -154,6 +159,16 @@ export interface EntityRow extends StateEntity {
|
||||
available: string;
|
||||
}
|
||||
|
||||
// Local variant of data/expose/getExposedEntityVoiceAssistantIds, which works
|
||||
// for entities both with and without a unique ID
|
||||
const getVoiceAssistantIds = (
|
||||
exposedEntities: Record<string, ExposeEntitySettings> | undefined,
|
||||
entityId: string
|
||||
) =>
|
||||
Object.keys(voiceAssistants).filter(
|
||||
(vaId) => exposedEntities?.[entityId]?.[vaId]
|
||||
);
|
||||
|
||||
@customElement("ha-config-entities")
|
||||
export class HaConfigEntities extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -190,9 +205,7 @@ export class HaConfigEntities extends LitElement {
|
||||
state: true,
|
||||
subscribe: false,
|
||||
})
|
||||
private _filter: string = getHistoryState()?.filter || "";
|
||||
|
||||
@state() private _searchParms = new URLSearchParams(window.location.search);
|
||||
private _filter = "";
|
||||
|
||||
@state()
|
||||
private _filters: DataTableFiltersValues = {};
|
||||
@@ -251,12 +264,19 @@ export class HaConfigEntities extends LitElement {
|
||||
@query("hass-tabs-subpage-data-table", true)
|
||||
private _dataTable!: HaTabsSubpageDataTable;
|
||||
|
||||
private _fromUrl = false;
|
||||
// The filters are kept in the URL by the page, the search, sorting and
|
||||
// grouping by the table
|
||||
private _filterParams = new QueryParamsController(
|
||||
this,
|
||||
entitiesFilterQueryParamConfig,
|
||||
{
|
||||
enabled: () => true,
|
||||
navigated: () => this._applyLink(),
|
||||
}
|
||||
);
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
window.addEventListener("location-changed", this._locationChanged);
|
||||
window.addEventListener("popstate", this._popState);
|
||||
window.addEventListener(
|
||||
"exposed-entities-changed",
|
||||
this._fetchExposedEntities
|
||||
@@ -265,28 +285,12 @@ export class HaConfigEntities extends LitElement {
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
window.removeEventListener("location-changed", this._locationChanged);
|
||||
window.removeEventListener("popstate", this._popState);
|
||||
window.removeEventListener(
|
||||
"exposed-entities-changed",
|
||||
this._fetchExposedEntities
|
||||
);
|
||||
}
|
||||
|
||||
private _locationChanged = () => {
|
||||
if (window.location.search.substring(1) !== this._searchParms.toString()) {
|
||||
this._searchParms = new URLSearchParams(window.location.search);
|
||||
this._setFiltersFromUrl();
|
||||
}
|
||||
};
|
||||
|
||||
private _popState = () => {
|
||||
if (window.location.search.substring(1) !== this._searchParms.toString()) {
|
||||
this._searchParms = new URLSearchParams(window.location.search);
|
||||
this._setFiltersFromUrl();
|
||||
}
|
||||
};
|
||||
|
||||
private _states = memoize((localize: LocalizeFunc) => [
|
||||
{
|
||||
value: "available",
|
||||
@@ -368,6 +372,7 @@ export class HaConfigEntities extends LitElement {
|
||||
title: localize("ui.panel.config.entities.picker.headers.device"),
|
||||
filterable: true,
|
||||
groupable: true,
|
||||
urlName: "device",
|
||||
hidden: true,
|
||||
},
|
||||
area: getAreaTableColumn(localize),
|
||||
@@ -376,6 +381,7 @@ export class HaConfigEntities extends LitElement {
|
||||
title: localize("ui.panel.config.entities.picker.headers.integration"),
|
||||
sortable: true,
|
||||
groupable: true,
|
||||
urlName: "integration",
|
||||
filterable: true,
|
||||
},
|
||||
domain: getDomainTableColumn(localize),
|
||||
@@ -462,18 +468,21 @@ export class HaConfigEntities extends LitElement {
|
||||
title: localize("ui.panel.config.entities.picker.headers.availability"),
|
||||
sortable: true,
|
||||
groupable: true,
|
||||
urlName: "available",
|
||||
hidden: true,
|
||||
},
|
||||
visible: {
|
||||
title: localize("ui.panel.config.entities.picker.headers.visibility"),
|
||||
sortable: true,
|
||||
groupable: true,
|
||||
urlName: "visible",
|
||||
hidden: true,
|
||||
},
|
||||
enabled: {
|
||||
title: localize("ui.panel.config.entities.picker.headers.enabled"),
|
||||
sortable: true,
|
||||
groupable: true,
|
||||
urlName: "enabled",
|
||||
hidden: true,
|
||||
},
|
||||
labels: getLabelsTableColumn(),
|
||||
@@ -495,6 +504,7 @@ export class HaConfigEntities extends LitElement {
|
||||
entitiesWithoutUniqueId: StateEntity[],
|
||||
filters: DataTableFiltersValues,
|
||||
filteredItems: DataTableFiltersItems,
|
||||
exposedEntities?: Record<string, ExposeEntitySettings>,
|
||||
entries?: ConfigEntry[],
|
||||
labelReg?: LabelRegistryEntry[]
|
||||
) => {
|
||||
@@ -554,19 +564,21 @@ export class HaConfigEntities extends LitElement {
|
||||
Array.isArray(filter) &&
|
||||
filter.length
|
||||
) {
|
||||
if (!(
|
||||
Array.isArray(this._filters.config_entry) &&
|
||||
this._filters.config_entry.length === 1
|
||||
)) {
|
||||
const configEntryIds = this._filters.config_entry;
|
||||
|
||||
if (!Array.isArray(configEntryIds) || !configEntryIds.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
filteredEntities = filteredEntities.filter(
|
||||
(entity) =>
|
||||
entity.config_subentry_id &&
|
||||
(filter as string[]).includes(entity.config_subentry_id)
|
||||
);
|
||||
if (!this._subEntries) {
|
||||
this._loadSubEntries(this._filters.config_entry[0]);
|
||||
|
||||
// The titles shown in the filter chip need a single config entry
|
||||
if (configEntryIds.length === 1 && !this._subEntries) {
|
||||
this._loadSubEntries(configEntryIds[0]);
|
||||
}
|
||||
} else if (
|
||||
key === "ha-filter-integrations" &&
|
||||
@@ -632,8 +644,8 @@ export class HaConfigEntities extends LitElement {
|
||||
filter.length
|
||||
) {
|
||||
filteredEntities = filteredEntities.filter((entity) =>
|
||||
this._getExposedEntityVoiceAssistantIds(entity.entity_id).some(
|
||||
(va) => (filter as string[]).includes(va)
|
||||
getVoiceAssistantIds(exposedEntities, entity.entity_id).some((va) =>
|
||||
(filter as string[]).includes(va)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -689,7 +701,9 @@ export class HaConfigEntities extends LitElement {
|
||||
? `${deviceName} (${areaName})`
|
||||
: deviceName
|
||||
: undefined;
|
||||
const assistants = this._getExposedEntityVoiceAssistantIds(
|
||||
|
||||
const assistants = getVoiceAssistantIds(
|
||||
exposedEntities,
|
||||
entry.entity_id
|
||||
);
|
||||
|
||||
@@ -793,6 +807,7 @@ export class HaConfigEntities extends LitElement {
|
||||
this._entitiesWithoutUniqueId,
|
||||
this._filters,
|
||||
this._filteredItems,
|
||||
this._exposedEntities,
|
||||
this._entries,
|
||||
this._labels
|
||||
);
|
||||
@@ -840,6 +855,7 @@ export class HaConfigEntities extends LitElement {
|
||||
@collapsed-changed=${this._handleCollapseChanged}
|
||||
@selection-changed=${this._handleSelectionChanged}
|
||||
clickable
|
||||
sync-url
|
||||
@clear-filter=${this._clearFilter}
|
||||
.filter=${this._filter}
|
||||
@search-changed=${this._handleSearchChange}
|
||||
@@ -1072,59 +1088,41 @@ export class HaConfigEntities extends LitElement {
|
||||
|
||||
this._filters = { ...this._filters, [type]: ev.detail.value };
|
||||
this._filteredItems = { ...this._filteredItems, [type]: ev.detail.items };
|
||||
if (!this._fromUrl) {
|
||||
this._storageFilters = this._filters;
|
||||
}
|
||||
this._storageFilters = this._filters;
|
||||
this._writeFilters();
|
||||
}
|
||||
|
||||
protected firstUpdated() {
|
||||
this._setFiltersFromUrl();
|
||||
this._fetchExposedEntities();
|
||||
fetchEntitySourcesWithCache(this.hass).then((sources) => {
|
||||
this._entitySources = sources;
|
||||
});
|
||||
if (Object.keys(this._filters).length) {
|
||||
return;
|
||||
}
|
||||
this._filters = {
|
||||
"ha-filter-states": ["enabled"],
|
||||
};
|
||||
}
|
||||
|
||||
private _setFiltersFromUrl() {
|
||||
const area = this._searchParms.get("area");
|
||||
const domain = this._searchParms.get("domain");
|
||||
const configEntry = this._searchParms.get("config_entry");
|
||||
const subEntry = this._searchParms.get("sub_entry");
|
||||
const device = this._searchParms.get("device");
|
||||
const label = this._searchParms.get("label");
|
||||
const voiceAssistant = this._searchParms.get("voice_assistant");
|
||||
// Links, like the ones on a device or integration page, say in the URL what
|
||||
// to show. The router keeps the page open, so a link can land on it.
|
||||
private _applyLink() {
|
||||
const searchParams = this._filterParams.searchParams();
|
||||
const link = searchParams ? entitiesLinkFromUrl(searchParams) : undefined;
|
||||
|
||||
if (!area && !domain && !configEntry && !label && !device) {
|
||||
return;
|
||||
if (link) {
|
||||
this._filter = link.search;
|
||||
this._filters = link.filters;
|
||||
this._storageFilters = link.filters;
|
||||
}
|
||||
|
||||
this._fromUrl = true;
|
||||
this._filter = getHistoryState()?.filter || "";
|
||||
this._writeFilters();
|
||||
}
|
||||
|
||||
this._filters = {
|
||||
"ha-filter-states": [],
|
||||
"ha-filter-floor-areas": area ? { areas: [area] } : undefined,
|
||||
"ha-filter-integrations": domain ? [domain] : [],
|
||||
"ha-filter-devices": device ? [device] : [],
|
||||
"ha-filter-labels": label ? [label] : [],
|
||||
"ha-filter-voice-assistants": voiceAssistant ? [voiceAssistant] : [],
|
||||
config_entry: configEntry ? [configEntry] : [],
|
||||
sub_entry: subEntry ? [subEntry] : [],
|
||||
};
|
||||
private _writeFilters() {
|
||||
this._filterParams.write(entitiesFilterQueryParams(this._filters));
|
||||
}
|
||||
|
||||
private _clearFilter() {
|
||||
this._filters = {};
|
||||
this._filteredItems = {};
|
||||
if (!this._fromUrl) {
|
||||
this._storageFilters = {};
|
||||
}
|
||||
this._storageFilters = {};
|
||||
this._writeFilters();
|
||||
}
|
||||
|
||||
private _fetchExposedEntities = async () => {
|
||||
@@ -1139,14 +1137,6 @@ export class HaConfigEntities extends LitElement {
|
||||
}
|
||||
};
|
||||
|
||||
// local variant of data/expose/getExposedEntityVoiceAssistantIds
|
||||
// which works for both entities with and without unique id
|
||||
private _getExposedEntityVoiceAssistantIds(entityId: string) {
|
||||
return Object.keys(voiceAssistants).filter(
|
||||
(vaId) => this._exposedEntities?.[entityId]?.[vaId]
|
||||
);
|
||||
}
|
||||
|
||||
private _getExposedEntitySettingsAsOptions(
|
||||
entityId: string
|
||||
): EntityRegistryOptions {
|
||||
@@ -1165,8 +1155,10 @@ export class HaConfigEntities extends LitElement {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
if (!this.hasUpdated) {
|
||||
this._filters = this._storageFilters;
|
||||
this._setFiltersFromUrl();
|
||||
this._filters = Object.keys(this._storageFilters).length
|
||||
? this._storageFilters
|
||||
: { "ha-filter-states": ["enabled"] };
|
||||
this._applyLink();
|
||||
}
|
||||
|
||||
const oldHass = changedProps.get("hass");
|
||||
@@ -1240,7 +1232,6 @@ export class HaConfigEntities extends LitElement {
|
||||
|
||||
private _handleSearchChange(ev: CustomEvent) {
|
||||
this._filter = ev.detail.value;
|
||||
updateHistoryState({ filter: this._filter });
|
||||
}
|
||||
|
||||
private _handleSelectionChanged(
|
||||
@@ -1541,6 +1532,7 @@ ${rejected
|
||||
this._entitiesWithoutUniqueId,
|
||||
this._filters,
|
||||
this._filteredItems,
|
||||
this._exposedEntities,
|
||||
this._entries,
|
||||
this._labels
|
||||
);
|
||||
@@ -1555,8 +1547,13 @@ ${rejected
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const integrations = this._filters["ha-filter-integrations"];
|
||||
showAddIntegrationDialog(this, {
|
||||
domain: this._searchParms.get("domain") || undefined,
|
||||
domain:
|
||||
Array.isArray(integrations) && integrations.length === 1
|
||||
? integrations[0]
|
||||
: undefined,
|
||||
navigateToResult: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ class HaConfigEntryDeviceRow extends LitElement {
|
||||
};
|
||||
|
||||
private _handleNavigateToEntities = () => {
|
||||
navigate(`/config/entities/?historyBack=1&device=${this.device.id}`);
|
||||
navigate(`/config/entities?historyBack=1&device=${this.device.id}`);
|
||||
};
|
||||
|
||||
private _doDisableDevice = async () => {
|
||||
|
||||
@@ -156,7 +156,7 @@ export class HaConfigEntryRow extends LitElement {
|
||||
} else if (!allDevices.length && !allServices.length && entities.length) {
|
||||
statusLine.push(
|
||||
html`<a
|
||||
href=${`/config/entities/?historyBack=1&config_entry=${item.entry_id}`}
|
||||
href=${`/config/entities?historyBack=1&config_entry=${item.entry_id}`}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.entities",
|
||||
{ count: entities.length }
|
||||
|
||||
@@ -190,7 +190,7 @@ export class MatterConfigDashboard extends LitElement {
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button
|
||||
href=${`/config/entities/dashboard?historyBack=1&config_entry=${this._configEntry?.entry_id}`}
|
||||
href=${`/config/entities?historyBack=1&config_entry=${this._configEntry?.entry_id}`}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiShape}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
|
||||
@@ -232,7 +232,7 @@ export class MQTTConfigPanel extends LitElement {
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button
|
||||
href=${`/config/entities/dashboard?historyBack=1&config_entry=${this._configEntry?.entry_id}`}
|
||||
href=${`/config/entities?historyBack=1&config_entry=${this._configEntry?.entry_id}`}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiShape}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
|
||||
@@ -249,7 +249,7 @@ class ZHAConfigDashboard extends LitElement {
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button
|
||||
href=${`/config/entities/dashboard?historyBack=1&config_entry=${this._configEntry?.entry_id}`}
|
||||
href=${`/config/entities?historyBack=1&config_entry=${this._configEntry?.entry_id}`}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiShape}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
|
||||
+1
-1
@@ -338,7 +338,7 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button
|
||||
href=${`/config/entities/dashboard?historyBack=1&config_entry=${this.configEntryId}`}
|
||||
href=${`/config/entities?historyBack=1&config_entry=${this.configEntryId}`}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiShape}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
|
||||
@@ -31,6 +31,24 @@ const marketplaceTableParams = {
|
||||
direction: "string?",
|
||||
} satisfies Redirect["params"];
|
||||
|
||||
// The entities table reads these from the link
|
||||
const entitiesTableParams = {
|
||||
search: "string?",
|
||||
sort: "string?",
|
||||
direction: "string?",
|
||||
group: "string?",
|
||||
status: "string?",
|
||||
floor: "string?",
|
||||
area: "string?",
|
||||
device: "string?",
|
||||
domain: "string?",
|
||||
entity_domain: "string?",
|
||||
label: "string?",
|
||||
assistants: "string?",
|
||||
config_entry: "string?",
|
||||
sub_entry: "string?",
|
||||
} satisfies Redirect["params"];
|
||||
|
||||
export const getMyRedirects = (): Redirects => ({
|
||||
application_credentials: {
|
||||
redirect: "/config/application_credentials",
|
||||
@@ -223,6 +241,7 @@ export const getMyRedirects = (): Redirects => ({
|
||||
},
|
||||
entities: {
|
||||
redirect: "/config/entities",
|
||||
params: entitiesTableParams,
|
||||
},
|
||||
labels: {
|
||||
redirect: "/config/labels",
|
||||
|
||||
@@ -107,6 +107,17 @@ describe("QueryParamsController", () => {
|
||||
expect(navigated).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does nothing once disconnected, even on the same page", () => {
|
||||
controller.hostDisconnected();
|
||||
controller.write({ search: "abc" });
|
||||
expect(currentUrl()).toBe("/page?other=1");
|
||||
expect(controller.searchParams()).toBeUndefined();
|
||||
|
||||
controller.hostConnected();
|
||||
controller.write({ search: "abc" });
|
||||
expect(currentUrl()).toBe("/page?other=1&search=abc");
|
||||
});
|
||||
|
||||
it("does nothing when not enabled", () => {
|
||||
enabled = false;
|
||||
controller.write({ search: "abc" });
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
entitiesFilterQueryParamConfig,
|
||||
entitiesFilterQueryParams,
|
||||
entitiesLinkFromUrl,
|
||||
} from "../../../src/common/url/entities-query-params";
|
||||
import {
|
||||
createQueryString,
|
||||
updateQueryString,
|
||||
} from "../../../src/common/url/query-params";
|
||||
|
||||
describe("entities query params", () => {
|
||||
it("reads nothing without table params", () => {
|
||||
expect(entitiesLinkFromUrl("?historyBack=1")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("replaces every filter and the search with what a link asks for", () => {
|
||||
expect(entitiesLinkFromUrl("?device=abc&label=a,b")).toEqual({
|
||||
search: "",
|
||||
filters: {
|
||||
"ha-filter-states": [],
|
||||
"ha-filter-floor-areas": undefined,
|
||||
"ha-filter-devices": ["abc"],
|
||||
"ha-filter-integrations": [],
|
||||
"ha-filter-domains": [],
|
||||
"ha-filter-labels": ["a", "b"],
|
||||
"ha-filter-voice-assistants": [],
|
||||
config_entry: [],
|
||||
sub_entry: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("still reads voice_assistant, with assistants winning", () => {
|
||||
expect(
|
||||
entitiesLinkFromUrl("?voice_assistant=conversation")?.filters[
|
||||
"ha-filter-voice-assistants"
|
||||
]
|
||||
).toEqual(["conversation"]);
|
||||
expect(
|
||||
entitiesLinkFromUrl(
|
||||
"?voice_assistant=conversation&assistants=cloud.alexa"
|
||||
)?.filters["ha-filter-voice-assistants"]
|
||||
).toEqual(["cloud.alexa"]);
|
||||
});
|
||||
|
||||
it("replaces voice_assistant with assistants on write", () => {
|
||||
const url = "?historyBack=1&voice_assistant=conversation";
|
||||
const link = entitiesLinkFromUrl(url);
|
||||
|
||||
expect(
|
||||
updateQueryString(
|
||||
url,
|
||||
entitiesFilterQueryParams(link?.filters ?? {}),
|
||||
entitiesFilterQueryParamConfig
|
||||
)
|
||||
).toBe("historyBack=1&assistants=conversation");
|
||||
});
|
||||
|
||||
it("round trips the filters", () => {
|
||||
const filters = {
|
||||
"ha-filter-states": ["enabled", "hidden"],
|
||||
"ha-filter-floor-areas": { floors: ["ground"], areas: ["kitchen"] },
|
||||
"ha-filter-devices": ["abc"],
|
||||
"ha-filter-integrations": ["hue"],
|
||||
"ha-filter-domains": ["light"],
|
||||
"ha-filter-labels": ["a"],
|
||||
"ha-filter-voice-assistants": ["conversation"],
|
||||
config_entry: ["entry"],
|
||||
sub_entry: ["sub"],
|
||||
};
|
||||
|
||||
expect(
|
||||
entitiesLinkFromUrl(
|
||||
createQueryString(
|
||||
entitiesFilterQueryParams(filters),
|
||||
entitiesFilterQueryParamConfig
|
||||
)
|
||||
)
|
||||
).toEqual({ search: "", filters });
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user