返回 presentation-ai
infographic-icon-loader.ts
根目录 / src / hooks / presentation / infographic / infographic-icon-loader.ts
1 import { registerResourceLoader } from "@antv/infographic";
2 import React from "react";
3 import { renderToStaticMarkup } from "react-dom/server";
4
5 import { resolvePresentationIcon } from "@/components/notebook/presentation/editor/custom-elements/presentation-icon-utils";
6
7 type ResourceConfig = {
8 source: string;
9 format?: string;
10 encoding?: string;
11 data: string;
12 scene?: string;
13 [key: string]: unknown;
14 };
15
16 let loaderRegistered = false;
17 const iconSymbolCache = new Map<string, Promise<SVGSymbolElement | null>>();
18
19 async function loadIconSymbol(
20 iconName: string,
21 ): Promise<SVGSymbolElement | null> {
22 const cachedSymbol = iconSymbolCache.get(iconName);
23 if (cachedSymbol) return cachedSymbol;
24
25 const symbolPromise = (async () => {
26 const resolvedIcon = await resolvePresentationIcon(iconName);
27 if (!resolvedIcon) return null;
28
29 const svgString = renderToStaticMarkup(
30 React.createElement(resolvedIcon.Component, {
31 size: 24,
32 color: "currentColor",
33 strokeWidth: 2,
34 }),
35 );
36
37 const parser = new DOMParser();
38 const doc = parser.parseFromString(svgString, "image/svg+xml");
39 const svgElement = doc.querySelector("svg");
40
41 if (!svgElement) return null;
42
43 const symbol = document.createElementNS(
44 "http://www.w3.org/2000/svg",
45 "symbol",
46 );
47
48 if (svgElement.hasAttribute("viewBox")) {
49 const viewBox = svgElement.getAttribute("viewBox");
50 if (viewBox) symbol.setAttribute("viewBox", viewBox);
51 } else {
52 symbol.setAttribute("viewBox", "0 0 24 24");
53 }
54
55 while (svgElement.firstChild) {
56 symbol.appendChild(svgElement.firstChild);
57 }
58
59 return symbol as SVGSymbolElement;
60 })().catch((error: unknown) => {
61 iconSymbolCache.delete(iconName);
62 throw error;
63 });
64
65 iconSymbolCache.set(iconName, symbolPromise);
66 return symbolPromise;
67 }
68
69 export const registerLucideIconLoader = () => {
70 if (loaderRegistered) return;
71
72 registerResourceLoader(async (config: ResourceConfig) => {
73 if (
74 !config.data ||
75 config.data.startsWith("<") ||
76 config.data.startsWith("data:")
77 ) {
78 return null;
79 }
80
81 const iconName = config.data;
82
83 try {
84 const symbol = await loadIconSymbol(iconName);
85 return symbol?.cloneNode(true) as SVGSymbolElement | null;
86 } catch (error) {
87 console.error("Failed to load icon resource:", error);
88 return null;
89 }
90 });
91
92 loaderRegistered = true;
93 };
94
94 lines TYPESCRIPT