Files
EZPPLauncher/src/lib/themeIconCatalog.ts
T

202 lines
5.6 KiB
TypeScript

import { THEME_ICON_BODIES, type ThemeIconBody } from './themeIconData';
import { DEFAULT_THEME_ICON_SET, THEME_ICON_NAMES, type ThemeIconSet } from './themeIcons';
import lucideCollection from '@iconify-json/lucide/icons.json';
import phCollection from '@iconify-json/ph/icons.json';
import tablerCollection from '@iconify-json/tabler/icons.json';
export type { ThemeIconBody };
export type IconPackKey = 'launcher' | 'lucide' | 'phosphor' | 'tabler';
export type ThemeIconCatalogItem = {
id: string;
name: string;
pack: IconPackKey;
packLabel: string;
};
type IconifyRawCollection = {
width?: number;
height?: number;
icons: Record<string, { body: string; width?: number; height?: number }>;
aliases?: Record<string, { parent: string }>;
};
const collections: Record<'lucide' | 'ph' | 'tabler', IconifyRawCollection> = {
lucide: lucideCollection as unknown as IconifyRawCollection,
ph: phCollection as unknown as IconifyRawCollection,
tabler: tablerCollection as unknown as IconifyRawCollection,
};
const resolveFromCollection = (
collection: IconifyRawCollection,
id: string
): ThemeIconBody | undefined => {
const direct = collection.icons[id];
if (direct) {
return {
width: direct.width ?? collection.width ?? 24,
height: direct.height ?? collection.height ?? 24,
body: direct.body,
};
}
const alias = collection.aliases?.[id];
if (alias) {
const parent = collection.icons[alias.parent];
if (parent) {
return {
width: parent.width ?? collection.width ?? 24,
height: parent.height ?? collection.height ?? 24,
body: parent.body,
};
}
}
return undefined;
};
const FALLBACK_ICON: ThemeIconBody = {
width: 24,
height: 24,
body: '<circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" stroke-width="2"/><path d="M12 8v4m0 4h.01" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>',
};
// Cache for quickly resolving icon bodies
const bodyCache = new Map<string, ThemeIconBody>();
/**
* Resolve any icon by name (semantic launcher name, prefixed like `lucide:bell`,
* `ph:sword`, `tabler:star`, or unprefixed matching an active/fallback pack).
*/
export function getThemeIconBody(
name: string | undefined | null,
set: ThemeIconSet = DEFAULT_THEME_ICON_SET
): ThemeIconBody {
if (!name || typeof name !== 'string') {
return THEME_ICON_BODIES[set]?.home ?? THEME_ICON_BODIES[DEFAULT_THEME_ICON_SET].home;
}
const cacheKey = `${set}:${name}`;
const cached = bodyCache.get(cacheKey);
if (cached) return cached;
// 1. Check if semantic launcher icon in THEME_ICON_BODIES
const semantic = (THEME_ICON_BODIES[set] as Record<string, ThemeIconBody> | undefined)?.[name];
if (semantic) {
bodyCache.set(cacheKey, semantic);
return semantic;
}
const semanticDefault = (
THEME_ICON_BODIES[DEFAULT_THEME_ICON_SET] as Record<string, ThemeIconBody> | undefined
)?.[name];
if (semanticDefault) {
bodyCache.set(cacheKey, semanticDefault);
return semanticDefault;
}
// 2. Check for pack prefix like "lucide:xxx", "ph:xxx" / "phosphor:xxx", "tabler:xxx"
if (name.includes(':')) {
const colonIdx = name.indexOf(':');
const prefix = name.slice(0, colonIdx).toLowerCase();
const iconId = name.slice(colonIdx + 1);
const packCol =
prefix === 'lucide'
? collections.lucide
: prefix === 'ph' || prefix === 'phosphor'
? collections.ph
: prefix === 'tabler'
? collections.tabler
: undefined;
if (packCol) {
const resolved = resolveFromCollection(packCol, iconId);
if (resolved) {
bodyCache.set(cacheKey, resolved);
return resolved;
}
}
}
// 3. Try current set collection
const currentCollectionKey = set === 'phosphor' ? 'ph' : set === 'tabler' ? 'tabler' : 'lucide';
const directCurrent = resolveFromCollection(collections[currentCollectionKey], name);
if (directCurrent) {
bodyCache.set(cacheKey, directCurrent);
return directCurrent;
}
// 4. Try other collections (lucide, ph, tabler)
const lucideResolved = resolveFromCollection(collections.lucide, name);
if (lucideResolved) {
bodyCache.set(cacheKey, lucideResolved);
return lucideResolved;
}
const phResolved = resolveFromCollection(collections.ph, name);
if (phResolved) {
bodyCache.set(cacheKey, phResolved);
return phResolved;
}
const tablerResolved = resolveFromCollection(collections.tabler, name);
if (tablerResolved) {
bodyCache.set(cacheKey, tablerResolved);
return tablerResolved;
}
// Fallback icon
return FALLBACK_ICON;
}
// Cached full catalog
let fullCatalog: ThemeIconCatalogItem[] | null = null;
export function getFullIconCatalog(): ThemeIconCatalogItem[] {
if (fullCatalog) return fullCatalog;
const items: ThemeIconCatalogItem[] = [];
// 1. Semantic Launcher icons
for (const name of THEME_ICON_NAMES) {
items.push({
id: name,
name,
pack: 'launcher',
packLabel: 'Launcher',
});
}
// 2. Lucide icons
for (const name of Object.keys(collections.lucide.icons)) {
items.push({
id: `lucide:${name}`,
name,
pack: 'lucide',
packLabel: 'Lucide',
});
}
// 3. Phosphor icons
for (const name of Object.keys(collections.ph.icons)) {
items.push({
id: `ph:${name}`,
name,
pack: 'phosphor',
packLabel: 'Phosphor',
});
}
// 4. Tabler icons
for (const name of Object.keys(collections.tabler.icons)) {
items.push({
id: `tabler:${name}`,
name,
pack: 'tabler',
packLabel: 'Tabler',
});
}
fullCatalog = items;
return items;
}