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

View File

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

View File

@ -74,7 +74,7 @@ export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
title: TemplateResult | string;
label?: TemplateResult | string;
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;
maxWidth?: string;
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>
`;
})}

View File

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

View File

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

View File

@ -10,14 +10,14 @@ import {
} from "@mdi/js";
import {
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
html,
} from "lit";
import { customElement, property } from "lit/decorators";
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 { navigate } from "../../../common/navigate";
import { extractSearchParam } from "../../../common/url/search-params";
@ -32,7 +32,6 @@ import "../../../components/ha-icon-overflow-menu";
import "../../../components/ha-svg-icon";
import { showAutomationEditor } from "../../../data/automation";
import {
BlueprintDomain,
BlueprintMetaData,
Blueprints,
deleteBlueprint,
@ -50,10 +49,12 @@ import { documentationUrl } from "../../../util/documentation-url";
import { configSections } from "../ha-panel-config";
import { showAddBlueprintDialog } from "./show-dialog-import-blueprint";
interface BlueprintMetaDataPath extends BlueprintMetaData {
type BlueprintMetaDataPath = BlueprintMetaData & {
path: string;
error: boolean;
}
type: "automation" | "script";
fullpath: string;
};
const createNewFunctions = {
automation: (blueprintMeta: BlueprintMetaDataPath) => {
@ -86,7 +87,7 @@ class HaBlueprintOverview extends LitElement {
>;
private _processedBlueprints = memoizeOne(
(blueprints: Record<string, Blueprints>) => {
(blueprints: Record<string, Blueprints>): BlueprintMetaDataPath[] => {
const result: any[] = [];
Object.entries(blueprints).forEach(([type, typeBlueprints]) =>
Object.entries(typeBlueprints).forEach(([path, blueprint]) => {
@ -125,9 +126,9 @@ class HaBlueprintOverview extends LitElement {
direction: "asc",
grows: true,
template: narrow
? (name, entity: any) => html`
${name}<br />
<div class="secondary">${entity.path}</div>
? (blueprint) => html`
${blueprint.name}<br />
<div class="secondary">${blueprint.path}</div>
`
: undefined,
},
@ -135,9 +136,9 @@ class HaBlueprintOverview extends LitElement {
title: this.hass.localize(
"ui.panel.config.blueprint.overview.headers.type"
),
template: (type: BlueprintDomain) =>
template: (blueprint) =>
html`${this.hass.localize(
`ui.panel.config.blueprint.overview.types.${type}`
`ui.panel.config.blueprint.overview.types.${blueprint.type}`
)}`,
sortable: true,
filterable: true,
@ -163,7 +164,7 @@ class HaBlueprintOverview extends LitElement {
title: "",
width: this.narrow ? undefined : "10%",
type: "overflow-menu",
template: (_: string, blueprint) =>
template: (blueprint) =>
blueprint.error
? html`<ha-svg-icon
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,
label: this.hass.localize(
`ui.panel.config.blueprint.overview.create_${blueprint.domain}`
`ui.panel.config.blueprint.overview.create_${blueprint.type}`
),
action: () => this._createNew(blueprint),
},
@ -324,7 +325,7 @@ class HaBlueprintOverview extends LitElement {
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
const blueprint = this._processedBlueprints(this.blueprints).find(
(b) => b.fullpath === ev.detail.id
);
)!;
if (blueprint.error) {
showAlertDialog(this, {
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 { mdiCancel, mdiFilterVariant, mdiPlus } from "@mdi/js";
import {
css,
CSSResultGroup,
html,
LitElement,
nothing,
TemplateResult,
css,
html,
nothing,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { HASSDomEvent } from "../../../common/dom/fire_event";
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import {
protocolIntegrationPicked,
PROTOCOL_INTEGRATIONS,
protocolIntegrationPicked,
} from "../../../common/integrations/protocolIntegrationPicked";
import { navigate } from "../../../common/navigate";
import { LocalizeFunc } from "../../../common/translations/localize";
import { computeRTL } from "../../../common/util/compute_rtl";
import {
DataTableColumnContainer,
DataTableRowData,
RowClickedEvent,
} from "../../../components/data-table/ha-data-table";
import "../../../components/entity/ha-battery-icon";
@ -33,9 +32,9 @@ import "../../../components/ha-icon-button";
import { AreaRegistryEntry } from "../../../data/area_registry";
import { ConfigEntry, sortConfigEntries } from "../../../data/config_entries";
import {
computeDeviceName,
DeviceEntityLookup,
DeviceRegistryEntry,
computeDeviceName,
} from "../../../data/device_registry";
import {
EntityRegistryEntry,
@ -231,7 +230,7 @@ export class HaConfigDeviceDashboard extends LitElement {
outputDevices = outputDevices.filter((device) => !device.disabled_by);
}
outputDevices = outputDevices.map((device) => {
const formattedOutputDevices = outputDevices.map((device) => {
const deviceEntries = sortConfigEntries(
device.config_entries
.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 {
devicesOutput: outputDevices,
devicesOutput: formattedOutputDevices,
filteredConfigEntry: filterConfigEntry,
filteredDomains,
};
}
);
private _columns = memoizeOne(
(narrow: boolean, showDisabled: boolean): DataTableColumnContainer => {
const columns: DataTableColumnContainer = {
icon: {
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,
})}
/>`
: "",
},
};
private _columns = memoizeOne((narrow: boolean, showDisabled: boolean) => {
type DeviceItem = ReturnType<
typeof this._devicesAndFilterDomains
>["devicesOutput"][number];
if (narrow) {
columns.name = {
title: this.hass.localize(
"ui.panel.config.devices.data_table.device"
),
main: true,
sortable: true,
filterable: true,
direction: "asc",
grows: true,
template: (name, device: DataTableRowData) => html`
${name}
<div class="secondary">${device.area} | ${device.integration}</div>
`,
};
} else {
columns.name = {
title: this.hass.localize(
"ui.panel.config.devices.data_table.device"
),
main: true,
sortable: true,
filterable: true,
grows: true,
direction: "asc",
};
}
const columns: DataTableColumnContainer<DeviceItem> = {
icon: {
title: "",
type: "icon",
template: (device) =>
device.domains.length
? html`<img
alt=""
referrerpolicy="no-referrer"
src=${brandsUrl({
domain: device.domains[0],
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
})}
/>`
: "",
},
};
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"),
if (narrow) {
columns.name = {
title: this.hass.localize("ui.panel.config.devices.data_table.device"),
main: true,
sortable: true,
filterable: true,
type: "numeric",
width: narrow ? "95px" : "15%",
maxWidth: "95px",
valueColumn: "battery_level",
template: (batteryEntityPair: DeviceRowData["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``;
},
direction: "asc",
grows: true,
template: (device) => html`
${device.name}
<div class="secondary">${device.area} | ${device.integration}</div>
`,
};
} else {
columns.name = {
title: this.hass.localize("ui.panel.config.devices.data_table.device"),
main: true,
sortable: true,
filterable: true,
grows: true,
direction: "asc",
};
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) {
if (changedProps.has("_searchParms")) {

View File

@ -183,7 +183,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
"ui.panel.config.entities.picker.headers.state_icon"
),
type: "icon",
template: (_, entry: EntityRow) => html`
template: (entry) => html`
<ha-state-icon
title=${ifDefined(entry.entity?.state)}
slot="item-icon"
@ -201,12 +201,12 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
direction: "asc",
grows: true,
template: narrow
? (name, entity: EntityRow) => html`
${name}<br />
? (entry) => html`
${entry.name}<br />
<div class="secondary">
${entity.entity_id} |
${this.hass.localize(`component.${entity.platform}.title`) ||
entity.platform}
${entry.entity_id} |
${this.hass.localize(`component.${entry.platform}.title`) ||
entry.platform}
</div>
`
: undefined,
@ -228,8 +228,9 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
sortable: true,
filterable: true,
width: "20%",
template: (platform) =>
this.hass.localize(`component.${platform}.title`) || platform,
template: (entry) =>
this.hass.localize(`component.${entry.platform}.title`) ||
entry.platform,
},
area: {
title: this.hass.localize(
@ -248,10 +249,12 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
hidden: narrow || !showDisabled,
filterable: true,
width: "15%",
template: (disabled_by: EntityRegistryEntry["disabled_by"]) =>
disabled_by === null
template: (entry) =>
entry.disabled_by === null
? "—"
: this.hass.localize(`config_entry.disabled_by.${disabled_by}`),
: this.hass.localize(
`config_entry.disabled_by.${entry.disabled_by}`
),
},
status: {
title: this.hass.localize(
@ -261,11 +264,11 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
sortable: true,
filterable: true,
width: "68px",
template: (_status, entity: EntityRow) =>
entity.unavailable ||
entity.disabled_by ||
entity.hidden_by ||
entity.readonly
template: (entry) =>
entry.unavailable ||
entry.disabled_by ||
entry.hidden_by ||
entry.readonly
? html`
<div
tabindex="0"
@ -273,32 +276,32 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
>
<ha-svg-icon
style=${styleMap({
color: entity.unavailable ? "var(--error-color)" : "",
color: entry.unavailable ? "var(--error-color)" : "",
})}
.path=${entity.restored
.path=${entry.restored
? mdiRestoreAlert
: entity.unavailable
: entry.unavailable
? mdiAlertCircle
: entity.disabled_by
: entry.disabled_by
? mdiCancel
: entity.hidden_by
: entry.hidden_by
? mdiEyeOff
: mdiPencilOff}
></ha-svg-icon>
<simple-tooltip animation-delay="0" position="left">
${entity.restored
${entry.restored
? this.hass.localize(
"ui.panel.config.entities.picker.status.restored"
)
: entity.unavailable
: entry.unavailable
? this.hass.localize(
"ui.panel.config.entities.picker.status.unavailable"
)
: entity.disabled_by
: entry.disabled_by
? this.hass.localize(
"ui.panel.config.entities.picker.status.disabled"
)
: entity.hidden_by
: entry.hidden_by
? this.hass.localize(
"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 { computeStateDomain } from "../../../common/entity/compute_state_domain";
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 {
DataTableColumnContainer,
@ -27,6 +30,7 @@ import {
} from "../../../data/entity_registry";
import { domainToName } from "../../../data/integration";
import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow";
import { showOptionsFlowDialog } from "../../../dialogs/config-flow/show-dialog-options-flow";
import {
showAlertDialog,
showConfirmationDialog,
@ -38,9 +42,19 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { HomeAssistant, Route } from "../../../types";
import { configSections } from "../ha-panel-config";
import "../integrations/ha-integration-overflow-menu";
import { HelperDomain, isHelperDomain } from "./const";
import { isHelperDomain } from "./const";
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.
const groupByOne = <T>(
@ -108,16 +122,16 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
private _columns = memoizeOne(
(narrow: boolean, localize: LocalizeFunc): DataTableColumnContainer => {
const columns: DataTableColumnContainer = {
const columns: DataTableColumnContainer<HelperItem> = {
icon: {
title: "",
label: localize("ui.panel.config.helpers.picker.headers.icon"),
type: "icon",
template: (icon, helper: any) =>
template: (helper) =>
helper.entity
? html`<ha-state-icon .state=${helper.entity}></ha-state-icon>`
: html`<ha-svg-icon
.path=${icon}
.path=${helper.icon}
style="color: var(--error-color)"
></ha-svg-icon>`,
},
@ -128,10 +142,10 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
filterable: true,
grows: true,
direction: "asc",
template: (name, item: any) => html`
${name}
template: (helper) => html`
${helper.name}
${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,
width: "25%",
filterable: true,
template: (type: HelperDomain, row) =>
row.configEntry
? domainToName(localize, type)
template: (helper) =>
helper.configEntry
? domainToName(localize, helper.type)
: html`
${localize(`ui.panel.config.helpers.types.${type}`) || type}
${localize(
`ui.panel.config.helpers.types.${helper.type}` as LocalizeKeys
) || helper.type}
`,
};
columns.editable = {
@ -162,8 +178,8 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
"ui.panel.config.helpers.picker.headers.editable"
),
type: "icon",
template: (editable) => html`
${!editable
template: (helper) => html`
${!helper.editable
? html`
<div
tabindex="0"
@ -189,7 +205,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
stateItems: HassEntity[],
entityEntries: Record<string, EntityRegistryEntry>,
configEntries: Record<string, ConfigEntry>
) => {
): HelperItem[] => {
const configEntriesCopy = { ...configEntries };
const states = stateItems.map((entityState) => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,16 +7,15 @@ import {
mdiPlus,
mdiTransitConnection,
} 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 { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { formatShortDateTime } from "../../../common/datetime/format_date_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 { navigate } from "../../../common/navigate";
import { computeRTL } from "../../../common/util/compute_rtl";
@ -29,13 +28,18 @@ import "../../../components/ha-fab";
import "../../../components/ha-icon-button";
import "../../../components/ha-icon-overflow-menu";
import "../../../components/ha-svg-icon";
import { fetchBlueprints } from "../../../data/blueprint";
import { UNAVAILABLE } from "../../../data/entity";
import { EntityRegistryEntry } from "../../../data/entity_registry";
import {
ScriptEntity,
deleteScript,
fetchScriptFileConfig,
getScriptStateConfig,
showScriptEditor,
triggerScript,
} from "../../../data/script";
import { findRelated } from "../../../data/search";
import {
showAlertDialog,
showConfirmationDialog,
@ -45,18 +49,18 @@ import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
import { showToast } from "../../../util/toast";
import { configSections } from "../ha-panel-config";
import { showNewAutomationDialog } from "../automation/show-dialog-new-automation";
import { EntityRegistryEntry } from "../../../data/entity_registry";
import { findRelated } from "../../../data/search";
import { fetchBlueprints } from "../../../data/blueprint";
import { UNAVAILABLE } from "../../../data/entity";
import { configSections } from "../ha-panel-config";
type ScriptItem = ScriptEntity & {
name: string;
};
@customElement("ha-script-picker")
class HaScriptPicker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public scripts!: HassEntity[];
@property() public scripts!: ScriptEntity[];
@property() public isWide!: boolean;
@ -75,7 +79,10 @@ class HaScriptPicker extends LitElement {
@state() private _filterValue?;
private _scripts = memoizeOne(
(scripts: HassEntity[], filteredScripts?: string[] | null) => {
(
scripts: ScriptEntity[],
filteredScripts?: string[] | null
): ScriptItem[] => {
if (filteredScripts === null) {
return [];
}
@ -93,126 +100,136 @@ class HaScriptPicker extends LitElement {
}
);
private _columns = memoizeOne((narrow, _locale): DataTableColumnContainer => {
const columns: DataTableColumnContainer = {
icon: {
title: "",
label: this.hass.localize(
"ui.panel.config.script.picker.headers.state"
),
type: "icon",
template: (_icon, script) =>
html`<ha-state-icon
.state=${script}
style=${styleMap({
color:
script.state === UNAVAILABLE ? "var(--error-color)" : "unset",
})}
></ha-state-icon>`,
},
name: {
title: this.hass.localize("ui.panel.config.script.picker.headers.name"),
main: true,
sortable: true,
filterable: true,
direction: "asc",
grows: true,
template: narrow
? (name, script: any) => {
const date = new Date(script.attributes.last_triggered);
const now = new Date();
const dayDifference = differenceInDays(now, date);
return html`
${name}
<div class="secondary">
${this.hass.localize("ui.card.automation.last_triggered")}:
${script.attributes.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")}
</div>
`;
}
: undefined,
},
};
if (!narrow) {
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")}
`;
private _columns = memoizeOne(
(narrow, _locale): DataTableColumnContainer<ScriptItem> => {
const columns: DataTableColumnContainer = {
icon: {
title: "",
label: this.hass.localize(
"ui.panel.config.script.picker.headers.state"
),
type: "icon",
template: (script) =>
html`<ha-state-icon
.state=${script}
style=${styleMap({
color:
script.state === UNAVAILABLE ? "var(--error-color)" : "unset",
})}
></ha-state-icon>`,
},
name: {
title: this.hass.localize(
"ui.panel.config.script.picker.headers.name"
),
main: true,
sortable: true,
filterable: true,
direction: "asc",
grows: true,
template: narrow
? (script) => {
const date = new Date(script.last_triggered);
const now = new Date();
const dayDifference = differenceInDays(now, date);
return html`
${script.name}
<div class="secondary">
${this.hass.localize("ui.card.automation.last_triggered")}:
${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")}
</div>
`;
}
: undefined,
},
};
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 {
return html`

View File

@ -36,6 +36,7 @@ import { showTagDetailDialog } from "./show-dialog-tag-detail";
import "./tag-image";
export interface TagRowData extends Tag {
display_name: string;
last_scanned_datetime: Date | null;
}
@ -55,94 +56,90 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
return this.hass.auth.external?.config.canWriteTag;
}
private _columns = memoizeOne(
(narrow: boolean, _language): DataTableColumnContainer => {
const columns: DataTableColumnContainer = {
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 = {
private _columns = memoizeOne((narrow: boolean, _language) => {
const columns: DataTableColumnContainer<TagRowData> = {
icon: {
title: "",
type: "icon-button",
template: (_automation, tag: any) =>
html` <ha-icon-button
.tag=${tag}
@click=${this._handleAutomationClick}
.label=${this.hass.localize(
"ui.panel.config.tag.create_automation"
)}
.path=${mdiRobot}
></ha-icon-button>`,
label: this.hass.localize("ui.panel.config.tag.headers.icon"),
type: "icon",
template: (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: (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[] =>
tags.map((tag) => ({

View File

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

View File

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

View File

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

View File

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