Refactor icon loading
parent
01d2ef13c6
commit
31c56eb8b5
|
@ -2,34 +2,9 @@ import type { PropertyValues } from "lit";
|
|||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import type { CustomIcon } from "../data/custom_icons";
|
||||
import { customIcons } from "../data/custom_icons";
|
||||
import type { Chunks, Icons } from "../data/iconsets";
|
||||
import {
|
||||
MDI_PREFIXES,
|
||||
findIconChunk,
|
||||
getIcon,
|
||||
writeCache,
|
||||
} from "../data/iconsets";
|
||||
import { loadIcon } from "../data/load_icon";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
type DeprecatedIcon = Record<
|
||||
string,
|
||||
{
|
||||
removeIn: string;
|
||||
newName?: string;
|
||||
}
|
||||
>;
|
||||
|
||||
const mdiDeprecatedIcons: DeprecatedIcon = {};
|
||||
|
||||
const chunks: Chunks = {};
|
||||
|
||||
const debouncedWriteCache = debounce(() => writeCache(chunks), 2000);
|
||||
|
||||
const cachedIcons: Record<string, string> = {};
|
||||
|
||||
@customElement("ha-icon")
|
||||
export class HaIcon extends LitElement {
|
||||
@property() public icon?: string;
|
||||
|
@ -71,118 +46,24 @@ export class HaIcon extends LitElement {
|
|||
if (!this.icon) {
|
||||
return;
|
||||
}
|
||||
const requestedIcon = this.icon;
|
||||
const [iconPrefix, origIconName] = this.icon.split(":", 2);
|
||||
const result = await loadIcon(this.icon, this._handleWarning);
|
||||
|
||||
let iconName = origIconName;
|
||||
|
||||
if (!iconPrefix || !iconName) {
|
||||
if (result.icon !== this.icon) {
|
||||
// The icon was changed while we were loading it, so we don't update the state
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MDI_PREFIXES.includes(iconPrefix)) {
|
||||
const customIcon = customIcons[iconPrefix];
|
||||
if (customIcon) {
|
||||
if (customIcon && typeof customIcon.getIcon === "function") {
|
||||
this._setCustomPath(customIcon.getIcon(iconName), requestedIcon);
|
||||
}
|
||||
return;
|
||||
}
|
||||
this._legacy = true;
|
||||
return;
|
||||
this._legacy = result.legacy || false;
|
||||
this._path = result.path;
|
||||
this._secondaryPath = result.secondaryPath;
|
||||
this._viewBox = result.viewBox;
|
||||
}
|
||||
|
||||
this._legacy = false;
|
||||
|
||||
if (iconName in mdiDeprecatedIcons) {
|
||||
const deprecatedIcon = mdiDeprecatedIcons[iconName];
|
||||
let message: string;
|
||||
|
||||
if (deprecatedIcon.newName) {
|
||||
message = `Icon ${iconPrefix}:${iconName} was renamed to ${iconPrefix}:${deprecatedIcon.newName}, please change your config, it will be removed in version ${deprecatedIcon.removeIn}.`;
|
||||
iconName = deprecatedIcon.newName!;
|
||||
} else {
|
||||
message = `Icon ${iconPrefix}:${iconName} was removed from MDI, please replace this icon with an other icon in your config, it will be removed in version ${deprecatedIcon.removeIn}.`;
|
||||
}
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(message);
|
||||
private _handleWarning = (message: string) => {
|
||||
fireEvent(this, "write_log", {
|
||||
level: "warning",
|
||||
message,
|
||||
});
|
||||
}
|
||||
|
||||
if (iconName in cachedIcons) {
|
||||
this._path = cachedIcons[iconName];
|
||||
return;
|
||||
}
|
||||
|
||||
if (iconName === "home-assistant") {
|
||||
const icon = (await import("../resources/home-assistant-logo-svg"))
|
||||
.mdiHomeAssistant;
|
||||
|
||||
if (this.icon === requestedIcon) {
|
||||
this._path = icon;
|
||||
}
|
||||
cachedIcons[iconName] = icon;
|
||||
return;
|
||||
}
|
||||
|
||||
let databaseIcon: string | undefined;
|
||||
try {
|
||||
databaseIcon = await getIcon(iconName);
|
||||
} catch (_err) {
|
||||
// Firefox in private mode doesn't support IDB
|
||||
// iOS Safari sometimes doesn't open the DB
|
||||
databaseIcon = undefined;
|
||||
}
|
||||
|
||||
if (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, requestedIcon);
|
||||
return;
|
||||
}
|
||||
|
||||
const iconPromise = fetch(`/static/mdi/${chunk}.json`).then((response) =>
|
||||
response.json()
|
||||
);
|
||||
chunks[chunk] = iconPromise;
|
||||
this._setPath(iconPromise, iconName, requestedIcon);
|
||||
debouncedWriteCache();
|
||||
}
|
||||
|
||||
private async _setCustomPath(
|
||||
promise: Promise<CustomIcon>,
|
||||
requestedIcon: string
|
||||
) {
|
||||
const icon = await promise;
|
||||
if (this.icon !== requestedIcon) {
|
||||
return;
|
||||
}
|
||||
this._path = icon.path;
|
||||
this._secondaryPath = icon.secondaryPath;
|
||||
this._viewBox = icon.viewBox;
|
||||
}
|
||||
|
||||
private async _setPath(
|
||||
promise: Promise<Icons>,
|
||||
iconName: string,
|
||||
requestedIcon: string
|
||||
) {
|
||||
const iconPack = await promise;
|
||||
if (this.icon === requestedIcon) {
|
||||
this._path = iconPack[iconName];
|
||||
}
|
||||
cachedIcons[iconName] = iconPack[iconName];
|
||||
}
|
||||
};
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
import { debounce } from "../common/util/debounce";
|
||||
import { customIcons } from "./custom_icons";
|
||||
import {
|
||||
findIconChunk,
|
||||
getIcon,
|
||||
MDI_PREFIXES,
|
||||
writeCache,
|
||||
type Chunks,
|
||||
} from "./iconsets";
|
||||
|
||||
interface IconLoadResult {
|
||||
icon: string;
|
||||
legacy?: boolean;
|
||||
path?: string;
|
||||
secondaryPath?: string;
|
||||
viewBox?: string;
|
||||
}
|
||||
|
||||
type DeprecatedIcon = Record<
|
||||
string,
|
||||
{
|
||||
removeIn: string;
|
||||
newName?: string;
|
||||
}
|
||||
>;
|
||||
|
||||
const chunks: Chunks = {};
|
||||
|
||||
const debouncedWriteCache = debounce(() => writeCache(chunks), 2000);
|
||||
|
||||
const cachedIcons: Record<string, string> = {};
|
||||
|
||||
const mdiDeprecatedIcons: DeprecatedIcon = {};
|
||||
|
||||
export const loadIcon = async (
|
||||
icon: string,
|
||||
warningCallback?: (message) => void
|
||||
): Promise<IconLoadResult> => {
|
||||
const [iconPrefix, origIconName] = icon.split(":", 2);
|
||||
|
||||
let iconName = origIconName;
|
||||
|
||||
if (!iconPrefix || !iconName) {
|
||||
return {
|
||||
icon,
|
||||
};
|
||||
}
|
||||
|
||||
if (!MDI_PREFIXES.includes(iconPrefix)) {
|
||||
const customIcon = customIcons[iconPrefix];
|
||||
if (customIcon) {
|
||||
if (customIcon && typeof customIcon.getIcon === "function") {
|
||||
const custom = await customIcon.getIcon(iconName);
|
||||
return {
|
||||
icon,
|
||||
path: custom.path,
|
||||
secondaryPath: custom.secondaryPath,
|
||||
viewBox: custom.viewBox,
|
||||
};
|
||||
}
|
||||
return {
|
||||
icon,
|
||||
};
|
||||
}
|
||||
return {
|
||||
icon,
|
||||
legacy: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (iconName in mdiDeprecatedIcons) {
|
||||
const deprecatedIcon = mdiDeprecatedIcons[iconName];
|
||||
let message: string;
|
||||
|
||||
if (deprecatedIcon.newName) {
|
||||
message = `Icon ${iconPrefix}:${iconName} was renamed to ${iconPrefix}:${deprecatedIcon.newName}, please change your config, it will be removed in version ${deprecatedIcon.removeIn}.`;
|
||||
iconName = deprecatedIcon.newName!;
|
||||
} else {
|
||||
message = `Icon ${iconPrefix}:${iconName} was removed from MDI, please replace this icon with an other icon in your config, it will be removed in version ${deprecatedIcon.removeIn}.`;
|
||||
}
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(message);
|
||||
if (warningCallback) {
|
||||
warningCallback(message);
|
||||
}
|
||||
}
|
||||
|
||||
if (iconName in cachedIcons) {
|
||||
return {
|
||||
icon,
|
||||
path: cachedIcons[iconName],
|
||||
};
|
||||
}
|
||||
|
||||
if (iconName === "home-assistant") {
|
||||
const ha = (await import("../resources/home-assistant-logo-svg"))
|
||||
.mdiHomeAssistant;
|
||||
|
||||
cachedIcons[iconName] = ha;
|
||||
return {
|
||||
icon,
|
||||
path: ha,
|
||||
};
|
||||
}
|
||||
|
||||
let databaseIcon: string | undefined;
|
||||
try {
|
||||
databaseIcon = await getIcon(iconName);
|
||||
} catch (_err) {
|
||||
// Firefox in private mode doesn't support IDB
|
||||
// iOS Safari sometimes doesn't open the DB
|
||||
databaseIcon = undefined;
|
||||
}
|
||||
|
||||
if (databaseIcon) {
|
||||
cachedIcons[iconName] = databaseIcon;
|
||||
return {
|
||||
icon,
|
||||
path: databaseIcon,
|
||||
};
|
||||
}
|
||||
const chunk = findIconChunk(iconName);
|
||||
|
||||
if (chunk in chunks) {
|
||||
const iconPack = await chunks[chunk];
|
||||
return {
|
||||
icon,
|
||||
path: iconPack[iconName],
|
||||
};
|
||||
}
|
||||
|
||||
const iconPromise = fetch(`/static/mdi/${chunk}.json`).then((response) =>
|
||||
response.json()
|
||||
);
|
||||
chunks[chunk] = iconPromise;
|
||||
debouncedWriteCache();
|
||||
const iconPack = await iconPromise;
|
||||
return {
|
||||
icon,
|
||||
path: iconPack[iconName],
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue