189 lines
8.7 KiB
Markdown
189 lines
8.7 KiB
Markdown
# Plan: Download an individual sub-document
|
|
|
|
**Status: Done**
|
|
|
|
## Goal
|
|
|
|
Each sub-document displayed inside a processed ZIP archive shall have its own download action. The
|
|
action creates and downloads one PDF containing only that sub-document's attachments, in their
|
|
current order.
|
|
|
|
This is an additional interaction. The existing archive-level export modes remain available:
|
|
|
|
- **Eine PDF** continues to flatten all sub-documents and loose attachments into one PDF.
|
|
- **Getrennt** continues to download every non-empty sub-document plus the loose-attachment PDF.
|
|
- The new sub-document action downloads only the selected sub-document and does not include loose
|
|
attachments or sibling sub-documents.
|
|
|
|
## Current implementation
|
|
|
|
- `ProcessedZipArchiveEditor.svelte` owns archive-level download state and the browser download helper.
|
|
- `SubDocumentEditor.svelte` renders each sub-document but currently exposes only edit, drag/drop, and
|
|
dissolve callbacks.
|
|
- `mergeAttachments()` in `src/lib/zip-processing.ts` already creates a PDF from an ordered attachment
|
|
list.
|
|
- `buildArchiveExport(archive, 'separate')` already defines the desired sub-document naming convention:
|
|
`{archiveName} - {subDocumentName}.pdf`. It currently creates exports for every sub-document, so it
|
|
should not be called for an individual download.
|
|
|
|
## UX and behavior
|
|
|
|
Add a **PDF herunterladen** button to each sub-document header, before **Auflösen**.
|
|
|
|
- Clicking it downloads one PDF for that sub-document.
|
|
- The PDF page order matches `subDocument.attachments` exactly.
|
|
- The filename remains consistent with separate archive export:
|
|
`{archiveName without .pdf} - {subDocumentName without .pdf}.pdf`.
|
|
- The action is disabled when the sub-document has no attachments.
|
|
- While an individual PDF is being generated, its button reads **PDF wird erstellt** and cannot be
|
|
triggered again.
|
|
- Only one PDF-generation operation may run in an archive editor at a time. While an individual
|
|
download is active, archive-level and sibling download actions are disabled; while archive-level
|
|
export is active, individual actions are disabled.
|
|
- A failure leaves the archive and sub-document unchanged and shows the existing inline error area
|
|
with a sub-document-specific German message.
|
|
- Existing drag/drop, rename, selection, dissolve, and archive export behavior must remain unchanged.
|
|
|
|
## Implementation
|
|
|
|
### 1. Add a focused export helper
|
|
|
|
Update `src/lib/zip-processing.ts` with an exported helper:
|
|
|
|
```ts
|
|
export const buildSubDocumentExport = async (
|
|
archiveName: string,
|
|
subDocument: SubDocument
|
|
): Promise<ExportUnit>
|
|
```
|
|
|
|
Behavior:
|
|
|
|
1. Reject an empty attachment list with the same explicit empty-export behavior used by
|
|
`mergeAttachments()`.
|
|
2. Merge `subDocument.attachments` by calling `mergeAttachments()`.
|
|
3. Return one `ExportUnit` using the existing archive/sub-document filename convention.
|
|
4. Keep `.pdf` normalization centralized through the existing `stripPdfExtension()` helper.
|
|
|
|
Refactor the `mode === 'separate'` branch of `buildArchiveExport()` to call
|
|
`buildSubDocumentExport()` for each non-empty sub-document. This ensures bulk-separate and
|
|
individual exports cannot drift in PDF contents or naming. Preserve sequential generation and the
|
|
existing loose-attachment export behavior.
|
|
|
|
Do not construct a temporary `ProcessedZipArchive` and do not invoke `buildArchiveExport(...,
|
|
'separate')` for one item: both approaches encode the wrong scope and risk exporting unrelated
|
|
attachments later.
|
|
|
|
### 2. Add individual download orchestration to the archive editor
|
|
|
|
Update `src/lib/components/ProcessedZipArchiveEditor.svelte`:
|
|
|
|
- Import `buildSubDocumentExport`.
|
|
- Add `downloadingSubDocumentId: string | null` state alongside `isMerging` and `mergeError`.
|
|
- Derive whether any export is active from `isMerging || downloadingSubDocumentId !== null`.
|
|
- Add `downloadSubDocument(subDocument: SubDocument)` that:
|
|
1. Returns when another export is active or the sub-document is empty.
|
|
2. Stores the selected sub-document ID and clears `mergeError`.
|
|
3. Calls `buildSubDocumentExport(archiveName, subDocument)`.
|
|
4. Passes the returned bytes and name to the existing `downloadPdfBytes()` helper.
|
|
5. On failure, logs context including the archive and sub-document and sets
|
|
`Teildokument „{name}“ konnte nicht als PDF erstellt werden.`.
|
|
6. Clears the active ID in `finally`.
|
|
- Update `downloadArchive()` to guard against any active export, not only `isMerging`.
|
|
- Disable the archive-level download button and export-mode controls while an individual export is
|
|
active. The mode controls should remain visible and retain their selected value.
|
|
- Pass these props to every `SubDocumentEditor`:
|
|
- `onDownload={() => downloadSubDocument(subDocument)}`
|
|
- `isDownloading={downloadingSubDocumentId === subDocument.id}`
|
|
- `downloadDisabled={isMerging || downloadingSubDocumentId !== null}`
|
|
|
|
The browser download implementation stays in the parent so Blob/object-URL creation, filename
|
|
normalization, loading coordination, and errors remain centralized.
|
|
|
|
### 3. Add the sub-document header action
|
|
|
|
Update `src/lib/components/SubDocumentEditor.svelte`:
|
|
|
|
- Import the existing local `Button` component.
|
|
- Extend `Props` with:
|
|
- `onDownload?: () => void`
|
|
- `isDownloading?: boolean`
|
|
- `downloadDisabled?: boolean`
|
|
- Give all new props safe defaults.
|
|
- Render a bordered button before **Auflösen**:
|
|
- Normal label: **PDF herunterladen**
|
|
- Active label: **PDF wird erstellt**
|
|
- Disabled when `downloadDisabled`, `isDownloading`, or `attachments.length === 0`
|
|
- Keep the control as a native button through the shared `Button` component, with a clear accessible
|
|
label that includes the sub-document name if the visible label alone is ambiguous in assistive
|
|
technology.
|
|
- Prevent the button from participating in drag initiation in the same way as the existing name and
|
|
dissolve controls.
|
|
- Keep **Auflösen** visually distinct as the destructive action; do not change its behavior.
|
|
|
|
Use existing button variants and semantic theme utilities. Do not introduce a new UI dependency or a
|
|
second download utility in the child component.
|
|
|
|
## Files changed
|
|
|
|
- `src/lib/zip-processing.ts`
|
|
- Add `buildSubDocumentExport()`.
|
|
- Reuse it from separate archive export.
|
|
- `src/lib/components/ProcessedZipArchiveEditor.svelte`
|
|
- Coordinate individual export state, errors, and browser download.
|
|
- `src/lib/components/SubDocumentEditor.svelte`
|
|
- Add the individual download button and callback props.
|
|
- `agents.md`
|
|
- Clarify that per-archive separate export and direct per-sub-document export are both supported.
|
|
|
|
No route, server endpoint, persistence, package, lockfile, or Docker changes are required.
|
|
|
|
## Verification
|
|
|
|
### Automated checks
|
|
|
|
Run:
|
|
|
|
```sh
|
|
pnpm exec prettier --write \
|
|
src/lib/zip-processing.ts \
|
|
src/lib/components/ProcessedZipArchiveEditor.svelte \
|
|
src/lib/components/SubDocumentEditor.svelte \
|
|
agents.md
|
|
pnpm check
|
|
pnpm lint
|
|
pnpm build
|
|
```
|
|
|
|
There is no test framework in the repository. Do not add one solely for this feature. If a test
|
|
framework is introduced separately, cover `buildSubDocumentExport()` with non-empty, empty, ordering,
|
|
and filename cases.
|
|
|
|
### Manual scenarios
|
|
|
|
1. Create an archive with two sub-documents and at least one loose attachment.
|
|
2. Download the first sub-document and verify exactly one PDF is triggered.
|
|
3. Confirm the PDF contains only the first sub-document's attachments and preserves their displayed
|
|
order.
|
|
4. Confirm the filename is `{archive} - {sub-document}.pdf` without duplicate `.pdf` suffixes.
|
|
5. Download the second sub-document and verify it has independent contents and naming.
|
|
6. Confirm an empty sub-document's download action is disabled.
|
|
7. Start an individual export and verify duplicate, sibling, and archive-level export actions cannot
|
|
start until it finishes.
|
|
8. Force an invalid PDF/image merge and verify a German inline error appears, loading state clears,
|
|
and editor state is unchanged.
|
|
9. Verify **Eine PDF** still exports all sub-documents followed by loose attachments.
|
|
10. Verify **Getrennt** still exports every non-empty sub-document and the loose-attachment PDF.
|
|
11. Verify rename, reorder, move, selection, Escape, and **Auflösen** interactions still work.
|
|
12. Verify the new control remains usable on a narrow viewport and by keyboard.
|
|
|
|
## Acceptance criteria
|
|
|
|
- Every non-empty sub-document has a directly accessible download action.
|
|
- The action downloads one PDF containing only that sub-document's ordered attachments.
|
|
- Individual and archive-level exports use the same filename convention and merge implementation.
|
|
- Empty sub-documents cannot be exported.
|
|
- Loading and error states are visible, duplicate operations are prevented, and state always resets.
|
|
- Existing archive-level export behavior and all editor interactions are preserved.
|
|
- `pnpm check`, `pnpm lint`, and `pnpm build` pass without new issues.
|