global refactoring
This commit is contained in:
87
src/hooks/useImageUpload.js
Normal file
87
src/hooks/useImageUpload.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import { useState, useRef } from "react";
|
||||
import { compressImage } from "../utils/compressImage";
|
||||
|
||||
const MAX_SIZE_MB = 5;
|
||||
export const ACCEPT = "image/jpeg,image/png,image/webp";
|
||||
|
||||
/**
|
||||
* Управляет выбором, сжатием и удалением фотографии в форме объявления.
|
||||
*
|
||||
* @param {string|null} existingUrl — URL существующей картинки с сервера
|
||||
* (передаётся при редактировании)
|
||||
* @returns {{
|
||||
* imageFile: File|null,
|
||||
* displayImage: string|null,
|
||||
* imgError: string,
|
||||
* compressing: boolean,
|
||||
* fileInputRef: React.RefObject,
|
||||
* handleFileChange: function,
|
||||
* removeImage: function,
|
||||
* }}
|
||||
*/
|
||||
export function useImageUpload(existingUrl = null) {
|
||||
// Сжатый файл, готовый к отправке в FormData
|
||||
const [imageFile, setImageFile] = useState(null);
|
||||
// ObjectURL для предпросмотра нового файла
|
||||
const [preview, setPreview] = useState(null);
|
||||
// true — пользователь явно удалил фото, existingUrl больше не показываем
|
||||
const [removed, setRemoved] = useState(false);
|
||||
const [imgError, setImgError] = useState("");
|
||||
// true пока идёт сжатие — нужен чтобы заблокировать сабмит
|
||||
const [compressing, setCompressing] = useState(false);
|
||||
// ref на <input type="file"> — нужен для сброса значения после removeImage
|
||||
const fileInputRef = useRef(null);
|
||||
|
||||
// Что показывать в блоке превью:
|
||||
// новый preview имеет приоритет, иначе existingUrl (если не удалён)
|
||||
const displayImage = preview || (!removed ? existingUrl : null);
|
||||
|
||||
/** Проверяет файл, сжимает его и создаёт превью. */
|
||||
const handleFileChange = async (e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
setImgError("");
|
||||
|
||||
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 compressed = await compressImage(file);
|
||||
setImageFile(compressed);
|
||||
setPreview(URL.createObjectURL(compressed));
|
||||
setRemoved(false);
|
||||
} catch {
|
||||
setImgError("Не удалось обработать изображение");
|
||||
} finally {
|
||||
setCompressing(false);
|
||||
}
|
||||
};
|
||||
|
||||
/** Удаляет фото, освобождает ObjectURL и сбрасывает input. */
|
||||
const removeImage = () => {
|
||||
setImageFile(null);
|
||||
if (preview) URL.revokeObjectURL(preview); // предотвращаем утечку памяти
|
||||
setPreview(null);
|
||||
setRemoved(true);
|
||||
setImgError("");
|
||||
// Сброс нужен, иначе повторный выбор того же файла не вызовет onChange
|
||||
if (fileInputRef.current) fileInputRef.current.value = "";
|
||||
};
|
||||
|
||||
return {
|
||||
imageFile,
|
||||
displayImage,
|
||||
imgError,
|
||||
compressing,
|
||||
fileInputRef,
|
||||
handleFileChange,
|
||||
removeImage,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user