@umituz/react-native-video-editor 1.1.62 → 1.1.64

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-video-editor",
3
- "version": "1.1.62",
3
+ "version": "1.1.64",
4
4
  "description": "Professional video editor with layer-based timeline, text/image/shape/audio/animation layers, and export functionality",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Base Layer Operations Service
3
+ * Provides common layer operation logic with type safety
4
+ * Eliminates code duplication across layer types
5
+ */
6
+
7
+ import type { Scene, Layer, LayerOperationResult } from "../../../domain/entities/video-project.types";
8
+
9
+ /**
10
+ * Abstract base class for layer operations
11
+ * Implements template method pattern for layer-specific operations
12
+ */
13
+ export abstract class BaseLayerOperationsService<T extends Layer> {
14
+ /**
15
+ * Create a new layer instance (layer-specific)
16
+ */
17
+ protected abstract createLayer(data: unknown): T;
18
+
19
+ /**
20
+ * Validate layer data before creation (layer-specific)
21
+ */
22
+ protected abstract validateLayerData(data: unknown): string | null;
23
+
24
+ /**
25
+ * Get the layer type for type checking
26
+ */
27
+ protected abstract getLayerType(): T["type"];
28
+
29
+ /**
30
+ * Add layer to scene
31
+ */
32
+ addLayer(
33
+ scenes: Scene[],
34
+ sceneIndex: number,
35
+ layerData: unknown,
36
+ ): LayerOperationResult {
37
+ try {
38
+ const validationError = this.validateSceneIndex(scenes, sceneIndex);
39
+ if (validationError) {
40
+ return {
41
+ success: false,
42
+ updatedScenes: scenes,
43
+ error: validationError,
44
+ };
45
+ }
46
+
47
+ const dataValidationError = this.validateLayerData(layerData);
48
+ if (dataValidationError) {
49
+ return {
50
+ success: false,
51
+ updatedScenes: scenes,
52
+ error: dataValidationError,
53
+ };
54
+ }
55
+
56
+ const newLayer = this.createLayer(layerData);
57
+ const updatedScenes = this.addLayerToScene(scenes, sceneIndex, newLayer);
58
+
59
+ return { success: true, updatedScenes };
60
+ } catch (error) {
61
+ return {
62
+ success: false,
63
+ updatedScenes: scenes,
64
+ error: error instanceof Error ? error.message : "Failed to add layer",
65
+ };
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Edit existing layer
71
+ */
72
+ editLayer(
73
+ scenes: Scene[],
74
+ sceneIndex: number,
75
+ layerId: string,
76
+ layerData: Partial<T>,
77
+ ): LayerOperationResult {
78
+ try {
79
+ const validationError = this.validateSceneIndex(scenes, sceneIndex);
80
+ if (validationError) {
81
+ return {
82
+ success: false,
83
+ updatedScenes: scenes,
84
+ error: validationError,
85
+ };
86
+ }
87
+
88
+ const layerResult = this.findLayer(scenes, sceneIndex, layerId);
89
+ if (!layerResult) {
90
+ return {
91
+ success: false,
92
+ updatedScenes: scenes,
93
+ error: "Layer not found",
94
+ };
95
+ }
96
+
97
+ const typeError = this.validateLayerType(layerResult.layer);
98
+ if (typeError) {
99
+ return {
100
+ success: false,
101
+ updatedScenes: scenes,
102
+ error: typeError,
103
+ };
104
+ }
105
+
106
+ const updatedScenes = this.updateLayerInScene(
107
+ scenes,
108
+ sceneIndex,
109
+ layerResult.index,
110
+ layerData,
111
+ );
112
+
113
+ return { success: true, updatedScenes };
114
+ } catch (error) {
115
+ return {
116
+ success: false,
117
+ updatedScenes: scenes,
118
+ error: error instanceof Error ? error.message : "Failed to edit layer",
119
+ };
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Validate scene index
125
+ */
126
+ protected validateSceneIndex(
127
+ scenes: Scene[],
128
+ sceneIndex: number,
129
+ ): string | null {
130
+ if (sceneIndex < 0 || sceneIndex >= scenes.length) {
131
+ return "Invalid scene index";
132
+ }
133
+ return null;
134
+ }
135
+
136
+ /**
137
+ * Find layer by ID
138
+ */
139
+ protected findLayer(
140
+ scenes: Scene[],
141
+ sceneIndex: number,
142
+ layerId: string,
143
+ ): { layer: Layer; index: number } | null {
144
+ const layerIndex = scenes[sceneIndex].layers.findIndex(
145
+ (l) => l.id === layerId,
146
+ );
147
+
148
+ if (layerIndex === -1) {
149
+ return null;
150
+ }
151
+
152
+ return {
153
+ layer: scenes[sceneIndex].layers[layerIndex],
154
+ index: layerIndex,
155
+ };
156
+ }
157
+
158
+ /**
159
+ * Validate layer type matches service type
160
+ */
161
+ protected validateLayerType(layer: Layer): string | null {
162
+ if (layer.type !== this.getLayerType()) {
163
+ return `Layer is not a ${this.getLayerType()} layer`;
164
+ }
165
+ return null;
166
+ }
167
+
168
+ /**
169
+ * Add layer to scene (immutable update)
170
+ */
171
+ protected addLayerToScene(
172
+ scenes: Scene[],
173
+ sceneIndex: number,
174
+ layer: T,
175
+ ): Scene[] {
176
+ const updatedScenes = [...scenes];
177
+ updatedScenes[sceneIndex] = {
178
+ ...updatedScenes[sceneIndex],
179
+ layers: [...updatedScenes[sceneIndex].layers, layer],
180
+ };
181
+ return updatedScenes;
182
+ }
183
+
184
+ /**
185
+ * Update layer in scene (immutable update)
186
+ */
187
+ protected updateLayerInScene(
188
+ scenes: Scene[],
189
+ sceneIndex: number,
190
+ layerIndex: number,
191
+ layerData: Partial<T>,
192
+ ): Scene[] {
193
+ const updatedScenes = [...scenes];
194
+ const existingLayer = updatedScenes[sceneIndex].layers[layerIndex];
195
+ updatedScenes[sceneIndex].layers[layerIndex] = {
196
+ ...existingLayer,
197
+ ...layerData,
198
+ };
199
+ return updatedScenes;
200
+ }
201
+ }
@@ -4,122 +4,57 @@
4
4
  */
5
5
 
6
6
  import { generateUUID } from "@umituz/react-native-design-system/uuid";
7
- import type { Scene, ImageLayer } from "../../domain/entities/video-project.types";
7
+ import type { ImageLayer, Scene } from "../../domain/entities/video-project.types";
8
8
  import type { LayerOperationResult, AddImageLayerData } from "../../domain/entities/video-project.types";
9
+ import { BaseLayerOperationsService } from "./base/base-layer-operations.service";
10
+
11
+ class ImageLayerOperationsService extends BaseLayerOperationsService<ImageLayer> {
12
+ protected createLayer(data: unknown): ImageLayer {
13
+ const layerData = data as AddImageLayerData;
14
+
15
+ return {
16
+ id: generateUUID(),
17
+ type: "image",
18
+ uri: layerData.uri ?? "",
19
+ position: { x: 15, y: 30 },
20
+ size: { width: 70, height: 40 },
21
+ rotation: 0,
22
+ opacity: layerData.opacity ?? 1,
23
+ animation: {
24
+ type: "fade",
25
+ duration: 500,
26
+ easing: "ease-in-out",
27
+ },
28
+ };
29
+ }
30
+
31
+ protected validateLayerData(data: unknown): string | null {
32
+ const layerData = data as AddImageLayerData;
33
+ if (!layerData.uri || layerData.uri.trim().length === 0) {
34
+ return "Image URI is required";
35
+ }
36
+ return null;
37
+ }
38
+
39
+ protected getLayerType(): ImageLayer["type"] {
40
+ return "image";
41
+ }
9
42
 
10
- class ImageLayerOperationsService {
11
- /**
12
- * Add image layer to scene
13
- */
14
43
  addImageLayer(
15
44
  scenes: Scene[],
16
45
  sceneIndex: number,
17
46
  layerData: AddImageLayerData,
18
47
  ): LayerOperationResult {
19
- try {
20
- if (sceneIndex < 0 || sceneIndex >= scenes.length) {
21
- return {
22
- success: false,
23
- updatedScenes: scenes,
24
- error: "Invalid scene index",
25
- };
26
- }
27
-
28
- // Validate URI
29
- if (!layerData.uri || layerData.uri.trim().length === 0) {
30
- return {
31
- success: false,
32
- updatedScenes: scenes,
33
- error: "Image URI is required",
34
- };
35
- }
36
-
37
- const newLayer: ImageLayer = {
38
- id: generateUUID(),
39
- type: "image",
40
- uri: layerData.uri ?? "",
41
- position: { x: 15, y: 30 },
42
- size: { width: 70, height: 40 },
43
- rotation: 0,
44
- opacity: layerData.opacity ?? 1,
45
- animation: {
46
- type: "fade",
47
- duration: 500,
48
- easing: "ease-in-out",
49
- },
50
- };
51
-
52
- const updatedScenes = [...scenes];
53
- updatedScenes[sceneIndex] = {
54
- ...updatedScenes[sceneIndex],
55
- layers: [...updatedScenes[sceneIndex].layers, newLayer],
56
- };
57
-
58
- return { success: true, updatedScenes };
59
- } catch (error) {
60
- return {
61
- success: false,
62
- updatedScenes: scenes,
63
- error:
64
- error instanceof Error ? error.message : "Failed to add image layer",
65
- };
66
- }
48
+ return this.addLayer(scenes, sceneIndex, layerData);
67
49
  }
68
50
 
69
- /**
70
- * Edit image layer
71
- */
72
51
  editImageLayer(
73
52
  scenes: Scene[],
74
53
  sceneIndex: number,
75
54
  layerId: string,
76
55
  layerData: Partial<ImageLayer>,
77
56
  ): LayerOperationResult {
78
- try {
79
- if (sceneIndex < 0 || sceneIndex >= scenes.length) {
80
- return {
81
- success: false,
82
- updatedScenes: scenes,
83
- error: "Invalid scene index",
84
- };
85
- }
86
-
87
- const updatedScenes = [...scenes];
88
- const layerIndex = updatedScenes[sceneIndex].layers.findIndex(
89
- (l) => l.id === layerId,
90
- );
91
-
92
- if (layerIndex === -1) {
93
- return {
94
- success: false,
95
- updatedScenes: scenes,
96
- error: "Layer not found",
97
- };
98
- }
99
-
100
- const existingLayer = updatedScenes[sceneIndex].layers[layerIndex];
101
- if (existingLayer.type !== "image") {
102
- return {
103
- success: false,
104
- updatedScenes: scenes,
105
- error: "Layer is not an image layer",
106
- };
107
- }
108
-
109
- updatedScenes[sceneIndex].layers[layerIndex] = {
110
- ...existingLayer,
111
- ...layerData,
112
- };
113
-
114
- return { success: true, updatedScenes };
115
- } catch (error) {
116
- return {
117
- success: false,
118
- updatedScenes: scenes,
119
- error:
120
- error instanceof Error ? error.message : "Failed to edit image layer",
121
- };
122
- }
57
+ return this.editLayer(scenes, sceneIndex, layerId, layerData);
123
58
  }
124
59
  }
125
60
 
@@ -4,61 +4,52 @@
4
4
  */
5
5
 
6
6
  import { generateUUID } from "@umituz/react-native-design-system/uuid";
7
- import type { Scene, ShapeLayer } from "../../domain/entities/video-project.types";
7
+ import type { ShapeLayer, Scene } from "../../domain/entities/video-project.types";
8
8
  import type { LayerOperationResult, AddShapeLayerData } from "../../domain/entities/video-project.types";
9
+ import { BaseLayerOperationsService } from "./base/base-layer-operations.service";
10
+
11
+ interface ShapeLayerDataWithDefault extends AddShapeLayerData {
12
+ defaultColor: string;
13
+ }
14
+
15
+ class ShapeLayerOperationsService extends BaseLayerOperationsService<ShapeLayer> {
16
+ protected createLayer(data: unknown): ShapeLayer {
17
+ const { defaultColor, ...layerData } = data as ShapeLayerDataWithDefault;
18
+
19
+ return {
20
+ id: generateUUID(),
21
+ type: "shape",
22
+ shape: (layerData.shape ?? "rectangle") as ShapeLayer["shape"],
23
+ position: { x: 25, y: 25 },
24
+ size: { width: 50, height: 50 },
25
+ rotation: 0,
26
+ opacity: layerData.opacity ?? 1,
27
+ fillColor: layerData.fillColor ?? defaultColor,
28
+ borderColor: layerData.borderColor,
29
+ borderWidth: layerData.borderWidth,
30
+ animation: {
31
+ type: "fade",
32
+ duration: 500,
33
+ easing: "ease-in-out",
34
+ },
35
+ };
36
+ }
37
+
38
+ protected validateLayerData(): string | null {
39
+ return null;
40
+ }
41
+
42
+ protected getLayerType(): ShapeLayer["type"] {
43
+ return "shape";
44
+ }
9
45
 
10
- class ShapeLayerOperationsService {
11
- /**
12
- * Add shape layer to scene
13
- */
14
46
  addShapeLayer(
15
47
  scenes: Scene[],
16
48
  sceneIndex: number,
17
49
  layerData: AddShapeLayerData,
18
50
  defaultColor: string,
19
51
  ): 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 ?? "rectangle") as ShapeLayer["shape"],
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
- }
52
+ return this.addLayer(scenes, sceneIndex, { ...layerData, defaultColor });
62
53
  }
63
54
  }
64
55
 
@@ -4,119 +4,67 @@
4
4
  */
5
5
 
6
6
  import { generateUUID } from "@umituz/react-native-design-system/uuid";
7
- import type { Scene, TextLayer } from "../../domain/entities/video-project.types";
7
+ import type { TextLayer, Scene } from "../../domain/entities/video-project.types";
8
8
  import type { LayerOperationResult, AddTextLayerData } from "../../domain/entities/video-project.types";
9
+ import { BaseLayerOperationsService } from "./base/base-layer-operations.service";
10
+
11
+ interface TextLayerDataWithDefault extends AddTextLayerData {
12
+ defaultColor: string;
13
+ }
14
+
15
+ class TextLayerOperationsService extends BaseLayerOperationsService<TextLayer> {
16
+ protected createLayer(data: unknown): TextLayer {
17
+ const { defaultColor, ...layerData } = data as TextLayerDataWithDefault;
18
+
19
+ return {
20
+ id: generateUUID(),
21
+ type: "text",
22
+ content: layerData.content || "",
23
+ position: { x: 10, y: 40 },
24
+ size: { width: 80, height: 20 },
25
+ rotation: 0,
26
+ opacity: 1,
27
+ fontSize: layerData.fontSize || 48,
28
+ fontFamily: layerData.fontFamily || "System",
29
+ fontWeight: (layerData.fontWeight as TextLayer["fontWeight"]) || "bold",
30
+ color: layerData.color || defaultColor,
31
+ textAlign: layerData.textAlign || "center",
32
+ animation: {
33
+ type: "fade",
34
+ duration: 500,
35
+ easing: "ease-in-out",
36
+ },
37
+ };
38
+ }
39
+
40
+ protected validateLayerData(data: unknown): string | null {
41
+ const layerData = data as AddTextLayerData;
42
+ if (!layerData.content || layerData.content.trim().length === 0) {
43
+ return "Text content is required";
44
+ }
45
+ return null;
46
+ }
47
+
48
+ protected getLayerType(): TextLayer["type"] {
49
+ return "text";
50
+ }
9
51
 
10
- class TextLayerOperationsService {
11
- /**
12
- * Add text layer to scene
13
- */
14
52
  addTextLayer(
15
53
  scenes: Scene[],
16
54
  sceneIndex: number,
17
55
  layerData: AddTextLayerData,
18
56
  defaultColor: string,
19
57
  ): 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
- }
58
+ return this.addLayer(scenes, sceneIndex, { ...layerData, defaultColor });
64
59
  }
65
60
 
66
- /**
67
- * Edit text layer
68
- */
69
61
  editTextLayer(
70
62
  scenes: Scene[],
71
63
  sceneIndex: number,
72
64
  layerId: string,
73
65
  layerData: Partial<TextLayer>,
74
66
  ): 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
- const existingLayer = updatedScenes[sceneIndex].layers[layerIndex];
98
- if (existingLayer.type !== "text") {
99
- return {
100
- success: false,
101
- updatedScenes: scenes,
102
- error: "Layer is not a text layer",
103
- };
104
- }
105
-
106
- updatedScenes[sceneIndex].layers[layerIndex] = {
107
- ...existingLayer,
108
- ...layerData,
109
- };
110
-
111
- return { success: true, updatedScenes };
112
- } catch (error) {
113
- return {
114
- success: false,
115
- updatedScenes: scenes,
116
- error:
117
- error instanceof Error ? error.message : "Failed to edit text layer",
118
- };
119
- }
67
+ return this.editLayer(scenes, sceneIndex, layerId, layerData);
120
68
  }
121
69
  }
122
70