107 lines
3.0 KiB
JavaScript
107 lines
3.0 KiB
JavaScript
import { useState, useRef, useEffect, useCallback } from "react";
|
||
import { compressImage } from "../utils/compressImage";
|
||
import { API_URL } from "../config/authConstants";
|
||
|
||
const MAX_SIZE_MB = 5;
|
||
export const ACCEPT = "image/jpeg,image/png,image/webp";
|
||
|
||
let idCounter = 0;
|
||
const nextId = () => `img-${++idCounter}-${Date.now()}`;
|
||
|
||
export function useImageUpload(existingUrls = []) {
|
||
const [images, setImages] = useState([]);
|
||
const [imgError, setImgError] = useState("");
|
||
const [compressing, setCompressing] = useState(false);
|
||
const fileInputRef = useRef(null);
|
||
|
||
const existingUrlList = Array.isArray(existingUrls) ? existingUrls : [];
|
||
|
||
useEffect(() => {
|
||
if (existingUrlList.length > 0) {
|
||
setImages(existingUrlList.map((url, i) => ({
|
||
id: `existing-${i}`,
|
||
file: null,
|
||
preview: `${API_URL}${url}`,
|
||
isExisting: true,
|
||
serverUrl: url,
|
||
})));
|
||
}
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, []);
|
||
|
||
const handleFileChange = useCallback(async (e) => {
|
||
const files = Array.from(e.target.files || []);
|
||
if (!files.length) return;
|
||
setImgError("");
|
||
|
||
for (const file of files) {
|
||
if (!file.type.startsWith("image/")) {
|
||
setImgError("Только изображения (JPEG, PNG, WebP)");
|
||
return;
|
||
}
|
||
if (file.size > MAX_SIZE_MB * 1024 * 1024) {
|
||
setImgError(`Файл слишком большой — максимум ${MAX_SIZE_MB} МБ`);
|
||
return;
|
||
}
|
||
}
|
||
|
||
setCompressing(true);
|
||
try {
|
||
const compressedFiles = await Promise.all(files.map(f => compressImage(f)));
|
||
const newImages = compressedFiles.map(file => ({
|
||
id: nextId(),
|
||
file,
|
||
preview: URL.createObjectURL(file),
|
||
isExisting: false,
|
||
serverUrl: null,
|
||
}));
|
||
setImages(prev => [...prev, ...newImages]);
|
||
} catch {
|
||
setImgError("Не удалось обработать изображение");
|
||
} finally {
|
||
setCompressing(false);
|
||
if (fileInputRef.current) fileInputRef.current.value = "";
|
||
}
|
||
}, []);
|
||
|
||
const removeImage = useCallback((id) => {
|
||
setImages(prev => {
|
||
const img = prev.find(i => i.id === id);
|
||
if (img && !img.isExisting && img.preview) {
|
||
URL.revokeObjectURL(img.preview);
|
||
}
|
||
return prev.filter(i => i.id !== id);
|
||
});
|
||
}, []);
|
||
|
||
const getFilesForUpload = useCallback(() => {
|
||
return images.filter(i => !i.isExisting).map(i => i.file);
|
||
}, [images]);
|
||
|
||
const getExistingUrlsToKeep = useCallback(() => {
|
||
return images.filter(i => i.isExisting).map(i => i.serverUrl);
|
||
}, [images]);
|
||
|
||
useEffect(() => {
|
||
return () => {
|
||
images.forEach(img => {
|
||
if (!img.isExisting && img.preview) {
|
||
URL.revokeObjectURL(img.preview);
|
||
}
|
||
});
|
||
};
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, []);
|
||
|
||
return {
|
||
images,
|
||
imgError,
|
||
compressing,
|
||
fileInputRef,
|
||
handleFileChange,
|
||
removeImage,
|
||
getFilesForUpload,
|
||
getExistingUrlsToKeep,
|
||
};
|
||
}
|