Skip to content

Commit d78be68

Browse files
committed
Add better zoom + controls
1 parent 5edae0e commit d78be68

File tree

3 files changed

+70
-22
lines changed

3 files changed

+70
-22
lines changed

src/app/pdf/[id]/page.tsx

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export default function PDFViewerPage() {
2525
const [document, setDocument] = useState<{ name: string; data: Blob } | null>(null);
2626
const [error, setError] = useState<string | null>(null);
2727
const [isLoading, setIsLoading] = useState(true);
28+
const [zoomLevel, setZoomLevel] = useState<number>(100);
2829

2930
useEffect(() => {
3031
async function loadDocument() {
@@ -46,6 +47,9 @@ export default function PDFViewerPage() {
4647
loadDocument();
4748
}, [id, getDocument]);
4849

50+
const handleZoomIn = () => setZoomLevel(prev => Math.min(prev + 10, 200));
51+
const handleZoomOut = () => setZoomLevel(prev => Math.max(prev - 10, 50));
52+
4953
if (error) {
5054
return (
5155
<div className="flex flex-col items-center justify-center min-h-screen">
@@ -71,20 +75,39 @@ export default function PDFViewerPage() {
7175
<>
7276
<TTSPlayer />
7377
<div className="p-2 pb-2 border-b border-offbase">
74-
<div className="flex items-center justify-between">
75-
<Link
76-
href="/"
77-
onClick={() => {
78-
setText('');
79-
stop();
80-
}}
81-
className="inline-flex items-center px-3 py-1 bg-base text-foreground rounded-lg hover:bg-offbase transition-colors"
82-
>
83-
<svg className="w-4 h-4 mr-2 text-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24">
84-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
85-
</svg>
86-
Documents
87-
</Link>
78+
<div className="flex flex-wrap items-center justify-between">
79+
<div className="flex items-center gap-4">
80+
<Link
81+
href="/"
82+
onClick={() => {
83+
setText('');
84+
stop();
85+
}}
86+
className="inline-flex items-center px-3 py-1 bg-base text-foreground rounded-lg hover:bg-offbase transition-colors"
87+
>
88+
<svg className="w-4 h-4 mr-2 text-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24">
89+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
90+
</svg>
91+
Documents
92+
</Link>
93+
<div className="bg-offbase px-2 py-0.5 rounded-full flex items-center gap-2">
94+
<button
95+
onClick={handleZoomOut}
96+
className="text-xs hover:text-accent transition-colors"
97+
aria-label="Zoom out"
98+
>
99+
100+
</button>
101+
<span className="text-xs">{zoomLevel}%</span>
102+
<button
103+
onClick={handleZoomIn}
104+
className="text-xs hover:text-accent transition-colors"
105+
aria-label="Zoom in"
106+
>
107+
108+
</button>
109+
</div>
110+
</div>
88111
<h1 className="mr-2 text-md font-semibold text-foreground">
89112
{isLoading ? 'Loading...' : document?.name}
90113
</h1>
@@ -95,7 +118,7 @@ export default function PDFViewerPage() {
95118
<PDFSkeleton />
96119
</div>
97120
) : (
98-
<PDFViewer pdfData={document?.data} />
121+
<PDFViewer pdfData={document?.data} zoomLevel={zoomLevel} />
99122
)}
100123
</>
101124
);

src/components/PDFViewer.tsx

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import { usePDF } from '@/contexts/PDFContext';
1111

1212
interface PDFViewerProps {
1313
pdfData: Blob | undefined;
14+
zoomLevel: number;
1415
}
1516

16-
export function PDFViewer({ pdfData }: PDFViewerProps) {
17+
export function PDFViewer({ pdfData, zoomLevel }: PDFViewerProps) {
1718
const [numPages, setNumPages] = useState<number>();
19+
const [containerWidth, setContainerWidth] = useState<number>(0);
1820
const { setText, currentSentence, stopAndPlayFromIndex, isProcessing } = useTTS();
1921
const [pdfText, setPdfText] = useState('');
2022
const [pdfDataUrl, setPdfDataUrl] = useState<string>();
@@ -143,14 +145,37 @@ export function PDFViewer({ pdfData }: PDFViewerProps) {
143145
};
144146
}, [pdfText, currentSentence, highlightPattern, clearHighlights]);
145147

148+
// Add scale calculation function
149+
const calculateScale = (pageWidth: number = 595) => { // 595 is default PDF width in points
150+
const margin = 24; // 24px padding on each side
151+
const targetWidth = containerWidth - margin;
152+
const baseScale = targetWidth / pageWidth;
153+
return baseScale * (zoomLevel / 100);
154+
};
155+
156+
// Add resize observer effect
157+
useEffect(() => {
158+
if (!containerRef.current) return;
159+
160+
const observer = new ResizeObserver(entries => {
161+
const width = entries[0]?.contentRect.width;
162+
if (width) {
163+
setContainerWidth(width);
164+
}
165+
});
166+
167+
observer.observe(containerRef.current);
168+
return () => observer.disconnect();
169+
}, []);
170+
146171
function onDocumentLoadSuccess({ numPages }: { numPages: number }): void {
147172
setNumPages(numPages);
148173
}
149174

150175
return (
151176
<div
152177
ref={containerRef}
153-
className="flex flex-col items-center overflow-auto max-h-[calc(100vh-100px)]"
178+
className="flex flex-col items-center overflow-auto max-h-[calc(100vh-100px)] w-full px-6"
154179
style={{ WebkitTapHighlightColor: 'transparent' }}
155180
>
156181
{loadingError ? (
@@ -161,7 +186,7 @@ export function PDFViewer({ pdfData }: PDFViewerProps) {
161186
noData={<PDFSkeleton />}
162187
file={pdfDataUrl}
163188
onLoadSuccess={onDocumentLoadSuccess}
164-
className="flex flex-col items-center"
189+
className="flex flex-col items-center m-0"
165190
>
166191
{Array.from(
167192
new Array(numPages),
@@ -178,7 +203,7 @@ export function PDFViewer({ pdfData }: PDFViewerProps) {
178203
renderAnnotationLayer={true}
179204
renderTextLayer={true}
180205
className="shadow-lg"
181-
scale={1.2}
206+
scale={calculateScale()}
182207
/>
183208
</div>
184209
</div>

src/contexts/TTSContext.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
131131
}, [audioContext]);
132132

133133
// Text preprocessing function to clean and normalize text
134-
const preprocessText = (text: string): string => {
134+
const preprocessSentenceForAudio = (text: string): string => {
135135
return text
136136
// Replace URLs with descriptive text including domain
137137
.replace(/\S*(?:https?:\/\/|www\.)([^\/\s]+)(?:\/\S*)?/gi, '- (link to $1) -')
@@ -147,7 +147,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
147147

148148
const splitIntoSentences = (text: string): string[] => {
149149
// Preprocess the text before splitting into sentences
150-
const cleanedText = preprocessText(text);
150+
const cleanedText = preprocessSentenceForAudio(text);
151151
const doc = nlp(cleanedText);
152152
return doc.sentences().out('array') as string[];
153153
};
@@ -173,7 +173,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
173173

174174
try {
175175
// Only set processing if we need to fetch from API
176-
const cleanedSentence = preprocessText(sentence);
176+
const cleanedSentence = preprocessSentenceForAudio(sentence);
177177
if (!audioCacheRef.current.has(cleanedSentence)) {
178178
setIsProcessing(true);
179179
}

0 commit comments

Comments
 (0)