1
0
mirror of https://github.com/home-assistant/frontend.git synced 2026-02-15 07:25:54 +00:00

Add protocols to quick search (#29248)

Add protocols to quick search, extract logic and translations
This commit is contained in:
Aidan Timson
2026-01-29 15:37:09 +00:00
committed by GitHub
parent 241a655765
commit 01f5df6671
6 changed files with 102 additions and 57 deletions

View File

@@ -0,0 +1,29 @@
import type { PageNavigation } from "../../layouts/hass-tabs-subpage";
import type { HomeAssistant } from "../../types";
import { canShowPage } from "./can_show_page";
export interface NavigationFilterOptions {
/** Whether there are Bluetooth config entries (pre-fetched by caller) */
hasBluetoothConfigEntries?: boolean;
}
/**
* Filters navigation pages based on visibility rules.
* Handles special cases like Bluetooth (requires config entries)
* and external app configuration.
*/
export const filterNavigationPages = (
hass: HomeAssistant,
pages: PageNavigation[],
options: NavigationFilterOptions = {}
): PageNavigation[] =>
pages.filter((page) => {
if (page.path === "#external-app-configuration") {
return hass.auth.external?.config.hasSettingsScreen;
}
// Only show Bluetooth page if there are Bluetooth config entries
if (page.component === "bluetooth") {
return options.hasBluetoothConfigEntries ?? false;
}
return canShowPage(hass, page);
});

View File

@@ -5,7 +5,10 @@ import {
mdiServerNetwork,
mdiStorePlus,
} from "@mdi/js";
import { canShowPage } from "../common/config/can_show_page";
import {
filterNavigationPages,
type NavigationFilterOptions,
} from "../common/config/filter_navigation_pages";
import { componentsWithService } from "../common/config/components_with_service";
import { isComponentLoaded } from "../common/config/is_component_loaded";
import type { PickerComboBoxItem } from "../components/ha-picker-combo-box";
@@ -99,33 +102,30 @@ const getNavigationInfoFromConfig = (
};
const generateNavigationConfigSectionCommands = (
hass: HomeAssistant
hass: HomeAssistant,
filterOptions: NavigationFilterOptions = {}
): BaseNavigationCommand[] => {
if (!hass.user?.is_admin) {
return [];
}
const items: NavigationInfo[] = [];
const allPages = Object.values(configSections).flat();
const visiblePages = filterNavigationPages(hass, allPages, filterOptions);
Object.values(configSections).forEach((sectionPages) => {
sectionPages.forEach((page) => {
if (!canShowPage(hass, page)) {
return;
}
for (const page of visiblePages) {
const info = getNavigationInfoFromConfig(hass.localize, page);
const info = getNavigationInfoFromConfig(hass.localize, page);
if (!info) {
continue;
}
// Add to list, but only if we do not already have an entry for the same path and component
if (items.some((e) => e.path === info.path)) {
continue;
}
if (!info) {
return;
}
// Add to list, but only if we do not already have an entry for the same path and component
if (items.some((e) => e.path === info.path)) {
return;
}
items.push(info);
});
});
items.push(info);
}
return items;
};
@@ -149,7 +149,8 @@ const finalizeNavigationCommands = (
export const generateNavigationCommands = (
hass: HomeAssistant,
apps?: HassioAddonInfo[]
apps?: HassioAddonInfo[],
filterOptions: NavigationFilterOptions = {}
): NavigationComboBoxItem[] => {
const panelItems = generateNavigationPanelCommands(
hass.localize,
@@ -157,7 +158,10 @@ export const generateNavigationCommands = (
apps
);
const sectionItems = generateNavigationConfigSectionCommands(hass);
const sectionItems = generateNavigationConfigSectionCommands(
hass,
filterOptions
);
const appItems: BaseNavigationCommand[] = [];
if (hass.user?.is_admin && isComponentLoaded(hass, "hassio")) {
appItems.push({

View File

@@ -45,6 +45,7 @@ import {
type ActionCommandComboBoxItem,
type NavigationComboBoxItem,
} from "../../data/quick_bar";
import type { NavigationFilterOptions } from "../../common/config/filter_navigation_pages";
import {
multiTermSortedSearch,
type FuseWeightedKey,
@@ -81,6 +82,8 @@ export class QuickBar extends LitElement {
private _addons?: HassioAddonInfo[];
private _navigationFilterOptions: NavigationFilterOptions = {};
private _translationsLoaded = false;
// #region lifecycle
@@ -105,6 +108,12 @@ export class QuickBar extends LitElement {
this._configEntryLookup = Object.fromEntries(
configEntries.map((entry) => [entry.entry_id, entry])
);
// Derive Bluetooth config entries status for navigation filtering
this._navigationFilterOptions = {
hasBluetoothConfigEntries: configEntries.some(
(entry) => entry.domain === "bluetooth"
),
};
} catch (err) {
// eslint-disable-next-line no-console
console.error("Error fetching config entries for quick bar", err);
@@ -397,7 +406,8 @@ export class QuickBar extends LitElement {
if (!section || section === "navigate") {
let navigateItems = this._generateNavigationCommandsMemoized(
this.hass,
this._addons
this._addons,
this._navigationFilterOptions
).sort(this._sortBySortingLabel);
if (filter) {
@@ -563,7 +573,11 @@ export class QuickBar extends LitElement {
);
private _generateNavigationCommandsMemoized = memoizeOne(
generateNavigationCommands
(
hass: HomeAssistant,
apps: HassioAddonInfo[] | undefined,
filterOptions: NavigationFilterOptions
) => generateNavigationCommands(hass, apps, filterOptions)
);
private _generateActionCommandsMemoized = memoizeOne(generateActionCommands);

View File

@@ -1,7 +1,7 @@
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { canShowPage } from "../../../common/config/can_show_page";
import { filterNavigationPages } from "../../../common/config/filter_navigation_pages";
import "../../../components/ha-card";
import "../../../components/ha-icon-next";
import "../../../components/ha-navigation-list";
@@ -30,38 +30,29 @@ class HaConfigNavigation extends LitElement {
}
protected render(): TemplateResult {
const pages = this.pages
.filter((page) => {
if (page.path === "#external-app-configuration") {
return this.hass.auth.external?.config.hasSettingsScreen;
}
// Only show Bluetooth page if there are Bluetooth config entries
if (page.component === "bluetooth") {
return this._hasBluetoothConfigEntries;
}
return canShowPage(this.hass, page);
})
.map((page) => ({
...page,
name:
page.name ||
this.hass.localize(
`ui.panel.config.dashboard.${page.translationKey}.main`
),
description:
page.component === "cloud" && (page.info as CloudStatus)
? page.info.logged_in
? `
const pages = filterNavigationPages(this.hass, this.pages, {
hasBluetoothConfigEntries: this._hasBluetoothConfigEntries,
}).map((page) => ({
...page,
name:
page.name ||
this.hass.localize(
`ui.panel.config.dashboard.${page.translationKey}.main`
),
description:
page.component === "cloud" && (page.info as CloudStatus)
? page.info.logged_in
? `
${this.hass.localize(
"ui.panel.config.cloud.description_login"
)}
`
: `
: `
${this.hass.localize(
"ui.panel.config.cloud.description_features"
)}
`
: `
: `
${
page.description ||
this.hass.localize(
@@ -69,7 +60,7 @@ class HaConfigNavigation extends LitElement {
)
}
`,
}));
}));
return html`
<div class="visually-hidden" role="heading" aria-level="2">
${this.hass.localize("panel.config")}

View File

@@ -116,7 +116,6 @@ export const configSections: Record<string, PageNavigation[]> = {
dashboard_2: [
{
path: "/config/matter",
name: "Matter",
iconPath:
"M7.228375 6.41685c0.98855 0.80195 2.16365 1.3412 3.416275 1.56765V1.30093l1.3612 -0.7854275 1.360125 0.7854275V7.9845c1.252875 -0.226675 2.4283 -0.765875 3.41735 -1.56765l2.471225 1.4293c-4.019075 3.976275 -10.490025 3.976275 -14.5091 0l2.482925 -1.4293Zm3.00335 17.067575c1.43325 -5.47035 -1.8052 -11.074775 -7.2604 -12.564675v2.859675c1.189125 0.455 2.244125 1.202875 3.0672 2.174275L0.25 19.2955v1.5719l1.3611925 0.781175L7.39865 18.3068c0.430175 1.19825 0.550625 2.48575 0.35015 3.743l2.482925 1.434625ZM21.034 10.91975c-5.452225 1.4932 -8.6871 7.09635 -7.254025 12.564675l2.47655 -1.43035c-0.200025 -1.257275 -0.079575 -2.544675 0.35015 -3.743025l5.7832 3.337525L23.75 20.86315V19.2955L17.961475 15.9537c0.8233 -0.97115 1.878225 -1.718975 3.0672 -2.174275l0.005325 -2.859675Z",
iconColor: "#2458B3",
@@ -125,7 +124,6 @@ export const configSections: Record<string, PageNavigation[]> = {
},
{
path: "/config/zha",
name: "Zigbee",
iconPath: mdiZigbee,
iconColor: "#E74011",
component: "zha",
@@ -133,7 +131,6 @@ export const configSections: Record<string, PageNavigation[]> = {
},
{
path: "/config/zwave_js",
name: "Z-Wave",
iconPath: mdiZWave,
iconColor: "#153163",
component: "zwave_js",
@@ -141,7 +138,6 @@ export const configSections: Record<string, PageNavigation[]> = {
},
{
path: "/knx",
name: "KNX",
iconPath:
"M 3.9861338,14.261456 3.7267552,13.934877 6.3179131,11.306266 H 4.466374 l -2.6385205,2.68258 V 11.312882 H 0.00440574 L 0,17.679803 l 1.8278535,5.43e-4 v -1.818482 l 0.7225444,-0.732459 2.1373588,2.543782 2.1869324,-5.44e-4 M 24,17.680369 21.809238,17.669359 19.885559,15.375598 17.640262,17.68037 h -1.828407 l 3.236048,-3.302138 -2.574075,-3.067547 2.135161,0.0016 1.610309,1.87687 1.866403,-1.87687 h 1.828429 l -2.857742,2.87478 m -10.589867,-2.924898 2.829625,3.990552 -0.01489,-3.977887 1.811889,-0.0044 0.0011,6.357564 -2.093314,-5.44e-4 -2.922133,-3.947594 -0.0314,3.947594 H 8.2581097 V 11.261677 M 11.971203,6.3517488 c 0,0 2.800714,-0.093203 6.172001,1.0812045 3.462393,1.0898845 5.770926,3.4695627 5.770926,3.4695627 l -1.823898,-5.43e-4 C 22.088532,10.900273 20.577938,9.4271528 17.660223,8.5024618 15.139256,7.703366 12.723057,7.645835 12.111178,7.6449876 l -9.71e-4,0.0011 c 0,0 -0.0259,-6.4e-4 -0.07527,-9.714e-4 -0.04726,3.33e-4 -0.07201,9.714e-4 -0.07201,9.714e-4 v -0.00113 C 11.337007,7.6453728 8.8132091,7.7001736 6.2821829,8.5024618 3.3627914,9.4276738 1.8521646,10.901973 1.8521646,10.901973 l -1.82398708,5.43e-4 C 0.03128403,10.899322 2.339143,8.5221038 5.799224,7.4329533 9.170444,6.2585642 11.971203,6.3517488 11.971203,6.3517488 Z",
iconColor: "#4EAA66",
@@ -150,7 +146,6 @@ export const configSections: Record<string, PageNavigation[]> = {
},
{
path: "/config/thread",
name: "Thread",
iconPath:
"m 17.126982,8.0730792 c 0,-0.7297242 -0.593746,-1.32357 -1.323637,-1.32357 -0.729454,0 -1.323199,0.5938458 -1.323199,1.32357 v 1.3234242 l 1.323199,1.458e-4 c 0.729891,0 1.323637,-0.5937006 1.323637,-1.32357 z M 11.999709,0 C 5.3829818,0 0,5.3838955 0,12.001455 0,18.574352 5.3105455,23.927406 11.865164,24 V 12.012075 l -3.9275642,-2.91e-4 c -1.1669814,0 -2.1169453,0.949979 -2.1169453,2.118323 0,1.16718 0.9499639,2.116868 2.1169453,2.116868 v 2.615717 c -2.6093089,0 -4.732218,-2.12327 -4.732218,-4.732585 0,-2.61048 2.1229091,-4.7343308 4.732218,-4.7343308 l 3.9275642,5.82e-4 v -1.323279 c 0,-2.172296 1.766691,-3.9395777 3.938181,-3.9395777 2.171928,0 3.9392,1.7672817 3.9392,3.9395777 0,2.1721498 -1.767272,3.9395768 -3.9392,3.9395768 l -1.323199,-1.45e-4 V 23.744102 C 19.911127,22.597726 24,17.768833 24,12.001455 24,5.3838955 18.616727,0 11.999709,0 Z",
iconColor: "#ED7744",
@@ -159,7 +154,6 @@ export const configSections: Record<string, PageNavigation[]> = {
},
{
path: "/config/bluetooth",
name: "Bluetooth",
iconPath: mdiBluetooth,
iconColor: "#0082FC",
component: "bluetooth",
@@ -167,7 +161,6 @@ export const configSections: Record<string, PageNavigation[]> = {
},
{
path: "/insteon",
name: "Insteon",
iconPath:
"m 12.001571,6.3842473 h 0.02973 c 3.652189,0 6.767389,-2.29456 7.987462,-5.5177193 L 15.389382,0 Z m 0,0 h -0.02972 c -3.6522186,0 -6.7673314,-2.2918546 -7.9874477,-5.5177193 h -0.00271 L 8.6111273,0 Z M 6.3840436,11.999287 v -0.02972 c 0,-3.6524074 -2.2944727,-6.7675928 -5.51754469,-7.9877383 L 0,8.6114473 Z m 0,0 v 0.02964 c 0,3.652378 -2.2917818,6.767578 -5.51754469,7.987796 v 0.0026 L 0,15.389818 Z M 24,8.6114473 23.133527,3.9818327 v 0.00269 C 19.907636,5.2046836 17.616,8.3198691 17.616,11.972276 v 0.02966 0.02972 0.0027 c 0,3.65232 2.2944,6.76752 5.517527,7.987738 L 24,15.392436 17.616,12.001935 Z M 20.018618,23.133527 15.389091,24 11.99872,17.615709 h 0.02964 c 3.652218,0 6.767418,2.291927 7.987491,5.517818 z M 11.99872,17.615709 8.6082618,24 3.9788364,23.133527 C 5.1989527,19.9104 8.3140655,17.615709 11.966284,17.615709 h 0.0027 z",
iconColor: "#E4002C",

View File

@@ -1447,7 +1447,14 @@
"app_info": "{app} info",
"shortcuts": "[%key:ui::panel::config::info::shortcuts%]",
"labs": "[%key:ui::panel::config::labs::caption%]",
"developer-tools": "[%key:ui::panel::config::dashboard::developer_tools::main%]"
"developer-tools": "[%key:ui::panel::config::dashboard::developer_tools::main%]",
"matter": "[%key:ui::panel::config::dashboard::matter::main%]",
"zha": "[%key:ui::panel::config::dashboard::zha::main%]",
"zwave_js": "[%key:ui::panel::config::dashboard::zwave_js::main%]",
"thread": "[%key:ui::panel::config::dashboard::thread::main%]",
"bluetooth": "[%key:ui::panel::config::dashboard::bluetooth::main%]",
"knx": "[%key:ui::panel::config::dashboard::knx::main%]",
"insteon": "[%key:ui::panel::config::dashboard::insteon::main%]"
}
},
"filter_placeholder": "Search entities",
@@ -2386,24 +2393,31 @@
"secondary": "Loading..."
},
"zwave_js": {
"main": "Z-Wave",
"secondary": "Sub-GHz mesh protocol"
},
"zha": {
"main": "Zigbee",
"secondary": "Low-power mesh network"
},
"matter": {
"main": "Matter",
"secondary": "Cross-vendor smart home standard"
},
"thread": {
"main": "Thread",
"secondary": "Mesh network often used for Matter devices"
},
"bluetooth": {
"main": "Bluetooth",
"secondary": "Local device connectivity"
},
"knx": {
"main": "KNX",
"secondary": "Building automation standard"
},
"insteon": {
"main": "Insteon",
"secondary": "Dual-mesh home automation"
}
},