Skip to content

Commit

Permalink
Add device search to quick bar (#23095)
Browse files Browse the repository at this point in the history
* Add device search to quick bar

* Process code review
  • Loading branch information
jpbede authored Dec 3, 2024
1 parent c3942d2 commit e731f06
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 61 deletions.
171 changes: 124 additions & 47 deletions src/dialogs/quick-bar/ha-quick-bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import type { ListItem } from "@material/mwc-list/mwc-list-item";
import {
mdiClose,
mdiConsoleLine,
mdiDevices,
mdiEarth,
mdiMagnify,
mdiReload,
mdiServerNetwork,
} from "@mdi/js";
import type { TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { styleMap } from "lit/directives/style-map";
Expand Down Expand Up @@ -38,7 +39,7 @@ import { haStyleDialog, haStyleScrollbar } from "../../resources/styles";
import { loadVirtualizer } from "../../resources/virtualizer";
import type { HomeAssistant } from "../../types";
import { showConfirmationDialog } from "../generic/show-dialog-box";
import type { QuickBarParams } from "./show-dialog-quick-bar";
import { QuickBarMode, type QuickBarParams } from "./show-dialog-quick-bar";

interface QuickBarItem extends ScorableTextItem {
primaryText: string;
Expand All @@ -56,9 +57,17 @@ interface EntityItem extends QuickBarItem {
icon?: TemplateResult;
}

interface DeviceItem extends QuickBarItem {
deviceId: string;
area?: string;
}

const isCommandItem = (item: QuickBarItem): item is CommandItem =>
(item as CommandItem).categoryKey !== undefined;

const isDeviceItem = (item: QuickBarItem): item is DeviceItem =>
(item as DeviceItem).deviceId !== undefined;

interface QuickBarNavigationItem extends CommandItem {
path: string;
}
Expand All @@ -77,28 +86,30 @@ export class QuickBar extends LitElement {

@state() private _entityItems?: EntityItem[];

@state() private _deviceItems?: DeviceItem[];

@state() private _filter = "";

@state() private _search = "";

@state() private _open = false;

@state() private _commandMode = false;

@state() private _opened = false;

@state() private _narrow = false;

@state() private _hint?: string;

@state() private _mode = QuickBarMode.Entity;

@query("ha-textfield", false) private _filterInputField?: HTMLElement;

private _focusSet = false;

private _focusListElement?: ListItem | null;

public async showDialog(params: QuickBarParams) {
this._commandMode = params.commandMode || this._toggleIfAlreadyOpened();
this._mode = params.mode || QuickBarMode.Entity;
this._hint = params.hint;
this._narrow = matchMedia(
"all and (max-width: 450px), all and (max-height: 500px)"
Expand All @@ -125,8 +136,20 @@ export class QuickBar extends LitElement {
}

private _getItems = memoizeOne(
(commandMode: boolean, commandItems, entityItems, filter: string) => {
const items = commandMode ? commandItems : entityItems;
(
mode: QuickBarMode,
commandItems,
entityItems,
deviceItems,
filter: string
) => {
let items = entityItems;

if (mode === QuickBarMode.Command) {
items = commandItems;
} else if (mode === QuickBarMode.Device) {
items = deviceItems;
}

if (items && filter && filter !== " ") {
return this._filterItems(items, filter);
Expand All @@ -141,12 +164,28 @@ export class QuickBar extends LitElement {
}

const items: QuickBarItem[] | undefined = this._getItems(
this._commandMode,
this._mode,
this._commandItems,
this._entityItems,
this._deviceItems,
this._filter
);

const translationKey =
this._mode === QuickBarMode.Device ? "devices" : "entities";
const placeholder = this.hass.localize(
`ui.dialogs.quick-bar.filter_placeholder.${translationKey}`
);

const commandMode = this._mode === QuickBarMode.Command;
const deviceMode = this._mode === QuickBarMode.Device;
const icon = commandMode
? mdiConsoleLine
: deviceMode
? mdiDevices
: mdiMagnify;
const searchPrefix = commandMode ? ">" : deviceMode ? "#" : "";

return html`
<ha-dialog
.heading=${this.hass.localize("ui.dialogs.quick-bar.title")}
Expand All @@ -158,34 +197,20 @@ export class QuickBar extends LitElement {
<div slot="heading" class="heading">
<ha-textfield
dialogInitialFocus
.placeholder=${this.hass.localize(
"ui.dialogs.quick-bar.filter_placeholder"
)}
aria-label=${this.hass.localize(
"ui.dialogs.quick-bar.filter_placeholder"
)}
.value=${this._commandMode ? `>${this._search}` : this._search}
.placeholder=${placeholder}
aria-label=${placeholder}
.value="${searchPrefix}${this._search}"
icon
.iconTrailing=${this._search !== undefined || this._narrow}
@input=${this._handleSearchChange}
@keydown=${this._handleInputKeyDown}
@focus=${this._setFocusFirstListItem}
>
${this._commandMode
? html`
<ha-svg-icon
slot="leadingIcon"
class="prefix"
.path=${mdiConsoleLine}
></ha-svg-icon>
`
: html`
<ha-svg-icon
slot="leadingIcon"
class="prefix"
.path=${mdiMagnify}
></ha-svg-icon>
`}
<ha-svg-icon
slot="leadingIcon"
class="prefix"
.path=${icon}
></ha-svg-icon>
${this._search || this._narrow
? html`
<div slot="trailingIcon">
Expand Down Expand Up @@ -232,8 +257,7 @@ export class QuickBar extends LitElement {
height: this._narrow
? "calc(100vh - 56px)"
: `${Math.min(
items.length * (this._commandMode ? 56 : 72) +
26,
items.length * (commandMode ? 56 : 72) + 26,
500
)}px`,
})}
Expand All @@ -252,9 +276,11 @@ export class QuickBar extends LitElement {
}

private async _initializeItemsIfNeeded() {
if (this._commandMode) {
if (this._mode === QuickBarMode.Command) {
this._commandItems =
this._commandItems || (await this._generateCommandItems());
} else if (this._mode === QuickBarMode.Device) {
this._deviceItems = this._deviceItems || this._generateDeviceItems();
} else {
this._entityItems = this._entityItems || this._generateEntityItems();
}
Expand All @@ -279,11 +305,37 @@ export class QuickBar extends LitElement {
if (!item) {
return nothing;
}
return isCommandItem(item)
? this._renderCommandItem(item, index)
: this._renderEntityItem(item as EntityItem, index);

if (isDeviceItem(item)) {
return this._renderDeviceItem(item, index);
}

if (isCommandItem(item)) {
return this._renderCommandItem(item, index);
}

return this._renderEntityItem(item as EntityItem, index);
};

private _renderDeviceItem(item: DeviceItem, index?: number) {
return html`
<ha-list-item
.twoline=${Boolean(item.area)}
.item=${item}
index=${ifDefined(index)}
>
<span>${item.primaryText}</span>
${item.area
? html`
<span slot="secondary" class="item-text secondary"
>${item.area}</span
>
`
: nothing}
</ha-list-item>
`;
}

private _renderEntityItem(item: EntityItem, index?: number) {
return html`
<ha-list-item
Expand Down Expand Up @@ -376,31 +428,34 @@ export class QuickBar extends LitElement {

private _handleSearchChange(ev: CustomEvent): void {
const newFilter = (ev.currentTarget as any).value;
const oldCommandMode = this._commandMode;
const oldMode = this._mode;
const oldSearch = this._search;
let newCommandMode: boolean;
let newMode: QuickBarMode;
let newSearch: string;

if (newFilter.startsWith(">")) {
newCommandMode = true;
newMode = QuickBarMode.Command;
newSearch = newFilter.substring(1);
} else if (newFilter.startsWith("#")) {
newMode = QuickBarMode.Device;
newSearch = newFilter.substring(1);
} else {
newCommandMode = false;
newMode = QuickBarMode.Entity;
newSearch = newFilter;
}

if (oldCommandMode === newCommandMode && oldSearch === newSearch) {
if (oldMode === newMode && oldSearch === newSearch) {
return;
}

this._commandMode = newCommandMode;
this._mode = newMode;
this._search = newSearch;

if (this._hint) {
this._hint = undefined;
}

if (oldCommandMode !== this._commandMode) {
if (oldMode !== this._mode) {
this._focusSet = false;
this._initializeItemsIfNeeded();
this._filter = this._search;
Expand Down Expand Up @@ -464,6 +519,32 @@ export class QuickBar extends LitElement {
);
}

private _generateDeviceItems(): DeviceItem[] {
return Object.keys(this.hass.devices)
.map((deviceId) => {
const device = this.hass.devices[deviceId];
const area = this.hass.areas[device.area_id!];
const deviceItem = {
primaryText: device.name!,
deviceId: device.id,
area: area?.name,
action: () => navigate(`/config/devices/device/${device.id}`),
};

return {
...deviceItem,
strings: [deviceItem.primaryText],
};
})
.sort((a, b) =>
caseInsensitiveStringCompare(
a.primaryText,
b.primaryText,
this.hass.locale.language
)
);
}

private _generateEntityItems(): EntityItem[] {
return Object.keys(this.hass.states)
.map((entityId) => {
Expand Down Expand Up @@ -746,10 +827,6 @@ export class QuickBar extends LitElement {
});
}

private _toggleIfAlreadyOpened() {
return this._opened ? !this._commandMode : false;
}

private _filterItems = memoizeOne(
(items: QuickBarItem[], filter: string): QuickBarItem[] =>
fuzzyFilterSort<QuickBarItem>(filter.trimLeft(), items)
Expand Down
8 changes: 7 additions & 1 deletion src/dialogs/quick-bar/show-dialog-quick-bar.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { fireEvent } from "../../common/dom/fire_event";

export const enum QuickBarMode {
Command = "command",
Device = "device",
Entity = "entity",
}

export interface QuickBarParams {
entityFilter?: string;
commandMode?: boolean;
mode?: QuickBarMode;
hint?: string;
}

Expand Down
9 changes: 6 additions & 3 deletions src/panels/config/dashboard/ha-config-dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from "@mdi/js";
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { LitElement, css, html } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
Expand All @@ -33,7 +33,10 @@ import {
checkForEntityUpdates,
filterUpdateEntitiesWithInstall,
} from "../../../data/update";
import { showQuickBar } from "../../../dialogs/quick-bar/show-dialog-quick-bar";
import {
QuickBarMode,
showQuickBar,
} from "../../../dialogs/quick-bar/show-dialog-quick-bar";
import { showRestartDialog } from "../../../dialogs/restart/show-dialog-restart";
import type { PageNavigation } from "../../../layouts/hass-tabs-subpage";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
Expand Down Expand Up @@ -325,7 +328,7 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) {

private _showQuickBar(): void {
showQuickBar(this, {
commandMode: true,
mode: QuickBarMode.Command,
hint: this.hass.enableShortcuts
? this.hass.localize("ui.dialogs.quick-bar.key_c_hint")
: undefined,
Expand Down
9 changes: 6 additions & 3 deletions src/panels/lovelace/hui-root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
import "@polymer/paper-tabs/paper-tab";
import "@polymer/paper-tabs/paper-tabs";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { LitElement, css, html } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ifDefined } from "lit/directives/if-defined";
Expand Down Expand Up @@ -59,7 +59,10 @@ import {
showAlertDialog,
showConfirmationDialog,
} from "../../dialogs/generic/show-dialog-box";
import { showQuickBar } from "../../dialogs/quick-bar/show-dialog-quick-bar";
import {
QuickBarMode,
showQuickBar,
} from "../../dialogs/quick-bar/show-dialog-quick-bar";
import { showVoiceCommandDialog } from "../../dialogs/voice-command-dialog/show-ha-voice-command-dialog";
import { haStyle } from "../../resources/styles";
import type { HomeAssistant, PanelInfo } from "../../types";
Expand Down Expand Up @@ -662,7 +665,7 @@ class HUIRoot extends LitElement {

private _showQuickBar(): void {
showQuickBar(this, {
commandMode: false,
mode: QuickBarMode.Entity,
hint: this.hass.enableShortcuts
? this.hass.localize("ui.tips.key_e_hint")
: undefined,
Expand Down
Loading

0 comments on commit e731f06

Please sign in to comment.