diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6e7c247da..e130f13e0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,7 +12,9 @@ "@atlaskit/pragmatic-drag-and-drop": "^1.7.7", "@embedpdf/core": "^1.3.0", "@embedpdf/engines": "^1.2.1", + "@embedpdf/plugin-annotation": "^1.3.0", "@embedpdf/plugin-export": "^1.3.0", + "@embedpdf/plugin-history": "^1.3.0", "@embedpdf/plugin-interaction-manager": "^1.3.0", "@embedpdf/plugin-loader": "^1.3.0", "@embedpdf/plugin-pan": "^1.3.0", @@ -532,6 +534,26 @@ "integrity": "sha512-rSBFYjxwQ58L/HcqR0l5Vv4G5t+CCOKlFYrDReTZYNN7fhzKPUWbXUn4ARahZWCNmF8svHumV2P4ArakJJviuw==", "license": "MIT" }, + "node_modules/@embedpdf/plugin-annotation": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-annotation/-/plugin-annotation-1.3.0.tgz", + "integrity": "sha512-W9N8kQebnOT5ci7pp4RRPXK2ZAMvQbdd4Qkt4vXAsL9QIKqprAMrvo0GKzUAqMaYUk9WhVHgc5zwpeSP3PVUHg==", + "license": "MIT", + "dependencies": { + "@embedpdf/models": "1.3.0", + "@embedpdf/utils": "1.3.0" + }, + "peerDependencies": { + "@embedpdf/core": "1.3.0", + "@embedpdf/plugin-history": "1.3.0", + "@embedpdf/plugin-interaction-manager": "1.3.0", + "@embedpdf/plugin-selection": "1.3.0", + "preact": "^10.26.4", + "react": ">=16.8.0", + "react-dom": ">=16.8.0", + "vue": ">=3.2.0" + } + }, "node_modules/@embedpdf/plugin-export": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@embedpdf/plugin-export/-/plugin-export-1.3.0.tgz", @@ -548,6 +570,22 @@ "vue": ">=3.2.0" } }, + "node_modules/@embedpdf/plugin-history": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-1.3.0.tgz", + "integrity": "sha512-HiNig94e6jE4h3BTL8Yi1fLLtYPY50N7vrkHSImqDmUTIcNHbQVbBYVCPpFJxN5NtuuaQqN9p1Mr7DwOKX8lkw==", + "license": "MIT", + "dependencies": { + "@embedpdf/models": "1.3.0" + }, + "peerDependencies": { + "@embedpdf/core": "1.3.0", + "preact": "^10.26.4", + "react": ">=16.8.0", + "react-dom": ">=16.8.0", + "vue": ">=3.2.0" + } + }, "node_modules/@embedpdf/plugin-interaction-manager": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-1.3.0.tgz", @@ -770,6 +808,18 @@ "vue": ">=3.2.0" } }, + "node_modules/@embedpdf/utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@embedpdf/utils/-/utils-1.3.0.tgz", + "integrity": "sha512-KEgdR85vd2CNKoSBoE5h4+e1n7MqEuIq3jZwD9MXAVKpHMaAIuD+S1khD8m4XLnbQXn32A9cO6Z6fmH0ndZ7+A==", + "license": "MIT", + "peerDependencies": { + "preact": "^10.26.4", + "react": ">=16.8.0", + "react-dom": ">=16.8.0", + "vue": ">=3.2.0" + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.13.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", @@ -5830,10 +5880,14 @@ } }, "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } }, "node_modules/esutils": { "version": "2.0.3", @@ -10948,21 +11002,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/vite/node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/vite/node_modules/picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index f3ebfacf9..ac0e7d114 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,7 +8,9 @@ "@atlaskit/pragmatic-drag-and-drop": "^1.7.7", "@embedpdf/core": "^1.3.0", "@embedpdf/engines": "^1.2.1", + "@embedpdf/plugin-annotation": "^1.3.0", "@embedpdf/plugin-export": "^1.3.0", + "@embedpdf/plugin-history": "^1.3.0", "@embedpdf/plugin-interaction-manager": "^1.3.0", "@embedpdf/plugin-loader": "^1.3.0", "@embedpdf/plugin-pan": "^1.3.0", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index f8a5ace0a..da9a14eb5 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -15,7 +15,6 @@ import "./index.css"; import { RightRailProvider } from "./contexts/RightRailContext"; import { ViewerProvider } from "./contexts/ViewerContext"; import { SignatureProvider } from "./contexts/SignatureContext"; -import { ViewerProvider } from "./contexts/ViewerContext"; // Import file ID debugging helpers (development only) import "./utils/fileIdSafety"; diff --git a/frontend/src/components/viewer/ThumbnailSidebar.tsx b/frontend/src/components/viewer/ThumbnailSidebar.tsx index 8c6451cd2..b78e13774 100644 --- a/frontend/src/components/viewer/ThumbnailSidebar.tsx +++ b/frontend/src/components/viewer/ThumbnailSidebar.tsx @@ -196,201 +196,3 @@ export function ThumbnailSidebar({ visible, onToggle: _onToggle }: ThumbnailSide ); } -import React, { useState, useEffect } from 'react'; -import { Box, ScrollArea } from '@mantine/core'; -import { useViewer } from '../../contexts/ViewerContext'; - -interface ThumbnailSidebarProps { - visible: boolean; - onToggle: () => void; -} - -export function ThumbnailSidebar({ visible, onToggle: _onToggle }: ThumbnailSidebarProps) { - const { getScrollState, scrollActions, getThumbnailAPI } = useViewer(); - const [thumbnails, setThumbnails] = useState<{ [key: number]: string }>({}); - - const scrollState = getScrollState(); - const thumbnailAPI = getThumbnailAPI(); - - // Clear thumbnails when sidebar closes and revoke blob URLs to prevent memory leaks - useEffect(() => { - if (!visible) { - Object.values(thumbnails).forEach((thumbUrl) => { - // Only revoke if it's a blob URL (not 'error') - if (typeof thumbUrl === 'string' && thumbUrl.startsWith('blob:')) { - URL.revokeObjectURL(thumbUrl); - } - }); - setThumbnails({}); - } - }, [visible, thumbnails]); - - // Generate thumbnails when sidebar becomes visible - useEffect(() => { - if (!visible || scrollState.totalPages === 0) return; - if (!thumbnailAPI) return; - - const generateThumbnails = async () => { - for (let pageIndex = 0; pageIndex < scrollState.totalPages; pageIndex++) { - if (thumbnails[pageIndex]) continue; // Skip if already generated - - try { - const thumbTask = thumbnailAPI.renderThumb(pageIndex, 1.0); - - // Convert Task to Promise and handle properly - thumbTask.toPromise().then((thumbBlob: Blob) => { - const thumbUrl = URL.createObjectURL(thumbBlob); - - setThumbnails(prev => ({ - ...prev, - [pageIndex]: thumbUrl - })); - }).catch((error: any) => { - console.error('Failed to generate thumbnail for page', pageIndex + 1, error); - setThumbnails(prev => ({ - ...prev, - [pageIndex]: 'error' - })); - }); - - } catch (error) { - console.error('Failed to generate thumbnail for page', pageIndex + 1, error); - setThumbnails(prev => ({ - ...prev, - [pageIndex]: 'error' - })); - } - } - }; - - generateThumbnails(); - }, [visible, scrollState.totalPages, thumbnailAPI]); - - const handlePageClick = (pageIndex: number) => { - const pageNumber = pageIndex + 1; // Convert to 1-based - scrollActions.scrollToPage(pageNumber); - }; - - return ( - <> - {/* Thumbnail Sidebar */} - {visible && ( - - {/* Thumbnails Container */} - - -
- {Array.from({ length: scrollState.totalPages }, (_, pageIndex) => ( - handlePageClick(pageIndex)} - style={{ - cursor: 'pointer', - borderRadius: '8px', - padding: '8px', - backgroundColor: scrollState.currentPage === pageIndex + 1 - ? 'var(--color-primary-100)' - : 'transparent', - border: scrollState.currentPage === pageIndex + 1 - ? '2px solid var(--color-primary-500)' - : '2px solid transparent', - transition: 'all 0.2s ease', - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - gap: '8px' - }} - onMouseEnter={(e) => { - if (scrollState.currentPage !== pageIndex + 1) { - e.currentTarget.style.backgroundColor = 'var(--hover-bg)'; - } - }} - onMouseLeave={(e) => { - if (scrollState.currentPage !== pageIndex + 1) { - e.currentTarget.style.backgroundColor = 'transparent'; - } - }} - > - {/* Thumbnail Image */} - {thumbnails[pageIndex] && thumbnails[pageIndex] !== 'error' ? ( - {`Page - ) : thumbnails[pageIndex] === 'error' ? ( -
- Failed -
- ) : ( -
- Loading... -
- )} - - {/* Page Number */} -
- Page {pageIndex + 1} -
-
- ))} -
-
-
-
- )} - - ); -} diff --git a/frontend/src/contexts/ViewerContext.tsx b/frontend/src/contexts/ViewerContext.tsx index 83180899b..71cdd657f 100644 --- a/frontend/src/contexts/ViewerContext.tsx +++ b/frontend/src/contexts/ViewerContext.tsx @@ -51,549 +51,6 @@ interface ThumbnailAPIWrapper { renderThumb: (pageIndex: number, scale: number) => { toPromise: () => Promise }; } - -// State interfaces - represent the shape of data from each bridge -interface ScrollState { - currentPage: number; - totalPages: number; -} - -interface ZoomState { - currentZoom: number; - zoomPercent: number; -} - -interface PanState { - isPanning: boolean; -} - -interface SelectionState { - hasSelection: boolean; -} - -interface SpreadState { - spreadMode: SpreadMode; - isDualPage: boolean; -} - -interface RotationState { - rotation: number; -} - -interface SearchResult { - pageIndex: number; - rects: Array<{ - origin: { x: number; y: number }; - size: { width: number; height: number }; - }>; -} - -interface SearchState { - results: SearchResult[] | null; - activeIndex: number; -} - -// Bridge registration interface - bridges register with state and API -interface BridgeRef { - state: TState; - api: TApi; -} - -/** - * ViewerContext provides a unified interface to EmbedPDF functionality. - * - * Architecture: - * - Bridges store their own state locally and register with this context - * - Context provides read-only access to bridge state via getter functions - * - Actions call EmbedPDF APIs directly through bridge references - * - No circular dependencies - bridges don't call back into this context - */ -interface ViewerContextType { - // UI state managed by this context - isThumbnailSidebarVisible: boolean; - toggleThumbnailSidebar: () => void; - - // State getters - read current state from bridges - getScrollState: () => ScrollState; - getZoomState: () => ZoomState; - getPanState: () => PanState; - getSelectionState: () => SelectionState; - getSpreadState: () => SpreadState; - getRotationState: () => RotationState; - getSearchState: () => SearchState; - getThumbnailAPI: () => ThumbnailAPIWrapper | null; - - // Immediate update callbacks - registerImmediateZoomUpdate: (callback: (percent: number) => void) => void; - registerImmediateScrollUpdate: (callback: (currentPage: number, totalPages: number) => void) => void; - - // Internal - for bridges to trigger immediate updates - triggerImmediateScrollUpdate: (currentPage: number, totalPages: number) => void; - triggerImmediateZoomUpdate: (zoomPercent: number) => void; - - // Action handlers - call EmbedPDF APIs directly - scrollActions: { - scrollToPage: (page: number) => void; - scrollToFirstPage: () => void; - scrollToPreviousPage: () => void; - scrollToNextPage: () => void; - scrollToLastPage: () => void; - }; - - zoomActions: { - zoomIn: () => void; - zoomOut: () => void; - toggleMarqueeZoom: () => void; - requestZoom: (level: number) => void; - }; - - panActions: { - enablePan: () => void; - disablePan: () => void; - togglePan: () => void; - }; - - selectionActions: { - copyToClipboard: () => void; - getSelectedText: () => string; - getFormattedSelection: () => unknown; - }; - - spreadActions: { - setSpreadMode: (mode: SpreadMode) => void; - getSpreadMode: () => SpreadMode | null; - toggleSpreadMode: () => void; - }; - - rotationActions: { - rotateForward: () => void; - rotateBackward: () => void; - setRotation: (rotation: number) => void; - getRotation: () => number; - }; - - searchActions: { - search: (query: string) => Promise; - next: () => void; - previous: () => void; - clear: () => void; - }; - - // Bridge registration - internal use by bridges - registerBridge: (type: string, ref: BridgeRef) => void; -} - -export const ViewerContext = createContext(null); - -interface ViewerProviderProps { - children: ReactNode; -} - -export const ViewerProvider: React.FC = ({ children }) => { - // UI state - only state directly managed by this context - const [isThumbnailSidebarVisible, setIsThumbnailSidebarVisible] = useState(false); - - // Bridge registry - bridges register their state and APIs here - const bridgeRefs = useRef({ - scroll: null as BridgeRef | null, - zoom: null as BridgeRef | null, - pan: null as BridgeRef | null, - selection: null as BridgeRef | null, - search: null as BridgeRef | null, - spread: null as BridgeRef | null, - rotation: null as BridgeRef | null, - thumbnail: null as BridgeRef | null, - }); - - // Immediate zoom callback for responsive display updates - const immediateZoomUpdateCallback = useRef<((percent: number) => void) | null>(null); - - // Immediate scroll callback for responsive display updates - const immediateScrollUpdateCallback = useRef<((currentPage: number, totalPages: number) => void) | null>(null); - - const registerBridge = (type: string, ref: BridgeRef) => { - // Type-safe assignment - we know the bridges will provide correct types - switch (type) { - case 'scroll': - bridgeRefs.current.scroll = ref as BridgeRef; - break; - case 'zoom': - bridgeRefs.current.zoom = ref as BridgeRef; - break; - case 'pan': - bridgeRefs.current.pan = ref as BridgeRef; - break; - case 'selection': - bridgeRefs.current.selection = ref as BridgeRef; - break; - case 'search': - bridgeRefs.current.search = ref as BridgeRef; - break; - case 'spread': - bridgeRefs.current.spread = ref as BridgeRef; - break; - case 'rotation': - bridgeRefs.current.rotation = ref as BridgeRef; - break; - case 'thumbnail': - bridgeRefs.current.thumbnail = ref as BridgeRef; - break; - } - }; - - const toggleThumbnailSidebar = () => { - setIsThumbnailSidebarVisible(prev => !prev); - }; - - // State getters - read from bridge refs - const getScrollState = (): ScrollState => { - return bridgeRefs.current.scroll?.state || { currentPage: 1, totalPages: 0 }; - }; - - const getZoomState = (): ZoomState => { - return bridgeRefs.current.zoom?.state || { currentZoom: 1.4, zoomPercent: 140 }; - }; - - const getPanState = (): PanState => { - return bridgeRefs.current.pan?.state || { isPanning: false }; - }; - - const getSelectionState = (): SelectionState => { - return bridgeRefs.current.selection?.state || { hasSelection: false }; - }; - - const getSpreadState = (): SpreadState => { - return bridgeRefs.current.spread?.state || { spreadMode: SpreadMode.None, isDualPage: false }; - }; - - const getRotationState = (): RotationState => { - return bridgeRefs.current.rotation?.state || { rotation: 0 }; - }; - - const getSearchState = (): SearchState => { - return bridgeRefs.current.search?.state || { results: null, activeIndex: 0 }; - }; - - const getThumbnailAPI = () => { - return bridgeRefs.current.thumbnail?.api || null; - }; - - // Action handlers - call APIs directly - const scrollActions = { - scrollToPage: (page: number) => { - const api = bridgeRefs.current.scroll?.api; - if (api?.scrollToPage) { - api.scrollToPage({ pageNumber: page }); - } - }, - scrollToFirstPage: () => { - const api = bridgeRefs.current.scroll?.api; - if (api?.scrollToPage) { - api.scrollToPage({ pageNumber: 1 }); - } - }, - scrollToPreviousPage: () => { - const api = bridgeRefs.current.scroll?.api; - if (api?.scrollToPreviousPage) { - api.scrollToPreviousPage(); - } - }, - scrollToNextPage: () => { - const api = bridgeRefs.current.scroll?.api; - if (api?.scrollToNextPage) { - api.scrollToNextPage(); - } - }, - scrollToLastPage: () => { - const scrollState = getScrollState(); - const api = bridgeRefs.current.scroll?.api; - if (api?.scrollToPage && scrollState.totalPages > 0) { - api.scrollToPage({ pageNumber: scrollState.totalPages }); - } - } - }; - - const zoomActions = { - zoomIn: () => { - const api = bridgeRefs.current.zoom?.api; - if (api?.zoomIn) { - // Update display immediately if callback is registered - if (immediateZoomUpdateCallback.current) { - const currentState = getZoomState(); - const newPercent = Math.min(Math.round(currentState.zoomPercent * 1.2), 300); - immediateZoomUpdateCallback.current(newPercent); - } - api.zoomIn(); - } - }, - zoomOut: () => { - const api = bridgeRefs.current.zoom?.api; - if (api?.zoomOut) { - // Update display immediately if callback is registered - if (immediateZoomUpdateCallback.current) { - const currentState = getZoomState(); - const newPercent = Math.max(Math.round(currentState.zoomPercent / 1.2), 20); - immediateZoomUpdateCallback.current(newPercent); - } - api.zoomOut(); - } - }, - toggleMarqueeZoom: () => { - const api = bridgeRefs.current.zoom?.api; - if (api?.toggleMarqueeZoom) { - api.toggleMarqueeZoom(); - } - }, - requestZoom: (level: number) => { - const api = bridgeRefs.current.zoom?.api; - if (api?.requestZoom) { - api.requestZoom(level); - } - } - }; - - const panActions = { - enablePan: () => { - const api = bridgeRefs.current.pan?.api; - if (api?.enable) { - api.enable(); - } - }, - disablePan: () => { - const api = bridgeRefs.current.pan?.api; - if (api?.disable) { - api.disable(); - } - }, - togglePan: () => { - const api = bridgeRefs.current.pan?.api; - if (api?.toggle) { - api.toggle(); - } - } - }; - - const selectionActions = { - copyToClipboard: () => { - const api = bridgeRefs.current.selection?.api; - if (api?.copyToClipboard) { - api.copyToClipboard(); - } - }, - getSelectedText: () => { - const api = bridgeRefs.current.selection?.api; - if (api?.getSelectedText) { - return api.getSelectedText(); - } - return ''; - }, - getFormattedSelection: () => { - const api = bridgeRefs.current.selection?.api; - if (api?.getFormattedSelection) { - return api.getFormattedSelection(); - } - return null; - } - }; - - const spreadActions = { - setSpreadMode: (mode: SpreadMode) => { - const api = bridgeRefs.current.spread?.api; - if (api?.setSpreadMode) { - api.setSpreadMode(mode); - } - }, - getSpreadMode: () => { - const api = bridgeRefs.current.spread?.api; - if (api?.getSpreadMode) { - return api.getSpreadMode(); - } - return null; - }, - toggleSpreadMode: () => { - const api = bridgeRefs.current.spread?.api; - if (api?.toggleSpreadMode) { - api.toggleSpreadMode(); - } - } - }; - - const rotationActions = { - rotateForward: () => { - const api = bridgeRefs.current.rotation?.api; - if (api?.rotateForward) { - api.rotateForward(); - } - }, - rotateBackward: () => { - const api = bridgeRefs.current.rotation?.api; - if (api?.rotateBackward) { - api.rotateBackward(); - } - }, - setRotation: (rotation: number) => { - const api = bridgeRefs.current.rotation?.api; - if (api?.setRotation) { - api.setRotation(rotation); - } - }, - getRotation: () => { - const api = bridgeRefs.current.rotation?.api; - if (api?.getRotation) { - return api.getRotation(); - } - return 0; - } - }; - - const searchActions = { - search: async (query: string) => { - const api = bridgeRefs.current.search?.api; - if (api?.search) { - return api.search(query); - } - }, - next: () => { - const api = bridgeRefs.current.search?.api; - if (api?.next) { - api.next(); - } - }, - previous: () => { - const api = bridgeRefs.current.search?.api; - if (api?.previous) { - api.previous(); - } - }, - clear: () => { - const api = bridgeRefs.current.search?.api; - if (api?.clear) { - api.clear(); - } - } - }; - - const registerImmediateZoomUpdate = (callback: (percent: number) => void) => { - immediateZoomUpdateCallback.current = callback; - }; - - const registerImmediateScrollUpdate = (callback: (currentPage: number, totalPages: number) => void) => { - immediateScrollUpdateCallback.current = callback; - }; - - const triggerImmediateScrollUpdate = (currentPage: number, totalPages: number) => { - if (immediateScrollUpdateCallback.current) { - immediateScrollUpdateCallback.current(currentPage, totalPages); - } - }; - - const triggerImmediateZoomUpdate = (zoomPercent: number) => { - if (immediateZoomUpdateCallback.current) { - immediateZoomUpdateCallback.current(zoomPercent); - } - }; - - const value: ViewerContextType = { - // UI state - isThumbnailSidebarVisible, - toggleThumbnailSidebar, - - // State getters - getScrollState, - getZoomState, - getPanState, - getSelectionState, - getSpreadState, - getRotationState, - getSearchState, - getThumbnailAPI, - - // Immediate updates - registerImmediateZoomUpdate, - registerImmediateScrollUpdate, - triggerImmediateScrollUpdate, - triggerImmediateZoomUpdate, - - // Actions - scrollActions, - zoomActions, - panActions, - selectionActions, - spreadActions, - rotationActions, - searchActions, - - // Bridge registration - registerBridge, - }; - - return ( - - {children} - - ); -}; - -export const useViewer = (): ViewerContextType => { - const context = useContext(ViewerContext); - if (!context) { - throw new Error('useViewer must be used within a ViewerProvider'); - } - return context; -}; -import React, { createContext, useContext, useState, ReactNode, useRef } from 'react'; -import { SpreadMode } from '@embedpdf/plugin-spread/react'; - -// Bridge API interfaces - these match what the bridges provide -interface ScrollAPIWrapper { - scrollToPage: (params: { pageNumber: number }) => void; - scrollToPreviousPage: () => void; - scrollToNextPage: () => void; -} - -interface ZoomAPIWrapper { - zoomIn: () => void; - zoomOut: () => void; - toggleMarqueeZoom: () => void; - requestZoom: (level: number) => void; -} - -interface PanAPIWrapper { - enable: () => void; - disable: () => void; - toggle: () => void; -} - -interface SelectionAPIWrapper { - copyToClipboard: () => void; - getSelectedText: () => string | any; - getFormattedSelection: () => any; -} - -interface SpreadAPIWrapper { - setSpreadMode: (mode: SpreadMode) => void; - getSpreadMode: () => SpreadMode | null; - toggleSpreadMode: () => void; -} - -interface RotationAPIWrapper { - rotateForward: () => void; - rotateBackward: () => void; - setRotation: (rotation: number) => void; - getRotation: () => number; -} - -interface SearchAPIWrapper { - search: (query: string) => Promise; - clear: () => void; - next: () => void; - previous: () => void; -} - -interface ThumbnailAPIWrapper { - renderThumb: (pageIndex: number, scale: number) => { toPromise: () => Promise }; -} - interface ExportAPIWrapper { download: () => void; saveAsCopy: () => { toPromise: () => Promise }; diff --git a/frontend/src/data/useTranslatedToolRegistry.tsx b/frontend/src/data/useTranslatedToolRegistry.tsx index ecca8a9ef..8395d97f1 100644 --- a/frontend/src/data/useTranslatedToolRegistry.tsx +++ b/frontend/src/data/useTranslatedToolRegistry.tsx @@ -49,6 +49,8 @@ import { flattenOperationConfig } from "../hooks/tools/flatten/useFlattenOperati import { redactOperationConfig } from "../hooks/tools/redact/useRedactOperation"; import { rotateOperationConfig } from "../hooks/tools/rotate/useRotateOperation"; import { changeMetadataOperationConfig } from "../hooks/tools/changeMetadata/useChangeMetadataOperation"; +import { signOperationConfig } from "../hooks/tools/sign/useSignOperation"; +import { cropOperationConfig } from "../hooks/tools/crop/useCropOperation"; import CompressSettings from "../components/tools/compress/CompressSettings"; import SplitSettings from "../components/tools/split/SplitSettings"; import AddPasswordSettings from "../components/tools/addPassword/AddPasswordSettings"; @@ -72,6 +74,8 @@ import MergeSettings from '../components/tools/merge/MergeSettings'; import { adjustPageScaleOperationConfig } from "../hooks/tools/adjustPageScale/useAdjustPageScaleOperation"; import AdjustPageScaleSettings from "../components/tools/adjustPageScale/AdjustPageScaleSettings"; import ChangeMetadataSingleStep from "../components/tools/changeMetadata/ChangeMetadataSingleStep"; +import SignSettings from "../components/tools/sign/SignSettings"; +import CropSettings from "../components/tools/crop/CropSettings"; const showPlaceholderTools = true; // Show all tools; grey out unavailable ones in UI