@umituz/react-native-image 1.3.16 → 1.3.18
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/package.json +1 -1
- package/src/domain/entities/ValidationResult.ts +15 -0
- package/src/index.ts +24 -0
- package/src/infrastructure/utils/validation/image-validator.ts +77 -0
- package/src/infrastructure/utils/validation/mime-type-validator.ts +101 -0
- package/src/infrastructure/utils/validation/mime-types.constants.ts +41 -0
- package/src/presentation/components/image/AtomicImage.tsx +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-image",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.18",
|
|
4
4
|
"description": "Image manipulation and viewing for React Native apps - resize, crop, rotate, flip, compress, gallery viewer",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation Result Interface
|
|
3
|
+
* Represents the outcome of a validation operation
|
|
4
|
+
*/
|
|
5
|
+
export interface ValidationResult {
|
|
6
|
+
/**
|
|
7
|
+
* Whether the validation passed
|
|
8
|
+
*/
|
|
9
|
+
isValid: boolean;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Error message if validation failed
|
|
13
|
+
*/
|
|
14
|
+
error?: string;
|
|
15
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -97,3 +97,27 @@ export { useImageEnhance } from './presentation/hooks/useImageEnhance';
|
|
|
97
97
|
export { useImageMetadata } from './presentation/hooks/useImageMetadata';
|
|
98
98
|
|
|
99
99
|
|
|
100
|
+
|
|
101
|
+
// =============================================================================
|
|
102
|
+
// INFRASTRUCTURE LAYER - Validation
|
|
103
|
+
// =============================================================================
|
|
104
|
+
|
|
105
|
+
export type { ValidationResult } from './domain/entities/ValidationResult';
|
|
106
|
+
export {
|
|
107
|
+
getFileExtension,
|
|
108
|
+
getMimeTypeFromExtension,
|
|
109
|
+
getMimeTypeFromDataUrl,
|
|
110
|
+
validateImageMimeType,
|
|
111
|
+
validateImageExtension,
|
|
112
|
+
validateImageDataUrl,
|
|
113
|
+
} from './infrastructure/utils/validation/mime-type-validator';
|
|
114
|
+
export {
|
|
115
|
+
validateImageUri,
|
|
116
|
+
getImageMimeType,
|
|
117
|
+
} from './infrastructure/utils/validation/image-validator';
|
|
118
|
+
export {
|
|
119
|
+
IMAGE_MIME_TYPES,
|
|
120
|
+
SUPPORTED_IMAGE_MIME_TYPES,
|
|
121
|
+
EXTENSION_TO_MIME_TYPE,
|
|
122
|
+
MIME_TYPE_TO_EXTENSION,
|
|
123
|
+
} from './infrastructure/utils/validation/mime-types.constants';
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Validator
|
|
3
|
+
* Single Responsibility: Validate image files and URIs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ValidationResult } from '../../../domain/entities/ValidationResult';
|
|
7
|
+
import {
|
|
8
|
+
validateImageExtension,
|
|
9
|
+
validateImageDataUrl,
|
|
10
|
+
getMimeTypeFromExtension,
|
|
11
|
+
getMimeTypeFromDataUrl,
|
|
12
|
+
} from './mime-type-validator';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Check if URI is a local file path
|
|
16
|
+
*/
|
|
17
|
+
function isLocalFileUrl(uri: string): boolean {
|
|
18
|
+
return uri.startsWith('file://');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Check if URI is a public URL
|
|
23
|
+
*/
|
|
24
|
+
function isPublicUrl(uri: string): boolean {
|
|
25
|
+
return uri.startsWith('http://') || uri.startsWith('https://');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Check if URI is a data URL
|
|
30
|
+
*/
|
|
31
|
+
function isDataUrl(uri: string): boolean {
|
|
32
|
+
return uri.startsWith('data:');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Validate image URI (supports file://, http://, https://, and data: URLs)
|
|
37
|
+
*/
|
|
38
|
+
export function validateImageUri(
|
|
39
|
+
uri: string,
|
|
40
|
+
fieldName: string = 'Image'
|
|
41
|
+
): ValidationResult {
|
|
42
|
+
if (!uri || uri.trim() === '') {
|
|
43
|
+
return { isValid: false, error: `${fieldName} is required` };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Validate public URLs (assume they're images if they have image extension)
|
|
47
|
+
if (isPublicUrl(uri)) {
|
|
48
|
+
return validateImageExtension(uri, fieldName);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Validate data URLs
|
|
52
|
+
if (isDataUrl(uri)) {
|
|
53
|
+
return validateImageDataUrl(uri, fieldName);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Validate local file URLs
|
|
57
|
+
if (isLocalFileUrl(uri)) {
|
|
58
|
+
return validateImageExtension(uri, fieldName);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Unknown format
|
|
62
|
+
return {
|
|
63
|
+
isValid: false,
|
|
64
|
+
error: `${fieldName} must be a valid image URL or file path`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get MIME type from URI (supports all URI types)
|
|
70
|
+
*/
|
|
71
|
+
export function getImageMimeType(uri: string): string | null {
|
|
72
|
+
if (isDataUrl(uri)) {
|
|
73
|
+
return getMimeTypeFromDataUrl(uri);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return getMimeTypeFromExtension(uri);
|
|
77
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MIME Type Validator
|
|
3
|
+
* Single Responsibility: Validate MIME types
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ValidationResult } from '../../../domain/entities/ValidationResult';
|
|
7
|
+
import {
|
|
8
|
+
SUPPORTED_IMAGE_MIME_TYPES,
|
|
9
|
+
EXTENSION_TO_MIME_TYPE,
|
|
10
|
+
} from './mime-types.constants';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get file extension from URI
|
|
14
|
+
*/
|
|
15
|
+
export function getFileExtension(uri: string): string | null {
|
|
16
|
+
const match = uri.match(/\.([a-z0-9]+)(?:\?|$)/i);
|
|
17
|
+
return match ? match[1].toLowerCase() : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get MIME type from file extension
|
|
22
|
+
*/
|
|
23
|
+
export function getMimeTypeFromExtension(uri: string): string | null {
|
|
24
|
+
const extension = getFileExtension(uri);
|
|
25
|
+
if (!extension) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return EXTENSION_TO_MIME_TYPE[extension] || null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get MIME type from data URL
|
|
33
|
+
*/
|
|
34
|
+
export function getMimeTypeFromDataUrl(dataUrl: string): string | null {
|
|
35
|
+
const match = dataUrl.match(/^data:([^;]+);/);
|
|
36
|
+
return match ? match[1] : null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Validate MIME type is supported image type
|
|
41
|
+
*/
|
|
42
|
+
export function validateImageMimeType(
|
|
43
|
+
mimeType: string,
|
|
44
|
+
fieldName: string = 'File'
|
|
45
|
+
): ValidationResult {
|
|
46
|
+
if (!mimeType) {
|
|
47
|
+
return { isValid: false, error: `${fieldName} MIME type is required` };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!SUPPORTED_IMAGE_MIME_TYPES.includes(mimeType as any)) {
|
|
51
|
+
return {
|
|
52
|
+
isValid: false,
|
|
53
|
+
error: `${fieldName} must be an image (JPEG, PNG, WEBP, or GIF)`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { isValid: true };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Validate file extension is supported image type
|
|
62
|
+
*/
|
|
63
|
+
export function validateImageExtension(
|
|
64
|
+
uri: string,
|
|
65
|
+
fieldName: string = 'File'
|
|
66
|
+
): ValidationResult {
|
|
67
|
+
const mimeType = getMimeTypeFromExtension(uri);
|
|
68
|
+
if (!mimeType) {
|
|
69
|
+
return {
|
|
70
|
+
isValid: false,
|
|
71
|
+
error: `${fieldName} must have a valid image extension (jpg, jpeg, png, webp, gif)`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return validateImageMimeType(mimeType, fieldName);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Validate data URL is image type
|
|
80
|
+
*/
|
|
81
|
+
export function validateImageDataUrl(
|
|
82
|
+
dataUrl: string,
|
|
83
|
+
fieldName: string = 'File'
|
|
84
|
+
): ValidationResult {
|
|
85
|
+
if (!dataUrl || !dataUrl.startsWith('data:')) {
|
|
86
|
+
return {
|
|
87
|
+
isValid: false,
|
|
88
|
+
error: `${fieldName} must be a valid data URL`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const mimeType = getMimeTypeFromDataUrl(dataUrl);
|
|
93
|
+
if (!mimeType) {
|
|
94
|
+
return {
|
|
95
|
+
isValid: false,
|
|
96
|
+
error: `${fieldName} must have a valid MIME type`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return validateImageMimeType(mimeType, fieldName);
|
|
101
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MIME Type Constants
|
|
3
|
+
* Single Responsibility: Define supported MIME types for validation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Supported image MIME types
|
|
8
|
+
*/
|
|
9
|
+
export const IMAGE_MIME_TYPES = {
|
|
10
|
+
JPEG: 'image/jpeg',
|
|
11
|
+
JPG: 'image/jpeg',
|
|
12
|
+
PNG: 'image/png',
|
|
13
|
+
WEBP: 'image/webp',
|
|
14
|
+
GIF: 'image/gif',
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* All supported image MIME types as array
|
|
19
|
+
*/
|
|
20
|
+
export const SUPPORTED_IMAGE_MIME_TYPES = Object.values(IMAGE_MIME_TYPES);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* File extension to MIME type mapping
|
|
24
|
+
*/
|
|
25
|
+
export const EXTENSION_TO_MIME_TYPE: Record<string, string> = {
|
|
26
|
+
jpg: IMAGE_MIME_TYPES.JPEG,
|
|
27
|
+
jpeg: IMAGE_MIME_TYPES.JPEG,
|
|
28
|
+
png: IMAGE_MIME_TYPES.PNG,
|
|
29
|
+
webp: IMAGE_MIME_TYPES.WEBP,
|
|
30
|
+
gif: IMAGE_MIME_TYPES.GIF,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* MIME type to file extension mapping
|
|
35
|
+
*/
|
|
36
|
+
export const MIME_TYPE_TO_EXTENSION: Record<string, string> = {
|
|
37
|
+
[IMAGE_MIME_TYPES.JPEG]: 'jpg',
|
|
38
|
+
[IMAGE_MIME_TYPES.PNG]: 'png',
|
|
39
|
+
[IMAGE_MIME_TYPES.WEBP]: 'webp',
|
|
40
|
+
[IMAGE_MIME_TYPES.GIF]: 'gif',
|
|
41
|
+
};
|
|
@@ -13,13 +13,13 @@ export const AtomicImage: React.FC<AtomicImageProps> = ({
|
|
|
13
13
|
transition = 300,
|
|
14
14
|
...props
|
|
15
15
|
}) => {
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
|
|
18
18
|
return (
|
|
19
19
|
<ExpoImage
|
|
20
20
|
style={[
|
|
21
21
|
style,
|
|
22
|
-
rounded && { borderRadius:
|
|
22
|
+
rounded && { borderRadius: 9999 }
|
|
23
23
|
]}
|
|
24
24
|
contentFit={contentFit}
|
|
25
25
|
transition={transition}
|