UNPKG

@sitecore/sc-contenthub-blok

Version:

A UI library for [Sitecore Content Hub](https://doc.sitecore.com/ch/).

133 lines 5.58 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.IconDownloadManager = void 0; const dompurify_1 = __importDefault(require("dompurify")); class IconDownloadManager { constructor() { this.domPurify = (0, dompurify_1.default)(window); this.domPurify.addHook("afterSanitizeAttributes", (node) => { const element = node; if (element.hasAttribute("xlink:href") && !element.getAttribute("xlink:href")?.match(/^#/)) { element.remove(); } }); } static MAX_CONCURRENT_ICON_LOAD = 20; static WAIT_TIME = 10; loadingIcons = []; domPurify; iconHtmlContentCache = new Map(); svgPathByNameCache = new Map(); domParser = new DOMParser(); getSvgPathString(icon) { const svgDoc = this.domParser.parseFromString(icon, "image/svg+xml"); // Apparently a few icons have more than 1 path so we need to grab them all. const svgPaths = svgDoc.querySelectorAll("path"); const svgPathSerializer = new XMLSerializer(); if (svgPaths) { const pathStrings = Array.from(svgPaths).map((svgPath) => svgPathSerializer.serializeToString(svgPath)); return pathStrings.join(""); } return ""; } getSanitizedIcon(iconName, svgPath) { return this.domPurify.sanitize(`<svg viewBox="0 0 24 24"><g id="content-hub-icon-${iconName}">${svgPath}</g></svg>`); } async wait() { // Without this, the page will enter an infinite loop because, // the event loop will not have time to process the network calls. await new Promise((resolve) => { setTimeout(resolve, IconDownloadManager.WAIT_TIME); }); if (this.loadingIcons.length >= IconDownloadManager.MAX_CONCURRENT_ICON_LOAD) { return this.wait(); } return Promise.resolve(); } /** * Loads svg path while making sure that total number of concurrent downloads never exceed MAX_CONCURRENT_ICON_LOAD. * @param iconName - Name of the icon * @param baseUrl - if provided, the icons will be fetched from that server * @param fetchCreator - A function that returns a preconfigured fetch function */ async getSvgPathCachedAsync(iconName, baseUrl, fetchCreator) { const iconSpecifier = `${iconName}:${baseUrl}`; const cacheItem = this.svgPathByNameCache.get(iconSpecifier); if (cacheItem) { return cacheItem; } let url = baseUrl; if (!url.endsWith("/")) { url += "/"; } url += `${iconName}.svg`; if (this.loadingIcons.length >= IconDownloadManager.MAX_CONCURRENT_ICON_LOAD) { await this.wait(); } if (this.loadingIcons.includes(iconSpecifier)) { throw new Error("isLoading"); } this.loadingIcons.push(iconSpecifier); const promise = fetchCreator(url); const response = await promise .catch((error) => { if (error instanceof DOMException) { return null; } throw error; }) .finally(() => { this.loadingIcons.splice(this.loadingIcons.indexOf(iconSpecifier), 1); }); if (!response) { throw new Error("canceled"); } if (!response.ok) { throw new Error(`failed to load the icon: got ${response.status.toString(10)}`); } const icon = await response.text(); const svgPath = this.getSvgPathString(icon); this.svgPathByNameCache.set(iconSpecifier, svgPath); return svgPath; } async getReferencedSvgIconAsync(iconName, baseUrl) { const iconSpecifier = `${iconName}:${baseUrl}`; const cachedIcon = this.iconHtmlContentCache.get(iconSpecifier); if (cachedIcon) { return cachedIcon; } // We reuse an existing icon from the dom keeping the dom smaller now it is blazing fast. const returnValue = this.domPurify.sanitize(`<svg viewBox="0 0 24 24"><use href="#content-hub-icon-${iconName}"></use></svg>`, { ADD_TAGS: ["use"], }); let svgPath; try { svgPath = await this.getSvgPathCachedAsync(iconName, baseUrl, (url) => fetch(url)); } catch (error) { if (error.message === "isLoading") { // When the icon data is loading, we don't need to load it again. We just need to reference it in the dom. return returnValue; } throw error; } // By adding a group with an id we can point to it for reuse. const iconsWrapper = document.getElementById("icons-wrapper"); const newIcon = this.getSanitizedIcon(iconName, svgPath); // We insert the icon in a place in the dom that is never going to be removed so that we are sure that the icon we are reusing is always in the DOM if (iconsWrapper) { iconsWrapper.innerHTML = `${iconsWrapper.innerHTML}${newIcon}`; } else { throw new Error("Failed to find the icon wrapper on the DOM."); } this.iconHtmlContentCache.set(iconSpecifier, returnValue); return returnValue; } } exports.IconDownloadManager = IconDownloadManager; //# sourceMappingURL=IconDownloadManager.js.map