This commit is contained in:
Richard Roberson 2025-01-18 05:38:13 -07:00
parent 5ecd54a0be
commit 8263a84e40
9 changed files with 78 additions and 44 deletions

View file

@ -7,6 +7,8 @@
--foreground: #171717; --foreground: #171717;
--base: #f5f5f5; --base: #f5f5f5;
--offbase: #dbdbdb; --offbase: #dbdbdb;
--accent: #ef4444;
--muted: #737373;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
@ -15,6 +17,8 @@
--foreground: #ededed; --foreground: #ededed;
--base: #333333; --base: #333333;
--offbase: #4a4a4a; --offbase: #4a4a4a;
--accent: #f87171;
--muted: #a3a3a3;
} }
} }

View file

@ -28,7 +28,15 @@ export default function RootLayout({
<body <body
className={`${geistSans.variable} ${geistMono.variable} font-sans antialiased`} className={`${geistSans.variable} ${geistMono.variable} font-sans antialiased`}
> >
<Providers>{children}</Providers> <Providers>
<div className="min-h-screen bg-background py-4">
<div className="max-w-6xl mx-auto px-2">
<div className="bg-base rounded-lg shadow-lg">
{children}
</div>
</div>
</div>
</Providers>
</body> </body>
</html> </html>
); );

View file

@ -5,15 +5,11 @@ import { DocumentList } from '@/components/DocumentList';
export default function Home() { export default function Home() {
return ( return (
<div className="min-h-screen bg-background py-4"> <div className='p-4'>
<div className="max-w-5xl mx-auto px-2"> <h1 className="text-2xl font-bold mb-6 text-center">OpenReader WebUI</h1>
<div className="bg-base rounded-lg shadow-lg p-4"> <div className="flex flex-col items-center gap-6">
<h1 className="text-2xl font-bold mb-6 text-center">PDF Reader</h1> <PDFUploader className="w-full max-w-md" />
<div className="flex flex-col items-center gap-6"> <DocumentList />
<PDFUploader className="w-full max-w-md" />
<DocumentList />
</div>
</div>
</div> </div>
</div> </div>
); );

View file

@ -17,13 +17,13 @@ export default function PDFViewerPage() {
<div className="min-h-screen bg-background py-6 px-4 sm:py-8"> <div className="min-h-screen bg-background py-6 px-4 sm:py-8">
<div className="max-w-6xl mx-auto"> <div className="max-w-6xl mx-auto">
<div className="bg-base rounded-lg shadow-lg p-6"> <div className="bg-base rounded-lg shadow-lg p-6">
<p className="text-center text-lg">Document not found. Please select a document from the home page.</p> <p className="text-center text-lg text-muted">Document not found. Please select a document from the home page.</p>
<div className="mt-6 text-center"> <div className="mt-6 text-center">
<Link <Link
href="/" href="/"
className="inline-flex items-center px-4 py-2 bg-base text-foreground rounded-lg hover:bg-offbase transition-colors" className="inline-flex items-center px-4 py-2 bg-base text-foreground rounded-lg hover:bg-offbase transition-colors"
> >
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5 mr-2 text-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg> </svg>
Back to Documents Back to Documents
@ -36,28 +36,22 @@ export default function PDFViewerPage() {
} }
return ( return (
<div className="min-h-screen bg-background py-4 px-4 sm:py-2"> <>
<div className="max-w-6xl mx-auto"> <div className="p-2 pb-2 border-b border-offbase">
<div className="mb-4"> <div className="flex items-center justify-between">
<Link <Link
href="/" href="/"
className="inline-flex items-center px-3 py-1 bg-base text-foreground rounded-lg hover:bg-offbase transition-colors" className="inline-flex items-center px-3 py-1 bg-base text-foreground rounded-lg hover:bg-offbase transition-colors"
> >
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 mr-2 text-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg> </svg>
Documents Documents
</Link> </Link>
</div> <h1 className="mr-2 text-xl font-semibold text-foreground">{document.name}</h1>
<div className="bg-base rounded-lg shadow-lg overflow-hidden">
<div className="p-4 pb-2 border-b">
<h1 className="text-xl font-semibold">{document.name}</h1>
</div>
<div className="p-1">
<PDFViewer pdfFile={document.data} />
</div>
</div> </div>
</div> </div>
</div> <PDFViewer pdfFile={document.data} highlightText={'Richard Roberson'} />
</>
); );
} }

View file

@ -6,7 +6,7 @@ export function DocumentList() {
if (documents.length === 0) { if (documents.length === 0) {
return ( return (
<div className="w-full text-center text-gray-500"> <div className="w-full text-center text-muted">
No documents uploaded yet No documents uploaded yet
</div> </div>
); );
@ -14,32 +14,32 @@ export function DocumentList() {
return ( return (
<div className="w-full"> <div className="w-full">
<h2 className="text-xl font-semibold mb-4">Your Documents</h2> <h2 className="text-xl font-semibold mb-4 text-foreground">Your Documents</h2>
<div className="bg-white rounded-lg shadow p-4 space-y-2"> <div className="bg-background rounded-lg shadow p-2 space-y-2">
{documents.map((doc) => ( {documents.map((doc) => (
<div <div
key={doc.id} key={doc.id}
className="flex items-center justify-between hover:bg-gray-50 p-2 rounded-lg transition-colors" className="flex items-center justify-between hover:bg-base p-1 rounded-lg transition-colors"
> >
<Link <Link
href={`/pdf/${encodeURIComponent(doc.id)}`} href={`/pdf/${encodeURIComponent(doc.id)}`}
className="flex items-center space-x-4 flex-1" className="flex items-center space-x-4 flex-1"
> >
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<svg className="w-8 h-8 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-8 h-8 text-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
</svg> </svg>
</div> </div>
<div> <div>
<h3 className="font-medium">{doc.name}</h3> <h3 className="font-medium text-foreground">{doc.name}</h3>
<p className="text-sm text-gray-500"> <p className="text-sm text-muted">
{(doc.size / 1024 / 1024).toFixed(2)} MB {(doc.size / 1024 / 1024).toFixed(2)} MB
</p> </p>
</div> </div>
</Link> </Link>
<button <button
onClick={() => removeDocument(doc.id)} onClick={() => removeDocument(doc.id)}
className="p-2 text-gray-400 hover:text-red-500 transition-colors" className="p-2 text-muted hover:text-accent transition-colors"
title="Remove document" title="Remove document"
> >
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">

View file

@ -32,16 +32,16 @@ export function PDFUploader({ className = '' }: PDFUploaderProps) {
{...getRootProps()} {...getRootProps()}
className={` className={`
w-full p-8 border-2 border-dashed rounded-lg w-full p-8 border-2 border-dashed rounded-lg
${isDragActive ? 'border-blue-500 bg-blue-50' : 'border-gray-300'} ${isDragActive ? 'border-accent bg-base' : 'border-muted'}
transition-colors duration-200 ease-in-out cursor-pointer transition-colors duration-200 ease-in-out cursor-pointer
hover:border-blue-400 hover:bg-gray-50 hover:border-accent hover:bg-base
${className} ${className}
`} `}
> >
<input {...getInputProps()} /> <input {...getInputProps()} />
<div className="flex flex-col items-center justify-center text-center"> <div className="flex flex-col items-center justify-center text-center">
<svg <svg
className="w-12 h-12 mb-4 text-gray-400" className="w-12 h-12 mb-4 text-muted"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@ -55,10 +55,10 @@ export function PDFUploader({ className = '' }: PDFUploaderProps) {
/> />
</svg> </svg>
<p className="mb-2 text-lg font-semibold text-gray-700"> <p className="mb-2 text-lg font-semibold text-foreground">
Drop your PDF here, or click to select Drop your PDF here, or click to select
</p> </p>
<p className="text-sm text-gray-500"> <p className="text-sm text-muted">
Only PDF files are accepted Only PDF files are accepted
</p> </p>
</div> </div>

View file

@ -3,22 +3,47 @@
import { Document, Page, pdfjs } from 'react-pdf'; import { Document, Page, pdfjs } from 'react-pdf';
import 'react-pdf/dist/Page/AnnotationLayer.css'; import 'react-pdf/dist/Page/AnnotationLayer.css';
import 'react-pdf/dist/Page/TextLayer.css'; import 'react-pdf/dist/Page/TextLayer.css';
import { useState } from 'react'; import { useState, useEffect } from 'react';
// Set worker from public directory // Set worker from public directory
pdfjs.GlobalWorkerOptions.workerSrc = '/pdf.worker.mjs'; pdfjs.GlobalWorkerOptions.workerSrc = '/pdf.worker.mjs';
interface PDFViewerProps { interface PDFViewerProps {
pdfFile: string; pdfFile: string;
highlightText?: string; // Text to highlight in the PDF
} }
export function PDFViewer({ pdfFile }: PDFViewerProps) { export function PDFViewer({ pdfFile, highlightText }: PDFViewerProps) {
const [numPages, setNumPages] = useState<number>(); const [numPages, setNumPages] = useState<number>();
function onDocumentLoadSuccess({ numPages }: { numPages: number }): void { function onDocumentLoadSuccess({ numPages }: { numPages: number }): void {
setNumPages(numPages); setNumPages(numPages);
} }
// Function to highlight text in the PDF
const highlightPattern = (text: string, pattern: string) => {
if (!pattern) return text;
const regex = new RegExp(`(${pattern})`, 'gi');
return text.replace(regex, '<mark>$1</mark>');
};
// Add styles for highlighted text
useEffect(() => {
const style = document.createElement('style');
style.textContent = `
.react-pdf__Page__textContent mark {
background-color: yellow;
border-radius: 2px;
padding: 0;
margin: 0;
}
`;
document.head.appendChild(style);
return () => {
document.head.removeChild(style);
};
}, []);
return ( return (
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<Document <Document
@ -42,6 +67,11 @@ export function PDFViewer({ pdfFile }: PDFViewerProps) {
renderTextLayer={true} renderTextLayer={true}
className="rounded-xl shadow-lg" className="rounded-xl shadow-lg"
scale={1.2} scale={1.2}
customTextRenderer={(textItem) =>
highlightText ?
highlightPattern(textItem.str, highlightText) :
textItem.str
}
/> />
</div> </div>
</div> </div>

View file

@ -43,7 +43,7 @@ export function PDFProvider({ children }: { children: ReactNode }) {
reader.onloadend = () => { reader.onloadend = () => {
const base64Data = reader.result as string; const base64Data = reader.result as string;
const newDoc: PDFDocument = { const newDoc: PDFDocument = {
id: `${file.name}-${Date.now()}`, id: `${file.name.replace(/[^a-zA-Z0-9]/g, '-').replace(/-+/g, '-')}-${Date.now()}`,
name: file.name, name: file.name,
size: file.size, size: file.size,
lastModified: file.lastModified, lastModified: file.lastModified,

View file

@ -13,6 +13,8 @@ export default {
foreground: "var(--foreground)", foreground: "var(--foreground)",
base: "var(--base)", base: "var(--base)",
offbase: "var(--offbase)", offbase: "var(--offbase)",
accent: "var(--accent)",
muted: "var(--muted)",
}, },
}, },
}, },