diff --git a/src/common/string/filter/sequence-matching.ts b/src/common/string/filter/sequence-matching.ts index dac81672a8..6e4a5bf2e5 100644 --- a/src/common/string/filter/sequence-matching.ts +++ b/src/common/string/filter/sequence-matching.ts @@ -42,7 +42,7 @@ export const fuzzySequentialMatch = (filter: string, ...words: string[]) => { export interface ScorableTextItem { score?: number; - text: string; + filterText: string; altText?: string; } @@ -55,8 +55,8 @@ export const fuzzyFilterSort: FuzzyFilterSort = (filter, items) => { return items .map((item) => { item.score = item.altText - ? fuzzySequentialMatch(filter, item.text, item.altText) - : fuzzySequentialMatch(filter, item.text); + ? fuzzySequentialMatch(filter, item.filterText, item.altText) + : fuzzySequentialMatch(filter, item.filterText); return item; }) .filter((item) => item.score !== undefined && item.score > 0) diff --git a/src/dialogs/quick-bar/ha-quick-bar.ts b/src/dialogs/quick-bar/ha-quick-bar.ts index 49920892c2..80d9c5ccc4 100644 --- a/src/dialogs/quick-bar/ha-quick-bar.ts +++ b/src/dialogs/quick-bar/ha-quick-bar.ts @@ -51,24 +51,38 @@ const DEFAULT_NAVIGATION_ICON = "hass:arrow-right-circle"; const DEFAULT_SERVER_ICON = "hass:server"; interface QuickBarItem extends ScorableTextItem { + primaryText: string; icon?: string; iconPath?: string; action(data?: any): void; } -interface QuickBarNavigationItem extends QuickBarItem { +interface CommandItem extends QuickBarItem { + categoryKey: "reload" | "navigation" | "server_control"; + categoryText: string; +} + +const isCommandItem = ( + item: QuickBarItem | CommandItem +): item is CommandItem => { + return (item as CommandItem).categoryKey !== undefined; +}; + +interface QuickBarNavigationItem extends CommandItem { path: string; } -interface NavigationInfo extends PageNavigation { - text: string; -} +type NavigationInfo = PageNavigation & Pick; +type BaseNavigationCommand = Pick< + QuickBarNavigationItem, + "primaryText" | "path" +>; @customElement("ha-quick-bar") export class QuickBar extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @internalProperty() private _commandItems?: QuickBarItem[]; + @internalProperty() private _commandItems?: CommandItem[]; @internalProperty() private _entityItems?: QuickBarItem[]; @@ -201,6 +215,12 @@ export class QuickBar extends LitElement { } private _renderItem(item: QuickBarItem, index?: number) { + return isCommandItem(item) + ? this._renderCommandItem(item, index) + : this._renderEntityItem(item, index); + } + + private _renderEntityItem(item: QuickBarItem, index?: number) { return html` ` : html``} - ${item.text} + ${item.primaryText} + ${item.altText + ? html` + ${item.altText} + ` + : null} + + `; + } + + private _renderCommandItem(item: CommandItem, index?: number) { + return html` + + ${item.categoryText} + + + ${item.primaryText} ${item.altText ? html` ({ - text: computeStateName(this.hass.states[entityId]), - altText: entityId, - icon: domainIcon(computeDomain(entityId), this.hass.states[entityId]), - action: () => fireEvent(this, "hass-more-info", { entityId }), - })) - .sort((a, b) => compare(a.text.toLowerCase(), b.text.toLowerCase())); + .map((entityId) => { + const primaryText = computeStateName(this.hass.states[entityId]); + return { + primaryText, + filterText: primaryText, + altText: entityId, + icon: domainIcon(computeDomain(entityId), this.hass.states[entityId]), + action: () => fireEvent(this, "hass-more-info", { entityId }), + }; + }) + .sort((a, b) => + compare(a.primaryText.toLowerCase(), b.primaryText.toLowerCase()) + ); } - private _generateCommandItems(): QuickBarItem[] { + private _generateCommandItems(): CommandItem[] { return [ ...this._generateReloadCommands(), ...this._generateServerControlCommands(), ...this._generateNavigationCommands(), - ].sort((a, b) => compare(a.text.toLowerCase(), b.text.toLowerCase())); + ].sort((a, b) => + compare(a.filterText.toLowerCase(), b.filterText.toLowerCase()) + ); } - private _generateReloadCommands(): QuickBarItem[] { + private _generateReloadCommands(): CommandItem[] { const reloadableDomains = componentsWithService(this.hass, "reload").sort(); - return reloadableDomains.map((domain) => ({ - text: + return reloadableDomains.map((domain) => { + const categoryText = this.hass.localize( + `ui.dialogs.quick-bar.commands.types.reload` + ); + const primaryText = this.hass.localize(`ui.dialogs.quick-bar.commands.reload.${domain}`) || this.hass.localize( "ui.dialogs.quick-bar.commands.reload.reload", "domain", domainToName(this.hass.localize, domain) - ), - icon: domainIcon(domain), - action: () => this.hass.callService(domain, "reload"), - })); + ); + + return { + primaryText, + filterText: `${categoryText} ${primaryText}`, + icon: domainIcon(domain), + action: () => this.hass.callService(domain, "reload"), + categoryKey: "reload", + categoryText, + }; + }); } - private _generateServerControlCommands(): QuickBarItem[] { + private _generateServerControlCommands(): CommandItem[] { const serverActions = ["restart", "stop"]; - return serverActions.map((action) => - this._generateConfirmationCommand( + return serverActions.map((action) => { + const categoryKey = "server_control"; + const categoryText = this.hass.localize( + `ui.dialogs.quick-bar.commands.types.${categoryKey}` + ); + const primaryText = this.hass.localize( + "ui.dialogs.quick-bar.commands.server_control.perform_action", + "action", + this.hass.localize( + `ui.dialogs.quick-bar.commands.server_control.${action}` + ) + ); + + return this._generateConfirmationCommand( { - text: this.hass.localize( - "ui.dialogs.quick-bar.commands.server_control.perform_action", - "action", - this.hass.localize( - `ui.dialogs.quick-bar.commands.server_control.${action}` - ) - ), + primaryText, + filterText: `${categoryText} ${primaryText}`, + categoryKey, + categoryText, icon: DEFAULT_SERVER_ICON, action: () => this.hass.callService("homeassistant", action), }, this.hass.localize("ui.dialogs.generic.ok") - ) - ); + ); + }); } - private _generateNavigationCommands(): QuickBarItem[] { + private _generateNavigationCommands(): CommandItem[] { const panelItems = this._generateNavigationPanelCommands(); const sectionItems = this._generateNavigationConfigSectionCommands(); - return this._withNavigationActions([...panelItems, ...sectionItems]); + return this._finalizeNavigationCommands([...panelItems, ...sectionItems]); } - private _generateNavigationPanelCommands(): Omit< - QuickBarNavigationItem, - "action" - >[] { + private _generateNavigationPanelCommands(): BaseNavigationCommand[] { return Object.keys(this.hass.panels).map((panelKey) => { const panel = this.hass.panels[panelKey]; const translationKey = getPanelNameTranslationKey(panel); - const text = this.hass.localize( - "ui.dialogs.quick-bar.commands.navigation.navigate_to", - "panel", - this.hass.localize(translationKey) || panel.title || panel.url_path - ); + const primaryText = + this.hass.localize(translationKey) || panel.title || panel.url_path; return { - text, - icon: getPanelIcon(panel) || DEFAULT_NAVIGATION_ICON, + primaryText, path: `/${panel.url_path}`, }; }); } - private _generateNavigationConfigSectionCommands(): Partial< - QuickBarNavigationItem - >[] { + private _generateNavigationConfigSectionCommands(): BaseNavigationCommand[] { const items: NavigationInfo[] = []; for (const sectionKey of Object.keys(configSections)) { @@ -422,28 +483,20 @@ export class QuickBar extends LitElement { page: PageNavigation ): NavigationInfo | undefined { if (page.component) { - const shortCaption = this.hass.localize( + const caption = this.hass.localize( `ui.dialogs.quick-bar.commands.navigation.${page.component}` ); - if (page.translationKey) { - const caption = this.hass.localize( - "ui.dialogs.quick-bar.commands.navigation.navigate_to_config", - "panel", - shortCaption - ); - - return { ...page, text: caption }; - } + return { ...page, primaryText: caption }; } return undefined; } private _generateConfirmationCommand( - item: QuickBarItem, + item: CommandItem, confirmText: ConfirmationDialogParams["confirmText"] - ): QuickBarItem { + ): CommandItem { return { ...item, action: () => @@ -454,13 +507,23 @@ export class QuickBar extends LitElement { }; } - private _withNavigationActions(items) { - return items.map(({ text, icon, iconPath, path }) => ({ - text, - icon, - iconPath, - action: () => navigate(this, path), - })); + private _finalizeNavigationCommands( + items: BaseNavigationCommand[] + ): CommandItem[] { + return items.map((item) => { + const categoryKey = "navigation"; + const categoryText = this.hass.localize( + `ui.dialogs.quick-bar.commands.types.${categoryKey}` + ); + + return { + ...item, + categoryKey, + categoryText, + filterText: `${categoryText} ${item.primaryText}`, + action: () => navigate(this, item.path), + }; + }); } private _toggleIfAlreadyOpened() { @@ -512,6 +575,27 @@ export class QuickBar extends LitElement { color: var(--primary-text-color); } + span.command-category { + font-weight: bold; + padding: 3px; + display: inline-flex; + border-radius: 6px; + color: black; + } + + span.command-category.reload { + background: pink; + } + + span.command-category.navigation { + background: lightblue; + } + + span.command-category.server_control { + background: orange; + color: white; + } + .uni-virtualizer-host { display: block; position: relative; @@ -526,7 +610,6 @@ export class QuickBar extends LitElement { mwc-list-item { width: 100%; - text-transform: capitalize; } `, ]; diff --git a/src/translations/en.json b/src/translations/en.json index ff7739e88a..3d41913543 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -288,7 +288,6 @@ "overflow_menu": "Overflow menu", "successfully_saved": "Successfully saved", "successfully_deleted": "Successfully deleted", - "error_required": "Required", "copied": "Copied" }, @@ -496,13 +495,16 @@ "rpi_gpio": "[%key:ui::panel::config::server_control::section::reloading::rpi_gpio%]" }, "server_control": { - "perform_action": "{action} Server", + "perform_action": "{action} server", "restart": "[%key:ui::panel::config::server_control::section::server_management::restart%]", "stop": "[%key:ui::panel::config::server_control::section::server_management::stop%]" }, + "types": { + "reload": "Reload", + "navigation": "Go to", + "server_control": "Server" + }, "navigation": { - "navigate_to": "Navigate to {panel}", - "navigate_to_config": "Navigate to {panel} configuration", "logs": "[%key:ui::panel::config::logs::caption%]", "automation": "[%key:ui::panel::config::automation::caption%]", "script": "[%key:ui::panel::config::script::caption%]", @@ -1026,36 +1028,36 @@ "heading": "YAML configuration reloading", "introduction": "Some parts of Home Assistant can reload without requiring a restart. Hitting reload will unload their current YAML configuration and load the new one.", "reload": "Reload {domain}", - "core": "Reload location & customizations", - "group": "Reload groups, group entities, and notify services", - "automation": "Reload automations", - "script": "Reload scripts", - "scene": "Reload scenes", - "person": "Reload people", - "zone": "Reload zones", - "input_boolean": "Reload input booleans", - "input_text": "Reload input texts", - "input_number": "Reload input numbers", - "input_datetime": "Reload input date times", - "input_select": "Reload input selects", - "template": "Reload template entities", - "universal": "Reload universal media player entities", - "rest": "Reload rest entities and notify services", - "command_line": "Reload command line entities", - "filter": "Reload filter entities", - "statistics": "Reload statistics entities", - "generic": "Reload generic IP camera entities", - "generic_thermostat": "Reload generic thermostat entities", - "homekit": "Reload HomeKit", - "min_max": "Reload min/max entities", - "history_stats": "Reload history stats entities", - "trend": "Reload trend entities", - "ping": "Reload ping binary sensor entities", - "filesize": "Reload file size entities", - "telegram": "Reload telegram notify services", - "smtp": "Reload SMTP notify services", - "mqtt": "Reload manually configured MQTT entities", - "rpi_gpio": "Reload Raspberry Pi GPIO entities" + "core": "Location & customizations", + "group": "Groups, group entities, and notify services", + "automation": "Automations", + "script": "Scripts", + "scene": "Scenes", + "person": "People", + "zone": "Zones", + "input_boolean": "Input booleans", + "input_text": "Input texts", + "input_number": "Input numbers", + "input_datetime": "Input date times", + "input_select": "Input selects", + "template": "Template entities", + "universal": "Universal media player entities", + "rest": "Rest entities and notify services", + "command_line": "Command line entities", + "filter": "Filter entities", + "statistics": "Statistics entities", + "generic": "Generic IP camera entities", + "generic_thermostat": "Generic thermostat entities", + "homekit": "HomeKit", + "min_max": "Min/max entities", + "history_stats": "History stats entities", + "trend": "Trend entities", + "ping": "Ping binary sensor entities", + "filesize": "File size entities", + "telegram": "Telegram notify services", + "smtp": "SMTP notify services", + "mqtt": "Manually configured MQTT entities", + "rpi_gpio": "Raspberry Pi GPIO entities" }, "server_management": { "heading": "Server management",