Fix Apple WebKit PDF Upload
This commit is contained in:
parent
28a3804843
commit
136fdb9652
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:
|
pull_request:
|
||||||
branches: [ main, master ]
|
branches: [ main, master ]
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
e2e-testing:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ export function PDFViewer({ zoomLevel }: PDFViewerProps) {
|
||||||
clearHighlights,
|
clearHighlights,
|
||||||
handleTextClick,
|
handleTextClick,
|
||||||
onDocumentLoadSuccess,
|
onDocumentLoadSuccess,
|
||||||
currDocURL,
|
currDocData,
|
||||||
currDocPages,
|
currDocPages,
|
||||||
currDocText,
|
currDocText,
|
||||||
currDocPage,
|
currDocPage,
|
||||||
|
|
@ -163,7 +163,7 @@ export function PDFViewer({ zoomLevel }: PDFViewerProps) {
|
||||||
<Document
|
<Document
|
||||||
loading={<DocumentSkeleton />}
|
loading={<DocumentSkeleton />}
|
||||||
noData={<DocumentSkeleton />}
|
noData={<DocumentSkeleton />}
|
||||||
file={currDocURL}
|
file={currDocData}
|
||||||
onLoadSuccess={(pdf) => {
|
onLoadSuccess={(pdf) => {
|
||||||
onDocumentLoadSuccess(pdf);
|
onDocumentLoadSuccess(pdf);
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import { useTTS } from '@/contexts/TTSContext';
|
||||||
import { useConfig } from '@/contexts/ConfigContext';
|
import { useConfig } from '@/contexts/ConfigContext';
|
||||||
import {
|
import {
|
||||||
extractTextFromPDF,
|
extractTextFromPDF,
|
||||||
convertPDFDataToURL,
|
|
||||||
highlightPattern,
|
highlightPattern,
|
||||||
clearHighlights,
|
clearHighlights,
|
||||||
handleTextClick,
|
handleTextClick,
|
||||||
|
|
@ -43,7 +42,7 @@ import { combineAudioChunks } from '@/utils/audio';
|
||||||
*/
|
*/
|
||||||
interface PDFContextType {
|
interface PDFContextType {
|
||||||
// Current document state
|
// Current document state
|
||||||
currDocURL: string | undefined;
|
currDocData: ArrayBuffer | undefined;
|
||||||
currDocName: string | undefined;
|
currDocName: string | undefined;
|
||||||
currDocPages: number | undefined;
|
currDocPages: number | undefined;
|
||||||
currDocPage: number;
|
currDocPage: number;
|
||||||
|
|
@ -99,7 +98,7 @@ export function PDFProvider({ children }: { children: ReactNode }) {
|
||||||
} = useConfig();
|
} = useConfig();
|
||||||
|
|
||||||
// Current document state
|
// Current document state
|
||||||
const [currDocURL, setCurrDocURL] = useState<string>();
|
const [currDocData, setCurrDocData] = useState<ArrayBuffer>();
|
||||||
const [currDocName, setCurrDocName] = useState<string>();
|
const [currDocName, setCurrDocName] = useState<string>();
|
||||||
const [currDocText, setCurrDocText] = useState<string>();
|
const [currDocText, setCurrDocText] = useState<string>();
|
||||||
const [pdfDocument, setPdfDocument] = useState<PDFDocumentProxy>();
|
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
|
* Triggers text extraction and processing when either the document URL or page changes
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currDocURL) {
|
if (currDocData) {
|
||||||
loadCurrDocText();
|
loadCurrDocText();
|
||||||
}
|
}
|
||||||
}, [currDocPage, currDocURL, loadCurrDocText]);
|
}, [currDocPage, currDocData, loadCurrDocText]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the current document based on its ID
|
* 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
|
* @param {string} id - The unique identifier of the document to set
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
|
|
@ -163,12 +162,11 @@ export function PDFProvider({ children }: { children: ReactNode }) {
|
||||||
try {
|
try {
|
||||||
const doc = await indexedDBService.getDocument(id);
|
const doc = await indexedDBService.getDocument(id);
|
||||||
if (doc) {
|
if (doc) {
|
||||||
const url = await convertPDFDataToURL(doc.data);
|
|
||||||
setCurrDocName(doc.name);
|
setCurrDocName(doc.name);
|
||||||
setCurrDocURL(url);
|
setCurrDocData(doc.data);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} 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(() => {
|
const clearCurrDoc = useCallback(() => {
|
||||||
setCurrDocName(undefined);
|
setCurrDocName(undefined);
|
||||||
setCurrDocURL(undefined);
|
setCurrDocData(undefined);
|
||||||
setCurrDocText(undefined);
|
setCurrDocText(undefined);
|
||||||
setCurrDocPages(undefined);
|
setCurrDocPages(undefined);
|
||||||
setPdfDocument(undefined);
|
setPdfDocument(undefined);
|
||||||
|
|
@ -306,7 +304,7 @@ export function PDFProvider({ children }: { children: ReactNode }) {
|
||||||
() => ({
|
() => ({
|
||||||
onDocumentLoadSuccess,
|
onDocumentLoadSuccess,
|
||||||
setCurrentDocument,
|
setCurrentDocument,
|
||||||
currDocURL,
|
currDocData,
|
||||||
currDocName,
|
currDocName,
|
||||||
currDocPages,
|
currDocPages,
|
||||||
currDocPage,
|
currDocPage,
|
||||||
|
|
@ -322,7 +320,7 @@ export function PDFProvider({ children }: { children: ReactNode }) {
|
||||||
[
|
[
|
||||||
onDocumentLoadSuccess,
|
onDocumentLoadSuccess,
|
||||||
setCurrentDocument,
|
setCurrentDocument,
|
||||||
currDocURL,
|
currDocData,
|
||||||
currDocName,
|
currDocName,
|
||||||
currDocPages,
|
currDocPages,
|
||||||
currDocPage,
|
currDocPage,
|
||||||
|
|
|
||||||
|
|
@ -39,13 +39,15 @@ export function usePDFDocuments() {
|
||||||
*/
|
*/
|
||||||
const addDocument = useCallback(async (file: File): Promise<string> => {
|
const addDocument = useCallback(async (file: File): Promise<string> => {
|
||||||
const id = uuidv4();
|
const id = uuidv4();
|
||||||
|
const arrayBuffer = await file.arrayBuffer();
|
||||||
|
|
||||||
const newDoc: PDFDocument = {
|
const newDoc: PDFDocument = {
|
||||||
id,
|
id,
|
||||||
type: 'pdf',
|
type: 'pdf',
|
||||||
name: file.name,
|
name: file.name,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
lastModified: file.lastModified,
|
lastModified: file.lastModified,
|
||||||
data: new Blob([file], { type: file.type }),
|
data: arrayBuffer,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export interface BaseDocument {
|
||||||
|
|
||||||
export interface PDFDocument extends BaseDocument {
|
export interface PDFDocument extends BaseDocument {
|
||||||
type: 'pdf';
|
type: 'pdf';
|
||||||
data: Blob;
|
data: ArrayBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EPUBDocument extends BaseDocument {
|
export interface EPUBDocument extends BaseDocument {
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,12 @@ class IndexedDBService {
|
||||||
console.log('Adding document to IndexedDB:', document.name);
|
console.log('Adding document to IndexedDB:', document.name);
|
||||||
const transaction = this.db!.transaction([PDF_STORE_NAME], 'readwrite');
|
const transaction = this.db!.transaction([PDF_STORE_NAME], 'readwrite');
|
||||||
const store = transaction.objectStore(PDF_STORE_NAME);
|
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) => {
|
request.onerror = (event) => {
|
||||||
const error = (event.target as IDBRequest).error;
|
const error = (event.target as IDBRequest).error;
|
||||||
|
|
@ -480,14 +485,12 @@ class IndexedDBService {
|
||||||
|
|
||||||
const documents = [];
|
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) {
|
for (const doc of pdfDocs) {
|
||||||
const arrayBuffer = await doc.data.arrayBuffer();
|
|
||||||
const uint8Array = new Uint8Array(arrayBuffer);
|
|
||||||
documents.push({
|
documents.push({
|
||||||
...doc,
|
...doc,
|
||||||
type: 'pdf',
|
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({
|
documents.push({
|
||||||
...doc,
|
...doc,
|
||||||
type: 'epub',
|
type: 'epub',
|
||||||
data: Array.from(new Uint8Array(doc.data)) // Convert to regular array for JSON serialization
|
data: Array.from(new Uint8Array(doc.data))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -523,28 +526,23 @@ class IndexedDBService {
|
||||||
|
|
||||||
// Process each document
|
// Process each document
|
||||||
for (const doc of documents) {
|
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') {
|
if (doc.type === 'pdf') {
|
||||||
// Create a Blob from the raw binary data
|
await this.addDocument(documentData);
|
||||||
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
|
|
||||||
});
|
|
||||||
} else if (doc.type === 'epub') {
|
} else if (doc.type === 'epub') {
|
||||||
// Convert the numeric array back to ArrayBuffer for EPUB
|
await this.addEPUBDocument(documentData);
|
||||||
const uint8Array = new Uint8Array(doc.data);
|
} else {
|
||||||
await this.addEPUBDocument({
|
console.warn(`Unknown document type: ${doc.type}`);
|
||||||
id: doc.id,
|
|
||||||
type: doc.type,
|
|
||||||
name: doc.name,
|
|
||||||
size: doc.size,
|
|
||||||
lastModified: doc.lastModified,
|
|
||||||
data: uint8Array.buffer
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,16 +15,6 @@ interface TextMatch {
|
||||||
lengthDiff: number;
|
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
|
// Text Processing functions
|
||||||
export async function extractTextFromPDF(
|
export async function extractTextFromPDF(
|
||||||
pdf: PDFDocumentProxy,
|
pdf: PDFDocumentProxy,
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ async function uploadFile(page: Page, filePath: string) {
|
||||||
test.describe('Document Upload and Display', () => {
|
test.describe('Document Upload and Display', () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
// Navigate to the home page before each test
|
// 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
|
// Click the "done" button to dismiss the welcome message
|
||||||
await page.getByText('Done').click();
|
await page.getByText('Done').click();
|
||||||
|
|
@ -23,7 +23,7 @@ test.describe('Document Upload and Display', () => {
|
||||||
await uploadFile(page, './public/sample.pdf');
|
await uploadFile(page, './public/sample.pdf');
|
||||||
|
|
||||||
// Verify upload success
|
// 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 }) => {
|
test('should display a PDF document', async ({ page }) => {
|
||||||
|
|
@ -31,11 +31,11 @@ test.describe('Document Upload and Display', () => {
|
||||||
await uploadFile(page, './public/sample.pdf');
|
await uploadFile(page, './public/sample.pdf');
|
||||||
|
|
||||||
// Click on the uploaded document
|
// Click on the uploaded document
|
||||||
await page.getByText('sample.pdf').click();
|
await page.getByText('sample.pdf').click({ timeout: 10000 });
|
||||||
|
|
||||||
// Verify PDF viewer is displayed
|
// Verify PDF viewer is displayed
|
||||||
await expect(page.locator('.react-pdf__Document')).toBeVisible();
|
await expect(page.locator('.react-pdf__Document')).toBeVisible({ timeout: 10000 });
|
||||||
await expect(page.locator('.react-pdf__Page')).toBeVisible();
|
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');
|
await uploadFile(page, './public/sample.epub');
|
||||||
|
|
||||||
// Verify upload success
|
// 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 }) => {
|
test('should display an EPUB document', async ({ page }) => {
|
||||||
|
|
@ -53,10 +53,10 @@ test.describe('Document Upload and Display', () => {
|
||||||
await uploadFile(page, './public/sample.epub');
|
await uploadFile(page, './public/sample.epub');
|
||||||
|
|
||||||
// Click on the uploaded document
|
// Click on the uploaded document
|
||||||
await page.getByText('sample.epub').click();
|
await page.getByText('sample.epub').click({ timeout: 10000 });
|
||||||
|
|
||||||
// Verify EPUB viewer is displayed
|
// 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