Allow to hide and sort columns in data tables (#21168)

* Allow to hide and sort columns in data tables

* fix unused

* store
This commit is contained in:
Bram Kragten 2024-06-26 11:51:32 +02:00 committed by GitHub
parent adea384f40
commit 7d28f3f585
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 1043 additions and 436 deletions

View File

@ -0,0 +1,280 @@
import "@material/mwc-list";
import { mdiDrag, mdiEye, mdiEyeOff } from "@mdi/js";
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { repeat } from "lit/directives/repeat";
import memoizeOne from "memoize-one";
import { haStyleDialog } from "../../resources/styles";
import { HomeAssistant } from "../../types";
import { createCloseHeading } from "../ha-dialog";
import "../ha-list-item";
import "../ha-sortable";
import "../ha-button";
import { DataTableColumnContainer, DataTableColumnData } from "./ha-data-table";
import { DataTableSettingsDialogParams } from "./show-dialog-data-table-settings";
import { fireEvent } from "../../common/dom/fire_event";
@customElement("dialog-data-table-settings")
export class DialogDataTableSettings extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _params?: DataTableSettingsDialogParams;
@state() private _columnOrder?: string[];
@state() private _hiddenColumns?: string[];
public showDialog(params: DataTableSettingsDialogParams) {
this._params = params;
this._columnOrder = params.columnOrder;
this._hiddenColumns = params.hiddenColumns;
}
public closeDialog() {
this._params = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
private _sortedColumns = memoizeOne(
(
columns: DataTableColumnContainer,
columnOrder: string[] | undefined,
hiddenColumns: string[] | undefined
) =>
Object.keys(columns)
.filter((col) => !columns[col].hidden)
.sort((a, b) => {
const orderA = columnOrder?.indexOf(a) ?? -1;
const orderB = columnOrder?.indexOf(b) ?? -1;
const hiddenA =
hiddenColumns?.includes(a) ?? Boolean(columns[a].defaultHidden);
const hiddenB =
hiddenColumns?.includes(b) ?? Boolean(columns[b].defaultHidden);
if (hiddenA !== hiddenB) {
return hiddenA ? 1 : -1;
}
if (orderA !== orderB) {
if (orderA === -1) {
return 1;
}
if (orderB === -1) {
return -1;
}
}
return orderA - orderB;
})
.reduce(
(arr, key) => {
arr.push({ key, ...columns[key] });
return arr;
},
[] as (DataTableColumnData & { key: string })[]
)
);
protected render() {
if (!this._params) {
return nothing;
}
const columns = this._sortedColumns(
this._params.columns,
this._columnOrder,
this._hiddenColumns
);
return html`
<ha-dialog
open
@closed=${this.closeDialog}
.heading=${createCloseHeading(
this.hass,
this.hass.localize("ui.components.data-table.settings.header")
)}
>
<ha-sortable
@item-moved=${this._columnMoved}
draggable-selector=".draggable"
handle-selector=".handle"
>
<mwc-list>
${repeat(
columns,
(col) => col.key,
(col, _idx) => {
const canMove = !col.main && col.moveable !== false;
const canHide = !col.main && col.hideable !== false;
const isVisible = !(this._columnOrder &&
this._columnOrder.includes(col.key)
? this._hiddenColumns?.includes(col.key) ?? col.defaultHidden
: col.defaultHidden);
return html`<ha-list-item
hasMeta
class=${classMap({
hidden: !isVisible,
draggable: canMove && isVisible,
})}
graphic="icon"
noninteractive
>${col.title || col.label || col.key}
${canMove && isVisible
? html`<ha-svg-icon
class="handle"
.path=${mdiDrag}
slot="graphic"
></ha-svg-icon>`
: nothing}
<ha-icon-button
tabindex="0"
class="action"
.disabled=${!canHide}
.hidden=${!isVisible}
.path=${isVisible ? mdiEye : mdiEyeOff}
slot="meta"
.label=${this.hass!.localize(
`ui.components.data-table.settings.${isVisible ? "hide" : "show"}`,
{ title: typeof col.title === "string" ? col.title : "" }
)}
.column=${col.key}
@click=${this._toggle}
></ha-icon-button>
</ha-list-item>`;
}
)}
</mwc-list>
</ha-sortable>
<ha-button slot="secondaryAction" @click=${this._reset}
>${this.hass.localize(
"ui.components.data-table.settings.restore"
)}</ha-button
>
<ha-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.components.data-table.settings.done")}
</ha-button>
</ha-dialog>
`;
}
private _columnMoved(ev: CustomEvent): void {
ev.stopPropagation();
if (!this._params) {
return;
}
const { oldIndex, newIndex } = ev.detail;
const columns = this._sortedColumns(
this._params.columns,
this._columnOrder,
this._hiddenColumns
);
const columnOrder = columns.map((column) => column.key);
const option = columnOrder.splice(oldIndex, 1)[0];
columnOrder.splice(newIndex, 0, option);
this._columnOrder = columnOrder;
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
}
_toggle(ev) {
if (!this._params) {
return;
}
const column = ev.target.column;
const wasHidden = ev.target.hidden;
const hidden = [
...(this._hiddenColumns ??
Object.entries(this._params.columns)
.filter(([_key, col]) => col.defaultHidden)
.map(([key]) => key)),
];
if (wasHidden && hidden.includes(column)) {
hidden.splice(hidden.indexOf(column), 1);
} else if (!wasHidden) {
hidden.push(column);
}
const columns = this._sortedColumns(
this._params.columns,
this._columnOrder,
this._hiddenColumns
);
if (!this._columnOrder) {
this._columnOrder = columns.map((col) => col.key);
} else {
columns.forEach((col) => {
if (!this._columnOrder!.includes(col.key)) {
this._columnOrder!.push(col.key);
if (col.defaultHidden) {
hidden.push(col.key);
}
}
});
}
this._hiddenColumns = hidden;
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
}
_reset() {
this._columnOrder = undefined;
this._hiddenColumns = undefined;
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
this.closeDialog();
}
static get styles(): CSSResultGroup {
return [
haStyleDialog,
css`
ha-dialog {
--mdc-dialog-max-width: 500px;
--dialog-z-index: 10;
--dialog-content-padding: 0 8px;
}
@media all and (max-width: 451px) {
ha-dialog {
--vertical-align-dialog: flex-start;
--dialog-surface-margin-top: 250px;
--ha-dialog-border-radius: 28px 28px 0 0;
--mdc-dialog-min-height: calc(100% - 250px);
--mdc-dialog-max-height: calc(100% - 250px);
}
}
ha-list-item {
--mdc-list-side-padding: 12px;
overflow: visible;
}
.hidden {
color: var(--disabled-text-color);
}
.handle {
cursor: move; /* fallback if grab cursor is unsupported */
cursor: grab;
}
.actions {
display: flex;
flex-direction: row;
}
ha-icon-button {
display: block;
margin: -12px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-data-table-settings": DialogDataTableSettings;
}
}

View File

@ -65,6 +65,10 @@ export interface DataTableSortColumnData {
valueColumn?: string; valueColumn?: string;
direction?: SortingDirection; direction?: SortingDirection;
groupable?: boolean; groupable?: boolean;
moveable?: boolean;
hideable?: boolean;
defaultHidden?: boolean;
showNarrow?: boolean;
} }
export interface DataTableColumnData<T = any> extends DataTableSortColumnData { export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
@ -79,6 +83,7 @@ export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
| "overflow-menu" | "overflow-menu"
| "flex"; | "flex";
template?: (row: T) => TemplateResult | string | typeof nothing; template?: (row: T) => TemplateResult | string | typeof nothing;
extraTemplate?: (row: T) => TemplateResult | string | typeof nothing;
width?: string; width?: string;
maxWidth?: string; maxWidth?: string;
grows?: boolean; grows?: boolean;
@ -105,6 +110,8 @@ const UNDEFINED_GROUP_KEY = "zzzzz_undefined";
export class HaDataTable extends LitElement { export class HaDataTable extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public narrow = false;
@property({ type: Object }) public columns: DataTableColumnContainer = {}; @property({ type: Object }) public columns: DataTableColumnContainer = {};
@property({ type: Array }) public data: DataTableRowData[] = []; @property({ type: Array }) public data: DataTableRowData[] = [];
@ -145,6 +152,10 @@ export class HaDataTable extends LitElement {
@property({ attribute: false }) public initialCollapsedGroups?: string[]; @property({ attribute: false }) public initialCollapsedGroups?: string[];
@property({ attribute: false }) public hiddenColumns?: string[];
@property({ attribute: false }) public columnOrder?: string[];
@state() private _filterable = false; @state() private _filterable = false;
@state() private _filter = ""; @state() private _filter = "";
@ -235,6 +246,7 @@ export class HaDataTable extends LitElement {
(column: ClonedDataTableColumnData) => { (column: ClonedDataTableColumnData) => {
delete column.title; delete column.title;
delete column.template; delete column.template;
delete column.extraTemplate;
} }
); );
@ -272,12 +284,44 @@ export class HaDataTable extends LitElement {
this._sortFilterData(); this._sortFilterData();
} }
if (properties.has("selectable")) { if (properties.has("selectable") || properties.has("hiddenColumns")) {
this._items = [...this._items]; this._items = [...this._items];
} }
} }
private _sortedColumns = memoizeOne(
(columns: DataTableColumnContainer, columnOrder?: string[]) => {
if (!columnOrder || !columnOrder.length) {
return columns;
}
return Object.keys(columns)
.sort((a, b) => {
const orderA = columnOrder!.indexOf(a);
const orderB = columnOrder!.indexOf(b);
if (orderA !== orderB) {
if (orderA === -1) {
return 1;
}
if (orderB === -1) {
return -1;
}
}
return orderA - orderB;
})
.reduce((obj, key) => {
obj[key] = columns[key];
return obj;
}, {}) as DataTableColumnContainer;
}
);
protected render() { protected render() {
const columns = this._sortedColumns(this.columns, this.columnOrder);
const renderRow = (row: DataTableRowData, index: number) =>
this._renderRow(columns, this.narrow, row, index);
return html` return html`
<div class="mdc-data-table"> <div class="mdc-data-table">
<slot name="header" @slotchange=${this._calcTableHeight}> <slot name="header" @slotchange=${this._calcTableHeight}>
@ -326,9 +370,14 @@ export class HaDataTable extends LitElement {
</div> </div>
` `
: ""} : ""}
${Object.entries(this.columns).map(([key, column]) => { ${Object.entries(columns).map(([key, column]) => {
if (column.hidden) { if (
return ""; column.hidden ||
(this.columnOrder && this.columnOrder.includes(key)
? this.hiddenColumns?.includes(key) ?? column.defaultHidden
: column.defaultHidden)
) {
return nothing;
} }
const sorted = key === this.sortColumn; const sorted = key === this.sortColumn;
const classes = { const classes = {
@ -399,7 +448,7 @@ export class HaDataTable extends LitElement {
@scroll=${this._saveScrollPos} @scroll=${this._saveScrollPos}
.items=${this._items} .items=${this._items}
.keyFunction=${this._keyFunction} .keyFunction=${this._keyFunction}
.renderItem=${this._renderRow} .renderItem=${renderRow}
></lit-virtualizer> ></lit-virtualizer>
`} `}
</div> </div>
@ -409,7 +458,12 @@ export class HaDataTable extends LitElement {
private _keyFunction = (row: DataTableRowData) => row?.[this.id] || row; private _keyFunction = (row: DataTableRowData) => row?.[this.id] || row;
private _renderRow = (row: DataTableRowData, index: number) => { private _renderRow = (
columns: DataTableColumnContainer,
narrow: boolean,
row: DataTableRowData,
index: number
) => {
// not sure how this happens... // not sure how this happens...
if (!row) { if (!row) {
return nothing; return nothing;
@ -454,8 +508,14 @@ export class HaDataTable extends LitElement {
</div> </div>
` `
: ""} : ""}
${Object.entries(this.columns).map(([key, column]) => { ${Object.entries(columns).map(([key, column]) => {
if (column.hidden) { if (
(narrow && !column.main && !column.showNarrow) ||
column.hidden ||
(this.columnOrder && this.columnOrder.includes(key)
? this.hiddenColumns?.includes(key) ?? column.defaultHidden
: column.defaultHidden)
) {
return nothing; return nothing;
} }
return html` return html`
@ -482,7 +542,38 @@ export class HaDataTable extends LitElement {
}) })
: ""} : ""}
> >
${column.template ? column.template(row) : row[key]} ${column.template
? column.template(row)
: narrow && column.main
? html`<div class="primary">${row[key]}</div>
<div class="secondary">
${Object.entries(columns)
.filter(
([key2, column2]) =>
!column2.hidden &&
!column2.main &&
!column2.showNarrow &&
!(this.columnOrder &&
this.columnOrder.includes(key2)
? this.hiddenColumns?.includes(key2) ??
column2.defaultHidden
: column2.defaultHidden)
)
.map(
([key2, column2], i) =>
html`${i !== 0
? " ⸱ "
: nothing}${column2.template
? column2.template(row)
: row[key2]}`
)}
</div>
${column.extraTemplate
? column.extraTemplate(row)
: nothing}`
: html`${row[key]}${column.extraTemplate
? column.extraTemplate(row)
: nothing}`}
</div> </div>
`; `;
})} })}
@ -861,6 +952,7 @@ export class HaDataTable extends LitElement {
width: 100%; width: 100%;
border: 0; border: 0;
white-space: nowrap; white-space: nowrap;
position: relative;
} }
.mdc-data-table__cell { .mdc-data-table__cell {

View File

@ -0,0 +1,26 @@
import { fireEvent } from "../../common/dom/fire_event";
import { DataTableColumnContainer } from "./ha-data-table";
export interface DataTableSettingsDialogParams {
columns: DataTableColumnContainer;
onUpdate: (
columnOrder: string[] | undefined,
hiddenColumns: string[] | undefined
) => void;
hiddenColumns?: string[];
columnOrder?: string[];
}
export const loadDataTableSettingsDialog = () =>
import("./dialog-data-table-settings");
export const showDataTableSettingsDialog = (
element: HTMLElement,
dialogParams: DataTableSettingsDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-data-table-settings",
dialogImport: loadDataTableSettingsDialog,
dialogParams,
});
};

View File

@ -6,6 +6,7 @@ import {
mdiArrowDown, mdiArrowDown,
mdiArrowUp, mdiArrowUp,
mdiClose, mdiClose,
mdiCog,
mdiFilterVariant, mdiFilterVariant,
mdiFilterVariantRemove, mdiFilterVariantRemove,
mdiFormatListChecks, mdiFormatListChecks,
@ -42,6 +43,7 @@ import "../components/search-input-outlined";
import type { HomeAssistant, Route } from "../types"; import type { HomeAssistant, Route } from "../types";
import "./hass-tabs-subpage"; import "./hass-tabs-subpage";
import type { PageNavigation } from "./hass-tabs-subpage"; import type { PageNavigation } from "./hass-tabs-subpage";
import { showDataTableSettingsDialog } from "../components/data-table/show-dialog-data-table-settings";
@customElement("hass-tabs-subpage-data-table") @customElement("hass-tabs-subpage-data-table")
export class HaTabsSubpageDataTable extends LitElement { export class HaTabsSubpageDataTable extends LitElement {
@ -171,6 +173,10 @@ export class HaTabsSubpageDataTable extends LitElement {
@property({ attribute: false }) public groupOrder?: string[]; @property({ attribute: false }) public groupOrder?: string[];
@property({ attribute: false }) public columnOrder?: string[];
@property({ attribute: false }) public hiddenColumns?: string[];
@state() private _sortColumn?: string; @state() private _sortColumn?: string;
@state() private _sortDirection: SortingDirection = null; @state() private _sortDirection: SortingDirection = null;
@ -290,6 +296,14 @@ export class HaTabsSubpageDataTable extends LitElement {
` `
: nothing; : nothing;
const settingsButton = html`<ha-assist-chip
class="has-dropdown select-mode-chip"
@click=${this._openSettings}
.title=${localize("ui.components.subpage-data-table.settings")}
>
<ha-svg-icon slot="icon" .path=${mdiCog}></ha-svg-icon>
</ha-assist-chip>`;
return html` return html`
<hass-tabs-subpage <hass-tabs-subpage
.hass=${this.hass} .hass=${this.hass}
@ -416,6 +430,7 @@ export class HaTabsSubpageDataTable extends LitElement {
: ""} : ""}
<ha-data-table <ha-data-table
.hass=${this.hass} .hass=${this.hass}
.narrow=${this.narrow}
.columns=${this.columns} .columns=${this.columns}
.data=${this.data} .data=${this.data}
.noDataText=${this.noDataText} .noDataText=${this.noDataText}
@ -430,6 +445,8 @@ export class HaTabsSubpageDataTable extends LitElement {
.groupColumn=${this._groupColumn} .groupColumn=${this._groupColumn}
.groupOrder=${this.groupOrder} .groupOrder=${this.groupOrder}
.initialCollapsedGroups=${this.initialCollapsedGroups} .initialCollapsedGroups=${this.initialCollapsedGroups}
.columnOrder=${this.columnOrder}
.hiddenColumns=${this.hiddenColumns}
> >
${!this.narrow ${!this.narrow
? html` ? html`
@ -438,7 +455,7 @@ export class HaTabsSubpageDataTable extends LitElement {
<div class="table-header"> <div class="table-header">
${this.hasFilters && !this.showFilters ${this.hasFilters && !this.showFilters
? html`${filterButton}` ? html`${filterButton}`
: nothing}${selectModeBtn}${searchBar}${groupByMenu}${sortByMenu} : nothing}${selectModeBtn}${searchBar}${groupByMenu}${sortByMenu}${settingsButton}
</div> </div>
</slot> </slot>
</div> </div>
@ -448,7 +465,7 @@ export class HaTabsSubpageDataTable extends LitElement {
${this.hasFilters && !this.showFilters ${this.hasFilters && !this.showFilters
? html`${filterButton}` ? html`${filterButton}`
: nothing} : nothing}
${selectModeBtn}${groupByMenu}${sortByMenu} ${selectModeBtn}${groupByMenu}${sortByMenu}${settingsButton}
</div>`} </div>`}
</ha-data-table>`} </ha-data-table>`}
<div slot="fab"><slot name="fab"></slot></div> <div slot="fab"><slot name="fab"></slot></div>
@ -608,6 +625,22 @@ export class HaTabsSubpageDataTable extends LitElement {
fireEvent(this, "grouping-changed", { value: columnId }); fireEvent(this, "grouping-changed", { value: columnId });
} }
private _openSettings() {
showDataTableSettingsDialog(this, {
columns: this.columns,
hiddenColumns: this.hiddenColumns,
columnOrder: this.columnOrder,
onUpdate: (
columnOrder: string[] | undefined,
hiddenColumns: string[] | undefined
) => {
this.columnOrder = columnOrder;
this.hiddenColumns = hiddenColumns;
fireEvent(this, "columns-changed", { columnOrder, hiddenColumns });
},
});
}
private _collapseAllGroups() { private _collapseAllGroups() {
this._dataTable.collapseAllGroups(); this._dataTable.collapseAllGroups();
} }
@ -874,6 +907,10 @@ declare global {
interface HASSDomEvents { interface HASSDomEvents {
"search-changed": { value: string }; "search-changed": { value: string };
"grouping-changed": { value: string }; "grouping-changed": { value: string };
"columns-changed": {
columnOrder: string[] | undefined;
hiddenColumns: string[] | undefined;
};
"clear-filter": undefined; "clear-filter": undefined;
} }
} }

View File

@ -192,6 +192,20 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
}) })
private _activeCollapsed?: string; private _activeCollapsed?: string;
@storage({
key: "automation-table-column-order",
state: false,
subscribe: false,
})
private _activeColumnOrder?: string[];
@storage({
key: "automation-table-hidden-columns",
state: false,
subscribe: false,
})
private _activeHiddenColumns?: string[];
@query("#overflow-menu") private _overflowMenu!: HaMenu; @query("#overflow-menu") private _overflowMenu!: HaMenu;
private _sizeController = new ResizeController(this, { private _sizeController = new ResizeController(this, {
@ -253,6 +267,8 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
title: "", title: "",
label: localize("ui.panel.config.automation.picker.headers.state"), label: localize("ui.panel.config.automation.picker.headers.state"),
type: "icon", type: "icon",
moveable: false,
showNarrow: true,
template: (automation) => template: (automation) =>
html`<ha-state-icon html`<ha-state-icon
.hass=${this.hass} .hass=${this.hass}
@ -272,30 +288,13 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
filterable: true, filterable: true,
direction: "asc", direction: "asc",
grows: true, grows: true,
template: (automation) => { extraTemplate: (automation) =>
const date = new Date(automation.attributes.last_triggered); automation.labels.length
const now = new Date();
const dayDifference = differenceInDays(now, date);
return html`
<div style="font-size: 14px;">${automation.name}</div>
${narrow
? html`<div class="secondary">
${this.hass.localize("ui.card.automation.last_triggered")}:
${automation.attributes.last_triggered
? dayDifference > 3
? formatShortDateTime(date, locale, this.hass.config)
: relativeTime(date, locale)
: localize("ui.components.relative_time.never")}
</div>`
: nothing}
${automation.labels.length
? html`<ha-data-table-labels ? html`<ha-data-table-labels
@label-clicked=${this._labelClicked} @label-clicked=${this._labelClicked}
.labels=${automation.labels} .labels=${automation.labels}
></ha-data-table-labels>` ></ha-data-table-labels>`
: nothing} : nothing,
`;
},
}, },
area: { area: {
title: localize("ui.panel.config.automation.picker.headers.area"), title: localize("ui.panel.config.automation.picker.headers.area"),
@ -322,7 +321,6 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
sortable: true, sortable: true,
width: "130px", width: "130px",
title: localize("ui.card.automation.last_triggered"), title: localize("ui.card.automation.last_triggered"),
hidden: narrow,
template: (automation) => { template: (automation) => {
if (!automation.last_triggered) { if (!automation.last_triggered) {
return this.hass.localize("ui.components.relative_time.never"); return this.hass.localize("ui.components.relative_time.never");
@ -341,9 +339,9 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
width: "82px", width: "82px",
sortable: true, sortable: true,
groupable: true, groupable: true,
hidden: narrow,
title: "", title: "",
type: "overflow", type: "overflow",
hidden: narrow,
label: this.hass.localize("ui.panel.config.automation.picker.state"), label: this.hass.localize("ui.panel.config.automation.picker.state"),
template: (automation) => html` template: (automation) => html`
<ha-entity-toggle <ha-entity-toggle
@ -356,6 +354,9 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
title: "", title: "",
width: "64px", width: "64px",
type: "icon-button", type: "icon-button",
showNarrow: true,
moveable: false,
hideable: false,
template: (automation) => html` template: (automation) => html`
<ha-icon-button <ha-icon-button
.automation=${automation} .automation=${automation}
@ -545,6 +546,9 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
.initialGroupColumn=${this._activeGrouping || "category"} .initialGroupColumn=${this._activeGrouping || "category"}
.initialCollapsedGroups=${this._activeCollapsed} .initialCollapsedGroups=${this._activeCollapsed}
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
.columnOrder=${this._activeColumnOrder}
.hiddenColumns=${this._activeHiddenColumns}
@columns-changed=${this._handleColumnsChanged}
@sorting-changed=${this._handleSortingChanged} @sorting-changed=${this._handleSortingChanged}
@grouping-changed=${this._handleGroupingChanged} @grouping-changed=${this._handleGroupingChanged}
@collapsed-changed=${this._handleCollapseChanged} @collapsed-changed=${this._handleCollapseChanged}
@ -1415,6 +1419,11 @@ ${rejected
this._activeCollapsed = ev.detail.value; this._activeCollapsed = ev.detail.value;
} }
private _handleColumnsChanged(ev: CustomEvent) {
this._activeColumnOrder = ev.detail.columnOrder;
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,

View File

@ -60,14 +60,19 @@ class HaConfigBackup extends LitElement {
sortable: true, sortable: true,
filterable: true, filterable: true,
grows: true, grows: true,
template: (backup) => template: narrow
? undefined
: (backup) =>
html`${backup.name} html`${backup.name}
<div class="secondary">${backup.path}</div>`, <div class="secondary">${backup.path}</div>`,
}, },
path: {
title: localize("ui.panel.config.backup.path"),
hidden: !narrow,
},
size: { size: {
title: localize("ui.panel.config.backup.size"), title: localize("ui.panel.config.backup.size"),
width: "15%", width: "15%",
hidden: narrow,
filterable: true, filterable: true,
sortable: true, sortable: true,
template: (backup) => Math.ceil(backup.size * 10) / 10 + " MB", template: (backup) => Math.ceil(backup.size * 10) / 10 + " MB",
@ -76,7 +81,6 @@ class HaConfigBackup extends LitElement {
title: localize("ui.panel.config.backup.created"), title: localize("ui.panel.config.backup.created"),
width: "15%", width: "15%",
direction: "desc", direction: "desc",
hidden: narrow,
filterable: true, filterable: true,
sortable: true, sortable: true,
template: (backup) => template: (backup) =>
@ -87,6 +91,9 @@ class HaConfigBackup extends LitElement {
title: "", title: "",
width: "15%", width: "15%",
type: "overflow-menu", type: "overflow-menu",
showNarrow: true,
hideable: false,
moveable: false,
template: (backup) => template: (backup) =>
html`<ha-icon-overflow-menu html`<ha-icon-overflow-menu
.hass=${this.hass} .hass=${this.hass}

View File

@ -107,6 +107,20 @@ class HaBlueprintOverview extends LitElement {
}) })
private _activeCollapsed?: string; private _activeCollapsed?: string;
@storage({
key: "blueprint-table-column-order",
state: false,
subscribe: false,
})
private _activeColumnOrder?: string[];
@storage({
key: "blueprint-table-hidden-columns",
state: false,
subscribe: false,
})
private _activeHiddenColumns?: string[];
@storage({ @storage({
storage: "sessionStorage", storage: "sessionStorage",
key: "blueprint-table-search", key: "blueprint-table-search",
@ -154,8 +168,6 @@ class HaBlueprintOverview extends LitElement {
private _columns = memoizeOne( private _columns = memoizeOne(
( (
narrow,
_language,
localize: LocalizeFunc localize: LocalizeFunc
): DataTableColumnContainer<BlueprintMetaDataPath> => ({ ): DataTableColumnContainer<BlueprintMetaDataPath> => ({
name: { name: {
@ -165,19 +177,12 @@ class HaBlueprintOverview extends LitElement {
filterable: true, filterable: true,
direction: "asc", direction: "asc",
grows: true, grows: true,
template: narrow
? (blueprint) => html`
${blueprint.name}<br />
<div class="secondary">${blueprint.path}</div>
`
: undefined,
}, },
translated_type: { translated_type: {
title: localize("ui.panel.config.blueprint.overview.headers.type"), title: localize("ui.panel.config.blueprint.overview.headers.type"),
sortable: true, sortable: true,
filterable: true, filterable: true,
groupable: true, groupable: true,
hidden: narrow,
direction: "asc", direction: "asc",
width: "10%", width: "10%",
}, },
@ -185,7 +190,6 @@ class HaBlueprintOverview extends LitElement {
title: localize("ui.panel.config.blueprint.overview.headers.file_name"), title: localize("ui.panel.config.blueprint.overview.headers.file_name"),
sortable: true, sortable: true,
filterable: true, filterable: true,
hidden: narrow,
direction: "asc", direction: "asc",
width: "25%", width: "25%",
}, },
@ -197,6 +201,9 @@ class HaBlueprintOverview extends LitElement {
title: "", title: "",
width: this.narrow ? undefined : "10%", width: this.narrow ? undefined : "10%",
type: "overflow-menu", type: "overflow-menu",
showNarrow: true,
moveable: false,
hideable: false,
template: (blueprint) => template: (blueprint) =>
blueprint.error blueprint.error
? html`<ha-svg-icon ? html`<ha-svg-icon
@ -280,11 +287,7 @@ class HaBlueprintOverview extends LitElement {
back-path="/config" back-path="/config"
.route=${this.route} .route=${this.route}
.tabs=${configSections.automations} .tabs=${configSections.automations}
.columns=${this._columns( .columns=${this._columns(this.hass.localize)}
this.narrow,
this.hass.language,
this.hass.localize
)}
.data=${this._processedBlueprints(this.blueprints, this.hass.localize)} .data=${this._processedBlueprints(this.blueprints, this.hass.localize)}
id="fullpath" id="fullpath"
.noDataText=${this.hass.localize( .noDataText=${this.hass.localize(
@ -313,6 +316,9 @@ class HaBlueprintOverview extends LitElement {
.initialGroupColumn=${this._activeGrouping} .initialGroupColumn=${this._activeGrouping}
.initialCollapsedGroups=${this._activeCollapsed} .initialCollapsedGroups=${this._activeCollapsed}
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
.columnOrder=${this._activeColumnOrder}
.hiddenColumns=${this._activeHiddenColumns}
@columns-changed=${this._handleColumnsChanged}
@sorting-changed=${this._handleSortingChanged} @sorting-changed=${this._handleSortingChanged}
@grouping-changed=${this._handleGroupingChanged} @grouping-changed=${this._handleGroupingChanged}
@collapsed-changed=${this._handleCollapseChanged} @collapsed-changed=${this._handleCollapseChanged}
@ -556,6 +562,11 @@ class HaBlueprintOverview extends LitElement {
this._filter = ev.detail.value; this._filter = ev.detail.value;
} }
private _handleColumnsChanged(ev: CustomEvent) {
this._activeColumnOrder = ev.detail.columnOrder;
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return haStyle; return haStyle;
} }

View File

@ -154,6 +154,20 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
@storage({ key: "devices-table-collapsed", state: false, subscribe: false }) @storage({ key: "devices-table-collapsed", state: false, subscribe: false })
private _activeCollapsed?: string; private _activeCollapsed?: string;
@storage({
key: "devices-table-column-order",
state: false,
subscribe: false,
})
private _activeColumnOrder?: string[];
@storage({
key: "devices-table-hidden-columns",
state: false,
subscribe: false,
})
private _activeHiddenColumns?: string[];
private _sizeController = new ResizeController(this, { private _sizeController = new ResizeController(this, {
callback: (entries) => entries[0]?.contentRect.width, callback: (entries) => entries[0]?.contentRect.width,
}); });
@ -434,10 +448,13 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
typeof this._devicesAndFilterDomains typeof this._devicesAndFilterDomains
>["devicesOutput"][number]; >["devicesOutput"][number];
const columns: DataTableColumnContainer<DeviceItem> = { return {
icon: { icon: {
title: "", title: "",
label: localize("ui.panel.config.devices.data_table.icon"),
type: "icon", type: "icon",
moveable: false,
showNarrow: true,
template: (device) => template: (device) =>
device.domains.length device.domains.length
? html`<img ? html`<img
@ -452,19 +469,14 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
/>` />`
: "", : "",
}, },
}; name: {
if (narrow) {
columns.name = {
title: localize("ui.panel.config.devices.data_table.device"), title: localize("ui.panel.config.devices.data_table.device"),
main: true, main: true,
sortable: true, sortable: true,
filterable: true, filterable: true,
direction: "asc", direction: "asc",
grows: true, grows: true,
template: (device) => html` extraTemplate: (device) => html`
<div style="font-size: 14px;">${device.name}</div>
<div class="secondary">${device.area} | ${device.integration}</div>
${device.label_entries.length ${device.label_entries.length
? html` ? html`
<ha-data-table-labels <ha-data-table-labels
@ -473,61 +485,37 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
` `
: nothing} : nothing}
`, `,
}; },
} else { manufacturer: {
columns.name = {
title: localize("ui.panel.config.devices.data_table.device"),
main: true,
sortable: true,
filterable: true,
direction: "asc",
grows: true,
template: (device) => html`
<div style="font-size: 14px;">${device.name}</div>
${device.label_entries.length
? html`
<ha-data-table-labels
.labels=${device.label_entries}
></ha-data-table-labels>
`
: nothing}
`,
};
}
columns.manufacturer = {
title: localize("ui.panel.config.devices.data_table.manufacturer"), title: localize("ui.panel.config.devices.data_table.manufacturer"),
sortable: true, sortable: true,
hidden: narrow,
filterable: true, filterable: true,
groupable: true, groupable: true,
width: "15%", width: "15%",
}; },
columns.model = { model: {
title: localize("ui.panel.config.devices.data_table.model"), title: localize("ui.panel.config.devices.data_table.model"),
sortable: true, sortable: true,
hidden: narrow,
filterable: true, filterable: true,
width: "15%", width: "15%",
}; },
columns.area = { area: {
title: localize("ui.panel.config.devices.data_table.area"), title: localize("ui.panel.config.devices.data_table.area"),
sortable: true, sortable: true,
hidden: narrow,
filterable: true, filterable: true,
groupable: true, groupable: true,
width: "15%", width: "15%",
}; },
columns.integration = { integration: {
title: localize("ui.panel.config.devices.data_table.integration"), title: localize("ui.panel.config.devices.data_table.integration"),
sortable: true, sortable: true,
hidden: narrow,
filterable: true, filterable: true,
groupable: true, groupable: true,
width: "15%", width: "15%",
}; },
columns.battery_entity = { battery_entity: {
title: localize("ui.panel.config.devices.data_table.battery"), title: localize("ui.panel.config.devices.data_table.battery"),
showNarrow: true,
sortable: true, sortable: true,
filterable: true, filterable: true,
type: "numeric", type: "numeric",
@ -540,7 +528,9 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
batteryEntityPair && batteryEntityPair[0] batteryEntityPair && batteryEntityPair[0]
? this.hass.states[batteryEntityPair[0]] ? this.hass.states[batteryEntityPair[0]]
: undefined; : undefined;
const batteryDomain = battery ? computeStateDomain(battery) : undefined; const batteryDomain = battery
? computeStateDomain(battery)
: undefined;
const batteryCharging = const batteryCharging =
batteryEntityPair && batteryEntityPair[1] batteryEntityPair && batteryEntityPair[1]
? this.hass.states[batteryEntityPair[1]] ? this.hass.states[batteryEntityPair[1]]
@ -560,8 +550,8 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
` `
: html``; : html``;
}, },
}; },
columns.disabled_by = { disabled_by: {
title: "", title: "",
label: localize("ui.panel.config.devices.data_table.disabled_by"), label: localize("ui.panel.config.devices.data_table.disabled_by"),
hidden: true, hidden: true,
@ -569,16 +559,15 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
device.disabled_by device.disabled_by
? this.hass.localize("ui.panel.config.devices.disabled") ? this.hass.localize("ui.panel.config.devices.disabled")
: "", : "",
}; },
columns.labels = { labels: {
title: "", title: "",
hidden: true, hidden: true,
filterable: true, filterable: true,
template: (device) => template: (device) =>
device.label_entries.map((lbl) => lbl.name).join(" "), device.label_entries.map((lbl) => lbl.name).join(" "),
}; },
} as DataTableColumnContainer<DeviceItem>;
return columns;
}); });
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] { protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
@ -704,6 +693,9 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
.initialGroupColumn=${this._activeGrouping} .initialGroupColumn=${this._activeGrouping}
.initialCollapsedGroups=${this._activeCollapsed} .initialCollapsedGroups=${this._activeCollapsed}
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
.columnOrder=${this._activeColumnOrder}
.hiddenColumns=${this._activeHiddenColumns}
@columns-changed=${this._handleColumnsChanged}
@clear-filter=${this._clearFilter} @clear-filter=${this._clearFilter}
@search-changed=${this._handleSearchChange} @search-changed=${this._handleSearchChange}
@sorting-changed=${this._handleSortingChanged} @sorting-changed=${this._handleSortingChanged}
@ -1043,6 +1035,11 @@ ${rejected
this._activeCollapsed = ev.detail.value; this._activeCollapsed = ev.detail.value;
} }
private _handleColumnsChanged(ev: CustomEvent) {
this._activeColumnOrder = ev.detail.columnOrder;
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
css` css`

View File

@ -186,6 +186,20 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
}) })
private _activeCollapsed?: string; private _activeCollapsed?: string;
@storage({
key: "entities-table-column-order",
state: false,
subscribe: false,
})
private _activeColumnOrder?: string[];
@storage({
key: "entities-table-hidden-columns",
state: false,
subscribe: false,
})
private _activeHiddenColumns?: string[];
@query("hass-tabs-subpage-data-table", true) @query("hass-tabs-subpage-data-table", true)
private _dataTable!: HaTabsSubpageDataTable; private _dataTable!: HaTabsSubpageDataTable;
@ -251,15 +265,13 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
]); ]);
private _columns = memoize( private _columns = memoize(
( (localize: LocalizeFunc): DataTableColumnContainer<EntityRow> => ({
localize: LocalizeFunc,
narrow,
_language
): DataTableColumnContainer<EntityRow> => ({
icon: { icon: {
title: "", title: "",
label: localize("ui.panel.config.entities.picker.headers.state_icon"), label: localize("ui.panel.config.entities.picker.headers.state_icon"),
type: "icon", type: "icon",
showNarrow: true,
moveable: false,
template: (entry) => template: (entry) =>
entry.icon entry.icon
? html`<ha-icon .icon=${entry.icon}></ha-icon>` ? html`<ha-icon .icon=${entry.icon}></ha-icon>`
@ -283,32 +295,23 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
filterable: true, filterable: true,
direction: "asc", direction: "asc",
grows: true, grows: true,
template: (entry) => html` extraTemplate: (entry) =>
<div style="font-size: 14px;">${entry.name}</div> entry.label_entries.length
${narrow
? html`<div class="secondary">
${entry.entity_id} | ${entry.localized_platform}
</div>`
: nothing}
${entry.label_entries.length
? html` ? html`
<ha-data-table-labels <ha-data-table-labels
.labels=${entry.label_entries} .labels=${entry.label_entries}
></ha-data-table-labels> ></ha-data-table-labels>
` `
: nothing} : nothing,
`,
}, },
entity_id: { entity_id: {
title: localize("ui.panel.config.entities.picker.headers.entity_id"), title: localize("ui.panel.config.entities.picker.headers.entity_id"),
hidden: narrow,
sortable: true, sortable: true,
filterable: true, filterable: true,
width: "25%", width: "25%",
}, },
localized_platform: { localized_platform: {
title: localize("ui.panel.config.entities.picker.headers.integration"), title: localize("ui.panel.config.entities.picker.headers.integration"),
hidden: narrow,
sortable: true, sortable: true,
groupable: true, groupable: true,
filterable: true, filterable: true,
@ -324,7 +327,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
area: { area: {
title: localize("ui.panel.config.entities.picker.headers.area"), title: localize("ui.panel.config.entities.picker.headers.area"),
sortable: true, sortable: true,
hidden: narrow,
filterable: true, filterable: true,
groupable: true, groupable: true,
width: "15%", width: "15%",
@ -343,6 +345,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
status: { status: {
title: localize("ui.panel.config.entities.picker.headers.status"), title: localize("ui.panel.config.entities.picker.headers.status"),
type: "icon", type: "icon",
showNarrow: true,
sortable: true, sortable: true,
filterable: true, filterable: true,
width: "68px", width: "68px",
@ -688,11 +691,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
} }
.route=${this.route} .route=${this.route}
.tabs=${configSections.devices} .tabs=${configSections.devices}
.columns=${this._columns( .columns=${this._columns(this.hass.localize)}
this.hass.localize,
this.narrow,
this.hass.language
)}
.data=${filteredEntities} .data=${filteredEntities}
.searchLabel=${this.hass.localize( .searchLabel=${this.hass.localize(
"ui.panel.config.entities.picker.search", "ui.panel.config.entities.picker.search",
@ -714,6 +713,9 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
.initialGroupColumn=${this._activeGrouping} .initialGroupColumn=${this._activeGrouping}
.initialCollapsedGroups=${this._activeCollapsed} .initialCollapsedGroups=${this._activeCollapsed}
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
.columnOrder=${this._activeColumnOrder}
.hiddenColumns=${this._activeHiddenColumns}
@columns-changed=${this._handleColumnsChanged}
@sorting-changed=${this._handleSortingChanged} @sorting-changed=${this._handleSortingChanged}
@grouping-changed=${this._handleGroupingChanged} @grouping-changed=${this._handleGroupingChanged}
@collapsed-changed=${this._handleCollapseChanged} @collapsed-changed=${this._handleCollapseChanged}
@ -1335,6 +1337,11 @@ ${rejected
this._activeCollapsed = ev.detail.value; this._activeCollapsed = ev.detail.value;
} }
private _handleColumnsChanged(ev: CustomEvent) {
this._activeColumnOrder = ev.detail.columnOrder;
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,

View File

@ -167,6 +167,20 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
}) })
private _filter = ""; private _filter = "";
@storage({
key: "helpers-table-column-order",
state: false,
subscribe: false,
})
private _activeColumnOrder?: string[];
@storage({
key: "helpers-table-hidden-columns",
state: false,
subscribe: false,
})
private _activeHiddenColumns?: string[];
@state() private _stateItems: HassEntity[] = []; @state() private _stateItems: HassEntity[] = [];
@state() private _entityEntries?: Record<string, EntityRegistryEntry>; @state() private _entityEntries?: Record<string, EntityRegistryEntry>;
@ -243,14 +257,13 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
} }
private _columns = memoizeOne( private _columns = memoizeOne(
( (localize: LocalizeFunc): DataTableColumnContainer<HelperItem> => ({
narrow: boolean,
localize: LocalizeFunc
): DataTableColumnContainer<HelperItem> => ({
icon: { icon: {
title: "", title: "",
label: localize("ui.panel.config.helpers.picker.headers.icon"), label: localize("ui.panel.config.helpers.picker.headers.icon"),
type: "icon", type: "icon",
showNarrow: true,
moveable: false,
template: (helper) => template: (helper) =>
helper.entity helper.entity
? html`<ha-state-icon ? html`<ha-state-icon
@ -269,23 +282,17 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
filterable: true, filterable: true,
grows: true, grows: true,
direction: "asc", direction: "asc",
template: (helper) => html` extraTemplate: (helper) =>
<div style="font-size: 14px;">${helper.name}</div> helper.label_entries.length
${narrow
? html`<div class="secondary">${helper.entity_id}</div> `
: nothing}
${helper.label_entries.length
? html` ? html`
<ha-data-table-labels <ha-data-table-labels
.labels=${helper.label_entries} .labels=${helper.label_entries}
></ha-data-table-labels> ></ha-data-table-labels>
` `
: nothing} : nothing,
`,
}, },
entity_id: { entity_id: {
title: localize("ui.panel.config.helpers.picker.headers.entity_id"), title: localize("ui.panel.config.helpers.picker.headers.entity_id"),
hidden: this.narrow,
sortable: true, sortable: true,
filterable: true, filterable: true,
width: "25%", width: "25%",
@ -313,10 +320,9 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
}, },
editable: { editable: {
title: "", title: "",
label: this.hass.localize( label: localize("ui.panel.config.helpers.picker.headers.editable"),
"ui.panel.config.helpers.picker.headers.editable"
),
type: "icon", type: "icon",
showNarrow: true,
template: (helper) => html` template: (helper) => html`
${!helper.editable ${!helper.editable
? html` ? html`
@ -337,8 +343,12 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
}, },
actions: { actions: {
title: "", title: "",
label: "Actions",
width: "64px", width: "64px",
type: "overflow-menu", type: "overflow-menu",
hideable: false,
moveable: false,
showNarrow: true,
template: (helper) => html` template: (helper) => html`
<ha-icon-overflow-menu <ha-icon-overflow-menu
.hass=${this.hass} .hass=${this.hass}
@ -556,11 +566,14 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
Array.isArray(val) ? val.length : val Array.isArray(val) ? val.length : val
) )
).length} ).length}
.columns=${this._columns(this.narrow, this.hass.localize)} .columns=${this._columns(this.hass.localize)}
.data=${helpers} .data=${helpers}
.initialGroupColumn=${this._activeGrouping || "category"} .initialGroupColumn=${this._activeGrouping || "category"}
.initialCollapsedGroups=${this._activeCollapsed} .initialCollapsedGroups=${this._activeCollapsed}
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
.columnOrder=${this._activeColumnOrder}
.hiddenColumns=${this._activeHiddenColumns}
@columns-changed=${this._handleColumnsChanged}
@sorting-changed=${this._handleSortingChanged} @sorting-changed=${this._handleSortingChanged}
@grouping-changed=${this._handleGroupingChanged} @grouping-changed=${this._handleGroupingChanged}
@collapsed-changed=${this._handleCollapseChanged} @collapsed-changed=${this._handleCollapseChanged}
@ -1084,6 +1097,11 @@ ${rejected
this._filter = ev.detail.value; this._filter = ev.detail.value;
} }
private _handleColumnsChanged(ev: CustomEvent) {
this._activeColumnOrder = ev.detail.columnOrder;
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,

View File

@ -66,10 +66,26 @@ export class HaConfigLabels extends LitElement {
}) })
private _activeSorting?: SortingChangedEvent; private _activeSorting?: SortingChangedEvent;
@storage({
key: "labels-table-column-order",
state: false,
subscribe: false,
})
private _activeColumnOrder?: string[];
@storage({
key: "labels-table-hidden-columns",
state: false,
subscribe: false,
})
private _activeHiddenColumns?: string[];
private _columns = memoizeOne((localize: LocalizeFunc) => { private _columns = memoizeOne((localize: LocalizeFunc) => {
const columns: DataTableColumnContainer<LabelRegistryEntry> = { const columns: DataTableColumnContainer<LabelRegistryEntry> = {
icon: { icon: {
title: "", title: "",
moveable: false,
showNarrow: true,
label: localize("ui.panel.config.labels.headers.icon"), label: localize("ui.panel.config.labels.headers.icon"),
type: "icon", type: "icon",
template: (label) => template: (label) =>
@ -77,6 +93,7 @@ export class HaConfigLabels extends LitElement {
}, },
color: { color: {
title: "", title: "",
showNarrow: true,
label: localize("ui.panel.config.labels.headers.color"), label: localize("ui.panel.config.labels.headers.color"),
type: "icon", type: "icon",
template: (label) => template: (label) =>
@ -105,6 +122,9 @@ export class HaConfigLabels extends LitElement {
}, },
actions: { actions: {
title: "", title: "",
showNarrow: true,
moveable: false,
hideable: false,
width: "64px", width: "64px",
type: "overflow-menu", type: "overflow-menu",
template: (label) => html` template: (label) => html`
@ -167,6 +187,9 @@ export class HaConfigLabels extends LitElement {
.noDataText=${this.hass.localize("ui.panel.config.labels.no_labels")} .noDataText=${this.hass.localize("ui.panel.config.labels.no_labels")}
hasFab hasFab
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
.columnOrder=${this._activeColumnOrder}
.hiddenColumns=${this._activeHiddenColumns}
@columns-changed=${this._handleColumnsChanged}
@sorting-changed=${this._handleSortingChanged} @sorting-changed=${this._handleSortingChanged}
.filter=${this._filter} .filter=${this._filter}
@search-changed=${this._handleSearchChange} @search-changed=${this._handleSearchChange}
@ -297,6 +320,11 @@ export class HaConfigLabels extends LitElement {
private _handleSearchChange(ev: CustomEvent) { private _handleSearchChange(ev: CustomEvent) {
this._filter = ev.detail.value; this._filter = ev.detail.value;
} }
private _handleColumnsChanged(ev: CustomEvent) {
this._activeColumnOrder = ev.detail.columnOrder;
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
} }
declare global { declare global {

View File

@ -85,6 +85,20 @@ export class HaConfigLovelaceDashboards extends LitElement {
}) })
private _activeSorting?: SortingChangedEvent; private _activeSorting?: SortingChangedEvent;
@storage({
key: "lovelace-dashboards-table-column-order",
state: false,
subscribe: false,
})
private _activeColumnOrder?: string[];
@storage({
key: "lovelace-dashboards-table-hidden-columns",
state: false,
subscribe: false,
})
private _activeHiddenColumns?: string[];
public willUpdate() { public willUpdate() {
if (!this.hasUpdated) { if (!this.hasUpdated) {
this.hass.loadFragmentTranslation("lovelace"); this.hass.loadFragmentTranslation("lovelace");
@ -101,6 +115,8 @@ export class HaConfigLovelaceDashboards extends LitElement {
const columns: DataTableColumnContainer<DataTableItem> = { const columns: DataTableColumnContainer<DataTableItem> = {
icon: { icon: {
title: "", title: "",
moveable: false,
showNarrow: true,
label: localize( label: localize(
"ui.panel.config.lovelace.dashboards.picker.headers.icon" "ui.panel.config.lovelace.dashboards.picker.headers.icon"
), ),
@ -128,8 +144,9 @@ export class HaConfigLovelaceDashboards extends LitElement {
sortable: true, sortable: true,
filterable: true, filterable: true,
grows: true, grows: true,
template: (dashboard) => { template: narrow
const titleTemplate = html` ? undefined
: (dashboard) => html`
${dashboard.title} ${dashboard.title}
${dashboard.default ${dashboard.default
? html` ? html`
@ -144,24 +161,10 @@ export class HaConfigLovelaceDashboards extends LitElement {
</simple-tooltip> </simple-tooltip>
` `
: ""} : ""}
`; `,
return narrow
? html`
${titleTemplate}
<div class="secondary">
${this.hass.localize(
`ui.panel.config.lovelace.dashboards.conf_mode.${dashboard.mode}`
)}${dashboard.filename
? html` ${dashboard.filename} `
: ""}
</div>
`
: titleTemplate;
},
}, },
}; };
if (!narrow) {
columns.mode = { columns.mode = {
title: localize( title: localize(
"ui.panel.config.lovelace.dashboards.picker.headers.conf_mode" "ui.panel.config.lovelace.dashboards.picker.headers.conf_mode"
@ -191,6 +194,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
), ),
sortable: true, sortable: true,
type: "icon", type: "icon",
hidden: narrow,
width: "100px", width: "100px",
template: (dashboard) => template: (dashboard) =>
dashboard.require_admin dashboard.require_admin
@ -202,13 +206,13 @@ export class HaConfigLovelaceDashboards extends LitElement {
"ui.panel.config.lovelace.dashboards.picker.headers.sidebar" "ui.panel.config.lovelace.dashboards.picker.headers.sidebar"
), ),
type: "icon", type: "icon",
hidden: narrow,
width: "121px", width: "121px",
template: (dashboard) => template: (dashboard) =>
dashboard.show_in_sidebar dashboard.show_in_sidebar
? html`<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>` ? html`<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>`
: html``, : html``,
}; };
}
columns.url_path = { columns.url_path = {
title: "", title: "",
@ -216,6 +220,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
"ui.panel.config.lovelace.dashboards.picker.headers.url" "ui.panel.config.lovelace.dashboards.picker.headers.url"
), ),
filterable: true, filterable: true,
showNarrow: true,
width: "100px", width: "100px",
template: (dashboard) => template: (dashboard) =>
narrow narrow
@ -311,6 +316,9 @@ export class HaConfigLovelaceDashboards extends LitElement {
)} )}
.data=${this._getItems(this._dashboards)} .data=${this._getItems(this._dashboards)}
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
.columnOrder=${this._activeColumnOrder}
.hiddenColumns=${this._activeHiddenColumns}
@columns-changed=${this._handleColumnsChanged}
@sorting-changed=${this._handleSortingChanged} @sorting-changed=${this._handleSortingChanged}
.filter=${this._filter} .filter=${this._filter}
@search-changed=${this._handleSearchChange} @search-changed=${this._handleSearchChange}
@ -467,6 +475,11 @@ export class HaConfigLovelaceDashboards extends LitElement {
private _handleSearchChange(ev: CustomEvent) { private _handleSearchChange(ev: CustomEvent) {
this._filter = ev.detail.value; this._filter = ev.detail.value;
} }
private _handleColumnsChanged(ev: CustomEvent) {
this._activeColumnOrder = ev.detail.columnOrder;
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
} }
declare global { declare global {

View File

@ -67,12 +67,27 @@ export class HaConfigLovelaceRescources extends LitElement {
}) })
private _activeSorting?: SortingChangedEvent; private _activeSorting?: SortingChangedEvent;
@storage({
key: "lovelace-resources-table-column-order",
state: false,
subscribe: false,
})
private _activeColumnOrder?: string[];
@storage({
key: "lovelace-resources-table-hidden-columns",
state: false,
subscribe: false,
})
private _activeHiddenColumns?: string[];
private _columns = memoize( private _columns = memoize(
( (
_language, _language,
localize: LocalizeFunc localize: LocalizeFunc
): DataTableColumnContainer<LovelaceResource> => ({ ): DataTableColumnContainer<LovelaceResource> => ({
url: { url: {
main: true,
title: localize( title: localize(
"ui.panel.config.lovelace.resources.picker.headers.url" "ui.panel.config.lovelace.resources.picker.headers.url"
), ),
@ -145,6 +160,9 @@ export class HaConfigLovelaceRescources extends LitElement {
"ui.panel.config.lovelace.resources.picker.no_resources" "ui.panel.config.lovelace.resources.picker.no_resources"
)} )}
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
.columnOrder=${this._activeColumnOrder}
.hiddenColumns=${this._activeHiddenColumns}
@columns-changed=${this._handleColumnsChanged}
@sorting-changed=${this._handleSortingChanged} @sorting-changed=${this._handleSortingChanged}
.filter=${this._filter} .filter=${this._filter}
@search-changed=${this._handleSearchChange} @search-changed=${this._handleSearchChange}
@ -266,6 +284,11 @@ export class HaConfigLovelaceRescources extends LitElement {
this._filter = ev.detail.value; this._filter = ev.detail.value;
} }
private _handleColumnsChanged(ev: CustomEvent) {
this._activeColumnOrder = ev.detail.columnOrder;
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,

View File

@ -180,6 +180,20 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
}) })
private _activeCollapsed?: string; private _activeCollapsed?: string;
@storage({
key: "scene-table-column-order",
state: false,
subscribe: false,
})
private _activeColumnOrder?: string[];
@storage({
key: "scene-table-hidden-columns",
state: false,
subscribe: false,
})
private _activeHiddenColumns?: string[];
private _sizeController = new ResizeController(this, { private _sizeController = new ResizeController(this, {
callback: (entries) => entries[0]?.contentRect.width, callback: (entries) => entries[0]?.contentRect.width,
}); });
@ -225,11 +239,13 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
); );
private _columns = memoizeOne( private _columns = memoizeOne(
(narrow, localize: LocalizeFunc): DataTableColumnContainer => { (localize: LocalizeFunc): DataTableColumnContainer => {
const columns: DataTableColumnContainer<SceneItem> = { const columns: DataTableColumnContainer<SceneItem> = {
icon: { icon: {
title: "", title: "",
label: localize("ui.panel.config.scene.picker.headers.state"), label: localize("ui.panel.config.scene.picker.headers.state"),
moveable: false,
showNarrow: true,
type: "icon", type: "icon",
template: (scene) => html` template: (scene) => html`
<ha-state-icon <ha-state-icon
@ -245,15 +261,13 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
filterable: true, filterable: true,
direction: "asc", direction: "asc",
grows: true, grows: true,
template: (scene) => html` extraTemplate: (scene) =>
<div style="font-size: 14px;">${scene.name}</div> scene.labels.length
${scene.labels.length
? html`<ha-data-table-labels ? html`<ha-data-table-labels
@label-clicked=${this._labelClicked} @label-clicked=${this._labelClicked}
.labels=${scene.labels} .labels=${scene.labels}
></ha-data-table-labels>` ></ha-data-table-labels>`
: nothing} : nothing,
`,
}, },
area: { area: {
title: localize("ui.panel.config.scene.picker.headers.area"), title: localize("ui.panel.config.scene.picker.headers.area"),
@ -281,7 +295,6 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
), ),
sortable: true, sortable: true,
width: "30%", width: "30%",
hidden: narrow,
template: (scene) => { template: (scene) => {
const lastActivated = scene.state; const lastActivated = scene.state;
if (!lastActivated || isUnavailableState(lastActivated)) { if (!lastActivated || isUnavailableState(lastActivated)) {
@ -300,6 +313,7 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
only_editable: { only_editable: {
title: "", title: "",
width: "56px", width: "56px",
showNarrow: true,
template: (scene) => template: (scene) =>
!scene.attributes.id !scene.attributes.id
? html` ? html`
@ -319,6 +333,9 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
title: "", title: "",
width: "64px", width: "64px",
type: "overflow-menu", type: "overflow-menu",
showNarrow: true,
moveable: false,
hideable: false,
template: (scene) => html` template: (scene) => html`
<ha-icon-overflow-menu <ha-icon-overflow-menu
.hass=${this.hass} .hass=${this.hass}
@ -536,11 +553,14 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
Array.isArray(val) ? val.length : val Array.isArray(val) ? val.length : val
) )
).length} ).length}
.columns=${this._columns(this.narrow, this.hass.localize)} .columns=${this._columns(this.hass.localize)}
id="entity_id" id="entity_id"
.initialGroupColumn=${this._activeGrouping || "category"} .initialGroupColumn=${this._activeGrouping || "category"}
.initialCollapsedGroups=${this._activeCollapsed} .initialCollapsedGroups=${this._activeCollapsed}
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
.columnOrder=${this._activeColumnOrder}
.hiddenColumns=${this._activeHiddenColumns}
@columns-changed=${this._handleColumnsChanged}
@sorting-changed=${this._handleSortingChanged} @sorting-changed=${this._handleSortingChanged}
@grouping-changed=${this._handleGroupingChanged} @grouping-changed=${this._handleGroupingChanged}
@collapsed-changed=${this._handleCollapseChanged} @collapsed-changed=${this._handleCollapseChanged}
@ -1155,6 +1175,11 @@ ${rejected
this._filter = ev.detail.value; this._filter = ev.detail.value;
} }
private _handleColumnsChanged(ev: CustomEvent) {
this._activeColumnOrder = ev.detail.columnOrder;
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,

View File

@ -184,6 +184,20 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
}) })
private _activeCollapsed?: string; private _activeCollapsed?: string;
@storage({
key: "script-table-column-order",
state: false,
subscribe: false,
})
private _activeColumnOrder?: string[];
@storage({
key: "script-table-hidden-columns",
state: false,
subscribe: false,
})
private _activeHiddenColumns?: string[];
private _sizeController = new ResizeController(this, { private _sizeController = new ResizeController(this, {
callback: (entries) => entries[0]?.contentRect.width, callback: (entries) => entries[0]?.contentRect.width,
}); });
@ -232,14 +246,12 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
); );
private _columns = memoizeOne( private _columns = memoizeOne(
( (localize: LocalizeFunc): DataTableColumnContainer<ScriptItem> => {
narrow,
localize: LocalizeFunc,
locale: HomeAssistant["locale"]
): DataTableColumnContainer<ScriptItem> => {
const columns: DataTableColumnContainer = { const columns: DataTableColumnContainer = {
icon: { icon: {
title: "", title: "",
showNarrow: true,
moveable: false,
label: localize("ui.panel.config.script.picker.headers.state"), label: localize("ui.panel.config.script.picker.headers.state"),
type: "icon", type: "icon",
template: (script) => template: (script) =>
@ -259,30 +271,13 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
filterable: true, filterable: true,
direction: "asc", direction: "asc",
grows: true, grows: true,
template: (script) => { extraTemplate: (script) =>
const date = new Date(script.last_triggered); script.labels.length
const now = new Date();
const dayDifference = differenceInDays(now, date);
return html`
<div style="font-size: 14px;">${script.name}</div>
${narrow
? html`<div class="secondary">
${this.hass.localize("ui.card.automation.last_triggered")}:
${script.attributes.last_triggered
? dayDifference > 3
? formatShortDateTime(date, locale, this.hass.config)
: relativeTime(date, locale)
: localize("ui.components.relative_time.never")}
</div>`
: nothing}
${script.labels.length
? html`<ha-data-table-labels ? html`<ha-data-table-labels
@label-clicked=${this._labelClicked} @label-clicked=${this._labelClicked}
.labels=${script.labels} .labels=${script.labels}
></ha-data-table-labels>` ></ha-data-table-labels>`
: nothing} : nothing,
`;
},
}, },
area: { area: {
title: localize("ui.panel.config.script.picker.headers.area"), title: localize("ui.panel.config.script.picker.headers.area"),
@ -305,7 +300,6 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
template: (script) => script.labels.map((lbl) => lbl.name).join(" "), template: (script) => script.labels.map((lbl) => lbl.name).join(" "),
}, },
last_triggered: { last_triggered: {
hidden: narrow,
sortable: true, sortable: true,
width: "40%", width: "40%",
title: localize("ui.card.automation.last_triggered"), title: localize("ui.card.automation.last_triggered"),
@ -330,6 +324,9 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
title: "", title: "",
width: "64px", width: "64px",
type: "overflow-menu", type: "overflow-menu",
showNarrow: true,
moveable: false,
hideable: false,
template: (script) => html` template: (script) => html`
<ha-icon-overflow-menu <ha-icon-overflow-menu
.hass=${this.hass} .hass=${this.hass}
@ -539,6 +536,9 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
.initialGroupColumn=${this._activeGrouping || "category"} .initialGroupColumn=${this._activeGrouping || "category"}
.initialCollapsedGroups=${this._activeCollapsed} .initialCollapsedGroups=${this._activeCollapsed}
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
.columnOrder=${this._activeColumnOrder}
.hiddenColumns=${this._activeHiddenColumns}
@columns-changed=${this._handleColumnsChanged}
@sorting-changed=${this._handleSortingChanged} @sorting-changed=${this._handleSortingChanged}
@grouping-changed=${this._handleGroupingChanged} @grouping-changed=${this._handleGroupingChanged}
@collapsed-changed=${this._handleCollapseChanged} @collapsed-changed=${this._handleCollapseChanged}
@ -553,11 +553,7 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
Array.isArray(val) ? val.length : val Array.isArray(val) ? val.length : val
) )
).length} ).length}
.columns=${this._columns( .columns=${this._columns(this.hass.localize)}
this.narrow,
this.hass.localize,
this.hass.locale
)}
.data=${scripts} .data=${scripts}
.empty=${!this.scripts.length} .empty=${!this.scripts.length}
.activeFilters=${this._activeFilters} .activeFilters=${this._activeFilters}
@ -1270,6 +1266,11 @@ ${rejected
this._activeCollapsed = ev.detail.value; this._activeCollapsed = ev.detail.value;
} }
private _handleColumnsChanged(ev: CustomEvent) {
this._activeColumnOrder = ev.detail.columnOrder;
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,

View File

@ -66,11 +66,12 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
}) })
private _filter = ""; private _filter = "";
private _columns = memoizeOne( private _columns = memoizeOne((localize: LocalizeFunc) => {
(narrow: boolean, _language, localize: LocalizeFunc) => {
const columns: DataTableColumnContainer<TagRowData> = { const columns: DataTableColumnContainer<TagRowData> = {
icon: { icon: {
title: "", title: "",
moveable: false,
showNarrow: true,
label: localize("ui.panel.config.tag.headers.icon"), label: localize("ui.panel.config.tag.headers.icon"),
type: "icon", type: "icon",
template: (tag) => html`<tag-image .tag=${tag}></tag-image>`, template: (tag) => html`<tag-image .tag=${tag}></tag-image>`,
@ -81,24 +82,10 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
sortable: true, sortable: true,
filterable: true, filterable: true,
grows: true, grows: true,
template: (tag) =>
html`${tag.display_name}
${narrow
? html`<div class="secondary">
${tag.last_scanned_datetime
? html`<ha-relative-time
.hass=${this.hass}
.datetime=${tag.last_scanned_datetime}
capitalize
></ha-relative-time>`
: this.hass.localize("ui.panel.config.tag.never_scanned")}
</div>`
: ""}`,
}, },
last_scanned_datetime: { last_scanned_datetime: {
title: localize("ui.panel.config.tag.headers.last_scanned"), title: localize("ui.panel.config.tag.headers.last_scanned"),
sortable: true, sortable: true,
hidden: narrow,
direction: "desc", direction: "desc",
width: "20%", width: "20%",
template: (tag) => html` template: (tag) => html`
@ -117,8 +104,9 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
title: "", title: "",
label: localize("ui.panel.config.tag.headers.write"), label: localize("ui.panel.config.tag.headers.write"),
type: "icon-button", type: "icon-button",
showNarrow: true,
template: (tag) => template: (tag) =>
html` <ha-icon-button html`<ha-icon-button
.tag=${tag} .tag=${tag}
@click=${this._handleWriteClick} @click=${this._handleWriteClick}
.label=${this.hass.localize("ui.panel.config.tag.write")} .label=${this.hass.localize("ui.panel.config.tag.write")}
@ -129,21 +117,23 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
columns.automation = { columns.automation = {
title: "", title: "",
type: "icon-button", type: "icon-button",
showNarrow: true,
template: (tag) => template: (tag) =>
html` <ha-icon-button html`<ha-icon-button
.tag=${tag} .tag=${tag}
@click=${this._handleAutomationClick} @click=${this._handleAutomationClick}
.label=${this.hass.localize( .label=${this.hass.localize("ui.panel.config.tag.create_automation")}
"ui.panel.config.tag.create_automation"
)}
.path=${mdiRobot} .path=${mdiRobot}
></ha-icon-button>`, ></ha-icon-button>`,
}; };
columns.edit = { columns.edit = {
title: "", title: "",
type: "icon-button", type: "icon-button",
showNarrow: true,
hideable: false,
moveable: false,
template: (tag) => template: (tag) =>
html` <ha-icon-button html`<ha-icon-button
.tag=${tag} .tag=${tag}
@click=${this._handleEditClick} @click=${this._handleEditClick}
.label=${this.hass.localize("ui.panel.config.tag.edit")} .label=${this.hass.localize("ui.panel.config.tag.edit")}
@ -151,8 +141,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
></ha-icon-button>`, ></ha-icon-button>`,
}; };
return columns; return columns;
} });
);
private _data = memoizeOne((tags: Tag[]): TagRowData[] => private _data = memoizeOne((tags: Tag[]): TagRowData[] =>
tags.map((tag) => ({ tags.map((tag) => ({
@ -191,11 +180,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
back-path="/config" back-path="/config"
.route=${this.route} .route=${this.route}
.tabs=${configSections.tags} .tabs=${configSections.tags}
.columns=${this._columns( .columns=${this._columns(this.hass.localize)}
this.narrow,
this.hass.language,
this.hass.localize
)}
.data=${this._data(this._tags)} .data=${this._data(this._tags)}
.noDataText=${this.hass.localize("ui.panel.config.tag.no_tags")} .noDataText=${this.hass.localize("ui.panel.config.tag.no_tags")}
.filter=${this._filter} .filter=${this._filter}

View File

@ -46,6 +46,20 @@ export class HaConfigUsers extends LitElement {
@storage({ key: "users-table-grouping", state: false, subscribe: false }) @storage({ key: "users-table-grouping", state: false, subscribe: false })
private _activeGrouping?: string; private _activeGrouping?: string;
@storage({
key: "users-table-column-order",
state: false,
subscribe: false,
})
private _activeColumnOrder?: string[];
@storage({
key: "users-table-hidden-columns",
state: false,
subscribe: false,
})
private _activeHiddenColumns?: string[];
@storage({ @storage({
storage: "sessionStorage", storage: "sessionStorage",
key: "users-table-search", key: "users-table-search",
@ -72,17 +86,6 @@ export class HaConfigUsers extends LitElement {
width: "25%", width: "25%",
direction: "asc", direction: "asc",
grows: true, grows: true,
template: (user) =>
narrow
? html` ${user.name}<br />
<div class="secondary">
${user.username ? `${user.username} |` : ""}
${localize(`groups.${user.group_ids[0]}`)}
</div>`
: html` ${user.name ||
this.hass!.localize(
"ui.panel.config.users.editor.unnamed_user"
)}`,
}, },
username: { username: {
title: localize("ui.panel.config.users.picker.headers.username"), title: localize("ui.panel.config.users.picker.headers.username"),
@ -90,7 +93,6 @@ export class HaConfigUsers extends LitElement {
filterable: true, filterable: true,
width: "20%", width: "20%",
direction: "asc", direction: "asc",
hidden: narrow,
template: (user) => html`${user.username || "—"}`, template: (user) => html`${user.username || "—"}`,
}, },
group: { group: {
@ -100,7 +102,6 @@ export class HaConfigUsers extends LitElement {
groupable: true, groupable: true,
width: "20%", width: "20%",
direction: "asc", direction: "asc",
hidden: narrow,
}, },
is_active: { is_active: {
title: this.hass.localize( title: this.hass.localize(
@ -154,6 +155,7 @@ export class HaConfigUsers extends LitElement {
filterable: false, filterable: false,
width: "104px", width: "104px",
hidden: !narrow, hidden: !narrow,
showNarrow: true,
template: (user) => { template: (user) => {
const badges = computeUserBadges(this.hass, user, false); const badges = computeUserBadges(this.hass, user, false);
return html`${badges.map( return html`${badges.map(
@ -186,6 +188,9 @@ export class HaConfigUsers extends LitElement {
.tabs=${configSections.persons} .tabs=${configSections.persons}
.columns=${this._columns(this.narrow, this.hass.localize)} .columns=${this._columns(this.narrow, this.hass.localize)}
.data=${this._userData(this._users, this.hass.localize)} .data=${this._userData(this._users, this.hass.localize)}
.columnOrder=${this._activeColumnOrder}
.hiddenColumns=${this._activeHiddenColumns}
@columns-changed=${this._handleColumnsChanged}
.initialGroupColumn=${this._activeGrouping} .initialGroupColumn=${this._activeGrouping}
.initialCollapsedGroups=${this._activeCollapsed} .initialCollapsedGroups=${this._activeCollapsed}
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
@ -213,6 +218,7 @@ export class HaConfigUsers extends LitElement {
private _userData = memoizeOne((users: User[], localize: LocalizeFunc) => private _userData = memoizeOne((users: User[], localize: LocalizeFunc) =>
users.map((user) => ({ users.map((user) => ({
...user, ...user,
name: user.name || localize("ui.panel.config.users.editor.unnamed_user"),
group: localize(`groups.${user.group_ids[0]}`), group: localize(`groups.${user.group_ids[0]}`),
})) }))
); );
@ -302,6 +308,11 @@ export class HaConfigUsers extends LitElement {
private _handleSearchChange(ev: CustomEvent) { private _handleSearchChange(ev: CustomEvent) {
this._filter = ev.detail.value; this._filter = ev.detail.value;
} }
private _handleColumnsChanged(ev: CustomEvent) {
this._activeColumnOrder = ev.detail.columnOrder;
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
} }
declare global { declare global {

View File

@ -118,6 +118,20 @@ export class VoiceAssistantsExpose extends LitElement {
}) })
private _activeCollapsed?: string; private _activeCollapsed?: string;
@storage({
key: "voice-expose-table-column-order",
state: false,
subscribe: false,
})
private _activeColumnOrder?: string[];
@storage({
key: "voice-expose-table-hidden-columns",
state: false,
subscribe: false,
})
private _activeHiddenColumns?: string[];
@query("hass-tabs-subpage-data-table", true) @query("hass-tabs-subpage-data-table", true)
private _dataTable!: HaTabsSubpageDataTable; private _dataTable!: HaTabsSubpageDataTable;
@ -137,6 +151,7 @@ export class VoiceAssistantsExpose extends LitElement {
icon: { icon: {
title: "", title: "",
type: "icon", type: "icon",
moveable: false,
hidden: narrow, hidden: narrow,
template: (entry) => html` template: (entry) => html`
<ha-state-icon <ha-state-icon
@ -153,11 +168,21 @@ export class VoiceAssistantsExpose extends LitElement {
filterable: true, filterable: true,
direction: "asc", direction: "asc",
grows: true, grows: true,
template: (entry) => html` template: narrow
? undefined
: (entry) => html`
${entry.name}<br /> ${entry.name}<br />
<div class="secondary">${entry.entity_id}</div> <div class="secondary">${entry.entity_id}</div>
`, `,
}, },
// For search & narrow
entity_id: {
title: localize(
"ui.panel.config.voice_assistants.expose.headers.entity_id"
),
hidden: !narrow,
filterable: true,
},
domain: { domain: {
title: localize( title: localize(
"ui.panel.config.voice_assistants.expose.headers.domain" "ui.panel.config.voice_assistants.expose.headers.domain"
@ -171,7 +196,6 @@ export class VoiceAssistantsExpose extends LitElement {
title: localize("ui.panel.config.voice_assistants.expose.headers.area"), title: localize("ui.panel.config.voice_assistants.expose.headers.area"),
sortable: true, sortable: true,
groupable: true, groupable: true,
hidden: narrow,
filterable: true, filterable: true,
width: "15%", width: "15%",
}, },
@ -179,6 +203,7 @@ export class VoiceAssistantsExpose extends LitElement {
title: localize( title: localize(
"ui.panel.config.voice_assistants.expose.headers.assistants" "ui.panel.config.voice_assistants.expose.headers.assistants"
), ),
showNarrow: true,
sortable: true, sortable: true,
filterable: true, filterable: true,
width: "160px", width: "160px",
@ -208,7 +233,6 @@ export class VoiceAssistantsExpose extends LitElement {
), ),
sortable: true, sortable: true,
filterable: true, filterable: true,
hidden: narrow,
width: "15%", width: "15%",
template: (entry) => template: (entry) =>
entry.aliases.length === 0 entry.aliases.length === 0
@ -230,12 +254,6 @@ export class VoiceAssistantsExpose extends LitElement {
.path=${mdiCloseCircleOutline} .path=${mdiCloseCircleOutline}
></ha-icon-button>`, ></ha-icon-button>`,
}, },
// For search
entity_id: {
title: "",
hidden: true,
filterable: true,
},
}) })
); );
@ -552,6 +570,9 @@ export class VoiceAssistantsExpose extends LitElement {
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
.initialGroupColumn=${this._activeGrouping} .initialGroupColumn=${this._activeGrouping}
.initialCollapsedGroups=${this._activeCollapsed} .initialCollapsedGroups=${this._activeCollapsed}
.columnOrder=${this._activeColumnOrder}
.hiddenColumns=${this._activeHiddenColumns}
@columns-changed=${this._handleColumnsChanged}
@sorting-changed=${this._handleSortingChanged} @sorting-changed=${this._handleSortingChanged}
@selection-changed=${this._handleSelectionChanged} @selection-changed=${this._handleSelectionChanged}
@grouping-changed=${this._handleGroupingChanged} @grouping-changed=${this._handleGroupingChanged}
@ -757,6 +778,11 @@ export class VoiceAssistantsExpose extends LitElement {
this._activeCollapsed = ev.detail.value; this._activeCollapsed = ev.detail.value;
} }
private _handleColumnsChanged(ev: CustomEvent) {
this._activeColumnOrder = ev.detail.columnOrder;
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,

View File

@ -530,7 +530,8 @@
"selected": "Selected {selected}", "selected": "Selected {selected}",
"close_select_mode": "Close selection mode", "close_select_mode": "Close selection mode",
"select_all": "Select all", "select_all": "Select all",
"select_none": "Select none" "select_none": "Select none",
"settings": "Customize table"
}, },
"config-entry-picker": { "config-entry-picker": {
"config_entry": "Integration" "config_entry": "Integration"
@ -799,7 +800,14 @@
"filtering_by": "Filtering by", "filtering_by": "Filtering by",
"hidden": "{number} hidden", "hidden": "{number} hidden",
"clear": "Clear", "clear": "Clear",
"ungrouped": "Ungrouped" "ungrouped": "Ungrouped",
"settings": {
"header": "Customize",
"hide": "Hide column {title}",
"show": "Show column {title}",
"done": "Done",
"restore": "Restore defaults"
}
}, },
"media-browser": { "media-browser": {
"tts": { "tts": {
@ -2071,6 +2079,7 @@
"download_backup": "[%key:supervisor::backup::download_backup%]", "download_backup": "[%key:supervisor::backup::download_backup%]",
"remove_backup": "[%key:supervisor::backup::delete_backup_title%]", "remove_backup": "[%key:supervisor::backup::delete_backup_title%]",
"name": "[%key:supervisor::backup::name%]", "name": "[%key:supervisor::backup::name%]",
"path": "Path",
"size": "[%key:supervisor::backup::size%]", "size": "[%key:supervisor::backup::size%]",
"created": "[%key:supervisor::backup::created%]", "created": "[%key:supervisor::backup::created%]",
"no_backups": "[%key:supervisor::backup::no_backups%]", "no_backups": "[%key:supervisor::backup::no_backups%]",
@ -2665,6 +2674,7 @@
"caption": "Expose", "caption": "Expose",
"headers": { "headers": {
"name": "Name", "name": "Name",
"entity_id": "Entity ID",
"area": "Area", "area": "Area",
"domain": "Domain", "domain": "Domain",
"assistants": "Assistants", "assistants": "Assistants",
@ -4040,6 +4050,7 @@
"update_device_error": "Updating the device failed", "update_device_error": "Updating the device failed",
"disabled": "Disabled", "disabled": "Disabled",
"data_table": { "data_table": {
"icon": "Icon",
"device": "Device", "device": "Device",
"manufacturer": "Manufacturer", "manufacturer": "Manufacturer",
"model": "Model", "model": "Model",