@umituz/react-native-video-editor 1.0.1
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/README.md +92 -0
- package/package.json +48 -0
- package/src/domain/entities/index.ts +50 -0
- package/src/domain/entities/video-project.types.ts +153 -0
- package/src/index.ts +100 -0
- package/src/infrastructure/constants/animation-layer.constants.ts +32 -0
- package/src/infrastructure/constants/audio-layer.constants.ts +14 -0
- package/src/infrastructure/constants/export.constants.ts +28 -0
- package/src/infrastructure/constants/image-layer.constants.ts +12 -0
- package/src/infrastructure/constants/index.ts +11 -0
- package/src/infrastructure/constants/shape-layer.constants.ts +29 -0
- package/src/infrastructure/constants/text-layer.constants.ts +40 -0
- package/src/infrastructure/services/export-orchestrator.service.ts +122 -0
- package/src/infrastructure/services/image-layer-operations.service.ts +108 -0
- package/src/infrastructure/services/layer-manipulation.service.ts +93 -0
- package/src/infrastructure/services/layer-operations/index.ts +9 -0
- package/src/infrastructure/services/layer-operations/layer-delete.service.ts +47 -0
- package/src/infrastructure/services/layer-operations/layer-duplicate.service.ts +66 -0
- package/src/infrastructure/services/layer-operations/layer-order.service.ts +82 -0
- package/src/infrastructure/services/layer-operations/layer-transform.service.ts +160 -0
- package/src/infrastructure/services/layer-operations.service.ts +198 -0
- package/src/infrastructure/services/scene-operations.service.ts +166 -0
- package/src/infrastructure/services/shape-layer-operations.service.ts +65 -0
- package/src/infrastructure/services/text-layer-operations.service.ts +114 -0
- package/src/presentation/components/AnimationEditor.tsx +103 -0
- package/src/presentation/components/AudioEditor.tsx +144 -0
- package/src/presentation/components/DraggableLayer.tsx +110 -0
- package/src/presentation/components/EditorHeader.tsx +107 -0
- package/src/presentation/components/EditorPreviewArea.tsx +221 -0
- package/src/presentation/components/EditorTimeline.tsx +136 -0
- package/src/presentation/components/EditorToolPanel.tsx +180 -0
- package/src/presentation/components/ExportDialog.tsx +135 -0
- package/src/presentation/components/ImageLayerEditor.tsx +95 -0
- package/src/presentation/components/LayerActionsMenu.tsx +197 -0
- package/src/presentation/components/SceneActionsMenu.tsx +69 -0
- package/src/presentation/components/ShapeLayerEditor.tsx +108 -0
- package/src/presentation/components/TextLayerEditor.tsx +104 -0
- package/src/presentation/components/animation-layer/AnimationEditorActions.tsx +104 -0
- package/src/presentation/components/animation-layer/AnimationInfoBanner.tsx +43 -0
- package/src/presentation/components/animation-layer/AnimationTypeSelector.tsx +105 -0
- package/src/presentation/components/animation-layer/index.ts +8 -0
- package/src/presentation/components/audio-layer/AudioEditorActions.tsx +115 -0
- package/src/presentation/components/audio-layer/AudioFileSelector.tsx +126 -0
- package/src/presentation/components/audio-layer/FadeEffectsSelector.tsx +151 -0
- package/src/presentation/components/audio-layer/InfoBanner.tsx +43 -0
- package/src/presentation/components/audio-layer/VolumeSelector.tsx +98 -0
- package/src/presentation/components/audio-layer/index.ts +10 -0
- package/src/presentation/components/draggable-layer/LayerContent.tsx +106 -0
- package/src/presentation/components/draggable-layer/ResizeHandles.tsx +97 -0
- package/src/presentation/components/draggable-layer/index.ts +7 -0
- package/src/presentation/components/export/ExportActions.tsx +101 -0
- package/src/presentation/components/export/ExportInfoBanner.tsx +44 -0
- package/src/presentation/components/export/ExportProgress.tsx +114 -0
- package/src/presentation/components/export/OptionSelectorRow.tsx +101 -0
- package/src/presentation/components/export/ProjectInfoBox.tsx +61 -0
- package/src/presentation/components/export/WatermarkToggle.tsx +87 -0
- package/src/presentation/components/export/index.ts +11 -0
- package/src/presentation/components/image-layer/ImagePreview.tsx +70 -0
- package/src/presentation/components/image-layer/ImageSelectionButtons.tsx +82 -0
- package/src/presentation/components/image-layer/OpacitySelector.tsx +91 -0
- package/src/presentation/components/image-layer/index.ts +8 -0
- package/src/presentation/components/index.ts +17 -0
- package/src/presentation/components/shape-layer/ColorPickerHorizontal.tsx +92 -0
- package/src/presentation/components/shape-layer/ShapePreview.tsx +57 -0
- package/src/presentation/components/shape-layer/ShapeTypeSelector.tsx +102 -0
- package/src/presentation/components/shape-layer/ValueSelector.tsx +106 -0
- package/src/presentation/components/shape-layer/index.ts +9 -0
- package/src/presentation/components/text-layer/ColorPicker.tsx +91 -0
- package/src/presentation/components/text-layer/EditorActions.tsx +95 -0
- package/src/presentation/components/text-layer/FontSizeSelector.tsx +86 -0
- package/src/presentation/components/text-layer/OptionSelector.tsx +98 -0
- package/src/presentation/components/text-layer/TextAlignSelector.tsx +87 -0
- package/src/presentation/components/text-layer/TextInputSection.tsx +70 -0
- package/src/presentation/components/text-layer/TextPreview.tsx +71 -0
- package/src/presentation/components/text-layer/index.ts +12 -0
- package/src/presentation/hooks/useAnimationLayerForm.ts +72 -0
- package/src/presentation/hooks/useAudioLayerForm.ts +76 -0
- package/src/presentation/hooks/useDraggableLayerGestures.ts +166 -0
- package/src/presentation/hooks/useEditorActions.tsx +93 -0
- package/src/presentation/hooks/useEditorBottomSheet.ts +43 -0
- package/src/presentation/hooks/useEditorHistory.ts +80 -0
- package/src/presentation/hooks/useEditorLayers.ts +97 -0
- package/src/presentation/hooks/useEditorPlayback.ts +90 -0
- package/src/presentation/hooks/useEditorScenes.ts +106 -0
- package/src/presentation/hooks/useExport.ts +67 -0
- package/src/presentation/hooks/useExportActions.tsx +51 -0
- package/src/presentation/hooks/useExportForm.ts +96 -0
- package/src/presentation/hooks/useImageLayerForm.ts +57 -0
- package/src/presentation/hooks/useImageLayerOperations.ts +71 -0
- package/src/presentation/hooks/useLayerActions.tsx +162 -0
- package/src/presentation/hooks/useLayerManipulation.ts +178 -0
- package/src/presentation/hooks/useMenuActions.tsx +92 -0
- package/src/presentation/hooks/useSceneActions.tsx +81 -0
- package/src/presentation/hooks/useShapeLayerForm.ts +84 -0
- package/src/presentation/hooks/useShapeLayerOperations.ts +52 -0
- package/src/presentation/hooks/useTextLayerForm.ts +100 -0
- package/src/presentation/hooks/useTextLayerOperations.ts +74 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer Operations Service (Facade)
|
|
3
|
+
* Single Responsibility: Coordinate layer operations across specialized services
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { textLayerOperationsService } from "./text-layer-operations.service";
|
|
7
|
+
import { imageLayerOperationsService } from "./image-layer-operations.service";
|
|
8
|
+
import { shapeLayerOperationsService } from "./shape-layer-operations.service";
|
|
9
|
+
import { layerManipulationService } from "./layer-manipulation.service";
|
|
10
|
+
import type { Scene, TextLayer, ImageLayer, Animation } from "@domains/video";
|
|
11
|
+
import type {
|
|
12
|
+
LayerOperationResult,
|
|
13
|
+
LayerOrderAction,
|
|
14
|
+
AddTextLayerData,
|
|
15
|
+
AddImageLayerData,
|
|
16
|
+
AddShapeLayerData,
|
|
17
|
+
} from "../../types";
|
|
18
|
+
|
|
19
|
+
class LayerOperationsService {
|
|
20
|
+
/**
|
|
21
|
+
* Add text layer to scene
|
|
22
|
+
*/
|
|
23
|
+
addTextLayer(
|
|
24
|
+
scenes: Scene[],
|
|
25
|
+
sceneIndex: number,
|
|
26
|
+
layerData: AddTextLayerData,
|
|
27
|
+
defaultColor: string,
|
|
28
|
+
): LayerOperationResult {
|
|
29
|
+
return textLayerOperationsService.addTextLayer(
|
|
30
|
+
scenes,
|
|
31
|
+
sceneIndex,
|
|
32
|
+
layerData,
|
|
33
|
+
defaultColor,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Edit text layer
|
|
39
|
+
*/
|
|
40
|
+
editTextLayer(
|
|
41
|
+
scenes: Scene[],
|
|
42
|
+
sceneIndex: number,
|
|
43
|
+
layerId: string,
|
|
44
|
+
layerData: Partial<TextLayer>,
|
|
45
|
+
): LayerOperationResult {
|
|
46
|
+
return textLayerOperationsService.editTextLayer(
|
|
47
|
+
scenes,
|
|
48
|
+
sceneIndex,
|
|
49
|
+
layerId,
|
|
50
|
+
layerData,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Add image layer to scene
|
|
56
|
+
*/
|
|
57
|
+
addImageLayer(
|
|
58
|
+
scenes: Scene[],
|
|
59
|
+
sceneIndex: number,
|
|
60
|
+
layerData: AddImageLayerData,
|
|
61
|
+
): LayerOperationResult {
|
|
62
|
+
return imageLayerOperationsService.addImageLayer(
|
|
63
|
+
scenes,
|
|
64
|
+
sceneIndex,
|
|
65
|
+
layerData,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Edit image layer
|
|
71
|
+
*/
|
|
72
|
+
editImageLayer(
|
|
73
|
+
scenes: Scene[],
|
|
74
|
+
sceneIndex: number,
|
|
75
|
+
layerId: string,
|
|
76
|
+
layerData: Partial<ImageLayer>,
|
|
77
|
+
): LayerOperationResult {
|
|
78
|
+
return imageLayerOperationsService.editImageLayer(
|
|
79
|
+
scenes,
|
|
80
|
+
sceneIndex,
|
|
81
|
+
layerId,
|
|
82
|
+
layerData,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Add shape layer to scene
|
|
88
|
+
*/
|
|
89
|
+
addShapeLayer(
|
|
90
|
+
scenes: Scene[],
|
|
91
|
+
sceneIndex: number,
|
|
92
|
+
layerData: AddShapeLayerData,
|
|
93
|
+
defaultColor: string,
|
|
94
|
+
): LayerOperationResult {
|
|
95
|
+
return shapeLayerOperationsService.addShapeLayer(
|
|
96
|
+
scenes,
|
|
97
|
+
sceneIndex,
|
|
98
|
+
layerData,
|
|
99
|
+
defaultColor,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Delete layer from scene
|
|
105
|
+
*/
|
|
106
|
+
deleteLayer(
|
|
107
|
+
scenes: Scene[],
|
|
108
|
+
sceneIndex: number,
|
|
109
|
+
layerId: string,
|
|
110
|
+
): LayerOperationResult {
|
|
111
|
+
return layerManipulationService.deleteLayer(scenes, sceneIndex, layerId);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Change layer order
|
|
116
|
+
*/
|
|
117
|
+
changeLayerOrder(
|
|
118
|
+
scenes: Scene[],
|
|
119
|
+
sceneIndex: number,
|
|
120
|
+
layerId: string,
|
|
121
|
+
action: LayerOrderAction,
|
|
122
|
+
): LayerOperationResult {
|
|
123
|
+
return layerManipulationService.changeLayerOrder(
|
|
124
|
+
scenes,
|
|
125
|
+
sceneIndex,
|
|
126
|
+
layerId,
|
|
127
|
+
action,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Duplicate layer
|
|
133
|
+
*/
|
|
134
|
+
duplicateLayer(
|
|
135
|
+
scenes: Scene[],
|
|
136
|
+
sceneIndex: number,
|
|
137
|
+
layerId: string,
|
|
138
|
+
): LayerOperationResult {
|
|
139
|
+
return layerManipulationService.duplicateLayer(scenes, sceneIndex, layerId);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Update layer position
|
|
144
|
+
*/
|
|
145
|
+
updateLayerPosition(
|
|
146
|
+
scenes: Scene[],
|
|
147
|
+
sceneIndex: number,
|
|
148
|
+
layerId: string,
|
|
149
|
+
x: number,
|
|
150
|
+
y: number,
|
|
151
|
+
): LayerOperationResult {
|
|
152
|
+
return layerManipulationService.updateLayerPosition(
|
|
153
|
+
scenes,
|
|
154
|
+
sceneIndex,
|
|
155
|
+
layerId,
|
|
156
|
+
x,
|
|
157
|
+
y,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Update layer size
|
|
163
|
+
*/
|
|
164
|
+
updateLayerSize(
|
|
165
|
+
scenes: Scene[],
|
|
166
|
+
sceneIndex: number,
|
|
167
|
+
layerId: string,
|
|
168
|
+
width: number,
|
|
169
|
+
height: number,
|
|
170
|
+
): LayerOperationResult {
|
|
171
|
+
return layerManipulationService.updateLayerSize(
|
|
172
|
+
scenes,
|
|
173
|
+
sceneIndex,
|
|
174
|
+
layerId,
|
|
175
|
+
width,
|
|
176
|
+
height,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Update layer animation
|
|
182
|
+
*/
|
|
183
|
+
updateLayerAnimation(
|
|
184
|
+
scenes: Scene[],
|
|
185
|
+
sceneIndex: number,
|
|
186
|
+
layerId: string,
|
|
187
|
+
animation: Animation | undefined,
|
|
188
|
+
): LayerOperationResult {
|
|
189
|
+
return layerManipulationService.updateLayerAnimation(
|
|
190
|
+
scenes,
|
|
191
|
+
sceneIndex,
|
|
192
|
+
layerId,
|
|
193
|
+
animation,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export const layerOperationsService = new LayerOperationsService();
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scene Operations Service
|
|
3
|
+
* Single Responsibility: Business logic for scene operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { generateUUID } from "@umituz/react-native-uuid";
|
|
7
|
+
import type { Scene, Audio } from "@domains/video";
|
|
8
|
+
import type { SceneOperationResult } from "../../types";
|
|
9
|
+
|
|
10
|
+
class SceneOperationsService {
|
|
11
|
+
/**
|
|
12
|
+
* Add new scene
|
|
13
|
+
*/
|
|
14
|
+
addScene(scenes: Scene[]): SceneOperationResult {
|
|
15
|
+
try {
|
|
16
|
+
const newScene: Scene = {
|
|
17
|
+
id: generateUUID(),
|
|
18
|
+
duration: 5000,
|
|
19
|
+
background: { type: "color", value: "#000000" },
|
|
20
|
+
layers: [],
|
|
21
|
+
transition: { type: "fade", duration: 500 },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const updatedScenes = [...scenes, newScene];
|
|
25
|
+
return {
|
|
26
|
+
success: true,
|
|
27
|
+
updatedScenes,
|
|
28
|
+
newSceneIndex: updatedScenes.length - 1,
|
|
29
|
+
};
|
|
30
|
+
} catch (error) {
|
|
31
|
+
return {
|
|
32
|
+
success: false,
|
|
33
|
+
updatedScenes: scenes,
|
|
34
|
+
error: error instanceof Error ? error.message : "Failed to add scene",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Duplicate scene
|
|
41
|
+
*/
|
|
42
|
+
duplicateScene(scenes: Scene[], sceneIndex: number): SceneOperationResult {
|
|
43
|
+
try {
|
|
44
|
+
if (sceneIndex < 0 || sceneIndex >= scenes.length) {
|
|
45
|
+
return {
|
|
46
|
+
success: false,
|
|
47
|
+
updatedScenes: scenes,
|
|
48
|
+
error: "Invalid scene index",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const sceneToDuplicate = scenes[sceneIndex];
|
|
53
|
+
const duplicatedScene: Scene = {
|
|
54
|
+
...JSON.parse(JSON.stringify(sceneToDuplicate)),
|
|
55
|
+
id: generateUUID(),
|
|
56
|
+
layers: sceneToDuplicate.layers.map((layer) => ({
|
|
57
|
+
...layer,
|
|
58
|
+
id: generateUUID(),
|
|
59
|
+
})),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const updatedScenes = [...scenes];
|
|
63
|
+
updatedScenes.splice(sceneIndex + 1, 0, duplicatedScene);
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
success: true,
|
|
67
|
+
updatedScenes,
|
|
68
|
+
newSceneIndex: sceneIndex + 1,
|
|
69
|
+
};
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return {
|
|
72
|
+
success: false,
|
|
73
|
+
updatedScenes: scenes,
|
|
74
|
+
error:
|
|
75
|
+
error instanceof Error ? error.message : "Failed to duplicate scene",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Delete scene
|
|
82
|
+
*/
|
|
83
|
+
deleteScene(
|
|
84
|
+
scenes: Scene[],
|
|
85
|
+
sceneIndex: number,
|
|
86
|
+
currentSceneIndex: number,
|
|
87
|
+
): SceneOperationResult {
|
|
88
|
+
try {
|
|
89
|
+
if (scenes.length === 1) {
|
|
90
|
+
return {
|
|
91
|
+
success: false,
|
|
92
|
+
updatedScenes: scenes,
|
|
93
|
+
error: "Cannot delete the last scene",
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (sceneIndex < 0 || sceneIndex >= scenes.length) {
|
|
98
|
+
return {
|
|
99
|
+
success: false,
|
|
100
|
+
updatedScenes: scenes,
|
|
101
|
+
error: "Invalid scene index",
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const updatedScenes = scenes.filter((_, index) => index !== sceneIndex);
|
|
106
|
+
|
|
107
|
+
let newSceneIndex = currentSceneIndex;
|
|
108
|
+
if (currentSceneIndex >= updatedScenes.length) {
|
|
109
|
+
newSceneIndex = updatedScenes.length - 1;
|
|
110
|
+
} else if (currentSceneIndex > sceneIndex) {
|
|
111
|
+
newSceneIndex = currentSceneIndex - 1;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
success: true,
|
|
116
|
+
updatedScenes,
|
|
117
|
+
newSceneIndex,
|
|
118
|
+
};
|
|
119
|
+
} catch (error) {
|
|
120
|
+
return {
|
|
121
|
+
success: false,
|
|
122
|
+
updatedScenes: scenes,
|
|
123
|
+
error:
|
|
124
|
+
error instanceof Error ? error.message : "Failed to delete scene",
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Update scene audio
|
|
131
|
+
*/
|
|
132
|
+
updateSceneAudio(
|
|
133
|
+
scenes: Scene[],
|
|
134
|
+
sceneIndex: number,
|
|
135
|
+
audio: Audio | undefined,
|
|
136
|
+
): SceneOperationResult {
|
|
137
|
+
try {
|
|
138
|
+
if (sceneIndex < 0 || sceneIndex >= scenes.length) {
|
|
139
|
+
return {
|
|
140
|
+
success: false,
|
|
141
|
+
updatedScenes: scenes,
|
|
142
|
+
error: "Invalid scene index",
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const updatedScenes = [...scenes];
|
|
147
|
+
updatedScenes[sceneIndex] = {
|
|
148
|
+
...updatedScenes[sceneIndex],
|
|
149
|
+
audio,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
return { success: true, updatedScenes };
|
|
153
|
+
} catch (error) {
|
|
154
|
+
return {
|
|
155
|
+
success: false,
|
|
156
|
+
updatedScenes: scenes,
|
|
157
|
+
error:
|
|
158
|
+
error instanceof Error
|
|
159
|
+
? error.message
|
|
160
|
+
: "Failed to update scene audio",
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export const sceneOperationsService = new SceneOperationsService();
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shape Layer Operations Service
|
|
3
|
+
* Single Responsibility: Shape layer business logic
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { generateUUID } from "@umituz/react-native-uuid";
|
|
7
|
+
import type { Scene, ShapeLayer } from "@domains/video";
|
|
8
|
+
import type { LayerOperationResult, AddShapeLayerData } from "../../types";
|
|
9
|
+
|
|
10
|
+
class ShapeLayerOperationsService {
|
|
11
|
+
/**
|
|
12
|
+
* Add shape layer to scene
|
|
13
|
+
*/
|
|
14
|
+
addShapeLayer(
|
|
15
|
+
scenes: Scene[],
|
|
16
|
+
sceneIndex: number,
|
|
17
|
+
layerData: AddShapeLayerData,
|
|
18
|
+
defaultColor: string,
|
|
19
|
+
): LayerOperationResult {
|
|
20
|
+
try {
|
|
21
|
+
if (sceneIndex < 0 || sceneIndex >= scenes.length) {
|
|
22
|
+
return {
|
|
23
|
+
success: false,
|
|
24
|
+
updatedScenes: scenes,
|
|
25
|
+
error: "Invalid scene index",
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const newLayer: ShapeLayer = {
|
|
30
|
+
id: generateUUID(),
|
|
31
|
+
type: "shape",
|
|
32
|
+
shape: (layerData.shape as ShapeLayer["shape"]) || "rectangle",
|
|
33
|
+
position: { x: 25, y: 25 },
|
|
34
|
+
size: { width: 50, height: 50 },
|
|
35
|
+
rotation: 0,
|
|
36
|
+
opacity: layerData.opacity || 1,
|
|
37
|
+
fillColor: layerData.fillColor || defaultColor,
|
|
38
|
+
borderColor: layerData.borderColor,
|
|
39
|
+
borderWidth: layerData.borderWidth,
|
|
40
|
+
animation: {
|
|
41
|
+
type: "fade",
|
|
42
|
+
duration: 500,
|
|
43
|
+
easing: "ease-in-out",
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const updatedScenes = [...scenes];
|
|
48
|
+
updatedScenes[sceneIndex] = {
|
|
49
|
+
...updatedScenes[sceneIndex],
|
|
50
|
+
layers: [...updatedScenes[sceneIndex].layers, newLayer],
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return { success: true, updatedScenes };
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return {
|
|
56
|
+
success: false,
|
|
57
|
+
updatedScenes: scenes,
|
|
58
|
+
error:
|
|
59
|
+
error instanceof Error ? error.message : "Failed to add shape layer",
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const shapeLayerOperationsService = new ShapeLayerOperationsService();
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text Layer Operations Service
|
|
3
|
+
* Single Responsibility: Text layer business logic
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { generateUUID } from "@umituz/react-native-uuid";
|
|
7
|
+
import type { Scene, TextLayer } from "@domains/video";
|
|
8
|
+
import type { LayerOperationResult, AddTextLayerData } from "../../types";
|
|
9
|
+
|
|
10
|
+
class TextLayerOperationsService {
|
|
11
|
+
/**
|
|
12
|
+
* Add text layer to scene
|
|
13
|
+
*/
|
|
14
|
+
addTextLayer(
|
|
15
|
+
scenes: Scene[],
|
|
16
|
+
sceneIndex: number,
|
|
17
|
+
layerData: AddTextLayerData,
|
|
18
|
+
defaultColor: string,
|
|
19
|
+
): LayerOperationResult {
|
|
20
|
+
try {
|
|
21
|
+
if (sceneIndex < 0 || sceneIndex >= scenes.length) {
|
|
22
|
+
return {
|
|
23
|
+
success: false,
|
|
24
|
+
updatedScenes: scenes,
|
|
25
|
+
error: "Invalid scene index",
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const newLayer: TextLayer = {
|
|
30
|
+
id: generateUUID(),
|
|
31
|
+
type: "text",
|
|
32
|
+
content: layerData.content || "",
|
|
33
|
+
position: { x: 10, y: 40 },
|
|
34
|
+
size: { width: 80, height: 20 },
|
|
35
|
+
rotation: 0,
|
|
36
|
+
opacity: 1,
|
|
37
|
+
fontSize: layerData.fontSize || 48,
|
|
38
|
+
fontFamily: layerData.fontFamily || "System",
|
|
39
|
+
fontWeight: (layerData.fontWeight as TextLayer["fontWeight"]) || "bold",
|
|
40
|
+
color: layerData.color || defaultColor,
|
|
41
|
+
textAlign: layerData.textAlign || "center",
|
|
42
|
+
animation: {
|
|
43
|
+
type: "fade",
|
|
44
|
+
duration: 500,
|
|
45
|
+
easing: "ease-in-out",
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const updatedScenes = [...scenes];
|
|
50
|
+
updatedScenes[sceneIndex] = {
|
|
51
|
+
...updatedScenes[sceneIndex],
|
|
52
|
+
layers: [...updatedScenes[sceneIndex].layers, newLayer],
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return { success: true, updatedScenes };
|
|
56
|
+
} catch (error) {
|
|
57
|
+
return {
|
|
58
|
+
success: false,
|
|
59
|
+
updatedScenes: scenes,
|
|
60
|
+
error:
|
|
61
|
+
error instanceof Error ? error.message : "Failed to add text layer",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Edit text layer
|
|
68
|
+
*/
|
|
69
|
+
editTextLayer(
|
|
70
|
+
scenes: Scene[],
|
|
71
|
+
sceneIndex: number,
|
|
72
|
+
layerId: string,
|
|
73
|
+
layerData: Partial<TextLayer>,
|
|
74
|
+
): LayerOperationResult {
|
|
75
|
+
try {
|
|
76
|
+
if (sceneIndex < 0 || sceneIndex >= scenes.length) {
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
updatedScenes: scenes,
|
|
80
|
+
error: "Invalid scene index",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const updatedScenes = [...scenes];
|
|
85
|
+
const layerIndex = updatedScenes[sceneIndex].layers.findIndex(
|
|
86
|
+
(l) => l.id === layerId,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
if (layerIndex === -1) {
|
|
90
|
+
return {
|
|
91
|
+
success: false,
|
|
92
|
+
updatedScenes: scenes,
|
|
93
|
+
error: "Layer not found",
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
updatedScenes[sceneIndex].layers[layerIndex] = {
|
|
98
|
+
...updatedScenes[sceneIndex].layers[layerIndex],
|
|
99
|
+
...layerData,
|
|
100
|
+
} as TextLayer;
|
|
101
|
+
|
|
102
|
+
return { success: true, updatedScenes };
|
|
103
|
+
} catch (error) {
|
|
104
|
+
return {
|
|
105
|
+
success: false,
|
|
106
|
+
updatedScenes: scenes,
|
|
107
|
+
error:
|
|
108
|
+
error instanceof Error ? error.message : "Failed to edit text layer",
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export const textLayerOperationsService = new TextLayerOperationsService();
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AnimationEditor Component
|
|
3
|
+
* Main component for editing animation layers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, ScrollView, StyleSheet } from "react-native";
|
|
8
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
9
|
+
import type { Animation } from "@domains/video";
|
|
10
|
+
import { useAnimationLayerForm } from "../../hooks/useAnimationLayerForm";
|
|
11
|
+
import {
|
|
12
|
+
DURATIONS,
|
|
13
|
+
DELAYS,
|
|
14
|
+
EASINGS,
|
|
15
|
+
type Easing,
|
|
16
|
+
} from "../../constants/animation-layer.constants";
|
|
17
|
+
import { ValueSelector } from "./shape-layer/ValueSelector";
|
|
18
|
+
import { OptionSelector } from "./text-layer/OptionSelector";
|
|
19
|
+
import {
|
|
20
|
+
AnimationTypeSelector,
|
|
21
|
+
AnimationEditorActions,
|
|
22
|
+
} from "./animation-layer";
|
|
23
|
+
import { AnimationInfoBanner } from "./animation-layer";
|
|
24
|
+
|
|
25
|
+
interface AnimationEditorProps {
|
|
26
|
+
animation?: Animation;
|
|
27
|
+
onSave: (animation: Animation) => void;
|
|
28
|
+
onRemove?: () => void;
|
|
29
|
+
onCancel: () => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const AnimationEditor: React.FC<AnimationEditorProps> = ({
|
|
33
|
+
animation,
|
|
34
|
+
onSave,
|
|
35
|
+
onRemove,
|
|
36
|
+
onCancel,
|
|
37
|
+
}) => {
|
|
38
|
+
const {
|
|
39
|
+
formState,
|
|
40
|
+
setAnimationType,
|
|
41
|
+
setDuration,
|
|
42
|
+
setDelay,
|
|
43
|
+
setEasing,
|
|
44
|
+
buildAnimationData,
|
|
45
|
+
} = useAnimationLayerForm(animation);
|
|
46
|
+
|
|
47
|
+
const handleSave = () => {
|
|
48
|
+
onSave(buildAnimationData());
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<View style={styles.container}>
|
|
53
|
+
<ScrollView showsVerticalScrollIndicator={false}>
|
|
54
|
+
<AnimationTypeSelector
|
|
55
|
+
selectedType={formState.animationType}
|
|
56
|
+
onTypeChange={setAnimationType}
|
|
57
|
+
/>
|
|
58
|
+
|
|
59
|
+
{formState.animationType !== "none" && (
|
|
60
|
+
<>
|
|
61
|
+
<ValueSelector
|
|
62
|
+
title="Duration"
|
|
63
|
+
value={formState.duration}
|
|
64
|
+
options={DURATIONS}
|
|
65
|
+
formatValue={(val) => `${val}ms`}
|
|
66
|
+
onValueChange={setDuration}
|
|
67
|
+
/>
|
|
68
|
+
|
|
69
|
+
<ValueSelector
|
|
70
|
+
title="Delay"
|
|
71
|
+
value={formState.delay}
|
|
72
|
+
options={DELAYS}
|
|
73
|
+
formatValue={(val) => `${val}ms`}
|
|
74
|
+
onValueChange={setDelay}
|
|
75
|
+
/>
|
|
76
|
+
|
|
77
|
+
<OptionSelector
|
|
78
|
+
title="Easing"
|
|
79
|
+
options={EASINGS}
|
|
80
|
+
selectedValue={formState.easing}
|
|
81
|
+
onValueChange={(value) => setEasing(value as Easing)}
|
|
82
|
+
/>
|
|
83
|
+
|
|
84
|
+
<AnimationInfoBanner />
|
|
85
|
+
</>
|
|
86
|
+
)}
|
|
87
|
+
</ScrollView>
|
|
88
|
+
|
|
89
|
+
<AnimationEditorActions
|
|
90
|
+
hasAnimation={!!animation}
|
|
91
|
+
onRemove={onRemove}
|
|
92
|
+
onCancel={onCancel}
|
|
93
|
+
onSave={handleSave}
|
|
94
|
+
/>
|
|
95
|
+
</View>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const styles = StyleSheet.create({
|
|
100
|
+
container: {
|
|
101
|
+
paddingVertical: 16,
|
|
102
|
+
},
|
|
103
|
+
});
|