feat: Add UI and logic to handle password-protected PDF documents.
This commit is contained in:
@@ -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<HTMLElement | null>(null);
|
||||
const [isEncrypted, setIsEncrypted] = useState(false);
|
||||
const [passwordError, setPasswordError] = useState(false);
|
||||
const [passwordCallback, setPasswordCallback] = useState<((password: string) => void) | null>(null);
|
||||
const passwordInputRef = useRef<HTMLInputElement>(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 (
|
||||
<div className={viewerClasses}>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`pdf-viewer__canvas-stack pdf-viewer__canvas-stack--${viewMode}`}
|
||||
role="document"
|
||||
aria-label={title || 'PDF document'}
|
||||
style={stackStyle}
|
||||
>
|
||||
{pageElements}
|
||||
</div>
|
||||
{showStatus ? (
|
||||
<div className={statusClasses}>
|
||||
<div className="document-viewer__message">
|
||||
{statusMessage}
|
||||
</div>
|
||||
{isEncrypted && !pdfRef.current ? (
|
||||
<div
|
||||
className="pdf-viewer__password-container"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<form onSubmit={handlePasswordSubmit} className="pdf-viewer__password-form">
|
||||
<div className="pdf-viewer__password-message">
|
||||
This document is password protected.
|
||||
</div>
|
||||
<div className="pdf-viewer__password-input-group">
|
||||
<input
|
||||
ref={passwordInputRef}
|
||||
type="password"
|
||||
className="pdf-viewer__password-input"
|
||||
placeholder="Enter password"
|
||||
autoFocus
|
||||
/>
|
||||
<button type="submit" className="button button--primary">
|
||||
Unlock
|
||||
</button>
|
||||
</div>
|
||||
{passwordError ? (
|
||||
<div className="pdf-viewer__password-error">
|
||||
Incorrect password. Please try again.
|
||||
</div>
|
||||
) : null}
|
||||
</form>
|
||||
</div>
|
||||
) : null}
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`pdf-viewer__canvas-stack pdf-viewer__canvas-stack--${viewMode}`}
|
||||
role="document"
|
||||
aria-label={title || 'PDF document'}
|
||||
style={stackStyle}
|
||||
>
|
||||
{pageElements}
|
||||
</div>
|
||||
{showStatus ? (
|
||||
<div className={statusClasses}>
|
||||
<div className="document-viewer__message">
|
||||
{statusMessage}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user