@greatapps/common 1.1.529 → 1.1.530
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -42,27 +42,28 @@ function useImageUpload(options) {
|
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
44
|
currentFileName.current = file.name;
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
setShowTooSmallModal(true);
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
img.src = base64;
|
|
45
|
+
const objectUrl = URL.createObjectURL(file);
|
|
46
|
+
setOriginalImage(objectUrl);
|
|
47
|
+
const img = new Image();
|
|
48
|
+
img.onload = () => {
|
|
49
|
+
const { naturalWidth, naturalHeight } = img;
|
|
50
|
+
if (checkMinimumDimensions(naturalWidth, naturalHeight)) {
|
|
51
|
+
setShowCropModal(true);
|
|
52
|
+
} else {
|
|
53
|
+
setTooSmallError({
|
|
54
|
+
fileName: currentFileName.current,
|
|
55
|
+
minWidth: config.width,
|
|
56
|
+
minHeight: config.height
|
|
57
|
+
});
|
|
58
|
+
setShowTooSmallModal(true);
|
|
59
|
+
}
|
|
64
60
|
};
|
|
65
|
-
|
|
61
|
+
img.onerror = () => {
|
|
62
|
+
URL.revokeObjectURL(objectUrl);
|
|
63
|
+
setOriginalImage(null);
|
|
64
|
+
onErrorRef.current?.("Erro ao carregar a imagem. Tente uma imagem menor ou em outro formato.");
|
|
65
|
+
};
|
|
66
|
+
img.src = objectUrl;
|
|
66
67
|
},
|
|
67
68
|
[config, checkMinimumDimensions]
|
|
68
69
|
);
|
|
@@ -80,8 +81,9 @@ function useImageUpload(options) {
|
|
|
80
81
|
setShowTooSmallModal(false);
|
|
81
82
|
setShowCropModal(false);
|
|
82
83
|
setTooSmallError(null);
|
|
84
|
+
if (originalImage) URL.revokeObjectURL(originalImage);
|
|
83
85
|
setOriginalImage(null);
|
|
84
|
-
}, []);
|
|
86
|
+
}, [originalImage]);
|
|
85
87
|
const handleCropConfirm = useCallback(
|
|
86
88
|
async (imageElement, cropArea) => {
|
|
87
89
|
setIsProcessing(true);
|
|
@@ -94,6 +96,8 @@ function useImageUpload(options) {
|
|
|
94
96
|
);
|
|
95
97
|
setCroppedPreview(croppedBase64);
|
|
96
98
|
setShowCropModal(false);
|
|
99
|
+
if (originalImage) URL.revokeObjectURL(originalImage);
|
|
100
|
+
setOriginalImage(null);
|
|
97
101
|
onSuccessRef.current?.(croppedBase64);
|
|
98
102
|
} catch {
|
|
99
103
|
onErrorRef.current?.("Erro ao aplicar crop");
|
|
@@ -104,9 +108,10 @@ function useImageUpload(options) {
|
|
|
104
108
|
[config]
|
|
105
109
|
);
|
|
106
110
|
const clear = useCallback(() => {
|
|
111
|
+
if (originalImage) URL.revokeObjectURL(originalImage);
|
|
107
112
|
setOriginalImage(null);
|
|
108
113
|
setCroppedPreview(null);
|
|
109
|
-
}, []);
|
|
114
|
+
}, [originalImage]);
|
|
110
115
|
return {
|
|
111
116
|
originalImage,
|
|
112
117
|
croppedPreview,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/modules/images/hooks/use-image-upload.hook.ts"],"sourcesContent":["'use client'\n\nimport { useState, useCallback, useRef, useEffect } from 'react'\nimport type { Crop } from 'react-image-crop'\nimport { cropImageToCanvas } from '../utils/crop-image'\nimport { validateImage } from '../utils/validate-image'\nimport type { ImageConfig } from '../types/image.type'\n\nexport interface ImageTooSmallError {\n fileName: string\n minWidth: number\n minHeight: number\n}\n\nexport interface UseImageUploadOptions {\n config: ImageConfig\n onSuccess?: (base64Image: string) => void\n onError?: (error: string) => void\n}\n\nexport function useImageUpload(options: UseImageUploadOptions) {\n const { config, onSuccess, onError } = options\n\n // Refs para manter callbacks sempre atualizados (evita stale closures)\n const onSuccessRef = useRef(onSuccess)\n const onErrorRef = useRef(onError)\n useEffect(() => {\n onSuccessRef.current = onSuccess\n onErrorRef.current = onError\n }, [onSuccess, onError])\n\n const [originalImage, setOriginalImage] = useState<string | null>(null)\n const [croppedPreview, setCroppedPreview] = useState<string | null>(null)\n const [showCropModal, setShowCropModal] = useState(false)\n const [showTooSmallModal, setShowTooSmallModal] = useState(false)\n const [tooSmallError, setTooSmallError] = useState<ImageTooSmallError | null>(null)\n const [isProcessing, setIsProcessing] = useState(false)\n const fileInputRef = useRef<HTMLInputElement>(null)\n const currentFileName = useRef<string>('')\n\n // Verifica se a imagem cabe o crop mínimo\n const checkMinimumDimensions = useCallback(\n (naturalWidth: number, naturalHeight: number): boolean => {\n const { width: minW, height: minH, aspectRatio } = config\n const imgAspect = naturalWidth / naturalHeight\n\n if (imgAspect >= aspectRatio) {\n const cropHeight = naturalHeight\n const cropWidth = cropHeight * aspectRatio\n return cropWidth >= minW && cropHeight >= minH\n } else {\n const cropWidth = naturalWidth\n const cropHeight = cropWidth / aspectRatio\n return cropWidth >= minW && cropHeight >= minH\n }\n },\n [config]\n )\n\n // Valida dimensões ANTES de abrir qualquer modal\n const handleFileSelect = useCallback(\n (file: File) => {\n const validation = validateImage(file)\n if (!validation.valid) {\n onErrorRef.current?.(validation.error || 'Arquivo inválido')\n return\n }\n\n currentFileName.current = file.name\n\n //
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/images/hooks/use-image-upload.hook.ts"],"sourcesContent":["'use client'\n\nimport { useState, useCallback, useRef, useEffect } from 'react'\nimport type { Crop } from 'react-image-crop'\nimport { cropImageToCanvas } from '../utils/crop-image'\nimport { validateImage } from '../utils/validate-image'\nimport type { ImageConfig } from '../types/image.type'\n\nexport interface ImageTooSmallError {\n fileName: string\n minWidth: number\n minHeight: number\n}\n\nexport interface UseImageUploadOptions {\n config: ImageConfig\n onSuccess?: (base64Image: string) => void\n onError?: (error: string) => void\n}\n\nexport function useImageUpload(options: UseImageUploadOptions) {\n const { config, onSuccess, onError } = options\n\n // Refs para manter callbacks sempre atualizados (evita stale closures)\n const onSuccessRef = useRef(onSuccess)\n const onErrorRef = useRef(onError)\n useEffect(() => {\n onSuccessRef.current = onSuccess\n onErrorRef.current = onError\n }, [onSuccess, onError])\n\n const [originalImage, setOriginalImage] = useState<string | null>(null)\n const [croppedPreview, setCroppedPreview] = useState<string | null>(null)\n const [showCropModal, setShowCropModal] = useState(false)\n const [showTooSmallModal, setShowTooSmallModal] = useState(false)\n const [tooSmallError, setTooSmallError] = useState<ImageTooSmallError | null>(null)\n const [isProcessing, setIsProcessing] = useState(false)\n const fileInputRef = useRef<HTMLInputElement>(null)\n const currentFileName = useRef<string>('')\n\n // Verifica se a imagem cabe o crop mínimo\n const checkMinimumDimensions = useCallback(\n (naturalWidth: number, naturalHeight: number): boolean => {\n const { width: minW, height: minH, aspectRatio } = config\n const imgAspect = naturalWidth / naturalHeight\n\n if (imgAspect >= aspectRatio) {\n const cropHeight = naturalHeight\n const cropWidth = cropHeight * aspectRatio\n return cropWidth >= minW && cropHeight >= minH\n } else {\n const cropWidth = naturalWidth\n const cropHeight = cropWidth / aspectRatio\n return cropWidth >= minW && cropHeight >= minH\n }\n },\n [config]\n )\n\n // Valida dimensões ANTES de abrir qualquer modal\n const handleFileSelect = useCallback(\n (file: File) => {\n const validation = validateImage(file)\n if (!validation.valid) {\n onErrorRef.current?.(validation.error || 'Arquivo inválido')\n return\n }\n\n currentFileName.current = file.name\n\n // Usa object URL em vez de base64 — mais eficiente para imagens grandes\n const objectUrl = URL.createObjectURL(file)\n setOriginalImage(objectUrl)\n\n // Carrega imagem em memória para verificar dimensões ANTES de abrir modal\n const img = new Image()\n img.onload = () => {\n const { naturalWidth, naturalHeight } = img\n\n if (checkMinimumDimensions(naturalWidth, naturalHeight)) {\n setShowCropModal(true)\n } else {\n setTooSmallError({\n fileName: currentFileName.current,\n minWidth: config.width,\n minHeight: config.height,\n })\n setShowTooSmallModal(true)\n }\n }\n img.onerror = () => {\n URL.revokeObjectURL(objectUrl)\n setOriginalImage(null)\n onErrorRef.current?.('Erro ao carregar a imagem. Tente uma imagem menor ou em outro formato.')\n }\n img.src = objectUrl\n },\n [config, checkMinimumDimensions]\n )\n\n // Mantido para compatibilidade, mas não mais necessário para validação\n const handleImageLoadInModal = useCallback(\n (_naturalWidth: number, _naturalHeight: number) => {\n // Validação agora é feita antes de abrir a modal\n // Este callback pode ser usado para outros propósitos se necessário\n },\n []\n )\n\n const handleSelectAnotherFile = useCallback(() => {\n setShowTooSmallModal(false)\n setTooSmallError(null)\n fileInputRef.current?.click()\n }, [])\n\n const handleCancelTooSmall = useCallback(() => {\n setShowTooSmallModal(false)\n setShowCropModal(false)\n setTooSmallError(null)\n if (originalImage) URL.revokeObjectURL(originalImage)\n setOriginalImage(null)\n }, [originalImage])\n\n const handleCropConfirm = useCallback(\n async (imageElement: HTMLImageElement, cropArea: Crop) => {\n setIsProcessing(true)\n try {\n // Crop é feito inteiramente no cliente via Canvas\n // Já produz imagem no tamanho final (config.width x config.height)\n const croppedBase64 = await cropImageToCanvas(\n imageElement,\n cropArea,\n config.width,\n config.height\n )\n\n setCroppedPreview(croppedBase64)\n setShowCropModal(false)\n if (originalImage) URL.revokeObjectURL(originalImage)\n setOriginalImage(null)\n onSuccessRef.current?.(croppedBase64)\n } catch {\n onErrorRef.current?.('Erro ao aplicar crop')\n } finally {\n setIsProcessing(false)\n }\n },\n [config]\n )\n\n const clear = useCallback(() => {\n if (originalImage) URL.revokeObjectURL(originalImage)\n setOriginalImage(null)\n setCroppedPreview(null)\n }, [originalImage])\n\n return {\n originalImage,\n croppedPreview,\n showCropModal,\n showTooSmallModal,\n tooSmallError,\n config,\n isProcessing,\n fileInputRef,\n handleFileSelect,\n handleCropConfirm,\n handleImageLoadInModal,\n handleSelectAnotherFile,\n handleCancelTooSmall,\n setShowCropModal,\n setShowTooSmallModal,\n clear,\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -68,33 +68,32 @@ export function useImageUpload(options: UseImageUploadOptions) {
|
|
|
68
68
|
|
|
69
69
|
currentFileName.current = file.name
|
|
70
70
|
|
|
71
|
-
//
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
minWidth: config.width,
|
|
90
|
-
minHeight: config.height,
|
|
91
|
-
})
|
|
92
|
-
setShowTooSmallModal(true)
|
|
93
|
-
}
|
|
71
|
+
// Usa object URL em vez de base64 — mais eficiente para imagens grandes
|
|
72
|
+
const objectUrl = URL.createObjectURL(file)
|
|
73
|
+
setOriginalImage(objectUrl)
|
|
74
|
+
|
|
75
|
+
// Carrega imagem em memória para verificar dimensões ANTES de abrir modal
|
|
76
|
+
const img = new Image()
|
|
77
|
+
img.onload = () => {
|
|
78
|
+
const { naturalWidth, naturalHeight } = img
|
|
79
|
+
|
|
80
|
+
if (checkMinimumDimensions(naturalWidth, naturalHeight)) {
|
|
81
|
+
setShowCropModal(true)
|
|
82
|
+
} else {
|
|
83
|
+
setTooSmallError({
|
|
84
|
+
fileName: currentFileName.current,
|
|
85
|
+
minWidth: config.width,
|
|
86
|
+
minHeight: config.height,
|
|
87
|
+
})
|
|
88
|
+
setShowTooSmallModal(true)
|
|
94
89
|
}
|
|
95
|
-
img.src = base64
|
|
96
90
|
}
|
|
97
|
-
|
|
91
|
+
img.onerror = () => {
|
|
92
|
+
URL.revokeObjectURL(objectUrl)
|
|
93
|
+
setOriginalImage(null)
|
|
94
|
+
onErrorRef.current?.('Erro ao carregar a imagem. Tente uma imagem menor ou em outro formato.')
|
|
95
|
+
}
|
|
96
|
+
img.src = objectUrl
|
|
98
97
|
},
|
|
99
98
|
[config, checkMinimumDimensions]
|
|
100
99
|
)
|
|
@@ -118,8 +117,9 @@ export function useImageUpload(options: UseImageUploadOptions) {
|
|
|
118
117
|
setShowTooSmallModal(false)
|
|
119
118
|
setShowCropModal(false)
|
|
120
119
|
setTooSmallError(null)
|
|
120
|
+
if (originalImage) URL.revokeObjectURL(originalImage)
|
|
121
121
|
setOriginalImage(null)
|
|
122
|
-
}, [])
|
|
122
|
+
}, [originalImage])
|
|
123
123
|
|
|
124
124
|
const handleCropConfirm = useCallback(
|
|
125
125
|
async (imageElement: HTMLImageElement, cropArea: Crop) => {
|
|
@@ -136,6 +136,8 @@ export function useImageUpload(options: UseImageUploadOptions) {
|
|
|
136
136
|
|
|
137
137
|
setCroppedPreview(croppedBase64)
|
|
138
138
|
setShowCropModal(false)
|
|
139
|
+
if (originalImage) URL.revokeObjectURL(originalImage)
|
|
140
|
+
setOriginalImage(null)
|
|
139
141
|
onSuccessRef.current?.(croppedBase64)
|
|
140
142
|
} catch {
|
|
141
143
|
onErrorRef.current?.('Erro ao aplicar crop')
|
|
@@ -147,9 +149,10 @@ export function useImageUpload(options: UseImageUploadOptions) {
|
|
|
147
149
|
)
|
|
148
150
|
|
|
149
151
|
const clear = useCallback(() => {
|
|
152
|
+
if (originalImage) URL.revokeObjectURL(originalImage)
|
|
150
153
|
setOriginalImage(null)
|
|
151
154
|
setCroppedPreview(null)
|
|
152
|
-
}, [])
|
|
155
|
+
}, [originalImage])
|
|
153
156
|
|
|
154
157
|
return {
|
|
155
158
|
originalImage,
|