feat: open multi-part packages in browser

This commit is contained in:
2026-07-22 19:46:17 +02:00
parent 2c998952e5
commit 3b1777a5b3
11 changed files with 428 additions and 64 deletions

View File

@@ -20,10 +20,7 @@ import type {
OneNoteSectionDto,
} from './onenote/model/dto.js';
import type { ParserDiagnostic } from './onenote/model/diagnostics.js';
import {
MAX_SOURCE_FILE_BYTES,
sourceKindFromFileName,
} from './source-file.js';
import { selectSourceFiles } from './source-file.js';
import { toolboxApp } from './toolbox/manifest.js';
import {
OneNoteWorkerClient,
@@ -174,21 +171,13 @@ export function OneNoteApplication({
}
};
const openFile = async (file: File) => {
const sourceKind = sourceKindFromFileName(file.name);
if (!sourceKind) {
const openFiles = async (files: File[]) => {
const selection = selectSourceFiles(files);
if (!selection.ok) {
setState({
phase: 'error',
title: 'Unsupported file name',
message: 'Choose a file whose name ends in .one or .onepkg.',
});
return;
}
if (file.size > MAX_SOURCE_FILE_BYTES) {
setState({
phase: 'error',
title: 'File is too large',
message: 'The selected file exceeds the current 512 MiB safety limit.',
title: selection.title,
message: selection.message,
});
return;
}
@@ -201,12 +190,24 @@ export function OneNoteApplication({
activeClient.current = client;
setSelectedSectionId(undefined);
setPageState({ phase: 'empty' });
setState({ phase: 'reading', fileName: file.name });
setState({ phase: 'reading', fileName: selection.source.name });
try {
const bytes = await file.arrayBuffer();
const buffers = await Promise.all(
[selection.source, ...selection.cabinetParts].map((file) =>
file.arrayBuffer()
)
);
if (generation.current !== requestGeneration) return;
const response = await client.parseSource(sourceKind, file.name, bytes);
const response = await client.parseSource(
selection.kind,
selection.source.name,
buffers[0]!,
selection.cabinetParts.map((part, index) => ({
fileName: part.name,
bytes: buffers[index + 1]!,
}))
);
if (generation.current !== requestGeneration) return;
if (response.type === 'failure') {
@@ -226,7 +227,7 @@ export function OneNoteApplication({
if (response.type === 'section') {
setState({
phase: 'loaded',
fileName: file.name,
fileName: selection.source.name,
source: {
kind: 'one',
sessionId: response.sessionId,
@@ -253,7 +254,7 @@ export function OneNoteApplication({
setSelectedSectionId(preferredSection?.id);
setState({
phase: 'loaded',
fileName: file.name,
fileName: selection.source.name,
source: {
kind: 'onepkg',
sessionId: response.sessionId,
@@ -373,7 +374,7 @@ export function OneNoteApplication({
return (
<main className="application">
{state.phase === 'idle' ? (
<FileDropZone onFile={(file) => void openFile(file)} />
<FileDropZone onFiles={(files) => void openFiles(files)} />
) : null}
{state.phase === 'reading' ? (