@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.
Files changed (97) hide show
  1. package/README.md +92 -0
  2. package/package.json +48 -0
  3. package/src/domain/entities/index.ts +50 -0
  4. package/src/domain/entities/video-project.types.ts +153 -0
  5. package/src/index.ts +100 -0
  6. package/src/infrastructure/constants/animation-layer.constants.ts +32 -0
  7. package/src/infrastructure/constants/audio-layer.constants.ts +14 -0
  8. package/src/infrastructure/constants/export.constants.ts +28 -0
  9. package/src/infrastructure/constants/image-layer.constants.ts +12 -0
  10. package/src/infrastructure/constants/index.ts +11 -0
  11. package/src/infrastructure/constants/shape-layer.constants.ts +29 -0
  12. package/src/infrastructure/constants/text-layer.constants.ts +40 -0
  13. package/src/infrastructure/services/export-orchestrator.service.ts +122 -0
  14. package/src/infrastructure/services/image-layer-operations.service.ts +108 -0
  15. package/src/infrastructure/services/layer-manipulation.service.ts +93 -0
  16. package/src/infrastructure/services/layer-operations/index.ts +9 -0
  17. package/src/infrastructure/services/layer-operations/layer-delete.service.ts +47 -0
  18. package/src/infrastructure/services/layer-operations/layer-duplicate.service.ts +66 -0
  19. package/src/infrastructure/services/layer-operations/layer-order.service.ts +82 -0
  20. package/src/infrastructure/services/layer-operations/layer-transform.service.ts +160 -0
  21. package/src/infrastructure/services/layer-operations.service.ts +198 -0
  22. package/src/infrastructure/services/scene-operations.service.ts +166 -0
  23. package/src/infrastructure/services/shape-layer-operations.service.ts +65 -0
  24. package/src/infrastructure/services/text-layer-operations.service.ts +114 -0
  25. package/src/presentation/components/AnimationEditor.tsx +103 -0
  26. package/src/presentation/components/AudioEditor.tsx +144 -0
  27. package/src/presentation/components/DraggableLayer.tsx +110 -0
  28. package/src/presentation/components/EditorHeader.tsx +107 -0
  29. package/src/presentation/components/EditorPreviewArea.tsx +221 -0
  30. package/src/presentation/components/EditorTimeline.tsx +136 -0
  31. package/src/presentation/components/EditorToolPanel.tsx +180 -0
  32. package/src/presentation/components/ExportDialog.tsx +135 -0
  33. package/src/presentation/components/ImageLayerEditor.tsx +95 -0
  34. package/src/presentation/components/LayerActionsMenu.tsx +197 -0
  35. package/src/presentation/components/SceneActionsMenu.tsx +69 -0
  36. package/src/presentation/components/ShapeLayerEditor.tsx +108 -0
  37. package/src/presentation/components/TextLayerEditor.tsx +104 -0
  38. package/src/presentation/components/animation-layer/AnimationEditorActions.tsx +104 -0
  39. package/src/presentation/components/animation-layer/AnimationInfoBanner.tsx +43 -0
  40. package/src/presentation/components/animation-layer/AnimationTypeSelector.tsx +105 -0
  41. package/src/presentation/components/animation-layer/index.ts +8 -0
  42. package/src/presentation/components/audio-layer/AudioEditorActions.tsx +115 -0
  43. package/src/presentation/components/audio-layer/AudioFileSelector.tsx +126 -0
  44. package/src/presentation/components/audio-layer/FadeEffectsSelector.tsx +151 -0
  45. package/src/presentation/components/audio-layer/InfoBanner.tsx +43 -0
  46. package/src/presentation/components/audio-layer/VolumeSelector.tsx +98 -0
  47. package/src/presentation/components/audio-layer/index.ts +10 -0
  48. package/src/presentation/components/draggable-layer/LayerContent.tsx +106 -0
  49. package/src/presentation/components/draggable-layer/ResizeHandles.tsx +97 -0
  50. package/src/presentation/components/draggable-layer/index.ts +7 -0
  51. package/src/presentation/components/export/ExportActions.tsx +101 -0
  52. package/src/presentation/components/export/ExportInfoBanner.tsx +44 -0
  53. package/src/presentation/components/export/ExportProgress.tsx +114 -0
  54. package/src/presentation/components/export/OptionSelectorRow.tsx +101 -0
  55. package/src/presentation/components/export/ProjectInfoBox.tsx +61 -0
  56. package/src/presentation/components/export/WatermarkToggle.tsx +87 -0
  57. package/src/presentation/components/export/index.ts +11 -0
  58. package/src/presentation/components/image-layer/ImagePreview.tsx +70 -0
  59. package/src/presentation/components/image-layer/ImageSelectionButtons.tsx +82 -0
  60. package/src/presentation/components/image-layer/OpacitySelector.tsx +91 -0
  61. package/src/presentation/components/image-layer/index.ts +8 -0
  62. package/src/presentation/components/index.ts +17 -0
  63. package/src/presentation/components/shape-layer/ColorPickerHorizontal.tsx +92 -0
  64. package/src/presentation/components/shape-layer/ShapePreview.tsx +57 -0
  65. package/src/presentation/components/shape-layer/ShapeTypeSelector.tsx +102 -0
  66. package/src/presentation/components/shape-layer/ValueSelector.tsx +106 -0
  67. package/src/presentation/components/shape-layer/index.ts +9 -0
  68. package/src/presentation/components/text-layer/ColorPicker.tsx +91 -0
  69. package/src/presentation/components/text-layer/EditorActions.tsx +95 -0
  70. package/src/presentation/components/text-layer/FontSizeSelector.tsx +86 -0
  71. package/src/presentation/components/text-layer/OptionSelector.tsx +98 -0
  72. package/src/presentation/components/text-layer/TextAlignSelector.tsx +87 -0
  73. package/src/presentation/components/text-layer/TextInputSection.tsx +70 -0
  74. package/src/presentation/components/text-layer/TextPreview.tsx +71 -0
  75. package/src/presentation/components/text-layer/index.ts +12 -0
  76. package/src/presentation/hooks/useAnimationLayerForm.ts +72 -0
  77. package/src/presentation/hooks/useAudioLayerForm.ts +76 -0
  78. package/src/presentation/hooks/useDraggableLayerGestures.ts +166 -0
  79. package/src/presentation/hooks/useEditorActions.tsx +93 -0
  80. package/src/presentation/hooks/useEditorBottomSheet.ts +43 -0
  81. package/src/presentation/hooks/useEditorHistory.ts +80 -0
  82. package/src/presentation/hooks/useEditorLayers.ts +97 -0
  83. package/src/presentation/hooks/useEditorPlayback.ts +90 -0
  84. package/src/presentation/hooks/useEditorScenes.ts +106 -0
  85. package/src/presentation/hooks/useExport.ts +67 -0
  86. package/src/presentation/hooks/useExportActions.tsx +51 -0
  87. package/src/presentation/hooks/useExportForm.ts +96 -0
  88. package/src/presentation/hooks/useImageLayerForm.ts +57 -0
  89. package/src/presentation/hooks/useImageLayerOperations.ts +71 -0
  90. package/src/presentation/hooks/useLayerActions.tsx +162 -0
  91. package/src/presentation/hooks/useLayerManipulation.ts +178 -0
  92. package/src/presentation/hooks/useMenuActions.tsx +92 -0
  93. package/src/presentation/hooks/useSceneActions.tsx +81 -0
  94. package/src/presentation/hooks/useShapeLayerForm.ts +84 -0
  95. package/src/presentation/hooks/useShapeLayerOperations.ts +52 -0
  96. package/src/presentation/hooks/useTextLayerForm.ts +100 -0
  97. package/src/presentation/hooks/useTextLayerOperations.ts +74 -0
@@ -0,0 +1,96 @@
1
+ /**
2
+ * useExportForm Hook
3
+ * Manages form state for export dialog
4
+ */
5
+
6
+ import { useState, useCallback, useMemo } from "react";
7
+ import type { ExportSettings, VideoProject } from "@domains/video";
8
+ import type {
9
+ Resolution,
10
+ Quality,
11
+ Format,
12
+ } from "../constants/export.constants";
13
+ import {
14
+ BASE_SIZE_PER_SECOND,
15
+ RESOLUTION_MULTIPLIERS,
16
+ QUALITY_MULTIPLIERS,
17
+ } from "../constants/export.constants";
18
+
19
+ export interface ExportFormState {
20
+ resolution: Resolution;
21
+ quality: Quality;
22
+ format: Format;
23
+ includeWatermark: boolean;
24
+ }
25
+
26
+ export interface UseExportFormReturn {
27
+ formState: ExportFormState;
28
+ setResolution: (resolution: Resolution) => void;
29
+ setQuality: (quality: Quality) => void;
30
+ setFormat: (format: Format) => void;
31
+ setIncludeWatermark: (include: boolean) => void;
32
+ buildExportSettings: () => ExportSettings;
33
+ estimatedSize: string;
34
+ projectDuration: number;
35
+ }
36
+
37
+ /**
38
+ * Hook for managing export form state
39
+ */
40
+ export function useExportForm(project: VideoProject): UseExportFormReturn {
41
+ const [formState, setFormState] = useState<ExportFormState>({
42
+ resolution: "1080p",
43
+ quality: "high",
44
+ format: "mp4",
45
+ includeWatermark: false,
46
+ });
47
+
48
+ const projectDuration = useMemo(() => {
49
+ return (
50
+ project.scenes.reduce((acc, scene) => acc + scene.duration, 0) / 1000
51
+ );
52
+ }, [project.scenes]);
53
+
54
+ const estimatedSize = useMemo(() => {
55
+ const baseSize = projectDuration * BASE_SIZE_PER_SECOND;
56
+ const resolutionMultiplier = RESOLUTION_MULTIPLIERS[formState.resolution];
57
+ const qualityMultiplier = QUALITY_MULTIPLIERS[formState.quality];
58
+ return (baseSize * resolutionMultiplier * qualityMultiplier).toFixed(1);
59
+ }, [projectDuration, formState.resolution, formState.quality]);
60
+
61
+ const setResolution = useCallback((resolution: Resolution) => {
62
+ setFormState((prev) => ({ ...prev, resolution }));
63
+ }, []);
64
+
65
+ const setQuality = useCallback((quality: Quality) => {
66
+ setFormState((prev) => ({ ...prev, quality }));
67
+ }, []);
68
+
69
+ const setFormat = useCallback((format: Format) => {
70
+ setFormState((prev) => ({ ...prev, format }));
71
+ }, []);
72
+
73
+ const setIncludeWatermark = useCallback((include: boolean) => {
74
+ setFormState((prev) => ({ ...prev, includeWatermark: include }));
75
+ }, []);
76
+
77
+ const buildExportSettings = useCallback((): ExportSettings => {
78
+ return {
79
+ resolution: formState.resolution,
80
+ quality: formState.quality,
81
+ format: formState.format,
82
+ includeWatermark: formState.includeWatermark,
83
+ };
84
+ }, [formState]);
85
+
86
+ return {
87
+ formState,
88
+ setResolution,
89
+ setQuality,
90
+ setFormat,
91
+ setIncludeWatermark,
92
+ buildExportSettings,
93
+ estimatedSize,
94
+ projectDuration,
95
+ };
96
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * useImageLayerForm Hook
3
+ * Manages form state for image layer editor
4
+ */
5
+
6
+ import { useState, useCallback } from "react";
7
+ import type { ImageLayer } from "@domains/video";
8
+
9
+ export interface ImageLayerFormState {
10
+ imageUri: string;
11
+ opacity: number;
12
+ }
13
+
14
+ export interface UseImageLayerFormReturn {
15
+ formState: ImageLayerFormState;
16
+ setImageUri: (uri: string) => void;
17
+ setOpacity: (opacity: number) => void;
18
+ buildLayerData: () => Partial<ImageLayer>;
19
+ isValid: boolean;
20
+ }
21
+
22
+ /**
23
+ * Hook for managing image layer form state
24
+ */
25
+ export function useImageLayerForm(
26
+ initialLayer?: ImageLayer,
27
+ ): UseImageLayerFormReturn {
28
+ const [formState, setFormState] = useState<ImageLayerFormState>({
29
+ imageUri: initialLayer?.uri || "",
30
+ opacity: initialLayer?.opacity || 1,
31
+ });
32
+
33
+ const setImageUri = useCallback((uri: string) => {
34
+ setFormState((prev) => ({ ...prev, imageUri: uri }));
35
+ }, []);
36
+
37
+ const setOpacity = useCallback((opacity: number) => {
38
+ setFormState((prev) => ({ ...prev, opacity }));
39
+ }, []);
40
+
41
+ const buildLayerData = useCallback((): Partial<ImageLayer> => {
42
+ return {
43
+ uri: formState.imageUri,
44
+ opacity: formState.opacity,
45
+ };
46
+ }, [formState]);
47
+
48
+ const isValid = formState.imageUri.length > 0;
49
+
50
+ return {
51
+ formState,
52
+ setImageUri,
53
+ setOpacity,
54
+ buildLayerData,
55
+ isValid,
56
+ };
57
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * useImageLayerOperations Hook
3
+ * Single Responsibility: Image layer operations (add, edit)
4
+ */
5
+
6
+ import { useCallback } from "react";
7
+ import { Alert } from "react-native";
8
+ import { layerOperationsService } from "../infrastructure/services/layer-operations.service";
9
+ import type { AddImageLayerData } from "../types";
10
+ import type { ImageLayer } from "@domains/video";
11
+
12
+ export interface UseImageLayerOperationsParams {
13
+ scenes: any[];
14
+ sceneIndex: number;
15
+ onUpdateScenes: (scenes: any[]) => void;
16
+ onCloseBottomSheet: () => void;
17
+ }
18
+
19
+ export interface UseImageLayerOperationsReturn {
20
+ addImageLayer: (data: AddImageLayerData) => void;
21
+ editImageLayer: (layerId: string, data: Partial<ImageLayer>) => void;
22
+ }
23
+
24
+ export function useImageLayerOperations({
25
+ scenes,
26
+ sceneIndex,
27
+ onUpdateScenes,
28
+ onCloseBottomSheet,
29
+ }: UseImageLayerOperationsParams): UseImageLayerOperationsReturn {
30
+ const addImageLayer = useCallback(
31
+ (data: AddImageLayerData) => {
32
+ const result = layerOperationsService.addImageLayer(
33
+ scenes,
34
+ sceneIndex,
35
+ data,
36
+ );
37
+ if (result.success) {
38
+ onUpdateScenes(result.updatedScenes);
39
+ onCloseBottomSheet();
40
+ Alert.alert("Success", "Image layer added!");
41
+ } else {
42
+ Alert.alert("Error", result.error || "Failed to add image layer");
43
+ }
44
+ },
45
+ [scenes, sceneIndex, onUpdateScenes, onCloseBottomSheet],
46
+ );
47
+
48
+ const editImageLayer = useCallback(
49
+ (layerId: string, data: Partial<ImageLayer>) => {
50
+ const result = layerOperationsService.editImageLayer(
51
+ scenes,
52
+ sceneIndex,
53
+ layerId,
54
+ data,
55
+ );
56
+ if (result.success) {
57
+ onUpdateScenes(result.updatedScenes);
58
+ onCloseBottomSheet();
59
+ Alert.alert("Success", "Image layer updated!");
60
+ } else {
61
+ Alert.alert("Error", result.error || "Failed to update image layer");
62
+ }
63
+ },
64
+ [scenes, sceneIndex, onUpdateScenes, onCloseBottomSheet],
65
+ );
66
+
67
+ return {
68
+ addImageLayer,
69
+ editImageLayer,
70
+ };
71
+ }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * useLayerActions Hook
3
+ * Single Responsibility: Layer action handlers (add, edit, animate)
4
+ */
5
+
6
+ import { useCallback } from "react";
7
+ import { TextLayerEditor } from "../presentation/components/TextLayerEditor";
8
+ import { ImageLayerEditor } from "../presentation/components/ImageLayerEditor";
9
+ import { ShapeLayerEditor } from "../presentation/components/ShapeLayerEditor";
10
+ import { AnimationEditor } from "../presentation/components/AnimationEditor";
11
+ import type { ImageLayer } from "@domains/video";
12
+ import type { UseEditorLayersReturn } from "./useEditorLayers";
13
+ import type { UseEditorBottomSheetReturn } from "./useEditorBottomSheet";
14
+
15
+ export interface UseLayerActionsParams {
16
+ selectedLayerId: string | null;
17
+ currentScene: any;
18
+ layers: UseEditorLayersReturn;
19
+ bottomSheet: UseEditorBottomSheetReturn;
20
+ }
21
+
22
+ export interface UseLayerActionsReturn {
23
+ handleAddText: () => void;
24
+ handleEditLayer: () => void;
25
+ handleAddImage: () => void;
26
+ handleEditImageLayer: (layerId: string) => void;
27
+ handleAddShape: () => void;
28
+ handleAnimate: (layerId: string) => void;
29
+ }
30
+
31
+ export function useLayerActions({
32
+ selectedLayerId,
33
+ currentScene,
34
+ layers,
35
+ bottomSheet,
36
+ }: UseLayerActionsParams): UseLayerActionsReturn {
37
+ const { openBottomSheet, closeBottomSheet } = bottomSheet;
38
+
39
+ const handleAddText = useCallback(() => {
40
+ openBottomSheet({
41
+ title: "Add Text Layer",
42
+ children: (
43
+ <TextLayerEditor
44
+ onSave={layers.addTextLayer}
45
+ onCancel={closeBottomSheet}
46
+ />
47
+ ),
48
+ });
49
+ }, [layers.addTextLayer, openBottomSheet, closeBottomSheet]);
50
+
51
+ const handleEditLayer = useCallback(() => {
52
+ if (!selectedLayerId || !currentScene) return;
53
+ const layer = currentScene.layers.find(
54
+ (l: any) => l.id === selectedLayerId,
55
+ );
56
+ if (!layer || layer.type !== "text") return;
57
+
58
+ openBottomSheet({
59
+ title: "Edit Text Layer",
60
+ children: (
61
+ <TextLayerEditor
62
+ layer={layer}
63
+ onSave={(data) => layers.editTextLayer(selectedLayerId, data)}
64
+ onCancel={closeBottomSheet}
65
+ />
66
+ ),
67
+ });
68
+ }, [
69
+ selectedLayerId,
70
+ currentScene,
71
+ layers.editTextLayer,
72
+ openBottomSheet,
73
+ closeBottomSheet,
74
+ ]);
75
+
76
+ const handleAddImage = useCallback(() => {
77
+ openBottomSheet({
78
+ title: "Add Image Layer",
79
+ children: (
80
+ <ImageLayerEditor
81
+ onSave={layers.addImageLayer}
82
+ onCancel={closeBottomSheet}
83
+ />
84
+ ),
85
+ });
86
+ }, [layers.addImageLayer, openBottomSheet, closeBottomSheet]);
87
+
88
+ const handleEditImageLayer = useCallback(
89
+ (layerId: string) => {
90
+ if (!currentScene) return;
91
+ const layer = currentScene.layers.find((l: any) => l.id === layerId) as
92
+ | ImageLayer
93
+ | undefined;
94
+ if (!layer) return;
95
+
96
+ openBottomSheet({
97
+ title: "Edit Image Layer",
98
+ children: (
99
+ <ImageLayerEditor
100
+ layer={layer}
101
+ onSave={(data) => layers.editImageLayer(layerId, data)}
102
+ onCancel={closeBottomSheet}
103
+ />
104
+ ),
105
+ });
106
+ },
107
+ [currentScene, layers.editImageLayer, openBottomSheet, closeBottomSheet],
108
+ );
109
+
110
+ const handleAddShape = useCallback(() => {
111
+ openBottomSheet({
112
+ title: "Add Shape Layer",
113
+ children: (
114
+ <ShapeLayerEditor
115
+ onSave={layers.addShapeLayer}
116
+ onCancel={closeBottomSheet}
117
+ />
118
+ ),
119
+ });
120
+ }, [layers.addShapeLayer, openBottomSheet, closeBottomSheet]);
121
+
122
+ const handleAnimate = useCallback(
123
+ (layerId: string) => {
124
+ if (!currentScene) return;
125
+ const layer = currentScene.layers.find((l: any) => l.id === layerId);
126
+ if (!layer) return;
127
+
128
+ openBottomSheet({
129
+ title: layer.animation ? "Edit Animation" : "Add Animation",
130
+ children: (
131
+ <AnimationEditor
132
+ animation={layer.animation}
133
+ onSave={(animation) =>
134
+ layers.updateLayerAnimation(layerId, animation)
135
+ }
136
+ onRemove={
137
+ layer.animation
138
+ ? () => layers.updateLayerAnimation(layerId, undefined)
139
+ : undefined
140
+ }
141
+ onCancel={closeBottomSheet}
142
+ />
143
+ ),
144
+ });
145
+ },
146
+ [
147
+ currentScene,
148
+ layers.updateLayerAnimation,
149
+ openBottomSheet,
150
+ closeBottomSheet,
151
+ ],
152
+ );
153
+
154
+ return {
155
+ handleAddText,
156
+ handleEditLayer,
157
+ handleAddImage,
158
+ handleEditImageLayer,
159
+ handleAddShape,
160
+ handleAnimate,
161
+ };
162
+ }
@@ -0,0 +1,178 @@
1
+ /**
2
+ * useLayerManipulation Hook
3
+ * Single Responsibility: Layer manipulation operations (delete, order, duplicate, position, size, animation)
4
+ */
5
+
6
+ import { useCallback } from "react";
7
+ import { Alert } from "react-native";
8
+ import { layerOperationsService } from "../infrastructure/services/layer-operations.service";
9
+ import type { LayerOrderAction } from "../types";
10
+ import type { Animation } from "@domains/video";
11
+
12
+ export interface UseLayerManipulationParams {
13
+ scenes: any[];
14
+ sceneIndex: number;
15
+ onUpdateScenes: (scenes: any[]) => void;
16
+ onCloseBottomSheet: () => void;
17
+ onLayerDeleted?: () => void;
18
+ }
19
+
20
+ export interface UseLayerManipulationReturn {
21
+ deleteLayer: (layerId: string) => void;
22
+ changeLayerOrder: (layerId: string, action: LayerOrderAction) => void;
23
+ duplicateLayer: (layerId: string) => void;
24
+ updateLayerPosition: (layerId: string, x: number, y: number) => void;
25
+ updateLayerSize: (layerId: string, width: number, height: number) => void;
26
+ updateLayerAnimation: (
27
+ layerId: string,
28
+ animation: Animation | undefined,
29
+ ) => void;
30
+ }
31
+
32
+ export function useLayerManipulation({
33
+ scenes,
34
+ sceneIndex,
35
+ onUpdateScenes,
36
+ onCloseBottomSheet,
37
+ onLayerDeleted,
38
+ }: UseLayerManipulationParams): UseLayerManipulationReturn {
39
+ const deleteLayer = useCallback(
40
+ (layerId: string) => {
41
+ Alert.alert(
42
+ "Delete Layer",
43
+ "Are you sure you want to delete this layer?",
44
+ [
45
+ { text: "Cancel", style: "cancel" },
46
+ {
47
+ text: "Delete",
48
+ style: "destructive",
49
+ onPress: () => {
50
+ const result = layerOperationsService.deleteLayer(
51
+ scenes,
52
+ sceneIndex,
53
+ layerId,
54
+ );
55
+ if (result.success) {
56
+ onUpdateScenes(result.updatedScenes);
57
+ onLayerDeleted?.();
58
+ Alert.alert("Success", "Layer deleted");
59
+ } else {
60
+ Alert.alert("Error", result.error || "Failed to delete layer");
61
+ }
62
+ },
63
+ },
64
+ ],
65
+ );
66
+ },
67
+ [scenes, sceneIndex, onUpdateScenes, onLayerDeleted],
68
+ );
69
+
70
+ const changeLayerOrder = useCallback(
71
+ (layerId: string, action: LayerOrderAction) => {
72
+ const result = layerOperationsService.changeLayerOrder(
73
+ scenes,
74
+ sceneIndex,
75
+ layerId,
76
+ action,
77
+ );
78
+ if (result.success) {
79
+ onUpdateScenes(result.updatedScenes);
80
+ const actionNames = {
81
+ front: "Layer moved to front",
82
+ back: "Layer moved to back",
83
+ up: "Layer moved up",
84
+ down: "Layer moved down",
85
+ };
86
+ Alert.alert("Success", actionNames[action]);
87
+ } else {
88
+ Alert.alert("Error", result.error || "Failed to change layer order");
89
+ }
90
+ },
91
+ [scenes, sceneIndex, onUpdateScenes],
92
+ );
93
+
94
+ const duplicateLayer = useCallback(
95
+ (layerId: string) => {
96
+ const result = layerOperationsService.duplicateLayer(
97
+ scenes,
98
+ sceneIndex,
99
+ layerId,
100
+ );
101
+ if (result.success) {
102
+ onUpdateScenes(result.updatedScenes);
103
+ Alert.alert("Success", "Layer duplicated!");
104
+ } else {
105
+ Alert.alert("Error", result.error || "Failed to duplicate layer");
106
+ }
107
+ },
108
+ [scenes, sceneIndex, onUpdateScenes],
109
+ );
110
+
111
+ const updateLayerPosition = useCallback(
112
+ (layerId: string, x: number, y: number) => {
113
+ const result = layerOperationsService.updateLayerPosition(
114
+ scenes,
115
+ sceneIndex,
116
+ layerId,
117
+ x,
118
+ y,
119
+ );
120
+ if (result.success) {
121
+ onUpdateScenes(result.updatedScenes);
122
+ }
123
+ },
124
+ [scenes, sceneIndex, onUpdateScenes],
125
+ );
126
+
127
+ const updateLayerSize = useCallback(
128
+ (layerId: string, width: number, height: number) => {
129
+ const result = layerOperationsService.updateLayerSize(
130
+ scenes,
131
+ sceneIndex,
132
+ layerId,
133
+ width,
134
+ height,
135
+ );
136
+ if (result.success) {
137
+ onUpdateScenes(result.updatedScenes);
138
+ }
139
+ },
140
+ [scenes, sceneIndex, onUpdateScenes],
141
+ );
142
+
143
+ const updateLayerAnimation = useCallback(
144
+ (layerId: string, animation: Animation | undefined) => {
145
+ const result = layerOperationsService.updateLayerAnimation(
146
+ scenes,
147
+ sceneIndex,
148
+ layerId,
149
+ animation,
150
+ );
151
+ if (result.success) {
152
+ onUpdateScenes(result.updatedScenes);
153
+ onCloseBottomSheet();
154
+ Alert.alert(
155
+ "Success",
156
+ animation
157
+ ? "Animation applied to layer!"
158
+ : "Animation removed from layer",
159
+ );
160
+ } else {
161
+ Alert.alert(
162
+ "Error",
163
+ result.error || "Failed to update layer animation",
164
+ );
165
+ }
166
+ },
167
+ [scenes, sceneIndex, onUpdateScenes, onCloseBottomSheet],
168
+ );
169
+
170
+ return {
171
+ deleteLayer,
172
+ changeLayerOrder,
173
+ duplicateLayer,
174
+ updateLayerPosition,
175
+ updateLayerSize,
176
+ updateLayerAnimation,
177
+ };
178
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * useMenuActions Hook
3
+ * Single Responsibility: Menu action handlers (layer actions menu)
4
+ */
5
+
6
+ import { useCallback } from "react";
7
+ import { LayerActionsMenu } from "../presentation/components/LayerActionsMenu";
8
+ import type { UseEditorLayersReturn } from "./useEditorLayers";
9
+ import type { UseEditorBottomSheetReturn } from "./useEditorBottomSheet";
10
+
11
+ export interface UseMenuActionsParams {
12
+ layers: UseEditorLayersReturn;
13
+ bottomSheet: UseEditorBottomSheetReturn;
14
+ handleEditLayer: () => void;
15
+ handleEditImageLayer: (layerId: string) => void;
16
+ handleAnimate: (layerId: string) => void;
17
+ }
18
+
19
+ export interface UseMenuActionsReturn {
20
+ handleLayerActionsPress: (layer: any) => void;
21
+ }
22
+
23
+ export function useMenuActions({
24
+ layers,
25
+ bottomSheet,
26
+ handleEditLayer,
27
+ handleEditImageLayer,
28
+ handleAnimate,
29
+ }: UseMenuActionsParams): UseMenuActionsReturn {
30
+ const { openBottomSheet, closeBottomSheet } = bottomSheet;
31
+
32
+ const handleLayerActionsPress = useCallback(
33
+ (layer: any) => {
34
+ openBottomSheet({
35
+ title: "Layer Actions",
36
+ children: (
37
+ <LayerActionsMenu
38
+ layer={layer}
39
+ onEditText={() => {
40
+ closeBottomSheet();
41
+ setTimeout(() => handleEditLayer(), 300);
42
+ }}
43
+ onEditImage={() => {
44
+ closeBottomSheet();
45
+ setTimeout(() => handleEditImageLayer(layer.id), 300);
46
+ }}
47
+ onAnimate={() => {
48
+ closeBottomSheet();
49
+ setTimeout(() => handleAnimate(layer.id), 300);
50
+ }}
51
+ onDuplicate={() => {
52
+ closeBottomSheet();
53
+ layers.duplicateLayer(layer.id);
54
+ }}
55
+ onMoveFront={() => {
56
+ closeBottomSheet();
57
+ layers.changeLayerOrder(layer.id, "front");
58
+ }}
59
+ onMoveUp={() => {
60
+ closeBottomSheet();
61
+ layers.changeLayerOrder(layer.id, "up");
62
+ }}
63
+ onMoveDown={() => {
64
+ closeBottomSheet();
65
+ layers.changeLayerOrder(layer.id, "down");
66
+ }}
67
+ onMoveBack={() => {
68
+ closeBottomSheet();
69
+ layers.changeLayerOrder(layer.id, "back");
70
+ }}
71
+ onDelete={() => {
72
+ closeBottomSheet();
73
+ layers.deleteLayer(layer.id);
74
+ }}
75
+ />
76
+ ),
77
+ });
78
+ },
79
+ [
80
+ layers,
81
+ handleEditLayer,
82
+ handleEditImageLayer,
83
+ handleAnimate,
84
+ openBottomSheet,
85
+ closeBottomSheet,
86
+ ],
87
+ );
88
+
89
+ return {
90
+ handleLayerActionsPress,
91
+ };
92
+ }