@umituz/react-native-design-system 2.6.111 → 2.6.113
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 +7 -3
- package/src/atoms/image/AtomicImage.tsx +29 -0
- package/src/atoms/index.ts +3 -0
- package/src/exports/atoms.ts +2 -0
- package/src/exports/image.ts +7 -0
- package/src/image/domain/entities/EditorTypes.ts +23 -0
- package/src/image/domain/entities/ImageConstants.ts +38 -0
- package/src/image/domain/entities/ImageFilterTypes.ts +70 -0
- package/src/image/domain/entities/ImageTemplateTypes.ts +18 -0
- package/src/image/domain/entities/ImageTypes.ts +86 -0
- package/src/image/domain/entities/ValidationResult.ts +15 -0
- package/src/image/domain/entities/editor/EditorConfigTypes.ts +35 -0
- package/src/image/domain/entities/editor/EditorElementTypes.ts +60 -0
- package/src/image/domain/entities/editor/EditorFilterTypes.ts +9 -0
- package/src/image/domain/entities/editor/EditorLayerTypes.ts +34 -0
- package/src/image/domain/entities/editor/EditorStateTypes.ts +35 -0
- package/src/image/domain/entities/editor/EditorToolTypes.ts +33 -0
- package/src/image/domain/utils/ImageUtils.ts +103 -0
- package/src/image/index.ts +123 -0
- package/src/image/infrastructure/services/ImageBatchService.ts +110 -0
- package/src/image/infrastructure/services/ImageConversionService.ts +74 -0
- package/src/image/infrastructure/services/ImageEditorService.ts +136 -0
- package/src/image/infrastructure/services/ImageEnhanceService.ts +123 -0
- package/src/image/infrastructure/services/ImageMetadataService.ts +116 -0
- package/src/image/infrastructure/services/ImageStorageService.ts +37 -0
- package/src/image/infrastructure/services/ImageTemplateService.ts +66 -0
- package/src/image/infrastructure/services/ImageTransformService.ts +89 -0
- package/src/image/infrastructure/services/ImageViewerService.ts +64 -0
- package/src/image/infrastructure/utils/BatchProcessor.ts +95 -0
- package/src/image/infrastructure/utils/FilterProcessor.ts +124 -0
- package/src/image/infrastructure/utils/ImageAnalysisUtils.ts +122 -0
- package/src/image/infrastructure/utils/ImageEditorHistoryUtils.ts +63 -0
- package/src/image/infrastructure/utils/ImageErrorHandler.ts +40 -0
- package/src/image/infrastructure/utils/ImageFilterUtils.ts +21 -0
- package/src/image/infrastructure/utils/ImageQualityPresets.ts +110 -0
- package/src/image/infrastructure/utils/ImageTransformUtils.ts +25 -0
- package/src/image/infrastructure/utils/ImageValidator.ts +59 -0
- package/src/image/infrastructure/utils/LayerManager.ts +77 -0
- package/src/image/infrastructure/utils/MetadataExtractor.ts +83 -0
- package/src/image/infrastructure/utils/filters/BasicFilters.ts +61 -0
- package/src/image/infrastructure/utils/filters/FilterHelpers.ts +21 -0
- package/src/image/infrastructure/utils/filters/SpecialFilters.ts +84 -0
- package/src/image/infrastructure/utils/validation/image-validator.ts +77 -0
- package/src/image/infrastructure/utils/validation/mime-type-validator.ts +101 -0
- package/src/image/infrastructure/utils/validation/mime-types.constants.ts +41 -0
- package/src/image/presentation/components/GalleryHeader.tsx +126 -0
- package/src/image/presentation/components/ImageGallery.tsx +138 -0
- package/src/image/presentation/components/editor/FilterPickerSheet.tsx +75 -0
- package/src/image/presentation/components/editor/StickerPickerSheet.tsx +62 -0
- package/src/image/presentation/components/editor/TextEditorSheet.tsx +98 -0
- package/src/image/presentation/components/editor/TextEditorTabs.tsx +111 -0
- package/src/image/presentation/components/image/AtomicImage.tsx +29 -0
- package/src/image/presentation/hooks/useImage.ts +39 -0
- package/src/image/presentation/hooks/useImageBatch.ts +28 -0
- package/src/image/presentation/hooks/useImageConversion.ts +29 -0
- package/src/image/presentation/hooks/useImageEnhance.ts +32 -0
- package/src/image/presentation/hooks/useImageGallery.ts +90 -0
- package/src/image/presentation/hooks/useImageMetadata.ts +28 -0
- package/src/image/presentation/hooks/useImageOperation.ts +37 -0
- package/src/image/presentation/hooks/useImageTransform.ts +42 -0
- package/src/index.ts +5 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presentation - Text Editor Tabs
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { View, TextInput, ScrollView, TouchableOpacity } from 'react-native';
|
|
7
|
+
import Slider from '@react-native-community/slider';
|
|
8
|
+
import { AtomicText, AtomicIcon, useAppDesignTokens } from '@umituz/react-native-design-system';
|
|
9
|
+
|
|
10
|
+
interface TabProps {
|
|
11
|
+
t: (key: string) => string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const TextContentTab: React.FC<TabProps & { text: string; onTextChange: (t: string) => void }> = ({ text, onTextChange, t }) => {
|
|
15
|
+
const tokens = useAppDesignTokens();
|
|
16
|
+
return (
|
|
17
|
+
<View style={{ gap: tokens.spacing.lg }}>
|
|
18
|
+
<TextInput
|
|
19
|
+
value={text}
|
|
20
|
+
onChangeText={onTextChange}
|
|
21
|
+
placeholder={t('editor.text_placeholder')}
|
|
22
|
+
style={{
|
|
23
|
+
...tokens.typography.bodyLarge,
|
|
24
|
+
borderWidth: 1,
|
|
25
|
+
borderColor: tokens.colors.border,
|
|
26
|
+
borderRadius: tokens.radius.md,
|
|
27
|
+
padding: tokens.spacing.md,
|
|
28
|
+
minHeight: 120,
|
|
29
|
+
textAlignVertical: 'top',
|
|
30
|
+
}}
|
|
31
|
+
multiline
|
|
32
|
+
/>
|
|
33
|
+
</View>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const TextStyleTab: React.FC<TabProps & {
|
|
38
|
+
fontSize: number; setFontSize: (s: number) => void;
|
|
39
|
+
color: string; setColor: (c: string) => void;
|
|
40
|
+
fontFamily: string; setFontFamily: (f: string) => void;
|
|
41
|
+
}> = ({ fontSize, setFontSize, color, setColor, fontFamily, setFontFamily, t }) => {
|
|
42
|
+
const tokens = useAppDesignTokens();
|
|
43
|
+
const colors = ['#FFFFFF', '#000000', '#FF0000', '#FFFF00', '#0000FF', '#00FF00', '#FF00FF', '#FFA500'];
|
|
44
|
+
const fonts = ['System', 'serif', 'sans-serif', 'monospace'];
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<View style={{ gap: tokens.spacing.xl }}>
|
|
48
|
+
<View>
|
|
49
|
+
<AtomicText style={{ ...tokens.typography.labelMedium, marginBottom: tokens.spacing.sm }}>Font</AtomicText>
|
|
50
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: tokens.spacing.sm }}>
|
|
51
|
+
{fonts.map(f => (
|
|
52
|
+
<TouchableOpacity key={f} onPress={() => setFontFamily(f)} style={{
|
|
53
|
+
paddingHorizontal: tokens.spacing.md, paddingVertical: tokens.spacing.xs, borderRadius: tokens.radius.full,
|
|
54
|
+
borderWidth: 1, borderColor: fontFamily === f ? tokens.colors.primary : tokens.colors.border,
|
|
55
|
+
backgroundColor: fontFamily === f ? tokens.colors.primaryContainer : tokens.colors.surface
|
|
56
|
+
}}>
|
|
57
|
+
<AtomicText style={{ fontFamily: f }}>{f}</AtomicText>
|
|
58
|
+
</TouchableOpacity>
|
|
59
|
+
))}
|
|
60
|
+
</ScrollView>
|
|
61
|
+
</View>
|
|
62
|
+
|
|
63
|
+
<View>
|
|
64
|
+
<AtomicText style={{ ...tokens.typography.labelMedium, marginBottom: tokens.spacing.sm }}>Color</AtomicText>
|
|
65
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: tokens.spacing.sm }}>
|
|
66
|
+
{colors.map(c => (
|
|
67
|
+
<TouchableOpacity key={c} onPress={() => setColor(c)} style={{
|
|
68
|
+
width: 40, height: 40, borderRadius: 20, backgroundColor: c,
|
|
69
|
+
borderWidth: color === c ? 3 : 1, borderColor: tokens.colors.primary
|
|
70
|
+
}} />
|
|
71
|
+
))}
|
|
72
|
+
</ScrollView>
|
|
73
|
+
</View>
|
|
74
|
+
|
|
75
|
+
<View>
|
|
76
|
+
<AtomicText style={{ ...tokens.typography.labelMedium, marginBottom: tokens.spacing.xs }}>Size: {fontSize}px</AtomicText>
|
|
77
|
+
<Slider value={fontSize} onValueChange={setFontSize} minimumValue={12} maximumValue={100} step={1} minimumTrackTintColor={tokens.colors.primary} />
|
|
78
|
+
</View>
|
|
79
|
+
</View>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const TextTransformTab: React.FC<TabProps & {
|
|
84
|
+
scale: number; setScale: (s: number) => void;
|
|
85
|
+
rotation: number; setRotation: (r: number) => void;
|
|
86
|
+
opacity: number; setOpacity: (o: number) => void;
|
|
87
|
+
onDelete?: () => void;
|
|
88
|
+
}> = ({ scale, setScale, rotation, setRotation, opacity, setOpacity, onDelete, t }) => {
|
|
89
|
+
const tokens = useAppDesignTokens();
|
|
90
|
+
return (
|
|
91
|
+
<View style={{ gap: tokens.spacing.xl }}>
|
|
92
|
+
<View>
|
|
93
|
+
<AtomicText style={{ ...tokens.typography.labelMedium, marginBottom: tokens.spacing.xs }}>Scale: {scale.toFixed(2)}x</AtomicText>
|
|
94
|
+
<Slider value={scale} onValueChange={setScale} minimumValue={0.2} maximumValue={3} step={0.1} minimumTrackTintColor={tokens.colors.primary} />
|
|
95
|
+
</View>
|
|
96
|
+
<View>
|
|
97
|
+
<AtomicText style={{ ...tokens.typography.labelMedium, marginBottom: tokens.spacing.xs }}>Rotation: {Math.round(rotation)}°</AtomicText>
|
|
98
|
+
<Slider value={rotation} onValueChange={setRotation} minimumValue={0} maximumValue={360} step={1} minimumTrackTintColor={tokens.colors.primary} />
|
|
99
|
+
</View>
|
|
100
|
+
{onDelete && (
|
|
101
|
+
<TouchableOpacity onPress={onDelete} style={{
|
|
102
|
+
flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: tokens.spacing.sm,
|
|
103
|
+
padding: tokens.spacing.md, borderRadius: tokens.radius.md, borderWidth: 1, borderColor: tokens.colors.error
|
|
104
|
+
}}>
|
|
105
|
+
<AtomicIcon name="trash" size={20} color="error" />
|
|
106
|
+
<AtomicText style={{ ...tokens.typography.labelMedium, color: tokens.colors.error }}>Delete Layer</AtomicText>
|
|
107
|
+
</TouchableOpacity>
|
|
108
|
+
)}
|
|
109
|
+
</View>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Image as ExpoImage, ImageProps as ExpoImageProps } from 'expo-image';
|
|
3
|
+
import { StyleSheet, ViewStyle } from 'react-native';
|
|
4
|
+
|
|
5
|
+
export type AtomicImageProps = ExpoImageProps & {
|
|
6
|
+
rounded?: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const AtomicImage: React.FC<AtomicImageProps> = ({
|
|
10
|
+
style,
|
|
11
|
+
rounded,
|
|
12
|
+
contentFit = 'cover',
|
|
13
|
+
transition = 300,
|
|
14
|
+
...props
|
|
15
|
+
}) => {
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<ExpoImage
|
|
20
|
+
style={[
|
|
21
|
+
style,
|
|
22
|
+
rounded && { borderRadius: 9999 }
|
|
23
|
+
]}
|
|
24
|
+
contentFit={contentFit}
|
|
25
|
+
transition={transition}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presentation - Image Hook
|
|
3
|
+
*
|
|
4
|
+
* Aggregator hook combining transformation and conversion capabilities
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useImageTransform } from './useImageTransform';
|
|
8
|
+
import { useImageConversion } from './useImageConversion';
|
|
9
|
+
import { useImageBatch } from './useImageBatch';
|
|
10
|
+
import { useImageEnhance } from './useImageEnhance';
|
|
11
|
+
import { useImageMetadata } from './useImageMetadata';
|
|
12
|
+
|
|
13
|
+
export const useImage = () => {
|
|
14
|
+
const transform = useImageTransform();
|
|
15
|
+
const conversion = useImageConversion();
|
|
16
|
+
const batch = useImageBatch();
|
|
17
|
+
const enhance = useImageEnhance();
|
|
18
|
+
const metadata = useImageMetadata();
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
...transform,
|
|
22
|
+
...conversion,
|
|
23
|
+
...batch,
|
|
24
|
+
...enhance,
|
|
25
|
+
...metadata,
|
|
26
|
+
isProcessing:
|
|
27
|
+
transform.isTransforming ||
|
|
28
|
+
conversion.isConverting ||
|
|
29
|
+
batch.isBatchProcessing ||
|
|
30
|
+
enhance.isEnhancing ||
|
|
31
|
+
metadata.isExtracting,
|
|
32
|
+
error:
|
|
33
|
+
transform.transformError ||
|
|
34
|
+
conversion.conversionError ||
|
|
35
|
+
batch.batchError ||
|
|
36
|
+
enhance.enhancementError ||
|
|
37
|
+
metadata.metadataError,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presentation - Image Batch Hook
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useCallback } from 'react';
|
|
6
|
+
import { useImageOperation } from './useImageOperation';
|
|
7
|
+
import { ImageBatchService, type BatchOperation, type BatchProcessingOptions } from '../../infrastructure/services/ImageBatchService';
|
|
8
|
+
|
|
9
|
+
export const useImageBatch = () => {
|
|
10
|
+
const { isProcessing, error, execute } = useImageOperation();
|
|
11
|
+
|
|
12
|
+
const processBatch = useCallback((operations: BatchOperation[], options?: BatchProcessingOptions) =>
|
|
13
|
+
execute(() => ImageBatchService.processBatch(operations, options), 'Failed to process batch'), [execute]);
|
|
14
|
+
|
|
15
|
+
const resizeBatch = useCallback((uris: string[], width?: number, height?: number, options?: BatchProcessingOptions & { saveOptions?: any }) =>
|
|
16
|
+
execute(() => ImageBatchService.resizeBatch(uris, width, height, options), 'Failed to resize batch'), [execute]);
|
|
17
|
+
|
|
18
|
+
const compressBatch = useCallback((uris: string[], quality?: number, options?: BatchProcessingOptions) =>
|
|
19
|
+
execute(() => ImageBatchService.compressBatch(uris, quality, options), 'Failed to compress batch'), [execute]);
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
processBatch,
|
|
23
|
+
resizeBatch,
|
|
24
|
+
compressBatch,
|
|
25
|
+
isBatchProcessing: isProcessing,
|
|
26
|
+
batchError: error,
|
|
27
|
+
};
|
|
28
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presentation - Image Conversion Hook
|
|
3
|
+
*/
|
|
4
|
+
import { useCallback } from 'react';
|
|
5
|
+
import { useImageOperation } from './useImageOperation';
|
|
6
|
+
import { ImageConversionService } from '../../infrastructure/services/ImageConversionService';
|
|
7
|
+
import { ImageStorageService } from '../../infrastructure/services/ImageStorageService';
|
|
8
|
+
import type { ImageSaveOptions, SaveFormat } from '../../domain/entities/ImageTypes';
|
|
9
|
+
|
|
10
|
+
export const useImageConversion = () => {
|
|
11
|
+
const { isProcessing, error, execute } = useImageOperation();
|
|
12
|
+
|
|
13
|
+
const compress = useCallback((uri: string, quality?: number) =>
|
|
14
|
+
execute(() => ImageConversionService.compress(uri, quality), 'Failed to compress'), [execute]);
|
|
15
|
+
|
|
16
|
+
const convertFormat = useCallback((uri: string, format: SaveFormat, quality?: number) =>
|
|
17
|
+
execute(() => ImageConversionService.convertFormat(uri, format, quality), 'Failed to convert format'), [execute]);
|
|
18
|
+
|
|
19
|
+
const createThumbnail = useCallback((uri: string, size?: number, options?: ImageSaveOptions) =>
|
|
20
|
+
execute(() => ImageConversionService.createThumbnail(uri, size, options), 'Failed to create thumbnail'), [execute]);
|
|
21
|
+
|
|
22
|
+
const saveImage = useCallback((uri: string, filename?: string) =>
|
|
23
|
+
execute(() => ImageStorageService.saveImage(uri, filename), 'Failed to save image'), [execute]);
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
compress, convertFormat, createThumbnail, saveImage,
|
|
27
|
+
isConverting: isProcessing, conversionError: error,
|
|
28
|
+
};
|
|
29
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presentation - Image Enhance Hook
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useCallback } from 'react';
|
|
6
|
+
import { useImageOperation } from './useImageOperation';
|
|
7
|
+
import { ImageEnhanceService, type AutoEnhancementOptions } from '../../infrastructure/services/ImageEnhanceService';
|
|
8
|
+
|
|
9
|
+
export const useImageEnhance = () => {
|
|
10
|
+
const { isProcessing, error, execute } = useImageOperation();
|
|
11
|
+
|
|
12
|
+
const autoEnhance = useCallback((uri: string, options?: AutoEnhancementOptions) =>
|
|
13
|
+
execute(() => ImageEnhanceService.autoEnhance(uri, options), 'Failed to auto enhance'), [execute]);
|
|
14
|
+
|
|
15
|
+
const enhancePortrait = useCallback((uri: string) =>
|
|
16
|
+
execute(() => ImageEnhanceService.enhancePortrait(uri), 'Failed to enhance portrait'), [execute]);
|
|
17
|
+
|
|
18
|
+
const enhanceLandscape = useCallback((uri: string) =>
|
|
19
|
+
execute(() => ImageEnhanceService.enhanceLandscape(uri), 'Failed to enhance landscape'), [execute]);
|
|
20
|
+
|
|
21
|
+
const analyzeImage = useCallback((uri: string) =>
|
|
22
|
+
execute(() => ImageEnhanceService.analyzeImage(uri), 'Failed to analyze image'), [execute]);
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
autoEnhance,
|
|
26
|
+
enhancePortrait,
|
|
27
|
+
enhanceLandscape,
|
|
28
|
+
analyzeImage,
|
|
29
|
+
isEnhancing: isProcessing,
|
|
30
|
+
enhancementError: error,
|
|
31
|
+
};
|
|
32
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presentation - Image Gallery Hook
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useState, useCallback, useMemo } from 'react';
|
|
6
|
+
import { ImageViewerService } from '../../infrastructure/services/ImageViewerService';
|
|
7
|
+
import type {
|
|
8
|
+
ImageViewerItem,
|
|
9
|
+
ImageGalleryOptions,
|
|
10
|
+
} from '../../domain/entities/ImageTypes';
|
|
11
|
+
|
|
12
|
+
export interface UseImageGalleryReturn {
|
|
13
|
+
visible: boolean;
|
|
14
|
+
currentIndex: number;
|
|
15
|
+
images: ImageViewerItem[];
|
|
16
|
+
open: (images: ImageViewerItem[] | string[], startIndex?: number, options?: ImageGalleryOptions) => void;
|
|
17
|
+
close: () => void;
|
|
18
|
+
setIndex: (index: number) => void;
|
|
19
|
+
options: ImageGalleryOptions;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const useImageGallery = (
|
|
23
|
+
defaultOptions?: ImageGalleryOptions
|
|
24
|
+
): UseImageGalleryReturn => {
|
|
25
|
+
const [visible, setVisible] = useState(false);
|
|
26
|
+
const [currentIndex, setCurrentIndex] = useState(0);
|
|
27
|
+
const [images, setImages] = useState<ImageViewerItem[]>([]);
|
|
28
|
+
const [galleryOptions, setGalleryOptions] = useState<ImageGalleryOptions>(
|
|
29
|
+
defaultOptions || ImageViewerService.getDefaultOptions()
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const open = useCallback(
|
|
33
|
+
(
|
|
34
|
+
imageData: ImageViewerItem[] | string[],
|
|
35
|
+
startIndex: number = 0,
|
|
36
|
+
options?: ImageGalleryOptions
|
|
37
|
+
) => {
|
|
38
|
+
const preparedImages =
|
|
39
|
+
typeof imageData[0] === 'string'
|
|
40
|
+
? ImageViewerService.prepareImages(imageData as string[])
|
|
41
|
+
: ImageViewerService.prepareImagesWithMetadata(imageData as ImageViewerItem[]);
|
|
42
|
+
|
|
43
|
+
setImages(preparedImages);
|
|
44
|
+
setCurrentIndex(options?.index ?? startIndex);
|
|
45
|
+
|
|
46
|
+
if (options) {
|
|
47
|
+
setGalleryOptions({
|
|
48
|
+
...galleryOptions,
|
|
49
|
+
...options,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
setVisible(true);
|
|
54
|
+
},
|
|
55
|
+
[galleryOptions]
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const close = useCallback(() => {
|
|
59
|
+
setVisible(false);
|
|
60
|
+
|
|
61
|
+
if (galleryOptions.onDismiss) {
|
|
62
|
+
galleryOptions.onDismiss();
|
|
63
|
+
}
|
|
64
|
+
}, [galleryOptions]);
|
|
65
|
+
|
|
66
|
+
const setIndex = useCallback((index: number) => {
|
|
67
|
+
setCurrentIndex(index);
|
|
68
|
+
|
|
69
|
+
if (galleryOptions.onIndexChange) {
|
|
70
|
+
galleryOptions.onIndexChange(index);
|
|
71
|
+
}
|
|
72
|
+
}, [galleryOptions]);
|
|
73
|
+
|
|
74
|
+
const options = useMemo(() => ({
|
|
75
|
+
backgroundColor: galleryOptions.backgroundColor || '#000000',
|
|
76
|
+
swipeToCloseEnabled: galleryOptions.swipeToCloseEnabled ?? true,
|
|
77
|
+
doubleTapToZoomEnabled: galleryOptions.doubleTapToZoomEnabled ?? true,
|
|
78
|
+
}), [galleryOptions]);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
visible,
|
|
82
|
+
currentIndex,
|
|
83
|
+
images,
|
|
84
|
+
open,
|
|
85
|
+
close,
|
|
86
|
+
setIndex,
|
|
87
|
+
options,
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presentation - Image Metadata Hook
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useCallback } from 'react';
|
|
6
|
+
import { useImageOperation } from './useImageOperation';
|
|
7
|
+
import { ImageMetadataService, type ImageMetadataExtractionOptions } from '../../infrastructure/services/ImageMetadataService';
|
|
8
|
+
|
|
9
|
+
export const useImageMetadata = () => {
|
|
10
|
+
const { isProcessing, error, execute } = useImageOperation();
|
|
11
|
+
|
|
12
|
+
const extractMetadata = useCallback((uri: string, options?: ImageMetadataExtractionOptions) =>
|
|
13
|
+
execute(() => ImageMetadataService.extractMetadata(uri, options), 'Failed to extract metadata'), [execute]);
|
|
14
|
+
|
|
15
|
+
const getBasicInfo = useCallback((uri: string) =>
|
|
16
|
+
execute(() => ImageMetadataService.getBasicInfo(uri), 'Failed to get basic info'), [execute]);
|
|
17
|
+
|
|
18
|
+
const hasMetadata = useCallback((uri: string) =>
|
|
19
|
+
execute(() => ImageMetadataService.hasMetadata(uri), 'Failed to check metadata'), [execute]);
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
extractMetadata,
|
|
23
|
+
getBasicInfo,
|
|
24
|
+
hasMetadata,
|
|
25
|
+
isExtracting: isProcessing,
|
|
26
|
+
metadataError: error,
|
|
27
|
+
};
|
|
28
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presentation - Image Operation Hook
|
|
3
|
+
*
|
|
4
|
+
* Generic state management for async image operations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState, useCallback } from 'react';
|
|
8
|
+
import { ImageError } from '../../infrastructure/utils/ImageErrorHandler';
|
|
9
|
+
|
|
10
|
+
export const useImageOperation = () => {
|
|
11
|
+
const [isProcessing, setIsProcessing] = useState(false);
|
|
12
|
+
const [error, setError] = useState<string | null>(null);
|
|
13
|
+
|
|
14
|
+
const execute = useCallback(async <T>(
|
|
15
|
+
operation: () => Promise<T>,
|
|
16
|
+
errorMessage: string
|
|
17
|
+
): Promise<T | null> => {
|
|
18
|
+
setIsProcessing(true);
|
|
19
|
+
setError(null);
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const result = await operation();
|
|
23
|
+
return result;
|
|
24
|
+
} catch (err) {
|
|
25
|
+
if (err instanceof ImageError) {
|
|
26
|
+
setError(err.message);
|
|
27
|
+
} else {
|
|
28
|
+
setError(err instanceof Error ? err.message : errorMessage);
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
} finally {
|
|
32
|
+
setIsProcessing(false);
|
|
33
|
+
}
|
|
34
|
+
}, []);
|
|
35
|
+
|
|
36
|
+
return { isProcessing, error, execute };
|
|
37
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presentation - Image Transform Hook
|
|
3
|
+
*/
|
|
4
|
+
import { useCallback } from 'react';
|
|
5
|
+
import { useImageOperation } from './useImageOperation';
|
|
6
|
+
import { ImageTransformService } from '../../infrastructure/services/ImageTransformService';
|
|
7
|
+
import type {
|
|
8
|
+
ImageManipulateAction,
|
|
9
|
+
ImageSaveOptions,
|
|
10
|
+
ImageCropArea,
|
|
11
|
+
ImageFlipOptions,
|
|
12
|
+
} from '../../domain/entities/ImageTypes';
|
|
13
|
+
|
|
14
|
+
export const useImageTransform = () => {
|
|
15
|
+
const { isProcessing, error, execute } = useImageOperation();
|
|
16
|
+
|
|
17
|
+
const resize = useCallback((uri: string, width?: number, height?: number, options?: ImageSaveOptions) =>
|
|
18
|
+
execute(() => ImageTransformService.resize(uri, width, height, options), 'Failed to resize'), [execute]);
|
|
19
|
+
|
|
20
|
+
const crop = useCallback((uri: string, cropArea: ImageCropArea, options?: ImageSaveOptions) =>
|
|
21
|
+
execute(() => ImageTransformService.crop(uri, cropArea, options), 'Failed to crop'), [execute]);
|
|
22
|
+
|
|
23
|
+
const rotate = useCallback((uri: string, degrees: number, options?: ImageSaveOptions) =>
|
|
24
|
+
execute(() => ImageTransformService.rotate(uri, degrees, options), 'Failed to rotate'), [execute]);
|
|
25
|
+
|
|
26
|
+
const flip = useCallback((uri: string, flipParams: ImageFlipOptions, options?: ImageSaveOptions) =>
|
|
27
|
+
execute(() => ImageTransformService.flip(uri, flipParams, options), 'Failed to flip'), [execute]);
|
|
28
|
+
|
|
29
|
+
const manipulate = useCallback((uri: string, action: ImageManipulateAction, options?: ImageSaveOptions) =>
|
|
30
|
+
execute(() => ImageTransformService.manipulate(uri, action, options), 'Failed to manipulate'), [execute]);
|
|
31
|
+
|
|
32
|
+
const resizeToFit = useCallback((uri: string, maxWidth: number, maxHeight: number, options?: ImageSaveOptions) =>
|
|
33
|
+
execute(() => ImageTransformService.resizeToFit(uri, maxWidth, maxHeight, options), 'Failed to resize to fit'), [execute]);
|
|
34
|
+
|
|
35
|
+
const cropToSquare = useCallback((uri: string, width: number, height: number, options?: ImageSaveOptions) =>
|
|
36
|
+
execute(() => ImageTransformService.cropToSquare(uri, width, height, options), 'Failed to crop square'), [execute]);
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
resize, crop, rotate, flip, manipulate, resizeToFit, cropToSquare,
|
|
40
|
+
isTransforming: isProcessing, transformError: error,
|
|
41
|
+
};
|
|
42
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -72,6 +72,11 @@ export * from './exports/infinite-scroll';
|
|
|
72
72
|
// =============================================================================
|
|
73
73
|
export * from './exports/uuid';
|
|
74
74
|
|
|
75
|
+
// =============================================================================
|
|
76
|
+
// IMAGE EXPORTS
|
|
77
|
+
// =============================================================================
|
|
78
|
+
export * from './exports/image';
|
|
79
|
+
|
|
75
80
|
// =============================================================================
|
|
76
81
|
// VARIANT UTILITIES
|
|
77
82
|
// =============================================================================
|