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.