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 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@ type Props = {
|
||||
blockNumbers?: Record<string, number>;
|
||||
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}
|
||||
|
||||
@@ -16,7 +16,8 @@ let {
|
||||
activeAnnotationId = $bindable<string | null>(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<PDFDocumentProxy | null>(null);
|
||||
@@ -456,6 +458,7 @@ function zoomOut() {
|
||||
color={TRANSCRIPTION_COLOR}
|
||||
blockNumbers={blockNumbers}
|
||||
activeAnnotationId={activeAnnotationId}
|
||||
dimmed={annotationsDimmed}
|
||||
onDraw={handleDraw}
|
||||
onAnnotationClick={handleAnnotationClick}
|
||||
/>
|
||||
|
||||
@@ -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<string | null>(null);
|
||||
let highlightBlockId = $state<string | null>(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}
|
||||
<div
|
||||
class="shrink-0 border-t border-line md:w-[400px] md:border-t-0 md:border-l lg:w-[480px]"
|
||||
class="flex shrink-0 flex-col border-t border-line md:w-[400px] md:border-t-0 md:border-l lg:w-[480px]"
|
||||
>
|
||||
<TranscriptionEditView
|
||||
documentId={doc.id}
|
||||
blocks={transcriptionBlocks}
|
||||
canComment={canWrite}
|
||||
currentUserId={currentUserId}
|
||||
activeAnnotationId={activeAnnotationId}
|
||||
onBlockFocus={handleBlockFocus}
|
||||
onSaveBlock={saveBlock}
|
||||
onDeleteBlock={deleteBlock}
|
||||
<TranscriptionPanelHeader
|
||||
mode={panelMode}
|
||||
hasBlocks={hasBlocks}
|
||||
blockCount={transcriptionBlocks.length}
|
||||
lastEditedAt={lastEditedAt}
|
||||
onModeChange={(m) => (panelMode = m)}
|
||||
onClose={() => (transcribeMode = false)}
|
||||
/>
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
{#if panelMode === 'read'}
|
||||
<TranscriptionReadView
|
||||
blocks={transcriptionBlocks}
|
||||
highlightBlockId={highlightBlockId}
|
||||
onParagraphClick={handleParagraphClick}
|
||||
/>
|
||||
{:else}
|
||||
<TranscriptionEditView
|
||||
documentId={doc.id}
|
||||
blocks={transcriptionBlocks}
|
||||
canComment={canWrite}
|
||||
currentUserId={currentUserId}
|
||||
activeAnnotationId={activeAnnotationId}
|
||||
onBlockFocus={handleBlockFocus}
|
||||
onSaveBlock={saveBlock}
|
||||
onDeleteBlock={deleteBlock}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user