mirror of
https://github.com/home-assistant/frontend.git
synced 2026-10-20 12:50:04 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50502fde99 | ||
|
|
9b809f6a16 | ||
|
|
7e764ff927 | ||
|
|
768c182336 |
No files matched your search
@@ -3,7 +3,7 @@ import type {
|
||||
ReactiveControllerHost,
|
||||
} from "@lit/reactive-element/reactive-controller";
|
||||
import { mainWindow } from "../dom/get_main_window";
|
||||
import { replaceCurrentUrl } from "../navigate";
|
||||
import { replaceCurrentUrl, type NavigateOptions } from "../navigate";
|
||||
import { constructUrlCurrentPath } from "../url/construct-url";
|
||||
import { currentPath } from "../url/current-path";
|
||||
import {
|
||||
@@ -30,7 +30,7 @@ export interface QueryParamsControllerOptions {
|
||||
* shared, bookmarked and reloaded. Writes replace the current history entry,
|
||||
* so Back leaves the page instead of undoing each change, and only touch the
|
||||
* config's params. The controller only acts while the URL is still the
|
||||
* page the host was connected on.
|
||||
* page the host was connected on, or the default page its router opened.
|
||||
*/
|
||||
export class QueryParamsController<
|
||||
C extends QueryParamConfig,
|
||||
@@ -120,8 +120,16 @@ export class QueryParamsController<
|
||||
return this._options.enabled() && currentPath() === this._path;
|
||||
}
|
||||
|
||||
private _navigated = () => {
|
||||
private _navigated = (ev?: Event & { detail?: NavigateOptions }) => {
|
||||
this._cancelPending();
|
||||
// A router opening its default page, like /config/devices going to
|
||||
// /config/devices/dashboard, replaces the URL after the host connected
|
||||
const path = currentPath();
|
||||
|
||||
if (ev?.detail?.replace && path.startsWith(`${this._path}/`)) {
|
||||
this._path = path;
|
||||
}
|
||||
|
||||
// Leaving the page gets here too, before the router swaps the page
|
||||
if (this._isActive()) {
|
||||
this._options.navigated();
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import type {
|
||||
DataTableFilter,
|
||||
DataTableFilters,
|
||||
} 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 devices table. Search, sorting and grouping are kept by
|
||||
// the data table, see data-table-query-params.
|
||||
export const devicesFilterQueryParamConfig = {
|
||||
list: [
|
||||
"status",
|
||||
"floor",
|
||||
"area",
|
||||
"domain",
|
||||
"label",
|
||||
"config_entry",
|
||||
"sub_entry",
|
||||
],
|
||||
} as const satisfies QueryParamConfig;
|
||||
|
||||
export type DevicesFilterQueryParams = QueryParamValues<
|
||||
typeof devicesFilterQueryParamConfig
|
||||
>;
|
||||
|
||||
const list = (filter: DataTableFilter | undefined): string[] | undefined =>
|
||||
Array.isArray(filter?.value) ? filter.value : undefined;
|
||||
|
||||
const floorAreas = (
|
||||
filter: DataTableFilter | undefined
|
||||
): Record<string, string[]> | undefined =>
|
||||
filter?.value && !Array.isArray(filter.value) ? filter.value : undefined;
|
||||
|
||||
export const devicesFilterQueryParams = (
|
||||
filters: DataTableFilters
|
||||
): DevicesFilterQueryParams => ({
|
||||
status: list(filters["ha-filter-states"]),
|
||||
floor: floorAreas(filters["ha-filter-floor-areas"])?.floors,
|
||||
area: floorAreas(filters["ha-filter-floor-areas"])?.areas,
|
||||
domain: list(filters["ha-filter-integrations"]),
|
||||
label: list(filters["ha-filter-labels"]),
|
||||
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. The filters work out their items from the values.
|
||||
export interface DevicesLink {
|
||||
search: string;
|
||||
filters: DataTableFilters;
|
||||
}
|
||||
|
||||
export const devicesLinkFromUrl = (
|
||||
searchParams: SearchParamsSource
|
||||
): DevicesLink | undefined => {
|
||||
if (
|
||||
!hasQueryParams(searchParams, devicesFilterQueryParamConfig) &&
|
||||
!hasQueryParams(searchParams, dataTableQueryParamConfig)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const params = decodeQueryParams(searchParams, devicesFilterQueryParamConfig);
|
||||
|
||||
const { search } = decodeQueryParams(searchParams, dataTableQueryParamConfig);
|
||||
|
||||
const values = (key: keyof DevicesFilterQueryParams): string[] =>
|
||||
params[key]?.filter(Boolean) ?? [];
|
||||
|
||||
const filter = (value: DataTableFilter["value"]): DataTableFilter => ({
|
||||
value,
|
||||
items: undefined,
|
||||
});
|
||||
|
||||
const floors = values("floor");
|
||||
const areas = values("area");
|
||||
|
||||
return {
|
||||
search: search ?? "",
|
||||
filters: {
|
||||
"ha-filter-states": filter(values("status")),
|
||||
"ha-filter-floor-areas": filter(
|
||||
floors.length || areas.length ? { floors, areas } : undefined
|
||||
),
|
||||
"ha-filter-integrations": filter(values("domain")),
|
||||
"ha-filter-labels": filter(values("label")),
|
||||
config_entry: filter(values("config_entry")),
|
||||
sub_entry: filter(values("sub_entry")),
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -89,12 +89,14 @@ type AreaQuickLinkKey =
|
||||
const NAVIGATION_ACTIONS: {
|
||||
value: string;
|
||||
path: string;
|
||||
params?: string;
|
||||
icon: string;
|
||||
countKey: AreaQuickLinkKey;
|
||||
}[] = [
|
||||
{
|
||||
value: "navigate-devices",
|
||||
path: "/config/devices/dashboard",
|
||||
params: "&status=disabled",
|
||||
icon: mdiDevices,
|
||||
countKey: "devices",
|
||||
},
|
||||
@@ -852,7 +854,9 @@ class HaConfigAreaPage extends LitElement {
|
||||
|
||||
const navAction = NAVIGATION_ACTIONS.find((a) => a.value === action);
|
||||
if (navAction) {
|
||||
navigate(`${navAction.path}?historyBack=1&area=${this.areaId}`);
|
||||
navigate(
|
||||
`${navAction.path}?historyBack=1&area=${this.areaId}${navAction.params ?? ""}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne 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";
|
||||
@@ -24,12 +25,14 @@ import {
|
||||
PROTOCOL_INTEGRATIONS,
|
||||
protocolIntegrationPicked,
|
||||
} from "../../../common/integrations/protocolIntegrationPicked";
|
||||
import {
|
||||
getHistoryState,
|
||||
navigate,
|
||||
updateHistoryState,
|
||||
} from "../../../common/navigate";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import { deepEqual } from "../../../common/util/deep-equal";
|
||||
import {
|
||||
devicesFilterQueryParamConfig,
|
||||
devicesFilterQueryParams,
|
||||
devicesLinkFromUrl,
|
||||
} from "../../../common/url/devices-query-params";
|
||||
import {
|
||||
hasRejectedItems,
|
||||
rejectedItems,
|
||||
@@ -132,8 +135,6 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@state() private _searchParms = new URLSearchParams(window.location.search);
|
||||
|
||||
@state() private _selected: string[] = [];
|
||||
|
||||
private _selectedCanDelete: string[] = [];
|
||||
@@ -148,7 +149,7 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
state: true,
|
||||
subscribe: false,
|
||||
})
|
||||
private _filter: string = getHistoryState()?.filter || "";
|
||||
private _filter = "";
|
||||
|
||||
@state()
|
||||
private _filters: DataTableFilters = {};
|
||||
@@ -196,39 +197,16 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
callback: (entries) => entries[0]?.contentRect.width,
|
||||
});
|
||||
|
||||
private _ignoreLocationChange = false;
|
||||
|
||||
private _fromUrl = false;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
window.addEventListener("location-changed", this._locationChanged);
|
||||
window.addEventListener("popstate", this._popState);
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
window.removeEventListener("location-changed", this._locationChanged);
|
||||
window.removeEventListener("popstate", this._popState);
|
||||
}
|
||||
|
||||
private _locationChanged = () => {
|
||||
if (this._ignoreLocationChange) {
|
||||
this._ignoreLocationChange = false;
|
||||
return;
|
||||
// The filters are kept in the URL by the page, the search, sorting and
|
||||
// grouping by the table
|
||||
private _filterParams = new QueryParamsController(
|
||||
this,
|
||||
devicesFilterQueryParamConfig,
|
||||
{
|
||||
enabled: () => true,
|
||||
navigated: () => this._applyLink(),
|
||||
}
|
||||
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 = memoizeOne((localize: LocalizeFunc) => [
|
||||
{
|
||||
@@ -241,7 +219,7 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
super.willUpdate(changedProps);
|
||||
if (!this.hasUpdated) {
|
||||
this._filters = this._storageFilters;
|
||||
this._setFiltersFromUrl();
|
||||
this._applyLink();
|
||||
}
|
||||
if (changedProps.has("_selected") || changedProps.has("entries")) {
|
||||
this._selectedCanDelete = this._selected.filter((d) => {
|
||||
@@ -256,60 +234,38 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
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 label = this._searchParms.has("label");
|
||||
// Links, like the ones on an 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 ? devicesLinkFromUrl(searchParams) : undefined;
|
||||
|
||||
if (!area && !domain && !configEntry && !label) {
|
||||
return;
|
||||
if (link) {
|
||||
// The filters only work out their items again when their value changes,
|
||||
// so keep the items of the ones the link leaves as they are
|
||||
const filters = Object.fromEntries(
|
||||
Object.entries(link.filters).map(([key, filter]) => {
|
||||
const current = this._filters[key];
|
||||
|
||||
return [
|
||||
key,
|
||||
current && deepEqual(filter.value, current.value)
|
||||
? current
|
||||
: filter,
|
||||
];
|
||||
})
|
||||
);
|
||||
|
||||
this._filter = link.search;
|
||||
this._filters = filters;
|
||||
this._storageFilters = filters;
|
||||
}
|
||||
|
||||
this._fromUrl = true;
|
||||
this._filter = getHistoryState()?.filter || "";
|
||||
|
||||
this._filters = {
|
||||
"ha-filter-states": {
|
||||
value: [
|
||||
...((this._filters["ha-filter-states"]?.value as string[]) || []),
|
||||
"disabled",
|
||||
],
|
||||
items: undefined,
|
||||
},
|
||||
"ha-filter-floor-areas": {
|
||||
value: area ? { areas: [area] } : undefined,
|
||||
items: undefined,
|
||||
},
|
||||
"ha-filter-integrations": {
|
||||
value: domain ? [domain] : [],
|
||||
items: undefined,
|
||||
},
|
||||
config_entry: {
|
||||
value: configEntry ? [configEntry] : [],
|
||||
items: undefined,
|
||||
},
|
||||
sub_entry: {
|
||||
value: subEntry ? [subEntry] : [],
|
||||
items: undefined,
|
||||
},
|
||||
};
|
||||
this._filterLabel();
|
||||
this._writeFilters();
|
||||
}
|
||||
|
||||
private _filterLabel() {
|
||||
const label = this._searchParms.get("label");
|
||||
if (!label) {
|
||||
return;
|
||||
}
|
||||
this._filters = {
|
||||
...this._filters,
|
||||
"ha-filter-labels": {
|
||||
value: [label],
|
||||
items: undefined,
|
||||
},
|
||||
};
|
||||
private _writeFilters() {
|
||||
this._filterParams.write(devicesFilterQueryParams(this._filters));
|
||||
}
|
||||
|
||||
private _reloadConfigEntries() {
|
||||
@@ -318,9 +274,8 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
|
||||
private _clearFilter() {
|
||||
this._filters = {};
|
||||
if (!this._fromUrl) {
|
||||
this._storageFilters = {};
|
||||
}
|
||||
this._storageFilters = {};
|
||||
this._writeFilters();
|
||||
}
|
||||
|
||||
private _devicesAndFilterDomains = memoizeOne(
|
||||
@@ -399,24 +354,29 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
Array.isArray(filter.value) &&
|
||||
filter.value.length
|
||||
) {
|
||||
if (!(
|
||||
Array.isArray(this._filters.config_entry?.value) &&
|
||||
this._filters.config_entry.value.length === 1
|
||||
)) {
|
||||
const configEntryIds = this._filters.config_entry?.value;
|
||||
const subEntryIds = filter.value;
|
||||
|
||||
if (
|
||||
!Array.isArray(configEntryIds) ||
|
||||
!configEntryIds.length ||
|
||||
!Array.isArray(subEntryIds)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const configEntryId = this._filters.config_entry.value[0];
|
||||
outputDevices = outputDevices.filter(
|
||||
(device) =>
|
||||
device.config_entries_subentries[configEntryId] &&
|
||||
(filter.value as string[]).some((subEntryId) =>
|
||||
device.config_entries_subentries[configEntryId].includes(
|
||||
subEntryId
|
||||
)
|
||||
|
||||
outputDevices = outputDevices.filter((device) =>
|
||||
configEntryIds.some((configEntryId) =>
|
||||
device.config_entries_subentries[configEntryId]?.some(
|
||||
(subEntryId) =>
|
||||
subEntryId !== null && subEntryIds.includes(subEntryId)
|
||||
)
|
||||
)
|
||||
);
|
||||
if (!this._subEntries) {
|
||||
this._loadSubEntries(configEntryId);
|
||||
|
||||
// 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" &&
|
||||
@@ -654,6 +614,7 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
groupable: true,
|
||||
urlName: "integration",
|
||||
minWidth: "120px",
|
||||
template: (device) =>
|
||||
!this.entries
|
||||
@@ -669,6 +630,7 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
groupable: true,
|
||||
urlName: "parent",
|
||||
defaultHidden: true,
|
||||
minWidth: "120px",
|
||||
template: (device) => device.parent_device_name || "",
|
||||
@@ -678,6 +640,7 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
groupable: true,
|
||||
urlName: "manufacturer",
|
||||
minWidth: "120px",
|
||||
},
|
||||
model: {
|
||||
@@ -900,6 +863,7 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
@collapsed-changed=${this._handleCollapseChanged}
|
||||
@row-click=${this._handleRowClicked}
|
||||
clickable
|
||||
sync-url
|
||||
has-fab
|
||||
class=${this.narrow ? "narrow" : ""}
|
||||
>
|
||||
@@ -1094,9 +1058,8 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
private _filterChanged(ev) {
|
||||
const type = ev.target.localName;
|
||||
this._filters = { ...this._filters, [type]: ev.detail };
|
||||
if (!this._fromUrl) {
|
||||
this._storageFilters = this._filters;
|
||||
}
|
||||
this._storageFilters = this._filters;
|
||||
this._writeFilters();
|
||||
}
|
||||
|
||||
private _batteryEntity(
|
||||
@@ -1123,13 +1086,11 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
|
||||
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
|
||||
const deviceId = ev.detail.id;
|
||||
this._ignoreLocationChange = true;
|
||||
navigate(`/config/devices/device/${deviceId}`);
|
||||
}
|
||||
|
||||
private _handleSearchChange(ev: CustomEvent) {
|
||||
this._filter = ev.detail.value;
|
||||
updateHistoryState({ filter: this._filter });
|
||||
}
|
||||
|
||||
private _addDevice() {
|
||||
@@ -1157,8 +1118,13 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const integrations = this._filters["ha-filter-integrations"]?.value;
|
||||
showAddIntegrationDialog(this, {
|
||||
domain: this._searchParms.get("domain") || undefined,
|
||||
domain:
|
||||
Array.isArray(integrations) && integrations.length === 1
|
||||
? integrations[0]
|
||||
: undefined,
|
||||
navigateToResult: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -253,7 +253,7 @@ export class HaConfigEntryRow extends LitElement {
|
||||
href=${
|
||||
allDevices.length === 1
|
||||
? `/config/devices/device/${allDevices[0].id}`
|
||||
: `/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`
|
||||
: `/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}&status=disabled`
|
||||
}
|
||||
>
|
||||
<ha-dropdown-item value="devices">
|
||||
@@ -278,7 +278,7 @@ export class HaConfigEntryRow extends LitElement {
|
||||
href=${
|
||||
allServices.length === 1
|
||||
? `/config/devices/device/${allServices[0].id}`
|
||||
: `/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`
|
||||
: `/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}&status=disabled`
|
||||
}
|
||||
>
|
||||
<ha-dropdown-item value="services">
|
||||
|
||||
@@ -617,7 +617,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
href=${
|
||||
devices.length === 1
|
||||
? `/config/devices/device/${devices[0].id}`
|
||||
: `/config/devices/dashboard?historyBack=1&domain=${this.domain}`
|
||||
: `/config/devices/dashboard?historyBack=1&domain=${this.domain}&status=disabled`
|
||||
}
|
||||
>
|
||||
${this.hass.localize(
|
||||
@@ -635,7 +635,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
href=${
|
||||
services.length === 1
|
||||
? `/config/devices/device/${services[0].id}`
|
||||
: `/config/devices/dashboard?historyBack=1&domain=${this.domain}`
|
||||
: `/config/devices/dashboard?historyBack=1&domain=${this.domain}&status=disabled`
|
||||
}
|
||||
>
|
||||
${this.hass.localize(
|
||||
|
||||
@@ -113,7 +113,7 @@ class HaConfigSubEntryRow extends LitElement {
|
||||
href=${
|
||||
devices.length === 1
|
||||
? `/config/devices/device/${devices[0].id}`
|
||||
: `/config/devices/dashboard?historyBack=1&config_entry=${configEntry.entry_id}&sub_entry=${subEntry.subentry_id}`
|
||||
: `/config/devices/dashboard?historyBack=1&config_entry=${configEntry.entry_id}&sub_entry=${subEntry.subentry_id}&status=disabled`
|
||||
}
|
||||
>
|
||||
<ha-dropdown-item value="devices">
|
||||
@@ -138,7 +138,7 @@ class HaConfigSubEntryRow extends LitElement {
|
||||
href=${
|
||||
services.length === 1
|
||||
? `/config/devices/device/${services[0].id}`
|
||||
: `/config/devices/dashboard?historyBack=1&config_entry=${configEntry.entry_id}&sub_entry=${subEntry.subentry_id}`
|
||||
: `/config/devices/dashboard?historyBack=1&config_entry=${configEntry.entry_id}&sub_entry=${subEntry.subentry_id}&status=disabled`
|
||||
}
|
||||
>
|
||||
<ha-dropdown-item value="services">
|
||||
|
||||
@@ -178,7 +178,7 @@ export class MatterConfigDashboard extends LitElement {
|
||||
)}
|
||||
>
|
||||
<ha-list-item-button
|
||||
href=${`/config/devices/dashboard?historyBack=1&config_entry=${this._configEntry?.entry_id}`}
|
||||
href=${`/config/devices/dashboard?historyBack=1&config_entry=${this._configEntry?.entry_id}&status=disabled`}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDevices}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
|
||||
@@ -221,7 +221,7 @@ export class MQTTConfigPanel extends LitElement {
|
||||
)}
|
||||
>
|
||||
<ha-list-item-button
|
||||
href=${`/config/devices/dashboard?historyBack=1&config_entry=${this._configEntry?.entry_id}`}
|
||||
href=${`/config/devices/dashboard?historyBack=1&config_entry=${this._configEntry?.entry_id}&status=disabled`}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDevices}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
|
||||
@@ -237,7 +237,7 @@ class ZHAConfigDashboard extends LitElement {
|
||||
)}
|
||||
>
|
||||
<ha-list-item-button
|
||||
href=${`/config/devices/dashboard?historyBack=1&config_entry=${this._configEntry?.entry_id}`}
|
||||
href=${`/config/devices/dashboard?historyBack=1&config_entry=${this._configEntry?.entry_id}&status=disabled`}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDevices}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ export class DialogZWaveJSAddNode extends HTMLElement {
|
||||
|
||||
private async _openDialog() {
|
||||
await navigate(
|
||||
`/config/devices/dashboard?config_entry=${this.configEntryId}`,
|
||||
`/config/devices/dashboard?config_entry=${this.configEntryId}&status=disabled`,
|
||||
{
|
||||
replace: true,
|
||||
}
|
||||
|
||||
+1
-1
@@ -326,7 +326,7 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
)}
|
||||
>
|
||||
<ha-list-item-button
|
||||
href=${`/config/devices/dashboard?historyBack=1&config_entry=${this.configEntryId}`}
|
||||
href=${`/config/devices/dashboard?historyBack=1&config_entry=${this.configEntryId}&status=disabled`}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDevices}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
|
||||
@@ -478,7 +478,7 @@ export class HaConfigLabels extends LitElement {
|
||||
this._navigateConfig("/config/entities");
|
||||
break;
|
||||
case "navigate-devices":
|
||||
this._navigateConfig("/config/devices/dashboard");
|
||||
this._navigateConfig("/config/devices/dashboard", "&status=disabled");
|
||||
break;
|
||||
case "navigate-automations":
|
||||
this._navigateConfig("/config/automation/dashboard");
|
||||
@@ -495,8 +495,10 @@ export class HaConfigLabels extends LitElement {
|
||||
}
|
||||
};
|
||||
|
||||
private _navigateConfig = (path: string) => {
|
||||
navigate(`${path}?historyBack=1&label=${this._overflowLabel.label_id}`);
|
||||
private _navigateConfig = (path: string, params = "") => {
|
||||
navigate(
|
||||
`${path}?historyBack=1&label=${this._overflowLabel.label_id}${params}`
|
||||
);
|
||||
};
|
||||
|
||||
private _handleSortingChanged(ev: CustomEvent) {
|
||||
|
||||
@@ -31,6 +31,21 @@ const marketplaceTableParams = {
|
||||
direction: "string?",
|
||||
} satisfies Redirect["params"];
|
||||
|
||||
// The devices table reads these from the link
|
||||
const devicesTableParams = {
|
||||
search: "string?",
|
||||
sort: "string?",
|
||||
direction: "string?",
|
||||
group: "string?",
|
||||
status: "string?",
|
||||
floor: "string?",
|
||||
area: "string?",
|
||||
domain: "string?",
|
||||
label: "string?",
|
||||
config_entry: "string?",
|
||||
sub_entry: "string?",
|
||||
} satisfies Redirect["params"];
|
||||
|
||||
export const getMyRedirects = (): Redirects => ({
|
||||
application_credentials: {
|
||||
redirect: "/config/application_credentials",
|
||||
@@ -220,6 +235,7 @@ export const getMyRedirects = (): Redirects => ({
|
||||
},
|
||||
devices: {
|
||||
redirect: "/config/devices/dashboard",
|
||||
params: devicesTableParams,
|
||||
},
|
||||
entities: {
|
||||
redirect: "/config/entities",
|
||||
|
||||
@@ -107,6 +107,31 @@ describe("QueryParamsController", () => {
|
||||
expect(navigated).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("follows the router replacing the URL with its default page", () => {
|
||||
setUrl("/page/dashboard?other=1");
|
||||
window.dispatchEvent(
|
||||
Object.assign(new Event("location-changed"), {
|
||||
detail: { replace: true },
|
||||
})
|
||||
);
|
||||
expect(navigated).toHaveBeenCalledOnce();
|
||||
|
||||
controller.write({ search: "abc" });
|
||||
expect(currentUrl()).toBe("/page/dashboard?other=1&search=abc");
|
||||
});
|
||||
|
||||
it("does nothing once a link pushes a page below it", () => {
|
||||
setUrl("/page/item?other=1");
|
||||
window.dispatchEvent(
|
||||
Object.assign(new Event("location-changed"), {
|
||||
detail: { replace: false },
|
||||
})
|
||||
);
|
||||
controller.write({ search: "abc" });
|
||||
expect(currentUrl()).toBe("/page/item?other=1");
|
||||
expect(navigated).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does nothing when not enabled", () => {
|
||||
enabled = false;
|
||||
controller.write({ search: "abc" });
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
devicesFilterQueryParamConfig,
|
||||
devicesFilterQueryParams,
|
||||
devicesLinkFromUrl,
|
||||
} from "../../../src/common/url/devices-query-params";
|
||||
import { createQueryString } from "../../../src/common/url/query-params";
|
||||
|
||||
describe("devices query params", () => {
|
||||
it("reads nothing without table params", () => {
|
||||
expect(devicesLinkFromUrl("?historyBack=1")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("replaces every filter and the search with what a link asks for", () => {
|
||||
expect(devicesLinkFromUrl("?config_entry=abc&label=a,b")).toEqual({
|
||||
search: "",
|
||||
filters: {
|
||||
"ha-filter-states": { value: [], items: undefined },
|
||||
"ha-filter-floor-areas": { value: undefined, items: undefined },
|
||||
"ha-filter-integrations": { value: [], items: undefined },
|
||||
"ha-filter-labels": { value: ["a", "b"], items: undefined },
|
||||
config_entry: { value: ["abc"], items: undefined },
|
||||
sub_entry: { value: [], items: undefined },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("round trips the filters", () => {
|
||||
const filters = {
|
||||
"ha-filter-states": { value: ["disabled"], items: undefined },
|
||||
"ha-filter-floor-areas": {
|
||||
value: { floors: ["ground"], areas: ["kitchen"] },
|
||||
items: undefined,
|
||||
},
|
||||
"ha-filter-integrations": { value: ["hue"], items: undefined },
|
||||
"ha-filter-labels": { value: ["a"], items: undefined },
|
||||
config_entry: { value: ["abc"], items: undefined },
|
||||
sub_entry: { value: ["def"], items: undefined },
|
||||
};
|
||||
|
||||
expect(
|
||||
devicesLinkFromUrl(
|
||||
createQueryString(
|
||||
devicesFilterQueryParams(filters),
|
||||
devicesFilterQueryParamConfig
|
||||
)
|
||||
)
|
||||
).toEqual({ search: "", filters });
|
||||
});
|
||||
|
||||
it("writes the values, not the items the filters found", () => {
|
||||
expect(
|
||||
devicesFilterQueryParams({
|
||||
"ha-filter-floor-areas": {
|
||||
value: { areas: ["kitchen"] },
|
||||
items: new Set(["device_1"]),
|
||||
},
|
||||
})
|
||||
).toEqual({
|
||||
status: undefined,
|
||||
floor: undefined,
|
||||
area: ["kitchen"],
|
||||
domain: undefined,
|
||||
label: undefined,
|
||||
config_entry: undefined,
|
||||
sub_entry: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user