@umituz/react-native-photo-editor 2.0.25 → 2.0.26
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/PhotoEditor.tsx +1 -1
- package/src/application/hooks/useEditor.ts +2 -2
- package/src/application/hooks/useEditorUI.ts +1 -1
- package/src/application/stores/EditorStore.ts +27 -13
- package/src/domain/entities/Layer.entity.ts +53 -1
- package/src/domain/services/LayerFactory.service.ts +1 -1
- package/src/domain/services/LayerRepository.service.ts +6 -1
- package/src/domain/services/LayerService.ts +21 -23
- package/src/domain/value-objects/LayerDefaults.vo.ts +1 -1
- package/src/index.ts +4 -2
- package/src/infrastructure/gesture/createTransformGesture.ts +1 -1
- package/src/presentation/components/DraggableLayer.tsx +1 -1
- package/src/presentation/components/EditorCanvas.tsx +1 -1
- package/src/presentation/components/EditorContent.tsx +1 -1
- package/src/presentation/components/sheets/LayerManager.tsx +1 -1
- package/src/presentation/components/sheets/TextEditorSheet.tsx +1 -1
- package/src/types.ts +4 -3
package/package.json
CHANGED
package/src/PhotoEditor.tsx
CHANGED
|
@@ -21,7 +21,7 @@ export interface PhotoEditorProps {
|
|
|
21
21
|
onSave?: (uri: string, layers: Layer[], filters: FilterSettings) => void;
|
|
22
22
|
onClose: () => void;
|
|
23
23
|
title?: string;
|
|
24
|
-
customTools?: React.ReactNode | ((ui:
|
|
24
|
+
customTools?: React.ReactNode | ((ui: unknown) => React.ReactNode);
|
|
25
25
|
initialCaption?: string;
|
|
26
26
|
t: (key: string) => string;
|
|
27
27
|
fonts?: readonly string[];
|
|
@@ -59,7 +59,7 @@ export function useEditor() {
|
|
|
59
59
|
]);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
export type { Layer } from "
|
|
62
|
+
export type { Layer } from "../../domain/entities/Layer.entity";
|
|
63
63
|
export type { Transform } from "../../domain/entities/Transform";
|
|
64
64
|
export type { FilterValues } from "../../domain/entities/Filters";
|
|
65
|
-
export type { TextLayerData } from "
|
|
65
|
+
export type { TextContent as TextLayerData } from "../../domain/types";
|
|
@@ -7,7 +7,7 @@ import { useState, useRef, useCallback, useEffect } from "react";
|
|
|
7
7
|
import type { BottomSheetModalRef } from "@umituz/react-native-design-system/molecules";
|
|
8
8
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
9
9
|
import { useEditor } from "./useEditor";
|
|
10
|
-
import type { TextAlign } from "
|
|
10
|
+
import type { TextAlign } from "../../domain/types";
|
|
11
11
|
import type { Transform } from "../../domain/entities/Transform";
|
|
12
12
|
|
|
13
13
|
export function useEditorUI(initialCaption?: string) {
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { useState, useCallback, useMemo } from "react";
|
|
7
|
-
import { Layer
|
|
8
|
-
import {
|
|
7
|
+
import { Layer } from "../../domain/entities/Layer.entity";
|
|
8
|
+
import type { TextContent as TextLayerData } from "../../domain/types";
|
|
9
|
+
import { FilterSettings } from "../../domain/value-objects/FilterSettings.vo";
|
|
9
10
|
import { HistoryService, HistoryState } from "../../domain/services/HistoryService";
|
|
10
11
|
import { LayerService } from "../../domain/services/LayerService";
|
|
11
12
|
import type { Transform } from "../../domain/entities/Transform";
|
|
@@ -19,7 +20,7 @@ export function useEditorStore() {
|
|
|
19
20
|
historyService.createInitialState([])
|
|
20
21
|
);
|
|
21
22
|
const [activeLayerId, setActiveLayerId] = useState<string | null>(null);
|
|
22
|
-
const [filters, setFilters] = useState<
|
|
23
|
+
const [filters, setFilters] = useState<FilterSettings>(FilterSettings.DEFAULT);
|
|
23
24
|
|
|
24
25
|
// History actions
|
|
25
26
|
const pushLayers = useCallback((layers: Layer[]) => {
|
|
@@ -35,11 +36,8 @@ export function useEditorStore() {
|
|
|
35
36
|
}, []);
|
|
36
37
|
|
|
37
38
|
// Layer actions
|
|
38
|
-
const addTextLayer = useCallback((overrides?: Partial<
|
|
39
|
-
const layer = layerService.createTextLayer({
|
|
40
|
-
...overrides,
|
|
41
|
-
zIndex: history.present.length,
|
|
42
|
-
});
|
|
39
|
+
const addTextLayer = useCallback((overrides?: Partial<TextLayerData>) => {
|
|
40
|
+
const layer = layerService.createTextLayer(overrides || {});
|
|
43
41
|
pushLayers([...history.present, layer]);
|
|
44
42
|
setActiveLayerId(layer.id);
|
|
45
43
|
return layer.id;
|
|
@@ -99,13 +97,29 @@ export function useEditorStore() {
|
|
|
99
97
|
}, []);
|
|
100
98
|
|
|
101
99
|
// Filter actions
|
|
102
|
-
const updateFilters = useCallback((updates: Partial<
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
const updateFilters = useCallback((updates: Partial<{ brightness: number; contrast: number; saturation: number; sepia: number; grayscale: number; hueRotate?: number }>) => {
|
|
101
|
+
if (updates.brightness !== undefined) {
|
|
102
|
+
setFilters(filters.withBrightness(updates.brightness));
|
|
103
|
+
}
|
|
104
|
+
if (updates.contrast !== undefined) {
|
|
105
|
+
setFilters(filters.withContrast(updates.contrast));
|
|
106
|
+
}
|
|
107
|
+
if (updates.saturation !== undefined) {
|
|
108
|
+
setFilters(filters.withSaturation(updates.saturation));
|
|
109
|
+
}
|
|
110
|
+
if (updates.sepia !== undefined) {
|
|
111
|
+
setFilters(filters.withSepia(updates.sepia));
|
|
112
|
+
}
|
|
113
|
+
if (updates.grayscale !== undefined) {
|
|
114
|
+
setFilters(filters.withGrayscale(updates.grayscale));
|
|
115
|
+
}
|
|
116
|
+
if (updates.hueRotate !== undefined) {
|
|
117
|
+
setFilters(filters.withHueRotate(updates.hueRotate));
|
|
118
|
+
}
|
|
105
119
|
}, [filters]);
|
|
106
120
|
|
|
107
121
|
const resetFilters = useCallback(() => {
|
|
108
|
-
setFilters(
|
|
122
|
+
setFilters(FilterSettings.DEFAULT);
|
|
109
123
|
}, []);
|
|
110
124
|
|
|
111
125
|
// Getters
|
|
@@ -122,7 +136,7 @@ export function useEditorStore() {
|
|
|
122
136
|
layers,
|
|
123
137
|
activeLayerId,
|
|
124
138
|
activeLayer,
|
|
125
|
-
filters
|
|
139
|
+
filters,
|
|
126
140
|
canUndo,
|
|
127
141
|
canRedo,
|
|
128
142
|
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
import type { LayerType, Position, Appearance, LayerContent } from "../types";
|
|
7
7
|
import type { TextLayer } from "./TextLayer.entity";
|
|
8
8
|
import type { StickerLayer } from "./StickerLayer.entity";
|
|
9
|
+
import { TextLayer as TextLayerClass } from "./TextLayer.entity";
|
|
10
|
+
import { StickerLayer as StickerLayerClass } from "./StickerLayer.entity";
|
|
9
11
|
|
|
10
12
|
export class Layer {
|
|
11
13
|
readonly id: string;
|
|
@@ -34,6 +36,12 @@ export class Layer {
|
|
|
34
36
|
this.content = data.content;
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
// Convenience getters for backward compatibility
|
|
40
|
+
get x(): number { return this.position.x; }
|
|
41
|
+
get y(): number { return this.position.y; }
|
|
42
|
+
get zIndex(): number { return this.appearance.zIndex; }
|
|
43
|
+
get opacity(): number { return this.appearance.opacity; }
|
|
44
|
+
|
|
37
45
|
// Type guards
|
|
38
46
|
isText(): this is TextLayer {
|
|
39
47
|
return this.type === "text";
|
|
@@ -65,6 +73,10 @@ export class Layer {
|
|
|
65
73
|
});
|
|
66
74
|
}
|
|
67
75
|
|
|
76
|
+
withZIndex(zIndex: number): Layer {
|
|
77
|
+
return this.withAppearance({ zIndex });
|
|
78
|
+
}
|
|
79
|
+
|
|
68
80
|
toJSON() {
|
|
69
81
|
return {
|
|
70
82
|
id: this.id,
|
|
@@ -78,9 +90,49 @@ export class Layer {
|
|
|
78
90
|
...this.content,
|
|
79
91
|
};
|
|
80
92
|
}
|
|
93
|
+
|
|
94
|
+
static from(data: ReturnType<Layer["toJSON"]>): Layer {
|
|
95
|
+
if (data.type === "text") {
|
|
96
|
+
return new TextLayerClass({
|
|
97
|
+
id: data.id,
|
|
98
|
+
position: { x: data.x, y: data.y },
|
|
99
|
+
rotation: data.rotation,
|
|
100
|
+
scale: data.scale,
|
|
101
|
+
appearance: { opacity: data.opacity, zIndex: data.zIndex },
|
|
102
|
+
content: {
|
|
103
|
+
text: (data as Record<string, unknown>).text as string || "",
|
|
104
|
+
fontSize: (data as Record<string, unknown>).fontSize as number || 32,
|
|
105
|
+
fontFamily: (data as Record<string, unknown>).fontFamily as string || "System",
|
|
106
|
+
color: (data as Record<string, unknown>).color as string || "#FFFFFF",
|
|
107
|
+
backgroundColor: (data as Record<string, unknown>).backgroundColor as string || "transparent",
|
|
108
|
+
textAlign: (data as Record<string, unknown>).textAlign as "left" | "center" | "right" || "center",
|
|
109
|
+
isBold: (data as Record<string, unknown>).isBold as boolean || undefined,
|
|
110
|
+
isItalic: (data as Record<string, unknown>).isItalic as boolean || undefined,
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return new StickerLayerClass({
|
|
115
|
+
id: data.id,
|
|
116
|
+
position: { x: data.x, y: data.y },
|
|
117
|
+
rotation: data.rotation,
|
|
118
|
+
scale: data.scale,
|
|
119
|
+
appearance: { opacity: data.opacity, zIndex: data.zIndex },
|
|
120
|
+
content: { uri: (data as Record<string, unknown>).uri as string || "" },
|
|
121
|
+
});
|
|
122
|
+
}
|
|
81
123
|
}
|
|
82
124
|
|
|
83
|
-
//
|
|
125
|
+
// Type exports for backward compatibility
|
|
84
126
|
export type { TextLayer } from "./TextLayer.entity";
|
|
85
127
|
export type { StickerLayer } from "./StickerLayer.entity";
|
|
128
|
+
export type { TextAlign } from "../types";
|
|
129
|
+
|
|
130
|
+
// Type guard functions for backward compatibility
|
|
131
|
+
export function isTextLayer(layer: Layer): layer is TextLayer {
|
|
132
|
+
return layer.isText();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function isStickerLayer(layer: Layer): layer is StickerLayer {
|
|
136
|
+
return layer.isSticker();
|
|
137
|
+
}
|
|
86
138
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { TextLayer } from "../entities/TextLayer.entity";
|
|
7
7
|
import { StickerLayer } from "../entities/StickerLayer.entity";
|
|
8
8
|
import { LayerDefaults } from "../value-objects/LayerDefaults.vo";
|
|
9
|
-
import type { Position, Appearance, TextContent
|
|
9
|
+
import type { Position, Appearance, TextContent } from "../types";
|
|
10
10
|
|
|
11
11
|
export class LayerFactory {
|
|
12
12
|
createTextLayer(overrides?: Partial<Position & Appearance & TextContent>): TextLayer {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* CRUD operations for layers with history support
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { Layer } from "../entities/Layer.entity"
|
|
6
|
+
import type { Layer } from "../entities/Layer.entity";
|
|
7
7
|
import { LayerFactory } from "./LayerFactory.service";
|
|
8
8
|
|
|
9
9
|
export class LayerRepository {
|
|
@@ -48,6 +48,11 @@ export class LayerRepository {
|
|
|
48
48
|
const layer = layers.find((l) => l.id === layerId);
|
|
49
49
|
if (!layer) return layers;
|
|
50
50
|
|
|
51
|
+
// Type narrowing - only TextLayer and StickerLayer can be duplicated
|
|
52
|
+
if (!layer.isText() && !layer.isSticker()) {
|
|
53
|
+
return layers;
|
|
54
|
+
}
|
|
55
|
+
|
|
51
56
|
const duplicate = this.factory.duplicateLayer(layer, layers);
|
|
52
57
|
return [...layers, duplicate];
|
|
53
58
|
}
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
* Business logic for layer operations
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { Layer
|
|
6
|
+
import { Layer } from "../entities/Layer.entity";
|
|
7
|
+
import { TextLayer } from "../entities/TextLayer.entity";
|
|
8
|
+
import { StickerLayer } from "../entities/StickerLayer.entity";
|
|
9
|
+
import type { TextContent } from "../types";
|
|
7
10
|
import type { Transform } from "../entities/Transform";
|
|
8
11
|
|
|
9
12
|
export class LayerService {
|
|
@@ -11,18 +14,10 @@ export class LayerService {
|
|
|
11
14
|
return `${type}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
|
-
createTextLayer(overrides: Partial<
|
|
17
|
+
createTextLayer(overrides: Partial<TextContent> = {}): TextLayer {
|
|
15
18
|
const id = this.generateId("text");
|
|
16
|
-
const
|
|
17
|
-
id,
|
|
18
|
-
type: "text",
|
|
19
|
+
const contentDefaults: TextContent = {
|
|
19
20
|
text: "",
|
|
20
|
-
x: 50,
|
|
21
|
-
y: 50,
|
|
22
|
-
rotation: 0,
|
|
23
|
-
scale: 1,
|
|
24
|
-
opacity: 1,
|
|
25
|
-
zIndex: 0,
|
|
26
21
|
fontSize: 32,
|
|
27
22
|
fontFamily: "System",
|
|
28
23
|
color: "#FFFFFF",
|
|
@@ -32,24 +27,27 @@ export class LayerService {
|
|
|
32
27
|
isItalic: false,
|
|
33
28
|
};
|
|
34
29
|
|
|
35
|
-
return new TextLayer({
|
|
30
|
+
return new TextLayer({
|
|
31
|
+
id,
|
|
32
|
+
position: { x: 50, y: 50 },
|
|
33
|
+
rotation: 0,
|
|
34
|
+
scale: 1,
|
|
35
|
+
appearance: { opacity: 1, zIndex: 0 },
|
|
36
|
+
content: { ...contentDefaults, ...overrides },
|
|
37
|
+
});
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
createStickerLayer(uri: string,
|
|
40
|
+
createStickerLayer(uri: string, _overrides: Record<string, never> = {}): StickerLayer {
|
|
39
41
|
const id = this.generateId("sticker");
|
|
40
|
-
|
|
42
|
+
|
|
43
|
+
return new StickerLayer({
|
|
41
44
|
id,
|
|
42
|
-
|
|
43
|
-
uri,
|
|
44
|
-
x: 100,
|
|
45
|
-
y: 100,
|
|
45
|
+
position: { x: 100, y: 100 },
|
|
46
46
|
rotation: 0,
|
|
47
47
|
scale: 1,
|
|
48
|
-
opacity: 1,
|
|
49
|
-
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
return new StickerLayer({ ...defaults, ...overrides });
|
|
48
|
+
appearance: { opacity: 1, zIndex: 0 },
|
|
49
|
+
content: { uri },
|
|
50
|
+
});
|
|
53
51
|
}
|
|
54
52
|
|
|
55
53
|
updateLayer(layers: Layer[], layerId: string, updates: Partial<Transform>): Layer[] {
|
package/src/index.ts
CHANGED
|
@@ -10,10 +10,12 @@ export { PhotoEditor } from "./PhotoEditor";
|
|
|
10
10
|
export type { PhotoEditorProps } from "./PhotoEditor";
|
|
11
11
|
|
|
12
12
|
// Domain entities
|
|
13
|
-
export type { Layer, TextLayer, StickerLayer } from "
|
|
13
|
+
export type { Layer, TextLayer, StickerLayer } from "./domain/entities/Layer.entity";
|
|
14
|
+
export type { TextContent as TextLayerData, StickerContent as StickerLayerData } from "./domain/types";
|
|
15
|
+
export type { TextAlign } from "./domain/types";
|
|
14
16
|
export type { Transform } from "./domain/entities/Transform";
|
|
15
17
|
export type { FilterValues, FiltersVO } from "./domain/entities/Filters";
|
|
16
|
-
export { isTextLayer, isStickerLayer } from "
|
|
18
|
+
export { isTextLayer, isStickerLayer } from "./domain/entities/Layer.entity";
|
|
17
19
|
|
|
18
20
|
// Application hooks
|
|
19
21
|
export { useEditor } from "./application/hooks/useEditor";
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { useState, useCallback, useEffect, useRef } from "react";
|
|
7
7
|
import { Gesture } from "react-native-gesture-handler";
|
|
8
|
-
import type { Layer } from "
|
|
8
|
+
import type { Layer } from "../../domain/entities/Layer.entity";
|
|
9
9
|
|
|
10
10
|
export interface TransformGestureState {
|
|
11
11
|
position: { x: number; y: number };
|
|
@@ -11,7 +11,7 @@ import { Image } from "expo-image";
|
|
|
11
11
|
import { AtomicText } from "@umituz/react-native-design-system/atoms";
|
|
12
12
|
import { useAppDesignTokens, type DesignTokens } from "@umituz/react-native-design-system/theme";
|
|
13
13
|
import { useTransformGesture } from "../../infrastructure/gesture/useTransformGesture";
|
|
14
|
-
import type { Layer } from "
|
|
14
|
+
import type { Layer } from "../../domain/entities/Layer.entity";
|
|
15
15
|
|
|
16
16
|
interface DraggableLayerProps {
|
|
17
17
|
layer: Layer;
|
|
@@ -7,7 +7,7 @@ import React, { memo } from "react";
|
|
|
7
7
|
import { View, StyleSheet, type ViewStyle } from "react-native";
|
|
8
8
|
import { Image } from "expo-image";
|
|
9
9
|
import { DraggableLayer } from "./DraggableLayer";
|
|
10
|
-
import type { Layer } from "
|
|
10
|
+
import type { Layer } from "../../domain/entities/Layer.entity";
|
|
11
11
|
import type { FilterSettings } from "../../domain/value-objects/FilterSettings.vo";
|
|
12
12
|
|
|
13
13
|
interface EditorCanvasProps {
|
|
@@ -8,7 +8,7 @@ import { ScrollView } from "react-native";
|
|
|
8
8
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
9
9
|
import { EditorCanvas } from "./EditorCanvas";
|
|
10
10
|
import { FontControls } from "./FontControls";
|
|
11
|
-
import type { Layer } from "
|
|
11
|
+
import type { Layer } from "../../domain/entities/Layer.entity";
|
|
12
12
|
import type { FilterSettings } from "../../domain/value-objects/FilterSettings.vo";
|
|
13
13
|
import type { EditorUIState } from "../../application/hooks/useEditorUI";
|
|
14
14
|
|
|
@@ -7,7 +7,7 @@ import React, { memo } from "react";
|
|
|
7
7
|
import { View, ScrollView, TouchableOpacity } from "react-native";
|
|
8
8
|
import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system/atoms";
|
|
9
9
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
10
|
-
import { Layer } from "
|
|
10
|
+
import { Layer } from "../../../domain/entities/Layer.entity";
|
|
11
11
|
|
|
12
12
|
interface LayerManagerProps {
|
|
13
13
|
layers: Layer[];
|
|
@@ -8,7 +8,7 @@ import { View, TextInput, TouchableOpacity, StyleSheet } from "react-native";
|
|
|
8
8
|
import { AtomicText, AtomicButton } from "@umituz/react-native-design-system/atoms";
|
|
9
9
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
10
10
|
import { ColorPicker } from "../ui/ColorPicker";
|
|
11
|
-
import type { TextAlign } from "
|
|
11
|
+
import type { TextAlign } from "../../../domain/types";
|
|
12
12
|
|
|
13
13
|
interface TextEditorSheetProps {
|
|
14
14
|
value: string;
|
package/src/types.ts
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
* Re-exports domain types for convenience
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
export type { TextAlign } from "
|
|
7
|
-
export type { Layer, TextLayer, StickerLayer
|
|
6
|
+
export type { TextAlign } from "./domain/types";
|
|
7
|
+
export type { Layer, TextLayer, StickerLayer } from "./domain/entities/Layer.entity";
|
|
8
|
+
export type { TextContent as TextLayerData, StickerContent as StickerLayerData } from "./domain/types";
|
|
8
9
|
export type { Transform } from "./domain/entities/Transform";
|
|
9
10
|
export type { FilterValues as ImageFilters } from "./domain/entities/Filters";
|
|
10
11
|
|
|
11
12
|
// Re-export type guards
|
|
12
|
-
export { isTextLayer, isStickerLayer } from "
|
|
13
|
+
export { isTextLayer, isStickerLayer } from "./domain/entities/Layer.entity";
|
|
13
14
|
|
|
14
15
|
// Re-export DEFAULT_FILTERS as value (not type)
|
|
15
16
|
export { DEFAULT_FILTERS as DEFAULT_IMAGE_FILTERS } from "./domain/entities/Filters";
|