Compare commits

...

3 Commits

Author SHA1 Message Date
Aidan Timson
0c16c78984 Remove entry ids and just use integration pages 2025-12-11 14:32:27 +00:00
Aidan Timson
767d43021f Fix 2025-12-11 14:31:33 +00:00
Aidan Timson
fbeab4e151 Add integrations quick bar 2025-12-11 14:28:40 +00:00
4 changed files with 120 additions and 3 deletions

View File

@@ -6,6 +6,7 @@ import {
mdiEarth,
mdiKeyboard,
mdiMagnify,
mdiPuzzle,
mdiReload,
mdiServerNetwork,
} from "@mdi/js";
@@ -94,12 +95,22 @@ interface DeviceItem extends QuickBarItem {
area?: string;
}
interface IntegrationItem extends QuickBarItem {
domain: string;
translatedDomain: 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;
const isIntegrationItem = (item: QuickBarItem): item is IntegrationItem =>
(item as DeviceItem).deviceId === undefined &&
(item as IntegrationItem).domain !== undefined &&
(item as IntegrationItem).translatedDomain !== undefined;
interface QuickBarNavigationItem extends CommandItem {
path: string;
}
@@ -121,6 +132,8 @@ export class QuickBar extends LitElement {
@state() private _deviceItems?: DeviceItem[];
@state() private _integrationItems?: IntegrationItem[];
@state() private _filter = "";
@state() private _search = "";
@@ -159,6 +172,8 @@ export class QuickBar extends LitElement {
this._search = "";
this._entityItems = undefined;
this._commandItems = undefined;
this._deviceItems = undefined;
this._integrationItems = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
@@ -174,6 +189,7 @@ export class QuickBar extends LitElement {
commandItems,
entityItems,
deviceItems,
integrationItems,
filter: string
) => {
let items = entityItems;
@@ -182,6 +198,8 @@ export class QuickBar extends LitElement {
items = commandItems;
} else if (mode === QuickBarMode.Device) {
items = deviceItems;
} else if (mode === QuickBarMode.Integration) {
items = integrationItems;
}
if (items && filter && filter !== " ") {
@@ -201,25 +219,37 @@ export class QuickBar extends LitElement {
this._commandItems,
this._entityItems,
this._deviceItems,
this._integrationItems,
this._filter
);
const translationKey =
this._mode === QuickBarMode.Device
? "filter_placeholder_devices"
: "filter_placeholder";
: this._mode === QuickBarMode.Integration
? "filter_placeholder_integrations"
: "filter_placeholder";
const placeholder = this.hass.localize(
`ui.dialogs.quick-bar.${translationKey}`
);
const commandMode = this._mode === QuickBarMode.Command;
const deviceMode = this._mode === QuickBarMode.Device;
const integrationMode = this._mode === QuickBarMode.Integration;
const icon = commandMode
? mdiConsoleLine
: deviceMode
? mdiDevices
: mdiMagnify;
const searchPrefix = commandMode ? ">" : deviceMode ? "#" : "";
: integrationMode
? mdiPuzzle
: mdiMagnify;
const searchPrefix = commandMode
? ">"
: deviceMode
? "#"
: integrationMode
? "@"
: "";
return html`
<ha-dialog
@@ -318,6 +348,9 @@ export class QuickBar extends LitElement {
} else if (this._mode === QuickBarMode.Device) {
this._deviceItems =
this._deviceItems || (await this._generateDeviceItems());
} else if (this._mode === QuickBarMode.Integration) {
this._integrationItems =
this._integrationItems || (await this._generateIntegrationItems());
} else {
this._entityItems =
this._entityItems || (await this._generateEntityItems());
@@ -352,6 +385,10 @@ export class QuickBar extends LitElement {
return this._renderCommandItem(item, index);
}
if (isIntegrationItem(item)) {
return this._renderIntegrationItem(item, index);
}
return this._renderEntityItem(item as EntityItem, index);
};
@@ -459,6 +496,35 @@ export class QuickBar extends LitElement {
`;
}
private _renderIntegrationItem(item: IntegrationItem, index?: number) {
return html`
<ha-md-list-item
class="two-line"
.item=${item}
index=${ifDefined(index)}
tabindex="0"
type="button"
>
<img
slot="start"
alt=""
crossorigin="anonymous"
referrerpolicy="no-referrer"
src=${brandsUrl({
domain: item.domain,
type: "icon",
useFallback: true,
darkOptimized: this.hass.themes?.darkMode,
})}
/>
<span slot="headline">${item.primaryText}</span>
<div slot="trailing-supporting-text" class="domain">
${item.translatedDomain}
</div>
</ha-md-list-item>
`;
}
private async _processItemAndCloseDialog(item: QuickBarItem, index: number) {
this._addSpinnerToCommandItem(index);
@@ -507,6 +573,9 @@ export class QuickBar extends LitElement {
} else if (newFilter.startsWith("#")) {
newMode = QuickBarMode.Device;
newSearch = newFilter.substring(1);
} else if (newFilter.startsWith("@")) {
newMode = QuickBarMode.Integration;
newSearch = newFilter.substring(1);
} else {
newMode = QuickBarMode.Entity;
newSearch = newFilter;
@@ -706,6 +775,43 @@ export class QuickBar extends LitElement {
);
}
private async _generateIntegrationItems(): Promise<IntegrationItem[]> {
const configEntries = await getConfigEntries(this.hass);
// Get unique domains from enabled entries
const domains = new Set<string>();
for (const entry of configEntries) {
if (entry.disabled_by || entry.source === "ignore") {
continue;
}
domains.add(entry.domain);
}
const integrationItems: IntegrationItem[] = [];
for (const domain of domains) {
const translatedDomain = domainToName(this.hass.localize, domain);
const primaryText = translatedDomain;
integrationItems.push({
id: `integration-${domain}`,
primaryText,
domain,
translatedDomain,
action: () => navigate(`/config/integrations/integration/${domain}`),
strings: [primaryText, domain, translatedDomain],
});
}
return integrationItems.sort((a, b) =>
caseInsensitiveStringCompare(
a.primaryText,
b.primaryText,
this.hass.locale.language
)
);
}
private async _generateCommandItems(): Promise<CommandItem[]> {
return [
...(await this._generateReloadCommands()),

View File

@@ -4,6 +4,7 @@ export const enum QuickBarMode {
Command = "command",
Device = "device",
Entity = "entity",
Integration = "integration",
}
export interface QuickBarParams {

View File

@@ -47,6 +47,9 @@ export default <T extends Constructor<HassElement>>(superClass: T) =>
case "d":
this._showQuickBar(ev.detail, QuickBarMode.Device);
break;
case "i":
this._showQuickBar(ev.detail, QuickBarMode.Integration);
break;
case "m":
this._createMyLink(ev.detail);
break;
@@ -70,6 +73,9 @@ export default <T extends Constructor<HassElement>>(superClass: T) =>
m: { handler: (ev) => this._createMyLink(ev) },
a: { handler: (ev) => this._showVoiceCommandDialog(ev) },
d: { handler: (ev) => this._showQuickBar(ev, QuickBarMode.Device) },
i: {
handler: (ev) => this._showQuickBar(ev, QuickBarMode.Integration),
},
// Workaround see https://github.com/jamiebuilds/tinykeys/issues/130
"Shift+?": { handler: (ev) => this._showShortcutDialog(ev) },
// Those are fallbacks for non-latin keyboards that don't have e, c, m keys (qwerty-based shortcuts)
@@ -78,6 +84,9 @@ export default <T extends Constructor<HassElement>>(superClass: T) =>
KeyM: { handler: (ev) => this._createMyLink(ev) },
KeyA: { handler: (ev) => this._showVoiceCommandDialog(ev) },
KeyD: { handler: (ev) => this._showQuickBar(ev, QuickBarMode.Device) },
KeyI: {
handler: (ev) => this._showQuickBar(ev, QuickBarMode.Integration),
},
});
}

View File

@@ -1407,6 +1407,7 @@
},
"filter_placeholder": "Search entities",
"filter_placeholder_devices": "Search devices",
"filter_placeholder_integrations": "Search integrations",
"title": "Quick search",
"key_c_tip": "[%key:ui::tips::key_c_tip%]",
"nothing_found": "Nothing found!"