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