190 lines
6.4 KiB
TypeScript
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,
|
|
};
|
|
} |