Aliases and external mappings

This commit is contained in:
Lea Verou
2025-05-08 14:48:30 -04:00
parent ad539a00f2
commit b85e6bcdf3
4 changed files with 79 additions and 20 deletions

View File

@@ -9,14 +9,28 @@ export const CACHEABLE_HTTP_ERRORS = [410];
let parser: DOMParser;
const defaultFallback = function (name: string, family?: string, variant?: string): IconLocator | undefined {
if (this.name !== 'wa') {
return {
name,
family,
variant,
library: 'wa',
};
}
return undefined;
};
export default class IconLibrary {
/** The original object used to create this library */
private spec: UnregisteredIconLibrary;
readonly spec: UnregisteredIconLibrary;
readonly name: string;
readonly mutator?: IconLibraryMutator;
readonly system?: IconMapping;
readonly spriteSheet?: boolean;
readonly fallback?: IconMapping;
/** Inlined markup, keyed by URL */
inlined: IconLibraryCacheFlat = {};
@@ -33,6 +47,7 @@ export default class IconLibrary {
this.mutator = library.mutator;
this.system = library.system;
this.spriteSheet = library.spriteSheet;
this.fallback = library.fallback ?? defaultFallback;
if (library.inlined) {
this.inline(library.inlined);
@@ -42,7 +57,7 @@ export default class IconLibrary {
/**
* Convert an icon name, family, and variant into a URL
*/
getUrl(name: string, family?: string, variant?: string) {
getUrl(name: string, family?: string, variant?: string): string {
// console.warn('getUrl', name, family, variant);
if (name.startsWith('system:')) {
name = name.slice(7);
@@ -54,6 +69,14 @@ export default class IconLibrary {
name = resolved.name ?? name;
family = resolved.family ?? family;
variant = resolved.variant ?? variant;
if (resolved.library && resolved.library !== this.name) {
let library = IconLibrary.registry.get(resolved.library);
if (library) {
return library.getUrl(name, family, variant);
}
}
}
}
}
@@ -125,7 +148,15 @@ export default class IconLibrary {
// Try again with fallback
let fallback = this.spec.fallback(name, family, variant);
if (fallback) {
return this.getElement(fallback.name, fallback.family, fallback.variant);
let library: IconLibrary | undefined = this;
if (fallback.library && fallback.library !== this.name) {
library = IconLibrary.registry.get(fallback.library);
}
if (library) {
return library.getElement(fallback.name, fallback.family, fallback.variant);
}
}
}
@@ -195,14 +226,20 @@ export default class IconLibrary {
Object.assign(this.inlined, flatCache);
}
/**
* Create a clone of this library, optionally overriding some of its properties.
*/
extend(library: Partial<UnregisteredIconLibrary> = {}) {
return new IconLibrary({ ...this.spec, ...library });
}
static registry = new Map<string, IconLibrary>();
}
export type IconLibraryResolver = (name: string, family?: string, variant?: string) => string;
export type IconMapping = (
name: string,
family?: string,
variant?: string,
) => { name: string; family?: string; variant?: string; library?: string } | undefined;
export type IconLocator = { name: string; family?: string; variant?: string; library?: string };
export type IconMapping = (name: string, family?: string, variant?: string) => IconLocator | undefined;
export type IconLibraryGetKey = (name: string) => string;
export type IconLibraryMutator = (svg: SVGElement) => void;
export type IconFetchedResult = string | typeof CACHEABLE_ERROR | typeof RETRYABLE_ERROR;

View File

@@ -108,7 +108,7 @@ export const inlined: IconLibraryCacheDeep = {
};
const library: UnregisteredIconLibrary = {
name: 'default',
name: 'wa',
getUrl: getIconUrl,
// Cache icons using the free URL
getCacheKey: url => url.replace(/\?token=[^&]+/, '').replace(/ka-p\./, 'ka-f.'),

View File

@@ -1,5 +1,4 @@
import type WaIcon from './icon.js';
import defaultLibrary from './library.default.js';
import IconLibrary, {
CACHEABLE_ERROR,
RETRYABLE_ERROR,
@@ -9,14 +8,16 @@ import IconLibrary, {
type IconLibraryCacheFlat,
type UnregisteredIconLibrary,
} from './library.js';
import waDefaultLibrary from './library.wa.js';
export { CACHEABLE_ERROR, RETRYABLE_ERROR, fetchIcon };
export type { IconFetchedResult, IconLibrary, IconLibraryCacheDeep, IconLibraryCacheFlat, UnregisteredIconLibrary };
let registry: IconLibrary[] = [];
let registry = IconLibrary.registry;
let watchedIcons: WaIcon[] = [];
registerIconLibrary(defaultLibrary);
registerIconLibrary(waDefaultLibrary);
registerIconLibrary('default', waDefaultLibrary);
registerIconLibrary({ name: 'custom' });
/** Adds an icon to the list of watched icons. */
@@ -31,16 +32,37 @@ export function unwatchIcon(icon: WaIcon) {
/** Returns a library from the registry. */
export function getIconLibrary(name?: string) {
return registry.find(lib => lib.name === name);
return name ? registry.get(name) : undefined;
}
/** Adds an icon library to the registry, or overrides an existing one. */
export function registerIconLibrary(library: UnregisteredIconLibrary) {
unregisterIconLibrary(library.name);
/**
* Adds an icon library to the registry, or overrides an existing one.
* Optionally accepts a name argument, which will override the library's built-in name, allowing you to register aliases.
*/
export function registerIconLibrary(name: string, library: UnregisteredIconLibrary | IconLibrary): void;
export function registerIconLibrary(library: UnregisteredIconLibrary | IconLibrary): void;
export function registerIconLibrary(
nameOrLibrary: string | UnregisteredIconLibrary | IconLibrary,
library?: UnregisteredIconLibrary | IconLibrary,
) {
let name;
if (typeof nameOrLibrary === 'string') {
name = nameOrLibrary;
} else {
library = nameOrLibrary;
}
let registeredLibrary = new IconLibrary(library);
if (!library) {
throw new Error('No library provided');
}
registry.push(registeredLibrary);
let instance = library instanceof IconLibrary ? library : new IconLibrary(library);
if (name) {
instance = instance.extend({ name });
}
registry.set(instance.name, instance);
// Redraw watched icons
watchedIcons.forEach(icon => {
@@ -52,5 +74,5 @@ export function registerIconLibrary(library: UnregisteredIconLibrary) {
/** Removes an icon library from the registry. */
export function unregisterIconLibrary(name: string) {
registry = registry.filter(lib => lib.name !== name);
registry.delete(name);
}

View File

@@ -1,4 +1,4 @@
export { getKitCode, setKitCode } from './components/icon/library.default.js';
export { getKitCode, setKitCode } from './components/icon/library.wa.js';
export { registerIconLibrary, unregisterIconLibrary } from './components/icon/registry.js';
export { discover, preventTurboFouce, startLoader, stopLoader } from './utilities/autoloader.js';
export { getBasePath, setBasePath } from './utilities/base-path.js';