Add area, device and entity registry

This commit is contained in:
Zack 2022-04-15 23:06:17 -05:00
parent 43246029a1
commit 6d3d194f42
2 changed files with 151 additions and 101 deletions

View File

@ -1,44 +1,43 @@
import { import {
mdiServerNetwork,
mdiNavigationVariantOutline,
mdiEarth, mdiEarth,
mdiNavigationVariantOutline,
mdiReload, mdiReload,
mdiServerNetwork,
} from "@mdi/js"; } from "@mdi/js";
import { canShowPage } from "../common/config/can_show_page"; import { canShowPage } from "../common/config/can_show_page";
import { componentsWithService } from "../common/config/components_with_service"; import { componentsWithService } from "../common/config/components_with_service";
import { fireEvent } from "../common/dom/fire_event";
import { computeDomain } from "../common/entity/compute_domain"; import { computeDomain } from "../common/entity/compute_domain";
import { computeStateDisplay } from "../common/entity/compute_state_display"; import { computeStateDisplay } from "../common/entity/compute_state_display";
import { computeStateName } from "../common/entity/compute_state_name"; import { computeStateName } from "../common/entity/compute_state_name";
import { domainIcon } from "../common/entity/domain_icon"; import { domainIcon } from "../common/entity/domain_icon";
import { navigate } from "../common/navigate";
import { caseInsensitiveStringCompare } from "../common/string/compare"; import { caseInsensitiveStringCompare } from "../common/string/compare";
import { ScorableTextItem } from "../common/string/filter/sequence-matching"; import { ScorableTextItem } from "../common/string/filter/sequence-matching";
import {
ConfirmationDialogParams,
showConfirmationDialog,
} from "../dialogs/generic/show-dialog-box";
import { QuickBar } from "../dialogs/quick-bar/ha-quick-bar"; import { QuickBar } from "../dialogs/quick-bar/ha-quick-bar";
import { PageNavigation } from "../layouts/hass-tabs-subpage"; import { PageNavigation } from "../layouts/hass-tabs-subpage";
import { configSections } from "../panels/config/ha-panel-config"; import { configSections } from "../panels/config/ha-panel-config";
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
import { AreaRegistryEntry } from "./area_registry";
import { computeDeviceName, DeviceRegistryEntry } from "./device_registry";
import { EntityRegistryEntry } from "./entity_registry";
import { domainToName } from "./integration"; import { domainToName } from "./integration";
import { getPanelNameTranslationKey } from "./panel"; import { getPanelNameTranslationKey } from "./panel";
export interface QuickBarItem extends ScorableTextItem { export interface QuickBarItem extends ScorableTextItem {
primaryText: string; primaryText: string;
primaryTextAlt?: string; primaryTextAlt?: string;
secondaryText: string; secondaryText?: string;
metaText?: string;
categoryKey: categoryKey:
| "reload" | "reload"
| "navigation" | "navigation"
| "server_control" | "server_control"
| "entity" | "entity"
| "suggestion"; | "suggestion";
action(data?: any): void; actionData: string | string[];
iconPath?: string; iconPath?: string;
icon?: string; icon?: string;
path?: string; path?: string;
isSuggestion?: boolean;
} }
export type NavigationInfo = PageNavigation & export type NavigationInfo = PageNavigation &
@ -47,23 +46,34 @@ export type NavigationInfo = PageNavigation &
export type BaseNavigationCommand = Pick<QuickBarItem, "primaryText" | "path">; export type BaseNavigationCommand = Pick<QuickBarItem, "primaryText" | "path">;
export const generateEntityItems = ( export const generateEntityItems = (
element: QuickBar, hass: HomeAssistant,
hass: HomeAssistant entities: { [entityId: string]: EntityRegistryEntry },
devices: { [deviceId: string]: DeviceRegistryEntry },
areas: { [areaId: string]: AreaRegistryEntry }
): QuickBarItem[] => ): QuickBarItem[] =>
Object.keys(hass.states) Object.keys(hass.states)
.map((entityId) => { .map((entityId) => {
const entityState = hass.states[entityId]; const entityState = hass.states[entityId];
const entity = entities[entityId];
const deviceName = entity?.device_id
? computeDeviceName(devices[entity.device_id], hass)
: undefined;
const entityItem = { const entityItem = {
primaryText: computeStateName(entityState), primaryText: computeStateName(entityState),
primaryTextAlt: entityId, primaryTextAlt: computeStateDisplay(
secondaryText: hass.userData?.showAdvanced hass.localize,
? entityId entityState,
: computeStateDisplay(hass.localize, entityState, hass.locale), hass.locale
),
secondaryText:
(deviceName ? `${deviceName} | ` : "") +
(hass.userData?.showAdvanced ? entityId : ""),
metaText: entity?.area_id ? areas[entity.area_id].name : undefined,
icon: entityState.attributes.icon, icon: entityState.attributes.icon,
iconPath: entityState.attributes.icon iconPath: entityState.attributes.icon
? undefined ? undefined
: domainIcon(computeDomain(entityId), entityState), : domainIcon(computeDomain(entityId), entityState),
action: () => fireEvent(element, "hass-more-info", { entityId }), actionData: entityId,
categoryKey: "entity" as const, categoryKey: "entity" as const,
}; };
@ -95,21 +105,21 @@ export const generateReloadCommands = (hass: HomeAssistant): QuickBarItem[] => {
"domain", "domain",
domainToName(hass.localize, domain) domainToName(hass.localize, domain)
), ),
action: () => hass.callService(domain, "reload"), actionData: [domain, "reload"],
secondaryText: "Reload changes made to the domain file", secondaryText: "Reload changes made to the domain file",
})); }));
// Add "frontend.reload_themes" // Add "frontend.reload_themes"
commands.push({ commands.push({
primaryText: hass.localize("ui.dialogs.quick-bar.commands.reload.themes"), primaryText: hass.localize("ui.dialogs.quick-bar.commands.reload.themes"),
action: () => hass.callService("frontend", "reload_themes"), actionData: ["frontend", "reload_themes"],
secondaryText: "Reload changes made to themes.yaml", secondaryText: "Reload changes made to themes.yaml",
}); });
// Add "homeassistant.reload_core_config" // Add "homeassistant.reload_core_config"
commands.push({ commands.push({
primaryText: hass.localize("ui.dialogs.quick-bar.commands.reload.core"), primaryText: hass.localize("ui.dialogs.quick-bar.commands.reload.core"),
action: () => hass.callService("homeassistant", "reload_core_config"), actionData: ["homeassistant", "reload_core_config"],
secondaryText: "Reload changes made to configuration.yaml", secondaryText: "Reload changes made to configuration.yaml",
}); });
@ -141,23 +151,19 @@ export const generateServerControlCommands = (
hass.localize(`ui.dialogs.quick-bar.commands.server_control.${action}`) hass.localize(`ui.dialogs.quick-bar.commands.server_control.${action}`)
), ),
categoryKey, categoryKey,
action: () => hass.callService("homeassistant", action), actionData: action,
}; };
return generateConfirmationCommand( return {
element, ...item,
{ strings: [
...item, `${hass.localize(
strings: [ `ui.dialogs.quick-bar.commands.types.${categoryKey}`
`${hass.localize( )} ${item.primaryText}`,
`ui.dialogs.quick-bar.commands.types.${categoryKey}` ],
)} ${item.primaryText}`, secondaryText: "Control your server",
], iconPath: mdiServerNetwork,
secondaryText: "Control your server", };
iconPath: mdiServerNetwork,
},
hass.localize("ui.dialogs.generic.ok")
);
}); });
}; };
@ -249,19 +255,6 @@ export const getNavigationInfoFromConfig = (
return undefined; return undefined;
}; };
export const generateConfirmationCommand = (
element: QuickBar,
item: QuickBarItem,
confirmText: ConfirmationDialogParams["confirmText"]
): QuickBarItem => ({
...item,
action: () =>
showConfirmationDialog(element, {
confirmText,
confirm: item.action,
}),
});
const finalizeNavigationCommands = ( const finalizeNavigationCommands = (
items: BaseNavigationCommand[], items: BaseNavigationCommand[],
hass: HomeAssistant hass: HomeAssistant
@ -273,7 +266,7 @@ const finalizeNavigationCommands = (
secondaryText: "Navigation", secondaryText: "Navigation",
iconPath: mdiEarth, iconPath: mdiEarth,
...item, ...item,
action: () => navigate(item.path!), actionData: item.path!,
}; };
return { return {

View File

@ -3,33 +3,50 @@ import "@material/mwc-list/mwc-list";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import type { ListItem } from "@material/mwc-list/mwc-list-item"; import type { ListItem } from "@material/mwc-list/mwc-list-item";
import { mdiClose, mdiMagnify } from "@mdi/js"; import { mdiClose, mdiMagnify } from "@mdi/js";
import { css, html, LitElement, TemplateResult } from "lit"; import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined"; import { ifDefined } from "lit/directives/if-defined";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { LocalStorage } from "../../common/decorators/local-storage"; import { LocalStorage } from "../../common/decorators/local-storage";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
import { navigate } from "../../common/navigate";
import { fuzzyFilterSort } from "../../common/string/filter/sequence-matching"; import { fuzzyFilterSort } from "../../common/string/filter/sequence-matching";
import { debounce } from "../../common/util/debounce"; import { debounce } from "../../common/util/debounce";
import "../../components/ha-chip";
import "../../components/ha-circular-progress"; import "../../components/ha-circular-progress";
import "../../components/ha-icon-button"; import "../../components/ha-icon-button";
import "../../components/ha-textfield"; import "../../components/ha-textfield";
import {
AreaRegistryEntry,
subscribeAreaRegistry,
} from "../../data/area_registry";
import {
DeviceRegistryEntry,
subscribeDeviceRegistry,
} from "../../data/device_registry";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../data/entity_registry";
import { import {
generateCommandItems, generateCommandItems,
generateEntityItems, generateEntityItems,
QuickBarItem, QuickBarItem,
} from "../../data/quick-bar"; } from "../../data/quick-bar";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import { import {
haStyle, haStyle,
haStyleDialog, haStyleDialog,
haStyleScrollbar, haStyleScrollbar,
} from "../../resources/styles"; } from "../../resources/styles";
import type { HomeAssistant } from "../../types"; import type { HomeAssistant } from "../../types";
import { showConfirmationDialog } from "../generic/show-dialog-box";
import { QuickBarParams } from "./show-dialog-quick-bar"; import { QuickBarParams } from "./show-dialog-quick-bar";
@customElement("ha-quick-bar") @customElement("ha-quick-bar")
export class QuickBar extends LitElement { export class QuickBar extends SubscribeMixin(LitElement) {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@state() private _items?: Array<QuickBarItem[]>; @state() private _items?: Array<QuickBarItem[]>;
@ -40,10 +57,6 @@ export class QuickBar extends LitElement {
@state() private _search = ""; @state() private _search = "";
@state() private _open = false;
@state() private _commandMode = false;
@state() private _opened = false; @state() private _opened = false;
@state() private _done = false; @state() private _done = false;
@ -52,6 +65,12 @@ export class QuickBar extends LitElement {
@state() private _hint?: string; @state() private _hint?: string;
@state() private _entities?: EntityRegistryEntry[];
@state() private _areas?: AreaRegistryEntry[];
@state() private _devices?: DeviceRegistryEntry[];
@query("ha-textfield", false) private _filterInputField?: HTMLElement; @query("ha-textfield", false) private _filterInputField?: HTMLElement;
// @ts-ignore // @ts-ignore
@ -74,12 +93,23 @@ export class QuickBar extends LitElement {
this._narrow = matchMedia( this._narrow = matchMedia(
"all and (max-width: 450px), all and (max-height: 500px)" "all and (max-width: 450px), all and (max-height: 500px)"
).matches; ).matches;
this._initializeItems(); }
this._opened = true;
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeAreaRegistry(this.hass.connection!, (areas) => {
this._areas = areas;
}),
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
this._devices = devices;
}),
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entities = entities;
}),
];
} }
public closeDialog() { public closeDialog() {
this._open = false;
this._opened = false; this._opened = false;
this._focusSet = false; this._focusSet = false;
this._filter = ""; this._filter = "";
@ -87,8 +117,23 @@ export class QuickBar extends LitElement {
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
} }
protected render() { protected willUpdate(changedProperties: PropertyValues): void {
if (!this._open) { super.willUpdate(changedProperties);
if (
(changedProperties.has("_entity") ||
changedProperties.has("_areas") ||
changedProperties.has("_devices")) &&
this._areas &&
this._devices &&
this._entities
) {
this._initializeItems();
this._opened = true;
}
}
protected render(): TemplateResult {
if (!this._opened) {
return html``; return html``;
} }
@ -176,28 +221,6 @@ export class QuickBar extends LitElement {
` `
: html` : html`
<mwc-list> <mwc-list>
<<<<<<< HEAD
${this._opened
? html`<lit-virtualizer
scroller
@keydown=${this._handleListItemKeyDown}
@rangechange=${this._handleRangeChanged}
@click=${this._handleItemClick}
class="ha-scrollbar"
style=${styleMap({
height: this._narrow
? "calc(100vh - 56px)"
: `${Math.min(
items.length * (this._commandMode ? 56 : 72) + 26,
500
)}px`,
})}
.items=${items}
.renderItem=${this._renderItem}
>
</lit-virtualizer>`
: ""}
=======
<lit-virtualizer <lit-virtualizer
scroller scroller
@keydown=${this._handleListItemKeyDown} @keydown=${this._handleListItemKeyDown}
@ -218,7 +241,6 @@ export class QuickBar extends LitElement {
.renderItem=${this._renderItem} .renderItem=${this._renderItem}
> >
</lit-virtualizer> </lit-virtualizer>
>>>>>>> 64654972a (Stash)
</mwc-list> </mwc-list>
`} `}
${this._hint ? html`<div class="hint">${this._hint}</div>` : ""} ${this._hint ? html`<div class="hint">${this._hint}</div>` : ""}
@ -238,14 +260,19 @@ export class QuickBar extends LitElement {
${index === 0 || item?.categoryKey !== previous?.categoryKey ${index === 0 || item?.categoryKey !== previous?.categoryKey
? html` ? html`
<div class="entry-title"> <div class="entry-title">
${this.hass.localize( ${item.isSuggestion
`ui.dialogs.quick-bar.commands.types.${item.categoryKey}` ? this.hass.localize(
)} "ui.dialogs.quick-bar.commands.types.suggestions"
)
: this.hass.localize(
`ui.dialogs.quick-bar.commands.types.${item.categoryKey}`
)}
</div> </div>
` `
: ""} : ""}
<mwc-list-item <mwc-list-item
.twoline=${item.secondaryText} .twoline=${Boolean(item.secondaryText)}
.hasMeta=${Boolean(item.metaText)}
.item=${item} .item=${item}
index=${ifDefined(index)} index=${ifDefined(index)}
graphic="icon" graphic="icon"
@ -268,27 +295,64 @@ export class QuickBar extends LitElement {
> >
` `
: ""} : ""}
${item.metaText
? html`<ha-chip slot="meta">${item.metaText}</ha-chip>`
: ""}
</mwc-list-item> </mwc-list-item>
</div> </div>
`; `;
}; };
private _initializeItems() { private _initializeItems() {
const deviceLookup: { [deviceId: string]: DeviceRegistryEntry } = {};
for (const device of this._devices!) {
deviceLookup[device.id] = device;
}
const entityLookup: { [entityId: string]: EntityRegistryEntry } = {};
for (const entity of this._entities!) {
entityLookup[entity.entity_id] = entity;
}
const areaLookup: { [areaId: string]: AreaRegistryEntry } = {};
for (const area of this._areas!) {
areaLookup[area.area_id] = area;
}
this._items = this._items || [ this._items = this._items || [
generateEntityItems(this, this.hass), generateEntityItems(this.hass, entityLookup, deviceLookup, areaLookup),
...generateCommandItems(this, this.hass), ...generateCommandItems(this, this.hass),
]; ];
} }
private async processItemAndCloseDialog(item: QuickBarItem, index: number) { private async processItemAndCloseDialog(item: QuickBarItem, index: number) {
if (!this._suggestions.includes(item)) { if (!this._suggestions.includes(item)) {
this._suggestions.unshift({ ...item, categoryKey: "suggestion" }); this._suggestions.unshift({ ...item, isSuggestion: true });
this._suggestions = this._suggestions.slice(0, 3); this._suggestions = this._suggestions.slice(0, 3);
} }
this._addSpinnerToItem(index); this._addSpinnerToItem(index);
await item.action(); switch (item.categoryKey) {
case "entity":
fireEvent(this, "hass-more-info", {
entityId: item.actionData as string,
});
break;
case "reload":
this.hass.callService(item.actionData[0], item.actionData[1]);
break;
case "navigation":
navigate(item.actionData as string);
break;
case "server_control":
showConfirmationDialog(this, {
confirmText: this.hass.localize("ui.dialogs.generic.ok"),
confirm: () =>
this.hass.callService("homeassistant", item.actionData as string),
});
break;
}
this.closeDialog(); this.closeDialog();
} }
@ -416,17 +480,6 @@ export class QuickBar extends LitElement {
this._getItemAtIndex(index)?.appendChild(spinner); this._getItemAtIndex(index)?.appendChild(spinner);
} }
private _getSuggestionsWithActions(): QuickBarItem[] {
return this._suggestions.map((item) => {
let action;
switch (item.categoryKey) {
case "entity":
action = () => fireEvent(this, "hass-more-info", {});
}
return { ...item, action };
});
}
static get styles() { static get styles() {
return [ return [
haStyleScrollbar, haStyleScrollbar,
@ -523,6 +576,10 @@ export class QuickBar extends LitElement {
lit-virtualizer { lit-virtualizer {
contain: size layout !important; contain: size layout !important;
} }
ha-chip {
margin-left: -48px;
}
`, `,
]; ];
} }