@sitecore/sc-contenthub-blok
Version:
A UI library for [Sitecore Content Hub](https://doc.sitecore.com/ch/).
133 lines • 5.58 kB
JavaScript
;
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