diff --git a/frontend/src/core/components/viewer/EmbedPdfViewer.tsx b/frontend/src/core/components/viewer/EmbedPdfViewer.tsx index 6d525be5c4..33a6a80f17 100644 --- a/frontend/src/core/components/viewer/EmbedPdfViewer.tsx +++ b/frontend/src/core/components/viewer/EmbedPdfViewer.tsx @@ -25,6 +25,7 @@ import type { PDFDict, PDFNumber } from '@cantoo/pdf-lib'; import { useWheelZoom } from '@app/hooks/useWheelZoom'; import { useFormFill } from '@app/tools/formFill/FormFillContext'; import { FormSaveBar } from '@app/tools/formFill/FormSaveBar'; +import { useViewerKeyCommand } from '@app/hooks/useViewerKeyCommand'; // ─── Measure dictionary extraction ──────────────────────────────────────────── @@ -380,15 +381,19 @@ const EmbedPdfViewerContent = ({ onZoomOut: zoomActions.zoomOut, }); + const viewerKeyCommand = useViewerKeyCommand(); + // Handle keyboard shortcuts useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { const mod = event.ctrlKey || event.metaKey; - // Ctrl+P (print) and Ctrl+R (rotate) must be intercepted unconditionally + // Ctrl+P (print) must be intercepted unconditionally // whenever the viewer is mounted, even before the user has hovered over it. + // Ctrl+R (rotate) is intercepted only on desktop (Tauri), while on web it still falls through to browser refresh. // Without this, the browser falls through to its native "print HTML page" // or "reload page" behaviour. + if (mod) { const target = event.target as Element; const isInTextInput = @@ -397,12 +402,15 @@ const EmbedPdfViewerContent = ({ (target as HTMLElement).isContentEditable; if (!isInTextInput) { - switch (event.key) { - case 'p': - case 'P': - event.preventDefault(); - printActions.print(); - return; + const wasOverridden = viewerKeyCommand(event) + if (!wasOverridden){ + switch (event.key) { + case 'p': + case 'P': + event.preventDefault(); + printActions.print(); + return; + } } } } @@ -509,7 +517,7 @@ const EmbedPdfViewerContent = ({ }, [ isViewerHovered, isSearchInterfaceVisible, zoomActions, searchInterfaceActions, scrollActions, printActions, exportActions, rotationActions, historyApiRef, - viewerApplyChanges, cyclePdfRenderMode, + viewerApplyChanges, cyclePdfRenderMode, viewerKeyCommand, ]); // Watch the annotation history API to detect when the document becomes "dirty". diff --git a/frontend/src/core/hooks/useViewerKeyCommand.ts b/frontend/src/core/hooks/useViewerKeyCommand.ts new file mode 100644 index 0000000000..d01546b679 --- /dev/null +++ b/frontend/src/core/hooks/useViewerKeyCommand.ts @@ -0,0 +1,4 @@ +// Default implementation for non-desktop environments (overridden in desktop) +export function useViewerKeyCommand(): (event: KeyboardEvent) => boolean { + return () => false; +} \ No newline at end of file diff --git a/frontend/src/desktop/hooks/useViewerKeyCommand.ts b/frontend/src/desktop/hooks/useViewerKeyCommand.ts new file mode 100644 index 0000000000..3a15af21e1 --- /dev/null +++ b/frontend/src/desktop/hooks/useViewerKeyCommand.ts @@ -0,0 +1,20 @@ +import { useViewer } from "@app/contexts/ViewerContext" +import { useCallback } from "react"; + +export function useViewerKeyCommand(): (event: KeyboardEvent) => boolean { + const { rotationActions } = useViewer(); + return useCallback((event:KeyboardEvent): boolean => { + switch (event.key) { + case 'r': + case 'R': + event.preventDefault(); + if (event.shiftKey) { + rotationActions.rotateBackward(); + } else { + rotationActions.rotateForward(); + } + return true; + } + return false; + }, [rotationActions]); +} \ No newline at end of file