From df572d59c58c3c24df98c8d5af3167babd8b835a Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 28 Oct 2021 15:28:14 +0200 Subject: [PATCH] Custom iconsets in Icon Picker (#10399) Co-authored-by: Bram Kragten --- src/components/ha-icon-picker.ts | 30 +++++++++++++++++++++++- src/components/ha-icon.ts | 39 +++++++++++++++++++++---------- src/data/custom_icons.ts | 40 ++++++++++++++++++++++++++++++++ src/data/custom_iconsets.ts | 7 ++---- 4 files changed, 98 insertions(+), 18 deletions(-) create mode 100644 src/data/custom_icons.ts diff --git a/src/components/ha-icon-picker.ts b/src/components/ha-icon-picker.ts index 129e0d2c5c..9197d5bb7b 100644 --- a/src/components/ha-icon-picker.ts +++ b/src/components/ha-icon-picker.ts @@ -7,6 +7,7 @@ import { css, html, LitElement, TemplateResult } from "lit"; import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers"; import { customElement, property, query, state } from "lit/decorators"; import { fireEvent } from "../common/dom/fire_event"; +import { customIcons } from "../data/custom_icons"; import { PolymerChangedEvent } from "../polymer-types"; import "./ha-icon"; import "./ha-icon-button"; @@ -111,11 +112,36 @@ export class HaIconPicker extends LitElement { this._opened = ev.detail.value; if (this._opened && !iconItems.length) { const iconList = await import("../../build/mdi/iconList.json"); + iconItems = iconList.default.map((icon) => ({ icon: `mdi:${icon.name}`, keywords: icon.keywords, })); + (this.comboBox as any).filteredItems = iconItems; + + Object.keys(customIcons).forEach((iconSet) => { + this._loadCustomIconItems(iconSet); + }); + } + } + + private async _loadCustomIconItems(iconsetPrefix: string) { + try { + const getIconList = customIcons[iconsetPrefix].getIconList; + if (typeof getIconList !== "function") { + return; + } + const iconList = await getIconList(); + const customIconItems = iconList.map((icon) => ({ + icon: `${iconsetPrefix}:${icon.name}`, + keywords: icon.keywords ?? [], + })); + iconItems.push(...customIconItems); + (this.comboBox as any).filteredItems = iconItems; + } catch (e) { + // eslint-disable-next-line + console.warn(`Unable to load icon list for ${iconsetPrefix} iconset`); } } @@ -158,7 +184,9 @@ export class HaIconPicker extends LitElement { if (filteredItems.length > 0) { (this.comboBox as any).filteredItems = filteredItems; } else { - (this.comboBox as any).filteredItems = [filterString]; + (this.comboBox as any).filteredItems = [ + { icon: filterString, keywords: [] }, + ]; } } else { (this.comboBox as any).filteredItems = iconItems; diff --git a/src/components/ha-icon.ts b/src/components/ha-icon.ts index 5a353e946e..b6279d6ac0 100644 --- a/src/components/ha-icon.ts +++ b/src/components/ha-icon.ts @@ -10,7 +10,7 @@ import { import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../common/dom/fire_event"; import { debounce } from "../common/util/debounce"; -import { CustomIcon, customIconsets } from "../data/custom_iconsets"; +import { CustomIcon, customIcons } from "../data/custom_icons"; import { checkCacheVersion, Chunks, @@ -356,7 +356,7 @@ export class HaIcon extends LitElement { @state() private _path?: string; - @state() private _viewBox?; + @state() private _viewBox?: string; @state() private _legacy = false; @@ -386,6 +386,7 @@ export class HaIcon extends LitElement { if (!this.icon) { return; } + const requestedIcon = this.icon; const [iconPrefix, origIconName] = this.icon.split(":", 2); let iconName = origIconName; @@ -395,10 +396,10 @@ export class HaIcon extends LitElement { } if (!MDI_PREFIXES.includes(iconPrefix)) { - if (iconPrefix in customIconsets) { - const customIconset = customIconsets[iconPrefix]; - if (customIconset) { - this._setCustomPath(customIconset(iconName)); + if (iconPrefix in customIcons) { + const customIcon = customIcons[iconPrefix]; + if (customIcon && typeof customIcon.getIcon === "function") { + this._setCustomPath(customIcon.getIcon(iconName), requestedIcon); } return; } @@ -441,14 +442,16 @@ export class HaIcon extends LitElement { } if (databaseIcon) { - this._path = databaseIcon; + if (this.icon === requestedIcon) { + this._path = databaseIcon; + } cachedIcons[iconName] = databaseIcon; return; } const chunk = findIconChunk(iconName); if (chunk in chunks) { - this._setPath(chunks[chunk], iconName); + this._setPath(chunks[chunk], iconName, requestedIcon); return; } @@ -456,19 +459,31 @@ export class HaIcon extends LitElement { response.json() ); chunks[chunk] = iconPromise; - this._setPath(iconPromise, iconName); + this._setPath(iconPromise, iconName, requestedIcon); debouncedWriteCache(); } - private async _setCustomPath(promise: Promise) { + private async _setCustomPath( + promise: Promise, + requestedIcon: string + ) { const icon = await promise; + if (this.icon !== requestedIcon) { + return; + } this._path = icon.path; this._viewBox = icon.viewBox; } - private async _setPath(promise: Promise, iconName: string) { + private async _setPath( + promise: Promise, + iconName: string, + requestedIcon: string + ) { const iconPack = await promise; - this._path = iconPack[iconName]; + if (this.icon === requestedIcon) { + this._path = iconPack[iconName]; + } cachedIcons[iconName] = iconPack[iconName]; } diff --git a/src/data/custom_icons.ts b/src/data/custom_icons.ts new file mode 100644 index 0000000000..1fad2b9a66 --- /dev/null +++ b/src/data/custom_icons.ts @@ -0,0 +1,40 @@ +import { customIconsets } from "./custom_iconsets"; + +export interface CustomIcon { + path: string; + viewBox?: string; +} + +export interface CustomIconListItem { + name: string; + keywords?: string[]; +} + +export interface CustomIconHelpers { + getIcon: (name: string) => Promise; + getIconList?: () => Promise; +} + +export interface CustomIconsWindow { + customIcons?: { + [key: string]: CustomIconHelpers; + }; +} + +const customIconsWindow = window as CustomIconsWindow; + +if (!("customIcons" in customIconsWindow)) { + customIconsWindow.customIcons = {}; +} + +// Proxy for backward compatibility with icon sets +export const customIcons = new Proxy(customIconsWindow.customIcons!, { + get: (obj, prop: string) => + obj[prop] ?? + (customIconsets[prop] + ? { + getIcon: customIconsets[prop], + } + : undefined), + has: (obj, prop: string) => prop in obj || prop in customIconsets, +}); diff --git a/src/data/custom_iconsets.ts b/src/data/custom_iconsets.ts index 670c4041e2..3462c8e7a7 100644 --- a/src/data/custom_iconsets.ts +++ b/src/data/custom_iconsets.ts @@ -1,9 +1,6 @@ -export interface CustomIcon { - path: string; - viewBox?: string; -} +import { CustomIcon } from "./custom_icons"; -export interface CustomIconsetsWindow { +interface CustomIconsetsWindow { customIconsets?: { [key: string]: (name: string) => Promise }; }