Skip to content

Commit

Permalink
Feature/f 35 pdf preview implementation (#14)
Browse files Browse the repository at this point in the history
* feat: add basic pdf viewer

* feat: track current page

* feat: add download button

* feat: add tooltip

* fix: yarn lock

* feat: add dropdown

* feat: remove comments

* feat: change colors to grey, refactor function, center topbar, add shadow

* fix: one color for both themes and arrow functions
  • Loading branch information
bohdangarchu authored Nov 8, 2024
1 parent c6af467 commit 0a13259
Show file tree
Hide file tree
Showing 10 changed files with 1,975 additions and 42 deletions.
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"jsx-a11y/anchor-is-valid": "off",
"react/require-default-props": "off",
"react/jsx-props-no-spreading": "off",
"react/jsx-no-bind": "off",
"import/prefer-default-export": "off",
"react/jsx-filename-extension": [1, { "extensions": [".tsx", ".jsx"] }],
"class-methods-use-this": "off",
Expand Down
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 2,
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
"files.eol": "\n"
"files.eol": "\n",
"cSpell.words": [
"nextui"
]
}
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,21 @@
},
"dependencies": {
"@nextui-org/button": "2.0.38",
"@nextui-org/chip": "^2.0.33",
"@nextui-org/code": "2.0.33",
"@nextui-org/dropdown": "^2.1.31",
"@nextui-org/input": "2.2.5",
"@nextui-org/kbd": "2.0.34",
"@nextui-org/link": "2.0.35",
"@nextui-org/listbox": "2.1.27",
"@nextui-org/navbar": "2.0.37",
"@nextui-org/react": "^2.4.8",
"@nextui-org/skeleton": "^2.0.32",
"@nextui-org/snippet": "2.0.43",
"@nextui-org/switch": "2.0.34",
"@nextui-org/system": "2.2.6",
"@nextui-org/theme": "2.2.11",
"@nextui-org/tooltip": "^2.0.41",
"@react-aria/ssr": "3.9.4",
"@react-aria/visually-hidden": "3.8.12",
"@tanstack/react-query": "^5.59.17",
Expand All @@ -41,6 +45,7 @@
"react": "18.3.1",
"react-dom": "18.3.1",
"react-leaflet": "^4.2.1",
"react-pdf": "^9.1.1",
"tailwind-variants": "0.1.20"
},
"devDependencies": {
Expand Down
Binary file added public/report.pdf
Binary file not shown.
121 changes: 121 additions & 0 deletions src/components/Pdf/PdfViewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
'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';
import PdfViewerOperations from '@/operations/pdf/PdfViewerOperations';

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 handleDocumentScroll = (): void => {
PdfViewerOperations.handleDocumentScroll(document, setPageNumber, pageNumber);
};

const onDocumentLoadSuccess = ({ numPages }: { numPages: number }): void => {
setTotalPages(numPages);
window.addEventListener('scroll', handleDocumentScroll);
};

const onSelectStart = (): void => {
setSelectionText(null);
};

const onSelectEnd = (): void => {
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', handleDocumentScroll);
};
}, []);

return (
<div className="min-h-screen flex flex-col items-center justify-start relative">
{/* Top Bar */}
<div className="bg-surfaceGrey shadow-md w-full py-4 flex justify-between items-center px-6 sticky top-0 z-10">
<h1 className="text-lg font-semibold">Preview</h1>
<Chip className="absolute left-1/2 transform -translate-x-1/2" color="secondary" size="md">
<p className="text-sm text-black">
{pageNumber} / {totalPages}
</p>
</Chip>
<Dropdown>
<DropdownTrigger>
<Button className="text-black" 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="w-full h-full bg-surfaceGrey flex-grow flex justify-center items-center pb-10 z-0">
<div className="max-w-3xl">
<Document file={file} onLoadSuccess={onDocumentLoadSuccess} className="flex-col items-center mx-auto">
{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>
);
}
7 changes: 7 additions & 0 deletions src/domain/props/PdfViewerProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default interface PdfViewerProps {
onTooltipClick: (selectionText: string) => void;
onDownloadPdf: () => void;
onDownloadJson: () => void;
onDownloadCsv: () => void;
file: string;
}
23 changes: 23 additions & 0 deletions src/operations/pdf/PdfViewerOperations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export default class PdfViewerOperations {
static handleDocumentScroll(
document: Document,
updatePageNumber: (pageNumber: number) => void,
currentPageNumber: number
): void {
const pages = document.querySelectorAll('.react-pdf__Page');
let currentPage = currentPageNumber;

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;
}
});

updatePageNumber(currentPage);
}
}
5 changes: 5 additions & 0 deletions src/styles/globals.css
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 {
margin-bottom: 50px;
}
6 changes: 4 additions & 2 deletions tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ module.exports = {
light: {
colors: {
primary: { DEFAULT: '#157DBC', foreground: '#F5F5F5' },
secondary: '#EEEEEE',
secondary: '#666666',
foreground: '#333333',
background: '#F5F5F5',
divider: '#157DBC',
Expand All @@ -37,12 +37,13 @@ module.exports = {
clusterOrange: '#FFB74D',
clusterRed: '#FF5252',
default: '#157DBC',
surfaceGrey: '#B0B0B0',
},
},
dark: {
colors: {
primary: '#157DBC',
secondary: '#424242',
secondary: '#B0B0B0',
foreground: '#E0E0E0',
background: '#121212',
divider: '#157DBC',
Expand All @@ -53,6 +54,7 @@ module.exports = {
clusterOrange: '#FFB74D',
clusterRed: '#FF5252',
default: '#157DBC',
surfaceGrey: '#444444',
},
},
},
Expand Down
Loading

0 comments on commit 0a13259

Please sign in to comment.