/** * AnnotationToolbar — wires "I selected text" into "evidence appears in * the sidebar". * * Visible only when a `pendingSelection` is set (the viewer publishes * captures into context, then this toolbar lets the user attach commentary * and commit). On Save it runs the full pipeline: * * 1. `createSelectors(capture, representation)` — anchor builds the * maximal selector set against the active representation. * 2. `engine.annotations.create(...)` — engine mints an Annotation + * emits AnnotationCreated. * 3. `engine.evidence.create(...)` — engine mints the EvidenceItem with * the user's commentary, emits EvidenceItemCreated. * * The sidebar re-renders via the engine event bus, so no other glue is * needed. */ import { useEffect, useState } from "react"; import { createSelectors } from "@anchor/index"; import { useActiveDocument, useEngine, usePendingSelection, } from "./EngineContext"; export function AnnotationToolbar() { const engine = useEngine(); const { document, representation } = useActiveDocument(); const { pending, set } = usePendingSelection(); const [commentary, setCommentary] = useState(""); // Reset the commentary box whenever a fresh selection arrives. useEffect(() => { setCommentary(""); }, [pending]); if (!pending || !document || !representation) return null; const handleSave = () => { const selectors = createSelectors(pending.capture, representation); const annotation = engine.annotations.create({ documentId: document.id, representationId: representation.id, selectors, quote: pending.capture.text, }); engine.evidence.create({ annotationIds: [annotation.id], ...(commentary.trim().length > 0 ? { commentary: commentary.trim() } : {}), }); set(null); }; const handleDiscard = () => set(null); const quote = pending.capture.text; const shortQuote = quote.length > 200 ? `${quote.slice(0, 200)}…` : quote; return (
New annotation ({pending.selectors.length} selector{pending.selectors.length === 1 ? "" : "s"})
“{shortQuote}”