Reorder config entries in device pages (#17105)

This commit is contained in:
Paul Bottein 2023-06-30 17:15:49 +02:00 committed by GitHub
parent d6f8941098
commit 23ac7501b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 173 additions and 102 deletions

View File

@ -1,6 +1,6 @@
import { UnsubscribeFunc } from "home-assistant-js-websocket"; import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import { HomeAssistant } from "../types"; import type { HomeAssistant } from "../types";
import { IntegrationType } from "./integration"; import type { IntegrationManifest, IntegrationType } from "./integration";
export interface ConfigEntry { export interface ConfigEntry {
entry_id: string; entry_id: string;
@ -143,3 +143,23 @@ export const enableConfigEntry = (hass: HomeAssistant, configEntryId: string) =>
entry_id: configEntryId, entry_id: configEntryId,
disabled_by: null, disabled_by: null,
}); });
export const sortConfigEntries = (
configEntries: ConfigEntry[],
manifestLookup: { [domain: string]: IntegrationManifest }
): ConfigEntry[] => {
const sortedConfigEntries = [...configEntries];
const getScore = (entry: ConfigEntry) => {
const manifest = manifestLookup[entry.domain] as
| IntegrationManifest
| undefined;
const isHelper = manifest?.integration_type === "helper";
return isHelper ? -1 : 1;
};
const configEntriesCompare = (a: ConfigEntry, b: ConfigEntry) =>
getScore(b) - getScore(a);
return sortedConfigEntries.sort(configEntriesCompare);
};

View File

@ -41,6 +41,7 @@ import {
ConfigEntry, ConfigEntry,
disableConfigEntry, disableConfigEntry,
DisableConfigEntryResult, DisableConfigEntryResult,
sortConfigEntries,
} from "../../../data/config_entries"; } from "../../../data/config_entries";
import { import {
computeDeviceName, computeDeviceName,
@ -60,7 +61,7 @@ import {
findBatteryEntity, findBatteryEntity,
updateEntityRegistryEntry, updateEntityRegistryEntry,
} from "../../../data/entity_registry"; } from "../../../data/entity_registry";
import { domainToName } from "../../../data/integration"; import { IntegrationManifest, domainToName } from "../../../data/integration";
import { SceneEntities, showSceneEditor } from "../../../data/scene"; import { SceneEntities, showSceneEditor } from "../../../data/scene";
import { findRelated, RelatedResult } from "../../../data/search"; import { findRelated, RelatedResult } from "../../../data/search";
import { import {
@ -115,6 +116,8 @@ export class HaConfigDevicePage extends LitElement {
@property({ attribute: false }) public areas!: AreaRegistryEntry[]; @property({ attribute: false }) public areas!: AreaRegistryEntry[];
@property({ attribute: false }) public manifests!: IntegrationManifest[];
@property() public deviceId!: string; @property() public deviceId!: string;
@property({ type: Boolean, reflect: true }) public narrow!: boolean; @property({ type: Boolean, reflect: true }) public narrow!: boolean;
@ -145,14 +148,24 @@ export class HaConfigDevicePage extends LitElement {
); );
private _integrations = memoizeOne( private _integrations = memoizeOne(
(device: DeviceRegistryEntry, entries: ConfigEntry[]): ConfigEntry[] => { (
device: DeviceRegistryEntry,
entries: ConfigEntry[],
manifests: IntegrationManifest[]
): ConfigEntry[] => {
const entryLookup: { [entryId: string]: ConfigEntry } = {}; const entryLookup: { [entryId: string]: ConfigEntry } = {};
for (const entry of entries) { for (const entry of entries) {
entryLookup[entry.entry_id] = entry; entryLookup[entry.entry_id] = entry;
} }
return device.config_entries const manifestLookup: { [domain: string]: IntegrationManifest } = {};
.map((entry) => entryLookup[entry]) for (const manifest of manifests) {
.filter(Boolean); manifestLookup[manifest.domain] = manifest;
}
const deviceEntries = device.config_entries
.filter((entId) => entId in entryLookup)
.map((entry) => entryLookup[entry]);
return sortConfigEntries(deviceEntries, manifestLookup);
} }
); );
@ -292,7 +305,11 @@ export class HaConfigDevicePage extends LitElement {
} }
const deviceName = computeDeviceName(device, this.hass); const deviceName = computeDeviceName(device, this.hass);
const integrations = this._integrations(device, this.entries); const integrations = this._integrations(
device,
this.entries,
this.manifests
);
const entities = this._entities(this.deviceId, this.entities); const entities = this._entities(this.deviceId, this.entities);
const entitiesByCategory = this._entitiesByCategory(entities); const entitiesByCategory = this._entitiesByCategory(entities);
const batteryEntity = this._batteryEntity(entities); const batteryEntity = this._batteryEntity(entities);
@ -920,7 +937,7 @@ export class HaConfigDevicePage extends LitElement {
} }
let links = await Promise.all( let links = await Promise.all(
this._integrations(device, this.entries).map( this._integrations(device, this.entries, this.manifests).map(
async (entry): Promise<boolean | { link: string; domain: string }> => { async (entry): Promise<boolean | { link: string; domain: string }> => {
if (entry.state !== "loaded") { if (entry.state !== "loaded") {
return false; return false;
@ -983,50 +1000,55 @@ export class HaConfigDevicePage extends LitElement {
} }
const buttons: DeviceAction[] = []; const buttons: DeviceAction[] = [];
this._integrations(device, this.entries).forEach((entry) => { this._integrations(device, this.entries, this.manifests).forEach(
if (entry.state !== "loaded" || !entry.supports_remove_device) { (entry) => {
return; if (entry.state !== "loaded" || !entry.supports_remove_device) {
return;
}
buttons.push({
action: async () => {
const confirmed = await showConfirmationDialog(this, {
text:
this._integrations(device, this.entries, this.manifests)
.length > 1
? this.hass.localize(
`ui.panel.config.devices.confirm_delete_integration`,
{
integration: domainToName(
this.hass.localize,
entry.domain
),
}
)
: this.hass.localize(
`ui.panel.config.devices.confirm_delete`
),
});
if (!confirmed) {
return;
}
await removeConfigEntryFromDevice(
this.hass!,
this.deviceId,
entry.entry_id
);
},
classes: "warning",
icon: mdiDelete,
label:
this._integrations(device, this.entries, this.manifests).length > 1
? this.hass.localize(
`ui.panel.config.devices.delete_device_integration`,
{
integration: domainToName(this.hass.localize, entry.domain),
}
)
: this.hass.localize(`ui.panel.config.devices.delete_device`),
});
} }
buttons.push({ );
action: async () => {
const confirmed = await showConfirmationDialog(this, {
text:
this._integrations(device, this.entries).length > 1
? this.hass.localize(
`ui.panel.config.devices.confirm_delete_integration`,
{
integration: domainToName(
this.hass.localize,
entry.domain
),
}
)
: this.hass.localize(`ui.panel.config.devices.confirm_delete`),
});
if (!confirmed) {
return;
}
await removeConfigEntryFromDevice(
this.hass!,
this.deviceId,
entry.entry_id
);
},
classes: "warning",
icon: mdiDelete,
label:
this._integrations(device, this.entries).length > 1
? this.hass.localize(
`ui.panel.config.devices.delete_device_integration`,
{
integration: domainToName(this.hass.localize, entry.domain),
}
)
: this.hass.localize(`ui.panel.config.devices.delete_device`),
});
});
if (buttons.length > 0) { if (buttons.length > 0) {
this._deleteButtons = buttons; this._deleteButtons = buttons;
@ -1061,9 +1083,11 @@ export class HaConfigDevicePage extends LitElement {
}); });
} }
const domains = this._integrations(device, this.entries).map( const domains = this._integrations(
(int) => int.domain device,
); this.entries,
this.manifests
).map((int) => int.domain);
if (domains.includes("mqtt")) { if (domains.includes("mqtt")) {
const mqtt = await import( const mqtt = await import(
@ -1103,9 +1127,11 @@ export class HaConfigDevicePage extends LitElement {
const deviceAlerts: DeviceAlert[] = []; const deviceAlerts: DeviceAlert[] = [];
const domains = this._integrations(device, this.entries).map( const domains = this._integrations(
(int) => int.domain device,
); this.entries,
this.manifests
).map((int) => int.domain);
if (domains.includes("zwave_js")) { if (domains.includes("zwave_js")) {
const zwave = await import( const zwave = await import(

View File

@ -25,7 +25,7 @@ import "../../../components/ha-check-list-item";
import "../../../components/ha-fab"; import "../../../components/ha-fab";
import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button";
import { AreaRegistryEntry } from "../../../data/area_registry"; import { AreaRegistryEntry } from "../../../data/area_registry";
import { ConfigEntry } from "../../../data/config_entries"; import { ConfigEntry, sortConfigEntries } from "../../../data/config_entries";
import { import {
computeDeviceName, computeDeviceName,
DeviceEntityLookup, DeviceEntityLookup,
@ -36,7 +36,7 @@ import {
findBatteryChargingEntity, findBatteryChargingEntity,
findBatteryEntity, findBatteryEntity,
} from "../../../data/entity_registry"; } from "../../../data/entity_registry";
import { domainToName } from "../../../data/integration"; import { IntegrationManifest, domainToName } from "../../../data/integration";
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";
@ -68,6 +68,8 @@ export class HaConfigDeviceDashboard extends LitElement {
@property() public areas!: AreaRegistryEntry[]; @property() public areas!: AreaRegistryEntry[];
@property() public manifests!: IntegrationManifest[];
@property() public route!: Route; @property() public route!: Route;
@state() private _searchParms = new URLSearchParams(window.location.search); @state() private _searchParms = new URLSearchParams(window.location.search);
@ -149,6 +151,7 @@ export class HaConfigDeviceDashboard extends LitElement {
entries: ConfigEntry[], entries: ConfigEntry[],
entities: EntityRegistryEntry[], entities: EntityRegistryEntry[],
areas: AreaRegistryEntry[], areas: AreaRegistryEntry[],
manifests: IntegrationManifest[],
filters: URLSearchParams, filters: URLSearchParams,
showDisabled: boolean, showDisabled: boolean,
localize: LocalizeFunc localize: LocalizeFunc
@ -186,6 +189,11 @@ export class HaConfigDeviceDashboard extends LitElement {
areaLookup[area.area_id] = area; areaLookup[area.area_id] = area;
} }
const manifestLookup: { [domain: string]: IntegrationManifest } = {};
for (const manifest of manifests) {
manifestLookup[manifest.domain] = manifest;
}
let filterConfigEntry: ConfigEntry | undefined; let filterConfigEntry: ConfigEntry | undefined;
const filteredDomains = new Set<string>(); const filteredDomains = new Set<string>();
@ -217,47 +225,51 @@ export class HaConfigDeviceDashboard extends LitElement {
outputDevices = outputDevices.filter((device) => !device.disabled_by); outputDevices = outputDevices.filter((device) => !device.disabled_by);
} }
outputDevices = outputDevices.map((device) => ({ outputDevices = outputDevices.map((device) => {
...device, const deviceEntries = sortConfigEntries(
name: computeDeviceName( device.config_entries
device, .filter((entId) => entId in entryLookup)
this.hass, .map((entId) => entryLookup[entId]),
deviceEntityLookup[device.id] manifestLookup
), );
model: return {
device.model || ...device,
`<${localize("ui.panel.config.devices.data_table.unknown")}>`, name: computeDeviceName(
manufacturer: device,
device.manufacturer || this.hass,
`<${localize("ui.panel.config.devices.data_table.unknown")}>`, deviceEntityLookup[device.id]
area: ),
device.area_id && areaLookup[device.area_id] model:
? areaLookup[device.area_id].name device.model ||
: "—", `<${localize("ui.panel.config.devices.data_table.unknown")}>`,
integration: device.config_entries.length manufacturer:
? device.config_entries device.manufacturer ||
.filter((entId) => entId in entryLookup) `<${localize("ui.panel.config.devices.data_table.unknown")}>`,
.map( area:
(entId) => device.area_id && areaLookup[device.area_id]
localize(`component.${entryLookup[entId].domain}.title`) || ? areaLookup[device.area_id].name
entryLookup[entId].domain : "—",
) integration: deviceEntries.length
.join(", ") ? deviceEntries
: this.hass.localize( .map(
"ui.panel.config.devices.data_table.no_integration" (entry) =>
), localize(`component.${entry.domain}.title`) || entry.domain
domains: device.config_entries )
.filter((entId) => entId in entryLookup) .join(", ")
.map((entId) => entryLookup[entId].domain), : this.hass.localize(
battery_entity: [ "ui.panel.config.devices.data_table.no_integration"
this._batteryEntity(device.id, deviceEntityLookup), ),
this._batteryChargingEntity(device.id, deviceEntityLookup), domains: deviceEntries.map((entry) => entry.domain),
], battery_entity: [
battery_level: this._batteryEntity(device.id, deviceEntityLookup),
this.hass.states[ this._batteryChargingEntity(device.id, deviceEntityLookup),
this._batteryEntity(device.id, deviceEntityLookup) || "" ],
]?.state, battery_level:
})); this.hass.states[
this._batteryEntity(device.id, deviceEntityLookup) || ""
]?.state,
};
});
this._numHiddenDevices = startLength - outputDevices.length; this._numHiddenDevices = startLength - outputDevices.length;
return { return {
@ -429,6 +441,7 @@ export class HaConfigDeviceDashboard extends LitElement {
this.entries, this.entries,
this.entities, this.entities,
this.areas, this.areas,
this.manifests,
this._searchParms, this._searchParms,
this._showDisabled, this._showDisabled,
this.hass.localize this.hass.localize
@ -565,6 +578,7 @@ export class HaConfigDeviceDashboard extends LitElement {
this.entries, this.entries,
this.entities, this.entities,
this.areas, this.areas,
this.manifests,
this._searchParms, this._searchParms,
this._showDisabled, this._showDisabled,
this.hass.localize this.hass.localize

View File

@ -14,6 +14,10 @@ import {
EntityRegistryEntry, EntityRegistryEntry,
subscribeEntityRegistry, subscribeEntityRegistry,
} from "../../../data/entity_registry"; } from "../../../data/entity_registry";
import {
IntegrationManifest,
fetchIntegrationManifests,
} from "../../../data/integration";
import { import {
HassRouterPage, HassRouterPage,
RouterOptions, RouterOptions,
@ -47,6 +51,8 @@ class HaConfigDevices extends HassRouterPage {
@state() private _configEntries: ConfigEntry[] = []; @state() private _configEntries: ConfigEntry[] = [];
@state() private _manifests: IntegrationManifest[] = [];
@state() @state()
private _entityRegistryEntries: EntityRegistryEntry[] = []; private _entityRegistryEntries: EntityRegistryEntry[] = [];
@ -99,6 +105,7 @@ class HaConfigDevices extends HassRouterPage {
pageEl.entities = this._entityRegistryEntries; pageEl.entities = this._entityRegistryEntries;
pageEl.entries = this._configEntries; pageEl.entries = this._configEntries;
pageEl.manifests = this._manifests;
pageEl.devices = this._deviceRegistryEntries; pageEl.devices = this._deviceRegistryEntries;
pageEl.areas = this._areas; pageEl.areas = this._areas;
pageEl.narrow = this.narrow; pageEl.narrow = this.narrow;
@ -111,6 +118,10 @@ class HaConfigDevices extends HassRouterPage {
getConfigEntries(this.hass).then((configEntries) => { getConfigEntries(this.hass).then((configEntries) => {
this._configEntries = configEntries; this._configEntries = configEntries;
}); });
fetchIntegrationManifests(this.hass).then((manifests) => {
this._manifests = manifests;
});
if (this._unsubs) { if (this._unsubs) {
return; return;
} }