mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 21:03:08 +00:00
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.
This commit is contained in:
@@ -268,6 +268,52 @@ export function DescriptionImageDropZone({
|
|||||||
[images, onImagesChange]
|
[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 (
|
return (
|
||||||
<div className={cn("relative", className)}>
|
<div className={cn("relative", className)}>
|
||||||
{/* Hidden file input */}
|
{/* Hidden file input */}
|
||||||
@@ -313,6 +359,7 @@ export function DescriptionImageDropZone({
|
|||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => onChange(e.target.value)}
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
onPaste={handlePaste}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
aria-invalid={error}
|
aria-invalid={error}
|
||||||
@@ -326,7 +373,7 @@ export function DescriptionImageDropZone({
|
|||||||
|
|
||||||
{/* Hint text */}
|
{/* Hint text */}
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
Drag and drop images here or{" "}
|
Paste, drag and drop images, or{" "}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleBrowseClick}
|
onClick={handleBrowseClick}
|
||||||
|
|||||||
@@ -36,3 +36,47 @@ export async function simulateFileDrop(
|
|||||||
{ selector: targetSelector, content: fileContent, name: fileName, mime: mimeType }
|
{ selector: targetSelector, content: fileContent, name: fileName, mime: mimeType }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simulate pasting an image from clipboard onto an element
|
||||||
|
* Works across all OS (Windows, Linux, macOS)
|
||||||
|
*/
|
||||||
|
export async function simulateImagePaste(
|
||||||
|
page: Page,
|
||||||
|
targetSelector: string,
|
||||||
|
imageBase64: string,
|
||||||
|
mimeType: string = "image/png"
|
||||||
|
): Promise<void> {
|
||||||
|
await page.evaluate(
|
||||||
|
({ selector, base64, mime }) => {
|
||||||
|
const target = document.querySelector(selector);
|
||||||
|
if (!target) throw new Error(`Element not found: ${selector}`);
|
||||||
|
|
||||||
|
// Convert base64 to Blob
|
||||||
|
const byteCharacters = atob(base64);
|
||||||
|
const byteNumbers = new Array(byteCharacters.length);
|
||||||
|
for (let i = 0; i < byteCharacters.length; i++) {
|
||||||
|
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
||||||
|
}
|
||||||
|
const byteArray = new Uint8Array(byteNumbers);
|
||||||
|
const blob = new Blob([byteArray], { type: mime });
|
||||||
|
|
||||||
|
// Create a File from Blob
|
||||||
|
const file = new File([blob], "pasted-image.png", { type: mime });
|
||||||
|
|
||||||
|
// Create a DataTransfer with clipboard items
|
||||||
|
const dataTransfer = new DataTransfer();
|
||||||
|
dataTransfer.items.add(file);
|
||||||
|
|
||||||
|
// Create ClipboardEvent with the image data
|
||||||
|
const clipboardEvent = new ClipboardEvent("paste", {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
clipboardData: dataTransfer,
|
||||||
|
});
|
||||||
|
|
||||||
|
target.dispatchEvent(clipboardEvent);
|
||||||
|
},
|
||||||
|
{ selector: targetSelector, base64: imageBase64, mime: mimeType }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user