From 62de16bb8e57407fdb2921cac19a7d08954405c5 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 24 Apr 2024 11:06:00 +0200 Subject: [PATCH] Implement storing sorting and grouping for all tables (#20594) --- src/components/data-table/ha-data-table.ts | 18 +++--- .../config/automation/ha-automation-picker.ts | 42 +++++++++++-- .../config/blueprint/ha-blueprint-overview.ts | 60 +++++++++++++++---- .../config/entities/ha-config-entities.ts | 53 ++++++++++++---- .../config/helpers/ha-config-helpers.ts | 34 ++++++++++- src/panels/config/labels/ha-config-labels.ts | 19 +++++- .../ha-config-lovelace-dashboards.ts | 15 +++++ .../resources/ha-config-lovelace-resources.ts | 17 +++++- src/panels/config/scene/ha-scene-dashboard.ts | 42 +++++++++++-- src/panels/config/script/ha-script-picker.ts | 42 +++++++++++-- src/panels/config/users/ha-config-users.ts | 48 +++++++++++++-- .../ha-config-voice-assistants-expose.ts | 15 +++++ 12 files changed, 352 insertions(+), 53 deletions(-) diff --git a/src/components/data-table/ha-data-table.ts b/src/components/data-table/ha-data-table.ts index 7221587266..e019a5d400 100644 --- a/src/components/data-table/ha-data-table.ts +++ b/src/components/data-table/ha-data-table.ts @@ -529,11 +529,7 @@ export class HaDataTable extends LitElement { } if (this.appendRow || this.hasFab || this.groupColumn) { - const items = [...data]; - - if (this.appendRow) { - items.push({ append: true, content: this.appendRow }); - } + let items = [...data]; if (this.groupColumn) { const grouped = groupBy(items, (item) => item[this.groupColumn!]); @@ -599,14 +595,18 @@ export class HaDataTable extends LitElement { } }); - this._items = groupedItems; - } else { - this._items = items; + items = groupedItems; + } + + if (this.appendRow) { + items.push({ append: true, content: this.appendRow }); } if (this.hasFab) { - this._items = [...this._items, { empty: true }]; + items.push({ empty: true }); } + + this._items = items; } else { this._items = data; } diff --git a/src/panels/config/automation/ha-automation-picker.ts b/src/panels/config/automation/ha-automation-picker.ts index 4d58866e34..d11b038beb 100644 --- a/src/panels/config/automation/ha-automation-picker.ts +++ b/src/panels/config/automation/ha-automation-picker.ts @@ -37,15 +37,21 @@ import { computeCssColor } from "../../../common/color/compute-color"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { formatShortDateTime } from "../../../common/datetime/format_date_time"; import { relativeTime } from "../../../common/datetime/relative_time"; +import { storage } from "../../../common/decorators/storage"; import { HASSDomEvent, fireEvent } from "../../../common/dom/fire_event"; import { computeStateName } from "../../../common/entity/compute_state_name"; import { navigate } from "../../../common/navigate"; import { LocalizeFunc } from "../../../common/translations/localize"; +import { + hasRejectedItems, + rejectedItems, +} from "../../../common/util/promise-all-settled-results"; import "../../../components/chips/ha-assist-chip"; import type { DataTableColumnContainer, RowClickedEvent, SelectionChangedEvent, + SortingChangedEvent, } from "../../../components/data-table/ha-data-table"; import "../../../components/data-table/ha-data-table-labels"; import "../../../components/entity/ha-entity-toggle"; @@ -105,10 +111,6 @@ import { showCategoryRegistryDetailDialog } from "../category/show-dialog-catego import { configSections } from "../ha-panel-config"; import { showLabelDetailDialog } from "../labels/show-dialog-label-detail"; import { showNewAutomationDialog } from "./show-dialog-new-automation"; -import { - hasRejectedItems, - rejectedItems, -} from "../../../common/util/promise-all-settled-results"; type AutomationItem = AutomationEntity & { name: string; @@ -156,6 +158,19 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) { @state() private _overflowAutomation?: AutomationItem; + @storage({ key: "automation-table-sort", state: false, subscribe: false }) + private _activeSorting?: SortingChangedEvent; + + @storage({ key: "automation-table-grouping", state: false, subscribe: false }) + private _activeGrouping?: string; + + @storage({ + key: "automation-table-collapsed", + state: false, + subscribe: false, + }) + private _activeCollapsed?: string; + @query("#overflow-menu") private _overflowMenu!: HaMenu; private _sizeController = new ResizeController(this, { @@ -470,7 +485,12 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) { this.hass.localize, this.hass.locale )} - initialGroupColumn="category" + .initialGroupColumn=${this._activeGrouping || "category"} + .initialCollapsedGroups=${this._activeCollapsed} + .initialSorting=${this._activeSorting} + @sorting-changed=${this._handleSortingChanged} + @grouping-changed=${this._handleGroupingChanged} + @collapsed-changed=${this._handleCollapseChanged} .data=${automations} .empty=${!this.automations.length} @row-click=${this._handleRowClicked} @@ -1238,6 +1258,18 @@ ${rejected }); } + private _handleSortingChanged(ev: CustomEvent) { + this._activeSorting = ev.detail; + } + + private _handleGroupingChanged(ev: CustomEvent) { + this._activeGrouping = ev.detail.value; + } + + private _handleCollapseChanged(ev: CustomEvent) { + this._activeCollapsed = ev.detail.value; + } + static get styles(): CSSResultGroup { return [ haStyle, diff --git a/src/panels/config/blueprint/ha-blueprint-overview.ts b/src/panels/config/blueprint/ha-blueprint-overview.ts index 1133bf8fa4..45f02be911 100644 --- a/src/panels/config/blueprint/ha-blueprint-overview.ts +++ b/src/panels/config/blueprint/ha-blueprint-overview.ts @@ -24,6 +24,7 @@ import { extractSearchParam } from "../../../common/url/search-params"; import { DataTableColumnContainer, RowClickedEvent, + SortingChangedEvent, } from "../../../components/data-table/ha-data-table"; import "../../../components/entity/ha-entity-toggle"; import "../../../components/ha-button"; @@ -54,6 +55,7 @@ import { documentationUrl } from "../../../util/documentation-url"; import { showToast } from "../../../util/toast"; import { configSections } from "../ha-panel-config"; import { showAddBlueprintDialog } from "./show-dialog-import-blueprint"; +import { storage } from "../../../common/decorators/storage"; type BlueprintMetaDataPath = BlueprintMetaData & { path: string; @@ -92,8 +94,24 @@ class HaBlueprintOverview extends LitElement { Blueprints >; + @storage({ key: "blueprint-table-sort", state: false, subscribe: false }) + private _activeSorting?: SortingChangedEvent; + + @storage({ key: "blueprint-table-grouping", state: false, subscribe: false }) + private _activeGrouping?: string; + + @storage({ + key: "blueprint-table-collapsed", + state: false, + subscribe: false, + }) + private _activeCollapsed?: string; + private _processedBlueprints = memoizeOne( - (blueprints: Record): BlueprintMetaDataPath[] => { + ( + blueprints: Record, + localize: LocalizeFunc + ): BlueprintMetaDataPath[] => { const result: any[] = []; Object.entries(blueprints).forEach(([type, typeBlueprints]) => Object.entries(typeBlueprints).forEach(([path, blueprint]) => { @@ -101,6 +119,9 @@ class HaBlueprintOverview extends LitElement { result.push({ name: blueprint.error, type, + translated_type: localize( + `ui.panel.config.blueprint.overview.types.${type as "automation" | "script"}` + ), error: true, path, fullpath: `${type}/${path}`, @@ -109,6 +130,9 @@ class HaBlueprintOverview extends LitElement { result.push({ ...blueprint.metadata, type, + translated_type: localize( + `ui.panel.config.blueprint.overview.types.${type as "automation" | "script"}` + ), error: false, path, fullpath: `${type}/${path}`, @@ -140,14 +164,11 @@ class HaBlueprintOverview extends LitElement { ` : undefined, }, - type: { + translated_type: { title: localize("ui.panel.config.blueprint.overview.headers.type"), - template: (blueprint) => - html`${this.hass.localize( - `ui.panel.config.blueprint.overview.types.${blueprint.type}` - )}`, sortable: true, filterable: true, + groupable: true, hidden: narrow, direction: "asc", width: "10%", @@ -256,7 +277,7 @@ class HaBlueprintOverview extends LitElement { this.hass.language, this.hass.localize )} - .data=${this._processedBlueprints(this.blueprints)} + .data=${this._processedBlueprints(this.blueprints, this.hass.localize)} id="fullpath" .noDataText=${this.hass.localize( "ui.panel.config.blueprint.overview.no_blueprints" @@ -281,6 +302,12 @@ class HaBlueprintOverview extends LitElement { > `} + .initialGroupColumn=${this._activeGrouping} + .initialCollapsedGroups=${this._activeCollapsed} + .initialSorting=${this._activeSorting} + @sorting-changed=${this._handleSortingChanged} + @grouping-changed=${this._handleGroupingChanged} + @collapsed-changed=${this._handleCollapseChanged} > ) { - const blueprint = this._processedBlueprints(this.blueprints).find( - (b) => b.fullpath === ev.detail.id - )!; + const blueprint = this._processedBlueprints( + this.blueprints, + this.hass.localize + ).find((b) => b.fullpath === ev.detail.id)!; if (blueprint.error) { showAlertDialog(this, { title: this.hass.localize("ui.panel.config.blueprint.overview.error", { @@ -502,6 +530,18 @@ class HaBlueprintOverview extends LitElement { fireEvent(this, "reload-blueprints"); }; + private _handleSortingChanged(ev: CustomEvent) { + this._activeSorting = ev.detail; + } + + private _handleGroupingChanged(ev: CustomEvent) { + this._activeGrouping = ev.detail.value; + } + + private _handleCollapseChanged(ev: CustomEvent) { + this._activeCollapsed = ev.detail.value; + } + static get styles(): CSSResultGroup { return haStyle; } diff --git a/src/panels/config/entities/ha-config-entities.ts b/src/panels/config/entities/ha-config-entities.ts index 338ccd31e6..1e8d9a6db4 100644 --- a/src/panels/config/entities/ha-config-entities.ts +++ b/src/panels/config/entities/ha-config-entities.ts @@ -29,6 +29,7 @@ import { ifDefined } from "lit/directives/if-defined"; import { styleMap } from "lit/directives/style-map"; import memoize from "memoize-one"; import { computeCssColor } from "../../../common/color/compute-color"; +import { storage } from "../../../common/decorators/storage"; import type { HASSDomEvent } from "../../../common/dom/fire_event"; import { computeDomain } from "../../../common/entity/compute_domain"; import { computeStateName } from "../../../common/entity/compute_state_name"; @@ -37,10 +38,15 @@ import { protocolIntegrationPicked, } from "../../../common/integrations/protocolIntegrationPicked"; import { LocalizeFunc } from "../../../common/translations/localize"; +import { + hasRejectedItems, + rejectedItems, +} from "../../../common/util/promise-all-settled-results"; import type { DataTableColumnContainer, RowClickedEvent, SelectionChangedEvent, + SortingChangedEvent, } from "../../../components/data-table/ha-data-table"; import "../../../components/data-table/ha-data-table-labels"; import "../../../components/ha-alert"; @@ -66,6 +72,11 @@ import { removeEntityRegistryEntry, updateEntityRegistryEntry, } from "../../../data/entity_registry"; +import { + EntitySources, + fetchEntitySourcesWithCache, +} from "../../../data/entity_sources"; +import { domainToName } from "../../../data/integration"; import { LabelRegistryEntry, createLabelRegistryEntry, @@ -86,15 +97,6 @@ import { configSections } from "../ha-panel-config"; import "../integrations/ha-integration-overflow-menu"; import { showAddIntegrationDialog } from "../integrations/show-add-integration-dialog"; import { showLabelDetailDialog } from "../labels/show-dialog-label-detail"; -import { - EntitySources, - fetchEntitySourcesWithCache, -} from "../../../data/entity_sources"; -import { - hasRejectedItems, - rejectedItems, -} from "../../../common/util/promise-all-settled-results"; -import { domainToName } from "../../../data/integration"; export interface StateEntity extends Omit { @@ -151,6 +153,19 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { @state() private _entitySources?: EntitySources; + @storage({ key: "entities-table-sort", state: false, subscribe: false }) + private _activeSorting?: SortingChangedEvent; + + @storage({ key: "entities-table-grouping", state: false, subscribe: false }) + private _activeGrouping?: string; + + @storage({ + key: "entities-table-collapsed", + state: false, + subscribe: false, + }) + private _activeCollapsed?: string; + @query("hass-tabs-subpage-data-table", true) private _dataTable!: HaTabsSubpageDataTable; @@ -265,7 +280,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { }, domain: { title: localize("ui.panel.config.entities.picker.headers.domain"), - sortable: true, + sortable: false, hidden: true, filterable: true, groupable: true, @@ -603,6 +618,12 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { .filter=${this._filter} selectable .selected=${this._selected.length} + .initialGroupColumn=${this._activeGrouping} + .initialCollapsedGroups=${this._activeCollapsed} + .initialSorting=${this._activeSorting} + @sorting-changed=${this._handleSortingChanged} + @grouping-changed=${this._handleGroupingChanged} + @collapsed-changed=${this._handleCollapseChanged} @selection-changed=${this._handleSelectionChanged} clickable @clear-filter=${this._clearFilter} @@ -1205,6 +1226,18 @@ ${rejected }); } + private _handleSortingChanged(ev: CustomEvent) { + this._activeSorting = ev.detail; + } + + private _handleGroupingChanged(ev: CustomEvent) { + this._activeGrouping = ev.detail.value; + } + + private _handleCollapseChanged(ev: CustomEvent) { + this._activeCollapsed = ev.detail.value; + } + static get styles(): CSSResultGroup { return [ haStyle, diff --git a/src/panels/config/helpers/ha-config-helpers.ts b/src/panels/config/helpers/ha-config-helpers.ts index 14149c7d19..6be09330ee 100644 --- a/src/panels/config/helpers/ha-config-helpers.ts +++ b/src/panels/config/helpers/ha-config-helpers.ts @@ -24,6 +24,7 @@ import { import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { computeCssColor } from "../../../common/color/compute-color"; +import { storage } from "../../../common/decorators/storage"; import { HASSDomEvent } from "../../../common/dom/fire_event"; import { computeStateDomain } from "../../../common/entity/compute_state_domain"; import { navigate } from "../../../common/navigate"; @@ -40,6 +41,7 @@ import { DataTableColumnContainer, RowClickedEvent, SelectionChangedEvent, + SortingChangedEvent, } from "../../../components/data-table/ha-data-table"; import "../../../components/data-table/ha-data-table-labels"; import "../../../components/ha-fab"; @@ -139,6 +141,19 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { @property({ attribute: false }) public route!: Route; + @storage({ key: "helpers-table-sort", state: false, subscribe: false }) + private _activeSorting?: SortingChangedEvent; + + @storage({ key: "helpers-table-grouping", state: false, subscribe: false }) + private _activeGrouping?: string; + + @storage({ + key: "helpers-table-collapsed", + state: false, + subscribe: false, + }) + private _activeCollapsed?: string; + @state() private _stateItems: HassEntity[] = []; @state() private _entityEntries?: Record; @@ -525,7 +540,12 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { ).length} .columns=${this._columns(this.narrow, this.hass.localize)} .data=${helpers} - initialGroupColumn="category" + .initialGroupColumn=${this._activeGrouping || "category"} + .initialCollapsedGroups=${this._activeCollapsed} + .initialSorting=${this._activeSorting} + @sorting-changed=${this._handleSortingChanged} + @grouping-changed=${this._handleGroupingChanged} + @collapsed-changed=${this._handleCollapseChanged} .activeFilters=${this._activeFilters} @clear-filter=${this._clearFilter} @row-click=${this._openEditDialog} @@ -1020,6 +1040,18 @@ ${rejected }); } + private _handleSortingChanged(ev: CustomEvent) { + this._activeSorting = ev.detail; + } + + private _handleGroupingChanged(ev: CustomEvent) { + this._activeGrouping = ev.detail.value; + } + + private _handleCollapseChanged(ev: CustomEvent) { + this._activeCollapsed = ev.detail.value; + } + static get styles(): CSSResultGroup { return [ haStyle, diff --git a/src/panels/config/labels/ha-config-labels.ts b/src/panels/config/labels/ha-config-labels.ts index 5e05b468ae..43f346ce6a 100644 --- a/src/panels/config/labels/ha-config-labels.ts +++ b/src/panels/config/labels/ha-config-labels.ts @@ -10,15 +10,17 @@ import { LitElement, PropertyValues, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { computeCssColor } from "../../../common/color/compute-color"; +import { navigate } from "../../../common/navigate"; import { LocalizeFunc } from "../../../common/translations/localize"; import { DataTableColumnContainer, RowClickedEvent, + SortingChangedEvent, } from "../../../components/data-table/ha-data-table"; import "../../../components/ha-fab"; import "../../../components/ha-icon-button"; -import "../../../components/ha-relative-time"; import "../../../components/ha-icon-overflow-menu"; +import "../../../components/ha-relative-time"; import { LabelRegistryEntry, LabelRegistryEntryMutableParams, @@ -35,7 +37,7 @@ import "../../../layouts/hass-tabs-subpage-data-table"; import { HomeAssistant, Route } from "../../../types"; import { configSections } from "../ha-panel-config"; import { showLabelDetailDialog } from "./show-dialog-label-detail"; -import { navigate } from "../../../common/navigate"; +import { storage } from "../../../common/decorators/storage"; @customElement("ha-config-labels") export class HaConfigLabels extends LitElement { @@ -49,6 +51,13 @@ export class HaConfigLabels extends LitElement { @state() private _labels: LabelRegistryEntry[] = []; + @storage({ + key: "labels-table-sort", + state: false, + subscribe: false, + }) + private _activeSorting?: SortingChangedEvent; + private _columns = memoizeOne((localize: LocalizeFunc) => { const columns: DataTableColumnContainer = { icon: { @@ -149,6 +158,8 @@ export class HaConfigLabels extends LitElement { .data=${this._data(this._labels)} .noDataText=${this.hass.localize("ui.panel.config.labels.no_labels")} hasFab + .initialSorting=${this._activeSorting} + @sorting-changed=${this._handleSortingChanged} @row-click=${this._editLabel} clickable id="label_id" @@ -268,6 +279,10 @@ export class HaConfigLabels extends LitElement { `/config/automation/dashboard?historyBack=1&label=${label.label_id}` ); } + + private _handleSortingChanged(ev: CustomEvent) { + this._activeSorting = ev.detail; + } } declare global { diff --git a/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts b/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts index c393cd0ced..a089218c8c 100644 --- a/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts +++ b/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts @@ -16,6 +16,7 @@ import { stringCompare } from "../../../../common/string/compare"; import { DataTableColumnContainer, RowClickedEvent, + SortingChangedEvent, } from "../../../../components/data-table/ha-data-table"; import "../../../../components/ha-clickable-list-item"; import "../../../../components/ha-fab"; @@ -46,6 +47,7 @@ import { showNewDashboardDialog } from "../../dashboard/show-dialog-new-dashboar import { lovelaceTabs } from "../ha-config-lovelace"; import { showDashboardConfigureStrategyDialog } from "./show-dialog-lovelace-dashboard-configure-strategy"; import { showDashboardDetailDialog } from "./show-dialog-lovelace-dashboard-detail"; +import { storage } from "../../../../common/decorators/storage"; type DataTableItem = Pick< LovelaceDashboard, @@ -68,6 +70,13 @@ export class HaConfigLovelaceDashboards extends LitElement { @state() private _dashboards: LovelaceDashboard[] = []; + @storage({ + key: "lovelace-dashboards-table-sort", + state: false, + subscribe: false, + }) + private _activeSorting?: SortingChangedEvent; + public willUpdate() { if (!this.hasUpdated) { this.hass.loadFragmentTranslation("lovelace"); @@ -293,6 +302,8 @@ export class HaConfigLovelaceDashboards extends LitElement { this.hass.localize )} .data=${this._getItems(this._dashboards)} + .initialSorting=${this._activeSorting} + @sorting-changed=${this._handleSortingChanged} @row-click=${this._editDashboard} id="url_path" hasFab @@ -440,6 +451,10 @@ export class HaConfigLovelaceDashboards extends LitElement { }, }); } + + private _handleSortingChanged(ev: CustomEvent) { + this._activeSorting = ev.detail; + } } declare global { diff --git a/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts b/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts index 273713b950..3131af913e 100644 --- a/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts +++ b/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts @@ -10,9 +10,11 @@ import { import { customElement, property, state } from "lit/decorators"; import memoize from "memoize-one"; import { stringCompare } from "../../../../common/string/compare"; +import { LocalizeFunc } from "../../../../common/translations/localize"; import { DataTableColumnContainer, RowClickedEvent, + SortingChangedEvent, } from "../../../../components/data-table/ha-data-table"; import "../../../../components/ha-card"; import "../../../../components/ha-fab"; @@ -33,10 +35,10 @@ import "../../../../layouts/hass-subpage"; import "../../../../layouts/hass-tabs-subpage-data-table"; import { haStyle } from "../../../../resources/styles"; import { HomeAssistant, Route } from "../../../../types"; -import { LocalizeFunc } from "../../../../common/translations/localize"; import { loadLovelaceResources } from "../../../lovelace/common/load-resources"; import { lovelaceResourcesTabs } from "../ha-config-lovelace"; import { showResourceDetailDialog } from "./show-dialog-lovelace-resource-detail"; +import { storage } from "../../../../common/decorators/storage"; @customElement("ha-config-lovelace-resources") export class HaConfigLovelaceRescources extends LitElement { @@ -50,6 +52,13 @@ export class HaConfigLovelaceRescources extends LitElement { @state() private _resources: LovelaceResource[] = []; + @storage({ + key: "lovelace-resources-table-sort", + state: false, + subscribe: false, + }) + private _activeSorting?: SortingChangedEvent; + private _columns = memoize( ( _language, @@ -127,6 +136,8 @@ export class HaConfigLovelaceRescources extends LitElement { .noDataText=${this.hass.localize( "ui.panel.config.lovelace.resources.picker.no_resources" )} + .initialSorting=${this._activeSorting} + @sorting-changed=${this._handleSortingChanged} @row-click=${this._editResource} hasFab clickable @@ -237,6 +248,10 @@ export class HaConfigLovelaceRescources extends LitElement { }); } + private _handleSortingChanged(ev: CustomEvent) { + this._activeSorting = ev.detail; + } + static get styles(): CSSResultGroup { return [ haStyle, diff --git a/src/panels/config/scene/ha-scene-dashboard.ts b/src/panels/config/scene/ha-scene-dashboard.ts index 9a0402f6a9..c77bb1fb50 100644 --- a/src/panels/config/scene/ha-scene-dashboard.ts +++ b/src/panels/config/scene/ha-scene-dashboard.ts @@ -32,14 +32,20 @@ import memoizeOne from "memoize-one"; import { computeCssColor } from "../../../common/color/compute-color"; import { formatShortDateTime } from "../../../common/datetime/format_date_time"; import { relativeTime } from "../../../common/datetime/relative_time"; +import { storage } from "../../../common/decorators/storage"; import { HASSDomEvent, fireEvent } from "../../../common/dom/fire_event"; import { computeStateName } from "../../../common/entity/compute_state_name"; import { navigate } from "../../../common/navigate"; import { LocalizeFunc } from "../../../common/translations/localize"; +import { + hasRejectedItems, + rejectedItems, +} from "../../../common/util/promise-all-settled-results"; import { DataTableColumnContainer, RowClickedEvent, SelectionChangedEvent, + SortingChangedEvent, } from "../../../components/data-table/ha-data-table"; import "../../../components/data-table/ha-data-table-labels"; import "../../../components/ha-button"; @@ -95,10 +101,6 @@ import { showAssignCategoryDialog } from "../category/show-dialog-assign-categor import { showCategoryRegistryDetailDialog } from "../category/show-dialog-category-registry-detail"; import { configSections } from "../ha-panel-config"; import { showLabelDetailDialog } from "../labels/show-dialog-label-detail"; -import { - hasRejectedItems, - rejectedItems, -} from "../../../common/util/promise-all-settled-results"; type SceneItem = SceneEntity & { name: string; @@ -144,6 +146,19 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) { @consume({ context: fullEntitiesContext, subscribe: true }) _entityReg!: EntityRegistryEntry[]; + @storage({ key: "scene-table-sort", state: false, subscribe: false }) + private _activeSorting?: SortingChangedEvent; + + @storage({ key: "scene-table-grouping", state: false, subscribe: false }) + private _activeGrouping?: string; + + @storage({ + key: "scene-table-collapsed", + state: false, + subscribe: false, + }) + private _activeCollapsed?: string; + private _sizeController = new ResizeController(this, { callback: (entries) => entries[0]?.contentRect.width, }); @@ -463,7 +478,12 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) { ).length} .columns=${this._columns(this.narrow, this.hass.localize)} id="entity_id" - initialGroupColumn="category" + .initialGroupColumn=${this._activeGrouping || "category"} + .initialCollapsedGroups=${this._activeCollapsed} + .initialSorting=${this._activeSorting} + @sorting-changed=${this._handleSortingChanged} + @grouping-changed=${this._handleGroupingChanged} + @collapsed-changed=${this._handleCollapseChanged} .data=${scenes} .empty=${!this.scenes.length} .activeFilters=${this._activeFilters} @@ -975,6 +995,18 @@ ${rejected }); } + private _handleSortingChanged(ev: CustomEvent) { + this._activeSorting = ev.detail; + } + + private _handleGroupingChanged(ev: CustomEvent) { + this._activeGrouping = ev.detail.value; + } + + private _handleCollapseChanged(ev: CustomEvent) { + this._activeCollapsed = ev.detail.value; + } + static get styles(): CSSResultGroup { return [ haStyle, diff --git a/src/panels/config/script/ha-script-picker.ts b/src/panels/config/script/ha-script-picker.ts index 8282abf6b7..2d8f5e70c1 100644 --- a/src/panels/config/script/ha-script-picker.ts +++ b/src/panels/config/script/ha-script-picker.ts @@ -33,14 +33,20 @@ import { computeCssColor } from "../../../common/color/compute-color"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { formatShortDateTime } from "../../../common/datetime/format_date_time"; import { relativeTime } from "../../../common/datetime/relative_time"; +import { storage } from "../../../common/decorators/storage"; import { HASSDomEvent, fireEvent } from "../../../common/dom/fire_event"; import { computeStateName } from "../../../common/entity/compute_state_name"; import { navigate } from "../../../common/navigate"; import { LocalizeFunc } from "../../../common/translations/localize"; +import { + hasRejectedItems, + rejectedItems, +} from "../../../common/util/promise-all-settled-results"; import { DataTableColumnContainer, RowClickedEvent, SelectionChangedEvent, + SortingChangedEvent, } from "../../../components/data-table/ha-data-table"; import "../../../components/data-table/ha-data-table-labels"; import "../../../components/ha-fab"; @@ -97,10 +103,6 @@ import { showAssignCategoryDialog } from "../category/show-dialog-assign-categor import { showCategoryRegistryDetailDialog } from "../category/show-dialog-category-registry-detail"; import { configSections } from "../ha-panel-config"; import { showLabelDetailDialog } from "../labels/show-dialog-label-detail"; -import { - hasRejectedItems, - rejectedItems, -} from "../../../common/util/promise-all-settled-results"; type ScriptItem = ScriptEntity & { name: string; @@ -148,6 +150,19 @@ class HaScriptPicker extends SubscribeMixin(LitElement) { @consume({ context: fullEntitiesContext, subscribe: true }) _entityReg!: EntityRegistryEntry[]; + @storage({ key: "script-table-sort", state: false, subscribe: false }) + private _activeSorting?: SortingChangedEvent; + + @storage({ key: "script-table-grouping", state: false, subscribe: false }) + private _activeGrouping?: string; + + @storage({ + key: "script-table-collapsed", + state: false, + subscribe: false, + }) + private _activeCollapsed?: string; + private _sizeController = new ResizeController(this, { callback: (entries) => entries[0]?.contentRect.width, }); @@ -462,7 +477,12 @@ class HaScriptPicker extends SubscribeMixin(LitElement) { { number: scripts.length } )} hasFilters - initialGroupColumn="category" + .initialGroupColumn=${this._activeGrouping || "category"} + .initialCollapsedGroups=${this._activeCollapsed} + .initialSorting=${this._activeSorting} + @sorting-changed=${this._handleSortingChanged} + @grouping-changed=${this._handleGroupingChanged} + @collapsed-changed=${this._handleCollapseChanged} selectable .selected=${this._selected.length} @selection-changed=${this._handleSelectionChanged} @@ -1091,6 +1111,18 @@ ${rejected }); } + private _handleSortingChanged(ev: CustomEvent) { + this._activeSorting = ev.detail; + } + + private _handleGroupingChanged(ev: CustomEvent) { + this._activeGrouping = ev.detail.value; + } + + private _handleCollapseChanged(ev: CustomEvent) { + this._activeCollapsed = ev.detail.value; + } + static get styles(): CSSResultGroup { return [ haStyle, diff --git a/src/panels/config/users/ha-config-users.ts b/src/panels/config/users/ha-config-users.ts index 2512e0d3d1..a99d3ccf03 100644 --- a/src/panels/config/users/ha-config-users.ts +++ b/src/panels/config/users/ha-config-users.ts @@ -7,6 +7,7 @@ import { LocalizeFunc } from "../../../common/translations/localize"; import { DataTableColumnContainer, RowClickedEvent, + SortingChangedEvent, } from "../../../components/data-table/ha-data-table"; import "../../../components/data-table/ha-data-table-icon"; import "../../../components/ha-fab"; @@ -25,6 +26,7 @@ import { HomeAssistant, Route } from "../../../types"; import { configSections } from "../ha-panel-config"; import { showAddUserDialog } from "./show-dialog-add-user"; import { showUserDetailDialog } from "./show-dialog-user-detail"; +import { storage } from "../../../common/decorators/storage"; @customElement("ha-config-users") export class HaConfigUsers extends LitElement { @@ -38,6 +40,19 @@ export class HaConfigUsers extends LitElement { @state() private _users: User[] = []; + @storage({ key: "users-table-sort", state: false, subscribe: false }) + private _activeSorting?: SortingChangedEvent; + + @storage({ key: "users-table-grouping", state: false, subscribe: false }) + private _activeGrouping?: string; + + @storage({ + key: "users-table-collapsed", + state: false, + subscribe: false, + }) + private _activeCollapsed?: string; + private _columns = memoizeOne( (narrow: boolean, localize: LocalizeFunc): DataTableColumnContainer => { const columns: DataTableColumnContainer = { @@ -70,16 +85,14 @@ export class HaConfigUsers extends LitElement { hidden: narrow, template: (user) => html`${user.username || "—"}`, }, - group_ids: { + group: { title: localize("ui.panel.config.users.picker.headers.group"), sortable: true, filterable: true, + groupable: true, width: "20%", direction: "asc", hidden: narrow, - template: (user) => html` - ${localize(`groups.${user.group_ids[0]}`)} - `, }, is_active: { title: this.hass.localize( @@ -164,7 +177,13 @@ export class HaConfigUsers extends LitElement { backPath="/config" .tabs=${configSections.persons} .columns=${this._columns(this.narrow, this.hass.localize)} - .data=${this._users} + .data=${this._userData(this._users, this.hass.localize)} + .initialGroupColumn=${this._activeGrouping} + .initialCollapsedGroups=${this._activeCollapsed} + .initialSorting=${this._activeSorting} + @sorting-changed=${this._handleSortingChanged} + @grouping-changed=${this._handleGroupingChanged} + @collapsed-changed=${this._handleCollapseChanged} @row-click=${this._editUser} hasFab clickable @@ -181,6 +200,13 @@ export class HaConfigUsers extends LitElement { `; } + private _userData = memoizeOne((users: User[], localize: LocalizeFunc) => + users.map((user) => ({ + ...user, + group: localize(`groups.${user.group_ids[0]}`), + })) + ); + private async _fetchUsers() { this._users = await fetchUsers(this.hass); @@ -245,6 +271,18 @@ export class HaConfigUsers extends LitElement { }, }); } + + private _handleSortingChanged(ev: CustomEvent) { + this._activeSorting = ev.detail; + } + + private _handleGroupingChanged(ev: CustomEvent) { + this._activeGrouping = ev.detail.value; + } + + private _handleCollapseChanged(ev: CustomEvent) { + this._activeCollapsed = ev.detail.value; + } } declare global { diff --git a/src/panels/config/voice-assistants/ha-config-voice-assistants-expose.ts b/src/panels/config/voice-assistants/ha-config-voice-assistants-expose.ts index c64ea12cef..68caa31338 100644 --- a/src/panels/config/voice-assistants/ha-config-voice-assistants-expose.ts +++ b/src/panels/config/voice-assistants/ha-config-voice-assistants-expose.ts @@ -23,6 +23,7 @@ import { DataTableRowData, RowClickedEvent, SelectionChangedEvent, + SortingChangedEvent, } from "../../../components/data-table/ha-data-table"; import "../../../components/ha-fab"; import { AlexaEntity, fetchCloudAlexaEntities } from "../../../data/alexa"; @@ -52,6 +53,7 @@ import "./expose/expose-assistant-icon"; import { voiceAssistantTabs } from "./ha-config-voice-assistants"; import { showExposeEntityDialog } from "./show-dialog-expose-entity"; import { showVoiceSettingsDialog } from "./show-dialog-voice-settings"; +import { storage } from "../../../common/decorators/storage"; @customElement("ha-config-voice-assistants-expose") export class VoiceAssistantsExpose extends LitElement { @@ -87,6 +89,13 @@ export class VoiceAssistantsExpose extends LitElement { string[] | undefined >; + @storage({ + key: "voice-expose-table-sort", + state: false, + subscribe: false, + }) + private _activeSorting?: SortingChangedEvent; + @query("hass-tabs-subpage-data-table", true) private _dataTable!: HaTabsSubpageDataTable; @@ -505,6 +514,8 @@ export class VoiceAssistantsExpose extends LitElement { selectable .selected=${this._selectedEntities.length} clickable + .initialSorting=${this._activeSorting} + @sorting-changed=${this._handleSortingChanged} @selection-changed=${this._handleSelectionChanged} @clear-filter=${this._clearFilter} @search-changed=${this._handleSearchChange} @@ -696,6 +707,10 @@ export class VoiceAssistantsExpose extends LitElement { navigate(window.location.pathname, { replace: true }); } + private _handleSortingChanged(ev: CustomEvent) { + this._activeSorting = ev.detail; + } + static get styles(): CSSResultGroup { return [ haStyle,