Files
Webify/src/hooks/use-image-converter.ts

190 lines
6.4 KiB
TypeScript

import { useState, useEffect, useCallback } from "react";
import { toast } from "sonner";
import { ImageFile, ConversionSettings } from "@/types";
import { processImage, generateFinalFilename, downloadDataUrl } from "@/lib/image-processor";
export const initialSettings: ConversionSettings = {
width: "",
height: "",
aspectRatio: "custom",
keepOrientation: true,
format: "webp",
quality: 90,
prefix: "",
suffix: "",
useCounter: false,
counterStart: 1,
counterDigits: 3,
useDefaultBaseName: false,
defaultBaseName: "",
scaleMode: 'cover',
objectPosition: 'center center',
};
export function useImageConverter() {
const [images, setImages] = useState<ImageFile[]>([]);
const [settings, setSettings] = useState<ConversionSettings>(initialSettings);
const [isConverting, setIsConverting] = useState(false);
const [convertingIndex, setConvertingIndex] = useState<number | null>(null);
useEffect(() => {
const urls = images.map(img => img.previewUrl);
return () => {
urls.forEach(url => URL.revokeObjectURL(url));
};
}, [images]);
const updateSettings = useCallback((newSettings: Partial<ConversionSettings>) => {
setSettings(prev => ({ ...prev, ...newSettings }));
}, []);
const handleFiles = useCallback((files: FileList | null) => {
if (!files || files.length === 0) return;
const imageFiles = Array.from(files).filter(file => file.type.startsWith("image/"));
if (imageFiles.length === 0) {
toast.error("Keine gültigen Bilddateien gefunden.");
return;
}
const newImageFiles: ImageFile[] = imageFiles.map(file => ({
file,
previewUrl: URL.createObjectURL(file),
filename: settings.useDefaultBaseName && settings.defaultBaseName
? settings.defaultBaseName
: file.name.substring(0, file.name.lastIndexOf(".")),
}));
setImages(prev => [...prev, ...newImageFiles]);
toast.success(`${imageFiles.length} Bild(er) hinzugefügt.`);
}, [settings.useDefaultBaseName, settings.defaultBaseName]);
const handleRemoveImage = useCallback((indexToRemove: number) => {
setImages(prev => {
const imageToRemove = prev[indexToRemove];
if (imageToRemove) {
URL.revokeObjectURL(imageToRemove.previewUrl);
}
return prev.filter((_, i) => i !== indexToRemove);
});
}, []);
const handleClearAll = useCallback(() => {
setImages([]);
updateSettings({ width: initialSettings.width, height: initialSettings.height });
toast.info("Alle Bilder gelöscht.");
}, [updateSettings]);
const handleFilenameChange = useCallback((index: number, newName: string) => {
setImages(prev => {
const newImages = [...prev];
if (newImages[index]) {
newImages[index].filename = newName;
}
return newImages;
});
}, []);
const handleConvertAndDownloadSingle = useCallback(async (index: number) => {
setConvertingIndex(index);
toast.info(`Starte Konvertierung für ${images[index].filename}...`);
try {
const imageToConvert = images[index];
const dataUrl = await processImage(imageToConvert, settings);
const finalFilename = generateFinalFilename(imageToConvert.filename, settings, index);
downloadDataUrl(dataUrl, `${finalFilename}.${settings.format}`);
toast.success(`${imageToConvert.filename} erfolgreich exportiert!`);
} catch (error) {
const message = error instanceof Error ? error.message : "Ein unbekannter Fehler ist aufgetreten.";
toast.error(message);
} finally {
setConvertingIndex(null);
}
}, [images, settings]);
const handleConvertAndDownloadAll = useCallback(async () => {
if (images.length === 0) {
toast.error("Bitte laden Sie zuerst Bilder hoch.");
return;
}
setIsConverting(true);
toast.info(`Starte Konvertierung für ${images.length} Bilder...`);
const conversionPromises = images.map(async (image, index) => {
try {
const dataUrl = await processImage(image, settings);
const finalFilename = generateFinalFilename(image.filename, settings, index);
downloadDataUrl(dataUrl, `${finalFilename}.${settings.format}`);
} catch (error) {
const message = error instanceof Error ? error.message : `Verarbeitung von ${image.filename} fehlgeschlagen`;
toast.error(message);
throw error;
}
});
try {
await Promise.all(conversionPromises);
toast.success(`Alle ${images.length} Bilder erfolgreich exportiert!`);
} catch (error) {
toast.error("Einige Bilder konnten nicht konvertiert werden. Siehe einzelne Fehler.");
} finally {
setIsConverting(false);
}
}, [images, settings]);
const handleResetSettings = useCallback(() => {
setSettings(initialSettings);
toast.success("Alle Einstellungen wurden auf ihre Standardwerte zurückgesetzt.");
}, []);
const handleAspectRatioChange = useCallback((value: string) => {
updateSettings({ aspectRatio: value });
if (value === "custom") return;
const [w, h] = value.split("/").map(Number);
let newWidth: number, 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;
}
updateSettings({ width: newWidth, height: newHeight });
}, [updateSettings]);
const handleSwapDimensions = useCallback(() => {
updateSettings({ width: settings.height, height: settings.width });
}, [settings.height, settings.width, updateSettings]);
const handleApplyDefaultBaseNameToAll = useCallback(() => {
if (!settings.defaultBaseName) {
toast.error("Bitte geben Sie einen Standard-Basisnamen zum Anwenden ein.");
return;
}
if (images.length === 0) {
toast.info("Laden Sie zuerst einige Bilder hoch.");
return;
}
setImages(prev => prev.map(img => ({ ...img, filename: settings.defaultBaseName })));
toast.success(`Basisname für alle ${images.length} Bilder auf "${settings.defaultBaseName}" gesetzt.`);
}, [images.length, settings.defaultBaseName]);
return {
images,
settings,
isConverting,
convertingIndex,
updateSettings,
handleFiles,
handleRemoveImage,
handleClearAll,
handleFilenameChange,
handleConvertAndDownloadSingle,
handleConvertAndDownloadAll,
handleResetSettings,
handleAspectRatioChange,
handleSwapDimensions,
handleApplyDefaultBaseNameToAll,
};
}