@greatapps/common 1.1.448 → 1.1.449
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.
- package/dist/components/modals/cards/AddCardModal.mjs +5 -2
- package/dist/components/modals/cards/AddCardModal.mjs.map +1 -1
- package/dist/components/widgets/ImageUpload/ImageCropModal.mjs +76 -0
- package/dist/components/widgets/ImageUpload/ImageCropModal.mjs.map +1 -0
- package/dist/components/widgets/ImageUpload/ImageUpload.mjs +121 -0
- package/dist/components/widgets/ImageUpload/ImageUpload.mjs.map +1 -0
- package/dist/components/widgets/ImageUpload/index.mjs +7 -0
- package/dist/components/widgets/ImageUpload/index.mjs.map +1 -0
- package/dist/index.mjs +29 -1
- package/dist/index.mjs.map +1 -1
- package/dist/infra/utils/date.mjs +11 -0
- package/dist/infra/utils/date.mjs.map +1 -1
- package/dist/modules/images/actions/process-image.action.mjs +31 -0
- package/dist/modules/images/actions/process-image.action.mjs.map +1 -0
- package/dist/modules/images/constants/image.constants.mjs +14 -0
- package/dist/modules/images/constants/image.constants.mjs.map +1 -0
- package/dist/modules/images/hooks/use-image-upload.hook.mjs +96 -0
- package/dist/modules/images/hooks/use-image-upload.hook.mjs.map +1 -0
- package/dist/modules/images/services/image-processing.service.mjs +70 -0
- package/dist/modules/images/services/image-processing.service.mjs.map +1 -0
- package/dist/modules/images/types/image.type.mjs +1 -0
- package/dist/modules/images/types/image.type.mjs.map +1 -0
- package/dist/modules/images/utils/compress-image.mjs +18 -0
- package/dist/modules/images/utils/compress-image.mjs.map +1 -0
- package/dist/modules/images/utils/crop-image.mjs +29 -0
- package/dist/modules/images/utils/crop-image.mjs.map +1 -0
- package/dist/modules/images/utils/validate-image.mjs +50 -0
- package/dist/modules/images/utils/validate-image.mjs.map +1 -0
- package/dist/server.mjs +4 -0
- package/dist/server.mjs.map +1 -1
- package/package.json +4 -1
- package/src/components/modals/cards/AddCardModal.tsx +2 -1
- package/src/components/widgets/ImageUpload/ImageCropModal.tsx +97 -0
- package/src/components/widgets/ImageUpload/ImageUpload.tsx +125 -0
- package/src/components/widgets/ImageUpload/index.ts +2 -0
- package/src/index.ts +24 -1
- package/src/infra/utils/date.ts +11 -0
- package/src/modules/images/actions/process-image.action.ts +38 -0
- package/src/modules/images/constants/image.constants.ts +9 -0
- package/src/modules/images/hooks/use-image-upload.hook.ts +111 -0
- package/src/modules/images/services/image-processing.service.ts +76 -0
- package/src/modules/images/types/image.type.ts +54 -0
- package/src/modules/images/utils/compress-image.ts +21 -0
- package/src/modules/images/utils/crop-image.ts +41 -0
- package/src/modules/images/utils/validate-image.ts +59 -0
- package/src/server.ts +10 -0
package/src/index.ts
CHANGED
|
@@ -107,7 +107,7 @@ export type {
|
|
|
107
107
|
|
|
108
108
|
// Utils
|
|
109
109
|
export { cn } from "./infra/utils/clsx";
|
|
110
|
-
export { formatShortDate } from "./infra/utils/date";
|
|
110
|
+
export { formatShortDate, formatDateTime } from "./infra/utils/date";
|
|
111
111
|
export { buildQueryParams } from "./infra/utils/params";
|
|
112
112
|
export { parseSchema, parseResult } from "./infra/utils/parser";
|
|
113
113
|
export { withAction } from "./utils/withAction";
|
|
@@ -396,3 +396,26 @@ export {
|
|
|
396
396
|
NOTIFICATION_TYPES,
|
|
397
397
|
LANGUAGE_OPTIONS,
|
|
398
398
|
} from "./components/account/constants";
|
|
399
|
+
|
|
400
|
+
// Image Upload
|
|
401
|
+
export { ImageUpload, ImageCropModal } from "./components/widgets/ImageUpload";
|
|
402
|
+
export { useImageUpload } from "./modules/images/hooks/use-image-upload.hook";
|
|
403
|
+
export {
|
|
404
|
+
ACCEPTED_IMAGE_FORMATS,
|
|
405
|
+
MAX_FILE_SIZE_MB,
|
|
406
|
+
TARGET_FILE_SIZE_MB,
|
|
407
|
+
} from "./modules/images/constants/image.constants";
|
|
408
|
+
export type {
|
|
409
|
+
ImageConfig,
|
|
410
|
+
CropArea,
|
|
411
|
+
ProcessedImage,
|
|
412
|
+
CompressImageOptions,
|
|
413
|
+
} from "./modules/images/types/image.type";
|
|
414
|
+
export { compressImage } from "./modules/images/utils/compress-image";
|
|
415
|
+
export { cropImageToCanvas } from "./modules/images/utils/crop-image";
|
|
416
|
+
export {
|
|
417
|
+
validateImage,
|
|
418
|
+
validateImageFormat,
|
|
419
|
+
validateImageSize,
|
|
420
|
+
getImageDimensions,
|
|
421
|
+
} from "./modules/images/utils/validate-image";
|
package/src/infra/utils/date.ts
CHANGED
|
@@ -5,3 +5,14 @@ export function formatShortDate(date: Date | string | null | undefined): string
|
|
|
5
5
|
const month = String(d.getMonth() + 1).padStart(2, '0');
|
|
6
6
|
return `${day}/${month}`;
|
|
7
7
|
}
|
|
8
|
+
|
|
9
|
+
export function formatDateTime(date: Date | string | null | undefined): string | null {
|
|
10
|
+
if (!date) return null;
|
|
11
|
+
return new Date(date).toLocaleString('pt-BR', {
|
|
12
|
+
day: '2-digit',
|
|
13
|
+
month: '2-digit',
|
|
14
|
+
year: 'numeric',
|
|
15
|
+
hour: '2-digit',
|
|
16
|
+
minute: '2-digit',
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use server'
|
|
2
|
+
|
|
3
|
+
import { processImage } from '../services/image-processing.service'
|
|
4
|
+
import type {
|
|
5
|
+
ProcessImageActionInput,
|
|
6
|
+
ProcessImageActionOutput,
|
|
7
|
+
} from '../types/image.type'
|
|
8
|
+
|
|
9
|
+
export async function processImageAction(
|
|
10
|
+
input: ProcessImageActionInput
|
|
11
|
+
): Promise<ProcessImageActionOutput> {
|
|
12
|
+
try {
|
|
13
|
+
const base64Data = input.base64Image.replace(/^data:image\/\w+;base64,/, '')
|
|
14
|
+
const binaryString = atob(base64Data)
|
|
15
|
+
const bytes = new Uint8Array(binaryString.length)
|
|
16
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
17
|
+
bytes[i] = binaryString.charCodeAt(i)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const result = processImage({
|
|
21
|
+
imageBytes: bytes,
|
|
22
|
+
targetWidth: input.targetWidth,
|
|
23
|
+
targetHeight: input.targetHeight,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const base64Output = btoa(String.fromCharCode(...result.bytes))
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
success: true,
|
|
30
|
+
base64Image: `data:${result.format};base64,${base64Output}`,
|
|
31
|
+
}
|
|
32
|
+
} catch (error) {
|
|
33
|
+
return {
|
|
34
|
+
success: false,
|
|
35
|
+
error: error instanceof Error ? error.message : 'Erro ao processar imagem',
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback } from 'react'
|
|
4
|
+
import { useMutation } from '@tanstack/react-query'
|
|
5
|
+
import type { Crop } from 'react-image-crop'
|
|
6
|
+
import { compressImage } from '../utils/compress-image'
|
|
7
|
+
import { cropImageToCanvas } from '../utils/crop-image'
|
|
8
|
+
import { validateImage } from '../utils/validate-image'
|
|
9
|
+
import { processImageAction } from '../actions/process-image.action'
|
|
10
|
+
import type { ImageConfig } from '../types/image.type'
|
|
11
|
+
|
|
12
|
+
export interface UseImageUploadOptions {
|
|
13
|
+
config: ImageConfig
|
|
14
|
+
onSuccess?: (base64Image: string) => void
|
|
15
|
+
onError?: (error: string) => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function useImageUpload(options: UseImageUploadOptions) {
|
|
19
|
+
const { config, onSuccess, onError } = options
|
|
20
|
+
|
|
21
|
+
const [originalImage, setOriginalImage] = useState<string | null>(null)
|
|
22
|
+
const [croppedPreview, setCroppedPreview] = useState<string | null>(null)
|
|
23
|
+
const [showCropModal, setShowCropModal] = useState(false)
|
|
24
|
+
|
|
25
|
+
const processMutation = useMutation({
|
|
26
|
+
mutationFn: processImageAction,
|
|
27
|
+
onSuccess: (result) => {
|
|
28
|
+
if (result.success && result.base64Image) {
|
|
29
|
+
setCroppedPreview(result.base64Image)
|
|
30
|
+
onSuccess?.(result.base64Image)
|
|
31
|
+
} else {
|
|
32
|
+
onError?.(result.error || 'Erro desconhecido')
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
onError: (error) => {
|
|
36
|
+
onError?.(error instanceof Error ? error.message : 'Erro ao processar')
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const handleFileSelect = useCallback(
|
|
41
|
+
async (file: File) => {
|
|
42
|
+
const validation = validateImage(file)
|
|
43
|
+
if (!validation.valid) {
|
|
44
|
+
onError?.(validation.error || 'Arquivo inválido')
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const compressed = await compressImage(file)
|
|
50
|
+
const reader = new FileReader()
|
|
51
|
+
reader.onload = (e) => {
|
|
52
|
+
setOriginalImage(e.target?.result as string)
|
|
53
|
+
setShowCropModal(true)
|
|
54
|
+
}
|
|
55
|
+
reader.readAsDataURL(compressed)
|
|
56
|
+
} catch {
|
|
57
|
+
onError?.('Erro ao processar imagem')
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
[onError]
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
const handleCropConfirm = useCallback(
|
|
64
|
+
async (imageElement: HTMLImageElement, cropArea: Crop) => {
|
|
65
|
+
try {
|
|
66
|
+
const croppedBase64 = await cropImageToCanvas(
|
|
67
|
+
imageElement,
|
|
68
|
+
cropArea,
|
|
69
|
+
config.width,
|
|
70
|
+
config.height
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
setCroppedPreview(croppedBase64)
|
|
74
|
+
setShowCropModal(false)
|
|
75
|
+
|
|
76
|
+
processMutation.mutate({
|
|
77
|
+
base64Image: croppedBase64,
|
|
78
|
+
targetWidth: config.width,
|
|
79
|
+
targetHeight: config.height,
|
|
80
|
+
cropArea: {
|
|
81
|
+
x: cropArea.x,
|
|
82
|
+
y: cropArea.y,
|
|
83
|
+
width: cropArea.width,
|
|
84
|
+
height: cropArea.height,
|
|
85
|
+
unit: cropArea.unit,
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
} catch {
|
|
89
|
+
onError?.('Erro ao aplicar crop')
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
[config, processMutation, onError]
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
const clear = useCallback(() => {
|
|
96
|
+
setOriginalImage(null)
|
|
97
|
+
setCroppedPreview(null)
|
|
98
|
+
}, [])
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
originalImage,
|
|
102
|
+
croppedPreview,
|
|
103
|
+
showCropModal,
|
|
104
|
+
config,
|
|
105
|
+
isProcessing: processMutation.isPending,
|
|
106
|
+
handleFileSelect,
|
|
107
|
+
handleCropConfirm,
|
|
108
|
+
setShowCropModal,
|
|
109
|
+
clear,
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PhotonImage,
|
|
3
|
+
resize,
|
|
4
|
+
crop,
|
|
5
|
+
SamplingFilter,
|
|
6
|
+
} from '@cf-wasm/photon/edge-light'
|
|
7
|
+
import type { ProcessImageInput, ProcessImageOutput } from '../types/image.type'
|
|
8
|
+
|
|
9
|
+
export function processImage(input: ProcessImageInput): ProcessImageOutput {
|
|
10
|
+
const {
|
|
11
|
+
imageBytes,
|
|
12
|
+
targetWidth,
|
|
13
|
+
targetHeight,
|
|
14
|
+
outputFormat = 'jpeg',
|
|
15
|
+
quality = 85,
|
|
16
|
+
} = input
|
|
17
|
+
|
|
18
|
+
const image = PhotonImage.new_from_byteslice(imageBytes)
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const sourceWidth = image.get_width()
|
|
22
|
+
const sourceHeight = image.get_height()
|
|
23
|
+
const targetAspect = targetWidth / targetHeight
|
|
24
|
+
const sourceAspect = sourceWidth / sourceHeight
|
|
25
|
+
|
|
26
|
+
let cropX1 = 0
|
|
27
|
+
let cropY1 = 0
|
|
28
|
+
let cropX2 = sourceWidth
|
|
29
|
+
let cropY2 = sourceHeight
|
|
30
|
+
|
|
31
|
+
if (sourceAspect > targetAspect) {
|
|
32
|
+
const cropWidth = Math.round(sourceHeight * targetAspect)
|
|
33
|
+
cropX1 = Math.round((sourceWidth - cropWidth) / 2)
|
|
34
|
+
cropX2 = cropX1 + cropWidth
|
|
35
|
+
} else if (sourceAspect < targetAspect) {
|
|
36
|
+
const cropHeight = Math.round(sourceWidth / targetAspect)
|
|
37
|
+
cropY1 = Math.round((sourceHeight - cropHeight) / 2)
|
|
38
|
+
cropY2 = cropY1 + cropHeight
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const cropped = crop(image, cropX1, cropY1, cropX2, cropY2)
|
|
42
|
+
const resized = resize(
|
|
43
|
+
cropped,
|
|
44
|
+
targetWidth,
|
|
45
|
+
targetHeight,
|
|
46
|
+
SamplingFilter.Triangle
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
let outputBytes: Uint8Array
|
|
50
|
+
switch (outputFormat) {
|
|
51
|
+
case 'jpeg':
|
|
52
|
+
outputBytes = resized.get_bytes_jpeg(quality)
|
|
53
|
+
break
|
|
54
|
+
case 'png':
|
|
55
|
+
outputBytes = resized.get_bytes()
|
|
56
|
+
break
|
|
57
|
+
case 'webp':
|
|
58
|
+
outputBytes = resized.get_bytes_webp()
|
|
59
|
+
break
|
|
60
|
+
default:
|
|
61
|
+
outputBytes = resized.get_bytes_jpeg(quality)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
cropped.free()
|
|
65
|
+
resized.free()
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
bytes: outputBytes,
|
|
69
|
+
width: targetWidth,
|
|
70
|
+
height: targetHeight,
|
|
71
|
+
format: `image/${outputFormat}`,
|
|
72
|
+
}
|
|
73
|
+
} finally {
|
|
74
|
+
image.free()
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export interface ImageConfig {
|
|
2
|
+
width: number
|
|
3
|
+
height: number
|
|
4
|
+
aspectRatio: number
|
|
5
|
+
label?: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface CropArea {
|
|
9
|
+
x: number
|
|
10
|
+
y: number
|
|
11
|
+
width: number
|
|
12
|
+
height: number
|
|
13
|
+
unit?: '%' | 'px'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ProcessedImage {
|
|
17
|
+
base64: string
|
|
18
|
+
width: number
|
|
19
|
+
height: number
|
|
20
|
+
format: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CompressImageOptions {
|
|
24
|
+
maxSizeMB?: number
|
|
25
|
+
maxWidthOrHeight?: number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ProcessImageInput {
|
|
29
|
+
imageBytes: Uint8Array
|
|
30
|
+
targetWidth: number
|
|
31
|
+
targetHeight: number
|
|
32
|
+
outputFormat?: 'jpeg' | 'png' | 'webp'
|
|
33
|
+
quality?: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ProcessImageOutput {
|
|
37
|
+
bytes: Uint8Array
|
|
38
|
+
width: number
|
|
39
|
+
height: number
|
|
40
|
+
format: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ProcessImageActionInput {
|
|
44
|
+
base64Image: string
|
|
45
|
+
targetWidth: number
|
|
46
|
+
targetHeight: number
|
|
47
|
+
cropArea?: CropArea
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ProcessImageActionOutput {
|
|
51
|
+
success: boolean
|
|
52
|
+
base64Image?: string
|
|
53
|
+
error?: string
|
|
54
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import imageCompression from 'browser-image-compression'
|
|
2
|
+
import type { CompressImageOptions } from '../types/image.type'
|
|
3
|
+
import { TARGET_FILE_SIZE_MB } from '../constants/image.constants'
|
|
4
|
+
|
|
5
|
+
export async function compressImage(
|
|
6
|
+
file: File,
|
|
7
|
+
options: CompressImageOptions = {}
|
|
8
|
+
): Promise<File> {
|
|
9
|
+
const { maxSizeMB = TARGET_FILE_SIZE_MB, maxWidthOrHeight = 1920 } = options
|
|
10
|
+
|
|
11
|
+
if (file.size <= maxSizeMB * 1024 * 1024) {
|
|
12
|
+
return file
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return imageCompression(file, {
|
|
16
|
+
maxSizeMB,
|
|
17
|
+
maxWidthOrHeight,
|
|
18
|
+
useWebWorker: true,
|
|
19
|
+
fileType: file.type as 'image/jpeg' | 'image/png' | 'image/webp',
|
|
20
|
+
})
|
|
21
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Crop } from 'react-image-crop'
|
|
2
|
+
|
|
3
|
+
export async function cropImageToCanvas(
|
|
4
|
+
image: HTMLImageElement,
|
|
5
|
+
crop: Crop,
|
|
6
|
+
targetWidth: number,
|
|
7
|
+
targetHeight: number
|
|
8
|
+
): Promise<string> {
|
|
9
|
+
const canvas = document.createElement('canvas')
|
|
10
|
+
const ctx = canvas.getContext('2d')
|
|
11
|
+
|
|
12
|
+
if (!ctx) throw new Error('Canvas não suportado')
|
|
13
|
+
|
|
14
|
+
const pixelCrop = {
|
|
15
|
+
x: crop.unit === '%' ? (crop.x / 100) * image.naturalWidth : crop.x,
|
|
16
|
+
y: crop.unit === '%' ? (crop.y / 100) * image.naturalHeight : crop.y,
|
|
17
|
+
width:
|
|
18
|
+
crop.unit === '%' ? (crop.width / 100) * image.naturalWidth : crop.width,
|
|
19
|
+
height:
|
|
20
|
+
crop.unit === '%'
|
|
21
|
+
? (crop.height / 100) * image.naturalHeight
|
|
22
|
+
: crop.height,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
canvas.width = targetWidth
|
|
26
|
+
canvas.height = targetHeight
|
|
27
|
+
|
|
28
|
+
ctx.drawImage(
|
|
29
|
+
image,
|
|
30
|
+
pixelCrop.x,
|
|
31
|
+
pixelCrop.y,
|
|
32
|
+
pixelCrop.width,
|
|
33
|
+
pixelCrop.height,
|
|
34
|
+
0,
|
|
35
|
+
0,
|
|
36
|
+
targetWidth,
|
|
37
|
+
targetHeight
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
return canvas.toDataURL('image/jpeg', 0.9)
|
|
41
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ACCEPTED_IMAGE_FORMATS,
|
|
3
|
+
MAX_FILE_SIZE_MB,
|
|
4
|
+
} from '../constants/image.constants'
|
|
5
|
+
|
|
6
|
+
export interface ImageValidationResult {
|
|
7
|
+
valid: boolean
|
|
8
|
+
error?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function validateImageFormat(file: File): ImageValidationResult {
|
|
12
|
+
if (!ACCEPTED_IMAGE_FORMATS.includes(file.type as (typeof ACCEPTED_IMAGE_FORMATS)[number])) {
|
|
13
|
+
return {
|
|
14
|
+
valid: false,
|
|
15
|
+
error: 'Formato não suportado. Use PNG, JPEG ou WebP.',
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return { valid: true }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function validateImageSize(
|
|
22
|
+
file: File,
|
|
23
|
+
maxSizeMB: number = MAX_FILE_SIZE_MB
|
|
24
|
+
): ImageValidationResult {
|
|
25
|
+
if (file.size > maxSizeMB * 1024 * 1024) {
|
|
26
|
+
return {
|
|
27
|
+
valid: false,
|
|
28
|
+
error: `Arquivo muito grande. Máximo ${maxSizeMB}MB.`,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return { valid: true }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function validateImage(file: File): ImageValidationResult {
|
|
35
|
+
const formatValidation = validateImageFormat(file)
|
|
36
|
+
if (!formatValidation.valid) return formatValidation
|
|
37
|
+
|
|
38
|
+
const sizeValidation = validateImageSize(file)
|
|
39
|
+
if (!sizeValidation.valid) return sizeValidation
|
|
40
|
+
|
|
41
|
+
return { valid: true }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function getImageDimensions(
|
|
45
|
+
file: File
|
|
46
|
+
): Promise<{ width: number; height: number }> {
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const img = new Image()
|
|
49
|
+
img.onload = () => {
|
|
50
|
+
resolve({ width: img.naturalWidth, height: img.naturalHeight })
|
|
51
|
+
URL.revokeObjectURL(img.src)
|
|
52
|
+
}
|
|
53
|
+
img.onerror = () => {
|
|
54
|
+
reject(new Error('Falha ao carregar imagem'))
|
|
55
|
+
URL.revokeObjectURL(img.src)
|
|
56
|
+
}
|
|
57
|
+
img.src = URL.createObjectURL(file)
|
|
58
|
+
})
|
|
59
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -65,3 +65,13 @@ export { getClientInfoFromRequest } from './infra/utils/client-info';
|
|
|
65
65
|
export { getUserContext } from './modules/auth/utils/get-user-context';
|
|
66
66
|
export { buildWlOverride, buildWlOverrideFromJwt } from './modules/auth/utils/build-wl-override';
|
|
67
67
|
export { safeServerAction } from './utils/safeServerAction';
|
|
68
|
+
|
|
69
|
+
// Image Processing (server-side)
|
|
70
|
+
export { processImageAction } from './modules/images/actions/process-image.action';
|
|
71
|
+
export { processImage } from './modules/images/services/image-processing.service';
|
|
72
|
+
export type {
|
|
73
|
+
ProcessImageInput,
|
|
74
|
+
ProcessImageOutput,
|
|
75
|
+
ProcessImageActionInput,
|
|
76
|
+
ProcessImageActionOutput,
|
|
77
|
+
} from './modules/images/types/image.type';
|