@umituz/react-native-video-editor 1.1.63 → 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 +1 -1
- package/src/infrastructure/services/image-layer-operations.service.ts +1 -1
- package/src/infrastructure/services/shape-layer-operations.service.ts +1 -1
- package/src/infrastructure/services/text-layer-operations.service.ts +1 -1
- package/src/presentation/components/SubtitleListPanel.tsx +1 -1
- package/src/presentation/components/collage/CollageCanvas.tsx +2 -2
- package/src/presentation/components/collage/CollageLayoutSelector.tsx +0 -4
- package/src/presentation/components/subtitle/SubtitleListItem.tsx +4 -5
- package/src/presentation/components/subtitle/SubtitleModal.tsx +2 -2
- package/src/presentation/components/subtitle/useSubtitleForm.ts +1 -1
- package/src/presentation/hooks/generic/use-layer-form.hook.ts +19 -16
- package/src/presentation/hooks/useImageLayerForm.ts +9 -6
- package/src/presentation/hooks/useTextLayerForm.ts +9 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-video-editor",
|
|
3
|
-
"version": "1.1.
|
|
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",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { generateUUID } from "@umituz/react-native-design-system/uuid";
|
|
7
|
-
import type { 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
9
|
import { BaseLayerOperationsService } from "./base/base-layer-operations.service";
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { generateUUID } from "@umituz/react-native-design-system/uuid";
|
|
7
|
-
import type { 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
9
|
import { BaseLayerOperationsService } from "./base/base-layer-operations.service";
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { generateUUID } from "@umituz/react-native-design-system/uuid";
|
|
7
|
-
import type { 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
9
|
import { BaseLayerOperationsService } from "./base/base-layer-operations.service";
|
|
10
10
|
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
import React from "react";
|
|
7
7
|
import { View, ScrollView, TouchableOpacity } from "react-native";
|
|
8
|
-
import { AtomicText } from "@umituz/react-native-design-system/atoms";
|
|
9
8
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
10
9
|
import { COLLAGE_LAYOUTS } from "../../../infrastructure/constants/collage.constants";
|
|
11
10
|
import type { CollageLayout } from "../../../infrastructure/constants/collage.constants";
|
|
@@ -93,9 +92,6 @@ export const CollageLayoutSelector: React.FC<CollageLayoutSelectorProps> = ({
|
|
|
93
92
|
);
|
|
94
93
|
})}
|
|
95
94
|
</View>
|
|
96
|
-
<AtomicText type="caption" color="textSecondary">
|
|
97
|
-
{layout.name}
|
|
98
|
-
</AtomicText>
|
|
99
95
|
</TouchableOpacity>
|
|
100
96
|
))}
|
|
101
97
|
</ScrollView>
|
|
@@ -29,7 +29,6 @@ export const SubtitleListItem: React.FC<SubtitleListItemProps> = ({
|
|
|
29
29
|
item: {
|
|
30
30
|
flexDirection: "row" as const,
|
|
31
31
|
alignItems: "center" as const,
|
|
32
|
-
backgroundColor: tokens.colors.surface,
|
|
33
32
|
borderRadius: tokens.borders.radius.md,
|
|
34
33
|
marginHorizontal: tokens.spacing.md,
|
|
35
34
|
marginBottom: tokens.spacing.sm,
|
|
@@ -70,17 +69,17 @@ export const SubtitleListItem: React.FC<SubtitleListItemProps> = ({
|
|
|
70
69
|
activeOpacity={0.7}
|
|
71
70
|
>
|
|
72
71
|
<View style={styles.itemTimeRow}>
|
|
73
|
-
<AtomicText
|
|
72
|
+
<AtomicText color="textSecondary">
|
|
74
73
|
{formatTimeDetailed(subtitle.startTime)}
|
|
75
74
|
</AtomicText>
|
|
76
|
-
<AtomicText
|
|
75
|
+
<AtomicText color="textSecondary">
|
|
77
76
|
→
|
|
78
77
|
</AtomicText>
|
|
79
|
-
<AtomicText
|
|
78
|
+
<AtomicText color="textSecondary">
|
|
80
79
|
{formatTimeDetailed(subtitle.endTime)}
|
|
81
80
|
</AtomicText>
|
|
82
81
|
</View>
|
|
83
|
-
<AtomicText
|
|
82
|
+
<AtomicText color="textPrimary" numberOfLines={2}>
|
|
84
83
|
{subtitle.text}
|
|
85
84
|
</AtomicText>
|
|
86
85
|
</TouchableOpacity>
|
|
@@ -55,7 +55,7 @@ export const SubtitleModal: React.FC<SubtitleModalProps> = ({
|
|
|
55
55
|
paddingHorizontal: tokens.spacing.md,
|
|
56
56
|
paddingTop: tokens.spacing.md,
|
|
57
57
|
paddingBottom: tokens.spacing.xl,
|
|
58
|
-
maxHeight: "90%",
|
|
58
|
+
maxHeight: "90%" as const,
|
|
59
59
|
},
|
|
60
60
|
handle: {
|
|
61
61
|
width: 36,
|
|
@@ -136,7 +136,7 @@ export const SubtitleModal: React.FC<SubtitleModalProps> = ({
|
|
|
136
136
|
/>
|
|
137
137
|
</View>
|
|
138
138
|
|
|
139
|
-
<SubtitleStylePicker
|
|
139
|
+
<SubtitleStylePicker style={style} previewText={text} onChange={onChangeStyle} t={(key) => key} />
|
|
140
140
|
|
|
141
141
|
<View style={styles.actionRow}>
|
|
142
142
|
<TouchableOpacity style={styles.cancelBtn} onPress={onCancel}>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Manages subtitle form state and operations
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { useState, useCallback
|
|
6
|
+
import { useState, useCallback } from "react";
|
|
7
7
|
import { DEFAULT_SUBTITLE_STYLE } from "../../../infrastructure/constants/subtitle.constants";
|
|
8
8
|
import type { Subtitle, SubtitleStyle } from "../../../domain/entities/video-project.types";
|
|
9
9
|
|
|
@@ -5,30 +5,30 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { useState, useCallback } from "react";
|
|
8
|
-
import type { Layer } from "
|
|
8
|
+
import type { Layer, ImageLayer, TextLayer } from "../../../domain/entities/video-project.types";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Form field validator function type
|
|
12
12
|
*/
|
|
13
|
-
export type ValidatorFn<T> = (value: T[
|
|
13
|
+
export type ValidatorFn<T, K extends keyof T = keyof T> = (value: T[K]) => string | null;
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Layer form configuration
|
|
17
17
|
*/
|
|
18
18
|
export interface UseLayerFormConfig<T extends Record<string, unknown>> {
|
|
19
19
|
initialValues: Partial<T>;
|
|
20
|
-
validators?:
|
|
21
|
-
buildData: (formState: T) => Partial<Layer>;
|
|
20
|
+
validators?: Record<string, (value: unknown) => string | null>;
|
|
21
|
+
buildData: (formState: T) => Partial<Layer> | Partial<ImageLayer> | Partial<TextLayer>;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Layer form return type
|
|
26
26
|
*/
|
|
27
|
-
export interface UseLayerFormReturn<T extends Record<string, unknown>> {
|
|
27
|
+
export interface UseLayerFormReturn<T extends Record<string, unknown>, R = Partial<Layer>> {
|
|
28
28
|
formState: T;
|
|
29
29
|
updateField: <K extends keyof T>(field: K, value: T[K]) => void;
|
|
30
30
|
setFormState: (state: T | ((prev: T) => T)) => void;
|
|
31
|
-
buildLayerData: () =>
|
|
31
|
+
buildLayerData: () => R;
|
|
32
32
|
isValid: boolean;
|
|
33
33
|
errors: Partial<Record<keyof T, string | null>>;
|
|
34
34
|
validateField: <K extends keyof T>(field: K) => string | null;
|
|
@@ -39,9 +39,9 @@ export interface UseLayerFormReturn<T extends Record<string, unknown>> {
|
|
|
39
39
|
* Generic hook for managing layer form state
|
|
40
40
|
* Provides type-safe form management with validation support
|
|
41
41
|
*/
|
|
42
|
-
export function useLayerForm<T extends Record<string, unknown>>(
|
|
42
|
+
export function useLayerForm<T extends Record<string, unknown>, R = Partial<Layer>>(
|
|
43
43
|
config: UseLayerFormConfig<T>,
|
|
44
|
-
): UseLayerFormReturn<T> {
|
|
44
|
+
): UseLayerFormReturn<T, R> {
|
|
45
45
|
const { initialValues, validators = {}, buildData } = config;
|
|
46
46
|
|
|
47
47
|
const [formState, setFormState] = useState<T>(
|
|
@@ -60,7 +60,7 @@ export function useLayerForm<T extends Record<string, unknown>>(
|
|
|
60
60
|
}));
|
|
61
61
|
|
|
62
62
|
// Clear error for this field
|
|
63
|
-
if (errors[field]) {
|
|
63
|
+
if (errors[field as keyof typeof errors]) {
|
|
64
64
|
setErrors((prev) => ({
|
|
65
65
|
...prev,
|
|
66
66
|
[field]: null,
|
|
@@ -72,7 +72,7 @@ export function useLayerForm<T extends Record<string, unknown>>(
|
|
|
72
72
|
|
|
73
73
|
const validateField = useCallback(
|
|
74
74
|
<K extends keyof T>(field: K): string | null => {
|
|
75
|
-
const validator = validators[field];
|
|
75
|
+
const validator = validators[String(field)];
|
|
76
76
|
if (!validator) return null;
|
|
77
77
|
|
|
78
78
|
const error = validator(formState[field]);
|
|
@@ -91,10 +91,13 @@ export function useLayerForm<T extends Record<string, unknown>>(
|
|
|
91
91
|
const newErrors: Partial<Record<keyof T, string | null>> = {};
|
|
92
92
|
|
|
93
93
|
for (const field in validators) {
|
|
94
|
-
const
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
const validator = validators[field];
|
|
95
|
+
if (validator) {
|
|
96
|
+
const error = validator(formState[field as keyof T]);
|
|
97
|
+
if (error) {
|
|
98
|
+
newErrors[field as keyof T] = error;
|
|
99
|
+
hasError = true;
|
|
100
|
+
}
|
|
98
101
|
}
|
|
99
102
|
}
|
|
100
103
|
|
|
@@ -102,8 +105,8 @@ export function useLayerForm<T extends Record<string, unknown>>(
|
|
|
102
105
|
return !hasError;
|
|
103
106
|
}, [formState, validators]);
|
|
104
107
|
|
|
105
|
-
const buildLayerData = useCallback(():
|
|
106
|
-
return buildData(formState);
|
|
108
|
+
const buildLayerData = useCallback((): R => {
|
|
109
|
+
return buildData(formState) as R;
|
|
107
110
|
}, [formState, buildData]);
|
|
108
111
|
|
|
109
112
|
const isValid = Object.values(errors).every((error) => error === null);
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { ImageLayer } from "../../domain/entities/video-project.types";
|
|
7
|
-
import { useLayerForm } from "./generic/use-layer-form.hook";
|
|
7
|
+
import { useLayerForm, type UseLayerFormConfig } from "./generic/use-layer-form.hook";
|
|
8
8
|
|
|
9
9
|
interface ImageLayerFormState {
|
|
10
10
|
imageUri: string;
|
|
11
11
|
opacity: number;
|
|
12
|
+
[key: string]: unknown;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
interface UseImageLayerFormReturn {
|
|
@@ -25,14 +26,14 @@ interface UseImageLayerFormReturn {
|
|
|
25
26
|
export function useImageLayerForm(
|
|
26
27
|
initialLayer?: ImageLayer,
|
|
27
28
|
): UseImageLayerFormReturn {
|
|
28
|
-
const
|
|
29
|
+
const config: UseLayerFormConfig<ImageLayerFormState> = {
|
|
29
30
|
initialValues: {
|
|
30
31
|
imageUri: initialLayer?.uri || "",
|
|
31
32
|
opacity: initialLayer?.opacity || 1,
|
|
32
33
|
},
|
|
33
34
|
validators: {
|
|
34
|
-
imageUri: (value) => {
|
|
35
|
-
if (!value || value.trim().length === 0) {
|
|
35
|
+
imageUri: (value: unknown) => {
|
|
36
|
+
if (!value || (typeof value === "string" && value.trim().length === 0)) {
|
|
36
37
|
return "Image URI is required";
|
|
37
38
|
}
|
|
38
39
|
return null;
|
|
@@ -41,8 +42,10 @@ export function useImageLayerForm(
|
|
|
41
42
|
buildData: (formState) => ({
|
|
42
43
|
uri: formState.imageUri,
|
|
43
44
|
opacity: formState.opacity,
|
|
44
|
-
}),
|
|
45
|
-
}
|
|
45
|
+
} as Partial<ImageLayer>),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const form = useLayerForm<ImageLayerFormState, Partial<ImageLayer>>(config);
|
|
46
49
|
|
|
47
50
|
const setImageUri = (uri: string) => form.updateField("imageUri", uri);
|
|
48
51
|
const setOpacity = (opacity: number) => form.updateField("opacity", opacity);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
7
7
|
import type { TextLayer } from "../../domain/entities/video-project.types";
|
|
8
|
-
import { useLayerForm } from "./generic/use-layer-form.hook";
|
|
8
|
+
import { useLayerForm, type UseLayerFormConfig } from "./generic/use-layer-form.hook";
|
|
9
9
|
|
|
10
10
|
export interface TextLayerFormState {
|
|
11
11
|
text: string;
|
|
@@ -14,6 +14,7 @@ export interface TextLayerFormState {
|
|
|
14
14
|
fontWeight: "normal" | "bold" | "300" | "700";
|
|
15
15
|
color: string;
|
|
16
16
|
textAlign: "left" | "center" | "right";
|
|
17
|
+
[key: string]: unknown;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
interface UseTextLayerFormReturn {
|
|
@@ -36,7 +37,7 @@ export function useTextLayerForm(
|
|
|
36
37
|
): UseTextLayerFormReturn {
|
|
37
38
|
const tokens = useAppDesignTokens();
|
|
38
39
|
|
|
39
|
-
const
|
|
40
|
+
const config: UseLayerFormConfig<TextLayerFormState> = {
|
|
40
41
|
initialValues: {
|
|
41
42
|
text: initialLayer?.content || "",
|
|
42
43
|
fontSize: initialLayer?.fontSize || 48,
|
|
@@ -47,8 +48,8 @@ export function useTextLayerForm(
|
|
|
47
48
|
textAlign: initialLayer?.textAlign || "center",
|
|
48
49
|
},
|
|
49
50
|
validators: {
|
|
50
|
-
text: (value) => {
|
|
51
|
-
if (!value || value.trim().length === 0) {
|
|
51
|
+
text: (value: unknown) => {
|
|
52
|
+
if (!value || (typeof value === "string" && value.trim().length === 0)) {
|
|
52
53
|
return "Text content is required";
|
|
53
54
|
}
|
|
54
55
|
return null;
|
|
@@ -61,8 +62,10 @@ export function useTextLayerForm(
|
|
|
61
62
|
fontWeight: formState.fontWeight,
|
|
62
63
|
color: formState.color,
|
|
63
64
|
textAlign: formState.textAlign,
|
|
64
|
-
}),
|
|
65
|
-
}
|
|
65
|
+
} as Partial<TextLayer>),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const form = useLayerForm<TextLayerFormState, Partial<TextLayer>>(config);
|
|
66
69
|
|
|
67
70
|
const setText = (text: string) => form.updateField("text", text);
|
|
68
71
|
const setFontSize = (size: number) => form.updateField("fontSize", size);
|