feat: support multiple file uploads via drag & drop and paste (#166)

This commit is contained in:
Peter Steinberger 2025-07-01 07:07:33 +01:00 committed by GitHub
parent 28913cd490
commit 6c73decf84
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 73 additions and 14 deletions

View file

@ -227,7 +227,7 @@ describe('SessionView Drag & Drop and Paste', () => {
expect(mockFilePicker.uploadFile).not.toHaveBeenCalled();
});
it('should handle multiple files and pick the first one', async () => {
it('should handle multiple files and upload all of them', async () => {
const textFile = new File(['text'], 'test.txt', { type: 'text/plain' });
const jsonFile = new File(['{}'], 'test.json', { type: 'application/json' });
const pdfFile = new File(['pdf'], 'test.pdf', { type: 'application/pdf' });
@ -272,12 +272,15 @@ describe('SessionView Drag & Drop and Paste', () => {
element.dispatchEvent(dropEvent);
// Wait for async operations
// Wait for all async operations to complete
await vi.waitFor(() => {
expect(mockFilePicker.uploadFile).toHaveBeenCalledWith(textFile);
expect(mockFilePicker.uploadFile).toHaveBeenCalledTimes(3);
});
expect(mockFilePicker.uploadFile).toHaveBeenCalledTimes(1);
// Verify all files were uploaded in order
expect(mockFilePicker.uploadFile).toHaveBeenNthCalledWith(1, textFile);
expect(mockFilePicker.uploadFile).toHaveBeenNthCalledWith(2, jsonFile);
expect(mockFilePicker.uploadFile).toHaveBeenNthCalledWith(3, pdfFile);
});
});
@ -416,6 +419,50 @@ describe('SessionView Drag & Drop and Paste', () => {
);
});
});
it('should handle multiple files pasted from clipboard', async () => {
const textFile = new File(['text content'], 'text.txt', { type: 'text/plain' });
const imageFile = new File(['image content'], 'image.png', { type: 'image/png' });
const mockClipboardItems = [
{
kind: 'file',
type: 'text/plain',
getAsFile: () => textFile,
},
{
kind: 'file',
type: 'image/png',
getAsFile: () => imageFile,
},
];
const pasteEvent = new ClipboardEvent('paste', {
bubbles: true,
clipboardData: new DataTransfer(),
});
Object.defineProperty(pasteEvent.clipboardData, 'items', {
value: mockClipboardItems,
writable: false,
});
const mockFilePicker = {
uploadFile: vi.fn().mockResolvedValue(undefined),
};
element.querySelector = vi.fn(() => mockFilePicker);
document.dispatchEvent(pasteEvent);
// Wait for all uploads to complete
await vi.waitFor(() => {
expect(mockFilePicker.uploadFile).toHaveBeenCalledTimes(2);
});
// Verify both files were uploaded
expect(mockFilePicker.uploadFile).toHaveBeenNthCalledWith(1, textFile);
expect(mockFilePicker.uploadFile).toHaveBeenNthCalledWith(2, imageFile);
});
});
describe('Event Listener Management', () => {

View file

@ -809,7 +809,7 @@ export class SessionView extends LitElement {
}
}
private handleDrop(e: DragEvent) {
private async handleDrop(e: DragEvent) {
e.preventDefault();
e.stopPropagation();
this.isDragOver = false;
@ -821,12 +821,19 @@ export class SessionView extends LitElement {
return;
}
// Upload the first file (or we could upload all of them)
this.uploadFile(files[0]);
// Upload all files sequentially
for (const file of files) {
try {
await this.uploadFile(file);
logger.log(`Successfully uploaded file: ${file.name}`);
} catch (error) {
logger.error(`Failed to upload file: ${file.name}`, error);
}
}
}
// Paste handler
private handlePaste(e: ClipboardEvent) {
private async handlePaste(e: ClipboardEvent) {
// Only handle paste if session view is focused and no modal is open
if (this.showFileBrowser || this.showImagePicker || this.showMobileInput) {
return;
@ -841,12 +848,17 @@ export class SessionView extends LitElement {
e.preventDefault(); // Prevent default paste behavior for files
const fileItem = fileItems[0];
const file = fileItem.getAsFile();
if (file) {
logger.log('File pasted from clipboard');
this.uploadFile(file);
// Upload all pasted files
for (const fileItem of fileItems) {
const file = fileItem.getAsFile();
if (file) {
try {
await this.uploadFile(file);
logger.log(`Successfully pasted and uploaded file: ${file.name}`);
} catch (error) {
logger.error(`Failed to upload pasted file: ${file?.name}`, error);
}
}
}
}