Improve backup data-table on mobile (#23283)

* Improve backup data-table on mobile

* Group by default

* Fix top-header slot on mobile
This commit is contained in:
Paul Bottein 2024-12-16 12:23:17 +01:00 committed by GitHub
parent 1efe61445f
commit efcd57934a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 81 additions and 16 deletions

View File

@ -468,7 +468,7 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
${!this.narrow ${!this.narrow
? html` ? html`
<div slot="header"> <div slot="header">
<slot name="top_header"></slot> <slot name="top-header"></slot>
<slot name="header"> <slot name="header">
<div class="table-header"> <div class="table-header">
${this.hasFilters && !this.showFilters ${this.hasFilters && !this.showFilters
@ -478,13 +478,19 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
</slot> </slot>
</div> </div>
` `
: html`<div slot="header"></div> : html`
<div slot="header">
<slot name="top-header"></slot>
</div>
<div slot="header-row" class="narrow-header-row"> <div slot="header-row" class="narrow-header-row">
${this.hasFilters && !this.showFilters ${this.hasFilters && !this.showFilters
? html`${filterButton}` ? html`${filterButton}`
: nothing} : nothing}
${selectModeBtn}${groupByMenu}${sortByMenu}${settingsButton} ${selectModeBtn}
</div>`} <div class="flex"></div>
${groupByMenu}${sortByMenu}${settingsButton}
</div>
`}
</ha-data-table>`} </ha-data-table>`}
<div slot="fab"><slot name="fab"></slot></div> <div slot="fab"><slot name="fab"></slot></div>
</hass-tabs-subpage> </hass-tabs-subpage>
@ -835,13 +841,20 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
.narrow-header-row { .narrow-header-row {
display: flex; display: flex;
align-items: center; align-items: center;
min-width: 100%;
gap: 16px; gap: 16px;
padding: 0 16px; padding: 0 16px;
box-sizing: border-box;
overflow-x: scroll; overflow-x: scroll;
-ms-overflow-style: none; -ms-overflow-style: none;
scrollbar-width: none; scrollbar-width: none;
} }
.narrow-header-row .flex {
flex: 1;
margin-left: -16px;
}
.selection-bar { .selection-bar {
background: rgba(var(--rgb-primary-color), 0.1); background: rgba(var(--rgb-primary-color), 0.1);
width: 100%; width: 100%;

View File

@ -13,13 +13,14 @@ import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { relativeTime } from "../../../common/datetime/relative_time"; import { relativeTime } from "../../../common/datetime/relative_time";
import { storage } from "../../../common/decorators/storage";
import type { HASSDomEvent } from "../../../common/dom/fire_event"; import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { computeDomain } from "../../../common/entity/compute_domain";
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event"; import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
import { navigate } from "../../../common/navigate"; import { navigate } from "../../../common/navigate";
import type { LocalizeFunc } from "../../../common/translations/localize"; import type { LocalizeFunc } from "../../../common/translations/localize";
import type { import type {
DataTableColumnContainer, DataTableColumnContainer,
DataTableRowData,
RowClickedEvent, RowClickedEvent,
SelectionChangedEvent, SelectionChangedEvent,
} from "../../../components/data-table/ha-data-table"; } from "../../../components/data-table/ha-data-table";
@ -71,7 +72,15 @@ import { showBackupOnboardingDialog } from "./dialogs/show-dialog-backup_onboard
import { showGenerateBackupDialog } from "./dialogs/show-dialog-generate-backup"; import { showGenerateBackupDialog } from "./dialogs/show-dialog-generate-backup";
import { showNewBackupDialog } from "./dialogs/show-dialog-new-backup"; import { showNewBackupDialog } from "./dialogs/show-dialog-new-backup";
import { showUploadBackupDialog } from "./dialogs/show-dialog-upload-backup"; import { showUploadBackupDialog } from "./dialogs/show-dialog-upload-backup";
import { computeDomain } from "../../../common/entity/compute_domain"; import { capitalizeFirstLetter } from "../../../common/string/capitalize-first-letter";
interface BackupRow extends BackupContent {
formatted_type: string;
}
type BackupType = "strategy" | "custom";
const TYPE_ORDER: Array<BackupType> = ["strategy", "custom"];
@customElement("ha-config-backup-dashboard") @customElement("ha-config-backup-dashboard")
class HaConfigBackupDashboard extends SubscribeMixin(LitElement) { class HaConfigBackupDashboard extends SubscribeMixin(LitElement) {
@ -91,20 +100,29 @@ class HaConfigBackupDashboard extends SubscribeMixin(LitElement) {
@state() private _config?: BackupConfig; @state() private _config?: BackupConfig;
@storage({ key: "backups-table-grouping", state: false, subscribe: false })
private _activeGrouping?: string = "formatted_type";
@storage({
key: "backups-table-collapsed",
state: false,
subscribe: false,
})
private _activeCollapsed?: string;
private _subscribed?: Promise<() => void>; private _subscribed?: Promise<() => void>;
@query("hass-tabs-subpage-data-table", true) @query("hass-tabs-subpage-data-table", true)
private _dataTable!: HaTabsSubpageDataTable; private _dataTable!: HaTabsSubpageDataTable;
private _columns = memoizeOne( private _columns = memoizeOne(
(localize: LocalizeFunc): DataTableColumnContainer<BackupContent> => ({ (localize: LocalizeFunc): DataTableColumnContainer<BackupRow> => ({
name: { name: {
title: localize("ui.panel.config.backup.name"), title: localize("ui.panel.config.backup.name"),
main: true, main: true,
sortable: true, sortable: true,
filterable: true, filterable: true,
flex: 2, flex: 3,
template: (backup) => backup.name,
}, },
size: { size: {
title: localize("ui.panel.config.backup.size"), title: localize("ui.panel.config.backup.size"),
@ -120,15 +138,17 @@ class HaConfigBackupDashboard extends SubscribeMixin(LitElement) {
template: (backup) => template: (backup) =>
relativeTime(new Date(backup.date), this.hass.locale), relativeTime(new Date(backup.date), this.hass.locale),
}, },
with_strategy_settings: { formatted_type: {
title: "Type", title: "Type",
filterable: true, filterable: true,
sortable: true, sortable: true,
template: (backup) => groupable: true,
backup.with_strategy_settings ? "Strategy" : "Custom",
}, },
locations: { locations: {
title: "Locations", title: "Locations",
showNarrow: true,
minWidth: "60px",
maxWidth: "120px",
template: (backup) => html` template: (backup) => html`
<div style="display: flex; gap: 4px;"> <div style="display: flex; gap: 4px;">
${(backup.agent_ids || []).map((agentId) => { ${(backup.agent_ids || []).map((agentId) => {
@ -198,18 +218,44 @@ class HaConfigBackupDashboard extends SubscribeMixin(LitElement) {
}) })
); );
private _groupOrder = memoizeOne((activeGrouping: string | undefined) =>
activeGrouping === "formatted_type"
? TYPE_ORDER.map((type) => this._formatBackupType(type))
: undefined
);
private _handleGroupingChanged(ev: CustomEvent) {
this._activeGrouping = ev.detail.value;
}
private _handleCollapseChanged(ev: CustomEvent) {
this._activeCollapsed = ev.detail.value;
}
private _handleSelectionChanged( private _handleSelectionChanged(
ev: HASSDomEvent<SelectionChangedEvent> ev: HASSDomEvent<SelectionChangedEvent>
): void { ): void {
this._selected = ev.detail.value; this._selected = ev.detail.value;
} }
private _formatBackupType(type: BackupType): string {
// Todo translate
return capitalizeFirstLetter(type);
}
private _data = memoizeOne((backups: BackupContent[]): BackupRow[] =>
backups.map((backup) => ({
...backup,
formatted_type: this._formatBackupType(
backup.with_strategy_settings ? "strategy" : "custom"
),
}))
);
protected render(): TemplateResult { protected render(): TemplateResult {
const backupInProgress = const backupInProgress =
"state" in this._manager && this._manager.state === "in_progress"; "state" in this._manager && this._manager.state === "in_progress";
const data: DataTableRowData[] = this._backups;
return html` return html`
<hass-tabs-subpage-data-table <hass-tabs-subpage-data-table
hasFab hasFab
@ -226,17 +272,22 @@ class HaConfigBackupDashboard extends SubscribeMixin(LitElement) {
id="backup_id" id="backup_id"
selectable selectable
.selected=${this._selected.length} .selected=${this._selected.length}
.initialGroupColumn=${this._activeGrouping}
.initialCollapsedGroups=${this._activeCollapsed}
.groupOrder=${this._groupOrder(this._activeGrouping)}
@grouping-changed=${this._handleGroupingChanged}
@collapsed-changed=${this._handleCollapseChanged}
@selection-changed=${this._handleSelectionChanged} @selection-changed=${this._handleSelectionChanged}
.route=${this.route} .route=${this.route}
@row-click=${this._showBackupDetails} @row-click=${this._showBackupDetails}
.columns=${this._columns(this.hass.localize)} .columns=${this._columns(this.hass.localize)}
.data=${data} .data=${this._data(this._backups)}
.noDataText=${this.hass.localize("ui.panel.config.backup.no_backups")} .noDataText=${this.hass.localize("ui.panel.config.backup.no_backups")}
.searchLabel=${this.hass.localize( .searchLabel=${this.hass.localize(
"ui.panel.config.backup.picker.search" "ui.panel.config.backup.picker.search"
)} )}
> >
<div slot="top_header" class="header"> <div slot="top-header" class="header">
${this._fetching ${this._fetching
? html` ? html`
<ha-backup-summary-card <ha-backup-summary-card

View File

@ -37,6 +37,7 @@ class HaConfigBackup extends HassRouterPage {
protected updatePageEl(pageEl, changedProps: PropertyValues) { protected updatePageEl(pageEl, changedProps: PropertyValues) {
pageEl.hass = this.hass; pageEl.hass = this.hass;
pageEl.route = this.routeTail; pageEl.route = this.routeTail;
pageEl.narrow = this.narrow;
if ( if (
(!changedProps || changedProps.has("route")) && (!changedProps || changedProps.has("route")) &&