mirror of
https://github.com/home-assistant/frontend.git
synced 2026-10-21 21:23:32 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28ca86ac94 | ||
|
|
a37c4eefae | ||
|
|
ee2ba819f9 |
No files matched your search
@@ -1,80 +0,0 @@
|
||||
import type {
|
||||
DataTableFiltersValue,
|
||||
DataTableFiltersValues,
|
||||
} from "../../data/data_table_filters";
|
||||
import { dataTableQueryParamConfig } from "./data-table-query-params";
|
||||
import {
|
||||
createQueryString,
|
||||
decodeQueryParams,
|
||||
hasQueryParams,
|
||||
type QueryParamConfig,
|
||||
type QueryParamValues,
|
||||
type SearchParamsSource,
|
||||
} from "./query-params";
|
||||
|
||||
// The filter-pane keys of the backups table's filters
|
||||
export const BACKUP_TYPE_FILTER = "ha-filter-states";
|
||||
export const BACKUP_LOCATIONS_FILTER = "backup-locations";
|
||||
|
||||
// The filters of the backups table. Search, sorting and grouping are kept by
|
||||
// the data table, see data-table-query-params.
|
||||
export const backupsFilterQueryParamConfig = {
|
||||
list: ["type", "location"],
|
||||
} as const satisfies QueryParamConfig;
|
||||
|
||||
export type BackupsFilterQueryParams = QueryParamValues<
|
||||
typeof backupsFilterQueryParamConfig
|
||||
>;
|
||||
|
||||
const list = (value: DataTableFiltersValue): string[] | undefined =>
|
||||
Array.isArray(value) ? value : undefined;
|
||||
|
||||
export const backupsFilterQueryParams = (
|
||||
filters: DataTableFiltersValues
|
||||
): BackupsFilterQueryParams => ({
|
||||
type: list(filters[BACKUP_TYPE_FILTER]),
|
||||
location: list(filters[BACKUP_LOCATIONS_FILTER]),
|
||||
});
|
||||
|
||||
// The filters as the URL holds them, so values that filter the same, such as
|
||||
// no list and an empty one, compare equal
|
||||
export const backupsFilterQueryString = (
|
||||
filters: DataTableFiltersValues
|
||||
): string =>
|
||||
createQueryString(
|
||||
backupsFilterQueryParams(filters),
|
||||
backupsFilterQueryParamConfig
|
||||
);
|
||||
|
||||
// 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 BackupsLink {
|
||||
search: string;
|
||||
filters: DataTableFiltersValues;
|
||||
}
|
||||
|
||||
export const backupsLinkFromUrl = (
|
||||
searchParams: SearchParamsSource
|
||||
): BackupsLink | undefined => {
|
||||
if (
|
||||
!hasQueryParams(searchParams, backupsFilterQueryParamConfig) &&
|
||||
!hasQueryParams(searchParams, dataTableQueryParamConfig)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const params = decodeQueryParams(searchParams, backupsFilterQueryParamConfig);
|
||||
const { search } = decodeQueryParams(searchParams, dataTableQueryParamConfig);
|
||||
|
||||
return {
|
||||
search: search ?? "",
|
||||
filters: {
|
||||
// `type=all` asks for every type, as the backups overview links do
|
||||
[BACKUP_TYPE_FILTER]:
|
||||
params.type?.filter((type) => type && type !== "all") ?? [],
|
||||
[BACKUP_LOCATIONS_FILTER]: params.location?.filter(Boolean) ?? [],
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -3,13 +3,13 @@ import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { DataTableSettingsController } from "../../../common/controllers/data-table-settings-controller";
|
||||
import { storage } from "../../../common/decorators/storage";
|
||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import type {
|
||||
DataTableColumnContainer,
|
||||
SelectionChangedEvent,
|
||||
SortingChangedEvent,
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-help-tooltip";
|
||||
@@ -52,12 +52,10 @@ export class HaConfigApplicationCredentials extends LitElement {
|
||||
@query("hass-tabs-subpage-data-table", true)
|
||||
private _dataTable!: HaTabsSubpageDataTable;
|
||||
|
||||
@storage({
|
||||
key: "application-credentials-table-sort",
|
||||
state: false,
|
||||
subscribe: false,
|
||||
})
|
||||
private _activeSorting?: SortingChangedEvent;
|
||||
private _tableSettings = new DataTableSettingsController(this, {
|
||||
search: "application-credentials-table-search",
|
||||
sorting: "application-credentials-table-sort",
|
||||
});
|
||||
|
||||
@storage({
|
||||
key: "application-credentials-table-column-order",
|
||||
@@ -73,15 +71,6 @@ export class HaConfigApplicationCredentials extends LitElement {
|
||||
})
|
||||
private _activeHiddenColumns?: string[];
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "application-credentials-table-search",
|
||||
state: true,
|
||||
subscribe: false,
|
||||
})
|
||||
private _filter = "";
|
||||
|
||||
private _columns = memoizeOne(
|
||||
(localize: LocalizeFunc): DataTableColumnContainer => {
|
||||
const columns: DataTableColumnContainer<ApplicationCredential> = {
|
||||
@@ -107,6 +96,7 @@ export class HaConfigApplicationCredentials extends LitElement {
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
urlName: "integration",
|
||||
},
|
||||
actions: {
|
||||
lastFixed: true,
|
||||
@@ -175,13 +165,14 @@ export class HaConfigApplicationCredentials extends LitElement {
|
||||
selectable
|
||||
.selected=${this._selected.length}
|
||||
@selection-changed=${this._handleSelectionChanged}
|
||||
.initialSorting=${this._activeSorting}
|
||||
.initialSorting=${this._tableSettings.sorting}
|
||||
.columnOrder=${this._activeColumnOrder}
|
||||
.hiddenColumns=${this._activeHiddenColumns}
|
||||
@columns-changed=${this._handleColumnsChanged}
|
||||
@sorting-changed=${this._handleSortingChanged}
|
||||
.filter=${this._filter}
|
||||
@search-changed=${this._handleSearchChange}
|
||||
@sorting-changed=${this._tableSettings.sortingChanged}
|
||||
.filter=${this._tableSettings.search}
|
||||
@search-changed=${this._tableSettings.searchChanged}
|
||||
sync-url
|
||||
>
|
||||
<div class="header-btns" slot="selection-bar">
|
||||
${
|
||||
@@ -323,19 +314,11 @@ export class HaConfigApplicationCredentials extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _handleSortingChanged(ev: CustomEvent) {
|
||||
this._activeSorting = ev.detail;
|
||||
}
|
||||
|
||||
private _handleColumnsChanged(ev: CustomEvent) {
|
||||
this._activeColumnOrder = ev.detail.columnOrder;
|
||||
this._activeHiddenColumns = ev.detail.hiddenColumns;
|
||||
}
|
||||
|
||||
private _handleSearchChange(ev: CustomEvent) {
|
||||
this._filter = ev.detail.value;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.table-header {
|
||||
display: flex;
|
||||
|
||||
@@ -12,22 +12,12 @@ import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { DataTableSettingsController } from "../../../common/controllers/data-table-settings-controller";
|
||||
import { QueryParamsController } from "../../../common/controllers/query-params-controller";
|
||||
import { relativeTime } from "../../../common/datetime/relative_time";
|
||||
import { storage } from "../../../common/decorators/storage";
|
||||
import { fireEvent, type HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import {
|
||||
BACKUP_LOCATIONS_FILTER,
|
||||
BACKUP_TYPE_FILTER,
|
||||
backupsFilterQueryParamConfig,
|
||||
backupsFilterQueryParams,
|
||||
backupsFilterQueryString,
|
||||
backupsLinkFromUrl,
|
||||
} from "../../../common/url/backups-query-params";
|
||||
import type {
|
||||
DataTableColumnContainer,
|
||||
DataTableRowData,
|
||||
@@ -65,10 +55,7 @@ import {
|
||||
} from "../../../data/backup";
|
||||
import type { ManagerStateEvent } from "../../../data/backup_manager";
|
||||
import type { CloudStatus } from "../../../data/cloud";
|
||||
import type {
|
||||
DataTableFiltersValue,
|
||||
DataTableFiltersValues,
|
||||
} from "../../../data/data_table_filters";
|
||||
import type { DataTableFiltersValues } from "../../../data/data_table_filters";
|
||||
import { extractApiErrorMessage } from "../../../data/hassio/common";
|
||||
import {
|
||||
showAlertDialog,
|
||||
@@ -92,6 +79,9 @@ interface BackupRow extends DataTableRowData, BackupContent {
|
||||
agent_ids: string[];
|
||||
}
|
||||
|
||||
const TYPE_FILTER = "ha-filter-states";
|
||||
const LOCATIONS_FILTER = "backup-locations";
|
||||
|
||||
@customElement("ha-config-backup-backups")
|
||||
class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -123,11 +113,10 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
})
|
||||
private _storageFilters: DataTableFiltersValues = {};
|
||||
|
||||
private _tableSettings = new DataTableSettingsController(
|
||||
this,
|
||||
{ grouping: "backups-table-grouping" },
|
||||
{ defaultGrouping: "formatted_type" }
|
||||
);
|
||||
private _fromUrl = false;
|
||||
|
||||
@storage({ key: "backups-table-grouping", state: false, subscribe: false })
|
||||
private _activeGrouping?: string = "formatted_type";
|
||||
|
||||
@storage({
|
||||
key: "backups-table-collapsed",
|
||||
@@ -145,25 +134,38 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _overflowBackup?: BackupRow;
|
||||
|
||||
// The filters are kept in the URL by the page, the search, sorting and
|
||||
// grouping by the table
|
||||
private _filterParams = new QueryParamsController(
|
||||
this,
|
||||
backupsFilterQueryParamConfig,
|
||||
{
|
||||
enabled: () => true,
|
||||
navigated: () => this._applyLink(),
|
||||
}
|
||||
);
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues) {
|
||||
super.willUpdate(changedProps);
|
||||
if (!this.hasUpdated) {
|
||||
this._filters = this._storageFilters;
|
||||
this._applyLink();
|
||||
this._setFiltersFromUrl();
|
||||
}
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
// Re-apply the type filter from the URL when the page is (re)displayed,
|
||||
// e.g. when navigating back to a cached instance of this page with a
|
||||
// different `type` query param.
|
||||
this._setFiltersFromUrl();
|
||||
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 = () => {
|
||||
this._setFiltersFromUrl();
|
||||
};
|
||||
|
||||
private _popState = () => {
|
||||
this._setFiltersFromUrl();
|
||||
};
|
||||
|
||||
private _columns = memoizeOne(
|
||||
(
|
||||
localize: LocalizeFunc,
|
||||
@@ -195,7 +197,6 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
groupable: true,
|
||||
urlName: "type",
|
||||
},
|
||||
locations: {
|
||||
title: localize("ui.panel.config.backup.locations"),
|
||||
@@ -333,6 +334,10 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
this._overflowMenu.anchorElement = undefined;
|
||||
};
|
||||
|
||||
private _handleGroupingChanged(ev: CustomEvent) {
|
||||
this._activeGrouping = ev.detail.value;
|
||||
}
|
||||
|
||||
private _handleCollapseChanged(ev: CustomEvent) {
|
||||
this._activeCollapsed = ev.detail.value;
|
||||
}
|
||||
@@ -350,19 +355,16 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
localize: LocalizeFunc,
|
||||
isHassio: boolean
|
||||
): BackupRow[] => {
|
||||
const typeFilter = filters[BACKUP_TYPE_FILTER];
|
||||
const locationFilter = filters[BACKUP_LOCATIONS_FILTER];
|
||||
|
||||
const typeFilter = filters[TYPE_FILTER] as string[] | undefined;
|
||||
const locationFilter = filters[LOCATIONS_FILTER] as string[] | undefined;
|
||||
let filteredBackups = backups;
|
||||
|
||||
if (Array.isArray(typeFilter) && typeFilter.length) {
|
||||
if (typeFilter?.length) {
|
||||
filteredBackups = filteredBackups.filter((backup) => {
|
||||
const type = computeBackupType(backup, isHassio);
|
||||
return typeFilter.includes(type);
|
||||
});
|
||||
}
|
||||
|
||||
if (Array.isArray(locationFilter) && locationFilter.length) {
|
||||
if (locationFilter?.length) {
|
||||
filteredBackups = filteredBackups.filter((backup) =>
|
||||
Object.keys(backup.agents).some((agentId) =>
|
||||
locationFilter.includes(agentId)
|
||||
@@ -448,21 +450,18 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
selectable
|
||||
.selected=${this._selected.length}
|
||||
.initialGroupColumn=${this._tableSettings.grouping}
|
||||
.initialGroupColumn=${this._activeGrouping}
|
||||
.initialCollapsedGroups=${this._activeCollapsed}
|
||||
.groupOrder=${this._groupOrder(
|
||||
this._tableSettings.grouping,
|
||||
this._activeGrouping,
|
||||
this.hass.localize,
|
||||
isHassio
|
||||
)}
|
||||
@grouping-changed=${this._tableSettings.groupingChanged}
|
||||
@grouping-changed=${this._handleGroupingChanged}
|
||||
@collapsed-changed=${this._handleCollapseChanged}
|
||||
@selection-changed=${this._handleSelectionChanged}
|
||||
@clear-filter=${this._clearFilter}
|
||||
.route=${this.route}
|
||||
sync-url
|
||||
.filter=${this._tableSettings.search}
|
||||
@search-changed=${this._tableSettings.searchChanged}
|
||||
@row-click=${this._showBackupDetails}
|
||||
.columns=${this._columns(this.hass.localize, maxDisplayedAgents)}
|
||||
.data=${data}
|
||||
@@ -519,7 +518,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
|
||||
<ha-filter-states
|
||||
.label=${this.hass.localize("ui.panel.config.backup.backup_type")}
|
||||
.value=${this._filters[BACKUP_TYPE_FILTER]}
|
||||
.value=${this._filters[TYPE_FILTER]}
|
||||
.states=${this._states(this.hass.localize, isHassio)}
|
||||
@data-table-filter-changed=${this._typeFilterChanged}
|
||||
slot="filter-pane"
|
||||
@@ -527,7 +526,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
></ha-filter-states>
|
||||
<ha-filter-states
|
||||
.label=${this.hass.localize("ui.panel.config.backup.locations")}
|
||||
.value=${this._filters[BACKUP_LOCATIONS_FILTER]}
|
||||
.value=${this._filters[LOCATIONS_FILTER]}
|
||||
.states=${this._locations(
|
||||
this.hass.localize,
|
||||
this.agents,
|
||||
@@ -590,58 +589,38 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
);
|
||||
|
||||
private _typeFilterChanged(ev) {
|
||||
this._filterChanged(BACKUP_TYPE_FILTER, ev.detail.value);
|
||||
this._filters = { ...this._filters, [TYPE_FILTER]: ev.detail.value };
|
||||
if (!this._fromUrl) {
|
||||
this._storageFilters = this._filters;
|
||||
}
|
||||
}
|
||||
|
||||
private _locationsFilterChanged(ev) {
|
||||
this._filterChanged(BACKUP_LOCATIONS_FILTER, ev.detail.value);
|
||||
}
|
||||
|
||||
private _filterChanged(type: string, value: DataTableFiltersValue) {
|
||||
const filters = { ...this._filters, [type]: value };
|
||||
|
||||
// Only a change to what the URL holds is the user's, so a link isn't stored
|
||||
if (
|
||||
backupsFilterQueryString(filters) !==
|
||||
backupsFilterQueryString(this._filters)
|
||||
) {
|
||||
this._storageFilters = { ...this._storageFilters, [type]: value };
|
||||
this._filters = { ...this._filters, [LOCATIONS_FILTER]: ev.detail.value };
|
||||
if (!this._fromUrl) {
|
||||
this._storageFilters = this._filters;
|
||||
}
|
||||
|
||||
this._filters = filters;
|
||||
this._writeFilters();
|
||||
}
|
||||
|
||||
private _clearFilter() {
|
||||
this._filters = {};
|
||||
this._storageFilters = {};
|
||||
this._writeFilters();
|
||||
if (!this._fromUrl) {
|
||||
this._storageFilters = {};
|
||||
}
|
||||
}
|
||||
|
||||
// Links, like the ones on the backups overview, 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 ? backupsLinkFromUrl(searchParams) : undefined;
|
||||
|
||||
// Shown, but not stored as the user's choice
|
||||
if (link) {
|
||||
this._tableSettings.showSearch(link.search);
|
||||
this._filters = link.filters;
|
||||
} else if (searchParams?.toString() === "") {
|
||||
// Opened again without params, like from the sidebar. Written once the
|
||||
// table has seen the URL without params, so it restores what it stored
|
||||
this._filters = this._storageFilters;
|
||||
queueMicrotask(() => this._writeFilters());
|
||||
private _setFiltersFromUrl() {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const type = searchParams.get("type");
|
||||
|
||||
if (!type) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._writeFilters();
|
||||
}
|
||||
|
||||
private _writeFilters() {
|
||||
this._filterParams.write(backupsFilterQueryParams(this._filters));
|
||||
this._fromUrl = true;
|
||||
this._filters = {
|
||||
[TYPE_FILTER]: type === "all" ? [] : [type],
|
||||
};
|
||||
}
|
||||
|
||||
private get _needsOnboarding() {
|
||||
|
||||
@@ -54,9 +54,17 @@ const helpersTableParams = {
|
||||
assistants: "string?",
|
||||
} satisfies Redirect["params"];
|
||||
|
||||
// The application credentials table reads these from the link
|
||||
const applicationCredentialsTableParams = {
|
||||
search: "string?",
|
||||
sort: "string?",
|
||||
direction: "string?",
|
||||
} satisfies Redirect["params"];
|
||||
|
||||
export const getMyRedirects = (): Redirects => ({
|
||||
application_credentials: {
|
||||
redirect: "/config/application_credentials",
|
||||
params: applicationCredentialsTableParams,
|
||||
},
|
||||
tools_assist: {
|
||||
redirect: "/config/tools/assist",
|
||||
@@ -413,7 +421,6 @@ export const getMyRedirects = (): Redirects => ({
|
||||
backup_list: {
|
||||
component: "backup",
|
||||
redirect: "/config/backup/backups",
|
||||
params: backupsTableParams,
|
||||
},
|
||||
backup_config: {
|
||||
component: "backup",
|
||||
@@ -528,16 +535,6 @@ export const getMyRedirects = (): Redirects => ({
|
||||
const getRedirect = (path: string): Redirect | undefined =>
|
||||
getMyRedirects()?.[path];
|
||||
|
||||
// The backups table reads these from the link
|
||||
const backupsTableParams = {
|
||||
search: "string?",
|
||||
sort: "string?",
|
||||
direction: "string?",
|
||||
group: "string?",
|
||||
type: "string?",
|
||||
location: "string?",
|
||||
} satisfies Redirect["params"];
|
||||
|
||||
export type ParamType = "url" | "string" | "string?";
|
||||
|
||||
export type Redirects = Record<string, Redirect>;
|
||||
|
||||
Reference in new issue
Block a user