diff --git a/frontend/src/preview/PdfViewer.tsx b/frontend/src/preview/PdfViewer.tsx index 14ab38f..f8bf92b 100644 --- a/frontend/src/preview/PdfViewer.tsx +++ b/frontend/src/preview/PdfViewer.tsx @@ -10,6 +10,7 @@ import React, { import type { CSSProperties, JSX, MutableRefObject, RefObject } from 'react'; import { GlobalWorkerOptions, + PasswordResponses, getDocument, } from 'pdfjs-dist'; import type { @@ -106,6 +107,10 @@ const PdfViewer = ({ src, title, className, viewportRef }: PdfViewerProps): JSX. velocity: 0, }); const scrollElementRef = useRef(null); + const [isEncrypted, setIsEncrypted] = useState(false); + const [passwordError, setPasswordError] = useState(false); + const [passwordCallback, setPasswordCallback] = useState<((password: string) => void) | null>(null); + const passwordInputRef = useRef(null); const requestQueueFlush = useCallback(() => { const queue = renderQueueRef.current; @@ -283,6 +288,14 @@ const PdfViewer = ({ src, title, className, viewportRef }: PdfViewerProps): JSX. url: src, wasmUrl, }); + loadingTask.onPassword = (callback, reason) => { + setIsEncrypted(true); + setPasswordCallback(() => callback); + if (reason === PasswordResponses.INCORRECT_PASSWORD) { + setPasswordError(true); + } + }; + const pdf = await loadingTask.promise; if (cancelled) { await pdf.destroy(); @@ -342,6 +355,13 @@ const PdfViewer = ({ src, title, className, viewportRef }: PdfViewerProps): JSX. }; }, [ensureWasmUrl, renderWidth, src]); + const handlePasswordSubmit = useCallback((event: React.FormEvent) => { + event.preventDefault(); + if (passwordCallback && passwordInputRef.current) { + passwordCallback(passwordInputRef.current.value); + } + }, [passwordCallback]); + const viewerClasses = ['document-viewer__object', 'document-viewer__object--pdf', className] .filter(Boolean) .join(' '); @@ -553,22 +573,55 @@ const PdfViewer = ({ src, title, className, viewportRef }: PdfViewerProps): JSX. return (
-
- {pageElements} -
- {showStatus ? ( -
-
- {statusMessage} -
+ {isEncrypted && !pdfRef.current ? ( +
e.stopPropagation()} + onKeyDown={(e) => e.stopPropagation()} + > +
+
+ This document is password protected. +
+
+ + +
+ {passwordError ? ( +
+ Incorrect password. Please try again. +
+ ) : null} +
- ) : null} + ) : ( + <> +
+ {pageElements} +
+ {showStatus ? ( +
+
+ {statusMessage} +
+
+ ) : null} + + )}
); }; diff --git a/frontend/src/styles/documents/viewer.css b/frontend/src/styles/documents/viewer.css index 8985453..3d0e6f6 100644 --- a/frontend/src/styles/documents/viewer.css +++ b/frontend/src/styles/documents/viewer.css @@ -22,8 +22,8 @@ min-height: auto; } -.document-viewer-panel__body > .document-viewer, -.document-viewer-panel__body > .document-viewer--loading { +.document-viewer-panel__body>.document-viewer, +.document-viewer-panel__body>.document-viewer--loading { flex: 1; } @@ -182,6 +182,7 @@ flex: 1; grid-area: details; } + .document-viewer__tabs-wrapper { display: flex; flex-direction: column; @@ -564,11 +565,69 @@ width: 1rem; height: 1rem; } + .document-viewer__summary-tab-content { padding-top: 0.5rem; } + .pdf-viewer__page-wrapper--contain .pdf-viewer__page { width: auto; max-width: 100vw; max-height: 100vh; } + +.pdf-viewer__password-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + width: 100%; + background: var(--surface-subtle); +} + +.pdf-viewer__password-form { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + padding: 2rem; + background: var(--surface); + box-shadow: var(--shadow-pop); + max-width: 400px; + margin: 1rem; + box-sizing: border-box; +} + +.pdf-viewer__password-message { + font-size: 1rem; + font-weight: 500; + color: var(--fg); + text-align: center; +} + +.pdf-viewer__password-input-group { + display: flex; + gap: 0.5rem; + width: 100%; + box-sizing: border-box; +} + +.pdf-viewer__password-input-group .button { + flex-shrink: 0; +} + +.pdf-viewer__password-input { + flex: 1; + min-width: 0; + padding: 0.5rem; + border: 1px solid var(--outline); + border-radius: 0.25rem; + font-size: 1rem; + box-sizing: border-box; +} + +.pdf-viewer__password-error { + color: var(--danger); + font-size: 0.9rem; +} \ No newline at end of file