Fix compressed .mxl handling
This commit is contained in:
@@ -25,6 +25,7 @@
|
|||||||
"clsx": "catalog:",
|
"clsx": "catalog:",
|
||||||
"common": "workspace:^",
|
"common": "workspace:^",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
|
"jszip": "catalog:",
|
||||||
"lucide-react": "catalog:",
|
"lucide-react": "catalog:",
|
||||||
"opensheetmusicdisplay": "catalog:",
|
"opensheetmusicdisplay": "catalog:",
|
||||||
"react": "catalog:",
|
"react": "catalog:",
|
||||||
|
|||||||
@@ -67,13 +67,21 @@ const router = createBrowserRouter([
|
|||||||
path: "/login",
|
path: "/login",
|
||||||
Component: 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 rootElement = document.getElementById("root") as HTMLDivElement;
|
||||||
const root = createRoot(rootElement);
|
const root = createRoot(rootElement);
|
||||||
|
|
||||||
root.render(
|
root.render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<RouterProvider router={router} />
|
<RouterProvider router={router} future={{ v7_startTransition: true }} />
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { client } from "@/client";
|
import { client } from "@/client";
|
||||||
import { useLoading } from "@/hooks/useLoading.ts";
|
import { useLoading } from "@/hooks/useLoading.ts";
|
||||||
import { AttachmentId, PieceId } from "common";
|
import { AttachmentId, PieceId } from "common";
|
||||||
|
import JSZip from "jszip";
|
||||||
import { OpenSheetMusicDisplay } from "opensheetmusicdisplay";
|
import { OpenSheetMusicDisplay } from "opensheetmusicdisplay";
|
||||||
import { useEffect, useRef } from "react";
|
import { useCallback, useEffect, useRef } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
export default function Attachment() {
|
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 { isLoading, error, data } = useLoading(() => client.piece({ pieceId }).attachment({ attachmentId }).get(), [pieceId, attachmentId]);
|
||||||
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const renderFn = useRef<null | (() => void)>(null);
|
||||||
|
|
||||||
|
const load = useCallback(async () => {
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isLoading || error !== null) return;
|
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!, {
|
const osmd = new OpenSheetMusicDisplay(containerRef.current!, {
|
||||||
autoResize: false,
|
autoResize: false,
|
||||||
drawTitle: false,
|
drawTitle: false,
|
||||||
drawComposer: false,
|
drawComposer: false,
|
||||||
drawMeasureNumbers: true,
|
drawMeasureNumbers: true,
|
||||||
drawMeasureNumbersOnlyAtSystemStart: true,
|
drawMeasureNumbersOnlyAtSystemStart: false,
|
||||||
//measureNumberInterval: 5,
|
measureNumberInterval: 1,
|
||||||
//renderSingleHorizontalStaffline: true,
|
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);
|
window.addEventListener("resize", render);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
window.removeEventListener("resize", render);
|
window.removeEventListener("resize", render);
|
||||||
};
|
};
|
||||||
}, [data, error, isLoading]);
|
}, []);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -66,6 +66,9 @@ catalogs:
|
|||||||
eslint-plugin-react-hooks:
|
eslint-plugin-react-hooks:
|
||||||
specifier: ^5.1.0
|
specifier: ^5.1.0
|
||||||
version: 5.1.0
|
version: 5.1.0
|
||||||
|
jszip:
|
||||||
|
specifier: ^3.10.1
|
||||||
|
version: 3.10.1
|
||||||
kysely:
|
kysely:
|
||||||
specifier: ^0.27.4
|
specifier: ^0.27.4
|
||||||
version: 0.27.4
|
version: 0.27.4
|
||||||
@@ -192,6 +195,9 @@ importers:
|
|||||||
effect:
|
effect:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 3.11.4
|
version: 3.11.4
|
||||||
|
jszip:
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 3.10.1
|
||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 0.462.0(react@18.3.1)
|
version: 0.462.0(react@18.3.1)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ catalog:
|
|||||||
effect: '^3.11.4'
|
effect: '^3.11.4'
|
||||||
elysia: '^1.1.25'
|
elysia: '^1.1.25'
|
||||||
eslint-plugin-react-hooks: '^5.1.0'
|
eslint-plugin-react-hooks: '^5.1.0'
|
||||||
|
jszip: '^3.10.1'
|
||||||
kysely: '^0.27.4'
|
kysely: '^0.27.4'
|
||||||
kysely-bun-sqlite: '^0.3.2'
|
kysely-bun-sqlite: '^0.3.2'
|
||||||
lucide-react: '^0.462.0'
|
lucide-react: '^0.462.0'
|
||||||
|
|||||||
Reference in New Issue
Block a user