From 52933e617a6d4490588234a55782c2bc67bda945 Mon Sep 17 00:00:00 2001 From: Szymon Nowakowski Date: Mon, 30 Dec 2024 23:12:25 +0100 Subject: [PATCH] Fix compressed .mxl handling --- packages/frontend/package.json | 1 + packages/frontend/src/app.tsx | 12 +++- packages/frontend/src/routes/Attachment.tsx | 71 +++++++++++++++++---- pnpm-lock.yaml | 6 ++ pnpm-workspace.yaml | 1 + 5 files changed, 78 insertions(+), 13 deletions(-) diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 771f47a..5a30a55 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -25,6 +25,7 @@ "clsx": "catalog:", "common": "workspace:^", "effect": "catalog:", + "jszip": "catalog:", "lucide-react": "catalog:", "opensheetmusicdisplay": "catalog:", "react": "catalog:", diff --git a/packages/frontend/src/app.tsx b/packages/frontend/src/app.tsx index 551477a..cab2589 100644 --- a/packages/frontend/src/app.tsx +++ b/packages/frontend/src/app.tsx @@ -67,13 +67,21 @@ const router = createBrowserRouter([ path: "/login", Component: Login, }, -]); +], { + future: { + v7_fetcherPersist: true, + v7_normalizeFormMethod: true, + v7_partialHydration: true, + v7_relativeSplatPath: true, + v7_skipActionErrorRevalidation: true, + }, +}); const rootElement = document.getElementById("root") as HTMLDivElement; const root = createRoot(rootElement); root.render( - + , ); diff --git a/packages/frontend/src/routes/Attachment.tsx b/packages/frontend/src/routes/Attachment.tsx index 92e9808..2557636 100644 --- a/packages/frontend/src/routes/Attachment.tsx +++ b/packages/frontend/src/routes/Attachment.tsx @@ -1,8 +1,9 @@ import { client } from "@/client"; import { useLoading } from "@/hooks/useLoading.ts"; import { AttachmentId, PieceId } from "common"; +import JSZip from "jszip"; import { OpenSheetMusicDisplay } from "opensheetmusicdisplay"; -import { useEffect, useRef } from "react"; +import { useCallback, useEffect, useRef } from "react"; import { useParams } from "react-router-dom"; export default function Attachment() { @@ -14,33 +15,81 @@ export default function Attachment() { const { isLoading, error, data } = useLoading(() => client.piece({ pieceId }).attachment({ attachmentId }).get(), [pieceId, attachmentId]); const containerRef = useRef(null); + const renderFn = useRef void)>(null); + + const load = useCallback(async () => { - useEffect(() => { if (isLoading || error !== null) return; - const url = URL.createObjectURL(data); + let musixXmlBlob: Blob = data; - const render = () => osmd.render(); + /* If the file is the compressed .mxl file, we do the uncompression + * ourselves, because apparently OpenSheetMusicDisplay is incapable. + */ + if (data.type === "application/vnd.recordare.musicxml") { + const zip = new JSZip(); + await zip.loadAsync(data); + + const containerFile = zip.file("META-INF/container.xml"); + if (containerFile === null) { + console.error("Missing META-INF/container.xml in the .mxl file"); + return; + } + + const containerText = await containerFile.async("text"); + const containerXml = new DOMParser().parseFromString(containerText, "application/xml"); + + const rootFile = containerXml.querySelector("rootfile[media-type=\"application/vnd.recordare.musicxml+xml\"], rootfile:not([media-type])"); + if (rootFile === null) { + console.error("Missing root MusicXML file in META-INF/container.xml"); + return; + } + + const fullPath = rootFile.getAttribute("full-path"); + if (fullPath === null) { + console.error("Missing full-path attribute in rootfile element"); + return; + } + + const musicXmlFile = zip.file(fullPath); + if (musicXmlFile === null) { + console.error(`Missing ${fullPath} file in the .mxl archive`); + return; + } + + musixXmlBlob = await musicXmlFile.async("blob"); + } + + const musicXml = await musixXmlBlob.text(); const osmd = new OpenSheetMusicDisplay(containerRef.current!, { autoResize: false, drawTitle: false, drawComposer: false, drawMeasureNumbers: true, - drawMeasureNumbersOnlyAtSystemStart: true, - //measureNumberInterval: 5, - //renderSingleHorizontalStaffline: true, + drawMeasureNumbersOnlyAtSystemStart: false, + measureNumberInterval: 1, + renderSingleHorizontalStaffline: true, }); - osmd.load(url).then(render, (error) => console.error(error)); + const render = () => osmd.render(); + + await osmd.load(musicXml); + render(); + + renderFn.current = render; + }, [data, error, isLoading]); + + useEffect(() => void load(), [load]); + + useEffect(() => { + const render = () => renderFn.current?.(); window.addEventListener("resize", render); - return () => { - URL.revokeObjectURL(url); window.removeEventListener("resize", render); }; - }, [data, error, isLoading]); + }, []); if (isLoading) { return ( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 76fe5cc..bee8091 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,6 +66,9 @@ catalogs: eslint-plugin-react-hooks: specifier: ^5.1.0 version: 5.1.0 + jszip: + specifier: ^3.10.1 + version: 3.10.1 kysely: specifier: ^0.27.4 version: 0.27.4 @@ -192,6 +195,9 @@ importers: effect: specifier: 'catalog:' version: 3.11.4 + jszip: + specifier: 'catalog:' + version: 3.10.1 lucide-react: specifier: 'catalog:' version: 0.462.0(react@18.3.1) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0808841..251b700 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -22,6 +22,7 @@ catalog: effect: '^3.11.4' elysia: '^1.1.25' eslint-plugin-react-hooks: '^5.1.0' + jszip: '^3.10.1' kysely: '^0.27.4' kysely-bun-sqlite: '^0.3.2' lucide-react: '^0.462.0'