Embedded, browser-provided PDF viewer
This commit is contained in:
@@ -4,7 +4,7 @@ import { AttachmentId } from "common";
|
||||
import { Match } from "effect";
|
||||
import JSZip from "jszip";
|
||||
import { OpenSheetMusicDisplay } from "opensheetmusicdisplay";
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
export default function Attachment() {
|
||||
@@ -14,19 +14,62 @@ export default function Attachment() {
|
||||
|
||||
const { isLoading, error, data } = useLoading(client.getAttachment(attachmentId), [attachmentId]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="w-full h-full overflow-hidden flex items-center justify-center">
|
||||
<div>Ładowanie…</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error !== null) {
|
||||
return (
|
||||
<div className="w-full h-full overflow-hidden flex items-center justify-center">
|
||||
<div>
|
||||
Wystąpił błąd: {Match.value(error).pipe(
|
||||
Match.tag("FetchError", () => "Nie można połączyć się z serwerem"),
|
||||
Match.tag("NotFound", () => "Załącznik nie istnieje"),
|
||||
Match.tag("Unauthenticated", () => "Zaloguj się, aby kontynuować"),
|
||||
Match.tag("Unauthorized", () => "Nie posiadasz uprawnień"),
|
||||
Match.exhaustive,
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (data.mediaType === "application/vnd.recordare.musicxml" || data.mediaType === "application/vnd.recordare.musicxml+xml") {
|
||||
return <MusicXmlAttachment data={data.data} mediaType={data.mediaType} />;
|
||||
} else if (data.mediaType === "application/pdf") {
|
||||
return <PdfAttachment data={data.data} filename={data.filename} mediaType={data.mediaType} />;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
namespace MusicXmlAttachment {
|
||||
export interface Props {
|
||||
readonly data: Uint8Array<ArrayBuffer>;
|
||||
readonly mediaType:
|
||||
| "application/vnd.recordare.musicxml"
|
||||
| "application/vnd.recordare.musicxml+xml"
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
function MusicXmlAttachment({ data, mediaType }: MusicXmlAttachment.Props) {
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const renderFn = useRef<null | (() => void)>(null);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
|
||||
if (isLoading || error !== null) return;
|
||||
|
||||
let musixXmlData: Uint8Array = data.data;
|
||||
let musixXmlData: Uint8Array = data;
|
||||
|
||||
/* If the file is the compressed .mxl file, we do the uncompression
|
||||
* ourselves, because apparently OpenSheetMusicDisplay is incapable.
|
||||
*/
|
||||
if (data.mediaType === "application/vnd.recordare.musicxml") {
|
||||
if (mediaType === "application/vnd.recordare.musicxml") {
|
||||
const zip = new JSZip();
|
||||
await zip.loadAsync(musixXmlData);
|
||||
|
||||
@@ -78,7 +121,7 @@ export default function Attachment() {
|
||||
render();
|
||||
|
||||
renderFn.current = render;
|
||||
}, [data, error, isLoading]);
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => void load(), [load]);
|
||||
|
||||
@@ -91,29 +134,39 @@ export default function Attachment() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="w-full h-full overflow-hidden flex items-center justify-center">
|
||||
<div>Ładowanie…</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error !== null) {
|
||||
return (
|
||||
<div className="w-full h-full overflow-hidden flex items-center justify-center">
|
||||
<div>
|
||||
Wystąpił błąd: {Match.value(error).pipe(
|
||||
Match.tag("FetchError", () => "Nie można połączyć się z serwerem"),
|
||||
Match.tag("NotFound", () => "Załącznik nie istnieje"),
|
||||
Match.tag("Unauthenticated", () => "Zaloguj się, aby kontynuować"),
|
||||
Match.tag("Unauthorized", () => "Nie posiadasz uprawnień"),
|
||||
Match.exhaustive,
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <div ref={containerRef} className="w-full h-full overflow-scroll" />;
|
||||
};
|
||||
}
|
||||
|
||||
namespace PdfAttachment {
|
||||
export interface Props {
|
||||
readonly data: Uint8Array<ArrayBuffer>;
|
||||
readonly filename: string;
|
||||
readonly mediaType: "application/pdf";
|
||||
}
|
||||
}
|
||||
|
||||
function PdfAttachment({
|
||||
data,
|
||||
filename,
|
||||
mediaType,
|
||||
}: PdfAttachment.Props) {
|
||||
|
||||
const [url, setUrl] = useState<string>("");
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const file = new File([data], filename, { type: mediaType });
|
||||
const url = URL.createObjectURL(file);
|
||||
|
||||
setUrl(url);
|
||||
|
||||
return () => { URL.revokeObjectURL(url); };
|
||||
}, [data, mediaType]);
|
||||
|
||||
return url && (
|
||||
<object
|
||||
type={mediaType}
|
||||
data={url}
|
||||
className="w-full h-full"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -233,21 +233,6 @@ function AttachmentRow(props: AttachmentRow.Props) {
|
||||
URL.revokeObjectURL(url);
|
||||
}).pipe(Effect.runPromise);
|
||||
|
||||
const open = (event: MouseEvent<HTMLAnchorElement>) => Effect.gen(function* () {
|
||||
if (props.attachment.mediaType !== "application/pdf") {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
const { data, mediaType } = yield* client.getAttachment(props.attachment.attachmentId);
|
||||
const blob = new Blob([data], { type: mediaType });
|
||||
|
||||
const url = URL.createObjectURL(blob);
|
||||
window.open(url, "_target");
|
||||
URL.revokeObjectURL(url);
|
||||
}).pipe(Effect.runPromise);
|
||||
|
||||
const doDelete = () => Effect.gen(function* () {
|
||||
yield* client.deleteAttachment(props.attachment.attachmentId);
|
||||
yield* pieceCache.update(props.attachment.pieceId, mapProp("attachments", Array.filter<Attachment>((a) => a.attachmentId !== props.attachment.attachmentId)));
|
||||
@@ -280,7 +265,7 @@ function AttachmentRow(props: AttachmentRow.Props) {
|
||||
{props.attachment.mediaType === "application/vnd.recordare.musicxml"
|
||||
|| props.attachment.mediaType === "application/vnd.recordare.musicxml+xml"
|
||||
|| props.attachment.mediaType === "application/pdf" ? (
|
||||
<Link className="underline" to={`attachment/${props.attachment.attachmentId}`} onClick={open}>
|
||||
<Link className="underline" to={`attachment/${props.attachment.attachmentId}`}>
|
||||
{props.attachment.filename}
|
||||
</Link>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user