This commit is contained in:
Zack 2022-02-13 22:43:33 -06:00
parent 511368da13
commit fc06b9c29d
3 changed files with 127 additions and 104 deletions

View File

@ -4,9 +4,9 @@ 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 { import {
mdiClose, mdiClose,
mdiConsoleLine,
mdiEarth, mdiEarth,
mdiMagnify, mdiMagnify,
mdiNavigationVariantOutline,
mdiReload, mdiReload,
mdiServerNetwork, mdiServerNetwork,
} from "@mdi/js"; } from "@mdi/js";
@ -17,6 +17,7 @@ import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
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 { LocalStorage } from "../../common/decorators/local-storage";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
import { computeDomain } from "../../common/entity/compute_domain"; import { computeDomain } from "../../common/entity/compute_domain";
import { computeStateName } from "../../common/entity/compute_state_name"; import { computeStateName } from "../../common/entity/compute_state_name";
@ -35,9 +36,13 @@ import "../../components/ha-icon-button";
import "../../components/ha-textfield"; import "../../components/ha-textfield";
import { domainToName } from "../../data/integration"; import { domainToName } from "../../data/integration";
import { getPanelNameTranslationKey } from "../../data/panel"; import { getPanelNameTranslationKey } from "../../data/panel";
import { PageNavigation } from "../../layouts/hass-tabs-subpage"; import type { PageNavigation } from "../../layouts/hass-tabs-subpage";
import { configSections } from "../../panels/config/ha-panel-config"; import { configSections } from "../../panels/config/ha-panel-config";
import { haStyleDialog, haStyleScrollbar } from "../../resources/styles"; import {
haStyle,
haStyleDialog,
haStyleScrollbar,
} from "../../resources/styles";
import { HomeAssistant } from "../../types"; import { HomeAssistant } from "../../types";
import { import {
ConfirmationDialogParams, ConfirmationDialogParams,
@ -48,10 +53,13 @@ import { QuickBarParams } from "./show-dialog-quick-bar";
interface QuickBarItem extends ScorableTextItem { interface QuickBarItem extends ScorableTextItem {
primaryText: string; primaryText: string;
iconPath?: string; iconPath?: string;
altText?: string;
action(data?: any): void; action(data?: any): void;
} }
interface CommandItem extends QuickBarItem { interface CommandItem extends QuickBarItem {
altText: string;
icon?: string;
categoryKey: "reload" | "navigation" | "server_control"; categoryKey: "reload" | "navigation" | "server_control";
categoryText: string; categoryText: string;
} }
@ -68,12 +76,14 @@ interface QuickBarNavigationItem extends CommandItem {
path: string; path: string;
} }
type NavigationInfo = PageNavigation & Pick<QuickBarItem, "primaryText">; type NavigationInfo = PageNavigation &
Pick<QuickBarItem, "primaryText" | "altText">;
type BaseNavigationCommand = Pick< type BaseNavigationCommand = Pick<
QuickBarNavigationItem, QuickBarNavigationItem,
"primaryText" | "path" "primaryText" | "path"
>; >;
@customElement("ha-quick-bar") @customElement("ha-quick-bar")
export class QuickBar extends LitElement { export class QuickBar extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@ -82,6 +92,8 @@ export class QuickBar extends LitElement {
@state() private _entityItems?: EntityItem[]; @state() private _entityItems?: EntityItem[];
@state() private _items?: (EntityItem | CommandItem)[];
@state() private _filter = ""; @state() private _filter = "";
@state() private _search = ""; @state() private _search = "";
@ -92,18 +104,25 @@ export class QuickBar extends LitElement {
@state() private _opened = false; @state() private _opened = false;
@state() private _done = false;
@state() private _narrow = false; @state() private _narrow = false;
@state() private _hint?: string; @state() private _hint?: string;
@query("ha-textfield", false) private _filterInputField?: HTMLElement; @query("ha-textfield", false) private _filterInputField?: HTMLElement;
// @ts-ignore
@LocalStorage("suggestions", true, {
attribute: false,
})
private _suggestions: QuickBarItem[] = [];
private _focusSet = false; private _focusSet = false;
private _focusListElement?: ListItem | null; private _focusListElement?: ListItem | null;
public async showDialog(params: QuickBarParams) { public async showDialog(params: QuickBarParams) {
this._commandMode = params.commandMode || this._toggleIfAlreadyOpened();
this._hint = params.hint; this._hint = params.hint;
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)"
@ -121,28 +140,19 @@ export class QuickBar extends LitElement {
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
} }
private _getItems = memoizeOne(
(commandMode: boolean, commandItems, entityItems, filter: string) => {
const items = commandMode ? commandItems : entityItems;
if (items && filter && filter !== " ") {
return this._filterItems(items, filter);
}
return items;
}
);
protected render() { protected render() {
if (!this._open) { if (!this._open) {
return html``; return html``;
} }
const items: QuickBarItem[] | undefined = this._getItems( let items: QuickBarItem[] | undefined =
this._commandMode, this._filter === ""
this._commandItems, ? this._suggestions
this._entityItems, : [...this._commandItems!, ...this._entityItems!];
this._filter
); if (items && this._filter && this._filter !== " ") {
items = this._filterItems(items, this._filter);
}
return html` return html`
<ha-dialog <ha-dialog
@ -161,28 +171,18 @@ export class QuickBar extends LitElement {
aria-label=${this.hass.localize( aria-label=${this.hass.localize(
"ui.dialogs.quick-bar.filter_placeholder" "ui.dialogs.quick-bar.filter_placeholder"
)} )}
.value=${this._commandMode ? `>${this._search}` : this._search} .value=${this._search}
icon .icon=${true}
.iconTrailing=${this._search !== undefined || this._narrow} .iconTrailing=${this._search !== undefined || this._narrow}
@input=${this._handleSearchChange} @input=${this._handleSearchChange}
@keydown=${this._handleInputKeyDown} @keydown=${this._handleInputKeyDown}
@focus=${this._setFocusFirstListItem} @focus=${this._setFocusFirstListItem}
> >
${this._commandMode <ha-svg-icon
? html` slot="leadingIcon"
<ha-svg-icon class="prefix"
slot="leadingIcon" .path=${mdiMagnify}
class="prefix" ></ha-svg-icon>
.path=${mdiConsoleLine}
></ha-svg-icon>
`
: html`
<ha-svg-icon
slot="leadingIcon"
class="prefix"
.path=${mdiMagnify}
></ha-svg-icon>
`}
${this._search || this._narrow ${this._search || this._narrow
? html` ? html`
<div slot="trailingIcon"> <div slot="trailingIcon">
@ -206,11 +206,10 @@ export class QuickBar extends LitElement {
</ha-textfield> </ha-textfield>
</div> </div>
${!items ${!items
? html`<ha-circular-progress ? html`
size="small" <ha-circular-progress size="small" active></ha-circular-progress>
active `
></ha-circular-progress>` : items.length === 0 && this._filter !== ""
: items.length === 0
? html` ? html`
<div class="nothing-found"> <div class="nothing-found">
${this.hass.localize("ui.dialogs.quick-bar.nothing_found")} ${this.hass.localize("ui.dialogs.quick-bar.nothing_found")}
@ -218,6 +217,7 @@ export class QuickBar extends LitElement {
` `
: html` : html`
<mwc-list> <mwc-list>
<<<<<<< HEAD
${this._opened ${this._opened
? html`<lit-virtualizer ? html`<lit-virtualizer
scroller scroller
@ -238,6 +238,26 @@ export class QuickBar extends LitElement {
> >
</lit-virtualizer>` </lit-virtualizer>`
: ""} : ""}
=======
<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 * 54 + 26,
this._done ? 500 : 0
)}px`,
})}
.items=${items}
.renderItem=${this._renderItem}
>
</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>` : ""}
@ -246,11 +266,8 @@ export class QuickBar extends LitElement {
} }
private _initializeItemsIfNeeded() { private _initializeItemsIfNeeded() {
if (this._commandMode) { this._commandItems = this._commandItems || this._generateCommandItems();
this._commandItems = this._commandItems || this._generateCommandItems(); this._entityItems = this._entityItems || this._generateEntityItems();
} else {
this._entityItems = this._entityItems || this._generateEntityItems();
}
} }
private _handleOpened() { private _handleOpened() {
@ -280,30 +297,28 @@ export class QuickBar extends LitElement {
private _renderEntityItem(item: EntityItem, index?: number) { private _renderEntityItem(item: EntityItem, index?: number) {
return html` return html`
<mwc-list-item <mwc-list-item
.twoline=${Boolean(item.altText)} .twoline=${Boolean(this.hass.userData?.showAdvanced && item.altText)}
.item=${item} .item=${item}
index=${ifDefined(index)} index=${ifDefined(index)}
graphic="icon" graphic="icon"
class=${this.hass.userData?.showAdvanced && item.altText
? "single-line"
: ""}
> >
${item.iconPath ${item.iconPath
? html`<ha-svg-icon ? html`<ha-svg-icon
.path=${item.iconPath} .path=${item.iconPath}
class="entity"
slot="graphic" slot="graphic"
></ha-svg-icon>` ></ha-svg-icon>`
: html`<ha-icon : html`<ha-icon .icon=${item.icon} slot="graphic"></ha-icon>`}
.icon=${item.icon}
class="entity"
slot="graphic"
></ha-icon>`}
<span>${item.primaryText}</span> <span>${item.primaryText}</span>
${item.altText ${item.altText && this.hass.userData?.showAdvanced
? html` ? html`
<span slot="secondary" class="item-text secondary" <span slot="secondary" class="item-text secondary"
>${item.altText}</span >${item.altText}</span
> >
` `
: null} : ""}
</mwc-list-item> </mwc-list-item>
`; `;
} }
@ -312,32 +327,38 @@ export class QuickBar extends LitElement {
return html` return html`
<mwc-list-item <mwc-list-item
.item=${item} .item=${item}
twoline
index=${ifDefined(index)} index=${ifDefined(index)}
class="command-item" class="command-item"
hasMeta graphic="icon"
> >
<span> ${item.icon
<ha-chip ? html`<ha-icon
.label=${item.categoryText} .icon=${item.icon}
hasIcon class="entity"
class="command-category ${item.categoryKey}" slot="graphic"
> ></ha-icon>`
${item.iconPath : item.iconPath
? html`<ha-svg-icon ? html`<ha-svg-icon
.path=${item.iconPath} .path=${item.iconPath}
slot="icon" slot="graphic"
></ha-svg-icon>` ></ha-svg-icon>`
: ""} : ""}
${item.categoryText}</ha-chip
>
</span>
<span class="command-text">${item.primaryText}</span> <span>${item.primaryText}</span>
<span slot="secondary" class="secondary item-text"
>${item.altText}</span
>
</mwc-list-item> </mwc-list-item>
`; `;
} }
private async processItemAndCloseDialog(item: QuickBarItem, index: number) { private async processItemAndCloseDialog(item: QuickBarItem, index: number) {
if (!this._suggestions.includes(item)) {
this._suggestions.unshift(item);
this._suggestions = this._suggestions.slice(0, 3);
}
this._addSpinnerToCommandItem(index); this._addSpinnerToCommandItem(index);
await item.action(); await item.action();
@ -373,42 +394,26 @@ export class QuickBar extends LitElement {
private _handleSearchChange(ev: CustomEvent): void { private _handleSearchChange(ev: CustomEvent): void {
const newFilter = (ev.currentTarget as any).value; const newFilter = (ev.currentTarget as any).value;
const oldCommandMode = this._commandMode;
const oldSearch = this._search;
let newCommandMode: boolean;
let newSearch: string; let newSearch: string;
if (newFilter.startsWith(">")) { if (newFilter.startsWith(">")) {
newCommandMode = true;
newSearch = newFilter.substring(1); newSearch = newFilter.substring(1);
} else { } else {
newCommandMode = false;
newSearch = newFilter; newSearch = newFilter;
} }
if (oldCommandMode === newCommandMode && oldSearch === newSearch) {
return;
}
this._commandMode = newCommandMode;
this._search = newSearch; this._search = newSearch;
if (this._hint) { if (this._hint) {
this._hint = undefined; this._hint = undefined;
} }
if (oldCommandMode !== this._commandMode) { if (this._focusSet && this._focusListElement) {
this._focusSet = false; this._focusSet = false;
this._initializeItemsIfNeeded(); // @ts-ignore
this._filter = this._search; this._focusListElement.rippleHandlers.endFocus();
} else {
if (this._focusSet && this._focusListElement) {
this._focusSet = false;
// @ts-ignore
this._focusListElement.rippleHandlers.endFocus();
}
this._debouncedSetFilter(this._search);
} }
this._debouncedSetFilter(this._search);
} }
private _clearSearch() { private _clearSearch() {
@ -512,6 +517,7 @@ export class QuickBar extends LitElement {
categoryText: this.hass.localize( categoryText: this.hass.localize(
`ui.dialogs.quick-bar.commands.types.reload` `ui.dialogs.quick-bar.commands.types.reload`
), ),
altText: "Reload",
})); }));
// Add "frontend.reload_themes" // Add "frontend.reload_themes"
@ -524,6 +530,7 @@ export class QuickBar extends LitElement {
categoryText: this.hass.localize( categoryText: this.hass.localize(
"ui.dialogs.quick-bar.commands.types.reload" "ui.dialogs.quick-bar.commands.types.reload"
), ),
altText: "Reload",
}); });
// Add "homeassistant.reload_core_config" // Add "homeassistant.reload_core_config"
@ -537,6 +544,7 @@ export class QuickBar extends LitElement {
categoryText: this.hass.localize( categoryText: this.hass.localize(
"ui.dialogs.quick-bar.commands.types.reload" "ui.dialogs.quick-bar.commands.types.reload"
), ),
altText: "Reload",
}); });
return commands.map((command) => ({ return commands.map((command) => ({
@ -572,6 +580,7 @@ export class QuickBar extends LitElement {
{ {
...item, ...item,
strings: [`${item.categoryText} ${item.primaryText}`], strings: [`${item.categoryText} ${item.primaryText}`],
altText: "Server Control",
}, },
this.hass.localize("ui.dialogs.generic.ok") this.hass.localize("ui.dialogs.generic.ok")
); );
@ -598,6 +607,8 @@ export class QuickBar extends LitElement {
return { return {
primaryText, primaryText,
path: `/${panel.url_path}`, path: `/${panel.url_path}`,
icon: panel.icon,
altText: "Panel",
}; };
}); });
} }
@ -627,7 +638,11 @@ export class QuickBar extends LitElement {
continue; continue;
} }
items.push(info); items.push({
iconPath: mdiNavigationVariantOutline,
altText: "Navigation",
...info,
});
} }
} }
@ -678,6 +693,7 @@ export class QuickBar extends LitElement {
`ui.dialogs.quick-bar.commands.types.${categoryKey}` `ui.dialogs.quick-bar.commands.types.${categoryKey}`
), ),
action: () => navigate(item.path), action: () => navigate(item.path),
altText: "Navigation",
}; };
return { return {
@ -688,10 +704,6 @@ export class QuickBar extends LitElement {
}); });
} }
private _toggleIfAlreadyOpened() {
return this._opened ? !this._commandMode : false;
}
private _filterItems = memoizeOne( private _filterItems = memoizeOne(
(items: QuickBarItem[], filter: string): QuickBarItem[] => (items: QuickBarItem[], filter: string): QuickBarItem[] =>
fuzzyFilterSort<QuickBarItem>(filter.trimLeft(), items) fuzzyFilterSort<QuickBarItem>(filter.trimLeft(), items)
@ -701,11 +713,11 @@ export class QuickBar extends LitElement {
return [ return [
haStyleScrollbar, haStyleScrollbar,
haStyleDialog, haStyleDialog,
haStyle,
css` css`
.heading { .heading {
display: flex; display: flex;
align-items: center; align-items: center;
--mdc-theme-primary: var(--primary-text-color);
} }
.heading ha-textfield { .heading ha-textfield {
@ -739,8 +751,8 @@ export class QuickBar extends LitElement {
} }
} }
ha-icon.entity, ha-icon,
ha-svg-icon.entity { ha-svg-icon {
margin-left: 20px; margin-left: 20px;
} }
@ -748,6 +760,12 @@ export class QuickBar extends LitElement {
color: var(--primary-text-color); color: var(--primary-text-color);
} }
ha-textfield {
--mdc-text-field-fill-color: transparent;
--mdc-theme-primary: var(--divider-color);
--mdc-text-field-idle-line-color: var(--divider-color);
}
ha-textfield ha-icon-button { ha-textfield ha-icon-button {
--mdc-icon-button-size: 24px; --mdc-icon-button-size: 24px;
color: var(--primary-text-color); color: var(--primary-text-color);
@ -782,6 +800,10 @@ export class QuickBar extends LitElement {
text-transform: capitalize; text-transform: capitalize;
} }
mwc-list-item.single-line {
min-height: 48px;
}
.hint { .hint {
padding: 20px; padding: 20px;
font-style: italic; font-style: italic;

View File

@ -312,6 +312,7 @@ export const haStyleDialog = css`
--mdc-dialog-heading-ink-color: var(--primary-text-color); --mdc-dialog-heading-ink-color: var(--primary-text-color);
--mdc-dialog-content-ink-color: var(--primary-text-color); --mdc-dialog-content-ink-color: var(--primary-text-color);
--justify-action-buttons: space-between; --justify-action-buttons: space-between;
--ha-dialog-border-radius: 16px;
} }
ha-dialog .form { ha-dialog .form {

View File

@ -689,7 +689,7 @@
"server_control": "[%key:ui::panel::config::server_control::caption%]" "server_control": "[%key:ui::panel::config::server_control::caption%]"
} }
}, },
"filter_placeholder": "Entity Filter", "filter_placeholder": "Search for entities, pages or commands",
"title": "Quick Search", "title": "Quick Search",
"key_c_hint": "Press 'c' on any page to open this search bar", "key_c_hint": "Press 'c' on any page to open this search bar",
"nothing_found": "Nothing found!" "nothing_found": "Nothing found!"