From a23c2baed8db4d4c30130bdff625a8958b3c6138 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Wed, 29 Oct 2025 01:38:42 +0900 Subject: [PATCH] refactor: Convert icon manager into class --- packages/mermaid/src/rendering-util/icons.ts | 144 +++++++++++-------- 1 file changed, 83 insertions(+), 61 deletions(-) diff --git a/packages/mermaid/src/rendering-util/icons.ts b/packages/mermaid/src/rendering-util/icons.ts index 65f35bc62..03c9ccd50 100644 --- a/packages/mermaid/src/rendering-util/icons.ts +++ b/packages/mermaid/src/rendering-util/icons.ts @@ -23,66 +23,100 @@ export const unknownIcon: IconifyIcon = { width: 80, }; -const iconsStore = new Map(); -const loaderStore = new Map(); +class IconManager { + private iconsStore = new Map(); + private loaderStore = new Map(); -export const registerIconPacks = (iconLoaders: IconLoader[]) => { - for (const iconLoader of iconLoaders) { - if (!iconLoader.name) { - throw new Error( - 'Invalid icon loader. Must have a "name" property with non-empty string value.' - ); - } - log.debug('Registering icon pack:', iconLoader.name); - if ('loader' in iconLoader) { - loaderStore.set(iconLoader.name, iconLoader.loader); - } else if ('icons' in iconLoader) { - iconsStore.set(iconLoader.name, iconLoader.icons); - } else { - log.error('Invalid icon loader:', iconLoader); - throw new Error('Invalid icon loader. Must have either "icons" or "loader" property.'); + registerIconPacks(iconLoaders: IconLoader[]): void { + for (const iconLoader of iconLoaders) { + if (!iconLoader.name) { + throw new Error( + 'Invalid icon loader. Must have a "name" property with non-empty string value.' + ); + } + log.debug('Registering icon pack:', iconLoader.name); + if ('loader' in iconLoader) { + this.loaderStore.set(iconLoader.name, iconLoader.loader); + } else if ('icons' in iconLoader) { + this.iconsStore.set(iconLoader.name, iconLoader.icons); + } else { + log.error('Invalid icon loader:', iconLoader); + throw new Error('Invalid icon loader. Must have either "icons" or "loader" property.'); + } } } -}; -const getRegisteredIconData = async (iconName: string, fallbackPrefix?: string) => { - const data = stringToIcon(iconName, true, fallbackPrefix !== undefined); - if (!data) { - throw new Error(`Invalid icon name: ${iconName}`); - } - const prefix = data.prefix || fallbackPrefix; - if (!prefix) { - throw new Error(`Icon name must contain a prefix: ${iconName}`); - } - let icons = iconsStore.get(prefix); - if (!icons) { - const loader = loaderStore.get(prefix); - if (!loader) { - throw new Error(`Icon set not found: ${data.prefix}`); + private async getRegisteredIconData( + iconName: string, + fallbackPrefix?: string + ): Promise { + const data = stringToIcon(iconName, true, fallbackPrefix !== undefined); + if (!data) { + throw new Error(`Invalid icon name: ${iconName}`); } + const prefix = data.prefix || fallbackPrefix; + if (!prefix) { + throw new Error(`Icon name must contain a prefix: ${iconName}`); + } + let icons = this.iconsStore.get(prefix); + if (!icons) { + const loader = this.loaderStore.get(prefix); + if (!loader) { + throw new Error(`Icon set not found: ${data.prefix}`); + } + try { + const loaded = await loader(); + icons = { ...loaded, prefix }; + this.iconsStore.set(prefix, icons); + } catch (e) { + log.error(e); + throw new Error(`Failed to load icon set: ${data.prefix}`); + } + } + const iconData = getIconData(icons, data.name); + if (!iconData) { + throw new Error(`Icon not found: ${iconName}`); + } + return iconData; + } + + async isIconAvailable(iconName: string): Promise { try { - const loaded = await loader(); - icons = { ...loaded, prefix }; - iconsStore.set(prefix, icons); + await this.getRegisteredIconData(iconName); + return true; + } catch { + return false; + } + } + + async getIconSVG( + iconName: string, + customisations?: IconifyIconCustomisations & { fallbackPrefix?: string }, + extraAttributes?: Record + ): Promise { + let iconData: ExtendedIconifyIcon; + try { + iconData = await this.getRegisteredIconData(iconName, customisations?.fallbackPrefix); } catch (e) { log.error(e); - throw new Error(`Failed to load icon set: ${data.prefix}`); + iconData = unknownIcon; } + const renderData = iconToSVG(iconData, customisations); + const svg = iconToHTML(replaceIDs(renderData.body), { + ...renderData.attributes, + ...extraAttributes, + }); + return sanitizeText(svg, getConfig()); } - const iconData = getIconData(icons, data.name); - if (!iconData) { - throw new Error(`Icon not found: ${iconName}`); - } - return iconData; -}; +} +const globalIconManager = new IconManager(); + +// Export the singleton instance methods for backward compatibility +export const registerIconPacks = (iconLoaders: IconLoader[]) => + globalIconManager.registerIconPacks(iconLoaders); export const isIconAvailable = async (iconName: string) => { - try { - await getRegisteredIconData(iconName); - return true; - } catch { - return false; - } + return await globalIconManager.isIconAvailable(iconName); }; export const getIconSVG = async ( @@ -90,17 +124,5 @@ export const getIconSVG = async ( customisations?: IconifyIconCustomisations & { fallbackPrefix?: string }, extraAttributes?: Record ) => { - let iconData: ExtendedIconifyIcon; - try { - iconData = await getRegisteredIconData(iconName, customisations?.fallbackPrefix); - } catch (e) { - log.error(e); - iconData = unknownIcon; - } - const renderData = iconToSVG(iconData, customisations); - const svg = iconToHTML(replaceIDs(renderData.body), { - ...renderData.attributes, - ...extraAttributes, - }); - return sanitizeText(svg, getConfig()); + return await globalIconManager.getIconSVG(iconName, customisations, extraAttributes); };