mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-14 10:34:06 +02:00
make clicking on comments open the comments sidebar and more (#6174)
make clicking on comments open the comments sidebar and add a button to create comments into the empty state of the comments sidebar <img width="2056" height="1081" alt="Screenshot 2026-04-23 at 12 39 50 PM" src="https://github.com/user-attachments/assets/6f15484d-d04f-4900-92c6-b2cc397d6d08" /> <img width="627" height="396" alt="Screenshot 2026-04-23 at 12 40 06 PM" src="https://github.com/user-attachments/assets/509e5526-0082-4fc6-a98f-829bb4c1baf2" />
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { Group } from "@mantine/core";
|
||||
import { createPortal } from "react-dom";
|
||||
import { useEffect, useState, useRef, useCallback } from "react";
|
||||
import { useEffect, useState, useRef, useCallback, useMemo } from "react";
|
||||
import { useAnnotation } from "@embedpdf/plugin-annotation/react";
|
||||
import type { TrackedAnnotation } from "@embedpdf/plugin-annotation";
|
||||
import {
|
||||
@@ -47,7 +47,7 @@ function AnnotationSelectionMenuInner({
|
||||
}: AnnotationSelectionMenuProps & { documentId: string }) {
|
||||
const annotation = context?.annotation;
|
||||
const pageIndex = context?.pageIndex;
|
||||
const { provides } = useAnnotation(documentId);
|
||||
const { state, provides } = useAnnotation(documentId);
|
||||
const { scrollActions, requestCommentFocus } = useViewer();
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const [menuPosition, setMenuPosition] = useState<{
|
||||
@@ -65,17 +65,52 @@ function AnnotationSelectionMenuInner({
|
||||
wrapperRef,
|
||||
});
|
||||
|
||||
// Read isInSidebar from live EmbedPDF state rather than annotation.object, which can be
|
||||
// stale after updateAnnotation() is called while the annotation is selected.
|
||||
// Also checks non-empty contents: customData.isComment is not persisted to PDF, but
|
||||
// contents is a standard PDF field and survives save/reload.
|
||||
const isInSidebar = useMemo(() => {
|
||||
const annId = (annotation?.object as AnnotationObject | undefined)?.id;
|
||||
if (!annId) return false;
|
||||
for (const tracked of Object.values(state.byUid)) {
|
||||
const obj = tracked.object;
|
||||
if (obj.id !== annId) continue;
|
||||
const { type } = obj;
|
||||
// TEXT and CARET are standalone comment annotations — they use CommentButton,
|
||||
// not AttachCommentButton, so isInSidebar is irrelevant for them.
|
||||
if (
|
||||
type === PdfAnnotationSubtype.TEXT ||
|
||||
type === PdfAnnotationSubtype.CARET
|
||||
)
|
||||
return false;
|
||||
// customData is a runtime field EmbedPDF adds but doesn't declare in its TS types
|
||||
const customData = (
|
||||
obj as unknown as { customData?: Record<string, unknown> }
|
||||
).customData;
|
||||
const isExplicit = customData?.isComment === true;
|
||||
// customData (incl. toolId/isComment) is not persisted to PDF; contents is.
|
||||
// Any non-TEXT/FreeText/CARET annotation with non-empty contents has a comment.
|
||||
const hasContents =
|
||||
type !== PdfAnnotationSubtype.FREETEXT &&
|
||||
!obj.inReplyToId &&
|
||||
(obj.contents ?? "").trim().length > 0;
|
||||
return isExplicit || hasContents;
|
||||
}
|
||||
return false;
|
||||
}, [state, annotation?.object]);
|
||||
|
||||
// Auto-open the comments sidebar when a comment annotation is selected
|
||||
useEffect(() => {
|
||||
const annObj = annotation?.object as AnnotationObject | undefined;
|
||||
const annId = annObj?.id;
|
||||
if (!selected || !annId || pageIndex === undefined) return;
|
||||
const toolId = annObj?.customData?.toolId;
|
||||
const annType = annObj?.type;
|
||||
// TEXT (type 1) = textComment; CARET (type 14) = insertText/replaceText.
|
||||
// These are always comment annotations regardless of toolId (lost after reload).
|
||||
const isComment =
|
||||
(annType === PdfAnnotationSubtype.TEXT && toolId === "textComment") ||
|
||||
(annType === PdfAnnotationSubtype.CARET &&
|
||||
(toolId === "insertText" || toolId === "replaceText"));
|
||||
annType === PdfAnnotationSubtype.TEXT ||
|
||||
annType === PdfAnnotationSubtype.CARET ||
|
||||
isInSidebar;
|
||||
if (!isComment) return;
|
||||
requestCommentFocus(
|
||||
documentId,
|
||||
@@ -83,7 +118,7 @@ function AnnotationSelectionMenuInner({
|
||||
annId,
|
||||
(annObj?.contents ?? "").trim().length > 0,
|
||||
);
|
||||
}, [selected, annotation?.object]);
|
||||
}, [selected, annotation?.object, isInSidebar]);
|
||||
|
||||
// Click outside to deselect
|
||||
useEffect(() => {
|
||||
@@ -170,6 +205,7 @@ function AnnotationSelectionMenuInner({
|
||||
<Group gap="sm" wrap="nowrap" justify="center">
|
||||
<AnnotationTypeButtons
|
||||
{...handlers}
|
||||
isInSidebar={isInSidebar}
|
||||
annotation={annotation}
|
||||
documentId={documentId}
|
||||
pageIndex={pageIndex}
|
||||
|
||||
@@ -58,9 +58,13 @@ export function AnnotationTypeButtons(props: AnnotationTypeButtonsProps) {
|
||||
onCommentColorChange,
|
||||
} = props;
|
||||
|
||||
const attachCommentButton = (
|
||||
// When a comment is attached, show the same chat-bubble "View comment" button used by
|
||||
// standalone comment annotations. When no comment, show the "Add comment" attach button.
|
||||
const attachCommentButton = isInSidebar ? (
|
||||
<CommentButton hasContent={hasCommentContent} onClick={onViewComment} />
|
||||
) : (
|
||||
<AttachCommentButton
|
||||
isInSidebar={isInSidebar}
|
||||
isInSidebar={false}
|
||||
onView={onViewComment}
|
||||
onAdd={onAddToSidebar}
|
||||
/>
|
||||
@@ -221,10 +225,6 @@ export function AnnotationTypeButtons(props: AnnotationTypeButtonsProps) {
|
||||
return (
|
||||
<>
|
||||
{attachCommentButton}
|
||||
<CommentButton
|
||||
hasContent={hasCommentContent}
|
||||
onClick={onViewComment}
|
||||
/>
|
||||
<LinkButton
|
||||
firstLinkTarget={firstLinkTarget}
|
||||
onGoToLink={onGoToLink}
|
||||
|
||||
@@ -22,13 +22,37 @@ import EditIcon from "@mui/icons-material/Edit";
|
||||
import VisibilityIcon from "@mui/icons-material/Visibility";
|
||||
import { useAnnotation } from "@embedpdf/plugin-annotation/react";
|
||||
import { getSidebarAnnotationsWithRepliesGroupedByPage } from "@embedpdf/plugin-annotation";
|
||||
import { PdfAnnotationSubtype, PdfAnnotationReplyType } from "@embedpdf/models";
|
||||
import {
|
||||
PdfAnnotationSubtype,
|
||||
PdfAnnotationReplyType,
|
||||
type PdfAnnotationObject,
|
||||
type PdfTextAnnoObject,
|
||||
} from "@embedpdf/models";
|
||||
import { useCommentAuthor } from "@app/contexts/CommentAuthorContext";
|
||||
import { useViewer } from "@app/contexts/ViewerContext";
|
||||
import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext";
|
||||
import { useAnnotation as useAnnotationContext } from "@app/contexts/AnnotationContext";
|
||||
import LocalIcon from "@app/components/shared/LocalIcon";
|
||||
|
||||
const SIDEBAR_WIDTH = "18rem";
|
||||
|
||||
/** PDF subtypes that are inherently standalone comment annotations (not linked to other annotations). */
|
||||
const STANDALONE_COMMENT_SUBTYPES = new Set([
|
||||
PdfAnnotationSubtype.TEXT,
|
||||
PdfAnnotationSubtype.FREETEXT,
|
||||
PdfAnnotationSubtype.CARET,
|
||||
]);
|
||||
|
||||
function isStandaloneCommentType(type: number | undefined): boolean {
|
||||
return (
|
||||
type !== undefined &&
|
||||
STANDALONE_COMMENT_SUBTYPES.has(type as PdfAnnotationSubtype)
|
||||
);
|
||||
}
|
||||
|
||||
const ANNOTATE_PANEL_ID = "annotate" as const;
|
||||
const TEXT_COMMENT_TOOL_ID = "textComment" as const;
|
||||
|
||||
/** Format annotation date for display (e.g. "Mar 11, 6:05 PM"). */
|
||||
function formatCommentDate(obj: any): string {
|
||||
const raw =
|
||||
@@ -143,8 +167,20 @@ function isCommentAnnotation(ann: any): boolean {
|
||||
return true;
|
||||
// Any annotation explicitly added to comments via the "Add comment" button
|
||||
if (ann?.customData?.isComment === true) return true;
|
||||
// CARET (type 14) = insertText/replaceText; TEXT (type 1) = textComment
|
||||
if (!toolId && (ann?.type === 14 || ann?.type === 1)) return true;
|
||||
const type = ann?.type;
|
||||
// Standalone comment types (TEXT, FREETEXT, CARET) without a toolId are always comments
|
||||
if (!toolId && isStandaloneCommentType(type)) return true;
|
||||
// Non-standalone annotations with non-empty contents: customData (including isComment and
|
||||
// toolId) is NOT persisted to PDF, but `contents` is a standard PDF field and survives
|
||||
// save/reload. Exclude standalone comment types (TEXT, FREETEXT, CARET) which use
|
||||
// `contents` for their own annotation text. Exclude replies.
|
||||
if (
|
||||
type !== undefined &&
|
||||
!isStandaloneCommentType(type) &&
|
||||
!ann?.inReplyToId &&
|
||||
(ann?.contents ?? "").trim().length > 0
|
||||
)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -213,6 +249,8 @@ export function CommentsSidebar({
|
||||
} = useViewer() ?? {};
|
||||
const scrollViewportRef = useRef<HTMLDivElement | null>(null);
|
||||
const { state, provides } = useAnnotation(documentId);
|
||||
const { handleToolSelectForced } = useToolWorkflow();
|
||||
const { activateAnnotationToolRef } = useAnnotationContext();
|
||||
const [draftContents, setDraftContents] = useState<Record<string, string>>(
|
||||
{},
|
||||
);
|
||||
@@ -324,8 +362,8 @@ export function CommentsSidebar({
|
||||
const all = getSidebarAnnotationsWithRepliesGroupedByPage(state) ?? {};
|
||||
const filtered: typeof all = {};
|
||||
for (const [page, entries] of Object.entries(all)) {
|
||||
const commentEntries = (entries as typeof entries).filter((e) =>
|
||||
isCommentAnnotation((e as any).annotation?.object),
|
||||
const commentEntries = entries.filter((e) =>
|
||||
isCommentAnnotation(e.annotation.object),
|
||||
);
|
||||
if (commentEntries.length > 0) {
|
||||
filtered[Number(page)] = commentEntries;
|
||||
@@ -341,12 +379,11 @@ export function CommentsSidebar({
|
||||
// state is AnnotationDocumentState — selectedUids are keys in byUid, and may equal id.
|
||||
const selectedAnnotationIds = useMemo(() => {
|
||||
const selectedUids: string[] = state?.selectedUids ?? [];
|
||||
const byUid: Record<string, any> = (state as any)?.byUid ?? {};
|
||||
const ids = new Set<string>();
|
||||
for (const uid of selectedUids) {
|
||||
// uid itself may be the annotation id
|
||||
ids.add(uid);
|
||||
const annId = byUid[uid]?.object?.id;
|
||||
const annId = state.byUid[uid]?.object.id;
|
||||
if (annId) ids.add(annId);
|
||||
}
|
||||
return ids;
|
||||
@@ -383,12 +420,12 @@ export function CommentsSidebar({
|
||||
} | null>(null);
|
||||
|
||||
const isLinkedAnnotation = (ann: any) => {
|
||||
const toolId = ann?.customData?.toolId ?? ann?.customData?.annotationToolId;
|
||||
const type = ann?.type;
|
||||
if (isStandaloneCommentType(type)) return false;
|
||||
if (ann?.inReplyToId) return false;
|
||||
return (
|
||||
ann?.customData?.isComment === true &&
|
||||
toolId !== "textComment" &&
|
||||
toolId !== "insertText" &&
|
||||
toolId !== "replaceText"
|
||||
ann?.customData?.isComment === true ||
|
||||
(type !== undefined && (ann?.contents ?? "").trim().length > 0)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -408,7 +445,13 @@ export function CommentsSidebar({
|
||||
const { pageIndex, id, ann } = deleteModal;
|
||||
const existing = (ann?.customData ?? {}) as Record<string, unknown>;
|
||||
const { isComment: _removed, ...rest } = existing;
|
||||
provides.updateAnnotation(pageIndex, id, { customData: rest } as any);
|
||||
// Also clear contents: the contents field is the persisted signal for
|
||||
// post-reload linked annotations, so clearing it removes the annotation
|
||||
// from the sidebar (contents is not visually rendered on ink/shape/markup types).
|
||||
provides.updateAnnotation(pageIndex, id, {
|
||||
customData: rest,
|
||||
contents: "",
|
||||
} as unknown as Partial<PdfAnnotationObject>);
|
||||
setDeleteModal(null);
|
||||
}, [deleteModal, provides]);
|
||||
|
||||
@@ -443,7 +486,7 @@ export function CommentsSidebar({
|
||||
origin: { x: 0, y: 0 },
|
||||
size: { width: 1, height: 1 },
|
||||
};
|
||||
provides.createAnnotation(pageIndex, {
|
||||
const reply: PdfTextAnnoObject = {
|
||||
type: PdfAnnotationSubtype.TEXT,
|
||||
id: `reply-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
||||
pageIndex,
|
||||
@@ -452,7 +495,8 @@ export function CommentsSidebar({
|
||||
inReplyToId: parentId,
|
||||
replyType: PdfAnnotationReplyType.Reply,
|
||||
author: displayName,
|
||||
} as any);
|
||||
};
|
||||
provides.createAnnotation(pageIndex, reply);
|
||||
setReplyDrafts((prev) => ({ ...prev, [key]: "" }));
|
||||
},
|
||||
[provides, replyDrafts, displayName],
|
||||
@@ -476,6 +520,13 @@ export function CommentsSidebar({
|
||||
[provides, displayName],
|
||||
);
|
||||
|
||||
const handleAddComment = useCallback(() => {
|
||||
handleToolSelectForced(ANNOTATE_PANEL_ID);
|
||||
requestAnimationFrame(() => {
|
||||
activateAnnotationToolRef.current?.(TEXT_COMMENT_TOOL_ID);
|
||||
});
|
||||
}, [handleToolSelectForced, activateAnnotationToolRef]);
|
||||
|
||||
if (!visible) return null;
|
||||
|
||||
return (
|
||||
@@ -510,19 +561,49 @@ export function CommentsSidebar({
|
||||
height="1.25rem"
|
||||
style={{ color: "var(--mantine-color-dimmed)", flexShrink: 0 }}
|
||||
/>
|
||||
<Text fw={600} size="sm" tt="uppercase" lts={0.5}>
|
||||
<Text fw={600} size="sm" tt="uppercase" lts={0.5} style={{ flex: 1 }}>
|
||||
{t("viewer.comments.title", "Comments")}
|
||||
</Text>
|
||||
{totalCount > 0 && (
|
||||
<Tooltip label={t("viewer.comments.addComment", "Add comment")}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
color="gray"
|
||||
onClick={handleAddComment}
|
||||
>
|
||||
<LocalIcon icon="add" width="1.25rem" height="1.25rem" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<ScrollArea style={{ flex: 1 }}>
|
||||
<Stack p="sm" gap="md">
|
||||
{totalCount === 0 ? (
|
||||
<Text size="sm" c="dimmed">
|
||||
<Stack align="center" gap="sm" py="lg">
|
||||
<LocalIcon
|
||||
icon="comment"
|
||||
width="2rem"
|
||||
height="2rem"
|
||||
style={{ color: "var(--mantine-color-dimmed)" }}
|
||||
/>
|
||||
<Text size="sm" c="dimmed" ta="center">
|
||||
{t(
|
||||
"viewer.comments.hint",
|
||||
"Place comments with the Comment, Insert Text, or Replace Text tools. They will appear here by page.",
|
||||
)}
|
||||
</Text>
|
||||
<Button
|
||||
variant="light"
|
||||
size="xs"
|
||||
onClick={handleAddComment}
|
||||
leftSection={
|
||||
<LocalIcon icon="add" width="1rem" height="1rem" />
|
||||
}
|
||||
>
|
||||
{t("viewer.comments.addComment", "Add comment")}
|
||||
</Button>
|
||||
</Stack>
|
||||
) : (
|
||||
pageNumbers.map((pageIndex) => {
|
||||
const entries = byPage[pageIndex] ?? [];
|
||||
|
||||
@@ -27,6 +27,32 @@ export type AnnotationType =
|
||||
| "stamp"
|
||||
| "unknown";
|
||||
|
||||
const TEXT_MARKUP_SUBTYPES = [
|
||||
PdfAnnotationSubtype.HIGHLIGHT,
|
||||
PdfAnnotationSubtype.UNDERLINE,
|
||||
PdfAnnotationSubtype.SQUIGGLY,
|
||||
PdfAnnotationSubtype.STRIKEOUT,
|
||||
];
|
||||
|
||||
const SHAPE_SUBTYPES = [
|
||||
PdfAnnotationSubtype.SQUARE,
|
||||
PdfAnnotationSubtype.CIRCLE,
|
||||
PdfAnnotationSubtype.POLYGON,
|
||||
];
|
||||
|
||||
const LINE_SUBTYPES = [
|
||||
PdfAnnotationSubtype.LINE,
|
||||
PdfAnnotationSubtype.POLYLINE,
|
||||
];
|
||||
|
||||
const STROKE_COLOR_SUBTYPES = [
|
||||
PdfAnnotationSubtype.LINE,
|
||||
PdfAnnotationSubtype.SQUARE,
|
||||
PdfAnnotationSubtype.CIRCLE,
|
||||
PdfAnnotationSubtype.POLYGON,
|
||||
PdfAnnotationSubtype.POLYLINE,
|
||||
];
|
||||
|
||||
export type FirstLinkTarget =
|
||||
| { type: "uri"; uri: string }
|
||||
| { type: "goto"; pageIndex: number };
|
||||
@@ -134,17 +160,36 @@ export function useAnnotationMenuHandlers({
|
||||
const toolId = (annotation?.object as AnnotationObject | undefined)
|
||||
?.customData?.toolId;
|
||||
|
||||
if (type !== undefined && [9, 10, 11, 12].includes(type))
|
||||
if (
|
||||
type !== undefined &&
|
||||
TEXT_MARKUP_SUBTYPES.includes(type as PdfAnnotationSubtype)
|
||||
)
|
||||
return "textMarkup";
|
||||
if (type === 15)
|
||||
if (type === PdfAnnotationSubtype.INK)
|
||||
return toolId === "inkHighlighter" ? "inkHighlighter" : "ink";
|
||||
if (type === 1 && toolId === "textComment") return "comment";
|
||||
if (type === 14 && (toolId === "insertText" || toolId === "replaceText"))
|
||||
// TEXT and CARET fall back to type alone after save/reload (customData.toolId is absent).
|
||||
if (
|
||||
type === PdfAnnotationSubtype.TEXT &&
|
||||
(!toolId || toolId === "textComment")
|
||||
)
|
||||
return "comment";
|
||||
if (type === 3) return "note";
|
||||
if (type !== undefined && [5, 6, 7].includes(type)) return "shape";
|
||||
if (type !== undefined && [4, 8].includes(type)) return "line";
|
||||
if (type === 13) return "stamp";
|
||||
if (
|
||||
type === PdfAnnotationSubtype.CARET &&
|
||||
(!toolId || toolId === "insertText" || toolId === "replaceText")
|
||||
)
|
||||
return "comment";
|
||||
if (type === PdfAnnotationSubtype.FREETEXT) return "note";
|
||||
if (
|
||||
type !== undefined &&
|
||||
SHAPE_SUBTYPES.includes(type as PdfAnnotationSubtype)
|
||||
)
|
||||
return "shape";
|
||||
if (
|
||||
type !== undefined &&
|
||||
LINE_SUBTYPES.includes(type as PdfAnnotationSubtype)
|
||||
)
|
||||
return "line";
|
||||
if (type === PdfAnnotationSubtype.STAMP) return "stamp";
|
||||
return "unknown";
|
||||
}, [annotation]);
|
||||
|
||||
@@ -174,8 +219,12 @@ export function useAnnotationMenuHandlers({
|
||||
const currentColor = (() => {
|
||||
if (!obj) return "#000000";
|
||||
const type = obj.type;
|
||||
if (type === 3) return obj.textColor || obj.color || "#000000";
|
||||
if (type !== undefined && [4, 5, 6, 7, 8].includes(type))
|
||||
if (type === PdfAnnotationSubtype.FREETEXT)
|
||||
return obj.textColor || obj.color || "#000000";
|
||||
if (
|
||||
type !== undefined &&
|
||||
STROKE_COLOR_SUBTYPES.includes(type as PdfAnnotationSubtype)
|
||||
)
|
||||
return obj.strokeColor || obj.color || "#000000";
|
||||
return obj.color || obj.strokeColor || "#000000";
|
||||
})();
|
||||
@@ -247,17 +296,23 @@ export function useAnnotationMenuHandlers({
|
||||
patch.contents = obj?.contents ?? "";
|
||||
} else {
|
||||
patch.color = color;
|
||||
if (type !== undefined && [9, 10, 11, 12].includes(type)) {
|
||||
if (
|
||||
type !== undefined &&
|
||||
TEXT_MARKUP_SUBTYPES.includes(type as PdfAnnotationSubtype)
|
||||
) {
|
||||
patch.strokeColor = color;
|
||||
patch.fillColor = color;
|
||||
patch.opacity = obj?.opacity ?? 1;
|
||||
}
|
||||
if (type !== undefined && [4, 8].includes(type)) {
|
||||
if (
|
||||
type !== undefined &&
|
||||
LINE_SUBTYPES.includes(type as PdfAnnotationSubtype)
|
||||
) {
|
||||
patch.strokeColor = color;
|
||||
patch.strokeWidth = obj?.strokeWidth ?? obj?.lineWidth ?? 2;
|
||||
patch.lineWidth = obj?.lineWidth ?? obj?.strokeWidth ?? 2;
|
||||
}
|
||||
if (type === 15) {
|
||||
if (type === PdfAnnotationSubtype.INK) {
|
||||
patch.strokeColor = color;
|
||||
patch.strokeWidth = obj?.strokeWidth ?? obj?.thickness ?? 2;
|
||||
patch.opacity = obj?.opacity ?? 1;
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
import React, { createContext, useContext, ReactNode, useRef } from "react";
|
||||
import type { AnnotationAPI } from "@app/components/viewer/viewerTypes";
|
||||
import type {
|
||||
AnnotationAPI,
|
||||
AnnotationToolId,
|
||||
} from "@app/components/viewer/viewerTypes";
|
||||
|
||||
interface AnnotationContextValue {
|
||||
annotationApiRef: React.RefObject<AnnotationAPI | null>;
|
||||
/** Ref to the panel-level activateAnnotationTool function — updates React state so buttons highlight correctly. Populated by Annotate.tsx. */
|
||||
activateAnnotationToolRef: React.RefObject<
|
||||
((toolId: AnnotationToolId) => void) | null
|
||||
>;
|
||||
}
|
||||
|
||||
const AnnotationContext = createContext<AnnotationContextValue | undefined>(
|
||||
@@ -13,9 +20,13 @@ export const AnnotationProvider: React.FC<{ children: ReactNode }> = ({
|
||||
children,
|
||||
}) => {
|
||||
const annotationApiRef = useRef<AnnotationAPI>(null);
|
||||
const activateAnnotationToolRef = useRef<
|
||||
((toolId: AnnotationToolId) => void) | null
|
||||
>(null);
|
||||
|
||||
const value: AnnotationContextValue = {
|
||||
annotationApiRef,
|
||||
activateAnnotationToolRef,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useNavigation } from "@app/contexts/NavigationContext";
|
||||
import { useFileSelection } from "@app/contexts/FileContext";
|
||||
import { BaseToolProps } from "@app/types/tool";
|
||||
import { useSignature } from "@app/contexts/SignatureContext";
|
||||
import { useAnnotation as useAnnotationContext } from "@app/contexts/AnnotationContext";
|
||||
import { ViewerContext, useViewer } from "@app/contexts/ViewerContext";
|
||||
import type {
|
||||
AnnotationToolId,
|
||||
@@ -85,6 +86,7 @@ const Annotate = (_props: BaseToolProps) => {
|
||||
placementPreviewSize,
|
||||
setPlacementPreviewSize,
|
||||
} = useSignature();
|
||||
const { activateAnnotationToolRef } = useAnnotationContext();
|
||||
const viewerContext = useContext(ViewerContext);
|
||||
const viewerContextRef = useRef(viewerContext);
|
||||
useEffect(() => {
|
||||
@@ -375,6 +377,9 @@ const Annotate = (_props: BaseToolProps) => {
|
||||
}, 300);
|
||||
};
|
||||
|
||||
// Keep ref in sync so external callers (e.g. CommentsSidebar) get the latest closure
|
||||
activateAnnotationToolRef.current = activateAnnotationTool;
|
||||
|
||||
useEffect(() => {
|
||||
// push style updates to EmbedPDF when sliders/colors change
|
||||
if (activeTool === "stamp") {
|
||||
|
||||
Reference in New Issue
Block a user