mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +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();
|
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 textFile = new File(['text'], 'test.txt', { type: 'text/plain' });
|
||||||
const jsonFile = new File(['{}'], 'test.json', { type: 'application/json' });
|
const jsonFile = new File(['{}'], 'test.json', { type: 'application/json' });
|
||||||
const pdfFile = new File(['pdf'], 'test.pdf', { type: 'application/pdf' });
|
const pdfFile = new File(['pdf'], 'test.pdf', { type: 'application/pdf' });
|
||||||
|
|
@ -272,12 +272,15 @@ describe('SessionView Drag & Drop and Paste', () => {
|
||||||
|
|
||||||
element.dispatchEvent(dropEvent);
|
element.dispatchEvent(dropEvent);
|
||||||
|
|
||||||
// Wait for async operations
|
// Wait for all async operations to complete
|
||||||
await vi.waitFor(() => {
|
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', () => {
|
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.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.isDragOver = false;
|
this.isDragOver = false;
|
||||||
|
|
@ -821,12 +821,19 @@ export class SessionView extends LitElement {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload the first file (or we could upload all of them)
|
// Upload all files sequentially
|
||||||
this.uploadFile(files[0]);
|
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
|
// Paste handler
|
||||||
private handlePaste(e: ClipboardEvent) {
|
private async handlePaste(e: ClipboardEvent) {
|
||||||
// Only handle paste if session view is focused and no modal is open
|
// Only handle paste if session view is focused and no modal is open
|
||||||
if (this.showFileBrowser || this.showImagePicker || this.showMobileInput) {
|
if (this.showFileBrowser || this.showImagePicker || this.showMobileInput) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -841,12 +848,17 @@ export class SessionView extends LitElement {
|
||||||
|
|
||||||
e.preventDefault(); // Prevent default paste behavior for files
|
e.preventDefault(); // Prevent default paste behavior for files
|
||||||
|
|
||||||
const fileItem = fileItems[0];
|
// Upload all pasted files
|
||||||
const file = fileItem.getAsFile();
|
for (const fileItem of fileItems) {
|
||||||
|
const file = fileItem.getAsFile();
|
||||||
if (file) {
|
if (file) {
|
||||||
logger.log('File pasted from clipboard');
|
try {
|
||||||
this.uploadFile(file);
|
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