[dyad] Improved download flexibility - wrote 1 file(s)
This commit is contained in:
@@ -135,7 +135,7 @@ export function ImageConverter() {
|
||||
setFilenames(newFilenames);
|
||||
};
|
||||
|
||||
const generateFinalFilename = (index: number) => {
|
||||
const generateFinalFilename = (index: number, withDimensions: boolean = false) => {
|
||||
const baseName = filenames[index] || "filename";
|
||||
let finalName = `${prefix}${baseName}${suffix}`;
|
||||
|
||||
@@ -144,52 +144,64 @@ export function ImageConverter() {
|
||||
finalName += `${counter}`;
|
||||
}
|
||||
|
||||
if (withDimensions && width && height) {
|
||||
finalName += `_${width}x${height}`;
|
||||
}
|
||||
|
||||
return finalName;
|
||||
};
|
||||
|
||||
const handleConvertAndDownload = async () => {
|
||||
if (images.length === 0 || !width || !height) {
|
||||
toast.error("Please upload images and set dimensions.");
|
||||
const convertAndDownload = (image: File, previewUrl: string, index: number) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.crossOrigin = "anonymous";
|
||||
img.src = previewUrl;
|
||||
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement("canvas");
|
||||
const targetWidth = width ? Number(width) : img.naturalWidth;
|
||||
const targetHeight = height ? Number(height) : img.naturalHeight;
|
||||
|
||||
canvas.width = targetWidth;
|
||||
canvas.height = targetHeight;
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
if (ctx) {
|
||||
ctx.drawImage(img, 0, 0, targetWidth, targetHeight);
|
||||
const mimeType = `image/${format}`;
|
||||
const dataUrl = canvas.toDataURL(mimeType, format === 'png' ? undefined : quality / 100);
|
||||
const link = document.createElement("a");
|
||||
link.href = dataUrl;
|
||||
|
||||
const dimensionSuffix = width && height ? `_${width}x${height}` : '';
|
||||
link.download = `${generateFinalFilename(index)}${dimensionSuffix}.${format}`;
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`Could not process ${image.name}.`));
|
||||
}
|
||||
};
|
||||
img.onerror = () => {
|
||||
reject(new Error(`Failed to load ${image.name} for conversion.`));
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const handleConvertAndDownloadAll = async () => {
|
||||
if (images.length === 0) {
|
||||
toast.error("Please upload images first.");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsConverting(true);
|
||||
toast.info(`Starting conversion for ${images.length} images...`);
|
||||
|
||||
const conversionPromises = images.map((image, index) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const previewUrl = previewUrls[index];
|
||||
const img = new Image();
|
||||
img.crossOrigin = "anonymous";
|
||||
img.src = previewUrl;
|
||||
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = Number(width);
|
||||
canvas.height = Number(height);
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
if (ctx) {
|
||||
ctx.drawImage(img, 0, 0, Number(width), Number(height));
|
||||
const mimeType = `image/${format}`;
|
||||
const dataUrl = canvas.toDataURL(mimeType, format === 'png' ? undefined : quality / 100);
|
||||
const link = document.createElement("a");
|
||||
link.href = dataUrl;
|
||||
const finalFilename = generateFinalFilename(index);
|
||||
link.download = `${finalFilename}_${width}x${height}.${format}`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`Could not process ${image.name}.`));
|
||||
}
|
||||
};
|
||||
img.onerror = () => {
|
||||
reject(new Error(`Failed to load ${image.name} for conversion.`));
|
||||
};
|
||||
});
|
||||
});
|
||||
const conversionPromises = images.map((image, index) =>
|
||||
convertAndDownload(image, previewUrls[index], index)
|
||||
);
|
||||
|
||||
try {
|
||||
await Promise.all(conversionPromises);
|
||||
@@ -206,50 +218,11 @@ export function ImageConverter() {
|
||||
};
|
||||
|
||||
const handleConvertAndDownloadSingle = async (index: number) => {
|
||||
if (!width || !height) {
|
||||
toast.error("Please set dimensions before downloading.");
|
||||
return;
|
||||
}
|
||||
|
||||
setConvertingIndex(index);
|
||||
toast.info(`Starting conversion for ${filenames[index]}...`);
|
||||
|
||||
const image = images[index];
|
||||
const previewUrl = previewUrls[index];
|
||||
|
||||
try {
|
||||
const img = new Image();
|
||||
img.crossOrigin = "anonymous";
|
||||
img.src = previewUrl;
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = Number(width);
|
||||
canvas.height = Number(height);
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
if (ctx) {
|
||||
ctx.drawImage(img, 0, 0, Number(width), Number(height));
|
||||
const mimeType = `image/${format}`;
|
||||
const dataUrl = canvas.toDataURL(mimeType, format === 'png' ? undefined : quality / 100);
|
||||
const link = document.createElement("a");
|
||||
link.href = dataUrl;
|
||||
const finalFilename = generateFinalFilename(index);
|
||||
link.download = `${finalFilename}_${width}x${height}.${format}`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`Could not process ${image.name}.`));
|
||||
}
|
||||
};
|
||||
img.onerror = () => {
|
||||
reject(new Error(`Failed to load ${image.name} for conversion.`));
|
||||
};
|
||||
});
|
||||
|
||||
await convertAndDownload(images[index], previewUrls[index], index);
|
||||
toast.success(`Successfully exported ${filenames[index]}!`);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
@@ -281,11 +254,11 @@ export function ImageConverter() {
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="width">Width (px)</Label>
|
||||
<Input id="width" type="number" placeholder="e.g., 1920" value={width} onChange={(e) => setWidth(e.target.value)} />
|
||||
<Input id="width" type="number" placeholder="Original" value={width} onChange={(e) => setWidth(e.target.value)} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="height">Height (px)</Label>
|
||||
<Input id="height" type="number" placeholder="e.g., 1080" value={height} onChange={(e) => setHeight(e.target.value)} />
|
||||
<Input id="height" type="number" placeholder="Original" value={height} onChange={(e) => setHeight(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
@@ -382,14 +355,6 @@ export function ImageConverter() {
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<Button
|
||||
onClick={handleConvertAndDownload}
|
||||
disabled={!hasImages || !width || !height || isConverting || convertingIndex !== null}
|
||||
className="w-full"
|
||||
>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
{isConverting ? "Converting..." : `Apply Settings & Download All (${images.length})`}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="lg:col-span-2 flex flex-col gap-8">
|
||||
@@ -418,7 +383,7 @@ export function ImageConverter() {
|
||||
<CardTitle>Uploaded Images</CardTitle>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="ghost" size="sm" onClick={handleClearAll} disabled={isConverting || convertingIndex !== null}><Trash2 className="mr-2 h-4 w-4" />Clear All</Button>
|
||||
<Button onClick={handleConvertAndDownload} disabled={!hasImages || !width || !height || isConverting || convertingIndex !== null}>
|
||||
<Button onClick={handleConvertAndDownloadAll} disabled={!hasImages || isConverting || convertingIndex !== null}>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
{isConverting ? "Converting..." : `Download All (${images.length})`}
|
||||
</Button>
|
||||
@@ -428,45 +393,48 @@ export function ImageConverter() {
|
||||
<CardContent>
|
||||
<ScrollArea className="h-[400px] pr-4">
|
||||
<div className="space-y-4">
|
||||
{previewUrls.map((url, index) => (
|
||||
<div key={url} className="p-4 border rounded-lg flex items-center gap-4">
|
||||
<img src={url} alt={`Preview ${index + 1}`} className="w-20 h-20 object-cover rounded-md shrink-0" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<Label htmlFor={`filename-${index}`} className="text-xs text-muted-foreground">Base Name</Label>
|
||||
<Input
|
||||
id={`filename-${index}`}
|
||||
value={filenames[index]}
|
||||
onChange={(e) => handleFilenameChange(index, e.target.value)}
|
||||
className="text-sm font-medium h-8 mt-1"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground truncate mt-1" title={`${generateFinalFilename(index)}_${width || 'w'}x${height || 'h'}.${format}`}>
|
||||
Final name: {generateFinalFilename(index)}.{format}
|
||||
</p>
|
||||
{previewUrls.map((url, index) => {
|
||||
const finalFilename = generateFinalFilename(index, true);
|
||||
return (
|
||||
<div key={url} className="p-4 border rounded-lg flex items-center gap-4">
|
||||
<img src={url} alt={`Preview ${index + 1}`} className="w-20 h-20 object-cover rounded-md shrink-0" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<Label htmlFor={`filename-${index}`} className="text-xs text-muted-foreground">Base Name</Label>
|
||||
<Input
|
||||
id={`filename-${index}`}
|
||||
value={filenames[index]}
|
||||
onChange={(e) => handleFilenameChange(index, e.target.value)}
|
||||
className="text-sm font-medium h-8 mt-1"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground truncate mt-1" title={`${finalFilename}.${format}`}>
|
||||
Final name: {finalFilename}.{format}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center shrink-0">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-gray-500 hover:text-primary"
|
||||
onClick={() => handleConvertAndDownloadSingle(index)}
|
||||
disabled={isConverting || convertingIndex !== null}
|
||||
title="Download this image"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="shrink-0 text-gray-500 hover:text-destructive"
|
||||
onClick={() => handleRemoveImage(index)}
|
||||
disabled={isConverting || convertingIndex !== null}
|
||||
title="Remove this image"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center shrink-0">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-gray-500 hover:text-primary"
|
||||
onClick={() => handleConvertAndDownloadSingle(index)}
|
||||
disabled={isConverting || convertingIndex !== null || !width || !height}
|
||||
title="Download this image"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="shrink-0 text-gray-500 hover:text-destructive"
|
||||
onClick={() => handleRemoveImage(index)}
|
||||
disabled={isConverting || convertingIndex !== null}
|
||||
title="Remove this image"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</CardContent>
|
||||
|
||||
Reference in New Issue
Block a user