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>;
|
blockNumbers?: Record<string, number>;
|
||||||
annotationReloadKey?: number;
|
annotationReloadKey?: number;
|
||||||
activeAnnotationId: string | null;
|
activeAnnotationId: string | null;
|
||||||
|
annotationsDimmed?: boolean;
|
||||||
onAnnotationClick: (id: string) => void;
|
onAnnotationClick: (id: string) => void;
|
||||||
onTranscriptionDraw?: (rect: DrawRect) => void;
|
onTranscriptionDraw?: (rect: DrawRect) => void;
|
||||||
};
|
};
|
||||||
@@ -33,6 +34,7 @@ let {
|
|||||||
blockNumbers = {},
|
blockNumbers = {},
|
||||||
annotationReloadKey = 0,
|
annotationReloadKey = 0,
|
||||||
activeAnnotationId = $bindable(),
|
activeAnnotationId = $bindable(),
|
||||||
|
annotationsDimmed = false,
|
||||||
onAnnotationClick,
|
onAnnotationClick,
|
||||||
onTranscriptionDraw
|
onTranscriptionDraw
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
@@ -90,6 +92,7 @@ let {
|
|||||||
blockNumbers={blockNumbers}
|
blockNumbers={blockNumbers}
|
||||||
annotationReloadKey={annotationReloadKey}
|
annotationReloadKey={annotationReloadKey}
|
||||||
bind:activeAnnotationId={activeAnnotationId}
|
bind:activeAnnotationId={activeAnnotationId}
|
||||||
|
annotationsDimmed={annotationsDimmed}
|
||||||
onAnnotationClick={onAnnotationClick}
|
onAnnotationClick={onAnnotationClick}
|
||||||
onTranscriptionDraw={onTranscriptionDraw}
|
onTranscriptionDraw={onTranscriptionDraw}
|
||||||
documentFileHash={doc.fileHash ?? null}
|
documentFileHash={doc.fileHash ?? null}
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ let {
|
|||||||
activeAnnotationId = $bindable<string | null>(null),
|
activeAnnotationId = $bindable<string | null>(null),
|
||||||
onAnnotationClick,
|
onAnnotationClick,
|
||||||
onTranscriptionDraw,
|
onTranscriptionDraw,
|
||||||
documentFileHash
|
documentFileHash,
|
||||||
|
annotationsDimmed = false
|
||||||
}: {
|
}: {
|
||||||
url: string;
|
url: string;
|
||||||
documentId?: string;
|
documentId?: string;
|
||||||
@@ -27,6 +28,7 @@ let {
|
|||||||
onAnnotationClick?: (id: string) => void;
|
onAnnotationClick?: (id: string) => void;
|
||||||
onTranscriptionDraw?: (rect: DrawRect) => void;
|
onTranscriptionDraw?: (rect: DrawRect) => void;
|
||||||
documentFileHash?: string | null;
|
documentFileHash?: string | null;
|
||||||
|
annotationsDimmed?: boolean;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let pdfDoc = $state<PDFDocumentProxy | null>(null);
|
let pdfDoc = $state<PDFDocumentProxy | null>(null);
|
||||||
@@ -456,6 +458,7 @@ function zoomOut() {
|
|||||||
color={TRANSCRIPTION_COLOR}
|
color={TRANSCRIPTION_COLOR}
|
||||||
blockNumbers={blockNumbers}
|
blockNumbers={blockNumbers}
|
||||||
activeAnnotationId={activeAnnotationId}
|
activeAnnotationId={activeAnnotationId}
|
||||||
|
dimmed={annotationsDimmed}
|
||||||
onDraw={handleDraw}
|
onDraw={handleDraw}
|
||||||
onAnnotationClick={handleAnnotationClick}
|
onAnnotationClick={handleAnnotationClick}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { onMount } from 'svelte';
|
|||||||
import DocumentTopBar from '$lib/components/DocumentTopBar.svelte';
|
import DocumentTopBar from '$lib/components/DocumentTopBar.svelte';
|
||||||
import DocumentViewer from '$lib/components/DocumentViewer.svelte';
|
import DocumentViewer from '$lib/components/DocumentViewer.svelte';
|
||||||
import TranscriptionEditView from '$lib/components/TranscriptionEditView.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';
|
import type { TranscriptionBlockData } from '$lib/types';
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
@@ -49,7 +51,9 @@ async function loadFile(id: string) {
|
|||||||
// ── Mode state ───────────────────────────────────────────────────────────────
|
// ── Mode state ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
let transcribeMode = $state(false);
|
let transcribeMode = $state(false);
|
||||||
|
let panelMode = $state<'read' | 'edit'>('read');
|
||||||
let activeAnnotationId = $state<string | null>(null);
|
let activeAnnotationId = $state<string | null>(null);
|
||||||
|
let highlightBlockId = $state<string | null>(null);
|
||||||
|
|
||||||
// ── Transcription blocks ─────────────────────────────────────────────────────
|
// ── 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() {
|
async function loadTranscriptionBlocks() {
|
||||||
if (!doc?.id) return;
|
if (!doc?.id) return;
|
||||||
try {
|
try {
|
||||||
@@ -142,9 +157,17 @@ async function handleAnnotationClick(annotationId: string) {
|
|||||||
await loadTranscriptionBlocks();
|
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(() => {
|
requestAnimationFrame(() => {
|
||||||
const block = transcriptionBlocks.find((b) => b.annotationId === annotationId);
|
|
||||||
if (block) {
|
if (block) {
|
||||||
const el = document.querySelector(`[data-block-id="${block.id}"]`);
|
const el = document.querySelector(`[data-block-id="${block.id}"]`);
|
||||||
el?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
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(() => {
|
$effect(() => {
|
||||||
if (transcribeMode) {
|
if (transcribeMode) {
|
||||||
loadTranscriptionBlocks();
|
loadTranscriptionBlocks().then(() => {
|
||||||
|
panelMode = transcriptionBlocks.length > 0 ? 'read' : 'edit';
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -211,6 +240,7 @@ onMount(() => {
|
|||||||
transcribeMode={transcribeMode}
|
transcribeMode={transcribeMode}
|
||||||
blockNumbers={blockNumbers}
|
blockNumbers={blockNumbers}
|
||||||
annotationReloadKey={annotationReloadKey}
|
annotationReloadKey={annotationReloadKey}
|
||||||
|
annotationsDimmed={transcribeMode && panelMode === 'read'}
|
||||||
bind:activeAnnotationId={activeAnnotationId}
|
bind:activeAnnotationId={activeAnnotationId}
|
||||||
onAnnotationClick={handleAnnotationClick}
|
onAnnotationClick={handleAnnotationClick}
|
||||||
onTranscriptionDraw={createBlockFromDraw}
|
onTranscriptionDraw={createBlockFromDraw}
|
||||||
@@ -219,18 +249,36 @@ onMount(() => {
|
|||||||
|
|
||||||
{#if transcribeMode}
|
{#if transcribeMode}
|
||||||
<div
|
<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
|
<TranscriptionPanelHeader
|
||||||
documentId={doc.id}
|
mode={panelMode}
|
||||||
blocks={transcriptionBlocks}
|
hasBlocks={hasBlocks}
|
||||||
canComment={canWrite}
|
blockCount={transcriptionBlocks.length}
|
||||||
currentUserId={currentUserId}
|
lastEditedAt={lastEditedAt}
|
||||||
activeAnnotationId={activeAnnotationId}
|
onModeChange={(m) => (panelMode = m)}
|
||||||
onBlockFocus={handleBlockFocus}
|
onClose={() => (transcribeMode = false)}
|
||||||
onSaveBlock={saveBlock}
|
|
||||||
onDeleteBlock={deleteBlock}
|
|
||||||
/>
|
/>
|
||||||
|
<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>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user