-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/f 35 pdf preview implementation #14
Changes from 8 commits
758aa21
8e2cfc7
3d935c5
ba4e947
fd429ee
835e6a9
b502875
42a2c4b
2d65c24
5fd1c14
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
'use client'; | ||
|
||
import 'react-pdf/dist/esm/Page/TextLayer.css'; | ||
import 'react-pdf/dist/esm/Page/AnnotationLayer.css'; | ||
|
||
import { Button } from '@nextui-org/button'; | ||
import { Chip } from '@nextui-org/chip'; | ||
import { Dropdown, DropdownItem, DropdownMenu, DropdownTrigger } from '@nextui-org/react'; | ||
import { Tooltip } from '@nextui-org/tooltip'; | ||
import { useEffect, useState } from 'react'; | ||
import { Document, Page, pdfjs } from 'react-pdf'; | ||
|
||
import { PdfViewerProps } from '@/domain/props/PdfViewerProps'; | ||
|
||
pdfjs.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString(); | ||
|
||
export function PdfViewer({ onTooltipClick, file, onDownloadPdf, onDownloadJson, onDownloadCsv }: PdfViewerProps) { | ||
const [totalPages, setTotalPages] = useState<number>(); | ||
const [pageNumber, setPageNumber] = useState(1); | ||
const [selectionText, setSelectionText] = useState<string | null>(null); | ||
const [tooltipPosition, setTooltipPosition] = useState<{ top: number; left: number } | null>(null); | ||
|
||
const handleScroll = () => { | ||
const pages = document.querySelectorAll('.react-pdf__Page'); | ||
let currentPage = pageNumber; | ||
|
||
pages.forEach((page, index) => { | ||
const rect = page.getBoundingClientRect(); | ||
const pageTop = rect.top; | ||
const pageBottom = rect.bottom; | ||
const viewportMidpoint = window.innerHeight / 2; | ||
|
||
if (pageTop < viewportMidpoint && pageBottom > viewportMidpoint) { | ||
currentPage = index + 1; | ||
} | ||
}); | ||
|
||
setPageNumber(currentPage); | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function should be exported to an operations file in |
||
|
||
function onDocumentLoadSuccess({ numPages }: { numPages: number }): void { | ||
setTotalPages(numPages); | ||
window.addEventListener('scroll', handleScroll); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you should not use arrow function declaration and function declaration in the same file. Chose one of both approaches and be consistent |
||
|
||
function onSelectStart() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. return type of the function is missing |
||
setSelectionText(null); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here |
||
|
||
function onSelectEnd() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here + return type |
||
const activeSelection = document.getSelection(); | ||
const text = activeSelection?.toString(); | ||
|
||
if (!activeSelection || !text) { | ||
setSelectionText(null); | ||
return; | ||
} | ||
|
||
setSelectionText(text); | ||
|
||
const rect = activeSelection.getRangeAt(0).getBoundingClientRect(); | ||
|
||
setTooltipPosition({ | ||
top: rect.top + window.scrollY - 10, | ||
left: rect.left + rect.width / 2, | ||
}); | ||
} | ||
|
||
useEffect(() => { | ||
document.addEventListener('selectstart', onSelectStart); | ||
document.addEventListener('mouseup', onSelectEnd); | ||
|
||
return () => { | ||
document.removeEventListener('selectstart', onSelectStart); | ||
document.removeEventListener('mouseup', onSelectEnd); | ||
window.removeEventListener('scroll', handleScroll); | ||
}; | ||
}, []); | ||
|
||
return ( | ||
<div className="min-h-screen flex flex-col items-center justify-start relative"> | ||
{/* Top Bar */} | ||
<div className="bg-background w-full py-4 flex items-center justify-between px-6 sticky top-0 z-10"> | ||
<h1 className="text-lg font-semibold">Preview</h1> | ||
<Chip color="secondary" size="md"> | ||
<p className="text-sm"> | ||
{pageNumber} / {totalPages} | ||
</p> | ||
</Chip> | ||
<Dropdown> | ||
<DropdownTrigger> | ||
<Button color="secondary">Download As</Button> | ||
</DropdownTrigger> | ||
<DropdownMenu color="secondary"> | ||
<DropdownItem onClick={onDownloadPdf}>Pdf</DropdownItem> | ||
<DropdownItem onClick={onDownloadJson}>Json</DropdownItem> | ||
<DropdownItem onClick={onDownloadCsv}>Csv</DropdownItem> | ||
</DropdownMenu> | ||
</Dropdown> | ||
</div> | ||
|
||
{/* PDF Viewer */} | ||
<div className="flex-grow flex justify-center items-center pb-10 z-0"> | ||
<div className="w-full max-w-3xl"> | ||
<Document file={file} onLoadSuccess={onDocumentLoadSuccess} className="flex-col items-center"> | ||
{Array.from(new Array(totalPages), (_, index) => ( | ||
<Page canvasBackground="transparent" key={`page_${index + 1}`} pageNumber={index + 1} /> | ||
))} | ||
</Document> | ||
</div> | ||
</div> | ||
|
||
{/* Tooltip */} | ||
{tooltipPosition && selectionText && ( | ||
<div className="absolute z-20 p-2 rounded-lg" style={{ top: tooltipPosition.top, left: tooltipPosition.left }}> | ||
<Tooltip | ||
color="primary" | ||
showArrow | ||
isOpen | ||
content={ | ||
<Button size="sm" color="primary" onClick={() => onTooltipClick(selectionText)}> | ||
Ask AI | ||
</Button> | ||
} | ||
> | ||
<div /> | ||
</Tooltip> | ||
</div> | ||
)} | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export interface PdfViewerProps { | ||
onTooltipClick: (selectionText: string) => void; | ||
onDownloadPdf: () => void; | ||
onDownloadJson: () => void; | ||
onDownloadCsv: () => void; | ||
file: string; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,8 @@ | ||
@tailwind base; | ||
@tailwind components; | ||
@tailwind utilities; | ||
|
||
/* PdfViewer.css */ | ||
canvas.react-pdf__Page__canvas { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we need to use css here to add padding between pages |
||
margin-bottom: 50px; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
precise naming
handleDocumentScroll
+ return type of the function is missing