diff --git a/package-lock.json b/package-lock.json index 2a5472a..b481217 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@headlessui/react": "^2.2.0", "@types/howler": "^2.2.12", + "@types/react-window": "^1.8.8", "@types/string-similarity": "^4.0.2", "@types/uuid": "^10.0.0", "compromise": "^14.14.4", @@ -22,6 +23,7 @@ "react-dom": "^19.0.0", "react-dropzone": "^14.3.5", "react-pdf": "^9.2.1", + "react-window": "^1.8.11", "string-similarity": "^4.0.4", "uuid": "^11.0.5" }, @@ -50,6 +52,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@emnapi/runtime": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", @@ -1153,7 +1167,6 @@ "version": "19.0.7", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.7.tgz", "integrity": "sha512-MoFsEJKkAtZCrC1r6CM8U22GzhG7u2Wir8ons/aCKH6MBdD1ibV24zOSSkdZVUKqN5i396zG5VKLYZ3yaUZdLA==", - "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -1169,6 +1182,15 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/react-window": { + "version": "1.8.8", + "resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.8.tgz", + "integrity": "sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/string-similarity": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/string-similarity/-/string-similarity-4.0.2.tgz", @@ -2186,7 +2208,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -4449,6 +4470,12 @@ "node": ">= 0.4" } }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", + "license": "MIT" + }, "node_modules/merge-refs": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/merge-refs/-/merge-refs-1.3.0.tgz", @@ -5556,6 +5583,23 @@ } } }, + "node_modules/react-window": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.11.tgz", + "integrity": "sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.0.0", + "memoize-one": ">=3.1.1 <6" + }, + "engines": { + "node": ">8.0.0" + }, + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -5617,6 +5661,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", diff --git a/package.json b/package.json index 3fdc62e..bc8349a 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "dependencies": { "@headlessui/react": "^2.2.0", "@types/howler": "^2.2.12", + "@types/react-window": "^1.8.8", "@types/string-similarity": "^4.0.2", "@types/uuid": "^10.0.0", "compromise": "^14.14.4", @@ -23,6 +24,7 @@ "react-dom": "^19.0.0", "react-dropzone": "^14.3.5", "react-pdf": "^9.2.1", + "react-window": "^1.8.11", "string-similarity": "^4.0.4", "uuid": "^11.0.5" }, diff --git a/src/app/globals.css b/src/app/globals.css index 08d34f2..833e8d9 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -35,3 +35,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } + +.pdf-list > div { + padding: 0 !important; +} diff --git a/src/components/PDFViewer.tsx b/src/components/PDFViewer.tsx index 642d81e..dba8ec2 100644 --- a/src/components/PDFViewer.tsx +++ b/src/components/PDFViewer.tsx @@ -1,19 +1,62 @@ 'use client'; -import { RefObject } from 'react'; +import { RefObject, useCallback, useState, useEffect, useRef } from 'react'; import { Document, Page } from 'react-pdf'; +import { FixedSizeList } from 'react-window'; import 'react-pdf/dist/Page/AnnotationLayer.css'; import 'react-pdf/dist/Page/TextLayer.css'; -import { useState, useEffect, useRef } from 'react'; import { PDFSkeleton } from './PDFSkeleton'; import { useTTS } from '@/contexts/TTSContext'; import { usePDF } from '@/contexts/PDFContext'; +import { pdfjs } from 'react-pdf'; interface PDFViewerProps { pdfData: Blob | undefined; zoomLevel: number; } +interface PageItemData { + numPages: number; + scale: number; + containerWidth: number; + pageWidth: number; + pageHeight: number; +} + +const PageComponent = ({ index, style, data }: { index: number; style: any; data: PageItemData }) => { + const { numPages, scale, pageHeight } = data; + + return ( +
+ {index + 1} / {numPages} +
++ {index + 1} / {numPages} +
+- {index + 1} / {numPages} -
-