PageSourceSearch

https://www.hamienet.com/assets/ScoreMusicXmlViewer-D8VtSBA7.js

js hamienet.com collected 2026-10-02 05:02:55 UTC 16,463 bytes, 340 lines download raw bytes

1import { r as reactExports, j as jsxRuntimeExports } from "./vendor-react-Bay9Y7Lb.js";
2import { bg as Music, dT as CodeXml, ds as ZoomOut, dt as ZoomIn, c4 as RotateCcw, c_ as ChevronLeft, bF as ChevronRight, b7 as Check, c2 as Copy, b6 as LoaderCircle, aY as TriangleAlert, eD as purify } from "./vendor-D0P4XgNj.js";
3function ScoreMusicXmlViewer({
4  xmlString,
5  isLoading = false,
6  title,
7  className = ""
8}) {
9  const [viewMode, setViewMode] = reactExports.useState("score");
10  const [scale, setScale] = reactExports.useState(40);
11  const [currentPage, setCurrentPage] = reactExports.useState(1);
12  const [pageCount, setPageCount] = reactExports.useState(1);
13  const [renderedSvg, setRenderedSvg] = reactExports.useState("");
14  const [isVerovioReady, setIsVerovioReady] = reactExports.useState(false);
15  const [isRendering, setIsRendering] = reactExports.useState(false);
16  const [renderError, setRenderError] = reactExports.useState(null);
17  const [copied, setCopied] = reactExports.useState(false);
18  const toolkitRef = reactExports.useRef(null);
19  const containerRef = reactExports.useRef(null);
20  reactExports.useEffect(() => {
21    let mounted = true;
22    const injectScript = () => {
23      return new Promise((resolve, reject) => {
24        if (window.verovio) {
25          resolve();
26          return;
27        }
28        const existing = document.querySelector('script[src*="verovio"]');
29        if (existing) {
30          if (existing.dataset.loaded === "true") {
31            resolve();
32            return;
33          }
34          existing.addEventListener("load", () => resolve());
35          existing.addEventListener("error", () => reject(new Error("Verovio script load error")));
36          return;
37        }
38        const script = document.createElement("script");
39        script.src = "/libs/verovio/verovio-toolkit-wasm.js";
40        script.async = true;
41        script.onload = () => {
42          script.dataset.loaded = "true";
43          resolve();
44        };
45        script.onerror = () => reject(new Error("Failed to download verovio-toolkit-wasm.js"));
46        document.head.appendChild(script);
47      });
48    };
49    const initVerovio = async () => {
50      try {
51        await injectScript();
52      } catch (err) {
53        console.error("[ScoreViewer] Verovio script injection failed:", err);
54        if (mounted) setRenderError("Failed to load sheet music engine script.");
55        return;
56      }
57      let attempts = 0;
58      while (attempts < 100) {
59        if (!mounted) return;
60        const v2 = window.verovio;
61        if (v2 && typeof v2 === "object") break;
62        await new Promise((r) => setTimeout(r, 100));
63        attempts++;
64      }
65      const v = window.verovio;
66      if (!v) {
67        if (mounted) setRenderError("Verovio engine initialization timed out.");
68        return;
69      }
70      try {
71        let module = v.module;
72        if (module && typeof module.then === "function") module = await module;
73        else if (typeof module === "function") module = await module();
74        if (module && !module._vrvToolkit_loadData) {
75          let waitAttempts = 0;
76          while (!module._vrvToolkit_loadData && waitAttempts < 200) {
77            if (!mounted) return;
78            await new Promise((r) => setTimeout(r, 50));
79            waitAttempts++;
80          }
81        }
82        const ToolkitClass = v.toolkit;
83        let tk = null;
84        if (!module || !module.cwrap) {
85          try {
86            tk = new ToolkitClass();
87          } catch (err) {
88          }
89        }
90        if (!tk) {
91          let initAttempts = 0;
92          while (initAttempts < 10) {
93            try {
94              tk = new ToolkitClass(module);
95              if (tk && typeof tk.getVersion === "function") break;
96            } catch (err) {
97              if (initAttempts >= 9) throw err;
98              await new Promise((r) => setTimeout(r, 200));
99              initAttempts++;
100            }
101          }
102        }
103        if (tk && mounted) {
104          toolkitRef.current = tk;
105          setIsVerovioReady(true);
106        }
107      } catch (err) {
108        console.error("[ScoreViewer] Failed to instantiate Verovio toolkit:", err);
109        if (mounted) setRenderError(`Engine error: ${err.message}`);
110      }
111    };
112    initVerovio();
113    return () => {
114      mounted = false;
115    };
116  }, []);
117  const renderScore = reactExports.useCallback(() => {
118    var _a;
119    if (!toolkitRef.current || !xmlString || !xmlString.trim()) {
120      setRenderedSvg("");
121      setRenderError(null);
122      return;
123    }
124    const tk = toolkitRef.current;
125    setIsRendering(true);
126    setRenderError(null);
127    try {
128      const containerWidth = ((_a = containerRef.current) == null ? void 0 : _a.clientWidth) || 800;
129      const effectivePageWidth = Math.max(800, Math.round(containerWidth * (100 / scale)));
130      tk.setOptions({
131        pageWidth: effectivePageWidth,
132        pageHeight: 6e4,
133        // Flow mode / long pages
134        pageMarginTop: 100,
135        pageMarginBottom: 100,
136        pageMarginLeft: 100,
137        pageMarginRight: 100,
138        scale,
139        adjustPageWidth: 1,
140        breaks: "auto",
141        openMusicXmlSpacing: true,
142        font: "Bravura",
143        mnumInterval: 1,
144        header: "none",
145        footer: "none"
146      });
147      const success = tk.loadData(xmlString);
148      if (!success) {
149        setRenderError("Verovio could not parse the generated MusicXML. Verify XML structure, measures, and note tags.");
150        setIsRendering(false);
151        return;
152      }
153      const total = tk.getPageCount();
154      setPageCount(Math.max(1, total));
155      const targetPage = Math.min(Math.max(1, currentPage), Math.max(1, total));
156      setCurrentPage(targetPage);
157      const svg = tk.renderToSVG(targetPage, {});
158      if (svg) {
159        setRenderedSvg(svg);
160      } else {
161        setRenderError("Render returned empty SVG.");
162      }
163    } catch (err) {
164      console.error("[ScoreViewer] Render error:", err);
165      setRenderError(`Render failed: ${err.message}`);
166    } finally {
167      setIsRendering(false);
168    }
169  }, [xmlString, scale, currentPage]);
170  reactExports.useEffect(() => {
171    if (isVerovioReady && xmlString) {
172      renderScore();
173    }
174  }, [isVerovioReady, xmlString, scale, currentPage, renderScore]);
175  const handleCopy = () => {
176    if (!xmlString) return;
177    navigator.clipboard.writeText(xmlString);
178    setCopied(true);
179    setTimeout(() => setCopied(false), 2e3);
180  };
181  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex flex-col bg-white dark:bg-zinc-900 rounded-xl border border-zinc-200 dark:border-zinc-800 overflow-hidden shadow-sm ${className}`, children: [
182    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-2.5 bg-zinc-50 dark:bg-zinc-950 border-b border-zinc-200 dark:border-zinc-800 flex items-center justify-between gap-3 shrink-0 flex-wrap text-xs", children: [
183      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
184        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-zinc-700 dark:text-zinc-200 flex items-center gap-1.5", children: title || "Score Inspector" }),
185        xmlString && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "px-2 py-0.5 rounded-full bg-indigo-50 dark:bg-indigo-950/50 text-indigo-600 dark:text-indigo-400 font-mono text-[10px] border border-indigo-200 dark:border-indigo-800", children: [
186          (xmlString.length / 1024).toFixed(1),
187          " KB"
188        ] })
189      ] }),
190      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 ml-auto", children: [
191        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex bg-zinc-200 dark:bg-zinc-800 p-0.5 rounded-lg", children: [
192          /* @__PURE__ */ jsxRuntimeExports.jsxs(
193            "button",
194            {
195              type: "button",
196              onClick: () => setViewMode("score"),
197              className: `px-2.5 py-1 rounded-md font-semibold flex items-center gap-1.5 transition-all ${viewMode === "score" ? "bg-white dark:bg-zinc-900 text-indigo-600 dark:text-indigo-400 shadow-sm" : "text-zinc-500 hover:text-zinc-800 dark:hover:text-zinc-200"}`,
198              children: [
199                /* @__PURE__ */ jsxRuntimeExports.jsx(Music, { size: 13 }),
200                " Sheet Music"
201              ]
202            }
203          ),
204          /* @__PURE__ */ jsxRuntimeExports.jsxs(
205            "button",
206            {
207              type: "button",
208              onClick: () => setViewMode("xml"),
209              className: `px-2.5 py-1 rounded-md font-semibold flex items-center gap-1.5 transition-all ${viewMode === "xml" ? "bg-white dark:bg-zinc-900 text-indigo-600 dark:text-indigo-400 shadow-sm" : "text-zinc-500 hover:text-zinc-800 dark:hover:text-zinc-200"}`,
210              children: [
211                /* @__PURE__ */ jsxRuntimeExports.jsx(CodeXml, { size: 13 }),
212                " Raw XML"
213              ]
214            }
215          )
216        ] }),
217        viewMode === "score" && xmlString && !renderError && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 border-l border-zinc-200 dark:border-zinc-800 pl-2", children: [
218          /* @__PURE__ */ jsxRuntimeExports.jsx(
219            "button",
220            {
221              type: "button",
222              onClick: () => setScale((s) => Math.max(20, s - 5)),
223              title: "Zoom Out",
224              className: "p-1 rounded hover:bg-zinc-200 dark:hover:bg-zinc-800 text-zinc-600 dark:text-zinc-400",
225              children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomOut, { size: 14 })
226            }
227          ),
228          /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-[11px] min-w-[32px] text-center text-zinc-500", children: [
229            scale,
230            "%"
231          ] }),
232          /* @__PURE__ */ jsxRuntimeExports.jsx(
233            "button",
234            {
235              type: "button",
236              onClick: () => setScale((s) => Math.min(80, s + 5)),
237              title: "Zoom In",
238              className: "p-1 rounded hover:bg-zinc-200 dark:hover:bg-zinc-800 text-zinc-600 dark:text-zinc-400",
239              children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomIn, { size: 14 })
240            }
241          ),
242          /* @__PURE__ */ jsxRuntimeExports.jsx(
243            "button",
244            {
245              type: "button",
246              onClick: () => setScale(40),
247              title: "Reset Scale",
248              className: "p-1 rounded hover:bg-zinc-200 dark:hover:bg-zinc-800 text-zinc-600 dark:text-zinc-400",
249              children: /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { size: 13 })
250            }
251          ),
252          pageCount > 1 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 border-l border-zinc-200 dark:border-zinc-800 pl-2 ml-1", children: [
253            /* @__PURE__ */ jsxRuntimeExports.jsx(
254              "button",
255              {
256                type: "button",
257                disabled: currentPage <= 1,
258                onClick: () => setCurrentPage((p) => Math.max(1, p - 1)),
259                className: "p-1 rounded hover:bg-zinc-200 dark:hover:bg-zinc-800 disabled:opacity-30 text-zinc-600 dark:text-zinc-400",
260                children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronLeft, { size: 14 })
261              }
262            ),
263            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-[11px] text-zinc-500", children: [
264              currentPage,
265              "/",
266              pageCount
267            ] }),
268            /* @__PURE__ */ jsxRuntimeExports.jsx(
269              "button",
270              {
271                type: "button",
272                disabled: currentPage >= pageCount,
273                onClick: () => setCurrentPage((p) => Math.min(pageCount, p + 1)),
274                className: "p-1 rounded hover:bg-zinc-200 dark:hover:bg-zinc-800 disabled:opacity-30 text-zinc-600 dark:text-zinc-400",
275                children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 14 })
276              }
277            )
278          ] })
279        ] }),
280        viewMode === "xml" && xmlString && /* @__PURE__ */ jsxRuntimeExports.jsxs(
281          "button",
282          {
283            type: "button",
284            onClick: handleCopy,
285            className: "px-2.5 py-1 bg-zinc-200 hover:bg-zinc-300 dark:bg-zinc-800 dark:hover:bg-zinc-700 text-zinc-700 dark:text-zinc-300 rounded font-semibold flex items-center gap-1 transition-colors",
286            children: [
287              copied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 12, className: "text-green-500" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { size: 12 }),
288              copied ? "Copied" : "Copy"
289            ]
290          }
291        )
292      ] })
293    ] }),
294    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref: containerRef, className: "flex-1 min-h-0 relative overflow-auto bg-zinc-100 dark:bg-zinc-950 flex flex-col items-center", children: [
295      (isLoading || isRendering) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute inset-0 bg-white/70 dark:bg-zinc-950/70 backdrop-blur-xs flex flex-col items-center justify-center z-10 gap-2", children: [
296        /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "animate-spin text-indigo-500", size: 24 }),
297        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-semibold text-zinc-600 dark:text-zinc-400", children: isLoading ? "Executing Sandbox..." : "Engraving Sheet Music with Verovio..." })
298      ] }),
299      !xmlString && !isLoading && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col items-center justify-center p-8 text-center text-zinc-400", children: [
300        /* @__PURE__ */ jsxRuntimeExports.jsx(Music, { size: 36, className: "mb-2 opacity-30 text-indigo-500" }),
301        /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm font-medium", children: "No Score Generated Yet" }),
302        /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-zinc-500 mt-1 max-w-xs", children: [
303          "Select a benchmark piece or custom MIDI ID and click ",
304          /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { className: "text-zinc-400", children: "Run" }),
305          " to execute quantization logic and preview the engraved score."
306        ] })
307      ] }),
308      renderError && viewMode === "score" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "m-4 p-4 rounded-xl bg-amber-500/10 border border-amber-500/30 text-amber-600 dark:text-amber-400 text-xs flex items-start gap-3 w-[90%] max-w-lg", children: [
309        /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { size: 18, className: "shrink-0 mt-0.5" }),
310        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
311          /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "font-bold", children: "Sheet Music Parsing Warning" }),
312          /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mt-1 opacity-90", children: renderError }),
313          /* @__PURE__ */ jsxRuntimeExports.jsxs(
314            "button",
315            {
316              type: "button",
317              onClick: () => setViewMode("xml"),
318              className: "mt-2 text-indigo-600 dark:text-indigo-400 font-bold hover:underline flex items-center gap-1",
319              children: [
320                /* @__PURE__ */ jsxRuntimeExports.jsx(CodeXml, { size: 12 }),
321                " Inspect Raw MusicXML"
322              ]
323            }
324          )
325        ] })
326      ] }),
327      viewMode === "score" && renderedSvg && !renderError && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-4 w-full flex justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
328        "div",
329        {
330          className: "w-full max-w-4xl bg-white dark:bg-zinc-900 rounded-lg shadow-md p-6 overflow-hidden border border-zinc-200 dark:border-zinc-800 select-none",
331          dangerouslySetInnerHTML: { __html: purify.sanitize(renderedSvg) }
332        }
333      ) }),
334      viewMode === "xml" && xmlString && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-full h-full p-4 overflow-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "text-xs font-mono text-zinc-700 dark:text-zinc-300 whitespace-pre-wrap select-text leading-relaxed", children: xmlString }) })
335    ] })
336  ] });
337}
338export {
339  ScoreMusicXmlViewer as S
340};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.