diff --git a/src/components/filename-settings.tsx b/src/components/filename-settings.tsx deleted file mode 100644 index 488a845..0000000 --- a/src/components/filename-settings.tsx +++ /dev/null @@ -1,231 +0,0 @@ -"use client"; - -import { useTranslations } from "next-intl"; -import { - AccordionContent, - AccordionItem, - AccordionTrigger, -} from "@/components/ui/accordion"; -import { Label } from "@/components/ui/label"; -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { HelpCircle } from "lucide-react"; -import { Switch } from "@/components/ui/switch"; -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; - -interface FilenameSettingsProps { - useDefaultBaseName: boolean; - onUseDefaultBaseNameChange: (checked: boolean) => void; - defaultBaseName: string; - onDefaultBaseNameChange: (e: React.ChangeEvent) => void; - onApplyDefaultBaseNameToAll: () => void; - hasImages: boolean; - prefix: string; - onPrefixChange: (e: React.ChangeEvent) => void; - suffix: string; - onSuffixChange: (e: React.ChangeEvent) => void; - useCounter: boolean; - onUseCounterChange: (checked: boolean) => void; - counterStart: number; - onCounterStartChange: (e: React.ChangeEvent) => void; - counterDigits: number; - onCounterDigitsChange: (e: React.ChangeEvent) => void; -} - -export function FilenameSettings({ - useDefaultBaseName, - onUseDefaultBaseNameChange, - defaultBaseName, - onDefaultBaseNameChange, - onApplyDefaultBaseNameToAll, - hasImages, - prefix, - onPrefixChange, - suffix, - onSuffixChange, - useCounter, - onUseCounterChange, - counterStart, - onCounterStartChange, - counterDigits, - onCounterDigitsChange, -}: FilenameSettingsProps) { - const t = useTranslations("ImageConverter"); - - return ( - - -
-

- {t("filenameSettingsTitle")} -

-

- {t("filenameSettingsSubtitle")} -

-
-
- -
-
- - -
- {useDefaultBaseName && ( -
- -
- - - - - - -

{t("applyToAllTooltip")}

-
-
-
-
- )} -
-
- - - - - - -

{t("prefixTooltip")}

-
-
-
- -
-
-
- - - - - - -

{t("suffixTooltip")}

-
-
-
- -
-
- - -
- {useCounter && ( -
-
-
- - - - - - -

{t("counterStartTooltip")}

-
-
-
- -
-
-
- - - - - - -

{t("counterDigitsTooltip")}

-
-
-
- -
-
- )} -
-
-
- ); -} \ No newline at end of file diff --git a/src/components/image-converter.tsx b/src/components/image-converter.tsx index 008a5c5..85f0c3a 100644 --- a/src/components/image-converter.tsx +++ b/src/components/image-converter.tsx @@ -1,12 +1,42 @@ "use client"; -import { useState, useEffect, ChangeEvent } from "react"; +import { useState, useRef, ChangeEvent, useEffect } from "react"; +import { + Card, + CardContent, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Button } from "@/components/ui/button"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Upload, Download, X, Trash2, Check, ArrowRightLeft, HelpCircle, RotateCcw } from "lucide-react"; import { toast } from "sonner"; -import { TooltipProvider } from "@/components/ui/tooltip"; +import { cn } from "@/lib/utils"; +import { Switch } from "@/components/ui/switch"; +import { Checkbox } from "@/components/ui/checkbox"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import { Slider } from "@/components/ui/slider"; +import { ObjectPositionControl } from "./object-position-control"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; import { useTranslations } from "next-intl"; -import { ImageUpload } from "./image-upload"; -import { UploadedImageList } from "./uploaded-image-list"; -import { SettingsPanel } from "./settings-panel"; const initialSettings = { width: "", @@ -29,6 +59,14 @@ const initialSettings = { export function ImageConverter() { const t = useTranslations("ImageConverter"); + const aspectRatios = [ + { name: t("aspectRatios.custom"), value: "custom" }, + { name: t("aspectRatios.square"), value: "1/1" }, + { name: t("aspectRatios.standard"), value: "4/3" }, + { name: t("aspectRatios.photography"), value: "3/2" }, + { name: t("aspectRatios.widescreen"), value: "16/9" }, + ]; + const [images, setImages] = useState([]); const [previewUrls, setPreviewUrls] = useState([]); const [filenames, setFilenames] = useState([]); @@ -38,6 +76,7 @@ export function ImageConverter() { const [keepOrientation, setKeepOrientation] = useState(initialSettings.keepOrientation); const [format, setFormat] = useState<"png" | "jpeg" | "webp">(initialSettings.format); const [quality, setQuality] = useState(initialSettings.quality); + const [prefix, setPrefix] = useState(initialSettings.prefix); const [suffix, setSuffix] = useState(initialSettings.suffix); const [useCounter, setUseCounter] = useState(initialSettings.useCounter); @@ -45,11 +84,14 @@ export function ImageConverter() { const [counterDigits, setCounterDigits] = useState(initialSettings.counterDigits); const [useDefaultBaseName, setUseDefaultBaseName] = useState(initialSettings.useDefaultBaseName); const [defaultBaseName, setDefaultBaseName] = useState(initialSettings.defaultBaseName); + const [scaleMode, setScaleMode] = useState<'fill' | 'cover' | 'contain'>(initialSettings.scaleMode); const [objectPosition, setObjectPosition] = useState(initialSettings.objectPosition); + const [isConverting, setIsConverting] = useState(false); const [convertingIndex, setConvertingIndex] = useState(null); const [isDraggingOver, setIsDraggingOver] = useState(false); + const fileInputRef = useRef(null); useEffect(() => { return () => { @@ -90,6 +132,11 @@ export function ImageConverter() { toast.success(t("toasts.imagesAdded", { count: imageFiles.length })); }; + const handleImageChange = (e: ChangeEvent) => { + handleFiles(e.target.files); + if (e.target) e.target.value = ""; + }; + const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); setIsDraggingOver(true); @@ -108,9 +155,13 @@ export function ImageConverter() { const handleRemoveImage = (indexToRemove: number) => { URL.revokeObjectURL(previewUrls[indexToRemove]); - setImages((prev) => prev.filter((_, i) => i !== indexToRemove)); - setPreviewUrls((prev) => prev.filter((_, i) => i !== indexToRemove)); - setFilenames((prev) => prev.filter((_, i) => i !== indexToRemove)); + const newImages = images.filter((_, i) => i !== indexToRemove); + const newPreviewUrls = previewUrls.filter((_, i) => i !== indexToRemove); + const newFilenames = filenames.filter((_, i) => i !== indexToRemove); + + setImages(newImages); + setPreviewUrls(newPreviewUrls); + setFilenames(newFilenames); }; const handleClearAll = () => { @@ -124,11 +175,9 @@ export function ImageConverter() { }; const handleFilenameChange = (index: number, newName: string) => { - setFilenames((prev) => { - const newFilenames = [...prev]; - newFilenames[index] = newName; - return newFilenames; - }); + const newFilenames = [...filenames]; + newFilenames[index] = newName; + setFilenames(newFilenames); }; const generateFinalFilename = (index: number) => { @@ -258,13 +307,19 @@ export function ImageConverter() { setIsConverting(true); toast.info(t("toasts.conversionStarting", { count: images.length })); + const conversionPromises = images.map((image, index) => + convertAndDownload(image, previewUrls[index], index) + ); + try { - await Promise.all(images.map((image, index) => - convertAndDownload(image, previewUrls[index], index) - )); + await Promise.all(conversionPromises); toast.success(t("toasts.conversionSuccess", { count: images.length })); } catch (error) { - toast.error(error instanceof Error ? error.message : t("toasts.conversionError")); + if (error instanceof Error) { + toast.error(error.message); + } else { + toast.error(t("toasts.conversionError")); + } } finally { setIsConverting(false); } @@ -278,7 +333,11 @@ export function ImageConverter() { await convertAndDownload(images[index], previewUrls[index], index); toast.success(t("toasts.singleConversionSuccess", { filename: filenames[index] })); } catch (error) { - toast.error(error instanceof Error ? error.message : t("toasts.conversionError")); + if (error instanceof Error) { + toast.error(error.message); + } else { + toast.error(t("toasts.conversionError")); + } } finally { setConvertingIndex(null); } @@ -309,21 +368,36 @@ export function ImageConverter() { const handleAspectRatioChange = (value: string) => { setAspectRatio(value); - if (value === "custom") return; + + if (value === "custom") { + return; + } const [w, h] = value.split("/").map(Number); - const newWidth = w > h ? 1000 : Math.round((1000 * w) / h); - const newHeight = h > w ? 1000 : Math.round((1000 * h) / w); - setWidth(w === h ? 1000 : newWidth); - setHeight(w === h ? 1000 : newHeight); + let newWidth: number; + let newHeight: number; + + if (w > h) { + newWidth = 1000; + newHeight = Math.round((1000 * h) / w); + } else if (h > w) { + newHeight = 1000; + newWidth = Math.round((1000 * w) / h); + } else { + newWidth = 1000; + newHeight = 1000; + } + + setWidth(newWidth); + setHeight(newHeight); }; - const handleWidthChange = (e: ChangeEvent) => { + const handleWidthChange = (e: React.ChangeEvent) => { setWidth(e.target.value); setAspectRatio("custom"); }; - const handleHeightChange = (e: ChangeEvent) => { + const handleHeightChange = (e: React.ChangeEvent) => { setHeight(e.target.value); setAspectRatio("custom"); }; @@ -333,16 +407,19 @@ export function ImageConverter() { setHeight(width); }; + const hasImages = images.length > 0; + const handleApplyDefaultBaseNameToAll = () => { if (!defaultBaseName) { toast.error(t("toasts.defaultBaseNameMissing")); return; } - if (images.length === 0) { + if (!hasImages) { toast.info(t("toasts.uploadFirst")); return; } - setFilenames(filenames.map(() => defaultBaseName)); + const newFilenames = filenames.map(() => defaultBaseName); + setFilenames(newFilenames); toast.success(t("toasts.baseNameApplied", { name: defaultBaseName, count: images.length })); }; @@ -350,65 +427,484 @@ export function ImageConverter() {
- - + + +
+

{t("uploadTitle")}

+
fileInputRef.current?.click()} + > +
+ +

{t("uploadPrompt")}

+

{t("uploadHint")}

+
+ +
+
+
+
+ + {hasImages && ( + + +
+ {t("uploadedImagesTitle")} +
+ + + + + +

{t("clearAllTooltip")}

+
+
+ + + + + +

{t("downloadAllTooltip")}

+
+
+
+
+
+ +
+ {previewUrls.map((url, index) => { + const finalFilename = generateFinalFilename(index); + return ( +
+ {`Preview +
+ + handleFilenameChange(index, e.target.value)} + className="text-sm font-medium h-8 mt-1" + /> +

+ {t("finalNameLabel", { filename: `${finalFilename}.${format}` })} +

+
+
+ + + + + +

{t("downloadSingleTooltip")}

+
+
+ + + + + +

{t("removeSingleTooltip")}

+
+
+
+
+ ); + })} +
+
+
+ )}
- setDefaultBaseName(e.target.value)} - onApplyDefaultBaseNameToAll={handleApplyDefaultBaseNameToAll} - hasImages={images.length > 0} - prefix={prefix} - onPrefixChange={(e) => setPrefix(e.target.value)} - suffix={suffix} - onSuffixChange={(e) => setSuffix(e.target.value)} - useCounter={useCounter} - onUseCounterChange={setUseCounter} - counterStart={counterStart} - onCounterStartChange={(e) => setCounterStart(Math.max(0, Number(e.target.value)))} - counterDigits={counterDigits} - onCounterDigitsChange={(e) => setCounterDigits(Math.max(1, Number(e.target.value)))} - format={format} - onFormatChange={setFormat} - quality={quality} - onQualityChange={setQuality} - onResetSettings={handleResetSettings} - onApplySettings={handleApplySettings} - /> +
+ + + +
+

{t("imageSettingsTitle")}

+

+ {t("imageSettingsSubtitle")} +

+
+
+ +
+
+
+ + + + + + +

{t("aspectRatioTooltip")}

+
+
+
+ +
+
+
+
+ + + + + + +

{t("widthTooltip")}

+
+
+
+ +
+ + + + + +

{t("swapDimensionsTooltip")}

+
+
+
+
+ + + + + + +

{t("heightTooltip")}

+
+
+
+ +
+
+
+ setKeepOrientation(Boolean(checked))} /> + +
+
+
+
+ + + + + + +

{t("scalingTooltip")}

+
+
+
+ +
+ {scaleMode !== 'fill' && ( +
+
+ + + + + + +

{t("positionTooltip")}

+
+
+
+ setObjectPosition(pos)} /> +
+ )} +
+
+ + + +
+

{t("filenameSettingsTitle")}

+

{t("filenameSettingsSubtitle")}

+
+
+ +
+
+ + +
+ {useDefaultBaseName && ( +
+ +
+ setDefaultBaseName(e.target.value)} + /> + + + + + +

{t("applyToAllTooltip")}

+
+
+
+
+ )} +
+
+ + + + + + +

{t("prefixTooltip")}

+
+
+
+ setPrefix(e.target.value)} /> +
+
+
+ + + + + + +

{t("suffixTooltip")}

+
+
+
+ setSuffix(e.target.value)} /> +
+
+ + +
+ {useCounter && ( +
+
+
+ + + + + + +

{t("counterStartTooltip")}

+
+
+
+ setCounterStart(Math.max(0, Number(e.target.value)))} + min="0" + /> +
+
+
+ + + + + + +

{t("counterDigitsTooltip")}

+
+
+
+ setCounterDigits(Math.max(1, Number(e.target.value)))} + min="1" + /> +
+
+ )} +
+
+
+ + + +
+

{t("qualitySettingsTitle")}

+

{t("qualitySettingsSubtitle")}

+
+
+ +
+
+
+ + + + + + +

{t("formatTooltip")}

+
+
+
+ +
+
+
+
+ + + + + + +

{t("qualityTooltip")}

+
+
+
+ {quality}% +
+ setQuality(value[0])} + disabled={format === 'png'} + /> + {format === 'png' && ( +

{t("qualityDisabledHint")}

+ )} +
+
+
+
+
+
+ + + + + +

{t("resetButtonTooltip")}

+
+
+ + + + + +

{t("applyButtonTooltip")}

+
+
+
+
); diff --git a/src/components/image-settings.tsx b/src/components/image-settings.tsx deleted file mode 100644 index 7eaa131..0000000 --- a/src/components/image-settings.tsx +++ /dev/null @@ -1,233 +0,0 @@ -"use client"; - -import { useTranslations } from "next-intl"; -import { - AccordionContent, - AccordionItem, - AccordionTrigger, -} from "@/components/ui/accordion"; -import { Label } from "@/components/ui/label"; -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { HelpCircle, ArrowRightLeft } from "lucide-react"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; -import { Checkbox } from "@/components/ui/checkbox"; -import { ObjectPositionControl } from "./object-position-control"; - -interface ImageSettingsProps { - aspectRatio: string; - onAspectRatioChange: (value: string) => void; - width: number | string; - onWidthChange: (e: React.ChangeEvent) => void; - onSwapDimensions: () => void; - height: number | string; - onHeightChange: (e: React.ChangeEvent) => void; - keepOrientation: boolean; - onKeepOrientationChange: (checked: boolean) => void; - scaleMode: 'fill' | 'cover' | 'contain'; - onScaleModeChange: (value: 'fill' | 'cover' | 'contain') => void; - objectPosition: string; - onObjectPositionChange: (pos: any) => void; -} - -export function ImageSettings({ - aspectRatio, - onAspectRatioChange, - width, - onWidthChange, - onSwapDimensions, - height, - onHeightChange, - keepOrientation, - onKeepOrientationChange, - scaleMode, - onScaleModeChange, - objectPosition, - onObjectPositionChange, -}: ImageSettingsProps) { - const t = useTranslations("ImageConverter"); - - const aspectRatios = [ - { name: t("aspectRatios.custom"), value: "custom" }, - { name: t("aspectRatios.square"), value: "1/1" }, - { name: t("aspectRatios.standard"), value: "4/3" }, - { name: t("aspectRatios.photography"), value: "3/2" }, - { name: t("aspectRatios.widescreen"), value: "16/9" }, - ]; - - return ( - - -
-

- {t("imageSettingsTitle")} -

-

- {t("imageSettingsSubtitle")} -

-
-
- -
-
-
- - - - - - -

{t("aspectRatioTooltip")}

-
-
-
- -
-
-
-
- - - - - - -

{t("widthTooltip")}

-
-
-
- -
- - - - - -

{t("swapDimensionsTooltip")}

-
-
-
-
- - - - - - -

{t("heightTooltip")}

-
-
-
- -
-
-
- onKeepOrientationChange(Boolean(checked))} - /> - -
-
-
-
- - - - - - -

{t("scalingTooltip")}

-
-
-
- -
- {scaleMode !== "fill" && ( -
-
- - - - - - -

{t("positionTooltip")}

-
-
-
- -
- )} -
-
- ); -} \ No newline at end of file diff --git a/src/components/image-upload.tsx b/src/components/image-upload.tsx deleted file mode 100644 index f6ca428..0000000 --- a/src/components/image-upload.tsx +++ /dev/null @@ -1,66 +0,0 @@ -"use client"; - -import { useRef, ChangeEvent } from "react"; -import { useTranslations } from "next-intl"; -import { Upload } from "lucide-react"; -import { cn } from "@/lib/utils"; -import { Input } from "@/components/ui/input"; -import { Card, CardContent } from "@/components/ui/card"; - -interface ImageUploadProps { - onFiles: (files: FileList | null) => void; - isDraggingOver: boolean; - onDragOver: (e: React.DragEvent) => void; - onDragLeave: (e: React.DragEvent) => void; - onDrop: (e: React.DragEvent) => void; -} - -export function ImageUpload({ - onFiles, - isDraggingOver, - onDragOver, - onDragLeave, - onDrop, -}: ImageUploadProps) { - const t = useTranslations("ImageConverter"); - const fileInputRef = useRef(null); - - const handleImageChange = (e: ChangeEvent) => { - onFiles(e.target.files); - if (e.target) e.target.value = ""; - }; - - return ( - - -
-

{t("uploadTitle")}

-
fileInputRef.current?.click()} - > -
- -

{t("uploadPrompt")}

-

{t("uploadHint")}

-
- -
-
-
-
- ); -} \ No newline at end of file diff --git a/src/components/quality-settings.tsx b/src/components/quality-settings.tsx deleted file mode 100644 index 29c9acc..0000000 --- a/src/components/quality-settings.tsx +++ /dev/null @@ -1,113 +0,0 @@ -"use client"; - -import { useTranslations } from "next-intl"; -import { - AccordionContent, - AccordionItem, - AccordionTrigger, -} from "@/components/ui/accordion"; -import { Label } from "@/components/ui/label"; -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { HelpCircle } from "lucide-react"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Slider } from "@/components/ui/slider"; - -type Format = "png" | "jpeg" | "webp"; - -interface QualitySettingsProps { - format: Format; - onFormatChange: (value: Format) => void; - quality: number; - onQualityChange: (value: number) => void; -} - -export function QualitySettings({ - format, - onFormatChange, - quality, - onQualityChange, -}: QualitySettingsProps) { - const t = useTranslations("ImageConverter"); - - return ( - - -
-

- {t("qualitySettingsTitle")} -

-

- {t("qualitySettingsSubtitle")} -

-
-
- -
-
-
- - - - - - -

{t("formatTooltip")}

-
-
-
- -
-
-
-
- - - - - - -

{t("qualityTooltip")}

-
-
-
- {quality}% -
- onQualityChange(value[0])} - disabled={format === "png"} - /> - {format === "png" && ( -

- {t("qualityDisabledHint")} -

- )} -
-
-
-
- ); -} \ No newline at end of file diff --git a/src/components/settings-panel.tsx b/src/components/settings-panel.tsx deleted file mode 100644 index 9383145..0000000 --- a/src/components/settings-panel.tsx +++ /dev/null @@ -1,135 +0,0 @@ -"use client"; - -import { useTranslations } from "next-intl"; -import { Accordion } from "@/components/ui/accordion"; -import { Button } from "@/components/ui/button"; -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { RotateCcw, Check } from "lucide-react"; -import { ImageSettings } from "./image-settings"; -import { FilenameSettings } from "./filename-settings"; -import { QualitySettings } from "./quality-settings"; - -interface SettingsPanelProps { - aspectRatio: string; - onAspectRatioChange: (value: string) => void; - width: number | string; - onWidthChange: (e: React.ChangeEvent) => void; - onSwapDimensions: () => void; - height: number | string; - onHeightChange: (e: React.ChangeEvent) => void; - keepOrientation: boolean; - onKeepOrientationChange: (checked: boolean) => void; - scaleMode: 'fill' | 'cover' | 'contain'; - onScaleModeChange: (value: 'fill' | 'cover' | 'contain') => void; - objectPosition: string; - onObjectPositionChange: (pos: any) => void; - useDefaultBaseName: boolean; - onUseDefaultBaseNameChange: (checked: boolean) => void; - defaultBaseName: string; - onDefaultBaseNameChange: (e: React.ChangeEvent) => void; - onApplyDefaultBaseNameToAll: () => void; - hasImages: boolean; - prefix: string; - onPrefixChange: (e: React.ChangeEvent) => void; - suffix: string; - onSuffixChange: (e: React.ChangeEvent) => void; - useCounter: boolean; - onUseCounterChange: (checked: boolean) => void; - counterStart: number; - onCounterStartChange: (e: React.ChangeEvent) => void; - counterDigits: number; - onCounterDigitsChange: (e: React.ChangeEvent) => void; - format: "png" | "jpeg" | "webp"; - onFormatChange: (value: "png" | "jpeg" | "webp") => void; - quality: number; - onQualityChange: (value: number) => void; - onResetSettings: () => void; - onApplySettings: () => void; -} - -export function SettingsPanel(props: SettingsPanelProps) { - const t = useTranslations("ImageConverter"); - - return ( -
- - - - - -
- - - - - -

{t("resetButtonTooltip")}

-
-
- - - - - -

{t("applyButtonTooltip")}

-
-
-
-
- ); -} \ No newline at end of file diff --git a/src/components/uploaded-image-list.tsx b/src/components/uploaded-image-list.tsx deleted file mode 100644 index 9829c9b..0000000 --- a/src/components/uploaded-image-list.tsx +++ /dev/null @@ -1,175 +0,0 @@ -"use client"; - -import { useTranslations } from "next-intl"; -import { - Card, - CardContent, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { Download, Trash2, X } from "lucide-react"; - -interface UploadedImageListProps { - previewUrls: string[]; - filenames: string[]; - isConverting: boolean; - convertingIndex: number | null; - onClearAll: () => void; - onConvertAndDownloadAll: () => void; - onFilenameChange: (index: number, newName: string) => void; - generateFinalFilename: (index: number) => string; - format: string; - onConvertAndDownloadSingle: (index: number) => void; - onRemoveImage: (index: number) => void; -} - -export function UploadedImageList({ - previewUrls, - filenames, - isConverting, - convertingIndex, - onClearAll, - onConvertAndDownloadAll, - onFilenameChange, - generateFinalFilename, - format, - onConvertAndDownloadSingle, - onRemoveImage, -}: UploadedImageListProps) { - const t = useTranslations("ImageConverter"); - const hasImages = previewUrls.length > 0; - - if (!hasImages) { - return null; - } - - return ( - - - -
- {t("uploadedImagesTitle")} -
- - - - - -

{t("clearAllTooltip")}

-
-
- - - - - -

{t("downloadAllTooltip")}

-
-
-
-
-
- -
- {previewUrls.map((url, index) => { - const finalFilename = generateFinalFilename(index); - return ( -
- {`Preview -
- - onFilenameChange(index, e.target.value)} - className="text-sm font-medium h-8 mt-1" - /> -

- {t("finalNameLabel", { - filename: `${finalFilename}.${format}`, - })} -

-
-
- - - - - -

{t("downloadSingleTooltip")}

-
-
- - - - - -

{t("removeSingleTooltip")}

-
-
-
-
- ); - })} -
-
-
-
- ); -} \ No newline at end of file