@itwin/core-react
Version:
A react component library of iTwin.js UI general purpose components
109 lines • 4.24 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module Utilities
*/
import { UiError } from "@itwin/appui-abstract";
import { Logger } from "@itwin/core-bentley";
import { UiCore } from "../UiCore.js";
import DOMPurify from "dompurify";
import { reuseOrCreatePromise } from "./reuseOrCreatePromise.js";
/* eslint-disable @typescript-eslint/no-deprecated */
/**
* "getSvg" list
* (if multiple icon require the same thing,
* only do the fetch once, and have all icon use the same result)
*/
const cache = new Map();
/**
* Parse 'data:' uri to retrieve the Svg component within it.
* @param src data:image/svg+xml;base64 or data:image/svg+xml
* @param element Element for logging purpose.
* @returns HTMLElement (svg)
*/
function parseSvgFromDataUri(src, element) {
const dataUriParts = src.split(",");
if ((dataUriParts.length !== 2 &&
"data:image/svg+xml;base64" === dataUriParts[0]) ||
("data:image/svg+xml;base64" !== dataUriParts[0] &&
"data:image/svg+xml" !== dataUriParts[0])) {
Logger.logError(UiCore.loggerCategory(element), "Unable to load icon.");
return;
}
let rawSvg = "";
if ("data:image/svg+xml;base64" === dataUriParts[0]) {
rawSvg = window.atob(dataUriParts[1]);
}
else {
// `,` is valid character in data when not in base64, rebuild the data correctly
rawSvg = decodeURIComponent(dataUriParts.slice(1).join(","));
}
const sanitizedSvg = DOMPurify.sanitize(rawSvg);
const parsedSvg = new window.DOMParser().parseFromString(sanitizedSvg, "text/xml");
const errorNode = parsedSvg.querySelector("parsererror");
if (errorNode || "svg" !== parsedSvg.documentElement.nodeName.toLowerCase()) {
throw new UiError(UiCore.loggerCategory(element), "Unable to load icon.");
}
return parsedSvg.documentElement;
}
/**
* Fetch the src from the network and return the SVG element.
* @param src URL representing a .svg file
* @param element Element for logging purpose
* @returns HTMLElement (svg)
*/
async function fetchSvg(src, element) {
const response = await fetch(src).catch((_error) => {
Logger.logError(UiCore.loggerCategory(element), "Unable to load icon.");
});
if (!response || !response.ok) {
throw new UiError(UiCore.loggerCategory(element), "Unable to load icon.");
}
const str = await response.text();
if (str === undefined) {
throw new UiError(UiCore.loggerCategory(element), "Unable to load icon.");
}
const data = new window.DOMParser().parseFromString(str, "text/xml");
return data.documentElement;
}
/**
* Get the svg (fetch and parse for url, or decode and parse for data: url)
* @param src URL of the content to download/parse
* @param element Element for logging purpose.
* @returns HTMLElement (svg)
*/
async function getSvg(src, element) {
if (src.startsWith("data:")) {
return parseSvgFromDataUri(src, element);
}
return fetchSvg(src, element);
}
/** IconWebComponent loads icon from an svg path
* @internal
*/
export class IconWebComponent extends HTMLElement {
async connectedCallback() {
await this.loadSvg();
this.dispatchEvent(new CustomEvent("load"));
}
async loadSvg() {
// if svg was already appended don't request it again
if (this.childNodes.length)
return;
const src = this.getAttribute("src") || "";
if (!src)
return;
const svg = await reuseOrCreatePromise(src, async () => getSvg(src, this), cache);
if (svg && !this.childNodes.length) {
this.append(svg.cloneNode(true));
}
}
}
/** @internal */
export function registerIconWebComponent() {
if (window.customElements.get("svg-loader") === undefined)
window.customElements.define("svg-loader", IconWebComponent);
}
//# sourceMappingURL=IconWebComponent.js.map