import { useRedaction as useEmbedPdfRedaction, RedactionSelectionMenuProps } from '@embedpdf/plugin-redaction/react'; import { PdfAnnotationSubtype } from '@embedpdf/models'; import { ActionIcon, Tooltip, Button, Group } from '@mantine/core'; import { useTranslation } from 'react-i18next'; import { createPortal } from 'react-dom'; import { useEffect, useState, useRef, useCallback } from 'react'; import DeleteIcon from '@mui/icons-material/Delete'; import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import { useRedaction } from '@app/contexts/RedactionContext'; import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId'; export type { RedactionSelectionMenuProps }; export function RedactionSelectionMenu(props: any) { const activeDocumentId = useActiveDocumentId(); // Don't render until we have a valid document ID if (!activeDocumentId) { return null; } return ( ); } function RedactionSelectionMenuInner({ documentId, context, selected, menuWrapperProps, }: RedactionSelectionMenuProps & { documentId: string }) { const item = context?.type === 'redaction' ? context.item : (context?.type === 'annotation' ? (context as any).annotation?.object : null); const isRedaction = context?.type === 'redaction' || (context?.type === 'annotation' && item?.type === PdfAnnotationSubtype.REDACT); const pageIndex = context?.pageIndex; const { t } = useTranslation(); const { provides } = useEmbedPdfRedaction(documentId); const { setRedactionsApplied } = useRedaction(); const wrapperRef = useRef(null); const [menuPosition, setMenuPosition] = useState<{ top: number; left: number } | null>(null); // Merge refs - menuWrapperProps.ref is a callback ref const setRef = useCallback((node: HTMLDivElement | null) => { wrapperRef.current = node; // Call the EmbedPDF ref callback menuWrapperProps?.ref?.(node); }, [menuWrapperProps]); const handleRemove = useCallback(() => { if (provides?.removePending && item && pageIndex !== undefined) { provides.removePending(pageIndex, item.id); } }, [provides, item, pageIndex]); const handleApply = useCallback(() => { if (provides?.commitPending && item && pageIndex !== undefined) { provides.commitPending(pageIndex, item.id); // Mark redactions as applied (but not yet saved) so the Save Changes button stays enabled // This ensures the button doesn't become disabled when pendingCount decreases setRedactionsApplied(true); } }, [provides, item, pageIndex, setRedactionsApplied]); // Calculate position for portal based on wrapper element useEffect(() => { if (!selected || !isRedaction || !item || !wrapperRef.current) { setMenuPosition(null); return; } const updatePosition = () => { const wrapper = wrapperRef.current; if (!wrapper) { setMenuPosition(null); return; } const wrapperRect = wrapper.getBoundingClientRect(); // Position menu below the wrapper, centered // Use getBoundingClientRect which gives viewport-relative coordinates // Since we're using fixed positioning in the portal, we don't need to add scroll offsets setMenuPosition({ top: wrapperRect.bottom + 8, left: wrapperRect.left + wrapperRect.width / 2, }); }; updatePosition(); // Update position on scroll/resize window.addEventListener('scroll', updatePosition, true); window.addEventListener('resize', updatePosition); return () => { window.removeEventListener('scroll', updatePosition, true); window.removeEventListener('resize', updatePosition); }; }, [selected, item]); // Early return AFTER all hooks have been called if (!selected || !isRedaction || !item) return null; const menuContent = menuPosition ? (
) : null; return ( <> {/* Invisible wrapper that provides positioning - uses EmbedPDF's menuWrapperProps */}
{typeof document !== 'undefined' && menuContent ? createPortal(menuContent, document.body) : null} ); }