@greatapps/common 1.1.529 → 1.1.531
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,31 @@ 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
|
-
|
|
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
|
+
onErrorRef.current?.(`Imagem muito pequena. Tamanho m\xEDnimo: ${config.width}x${config.height}px.`);
|
|
60
|
+
URL.revokeObjectURL(objectUrl);
|
|
61
|
+
setOriginalImage(null);
|
|
62
|
+
}
|
|
64
63
|
};
|
|
65
|
-
|
|
64
|
+
img.onerror = () => {
|
|
65
|
+
URL.revokeObjectURL(objectUrl);
|
|
66
|
+
setOriginalImage(null);
|
|
67
|
+
onErrorRef.current?.("Erro ao carregar a imagem. Tente uma imagem menor ou em outro formato.");
|
|
68
|
+
};
|
|
69
|
+
img.src = objectUrl;
|
|
66
70
|
},
|
|
67
71
|
[config, checkMinimumDimensions]
|
|
68
72
|
);
|
|
@@ -80,8 +84,9 @@ function useImageUpload(options) {
|
|
|
80
84
|
setShowTooSmallModal(false);
|
|
81
85
|
setShowCropModal(false);
|
|
82
86
|
setTooSmallError(null);
|
|
87
|
+
if (originalImage) URL.revokeObjectURL(originalImage);
|
|
83
88
|
setOriginalImage(null);
|
|
84
|
-
}, []);
|
|
89
|
+
}, [originalImage]);
|
|
85
90
|
const handleCropConfirm = useCallback(
|
|
86
91
|
async (imageElement, cropArea) => {
|
|
87
92
|
setIsProcessing(true);
|
|
@@ -94,6 +99,8 @@ function useImageUpload(options) {
|
|
|
94
99
|
);
|
|
95
100
|
setCroppedPreview(croppedBase64);
|
|
96
101
|
setShowCropModal(false);
|
|
102
|
+
if (originalImage) URL.revokeObjectURL(originalImage);
|
|
103
|
+
setOriginalImage(null);
|
|
97
104
|
onSuccessRef.current?.(croppedBase64);
|
|
98
105
|
} catch {
|
|
99
106
|
onErrorRef.current?.("Erro ao aplicar crop");
|
|
@@ -104,9 +111,10 @@ function useImageUpload(options) {
|
|
|
104
111
|
[config]
|
|
105
112
|
);
|
|
106
113
|
const clear = useCallback(() => {
|
|
114
|
+
if (originalImage) URL.revokeObjectURL(originalImage);
|
|
107
115
|
setOriginalImage(null);
|
|
108
116
|
setCroppedPreview(null);
|
|
109
|
-
}, []);
|
|
117
|
+
}, [originalImage]);
|
|
110
118
|
return {
|
|
111
119
|
originalImage,
|
|
112
120
|
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 onErrorRef.current?.(`Imagem muito pequena. Tamanho mínimo: ${config.width}x${config.height}px.`)\n URL.revokeObjectURL(objectUrl)\n setOriginalImage(null)\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,35 @@ 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
|
-
|
|
90
|
-
|
|
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)
|
|
89
|
+
onErrorRef.current?.(`Imagem muito pequena. Tamanho mínimo: ${config.width}x${config.height}px.`)
|
|
90
|
+
URL.revokeObjectURL(objectUrl)
|
|
91
|
+
setOriginalImage(null)
|
|
94
92
|
}
|
|
95
|
-
img.src = base64
|
|
96
93
|
}
|
|
97
|
-
|
|
94
|
+
img.onerror = () => {
|
|
95
|
+
URL.revokeObjectURL(objectUrl)
|
|
96
|
+
setOriginalImage(null)
|
|
97
|
+
onErrorRef.current?.('Erro ao carregar a imagem. Tente uma imagem menor ou em outro formato.')
|
|
98
|
+
}
|
|
99
|
+
img.src = objectUrl
|
|
98
100
|
},
|
|
99
101
|
[config, checkMinimumDimensions]
|
|
100
102
|
)
|
|
@@ -118,8 +120,9 @@ export function useImageUpload(options: UseImageUploadOptions) {
|
|
|
118
120
|
setShowTooSmallModal(false)
|
|
119
121
|
setShowCropModal(false)
|
|
120
122
|
setTooSmallError(null)
|
|
123
|
+
if (originalImage) URL.revokeObjectURL(originalImage)
|
|
121
124
|
setOriginalImage(null)
|
|
122
|
-
}, [])
|
|
125
|
+
}, [originalImage])
|
|
123
126
|
|
|
124
127
|
const handleCropConfirm = useCallback(
|
|
125
128
|
async (imageElement: HTMLImageElement, cropArea: Crop) => {
|
|
@@ -136,6 +139,8 @@ export function useImageUpload(options: UseImageUploadOptions) {
|
|
|
136
139
|
|
|
137
140
|
setCroppedPreview(croppedBase64)
|
|
138
141
|
setShowCropModal(false)
|
|
142
|
+
if (originalImage) URL.revokeObjectURL(originalImage)
|
|
143
|
+
setOriginalImage(null)
|
|
139
144
|
onSuccessRef.current?.(croppedBase64)
|
|
140
145
|
} catch {
|
|
141
146
|
onErrorRef.current?.('Erro ao aplicar crop')
|
|
@@ -147,9 +152,10 @@ export function useImageUpload(options: UseImageUploadOptions) {
|
|
|
147
152
|
)
|
|
148
153
|
|
|
149
154
|
const clear = useCallback(() => {
|
|
155
|
+
if (originalImage) URL.revokeObjectURL(originalImage)
|
|
150
156
|
setOriginalImage(null)
|
|
151
157
|
setCroppedPreview(null)
|
|
152
|
-
}, [])
|
|
158
|
+
}, [originalImage])
|
|
153
159
|
|
|
154
160
|
return {
|
|
155
161
|
originalImage,
|