From e089192d7a8f7c7dc69d3bbbb5ae07ab53085d99 Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 7 Apr 2026 11:21:15 +0200 Subject: [PATCH] feat(ui): wire panelMode state with read/edit view switching Adds TranscriptionPanelHeader and TranscriptionReadView to the document detail page. Default mode is 'read' when blocks exist, 'edit' otherwise. Annotations dimmed in read mode. Co-Authored-By: Claude Sonnet 4.6 --- .../src/lib/components/DocumentViewer.svelte | 3 + frontend/src/lib/components/PdfViewer.svelte | 5 +- .../src/routes/documents/[id]/+page.svelte | 76 +++++++++++++++---- 3 files changed, 69 insertions(+), 15 deletions(-) diff --git a/frontend/src/lib/components/DocumentViewer.svelte b/frontend/src/lib/components/DocumentViewer.svelte index 6266f975..707ae0ba 100644 --- a/frontend/src/lib/components/DocumentViewer.svelte +++ b/frontend/src/lib/components/DocumentViewer.svelte @@ -20,6 +20,7 @@ type Props = { blockNumbers?: Record; annotationReloadKey?: number; activeAnnotationId: string | null; + annotationsDimmed?: boolean; onAnnotationClick: (id: string) => void; onTranscriptionDraw?: (rect: DrawRect) => void; }; @@ -33,6 +34,7 @@ let { blockNumbers = {}, annotationReloadKey = 0, activeAnnotationId = $bindable(), + annotationsDimmed = false, onAnnotationClick, onTranscriptionDraw }: Props = $props(); @@ -90,6 +92,7 @@ let { blockNumbers={blockNumbers} annotationReloadKey={annotationReloadKey} bind:activeAnnotationId={activeAnnotationId} + annotationsDimmed={annotationsDimmed} onAnnotationClick={onAnnotationClick} onTranscriptionDraw={onTranscriptionDraw} documentFileHash={doc.fileHash ?? null} diff --git a/frontend/src/lib/components/PdfViewer.svelte b/frontend/src/lib/components/PdfViewer.svelte index 2eac4308..05373848 100644 --- a/frontend/src/lib/components/PdfViewer.svelte +++ b/frontend/src/lib/components/PdfViewer.svelte @@ -16,7 +16,8 @@ let { activeAnnotationId = $bindable(null), onAnnotationClick, onTranscriptionDraw, - documentFileHash + documentFileHash, + annotationsDimmed = false }: { url: string; documentId?: string; @@ -27,6 +28,7 @@ let { onAnnotationClick?: (id: string) => void; onTranscriptionDraw?: (rect: DrawRect) => void; documentFileHash?: string | null; + annotationsDimmed?: boolean; } = $props(); let pdfDoc = $state(null); @@ -456,6 +458,7 @@ function zoomOut() { color={TRANSCRIPTION_COLOR} blockNumbers={blockNumbers} activeAnnotationId={activeAnnotationId} + dimmed={annotationsDimmed} onDraw={handleDraw} onAnnotationClick={handleAnnotationClick} /> diff --git a/frontend/src/routes/documents/[id]/+page.svelte b/frontend/src/routes/documents/[id]/+page.svelte index a3af67a0..f4f5d4eb 100644 --- a/frontend/src/routes/documents/[id]/+page.svelte +++ b/frontend/src/routes/documents/[id]/+page.svelte @@ -3,6 +3,8 @@ import { onMount } from 'svelte'; import DocumentTopBar from '$lib/components/DocumentTopBar.svelte'; import DocumentViewer from '$lib/components/DocumentViewer.svelte'; import TranscriptionEditView from '$lib/components/TranscriptionEditView.svelte'; +import TranscriptionReadView from '$lib/components/TranscriptionReadView.svelte'; +import TranscriptionPanelHeader from '$lib/components/TranscriptionPanelHeader.svelte'; import type { TranscriptionBlockData } from '$lib/types'; let { data } = $props(); @@ -49,7 +51,9 @@ async function loadFile(id: string) { // ── Mode state ─────────────────────────────────────────────────────────────── let transcribeMode = $state(false); +let panelMode = $state<'read' | 'edit'>('read'); let activeAnnotationId = $state(null); +let highlightBlockId = $state(null); // ── Transcription blocks ───────────────────────────────────────────────────── @@ -64,6 +68,17 @@ const blockNumbers = $derived( ) ); +const hasBlocks = $derived(transcriptionBlocks.length > 0); + +const lastEditedAt = $derived.by(() => { + if (transcriptionBlocks.length === 0) return null; + const dates = transcriptionBlocks + .filter((b) => b.updatedAt) + .map((b) => new Date(b.updatedAt!).getTime()); + if (dates.length === 0) return null; + return new Date(Math.max(...dates)).toISOString(); +}); + async function loadTranscriptionBlocks() { if (!doc?.id) return; try { @@ -142,9 +157,17 @@ async function handleAnnotationClick(annotationId: string) { await loadTranscriptionBlocks(); } - // Wait for DOM to render the blocks, then scroll to the matching one + // In read mode, highlight the matching paragraph + const block = transcriptionBlocks.find((b) => b.annotationId === annotationId); + if (block) { + highlightBlockId = block.id; + setTimeout(() => { + highlightBlockId = null; + }, 1500); + } + + // Wait for DOM to render, then scroll to the matching block requestAnimationFrame(() => { - const block = transcriptionBlocks.find((b) => b.annotationId === annotationId); if (block) { const el = document.querySelector(`[data-block-id="${block.id}"]`); el?.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); @@ -152,10 +175,16 @@ async function handleAnnotationClick(annotationId: string) { }); } -// Load blocks when transcribe mode is entered +function handleParagraphClick(annotationId: string) { + activeAnnotationId = annotationId; +} + +// Load blocks when transcribe mode is entered and set default panel mode $effect(() => { if (transcribeMode) { - loadTranscriptionBlocks(); + loadTranscriptionBlocks().then(() => { + panelMode = transcriptionBlocks.length > 0 ? 'read' : 'edit'; + }); } }); @@ -211,6 +240,7 @@ onMount(() => { transcribeMode={transcribeMode} blockNumbers={blockNumbers} annotationReloadKey={annotationReloadKey} + annotationsDimmed={transcribeMode && panelMode === 'read'} bind:activeAnnotationId={activeAnnotationId} onAnnotationClick={handleAnnotationClick} onTranscriptionDraw={createBlockFromDraw} @@ -219,18 +249,36 @@ onMount(() => { {#if transcribeMode}
- (panelMode = m)} + onClose={() => (transcribeMode = false)} /> +
+ {#if panelMode === 'read'} + + {:else} + + {/if} +
{/if}