// ici on désactive un regle de eslint exceptionnelement pour ce fichier /* eslint no-async-promise-executor: 0 */ // --> OFF import icons from "@supersoniks/concorde/core/components/ui/icon/icons.json"; import { unsafeHTML } from "lit/directives/unsafe-html.js"; import { log } from "@supersoniks/concorde/core/utils/api"; /** * Ce tableau static permet de ne pas appeler plusieurs fois le même service lors d'appel concurrents en GET. */ const loadingGetPromises: Map> = new Map(); /** * Les librairies en ligne. * Pour chaque identifiant de librairie a une template d'url de chargement * Les propriétés name et prefix de sonic-icon servent à remplir le template. * la propriété library de sonic-icon correspond à une clef de librairies. */ const libraries = { heroicons: { url: "https://cdn.jsdelivr.net/npm/heroicons@2.0.4/24/$prefix/$name.svg", defaultPrefix: "outline", }, iconoir: { url: "https://cdnjs.cloudflare.com/ajax/libs/iconoir/5.1.4/icons/$name.svg", }, feathers: { url: "https://cdn.jsdelivr.net/npm/feather-icons@4.29.0/dist/icons/$name.svg", }, lucide: { url: "https://cdn.jsdelivr.net/npm/lucide-static@0.16.29/icons/$name.svg", }, material: { url: "https://cdn.jsdelivr.net/npm/@material-icons/svg@1.0.5/svg/$name/$prefix.svg", defaultPrefix: "regular", }, fontAwesome: { url: "https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.15.1/svgs/$prefix/$name.svg", defaultPrefix: "regular", }, custom: { url: "", defaultPrefix: "" }, }; type Libraries = typeof libraries; type LibraryKey = keyof Libraries; type Library = Partial<{ url: string; defaultPrefix: string }>; let hasEnabledCustomLibrary = false; function enableCustomLibrary() { if (hasEnabledCustomLibrary) return; libraries.custom.url = document .querySelector("[customIconLibraryPath]") ?.getAttribute("customIconLibraryPath") || ""; libraries.custom.defaultPrefix = document .querySelector("[customIconDefaultPrefix]") ?.getAttribute("customIconDefaultPrefix") || ""; if (libraries.custom.url) { hasEnabledCustomLibrary = true; } } const iconCachStr = sessionStorage.getItem("sonicIconsCache"); const iconCache = iconCachStr ? JSON.parse(iconCachStr) : { icons: {}, names: [] }; const iconCacheMaxSize = 100; export declare type IconConf = { name: string; prefix?: string; library?: string; }; export default class Icons { static default = { get: async (params: IconConf) => { const library = params.library; if (!params.name) return ""; const name = params.name; const iconsAsRecord: Record> = icons; /** * SVGS en ligne */ if (library == "custom") { enableCustomLibrary(); } if (library && library in libraries) { const libraryItem = libraries[library as LibraryKey] as Library; const prefix: string = params.prefix || libraryItem.defaultPrefix || ""; const libIcons = iconsAsRecord[library] || ({} as Library); iconsAsRecord[library] = libIcons; const libIconsKey = prefix + "-" + name; /** * Si l'icone a déjà été chargée on ne la recharge pas */ if (libIcons[libIconsKey]) return unsafeHTML(libIcons[libIconsKey]); const url = (libraryItem.url || "") .replace("$prefix", prefix) .replace("$name", name); /** * MiniCache de session */ if (iconCache.icons[url]) { const cached = iconCache.icons[url]; const isSvgCached = /^\s*]/i.test(cached || ""); if (isSvgCached) { libIcons[libIconsKey] = cached; return unsafeHTML(cached); } delete iconCache.icons[url]; } /** * on utilise une promise mutualisée pour ne pas faire plusieurs appels concurents d'une même icone */ if (!loadingGetPromises.has(url)) { const promise = new Promise(async (resolve) => { try { const result = await fetch(url); if (!result.ok) { resolve(""); return; } try { // Vérifie le type de contenu pour éviter un fallback HTML (ex: service worker) const contentType = result.headers.get("content-type") || ""; if ( !contentType.includes("image/svg+xml") && !contentType.includes("svg") ) { resolve(""); return; } const text = await result.text(); // Vérifie que le contenu commence bien par une balise const isSvg = /^\s*]/i.test(text); resolve(isSvg ? text : ""); } catch (e) { resolve(""); } } catch (error) { log("concorde icon loading error", params); resolve(""); } }); loadingGetPromises.set(url, promise as Promise); } /** * Chargement de l'icone. */ const result = await loadingGetPromises.get(url); loadingGetPromises.delete(url); libIcons[libIconsKey] = result || ""; if (result && /^\s*]/i.test(result)) { iconCache.icons[url] = result; iconCache.names.push(url); } if (iconCache.names.length > iconCacheMaxSize) { const key = iconCache.names.shift(); delete iconCache.icons[key]; } sessionStorage.setItem("sonicIconsCache", JSON.stringify(iconCache)); return result ? unsafeHTML(result) : ""; } /** * svgs "locaux" */ return unsafeHTML(iconsAsRecord["core"][params.name] || ""); }, }; }