mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-03-25 09:25:50 +00:00
feat: support multiple file uploads via drag & drop and paste (#166)
This commit is contained in:
parent
28913cd490
commit
6c73decf84
2 changed files with 73 additions and 14 deletions
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue