Simplify data table template (#17825)

* Simplify data table template

* Fix backup and gallery
This commit is contained in:
Paul Bottein 2023-09-20 12:09:44 +02:00 committed by GitHub
parent 5e107d43d7
commit 3349031cbd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 607 additions and 560 deletions

View File

@ -343,7 +343,7 @@ export class DemoEntityState extends LitElement {
const columns: DataTableColumnContainer<EntityRowData> = { const columns: DataTableColumnContainer<EntityRowData> = {
icon: { icon: {
title: "Icon", title: "Icon",
template: (_, entry) => html` template: (entry) => html`
<state-badge <state-badge
.stateObj=${entry.stateObj} .stateObj=${entry.stateObj}
.stateColor=${true} .stateColor=${true}
@ -360,7 +360,7 @@ export class DemoEntityState extends LitElement {
title: "State", title: "State",
width: "20%", width: "20%",
sortable: true, sortable: true,
template: (_, entry) => template: (entry) =>
html`${computeStateDisplay( html`${computeStateDisplay(
hass.localize, hass.localize,
entry.stateObj, entry.stateObj,
@ -371,14 +371,14 @@ export class DemoEntityState extends LitElement {
}, },
device_class: { device_class: {
title: "Device class", title: "Device class",
template: (dc) => html`${dc ?? "-"}`, template: (entry) => html`${entry.device_class ?? "-"}`,
width: "20%", width: "20%",
filterable: true, filterable: true,
sortable: true, sortable: true,
}, },
domain: { domain: {
title: "Domain", title: "Domain",
template: (_, entry) => html`${computeDomain(entry.entity_id)}`, template: (entry) => html`${computeDomain(entry.entity_id)}`,
width: "20%", width: "20%",
filterable: true, filterable: true,
sortable: true, sortable: true,

View File

@ -49,6 +49,10 @@ import { showHassioCreateBackupDialog } from "../dialogs/backup/show-dialog-hass
import { supervisorTabs } from "../hassio-tabs"; import { supervisorTabs } from "../hassio-tabs";
import { hassioStyle } from "../resources/hassio-style"; import { hassioStyle } from "../resources/hassio-style";
type BackupItem = HassioBackup & {
secondary: string;
};
@customElement("hassio-backups") @customElement("hassio-backups")
export class HassioBackups extends LitElement { export class HassioBackups extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@ -117,15 +121,15 @@ export class HassioBackups extends LitElement {
} }
private _columns = memoizeOne( private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer => ({ (narrow: boolean): DataTableColumnContainer<BackupItem> => ({
name: { name: {
title: this.supervisor.localize("backup.name"), title: this.supervisor.localize("backup.name"),
main: true, main: true,
sortable: true, sortable: true,
filterable: true, filterable: true,
grows: true, grows: true,
template: (entry: string, backup: any) => template: (backup) =>
html`${entry || backup.slug} html`${backup.name || backup.slug}
<div class="secondary">${backup.secondary}</div>`, <div class="secondary">${backup.secondary}</div>`,
}, },
size: { size: {
@ -134,7 +138,7 @@ export class HassioBackups extends LitElement {
hidden: narrow, hidden: narrow,
filterable: true, filterable: true,
sortable: true, sortable: true,
template: (entry: number) => Math.ceil(entry * 10) / 10 + " MB", template: (backup) => Math.ceil(backup.size * 10) / 10 + " MB",
}, },
location: { location: {
title: this.supervisor.localize("backup.location"), title: this.supervisor.localize("backup.location"),
@ -142,8 +146,8 @@ export class HassioBackups extends LitElement {
hidden: narrow, hidden: narrow,
filterable: true, filterable: true,
sortable: true, sortable: true,
template: (entry: string | null) => template: (backup) =>
entry || this.supervisor.localize("backup.data_disk"), backup.location || this.supervisor.localize("backup.data_disk"),
}, },
date: { date: {
title: this.supervisor.localize("backup.created"), title: this.supervisor.localize("backup.created"),
@ -152,8 +156,8 @@ export class HassioBackups extends LitElement {
hidden: narrow, hidden: narrow,
filterable: true, filterable: true,
sortable: true, sortable: true,
template: (entry: string) => template: (backup) =>
relativeTime(new Date(entry), this.hass.locale), relativeTime(new Date(backup.date), this.hass.locale),
}, },
secondary: { secondary: {
title: "", title: "",
@ -163,7 +167,7 @@ export class HassioBackups extends LitElement {
}) })
); );
private _backupData = memoizeOne((backups: HassioBackup[]) => private _backupData = memoizeOne((backups: HassioBackup[]): BackupItem[] =>
backups.map((backup) => ({ backups.map((backup) => ({
...backup, ...backup,
secondary: this._computeBackupContent(backup), secondary: this._computeBackupContent(backup),

View File

@ -74,7 +74,7 @@ export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
title: TemplateResult | string; title: TemplateResult | string;
label?: TemplateResult | string; label?: TemplateResult | string;
type?: "numeric" | "icon" | "icon-button" | "overflow-menu" | "flex"; type?: "numeric" | "icon" | "icon-button" | "overflow-menu" | "flex";
template?: (data: any, row: T) => TemplateResult | string | typeof nothing; template?: (row: T) => TemplateResult | string | typeof nothing;
width?: string; width?: string;
maxWidth?: string; maxWidth?: string;
grows?: boolean; grows?: boolean;
@ -431,7 +431,7 @@ export class HaDataTable extends LitElement {
}) })
: ""} : ""}
> >
${column.template ? column.template(row[key], row) : row[key]} ${column.template ? column.template(row) : row[key]}
</div> </div>
`; `;
})} })}

View File

@ -62,17 +62,16 @@ export class HaConfigApplicationCredentials extends LitElement {
), ),
direction: "asc", direction: "asc",
grows: true, grows: true,
template: (_, entry: ApplicationCredential) => html`${entry.name}`, template: (entry) => html`${entry.name}`,
}, },
clientId: { client_id: {
title: localize( title: localize(
"ui.panel.config.application_credentials.picker.headers.client_id" "ui.panel.config.application_credentials.picker.headers.client_id"
), ),
width: "30%", width: "30%",
direction: "asc", direction: "asc",
hidden: narrow, hidden: narrow,
template: (_, entry: ApplicationCredential) => template: (entry) => html`${entry.client_id}`,
html`${entry.client_id}`,
}, },
application: { application: {
title: localize( title: localize(
@ -81,7 +80,7 @@ export class HaConfigApplicationCredentials extends LitElement {
sortable: true, sortable: true,
width: "30%", width: "30%",
direction: "asc", direction: "asc",
template: (_, entry) => html`${domainToName(localize, entry.domain)}`, template: (entry) => html`${domainToName(localize, entry.domain)}`,
}, },
}; };

View File

@ -55,6 +55,12 @@ import { findRelated } from "../../../data/search";
import { fetchBlueprints } from "../../../data/blueprint"; import { fetchBlueprints } from "../../../data/blueprint";
import { UNAVAILABLE } from "../../../data/entity"; import { UNAVAILABLE } from "../../../data/entity";
type AutomationItem = AutomationEntity & {
name: string;
last_triggered?: string | undefined;
disabled: boolean;
};
@customElement("ha-automation-picker") @customElement("ha-automation-picker")
class HaAutomationPicker extends LitElement { class HaAutomationPicker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@ -79,7 +85,7 @@ class HaAutomationPicker extends LitElement {
( (
automations: AutomationEntity[], automations: AutomationEntity[],
filteredAutomations?: string[] | null filteredAutomations?: string[] | null
) => { ): AutomationItem[] => {
if (filteredAutomations === null) { if (filteredAutomations === null) {
return []; return [];
} }
@ -100,14 +106,14 @@ class HaAutomationPicker extends LitElement {
private _columns = memoizeOne( private _columns = memoizeOne(
(narrow: boolean, _locale): DataTableColumnContainer => { (narrow: boolean, _locale): DataTableColumnContainer => {
const columns: DataTableColumnContainer = { const columns: DataTableColumnContainer<AutomationItem> = {
icon: { icon: {
title: "", title: "",
label: this.hass.localize( label: this.hass.localize(
"ui.panel.config.automation.picker.headers.state" "ui.panel.config.automation.picker.headers.state"
), ),
type: "icon", type: "icon",
template: (_, automation) => template: (automation) =>
html`<ha-state-icon html`<ha-state-icon
.state=${automation} .state=${automation}
style=${styleMap({ style=${styleMap({
@ -128,12 +134,12 @@ class HaAutomationPicker extends LitElement {
direction: "asc", direction: "asc",
grows: true, grows: true,
template: narrow template: narrow
? (name, automation: any) => { ? (automation) => {
const date = new Date(automation.attributes.last_triggered); const date = new Date(automation.attributes.last_triggered);
const now = new Date(); const now = new Date();
const dayDifference = differenceInDays(now, date); const dayDifference = differenceInDays(now, date);
return html` return html`
${name} ${automation.name}
<div class="secondary"> <div class="secondary">
${this.hass.localize("ui.card.automation.last_triggered")}: ${this.hass.localize("ui.card.automation.last_triggered")}:
${automation.attributes.last_triggered ${automation.attributes.last_triggered
@ -156,20 +162,17 @@ class HaAutomationPicker extends LitElement {
sortable: true, sortable: true,
width: "20%", width: "20%",
title: this.hass.localize("ui.card.automation.last_triggered"), title: this.hass.localize("ui.card.automation.last_triggered"),
template: (last_triggered) => { template: (automation) => {
const date = new Date(last_triggered); if (!automation.last_triggered) {
return this.hass.localize("ui.components.relative_time.never");
}
const date = new Date(automation.last_triggered);
const now = new Date(); const now = new Date();
const dayDifference = differenceInDays(now, date); const dayDifference = differenceInDays(now, date);
return html` return html`
${last_triggered ${dayDifference > 3
? dayDifference > 3 ? formatShortDateTime(date, this.hass.locale, this.hass.config)
? formatShortDateTime( : relativeTime(date, this.hass.locale)}
date,
this.hass.locale,
this.hass.config
)
: relativeTime(date, this.hass.locale)
: this.hass.localize("ui.components.relative_time.never")}
`; `;
}, },
}; };
@ -178,8 +181,8 @@ class HaAutomationPicker extends LitElement {
columns.disabled = this.narrow columns.disabled = this.narrow
? { ? {
title: "", title: "",
template: (disabled: boolean) => template: (automation) =>
disabled automation.disabled
? html` ? html`
<simple-tooltip animation-delay="0" position="left"> <simple-tooltip animation-delay="0" position="left">
${this.hass.localize( ${this.hass.localize(
@ -196,8 +199,8 @@ class HaAutomationPicker extends LitElement {
: { : {
width: "20%", width: "20%",
title: "", title: "",
template: (disabled: boolean) => template: (automation) =>
disabled automation.disabled
? html` ? html`
<ha-chip> <ha-chip>
${this.hass.localize( ${this.hass.localize(
@ -212,7 +215,7 @@ class HaAutomationPicker extends LitElement {
title: "", title: "",
width: this.narrow ? undefined : "10%", width: this.narrow ? undefined : "10%",
type: "overflow-menu", type: "overflow-menu",
template: (_: string, automation: any) => html` template: (automation) => html`
<ha-icon-overflow-menu <ha-icon-overflow-menu
.hass=${this.hass} .hass=${this.hass}
narrow narrow

View File

@ -1,12 +1,12 @@
import { mdiDelete, mdiDownload, mdiPlus } from "@mdi/js";
import "@lrnwebcomponents/simple-tooltip/simple-tooltip"; import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import { mdiDelete, mdiDownload, mdiPlus } from "@mdi/js";
import { import {
css,
CSSResultGroup, CSSResultGroup,
html,
LitElement, LitElement,
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
css,
html,
} from "lit"; } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoize from "memoize-one"; import memoize from "memoize-one";
@ -48,15 +48,15 @@ class HaConfigBackup extends LitElement {
@state() private _backupData?: BackupData; @state() private _backupData?: BackupData;
private _columns = memoize( private _columns = memoize(
(narrow, _language): DataTableColumnContainer => ({ (narrow, _language): DataTableColumnContainer<BackupContent> => ({
name: { name: {
title: this.hass.localize("ui.panel.config.backup.name"), title: this.hass.localize("ui.panel.config.backup.name"),
main: true, main: true,
sortable: true, sortable: true,
filterable: true, filterable: true,
grows: true, grows: true,
template: (entry: string, backup: BackupContent) => template: (backup) =>
html`${entry} html`${backup.name}
<div class="secondary">${backup.path}</div>`, <div class="secondary">${backup.path}</div>`,
}, },
size: { size: {
@ -65,7 +65,7 @@ class HaConfigBackup extends LitElement {
hidden: narrow, hidden: narrow,
filterable: true, filterable: true,
sortable: true, sortable: true,
template: (entry: number) => Math.ceil(entry * 10) / 10 + " MB", template: (backup) => Math.ceil(backup.size * 10) / 10 + " MB",
}, },
date: { date: {
title: this.hass.localize("ui.panel.config.backup.created"), title: this.hass.localize("ui.panel.config.backup.created"),
@ -74,15 +74,15 @@ class HaConfigBackup extends LitElement {
hidden: narrow, hidden: narrow,
filterable: true, filterable: true,
sortable: true, sortable: true,
template: (entry: string) => template: (backup) =>
relativeTime(new Date(entry), this.hass.locale), relativeTime(new Date(backup.date), this.hass.locale),
}, },
actions: { actions: {
title: "", title: "",
width: "15%", width: "15%",
type: "overflow-menu", type: "overflow-menu",
template: (_: string, backup: BackupContent) => template: (backup) =>
html`<ha-icon-overflow-menu html`<ha-icon-overflow-menu
.hass=${this.hass} .hass=${this.hass}
.narrow=${this.narrow} .narrow=${this.narrow}

View File

@ -10,14 +10,14 @@ import {
} from "@mdi/js"; } from "@mdi/js";
import { import {
CSSResultGroup, CSSResultGroup,
html,
LitElement, LitElement,
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
html,
} from "lit"; } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event"; import { HASSDomEvent, fireEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name"; import { computeStateName } from "../../../common/entity/compute_state_name";
import { navigate } from "../../../common/navigate"; import { navigate } from "../../../common/navigate";
import { extractSearchParam } from "../../../common/url/search-params"; import { extractSearchParam } from "../../../common/url/search-params";
@ -32,7 +32,6 @@ import "../../../components/ha-icon-overflow-menu";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import { showAutomationEditor } from "../../../data/automation"; import { showAutomationEditor } from "../../../data/automation";
import { import {
BlueprintDomain,
BlueprintMetaData, BlueprintMetaData,
Blueprints, Blueprints,
deleteBlueprint, deleteBlueprint,
@ -50,10 +49,12 @@ import { documentationUrl } from "../../../util/documentation-url";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import { showAddBlueprintDialog } from "./show-dialog-import-blueprint"; import { showAddBlueprintDialog } from "./show-dialog-import-blueprint";
interface BlueprintMetaDataPath extends BlueprintMetaData { type BlueprintMetaDataPath = BlueprintMetaData & {
path: string; path: string;
error: boolean; error: boolean;
} type: "automation" | "script";
fullpath: string;
};
const createNewFunctions = { const createNewFunctions = {
automation: (blueprintMeta: BlueprintMetaDataPath) => { automation: (blueprintMeta: BlueprintMetaDataPath) => {
@ -86,7 +87,7 @@ class HaBlueprintOverview extends LitElement {
>; >;
private _processedBlueprints = memoizeOne( private _processedBlueprints = memoizeOne(
(blueprints: Record<string, Blueprints>) => { (blueprints: Record<string, Blueprints>): BlueprintMetaDataPath[] => {
const result: any[] = []; const result: any[] = [];
Object.entries(blueprints).forEach(([type, typeBlueprints]) => Object.entries(blueprints).forEach(([type, typeBlueprints]) =>
Object.entries(typeBlueprints).forEach(([path, blueprint]) => { Object.entries(typeBlueprints).forEach(([path, blueprint]) => {
@ -125,9 +126,9 @@ class HaBlueprintOverview extends LitElement {
direction: "asc", direction: "asc",
grows: true, grows: true,
template: narrow template: narrow
? (name, entity: any) => html` ? (blueprint) => html`
${name}<br /> ${blueprint.name}<br />
<div class="secondary">${entity.path}</div> <div class="secondary">${blueprint.path}</div>
` `
: undefined, : undefined,
}, },
@ -135,9 +136,9 @@ class HaBlueprintOverview extends LitElement {
title: this.hass.localize( title: this.hass.localize(
"ui.panel.config.blueprint.overview.headers.type" "ui.panel.config.blueprint.overview.headers.type"
), ),
template: (type: BlueprintDomain) => template: (blueprint) =>
html`${this.hass.localize( html`${this.hass.localize(
`ui.panel.config.blueprint.overview.types.${type}` `ui.panel.config.blueprint.overview.types.${blueprint.type}`
)}`, )}`,
sortable: true, sortable: true,
filterable: true, filterable: true,
@ -163,7 +164,7 @@ class HaBlueprintOverview extends LitElement {
title: "", title: "",
width: this.narrow ? undefined : "10%", width: this.narrow ? undefined : "10%",
type: "overflow-menu", type: "overflow-menu",
template: (_: string, blueprint) => template: (blueprint) =>
blueprint.error blueprint.error
? html`<ha-svg-icon ? html`<ha-svg-icon
style="color: var(--error-color); display: block; margin-inline-end: 12px; margin-inline-start: auto;" style="color: var(--error-color); display: block; margin-inline-end: 12px; margin-inline-start: auto;"
@ -177,7 +178,7 @@ class HaBlueprintOverview extends LitElement {
{ {
path: mdiPlus, path: mdiPlus,
label: this.hass.localize( label: this.hass.localize(
`ui.panel.config.blueprint.overview.create_${blueprint.domain}` `ui.panel.config.blueprint.overview.create_${blueprint.type}`
), ),
action: () => this._createNew(blueprint), action: () => this._createNew(blueprint),
}, },
@ -324,7 +325,7 @@ class HaBlueprintOverview extends LitElement {
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) { private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
const blueprint = this._processedBlueprints(this.blueprints).find( const blueprint = this._processedBlueprints(this.blueprints).find(
(b) => b.fullpath === ev.detail.id (b) => b.fullpath === ev.detail.id
); )!;
if (blueprint.error) { if (blueprint.error) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.hass.localize("ui.panel.config.blueprint.overview.error", { title: this.hass.localize("ui.panel.config.blueprint.overview.error", {

View File

@ -2,27 +2,26 @@ import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item"; import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
import { mdiCancel, mdiFilterVariant, mdiPlus } from "@mdi/js"; import { mdiCancel, mdiFilterVariant, mdiPlus } from "@mdi/js";
import { import {
css,
CSSResultGroup, CSSResultGroup,
html,
LitElement, LitElement,
nothing,
TemplateResult, TemplateResult,
css,
html,
nothing,
} from "lit"; } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { HASSDomEvent } from "../../../common/dom/fire_event"; import { HASSDomEvent } from "../../../common/dom/fire_event";
import { computeStateDomain } from "../../../common/entity/compute_state_domain"; import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { import {
protocolIntegrationPicked,
PROTOCOL_INTEGRATIONS, PROTOCOL_INTEGRATIONS,
protocolIntegrationPicked,
} from "../../../common/integrations/protocolIntegrationPicked"; } from "../../../common/integrations/protocolIntegrationPicked";
import { navigate } from "../../../common/navigate"; import { navigate } from "../../../common/navigate";
import { LocalizeFunc } from "../../../common/translations/localize"; import { LocalizeFunc } from "../../../common/translations/localize";
import { computeRTL } from "../../../common/util/compute_rtl"; import { computeRTL } from "../../../common/util/compute_rtl";
import { import {
DataTableColumnContainer, DataTableColumnContainer,
DataTableRowData,
RowClickedEvent, RowClickedEvent,
} from "../../../components/data-table/ha-data-table"; } from "../../../components/data-table/ha-data-table";
import "../../../components/entity/ha-battery-icon"; import "../../../components/entity/ha-battery-icon";
@ -33,9 +32,9 @@ import "../../../components/ha-icon-button";
import { AreaRegistryEntry } from "../../../data/area_registry"; import { AreaRegistryEntry } from "../../../data/area_registry";
import { ConfigEntry, sortConfigEntries } from "../../../data/config_entries"; import { ConfigEntry, sortConfigEntries } from "../../../data/config_entries";
import { import {
computeDeviceName,
DeviceEntityLookup, DeviceEntityLookup,
DeviceRegistryEntry, DeviceRegistryEntry,
computeDeviceName,
} from "../../../data/device_registry"; } from "../../../data/device_registry";
import { import {
EntityRegistryEntry, EntityRegistryEntry,
@ -231,7 +230,7 @@ export class HaConfigDeviceDashboard extends LitElement {
outputDevices = outputDevices.filter((device) => !device.disabled_by); outputDevices = outputDevices.filter((device) => !device.disabled_by);
} }
outputDevices = outputDevices.map((device) => { const formattedOutputDevices = outputDevices.map((device) => {
const deviceEntries = sortConfigEntries( const deviceEntries = sortConfigEntries(
device.config_entries device.config_entries
.filter((entId) => entId in entryLookup) .filter((entId) => entId in entryLookup)
@ -277,156 +276,153 @@ export class HaConfigDeviceDashboard extends LitElement {
}; };
}); });
this._numHiddenDevices = startLength - outputDevices.length; this._numHiddenDevices = startLength - formattedOutputDevices.length;
return { return {
devicesOutput: outputDevices, devicesOutput: formattedOutputDevices,
filteredConfigEntry: filterConfigEntry, filteredConfigEntry: filterConfigEntry,
filteredDomains, filteredDomains,
}; };
} }
); );
private _columns = memoizeOne( private _columns = memoizeOne((narrow: boolean, showDisabled: boolean) => {
(narrow: boolean, showDisabled: boolean): DataTableColumnContainer => { type DeviceItem = ReturnType<
const columns: DataTableColumnContainer = { typeof this._devicesAndFilterDomains
icon: { >["devicesOutput"][number];
title: "",
type: "icon",
template: (_icon, device) =>
device.domains.length
? html`<img
alt=""
referrerpolicy="no-referrer"
src=${brandsUrl({
domain: device.domains[0],
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
})}
/>`
: "",
},
};
if (narrow) { const columns: DataTableColumnContainer<DeviceItem> = {
columns.name = { icon: {
title: this.hass.localize( title: "",
"ui.panel.config.devices.data_table.device" type: "icon",
), template: (device) =>
main: true, device.domains.length
sortable: true, ? html`<img
filterable: true, alt=""
direction: "asc", referrerpolicy="no-referrer"
grows: true, src=${brandsUrl({
template: (name, device: DataTableRowData) => html` domain: device.domains[0],
${name} type: "icon",
<div class="secondary">${device.area} | ${device.integration}</div> darkOptimized: this.hass.themes?.darkMode,
`, })}
}; />`
} else { : "",
columns.name = { },
title: this.hass.localize( };
"ui.panel.config.devices.data_table.device"
),
main: true,
sortable: true,
filterable: true,
grows: true,
direction: "asc",
};
}
columns.manufacturer = { if (narrow) {
title: this.hass.localize( columns.name = {
"ui.panel.config.devices.data_table.manufacturer" title: this.hass.localize("ui.panel.config.devices.data_table.device"),
), main: true,
sortable: true,
hidden: narrow,
filterable: true,
width: "15%",
};
columns.model = {
title: this.hass.localize("ui.panel.config.devices.data_table.model"),
sortable: true,
hidden: narrow,
filterable: true,
width: "15%",
};
columns.area = {
title: this.hass.localize("ui.panel.config.devices.data_table.area"),
sortable: true,
hidden: narrow,
filterable: true,
width: "15%",
};
columns.integration = {
title: this.hass.localize(
"ui.panel.config.devices.data_table.integration"
),
sortable: true,
hidden: narrow,
filterable: true,
width: "15%",
};
columns.battery_entity = {
title: this.hass.localize("ui.panel.config.devices.data_table.battery"),
sortable: true, sortable: true,
filterable: true, filterable: true,
type: "numeric", direction: "asc",
width: narrow ? "95px" : "15%", grows: true,
maxWidth: "95px", template: (device) => html`
valueColumn: "battery_level", ${device.name}
template: (batteryEntityPair: DeviceRowData["battery_entity"]) => { <div class="secondary">${device.area} | ${device.integration}</div>
const battery = `,
batteryEntityPair && batteryEntityPair[0] };
? this.hass.states[batteryEntityPair[0]] } else {
: undefined; columns.name = {
const batteryDomain = battery title: this.hass.localize("ui.panel.config.devices.data_table.device"),
? computeStateDomain(battery) main: true,
: undefined; sortable: true,
const batteryCharging = filterable: true,
batteryEntityPair && batteryEntityPair[1] grows: true,
? this.hass.states[batteryEntityPair[1]] direction: "asc",
: undefined;
return battery &&
(batteryDomain === "binary_sensor" || !isNaN(battery.state as any))
? html`
${batteryDomain === "sensor"
? this.hass.formatEntityState(battery)
: nothing}
<ha-battery-icon
.hass=${this.hass}
.batteryStateObj=${battery}
.batteryChargingStateObj=${batteryCharging}
></ha-battery-icon>
`
: html``;
},
}; };
if (showDisabled) {
columns.disabled_by = {
title: "",
label: this.hass.localize(
"ui.panel.config.devices.data_table.disabled_by"
),
type: "icon",
template: (disabled_by) =>
disabled_by
? html`<div
tabindex="0"
style="display:inline-block; position: relative;"
>
<ha-svg-icon .path=${mdiCancel}></ha-svg-icon>
<simple-tooltip animation-delay="0" position="left">
${this.hass.localize("ui.panel.config.devices.disabled")}
</simple-tooltip>
</div>`
: "—",
};
}
return columns;
} }
);
columns.manufacturer = {
title: this.hass.localize(
"ui.panel.config.devices.data_table.manufacturer"
),
sortable: true,
hidden: narrow,
filterable: true,
width: "15%",
};
columns.model = {
title: this.hass.localize("ui.panel.config.devices.data_table.model"),
sortable: true,
hidden: narrow,
filterable: true,
width: "15%",
};
columns.area = {
title: this.hass.localize("ui.panel.config.devices.data_table.area"),
sortable: true,
hidden: narrow,
filterable: true,
width: "15%",
};
columns.integration = {
title: this.hass.localize(
"ui.panel.config.devices.data_table.integration"
),
sortable: true,
hidden: narrow,
filterable: true,
width: "15%",
};
columns.battery_entity = {
title: this.hass.localize("ui.panel.config.devices.data_table.battery"),
sortable: true,
filterable: true,
type: "numeric",
width: narrow ? "95px" : "15%",
maxWidth: "95px",
valueColumn: "battery_level",
template: (device) => {
const batteryEntityPair = device.battery_entity;
const battery =
batteryEntityPair && batteryEntityPair[0]
? this.hass.states[batteryEntityPair[0]]
: undefined;
const batteryDomain = battery ? computeStateDomain(battery) : undefined;
const batteryCharging =
batteryEntityPair && batteryEntityPair[1]
? this.hass.states[batteryEntityPair[1]]
: undefined;
return battery &&
(batteryDomain === "binary_sensor" || !isNaN(battery.state as any))
? html`
${batteryDomain === "sensor"
? this.hass.formatEntityState(battery)
: nothing}
<ha-battery-icon
.hass=${this.hass}
.batteryStateObj=${battery}
.batteryChargingStateObj=${batteryCharging}
></ha-battery-icon>
`
: html``;
},
};
if (showDisabled) {
columns.disabled_by = {
title: "",
label: this.hass.localize(
"ui.panel.config.devices.data_table.disabled_by"
),
type: "icon",
template: (device) =>
device.disabled_by
? html`<div
tabindex="0"
style="display:inline-block; position: relative;"
>
<ha-svg-icon .path=${mdiCancel}></ha-svg-icon>
<simple-tooltip animation-delay="0" position="left">
${this.hass.localize("ui.panel.config.devices.disabled")}
</simple-tooltip>
</div>`
: "—",
};
}
return columns;
});
public willUpdate(changedProps) { public willUpdate(changedProps) {
if (changedProps.has("_searchParms")) { if (changedProps.has("_searchParms")) {

View File

@ -183,7 +183,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
"ui.panel.config.entities.picker.headers.state_icon" "ui.panel.config.entities.picker.headers.state_icon"
), ),
type: "icon", type: "icon",
template: (_, entry: EntityRow) => html` template: (entry) => html`
<ha-state-icon <ha-state-icon
title=${ifDefined(entry.entity?.state)} title=${ifDefined(entry.entity?.state)}
slot="item-icon" slot="item-icon"
@ -201,12 +201,12 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
direction: "asc", direction: "asc",
grows: true, grows: true,
template: narrow template: narrow
? (name, entity: EntityRow) => html` ? (entry) => html`
${name}<br /> ${entry.name}<br />
<div class="secondary"> <div class="secondary">
${entity.entity_id} | ${entry.entity_id} |
${this.hass.localize(`component.${entity.platform}.title`) || ${this.hass.localize(`component.${entry.platform}.title`) ||
entity.platform} entry.platform}
</div> </div>
` `
: undefined, : undefined,
@ -228,8 +228,9 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
sortable: true, sortable: true,
filterable: true, filterable: true,
width: "20%", width: "20%",
template: (platform) => template: (entry) =>
this.hass.localize(`component.${platform}.title`) || platform, this.hass.localize(`component.${entry.platform}.title`) ||
entry.platform,
}, },
area: { area: {
title: this.hass.localize( title: this.hass.localize(
@ -248,10 +249,12 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
hidden: narrow || !showDisabled, hidden: narrow || !showDisabled,
filterable: true, filterable: true,
width: "15%", width: "15%",
template: (disabled_by: EntityRegistryEntry["disabled_by"]) => template: (entry) =>
disabled_by === null entry.disabled_by === null
? "—" ? "—"
: this.hass.localize(`config_entry.disabled_by.${disabled_by}`), : this.hass.localize(
`config_entry.disabled_by.${entry.disabled_by}`
),
}, },
status: { status: {
title: this.hass.localize( title: this.hass.localize(
@ -261,11 +264,11 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
sortable: true, sortable: true,
filterable: true, filterable: true,
width: "68px", width: "68px",
template: (_status, entity: EntityRow) => template: (entry) =>
entity.unavailable || entry.unavailable ||
entity.disabled_by || entry.disabled_by ||
entity.hidden_by || entry.hidden_by ||
entity.readonly entry.readonly
? html` ? html`
<div <div
tabindex="0" tabindex="0"
@ -273,32 +276,32 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
> >
<ha-svg-icon <ha-svg-icon
style=${styleMap({ style=${styleMap({
color: entity.unavailable ? "var(--error-color)" : "", color: entry.unavailable ? "var(--error-color)" : "",
})} })}
.path=${entity.restored .path=${entry.restored
? mdiRestoreAlert ? mdiRestoreAlert
: entity.unavailable : entry.unavailable
? mdiAlertCircle ? mdiAlertCircle
: entity.disabled_by : entry.disabled_by
? mdiCancel ? mdiCancel
: entity.hidden_by : entry.hidden_by
? mdiEyeOff ? mdiEyeOff
: mdiPencilOff} : mdiPencilOff}
></ha-svg-icon> ></ha-svg-icon>
<simple-tooltip animation-delay="0" position="left"> <simple-tooltip animation-delay="0" position="left">
${entity.restored ${entry.restored
? this.hass.localize( ? this.hass.localize(
"ui.panel.config.entities.picker.status.restored" "ui.panel.config.entities.picker.status.restored"
) )
: entity.unavailable : entry.unavailable
? this.hass.localize( ? this.hass.localize(
"ui.panel.config.entities.picker.status.unavailable" "ui.panel.config.entities.picker.status.unavailable"
) )
: entity.disabled_by : entry.disabled_by
? this.hass.localize( ? this.hass.localize(
"ui.panel.config.entities.picker.status.disabled" "ui.panel.config.entities.picker.status.disabled"
) )
: entity.hidden_by : entry.hidden_by
? this.hass.localize( ? this.hass.localize(
"ui.panel.config.entities.picker.status.hidden" "ui.panel.config.entities.picker.status.hidden"
) )

View File

@ -6,7 +6,10 @@ import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { computeStateDomain } from "../../../common/entity/compute_state_domain"; import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { navigate } from "../../../common/navigate"; import { navigate } from "../../../common/navigate";
import { LocalizeFunc } from "../../../common/translations/localize"; import {
LocalizeFunc,
LocalizeKeys,
} from "../../../common/translations/localize";
import { extractSearchParam } from "../../../common/url/search-params"; import { extractSearchParam } from "../../../common/url/search-params";
import { import {
DataTableColumnContainer, DataTableColumnContainer,
@ -27,6 +30,7 @@ import {
} from "../../../data/entity_registry"; } from "../../../data/entity_registry";
import { domainToName } from "../../../data/integration"; import { domainToName } from "../../../data/integration";
import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow"; import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow";
import { showOptionsFlowDialog } from "../../../dialogs/config-flow/show-dialog-options-flow";
import { import {
showAlertDialog, showAlertDialog,
showConfirmationDialog, showConfirmationDialog,
@ -38,9 +42,19 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { HomeAssistant, Route } from "../../../types"; import { HomeAssistant, Route } from "../../../types";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import "../integrations/ha-integration-overflow-menu"; import "../integrations/ha-integration-overflow-menu";
import { HelperDomain, isHelperDomain } from "./const"; import { isHelperDomain } from "./const";
import { showHelperDetailDialog } from "./show-dialog-helper-detail"; import { showHelperDetailDialog } from "./show-dialog-helper-detail";
import { showOptionsFlowDialog } from "../../../dialogs/config-flow/show-dialog-options-flow";
type HelperItem = {
id: string;
name: string;
icon?: string;
entity_id: string;
editable?: boolean;
type: string;
configEntry?: ConfigEntry;
entity?: HassEntity;
};
// This groups items by a key but only returns last entry per key. // This groups items by a key but only returns last entry per key.
const groupByOne = <T>( const groupByOne = <T>(
@ -108,16 +122,16 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
private _columns = memoizeOne( private _columns = memoizeOne(
(narrow: boolean, localize: LocalizeFunc): DataTableColumnContainer => { (narrow: boolean, localize: LocalizeFunc): DataTableColumnContainer => {
const columns: DataTableColumnContainer = { const columns: 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",
template: (icon, helper: any) => template: (helper) =>
helper.entity helper.entity
? html`<ha-state-icon .state=${helper.entity}></ha-state-icon>` ? html`<ha-state-icon .state=${helper.entity}></ha-state-icon>`
: html`<ha-svg-icon : html`<ha-svg-icon
.path=${icon} .path=${helper.icon}
style="color: var(--error-color)" style="color: var(--error-color)"
></ha-svg-icon>`, ></ha-svg-icon>`,
}, },
@ -128,10 +142,10 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
filterable: true, filterable: true,
grows: true, grows: true,
direction: "asc", direction: "asc",
template: (name, item: any) => html` template: (helper) => html`
${name} ${helper.name}
${narrow ${narrow
? html`<div class="secondary">${item.entity_id}</div> ` ? html`<div class="secondary">${helper.entity_id}</div> `
: ""} : ""}
`, `,
}, },
@ -149,11 +163,13 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
sortable: true, sortable: true,
width: "25%", width: "25%",
filterable: true, filterable: true,
template: (type: HelperDomain, row) => template: (helper) =>
row.configEntry helper.configEntry
? domainToName(localize, type) ? domainToName(localize, helper.type)
: html` : html`
${localize(`ui.panel.config.helpers.types.${type}`) || type} ${localize(
`ui.panel.config.helpers.types.${helper.type}` as LocalizeKeys
) || helper.type}
`, `,
}; };
columns.editable = { columns.editable = {
@ -162,8 +178,8 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
"ui.panel.config.helpers.picker.headers.editable" "ui.panel.config.helpers.picker.headers.editable"
), ),
type: "icon", type: "icon",
template: (editable) => html` template: (helper) => html`
${!editable ${!helper.editable
? html` ? html`
<div <div
tabindex="0" tabindex="0"
@ -189,7 +205,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
stateItems: HassEntity[], stateItems: HassEntity[],
entityEntries: Record<string, EntityRegistryEntry>, entityEntries: Record<string, EntityRegistryEntry>,
configEntries: Record<string, ConfigEntry> configEntries: Record<string, ConfigEntry>
) => { ): HelperItem[] => {
const configEntriesCopy = { ...configEntries }; const configEntriesCopy = { ...configEntries };
const states = stateItems.map((entityState) => { const states = stateItems.map((entityState) => {

View File

@ -38,7 +38,7 @@ export class ZHAClustersDataTable extends LitElement {
}); });
private _columns = memoizeOne( private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer => (narrow: boolean): DataTableColumnContainer<ClusterRowData> =>
narrow narrow
? { ? {
name: { name: {
@ -57,7 +57,7 @@ export class ZHAClustersDataTable extends LitElement {
}, },
id: { id: {
title: "ID", title: "ID",
template: (id: number) => html` ${formatAsPaddedHex(id)} `, template: (cluster) => html` ${formatAsPaddedHex(cluster.id)} `,
sortable: true, sortable: true,
width: "25%", width: "25%",
}, },

View File

@ -67,9 +67,9 @@ export class ZHADeviceEndpointDataTable extends LitElement {
filterable: true, filterable: true,
direction: "asc", direction: "asc",
grows: true, grows: true,
template: (name, device: any) => html` template: (device) => html`
<a href=${`/config/devices/device/${device.dev_id}`}> <a href=${`/config/devices/device/${device.dev_id}`}>
${name} ${device.name}
</a> </a>
`, `,
}, },
@ -86,9 +86,9 @@ export class ZHADeviceEndpointDataTable extends LitElement {
filterable: true, filterable: true,
direction: "asc", direction: "asc",
grows: true, grows: true,
template: (name, device: any) => html` template: (device) => html`
<a href=${`/config/devices/device/${device.dev_id}`}> <a href=${`/config/devices/device/${device.dev_id}`}>
${name} ${device.name}
</a> </a>
`, `,
}, },
@ -102,10 +102,10 @@ export class ZHADeviceEndpointDataTable extends LitElement {
sortable: false, sortable: false,
filterable: false, filterable: false,
width: "50%", width: "50%",
template: (entities) => html` template: (device) => html`
${entities.length ${device.entities.length
? entities.length > 3 ? device.entities.length > 3
? html`${entities ? html`${device.entities
.slice(0, 2) .slice(0, 2)
.map( .map(
(entity) => (entity) =>
@ -115,8 +115,8 @@ export class ZHADeviceEndpointDataTable extends LitElement {
${entity.name || entity.original_name} ${entity.name || entity.original_name}
</div>` </div>`
)} )}
<div>And ${entities.length - 2} more...</div>` <div>And ${device.entities.length - 2} more...</div>`
: entities.map( : device.entities.map(
(entity) => (entity) =>
html`<div html`<div
style="overflow: hidden; text-overflow: ellipsis;" style="overflow: hidden; text-overflow: ellipsis;"

View File

@ -18,7 +18,7 @@ import {
} from "../../../../../components/data-table/ha-data-table"; } from "../../../../../components/data-table/ha-data-table";
import "../../../../../components/ha-fab"; import "../../../../../components/ha-fab";
import "../../../../../components/ha-icon-button"; import "../../../../../components/ha-icon-button";
import { fetchGroups, ZHADevice, ZHAGroup } from "../../../../../data/zha"; import { fetchGroups, ZHAGroup } from "../../../../../data/zha";
import "../../../../../layouts/hass-tabs-subpage-data-table"; import "../../../../../layouts/hass-tabs-subpage-data-table";
import { haStyle } from "../../../../../resources/styles"; import { haStyle } from "../../../../../resources/styles";
import { HomeAssistant, Route } from "../../../../../types"; import { HomeAssistant, Route } from "../../../../../types";
@ -71,7 +71,7 @@ export class ZHAGroupsDashboard extends LitElement {
}); });
private _columns = memoizeOne( private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer => (narrow: boolean): DataTableColumnContainer<GroupRowData> =>
narrow narrow
? { ? {
name: { name: {
@ -94,16 +94,14 @@ export class ZHAGroupsDashboard extends LitElement {
title: this.hass.localize("ui.panel.config.zha.groups.group_id"), title: this.hass.localize("ui.panel.config.zha.groups.group_id"),
type: "numeric", type: "numeric",
width: "15%", width: "15%",
template: (groupId: number) => html` template: (group) => html` ${formatAsPaddedHex(group.group_id)} `,
${formatAsPaddedHex(groupId)}
`,
sortable: true, sortable: true,
}, },
members: { members: {
title: this.hass.localize("ui.panel.config.zha.groups.members"), title: this.hass.localize("ui.panel.config.zha.groups.members"),
type: "numeric", type: "numeric",
width: "15%", width: "15%",
template: (members: ZHADevice[]) => html` ${members.length} `, template: (group) => html` ${group.members.length} `,
sortable: true, sortable: true,
}, },
} }

View File

@ -41,15 +41,15 @@ class ZWaveJSProvisioned extends LitElement {
} }
private _columns = memoizeOne( private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer => ({ (narrow: boolean): DataTableColumnContainer<ZwaveJSProvisioningEntry> => ({
included: { included: {
title: this.hass.localize( title: this.hass.localize(
"ui.panel.config.zwave_js.provisioned.included" "ui.panel.config.zwave_js.provisioned.included"
), ),
type: "icon", type: "icon",
width: "100px", width: "100px",
template: (_info, provisioningEntry: any) => template: (entry) =>
provisioningEntry.additional_properties.nodeId entry.additional_properties.nodeId
? html` ? html`
<ha-svg-icon <ha-svg-icon
.label=${this.hass.localize( .label=${this.hass.localize(
@ -81,14 +81,16 @@ class ZWaveJSProvisioned extends LitElement {
hidden: narrow, hidden: narrow,
filterable: true, filterable: true,
sortable: true, sortable: true,
template: (securityClasses: SecurityClass[]) => template: (entry) => {
securityClasses const securityClasses = entry.security_classes;
return securityClasses
.map((secClass) => .map((secClass) =>
this.hass.localize( this.hass.localize(
`ui.panel.config.zwave_js.security_classes.${SecurityClass[secClass]}.title` `ui.panel.config.zwave_js.security_classes.${SecurityClass[secClass]}.title`
) )
) )
.join(", "), .join(", ");
},
}, },
unprovision: { unprovision: {
title: this.hass.localize( title: this.hass.localize(
@ -96,13 +98,13 @@ class ZWaveJSProvisioned extends LitElement {
), ),
type: "icon-button", type: "icon-button",
width: "100px", width: "100px",
template: (_info, provisioningEntry: any) => html` template: (entry) => html`
<ha-icon-button <ha-icon-button
.label=${this.hass.localize( .label=${this.hass.localize(
"ui.panel.config.zwave_js.provisioned.unprovison" "ui.panel.config.zwave_js.provisioned.unprovison"
)} )}
.path=${mdiDelete} .path=${mdiDelete}
.provisioningEntry=${provisioningEntry} .provisioningEntry=${entry}
@click=${this._unprovision} @click=${this._unprovision}
></ha-icon-button> ></ha-icon-button>
`, `,

View File

@ -68,12 +68,12 @@ export class HaConfigLovelaceDashboards extends LitElement {
"ui.panel.config.lovelace.dashboards.picker.headers.icon" "ui.panel.config.lovelace.dashboards.picker.headers.icon"
), ),
type: "icon", type: "icon",
template: (icon: DataTableItem["icon"], dashboard) => template: (dashboard) =>
icon dashboard.icon
? html` ? html`
<ha-icon <ha-icon
slot="item-icon" slot="item-icon"
.icon=${icon} .icon=${dashboard.icon}
style=${ifDefined( style=${ifDefined(
dashboard.iconColor dashboard.iconColor
? `color: ${dashboard.iconColor}` ? `color: ${dashboard.iconColor}`
@ -91,9 +91,9 @@ export class HaConfigLovelaceDashboards extends LitElement {
sortable: true, sortable: true,
filterable: true, filterable: true,
grows: true, grows: true,
template: (title: DataTableItem["title"], dashboard) => { template: (dashboard) => {
const titleTemplate = html` const titleTemplate = html`
${title} ${dashboard.title}
${dashboard.default ${dashboard.default
? html` ? html`
<ha-svg-icon <ha-svg-icon
@ -132,10 +132,10 @@ export class HaConfigLovelaceDashboards extends LitElement {
sortable: true, sortable: true,
filterable: true, filterable: true,
width: "20%", width: "20%",
template: (mode: DataTableItem["mode"]) => html` template: (dashboard) => html`
${this.hass.localize( ${this.hass.localize(
`ui.panel.config.lovelace.dashboards.conf_mode.${mode}` `ui.panel.config.lovelace.dashboards.conf_mode.${dashboard.mode}`
) || mode} ) || dashboard.mode}
`, `,
}; };
if (dashboards.some((dashboard) => dashboard.filename)) { if (dashboards.some((dashboard) => dashboard.filename)) {
@ -155,8 +155,8 @@ export class HaConfigLovelaceDashboards extends LitElement {
sortable: true, sortable: true,
type: "icon", type: "icon",
width: "100px", width: "100px",
template: (requireAdmin: DataTableItem["require_admin"]) => template: (dashboard) =>
requireAdmin dashboard.require_admin
? html`<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>` ? html`<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>`
: html``, : html``,
}; };
@ -166,8 +166,8 @@ export class HaConfigLovelaceDashboards extends LitElement {
), ),
type: "icon", type: "icon",
width: "121px", width: "121px",
template: (sidebar: DataTableItem["show_in_sidebar"]) => template: (dashboard) =>
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``,
}; };
@ -180,12 +180,12 @@ export class HaConfigLovelaceDashboards extends LitElement {
), ),
filterable: true, filterable: true,
width: "100px", width: "100px",
template: (urlPath) => template: (dashboard) =>
narrow narrow
? html` ? html`
<ha-icon-button <ha-icon-button
.path=${mdiOpenInNew} .path=${mdiOpenInNew}
.urlPath=${urlPath} .urlPath=${dashboard.url_path}
@click=${this._navigate} @click=${this._navigate}
.label=${this.hass.localize( .label=${this.hass.localize(
"ui.panel.config.lovelace.dashboards.picker.open" "ui.panel.config.lovelace.dashboards.picker.open"
@ -193,7 +193,9 @@ export class HaConfigLovelaceDashboards extends LitElement {
></ha-icon-button> ></ha-icon-button>
` `
: html` : html`
<mwc-button .urlPath=${urlPath} @click=${this._navigate} <mwc-button
.urlPath=${dashboard.url_path}
@click=${this._navigate}
>${this.hass.localize( >${this.hass.localize(
"ui.panel.config.lovelace.dashboards.picker.open" "ui.panel.config.lovelace.dashboards.picker.open"
)}</mwc-button )}</mwc-button

View File

@ -40,7 +40,7 @@ export class HaConfigLovelaceRescources extends LitElement {
@state() private _resources: LovelaceResource[] = []; @state() private _resources: LovelaceResource[] = [];
private _columns = memoize( private _columns = memoize(
(_language): DataTableColumnContainer => ({ (_language): DataTableColumnContainer<LovelaceResource> => ({
url: { url: {
title: this.hass.localize( title: this.hass.localize(
"ui.panel.config.lovelace.resources.picker.headers.url" "ui.panel.config.lovelace.resources.picker.headers.url"
@ -58,10 +58,10 @@ export class HaConfigLovelaceRescources extends LitElement {
sortable: true, sortable: true,
filterable: true, filterable: true,
width: "30%", width: "30%",
template: (type: LovelaceResource["type"]) => html` template: (resource) => html`
${this.hass.localize( ${this.hass.localize(
`ui.panel.config.lovelace.resources.types.${type}` `ui.panel.config.lovelace.resources.types.${resource.type}`
) || type} ) || resource.type}
`, `,
}, },
}) })

View File

@ -47,6 +47,10 @@ import { formatShortDateTime } from "../../../common/datetime/format_date_time";
import { relativeTime } from "../../../common/datetime/relative_time"; import { relativeTime } from "../../../common/datetime/relative_time";
import { isUnavailableState } from "../../../data/entity"; import { isUnavailableState } from "../../../data/entity";
type SceneItem = SceneEntity & {
name: string;
};
@customElement("ha-scene-dashboard") @customElement("ha-scene-dashboard")
class HaSceneDashboard extends LitElement { class HaSceneDashboard extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@ -66,7 +70,7 @@ class HaSceneDashboard extends LitElement {
@state() private _filterValue?; @state() private _filterValue?;
private _scenes = memoizeOne( private _scenes = memoizeOne(
(scenes: SceneEntity[], filteredScenes?: string[] | null) => { (scenes: SceneEntity[], filteredScenes?: string[] | null): SceneItem[] => {
if (filteredScenes === null) { if (filteredScenes === null) {
return []; return [];
} }
@ -83,14 +87,14 @@ class HaSceneDashboard extends LitElement {
private _columns = memoizeOne( private _columns = memoizeOne(
(_language, narrow): DataTableColumnContainer => { (_language, narrow): DataTableColumnContainer => {
const columns: DataTableColumnContainer = { const columns: DataTableColumnContainer<SceneItem> = {
icon: { icon: {
title: "", title: "",
label: this.hass.localize( label: this.hass.localize(
"ui.panel.config.scene.picker.headers.state" "ui.panel.config.scene.picker.headers.state"
), ),
type: "icon", type: "icon",
template: (_, scene) => html` template: (scene) => html`
<ha-state-icon .state=${scene}></ha-state-icon> <ha-state-icon .state=${scene}></ha-state-icon>
`, `,
}, },
@ -112,20 +116,18 @@ class HaSceneDashboard extends LitElement {
), ),
sortable: true, sortable: true,
width: "30%", width: "30%",
template: (last_activated) => { template: (scene) => {
const date = new Date(last_activated); const lastActivated = scene.state;
if (!lastActivated || isUnavailableState(lastActivated)) {
return this.hass.localize("ui.components.relative_time.never");
}
const date = new Date(scene.state);
const now = new Date(); const now = new Date();
const dayDifference = differenceInDays(now, date); const dayDifference = differenceInDays(now, date);
return html` return html`
${last_activated && !isUnavailableState(last_activated) ${dayDifference > 3
? dayDifference > 3 ? formatShortDateTime(date, this.hass.locale, this.hass.config)
? formatShortDateTime( : relativeTime(date, this.hass.locale)}
date,
this.hass.locale,
this.hass.config
)
: relativeTime(date, this.hass.locale)
: this.hass.localize("ui.components.relative_time.never")}
`; `;
}, },
}; };
@ -133,7 +135,7 @@ class HaSceneDashboard extends LitElement {
columns.only_editable = { columns.only_editable = {
title: "", title: "",
width: "56px", width: "56px",
template: (_info, scene: any) => template: (scene) =>
!scene.attributes.id !scene.attributes.id
? html` ? html`
<simple-tooltip animation-delay="0" position="left"> <simple-tooltip animation-delay="0" position="left">
@ -152,7 +154,7 @@ class HaSceneDashboard extends LitElement {
title: "", title: "",
width: "72px", width: "72px",
type: "overflow-menu", type: "overflow-menu",
template: (_: string, scene: any) => html` template: (scene) => html`
<ha-icon-overflow-menu <ha-icon-overflow-menu
.hass=${this.hass} .hass=${this.hass}
narrow narrow

View File

@ -7,16 +7,15 @@ import {
mdiPlus, mdiPlus,
mdiTransitConnection, mdiTransitConnection,
} from "@mdi/js"; } from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { differenceInDays } from "date-fns/esm"; import { differenceInDays } from "date-fns/esm";
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { formatShortDateTime } from "../../../common/datetime/format_date_time"; import { formatShortDateTime } from "../../../common/datetime/format_date_time";
import { relativeTime } from "../../../common/datetime/relative_time"; import { relativeTime } from "../../../common/datetime/relative_time";
import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event"; import { HASSDomEvent, fireEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name"; import { computeStateName } from "../../../common/entity/compute_state_name";
import { navigate } from "../../../common/navigate"; import { navigate } from "../../../common/navigate";
import { computeRTL } from "../../../common/util/compute_rtl"; import { computeRTL } from "../../../common/util/compute_rtl";
@ -29,13 +28,18 @@ import "../../../components/ha-fab";
import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button";
import "../../../components/ha-icon-overflow-menu"; import "../../../components/ha-icon-overflow-menu";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import { fetchBlueprints } from "../../../data/blueprint";
import { UNAVAILABLE } from "../../../data/entity";
import { EntityRegistryEntry } from "../../../data/entity_registry";
import { import {
ScriptEntity,
deleteScript, deleteScript,
fetchScriptFileConfig, fetchScriptFileConfig,
getScriptStateConfig, getScriptStateConfig,
showScriptEditor, showScriptEditor,
triggerScript, triggerScript,
} from "../../../data/script"; } from "../../../data/script";
import { findRelated } from "../../../data/search";
import { import {
showAlertDialog, showAlertDialog,
showConfirmationDialog, showConfirmationDialog,
@ -45,18 +49,18 @@ import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types"; import { HomeAssistant, Route } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url"; import { documentationUrl } from "../../../util/documentation-url";
import { showToast } from "../../../util/toast"; import { showToast } from "../../../util/toast";
import { configSections } from "../ha-panel-config";
import { showNewAutomationDialog } from "../automation/show-dialog-new-automation"; import { showNewAutomationDialog } from "../automation/show-dialog-new-automation";
import { EntityRegistryEntry } from "../../../data/entity_registry"; import { configSections } from "../ha-panel-config";
import { findRelated } from "../../../data/search";
import { fetchBlueprints } from "../../../data/blueprint"; type ScriptItem = ScriptEntity & {
import { UNAVAILABLE } from "../../../data/entity"; name: string;
};
@customElement("ha-script-picker") @customElement("ha-script-picker")
class HaScriptPicker extends LitElement { class HaScriptPicker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property() public scripts!: HassEntity[]; @property() public scripts!: ScriptEntity[];
@property() public isWide!: boolean; @property() public isWide!: boolean;
@ -75,7 +79,10 @@ class HaScriptPicker extends LitElement {
@state() private _filterValue?; @state() private _filterValue?;
private _scripts = memoizeOne( private _scripts = memoizeOne(
(scripts: HassEntity[], filteredScripts?: string[] | null) => { (
scripts: ScriptEntity[],
filteredScripts?: string[] | null
): ScriptItem[] => {
if (filteredScripts === null) { if (filteredScripts === null) {
return []; return [];
} }
@ -93,126 +100,136 @@ class HaScriptPicker extends LitElement {
} }
); );
private _columns = memoizeOne((narrow, _locale): DataTableColumnContainer => { private _columns = memoizeOne(
const columns: DataTableColumnContainer = { (narrow, _locale): DataTableColumnContainer<ScriptItem> => {
icon: { const columns: DataTableColumnContainer = {
title: "", icon: {
label: this.hass.localize( title: "",
"ui.panel.config.script.picker.headers.state" label: this.hass.localize(
), "ui.panel.config.script.picker.headers.state"
type: "icon", ),
template: (_icon, script) => type: "icon",
html`<ha-state-icon template: (script) =>
.state=${script} html`<ha-state-icon
style=${styleMap({ .state=${script}
color: style=${styleMap({
script.state === UNAVAILABLE ? "var(--error-color)" : "unset", color:
})} script.state === UNAVAILABLE ? "var(--error-color)" : "unset",
></ha-state-icon>`, })}
}, ></ha-state-icon>`,
name: { },
title: this.hass.localize("ui.panel.config.script.picker.headers.name"), name: {
main: true, title: this.hass.localize(
sortable: true, "ui.panel.config.script.picker.headers.name"
filterable: true, ),
direction: "asc", main: true,
grows: true, sortable: true,
template: narrow filterable: true,
? (name, script: any) => { direction: "asc",
const date = new Date(script.attributes.last_triggered); grows: true,
const now = new Date(); template: narrow
const dayDifference = differenceInDays(now, date); ? (script) => {
return html` const date = new Date(script.last_triggered);
${name} const now = new Date();
<div class="secondary"> const dayDifference = differenceInDays(now, date);
${this.hass.localize("ui.card.automation.last_triggered")}: return html`
${script.attributes.last_triggered ${script.name}
? dayDifference > 3 <div class="secondary">
? formatShortDateTime( ${this.hass.localize("ui.card.automation.last_triggered")}:
date, ${script.last_triggered
this.hass.locale, ? dayDifference > 3
this.hass.config ? formatShortDateTime(
) date,
: relativeTime(date, this.hass.locale) this.hass.locale,
: this.hass.localize("ui.components.relative_time.never")} this.hass.config
</div> )
`; : relativeTime(date, this.hass.locale)
} : this.hass.localize("ui.components.relative_time.never")}
: undefined, </div>
}, `;
}; }
if (!narrow) { : undefined,
columns.last_triggered = {
sortable: true,
width: "40%",
title: this.hass.localize("ui.card.automation.last_triggered"),
template: (last_triggered) => {
const date = new Date(last_triggered);
const now = new Date();
const dayDifference = differenceInDays(now, date);
return html`
${last_triggered
? dayDifference > 3
? formatShortDateTime(date, this.hass.locale, this.hass.config)
: relativeTime(date, this.hass.locale)
: this.hass.localize("ui.components.relative_time.never")}
`;
}, },
}; };
if (!narrow) {
columns.last_triggered = {
sortable: true,
width: "40%",
title: this.hass.localize("ui.card.automation.last_triggered"),
template: (script) => {
const date = new Date(script.last_triggered);
const now = new Date();
const dayDifference = differenceInDays(now, date);
return html`
${script.last_triggered
? dayDifference > 3
? formatShortDateTime(
date,
this.hass.locale,
this.hass.config
)
: relativeTime(date, this.hass.locale)
: this.hass.localize("ui.components.relative_time.never")}
`;
},
};
}
columns.actions = {
title: "",
width: this.narrow ? undefined : "10%",
type: "overflow-menu",
template: (script) => html`
<ha-icon-overflow-menu
.hass=${this.hass}
narrow
.items=${[
{
path: mdiInformationOutline,
label: this.hass.localize(
"ui.panel.config.script.picker.show_info"
),
action: () => this._showInfo(script),
},
{
path: mdiPlay,
label: this.hass.localize("ui.panel.config.script.picker.run"),
action: () => this._runScript(script),
},
{
path: mdiTransitConnection,
label: this.hass.localize(
"ui.panel.config.script.picker.show_trace"
),
action: () => this._showTrace(script),
},
{
divider: true,
},
{
path: mdiContentDuplicate,
label: this.hass.localize(
"ui.panel.config.script.picker.duplicate"
),
action: () => this._duplicate(script),
},
{
label: this.hass.localize(
"ui.panel.config.script.picker.delete"
),
path: mdiDelete,
action: () => this._deleteConfirm(script),
warning: true,
},
]}
>
</ha-icon-overflow-menu>
`,
};
return columns;
} }
);
columns.actions = {
title: "",
width: this.narrow ? undefined : "10%",
type: "overflow-menu",
template: (_: string, script: any) => html`
<ha-icon-overflow-menu
.hass=${this.hass}
narrow
.items=${[
{
path: mdiInformationOutline,
label: this.hass.localize(
"ui.panel.config.script.picker.show_info"
),
action: () => this._showInfo(script),
},
{
path: mdiPlay,
label: this.hass.localize("ui.panel.config.script.picker.run"),
action: () => this._runScript(script),
},
{
path: mdiTransitConnection,
label: this.hass.localize(
"ui.panel.config.script.picker.show_trace"
),
action: () => this._showTrace(script),
},
{
divider: true,
},
{
path: mdiContentDuplicate,
label: this.hass.localize(
"ui.panel.config.script.picker.duplicate"
),
action: () => this._duplicate(script),
},
{
label: this.hass.localize("ui.panel.config.script.picker.delete"),
path: mdiDelete,
action: () => this._deleteConfirm(script),
warning: true,
},
]}
>
</ha-icon-overflow-menu>
`,
};
return columns;
});
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`

View File

@ -36,6 +36,7 @@ import { showTagDetailDialog } from "./show-dialog-tag-detail";
import "./tag-image"; import "./tag-image";
export interface TagRowData extends Tag { export interface TagRowData extends Tag {
display_name: string;
last_scanned_datetime: Date | null; last_scanned_datetime: Date | null;
} }
@ -55,94 +56,90 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
return this.hass.auth.external?.config.canWriteTag; return this.hass.auth.external?.config.canWriteTag;
} }
private _columns = memoizeOne( private _columns = memoizeOne((narrow: boolean, _language) => {
(narrow: boolean, _language): DataTableColumnContainer => { const columns: DataTableColumnContainer<TagRowData> = {
const columns: DataTableColumnContainer = { icon: {
icon: {
title: "",
label: this.hass.localize("ui.panel.config.tag.headers.icon"),
type: "icon",
template: (_icon, tag) => html`<tag-image .tag=${tag}></tag-image>`,
},
display_name: {
title: this.hass.localize("ui.panel.config.tag.headers.name"),
main: true,
sortable: true,
filterable: true,
grows: true,
template: (name, tag: any) =>
html`${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>`
: ""}`,
},
};
if (!narrow) {
columns.last_scanned_datetime = {
title: this.hass.localize("ui.panel.config.tag.headers.last_scanned"),
sortable: true,
direction: "desc",
width: "20%",
template: (last_scanned_datetime) => html`
${last_scanned_datetime
? html`<ha-relative-time
.hass=${this.hass}
.datetime=${last_scanned_datetime}
capitalize
></ha-relative-time>`
: this.hass.localize("ui.panel.config.tag.never_scanned")}
`,
};
}
if (this._canWriteTags) {
columns.write = {
title: "",
label: this.hass.localize("ui.panel.config.tag.headers.write"),
type: "icon-button",
template: (_write, tag: any) =>
html` <ha-icon-button
.tag=${tag}
@click=${this._handleWriteClick}
.label=${this.hass.localize("ui.panel.config.tag.write")}
.path=${mdiContentDuplicate}
></ha-icon-button>`,
};
}
columns.automation = {
title: "", title: "",
type: "icon-button", label: this.hass.localize("ui.panel.config.tag.headers.icon"),
template: (_automation, tag: any) => type: "icon",
html` <ha-icon-button template: (tag) => html`<tag-image .tag=${tag}></tag-image>`,
.tag=${tag} },
@click=${this._handleAutomationClick} display_name: {
.label=${this.hass.localize( title: this.hass.localize("ui.panel.config.tag.headers.name"),
"ui.panel.config.tag.create_automation" main: true,
)} sortable: true,
.path=${mdiRobot} filterable: true,
></ha-icon-button>`, grows: true,
template: (tag) =>
html`${tag.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>`
: ""}`,
},
};
if (!narrow) {
columns.last_scanned_datetime = {
title: this.hass.localize("ui.panel.config.tag.headers.last_scanned"),
sortable: true,
direction: "desc",
width: "20%",
template: (tag) => html`
${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")}
`,
}; };
columns.edit = {
title: "",
type: "icon-button",
template: (_settings, tag: any) =>
html` <ha-icon-button
.tag=${tag}
@click=${this._handleEditClick}
.label=${this.hass.localize("ui.panel.config.tag.edit")}
.path=${mdiCog}
></ha-icon-button>`,
};
return columns;
} }
); if (this._canWriteTags) {
columns.write = {
title: "",
label: this.hass.localize("ui.panel.config.tag.headers.write"),
type: "icon-button",
template: (tag) =>
html` <ha-icon-button
.tag=${tag}
@click=${this._handleWriteClick}
.label=${this.hass.localize("ui.panel.config.tag.write")}
.path=${mdiContentDuplicate}
></ha-icon-button>`,
};
}
columns.automation = {
title: "",
type: "icon-button",
template: (tag) =>
html` <ha-icon-button
.tag=${tag}
@click=${this._handleAutomationClick}
.label=${this.hass.localize("ui.panel.config.tag.create_automation")}
.path=${mdiRobot}
></ha-icon-button>`,
};
columns.edit = {
title: "",
type: "icon-button",
template: (tag) =>
html` <ha-icon-button
.tag=${tag}
@click=${this._handleEditClick}
.label=${this.hass.localize("ui.panel.config.tag.edit")}
.path=${mdiCog}
></ha-icon-button>`,
};
return columns;
});
private _data = memoizeOne((tags: Tag[]): TagRowData[] => private _data = memoizeOne((tags: Tag[]): TagRowData[] =>
tags.map((tag) => ({ tags.map((tag) => ({

View File

@ -49,14 +49,14 @@ export class HaConfigUsers extends LitElement {
width: "25%", width: "25%",
direction: "asc", direction: "asc",
grows: true, grows: true,
template: (name, user) => template: (user) =>
narrow narrow
? html` ${name}<br /> ? html` ${user.name}<br />
<div class="secondary"> <div class="secondary">
${user.username ? `${user.username} |` : ""} ${user.username ? `${user.username} |` : ""}
${localize(`groups.${user.group_ids[0]}`)} ${localize(`groups.${user.group_ids[0]}`)}
</div>` </div>`
: html` ${name || : html` ${user.name ||
this.hass!.localize( this.hass!.localize(
"ui.panel.config.users.editor.unnamed_user" "ui.panel.config.users.editor.unnamed_user"
)}`, )}`,
@ -68,7 +68,7 @@ export class HaConfigUsers extends LitElement {
width: "20%", width: "20%",
direction: "asc", direction: "asc",
hidden: narrow, hidden: narrow,
template: (username) => html`${username || "—"}`, template: (user) => html`${user.name || "—"}`,
}, },
group_ids: { group_ids: {
title: localize("ui.panel.config.users.picker.headers.group"), title: localize("ui.panel.config.users.picker.headers.group"),
@ -77,8 +77,8 @@ export class HaConfigUsers extends LitElement {
width: "20%", width: "20%",
direction: "asc", direction: "asc",
hidden: narrow, hidden: narrow,
template: (groupIds: User["group_ids"]) => html` template: (user) => html`
${localize(`groups.${groupIds[0]}`)} ${localize(`groups.${user.group_ids[0]}`)}
`, `,
}, },
is_active: { is_active: {
@ -90,8 +90,8 @@ export class HaConfigUsers extends LitElement {
filterable: true, filterable: true,
width: "80px", width: "80px",
hidden: narrow, hidden: narrow,
template: (is_active) => template: (user) =>
is_active user.is_active
? html`<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>` ? html`<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>`
: "", : "",
}, },
@ -104,8 +104,8 @@ export class HaConfigUsers extends LitElement {
filterable: true, filterable: true,
width: "80px", width: "80px",
hidden: narrow, hidden: narrow,
template: (generated) => template: (user) =>
generated user.system_generated
? html`<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>` ? html`<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>`
: "", : "",
}, },
@ -118,8 +118,10 @@ export class HaConfigUsers extends LitElement {
filterable: true, filterable: true,
width: "80px", width: "80px",
hidden: narrow, hidden: narrow,
template: (local) => template: (user) =>
local ? html`<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>` : "", user.local_only
? html`<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>`
: "",
}, },
icons: { icons: {
title: "", title: "",
@ -131,7 +133,7 @@ export class HaConfigUsers extends LitElement {
filterable: false, filterable: false,
width: "104px", width: "104px",
hidden: !narrow, hidden: !narrow,
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(
([icon, tooltip]) => ([icon, tooltip]) =>

View File

@ -134,7 +134,7 @@ export class VoiceAssistantsExpose extends LitElement {
title: "", title: "",
type: "icon", type: "icon",
hidden: narrow, hidden: narrow,
template: (_, entry) => html` template: (entry) => html`
<ha-state-icon <ha-state-icon
title=${ifDefined(entry.entity?.state)} title=${ifDefined(entry.entity?.state)}
.state=${entry.entity} .state=${entry.entity}
@ -150,8 +150,8 @@ export class VoiceAssistantsExpose extends LitElement {
filterable: true, filterable: true,
direction: "asc", direction: "asc",
grows: true, grows: true,
template: (name, entry) => html` template: (entry) => html`
${name}<br /> ${entry.name}<br />
<div class="secondary">${entry.entity_id}</div> <div class="secondary">${entry.entity_id}</div>
`, `,
}, },
@ -172,13 +172,13 @@ export class VoiceAssistantsExpose extends LitElement {
filterable: true, filterable: true,
width: "160px", width: "160px",
type: "flex", type: "flex",
template: (assistants, entry) => template: (entry) =>
html`${availableAssistants.map((key) => { html`${availableAssistants.map((key) => {
const supported = const supported =
!supportedEntities?.[key] || !supportedEntities?.[key] ||
supportedEntities[key].includes(entry.entity_id); supportedEntities[key].includes(entry.entity_id);
const manual = entry.manAssistants?.includes(key); const manual = entry.manAssistants?.includes(key);
return assistants.includes(key) return entry.assistants.includes(key)
? html` ? html`
<voice-assistants-expose-assistant-icon <voice-assistants-expose-assistant-icon
.assistant=${key} .assistant=${key}
@ -199,14 +199,14 @@ export class VoiceAssistantsExpose extends LitElement {
filterable: true, filterable: true,
hidden: narrow, hidden: narrow,
width: "15%", width: "15%",
template: (aliases) => template: (entry) =>
aliases.length === 0 entry.aliases.length === 0
? "-" ? "-"
: aliases.length === 1 : entry.aliases.length === 1
? aliases[0] ? entry.aliases[0]
: this.hass.localize( : this.hass.localize(
"ui.panel.config.voice_assistants.expose.aliases", "ui.panel.config.voice_assistants.expose.aliases",
{ count: aliases.length } { count: entry.aliases.length }
), ),
}, },
remove: { remove: {

View File

@ -80,7 +80,9 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) {
); );
private _columns = memoizeOne( private _columns = memoizeOne(
(localize: LocalizeFunc): DataTableColumnContainer => ({ (
localize: LocalizeFunc
): DataTableColumnContainer<DisplayedStatisticData> => ({
displayName: { displayName: {
title: localize( title: localize(
"ui.panel.developer-tools.tabs.statistics.data_table.name" "ui.panel.developer-tools.tabs.statistics.data_table.name"
@ -123,8 +125,8 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) {
filterable: true, filterable: true,
direction: "asc", direction: "asc",
width: "30%", width: "30%",
template: (issues_string) => template: (statistic) =>
html`${issues_string ?? html`${statistic.issues_string ??
localize("ui.panel.developer-tools.tabs.statistics.no_issue")}`, localize("ui.panel.developer-tools.tabs.statistics.no_issue")}`,
}, },
fix: { fix: {
@ -132,9 +134,12 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) {
label: this.hass.localize( label: this.hass.localize(
"ui.panel.developer-tools.tabs.statistics.fix_issue.fix" "ui.panel.developer-tools.tabs.statistics.fix_issue.fix"
), ),
template: (_, data: any) => template: (statistic) =>
html`${data.issues html`${statistic.issues
? html`<mwc-button @click=${this._fixIssue} .data=${data.issues}> ? html`<mwc-button
@click=${this._fixIssue}
.data=${statistic.issues}
>
${localize( ${localize(
"ui.panel.developer-tools.tabs.statistics.fix_issue.fix" "ui.panel.developer-tools.tabs.statistics.fix_issue.fix"
)} )}
@ -146,7 +151,7 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) {
title: "", title: "",
label: localize("ui.panel.developer-tools.tabs.statistics.adjust_sum"), label: localize("ui.panel.developer-tools.tabs.statistics.adjust_sum"),
type: "icon-button", type: "icon-button",
template: (_info, statistic: StatisticsMetaData) => template: (statistic) =>
statistic.has_sum statistic.has_sum
? html` ? html`
<ha-icon-button <ha-icon-button

View File

@ -54,7 +54,7 @@ export class HuiEntityPickerTable extends LitElement {
"ui.panel.lovelace.unused_entities.state_icon" "ui.panel.lovelace.unused_entities.state_icon"
), ),
type: "icon", type: "icon",
template: (_icon, entity: any) => html` template: (entity) => html`
<state-badge <state-badge
@click=${this._handleEntityClicked} @click=${this._handleEntityClicked}
.hass=${this.hass!} .hass=${this.hass!}
@ -68,9 +68,9 @@ export class HuiEntityPickerTable extends LitElement {
filterable: true, filterable: true,
grows: true, grows: true,
direction: "asc", direction: "asc",
template: (name, entity: any) => html` template: (entity: any) => html`
<div @click=${this._handleEntityClicked} style="cursor: pointer;"> <div @click=${this._handleEntityClicked} style="cursor: pointer;">
${name} ${entity.name}
${narrow ${narrow
? html` <div class="secondary">${entity.entity_id}</div> ` ? html` <div class="secondary">${entity.entity_id}</div> `
: ""} : ""}
@ -103,10 +103,10 @@ export class HuiEntityPickerTable extends LitElement {
sortable: true, sortable: true,
width: "15%", width: "15%",
hidden: narrow, hidden: narrow,
template: (lastChanged: string) => html` template: (entity) => html`
<ha-relative-time <ha-relative-time
.hass=${this.hass!} .hass=${this.hass!}
.datetime=${lastChanged} .datetime=${entity.last_changed}
capitalize capitalize
></ha-relative-time> ></ha-relative-time>
`, `,