Merge pull request #34 from richardr1126/apple-webkit
Fix Apple WebKit (iOS) PDF Upload
This commit is contained in:
commit
a704fcbec6
8 changed files with 56 additions and 68 deletions
2
.github/workflows/playwright.yml
vendored
2
.github/workflows/playwright.yml
vendored
|
|
@ -5,7 +5,7 @@ on:
|
|||
pull_request:
|
||||
branches: [ main, master ]
|
||||
jobs:
|
||||
test:
|
||||
e2e-testing:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export function PDFViewer({ zoomLevel }: PDFViewerProps) {
|
|||
clearHighlights,
|
||||
handleTextClick,
|
||||
onDocumentLoadSuccess,
|
||||
currDocURL,
|
||||
currDocData,
|
||||
currDocPages,
|
||||
currDocText,
|
||||
currDocPage,
|
||||
|
|
@ -163,7 +163,7 @@ export function PDFViewer({ zoomLevel }: PDFViewerProps) {
|
|||
<Document
|
||||
loading={<DocumentSkeleton />}
|
||||
noData={<DocumentSkeleton />}
|
||||
file={currDocURL}
|
||||
file={currDocData}
|
||||
onLoadSuccess={(pdf) => {
|
||||
onDocumentLoadSuccess(pdf);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ import { useTTS } from '@/contexts/TTSContext';
|
|||
import { useConfig } from '@/contexts/ConfigContext';
|
||||
import {
|
||||
extractTextFromPDF,
|
||||
convertPDFDataToURL,
|
||||
highlightPattern,
|
||||
clearHighlights,
|
||||
handleTextClick,
|
||||
|
|
@ -43,7 +42,7 @@ import { combineAudioChunks } from '@/utils/audio';
|
|||
*/
|
||||
interface PDFContextType {
|
||||
// Current document state
|
||||
currDocURL: string | undefined;
|
||||
currDocData: ArrayBuffer | undefined;
|
||||
currDocName: string | undefined;
|
||||
currDocPages: number | undefined;
|
||||
currDocPage: number;
|
||||
|
|
@ -99,7 +98,7 @@ export function PDFProvider({ children }: { children: ReactNode }) {
|
|||
} = useConfig();
|
||||
|
||||
// Current document state
|
||||
const [currDocURL, setCurrDocURL] = useState<string>();
|
||||
const [currDocData, setCurrDocData] = useState<ArrayBuffer>();
|
||||
const [currDocName, setCurrDocName] = useState<string>();
|
||||
const [currDocText, setCurrDocText] = useState<string>();
|
||||
const [pdfDocument, setPdfDocument] = useState<PDFDocumentProxy>();
|
||||
|
|
@ -147,14 +146,14 @@ export function PDFProvider({ children }: { children: ReactNode }) {
|
|||
* Triggers text extraction and processing when either the document URL or page changes
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (currDocURL) {
|
||||
if (currDocData) {
|
||||
loadCurrDocText();
|
||||
}
|
||||
}, [currDocPage, currDocURL, loadCurrDocText]);
|
||||
}, [currDocPage, currDocData, loadCurrDocText]);
|
||||
|
||||
/**
|
||||
* Sets the current document based on its ID
|
||||
* Retrieves document from IndexedDB and converts it to a viewable URL
|
||||
* Retrieves document from IndexedDB
|
||||
*
|
||||
* @param {string} id - The unique identifier of the document to set
|
||||
* @returns {Promise<void>}
|
||||
|
|
@ -163,12 +162,11 @@ export function PDFProvider({ children }: { children: ReactNode }) {
|
|||
try {
|
||||
const doc = await indexedDBService.getDocument(id);
|
||||
if (doc) {
|
||||
const url = await convertPDFDataToURL(doc.data);
|
||||
setCurrDocName(doc.name);
|
||||
setCurrDocURL(url);
|
||||
setCurrDocData(doc.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to get document URL:', error);
|
||||
console.error('Failed to get document:', error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
|
@ -178,7 +176,7 @@ export function PDFProvider({ children }: { children: ReactNode }) {
|
|||
*/
|
||||
const clearCurrDoc = useCallback(() => {
|
||||
setCurrDocName(undefined);
|
||||
setCurrDocURL(undefined);
|
||||
setCurrDocData(undefined);
|
||||
setCurrDocText(undefined);
|
||||
setCurrDocPages(undefined);
|
||||
setPdfDocument(undefined);
|
||||
|
|
@ -306,7 +304,7 @@ export function PDFProvider({ children }: { children: ReactNode }) {
|
|||
() => ({
|
||||
onDocumentLoadSuccess,
|
||||
setCurrentDocument,
|
||||
currDocURL,
|
||||
currDocData,
|
||||
currDocName,
|
||||
currDocPages,
|
||||
currDocPage,
|
||||
|
|
@ -322,7 +320,7 @@ export function PDFProvider({ children }: { children: ReactNode }) {
|
|||
[
|
||||
onDocumentLoadSuccess,
|
||||
setCurrentDocument,
|
||||
currDocURL,
|
||||
currDocData,
|
||||
currDocName,
|
||||
currDocPages,
|
||||
currDocPage,
|
||||
|
|
|
|||
|
|
@ -39,13 +39,15 @@ export function usePDFDocuments() {
|
|||
*/
|
||||
const addDocument = useCallback(async (file: File): Promise<string> => {
|
||||
const id = uuidv4();
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
|
||||
const newDoc: PDFDocument = {
|
||||
id,
|
||||
type: 'pdf',
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
lastModified: file.lastModified,
|
||||
data: new Blob([file], { type: file.type }),
|
||||
data: arrayBuffer,
|
||||
};
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export interface BaseDocument {
|
|||
|
||||
export interface PDFDocument extends BaseDocument {
|
||||
type: 'pdf';
|
||||
data: Blob;
|
||||
data: ArrayBuffer;
|
||||
}
|
||||
|
||||
export interface EPUBDocument extends BaseDocument {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class IndexedDBService {
|
|||
request.onupgradeneeded = (event) => {
|
||||
console.log('Upgrading IndexedDB schema...');
|
||||
const db = (event.target as IDBOpenDBRequest).result;
|
||||
|
||||
|
||||
if (!db.objectStoreNames.contains(PDF_STORE_NAME)) {
|
||||
console.log('Creating PDF documents store...');
|
||||
db.createObjectStore(PDF_STORE_NAME, { keyPath: 'id' });
|
||||
|
|
@ -75,7 +75,12 @@ class IndexedDBService {
|
|||
console.log('Adding document to IndexedDB:', document.name);
|
||||
const transaction = this.db!.transaction([PDF_STORE_NAME], 'readwrite');
|
||||
const store = transaction.objectStore(PDF_STORE_NAME);
|
||||
const request = store.put(document);
|
||||
|
||||
// Create a structured clone of the document to ensure proper storage
|
||||
const request = store.put({
|
||||
...document,
|
||||
data: document.data // Store ArrayBuffer directly
|
||||
});
|
||||
|
||||
request.onerror = (event) => {
|
||||
const error = (event.target as IDBRequest).error;
|
||||
|
|
@ -165,7 +170,7 @@ class IndexedDBService {
|
|||
// Create two transactions - one for document deletion, one for location cleanup
|
||||
const docTransaction = this.db!.transaction([PDF_STORE_NAME], 'readwrite');
|
||||
const configTransaction = this.db!.transaction([CONFIG_STORE_NAME], 'readwrite');
|
||||
|
||||
|
||||
const docStore = docTransaction.objectStore(PDF_STORE_NAME);
|
||||
const configStore = configTransaction.objectStore(CONFIG_STORE_NAME);
|
||||
|
||||
|
|
@ -221,7 +226,7 @@ class IndexedDBService {
|
|||
|
||||
const transaction = this.db!.transaction([EPUB_STORE_NAME], 'readwrite');
|
||||
const store = transaction.objectStore(EPUB_STORE_NAME);
|
||||
|
||||
|
||||
// Create a structured clone of the document to ensure proper storage
|
||||
const request = store.put({
|
||||
...document,
|
||||
|
|
@ -315,7 +320,7 @@ class IndexedDBService {
|
|||
// Create two transactions - one for document deletion, one for location cleanup
|
||||
const docTransaction = this.db!.transaction([EPUB_STORE_NAME], 'readwrite');
|
||||
const configTransaction = this.db!.transaction([CONFIG_STORE_NAME], 'readwrite');
|
||||
|
||||
|
||||
const docStore = docTransaction.objectStore(EPUB_STORE_NAME);
|
||||
const configStore = configTransaction.objectStore(CONFIG_STORE_NAME);
|
||||
|
||||
|
|
@ -477,17 +482,15 @@ class IndexedDBService {
|
|||
async syncToServer(): Promise<{ lastSync: number }> {
|
||||
const pdfDocs = await this.getAllDocuments();
|
||||
const epubDocs = await this.getAllEPUBDocuments();
|
||||
|
||||
|
||||
const documents = [];
|
||||
|
||||
// Process PDF documents - store the raw PDF data
|
||||
|
||||
// Process PDF documents - convert ArrayBuffer to array for JSON serialization
|
||||
for (const doc of pdfDocs) {
|
||||
const arrayBuffer = await doc.data.arrayBuffer();
|
||||
const uint8Array = new Uint8Array(arrayBuffer);
|
||||
documents.push({
|
||||
...doc,
|
||||
type: 'pdf',
|
||||
data: Array.from(uint8Array) // Convert to regular array for JSON serialization
|
||||
data: Array.from(new Uint8Array(doc.data))
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -496,7 +499,7 @@ class IndexedDBService {
|
|||
documents.push({
|
||||
...doc,
|
||||
type: 'epub',
|
||||
data: Array.from(new Uint8Array(doc.data)) // Convert to regular array for JSON serialization
|
||||
data: Array.from(new Uint8Array(doc.data))
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -520,31 +523,26 @@ class IndexedDBService {
|
|||
}
|
||||
|
||||
const { documents } = await response.json();
|
||||
|
||||
|
||||
// Process each document
|
||||
for (const doc of documents) {
|
||||
// Convert the numeric array back to ArrayBuffer
|
||||
const uint8Array = new Uint8Array(doc.data);
|
||||
const documentData = {
|
||||
id: doc.id,
|
||||
type: doc.type,
|
||||
name: doc.name,
|
||||
size: doc.size,
|
||||
lastModified: doc.lastModified,
|
||||
data: uint8Array.buffer
|
||||
};
|
||||
|
||||
if (doc.type === 'pdf') {
|
||||
// Create a Blob from the raw binary data
|
||||
const blob = new Blob([new Uint8Array(doc.data)], { type: 'application/pdf' });
|
||||
await this.addDocument({
|
||||
id: doc.id,
|
||||
type: doc.type,
|
||||
name: doc.name,
|
||||
size: doc.size,
|
||||
lastModified: doc.lastModified,
|
||||
data: blob
|
||||
});
|
||||
await this.addDocument(documentData);
|
||||
} else if (doc.type === 'epub') {
|
||||
// Convert the numeric array back to ArrayBuffer for EPUB
|
||||
const uint8Array = new Uint8Array(doc.data);
|
||||
await this.addEPUBDocument({
|
||||
id: doc.id,
|
||||
type: doc.type,
|
||||
name: doc.name,
|
||||
size: doc.size,
|
||||
lastModified: doc.lastModified,
|
||||
data: uint8Array.buffer
|
||||
});
|
||||
await this.addEPUBDocument(documentData);
|
||||
} else {
|
||||
console.warn(`Unknown document type: ${doc.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,16 +15,6 @@ interface TextMatch {
|
|||
lengthDiff: number;
|
||||
}
|
||||
|
||||
// URL Conversion functions
|
||||
export function convertPDFDataToURL(pdfData: Blob): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => resolve(reader.result as string);
|
||||
reader.onerror = () => reject(reader.error);
|
||||
reader.readAsDataURL(pdfData);
|
||||
});
|
||||
}
|
||||
|
||||
// Text Processing functions
|
||||
export async function extractTextFromPDF(
|
||||
pdf: PDFDocumentProxy,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ async function uploadFile(page: Page, filePath: string) {
|
|||
test.describe('Document Upload and Display', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Navigate to the home page before each test
|
||||
await page.goto('http://localhost:3003');
|
||||
await page.goto('/');
|
||||
|
||||
// Click the "done" button to dismiss the welcome message
|
||||
await page.getByText('Done').click();
|
||||
|
|
@ -23,7 +23,7 @@ test.describe('Document Upload and Display', () => {
|
|||
await uploadFile(page, './public/sample.pdf');
|
||||
|
||||
// Verify upload success
|
||||
await expect(page.getByText('sample.pdf')).toBeVisible();
|
||||
await expect(page.getByText('sample.pdf')).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('should display a PDF document', async ({ page }) => {
|
||||
|
|
@ -31,11 +31,11 @@ test.describe('Document Upload and Display', () => {
|
|||
await uploadFile(page, './public/sample.pdf');
|
||||
|
||||
// Click on the uploaded document
|
||||
await page.getByText('sample.pdf').click();
|
||||
await page.getByText('sample.pdf').click({ timeout: 10000 });
|
||||
|
||||
// Verify PDF viewer is displayed
|
||||
await expect(page.locator('.react-pdf__Document')).toBeVisible();
|
||||
await expect(page.locator('.react-pdf__Page')).toBeVisible();
|
||||
await expect(page.locator('.react-pdf__Document')).toBeVisible({ timeout: 10000 });
|
||||
await expect(page.locator('.react-pdf__Page')).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ test.describe('Document Upload and Display', () => {
|
|||
await uploadFile(page, './public/sample.epub');
|
||||
|
||||
// Verify upload success
|
||||
await expect(page.getByText('sample.epub')).toBeVisible();
|
||||
await expect(page.getByText('sample.epub')).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('should display an EPUB document', async ({ page }) => {
|
||||
|
|
@ -53,10 +53,10 @@ test.describe('Document Upload and Display', () => {
|
|||
await uploadFile(page, './public/sample.epub');
|
||||
|
||||
// Click on the uploaded document
|
||||
await page.getByText('sample.epub').click();
|
||||
await page.getByText('sample.epub').click({ timeout: 10000 });
|
||||
|
||||
// Verify EPUB viewer is displayed
|
||||
await expect(page.locator('.epub-container')).toBeVisible();
|
||||
await expect(page.locator('.epub-container')).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue