Allow PDF files to be drag and dropped (#31015)
This commit is contained in:
@@ -7,7 +7,7 @@ import * as React from 'react'
|
||||
import { getLocale } from '$web-common/locale'
|
||||
import Icon from '@brave/leo/react/icon'
|
||||
import styles from './style.module.scss'
|
||||
import { isImageFile } from '../../constants/file_types'
|
||||
import { isImageFile, isPdfFile } from '../../constants/file_types'
|
||||
import { useConversation } from '../../state/conversation_context'
|
||||
import { convertFileToUploadedFile } from '../../utils/file_utils'
|
||||
|
||||
@@ -23,7 +23,9 @@ export default function DragOverlay() {
|
||||
e.stopPropagation()
|
||||
clearDragState()
|
||||
|
||||
const files = Array.from(e.dataTransfer?.files || []).filter(isImageFile)
|
||||
const files = Array.from(e.dataTransfer?.files || []).filter(file =>
|
||||
isImageFile(file) || isPdfFile(file)
|
||||
)
|
||||
|
||||
if (files.length === 0) {
|
||||
return
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { isImageFile } from './file_types'
|
||||
import { isImageFile, isPdfFile } from './file_types'
|
||||
|
||||
describe('isImageFile', () => {
|
||||
// Helper function to create mock File objects
|
||||
@@ -114,3 +114,45 @@ describe('isImageFile', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('isPdfFile', () => {
|
||||
// Helper function to create mock File objects
|
||||
const createMockFile = (name: string, type: string): File => {
|
||||
return new File([''], name, { type })
|
||||
}
|
||||
|
||||
it('accepts PDF files', () => {
|
||||
const file = createMockFile('test.pdf', 'application/pdf')
|
||||
expect(isPdfFile(file)).toBe(true)
|
||||
})
|
||||
|
||||
it('handles uppercase MIME type', () => {
|
||||
const file = createMockFile('test.pdf', 'APPLICATION/PDF')
|
||||
expect(isPdfFile(file)).toBe(true)
|
||||
})
|
||||
|
||||
it('handles mixed case MIME type', () => {
|
||||
const file = createMockFile('test.pdf', 'Application/Pdf')
|
||||
expect(isPdfFile(file)).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects image files', () => {
|
||||
const file = createMockFile('test.png', 'image/png')
|
||||
expect(isPdfFile(file)).toBe(false)
|
||||
})
|
||||
|
||||
it('rejects text files', () => {
|
||||
const file = createMockFile('test.txt', 'text/plain')
|
||||
expect(isPdfFile(file)).toBe(false)
|
||||
})
|
||||
|
||||
it('rejects empty MIME type', () => {
|
||||
const file = createMockFile('test', '')
|
||||
expect(isPdfFile(file)).toBe(false)
|
||||
})
|
||||
|
||||
it('rejects malformed MIME type', () => {
|
||||
const file = createMockFile('test', 'not-a-mime-type')
|
||||
expect(isPdfFile(file)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -14,3 +14,8 @@ const SUPPORTED_IMAGE_TYPES: string[] = [
|
||||
export const isImageFile = (file: File): boolean => {
|
||||
return SUPPORTED_IMAGE_TYPES.includes(file.type.toLowerCase())
|
||||
}
|
||||
|
||||
// Check if file is a PDF
|
||||
export const isPdfFile = (file: File): boolean => {
|
||||
return file.type.toLowerCase() === 'application/pdf'
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { convertFileToUploadedFile, FileReadError, ImageProcessingError } from './file_utils'
|
||||
import { convertFileToUploadedFile, FileReadError, ImageProcessingError, UnsupportedFileTypeError } from './file_utils'
|
||||
import * as Mojom from '../../common/mojom'
|
||||
import getAPI from '../api'
|
||||
|
||||
@@ -20,7 +20,6 @@ jest.mock('$web-common/loadTimeData', () => ({
|
||||
}
|
||||
}))
|
||||
|
||||
|
||||
describe('convertFileToUploadedFile', () => {
|
||||
let mockHandler: any
|
||||
let mockService: any
|
||||
@@ -97,7 +96,6 @@ describe('convertFileToUploadedFile', () => {
|
||||
return new File(['mock content'], name, { type, size: size })
|
||||
}
|
||||
|
||||
|
||||
describe('successful file processing', () => {
|
||||
it('converts image file successfully', async () => {
|
||||
const file = createMockFile('test.png', 'image/png', 1500)
|
||||
@@ -161,6 +159,32 @@ describe('convertFileToUploadedFile', () => {
|
||||
expect(result).toEqual(mockProcessedFile)
|
||||
})
|
||||
|
||||
it('converts PDF file successfully', async () => {
|
||||
const file = createMockFile('test.pdf', 'application/pdf', 2000)
|
||||
const mockArrayBuffer = new ArrayBuffer(8)
|
||||
const expectedData = Array.from(new Uint8Array(mockArrayBuffer))
|
||||
|
||||
// Override readAsArrayBuffer to trigger success
|
||||
mockFileReader.readAsArrayBuffer.mockImplementation(() => {
|
||||
process.nextTick(() => {
|
||||
if (mockFileReader.onload) {
|
||||
mockFileReader.onload({ target: { result: mockArrayBuffer } })
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const result = await convertFileToUploadedFile(file)
|
||||
|
||||
expect(mockFileReader.readAsArrayBuffer).toHaveBeenCalledWith(file)
|
||||
expect(mockHandler.processImageFile).not.toHaveBeenCalled()
|
||||
expect(result).toEqual({
|
||||
filename: 'test.pdf',
|
||||
filesize: file.size, // Use actual file size
|
||||
data: expectedData,
|
||||
type: Mojom.UploadedFileType.kPdf
|
||||
})
|
||||
})
|
||||
|
||||
it('throws ImageProcessingError for empty files', async () => {
|
||||
const file = createMockFile('empty.png', 'image/png', 0)
|
||||
const mockArrayBuffer = new ArrayBuffer(0)
|
||||
@@ -258,6 +282,30 @@ describe('convertFileToUploadedFile', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('throws UnsupportedFileTypeError for unknown file types', async () => {
|
||||
const file = createMockFile('test.txt', 'text/plain')
|
||||
const mockArrayBuffer = new ArrayBuffer(8)
|
||||
|
||||
// Override readAsArrayBuffer to trigger success
|
||||
// (FileReader will work, but file type check will fail)
|
||||
mockFileReader.readAsArrayBuffer.mockImplementation(() => {
|
||||
process.nextTick(() => {
|
||||
if (mockFileReader.onload) {
|
||||
mockFileReader.onload({ target: { result: mockArrayBuffer } })
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
await expect(convertFileToUploadedFile(file)).rejects.toThrow(
|
||||
UnsupportedFileTypeError
|
||||
)
|
||||
await expect(convertFileToUploadedFile(file)).rejects.toThrow(
|
||||
'Unsupported file type: text/plain. Only images and PDF files are ' +
|
||||
'supported.'
|
||||
)
|
||||
expect(mockHandler.processImageFile).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('data transformation', () => {
|
||||
|
||||
@@ -21,12 +21,17 @@ export class ImageProcessingError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export class UnsupportedFileTypeError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message)
|
||||
this.name = 'UnsupportedFileTypeError'
|
||||
}
|
||||
}
|
||||
|
||||
// Utility function to convert File objects to UploadedFile format
|
||||
export const convertFileToUploadedFile = async (
|
||||
file: File
|
||||
): Promise<Mojom.UploadedFile> => {
|
||||
// Use backend processing for images via mojo call
|
||||
const reader = new FileReader()
|
||||
const arrayBuffer = await new Promise<ArrayBuffer>((resolve, reject) => {
|
||||
reader.onload = (e) => {
|
||||
@@ -44,26 +49,45 @@ export const convertFileToUploadedFile = async (
|
||||
|
||||
const uint8Array = new Uint8Array(arrayBuffer)
|
||||
|
||||
try {
|
||||
const api = getAPI()
|
||||
const response = await api.uiHandler.processImageFile(
|
||||
Array.from(uint8Array),
|
||||
file.name
|
||||
)
|
||||
|
||||
if (!response.processedFile) {
|
||||
throw new ImageProcessingError(
|
||||
'Failed to process image file: Backend returned no result'
|
||||
)
|
||||
// Check file type and handle accordingly
|
||||
const mimeType = file.type.toLowerCase()
|
||||
if (mimeType === 'application/pdf') {
|
||||
// Handle PDF files directly
|
||||
const uploadedFile: Mojom.UploadedFile = {
|
||||
filename: file.name,
|
||||
filesize: file.size,
|
||||
data: Array.from(uint8Array),
|
||||
type: Mojom.UploadedFileType.kPdf
|
||||
}
|
||||
return uploadedFile
|
||||
} else if (mimeType.startsWith('image/')) {
|
||||
// Use backend processing for images via mojo call
|
||||
try {
|
||||
const api = getAPI()
|
||||
const response = await api.uiHandler.processImageFile(
|
||||
Array.from(uint8Array),
|
||||
file.name
|
||||
)
|
||||
|
||||
return response.processedFile
|
||||
} catch (error) {
|
||||
if (error instanceof ImageProcessingError) {
|
||||
if (!response.processedFile) {
|
||||
throw new ImageProcessingError(
|
||||
'Failed to process image file: Backend returned no result'
|
||||
)
|
||||
}
|
||||
|
||||
return response.processedFile
|
||||
} catch (error) {
|
||||
if (error instanceof ImageProcessingError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
// Re-throw any other errors as-is
|
||||
throw error
|
||||
}
|
||||
|
||||
// Re-throw any other errors as-is
|
||||
throw error
|
||||
} else {
|
||||
throw new UnsupportedFileTypeError(
|
||||
`Unsupported file type: ${file.type}. Only images and PDF files ` +
|
||||
`are supported.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user