From 7eeba5f17c343bb82c10e8c647a8dcbd7a2a0eab Mon Sep 17 00:00:00 2001 From: Kacper Date: Tue, 16 Dec 2025 02:49:26 +0100 Subject: [PATCH] feat: add image paste functionality to DescriptionImageDropZone component - Implemented handlePaste function to process images from clipboard across all OS. - Updated the component to handle pasted images and prevent default paste behavior. - Enhanced user instructions to include pasting images in the UI. Added a utility function to simulate pasting images in tests, ensuring cross-platform compatibility. --- .../ui/description-image-dropzone.tsx | 49 ++++++++++++++++++- apps/app/tests/utils/files/drag-drop.ts | 44 +++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/apps/app/src/components/ui/description-image-dropzone.tsx b/apps/app/src/components/ui/description-image-dropzone.tsx index df685082..4d9df8a2 100644 --- a/apps/app/src/components/ui/description-image-dropzone.tsx +++ b/apps/app/src/components/ui/description-image-dropzone.tsx @@ -268,6 +268,52 @@ export function DescriptionImageDropZone({ [images, onImagesChange] ); + // Handle paste events to detect and process images from clipboard + // Works across all OS (Windows, Linux, macOS) + const handlePaste = useCallback( + (e: React.ClipboardEvent) => { + if (disabled || isProcessing) return; + + const clipboardItems = e.clipboardData?.items; + if (!clipboardItems) return; + + const imageFiles: File[] = []; + + // Iterate through clipboard items to find images + for (let i = 0; i < clipboardItems.length; i++) { + const item = clipboardItems[i]; + + // Check if the item is an image + if (item.type.startsWith("image/")) { + const file = item.getAsFile(); + if (file) { + // Generate a filename for pasted images since they don't have one + const extension = item.type.split("/")[1] || "png"; + const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); + const renamedFile = new File( + [file], + `pasted-image-${timestamp}.${extension}`, + { type: file.type } + ); + imageFiles.push(renamedFile); + } + } + } + + // If we found images, process them and prevent default paste behavior + if (imageFiles.length > 0) { + e.preventDefault(); + + // Create a FileList-like object from the array + const dataTransfer = new DataTransfer(); + imageFiles.forEach((file) => dataTransfer.items.add(file)); + processFiles(dataTransfer.files); + } + // If no images found, let the default paste behavior happen (paste text) + }, + [disabled, isProcessing, processFiles] + ); + return (
{/* Hidden file input */} @@ -313,6 +359,7 @@ export function DescriptionImageDropZone({ placeholder={placeholder} value={value} onChange={(e) => onChange(e.target.value)} + onPaste={handlePaste} disabled={disabled} autoFocus={autoFocus} aria-invalid={error} @@ -326,7 +373,7 @@ export function DescriptionImageDropZone({ {/* Hint text */}

- Drag and drop images here or{" "} + Paste, drag and drop images, or{" "}