@umituz/react-native-ai-generation-content 1.8.0 → 1.8.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/package.json +6 -3
- package/src/index.ts +8 -2
- package/src/presentation/components/GenerationProgressBar.tsx +77 -0
- package/src/presentation/components/GenerationProgressContent.tsx +123 -0
- package/src/presentation/components/GenerationProgressModal.tsx +47 -160
- package/src/presentation/components/PendingJobCard.tsx +32 -127
- package/src/presentation/components/PendingJobCardActions.tsx +73 -0
- package/src/presentation/components/PendingJobProgressBar.tsx +54 -0
- package/src/presentation/components/index.ts +11 -10
- package/src/presentation/hooks/use-background-generation.ts +2 -2
- package/src/presentation/hooks/use-pending-jobs.ts +7 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "Provider-agnostic AI generation orchestration for React Native",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -31,17 +31,20 @@
|
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@tanstack/react-query": ">=5.0.0",
|
|
34
|
+
"@umituz/react-native-design-system-theme": "latest",
|
|
34
35
|
"react": ">=18.0.0",
|
|
35
36
|
"react-native": ">=0.74.0"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
39
|
"@tanstack/react-query": "^5.0.0",
|
|
39
|
-
"@
|
|
40
|
+
"@umituz/react-native-design-system-theme": "latest",
|
|
41
|
+
"@types/react": "~19.1.10",
|
|
40
42
|
"@types/react-native": "^0.73.0",
|
|
41
43
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
42
44
|
"@typescript-eslint/parser": "^7.0.0",
|
|
43
45
|
"eslint": "^8.57.0",
|
|
44
|
-
"react
|
|
46
|
+
"react": "19.1.0",
|
|
47
|
+
"react-native": "0.81.5",
|
|
45
48
|
"typescript": "^5.3.0"
|
|
46
49
|
},
|
|
47
50
|
"publishConfig": {
|
package/src/index.ts
CHANGED
|
@@ -156,14 +156,20 @@ export type {
|
|
|
156
156
|
|
|
157
157
|
export {
|
|
158
158
|
GenerationProgressModal,
|
|
159
|
+
GenerationProgressContent,
|
|
160
|
+
GenerationProgressBar,
|
|
159
161
|
PendingJobCard,
|
|
162
|
+
PendingJobProgressBar,
|
|
163
|
+
PendingJobCardActions,
|
|
160
164
|
} from "./presentation/components";
|
|
161
165
|
|
|
162
166
|
export type {
|
|
163
167
|
GenerationProgressModalProps,
|
|
164
168
|
GenerationProgressRenderProps,
|
|
169
|
+
GenerationProgressContentProps,
|
|
170
|
+
GenerationProgressBarProps,
|
|
165
171
|
PendingJobCardProps,
|
|
166
|
-
PendingJobCardStyles,
|
|
167
|
-
PendingJobCardColors,
|
|
168
172
|
StatusLabels,
|
|
173
|
+
PendingJobProgressBarProps,
|
|
174
|
+
PendingJobCardActionsProps,
|
|
169
175
|
} from "./presentation/components";
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GenerationProgressBar
|
|
3
|
+
* Individual progress bar component for AI generation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, StyleSheet, Text } from "react-native";
|
|
8
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
9
|
+
|
|
10
|
+
export interface GenerationProgressBarProps {
|
|
11
|
+
progress: number;
|
|
12
|
+
textColor?: string;
|
|
13
|
+
progressColor?: string;
|
|
14
|
+
backgroundColor?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const GenerationProgressBar: React.FC<GenerationProgressBarProps> = ({
|
|
18
|
+
progress,
|
|
19
|
+
textColor,
|
|
20
|
+
progressColor,
|
|
21
|
+
backgroundColor,
|
|
22
|
+
}) => {
|
|
23
|
+
const tokens = useAppDesignTokens();
|
|
24
|
+
const clampedProgress = Math.max(0, Math.min(100, progress));
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<View style={styles.container}>
|
|
28
|
+
<View
|
|
29
|
+
style={[
|
|
30
|
+
styles.background,
|
|
31
|
+
{ backgroundColor: backgroundColor || tokens.colors.borderLight },
|
|
32
|
+
]}
|
|
33
|
+
>
|
|
34
|
+
<View
|
|
35
|
+
style={[
|
|
36
|
+
styles.fill,
|
|
37
|
+
{
|
|
38
|
+
backgroundColor: progressColor || tokens.colors.primary,
|
|
39
|
+
width: `${clampedProgress}%`,
|
|
40
|
+
},
|
|
41
|
+
]}
|
|
42
|
+
/>
|
|
43
|
+
</View>
|
|
44
|
+
<Text
|
|
45
|
+
style={[
|
|
46
|
+
styles.text,
|
|
47
|
+
{ color: textColor || tokens.colors.textPrimary },
|
|
48
|
+
]}
|
|
49
|
+
>
|
|
50
|
+
{Math.round(clampedProgress)}%
|
|
51
|
+
</Text>
|
|
52
|
+
</View>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const styles = StyleSheet.create({
|
|
57
|
+
container: {
|
|
58
|
+
width: "100%",
|
|
59
|
+
marginBottom: 16,
|
|
60
|
+
alignItems: "center",
|
|
61
|
+
},
|
|
62
|
+
background: {
|
|
63
|
+
width: "100%",
|
|
64
|
+
height: 8,
|
|
65
|
+
borderRadius: 4,
|
|
66
|
+
overflow: "hidden",
|
|
67
|
+
},
|
|
68
|
+
fill: {
|
|
69
|
+
height: "100%",
|
|
70
|
+
borderRadius: 4,
|
|
71
|
+
},
|
|
72
|
+
text: {
|
|
73
|
+
fontSize: 14,
|
|
74
|
+
fontWeight: "600",
|
|
75
|
+
marginTop: 8,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GenerationProgressContent
|
|
3
|
+
* Content for the AI generation progress modal
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
|
|
8
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
9
|
+
import { GenerationProgressBar } from "./GenerationProgressBar";
|
|
10
|
+
|
|
11
|
+
export interface GenerationProgressContentProps {
|
|
12
|
+
progress: number;
|
|
13
|
+
title?: string;
|
|
14
|
+
message?: string;
|
|
15
|
+
hint?: string;
|
|
16
|
+
dismissLabel?: string;
|
|
17
|
+
onDismiss?: () => void;
|
|
18
|
+
backgroundColor?: string;
|
|
19
|
+
textColor?: string;
|
|
20
|
+
progressColor?: string;
|
|
21
|
+
progressBackgroundColor?: string;
|
|
22
|
+
dismissButtonColor?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const GenerationProgressContent: React.FC<
|
|
26
|
+
GenerationProgressContentProps
|
|
27
|
+
> = ({
|
|
28
|
+
progress,
|
|
29
|
+
title,
|
|
30
|
+
message,
|
|
31
|
+
hint,
|
|
32
|
+
dismissLabel = "Got it",
|
|
33
|
+
onDismiss,
|
|
34
|
+
backgroundColor,
|
|
35
|
+
textColor,
|
|
36
|
+
progressColor,
|
|
37
|
+
progressBackgroundColor,
|
|
38
|
+
dismissButtonColor,
|
|
39
|
+
}) => {
|
|
40
|
+
const tokens = useAppDesignTokens();
|
|
41
|
+
|
|
42
|
+
const activeTextColor = textColor || tokens.colors.textPrimary;
|
|
43
|
+
const activeBgColor = backgroundColor || tokens.colors.surface;
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<View style={[styles.modal, { backgroundColor: activeBgColor }]}>
|
|
47
|
+
{title && (
|
|
48
|
+
<Text style={[styles.title, { color: activeTextColor }]}>{title}</Text>
|
|
49
|
+
)}
|
|
50
|
+
|
|
51
|
+
{message && (
|
|
52
|
+
<Text style={[styles.message, { color: activeTextColor }]}>
|
|
53
|
+
{message}
|
|
54
|
+
</Text>
|
|
55
|
+
)}
|
|
56
|
+
|
|
57
|
+
<GenerationProgressBar
|
|
58
|
+
progress={progress}
|
|
59
|
+
textColor={activeTextColor}
|
|
60
|
+
progressColor={progressColor}
|
|
61
|
+
backgroundColor={progressBackgroundColor}
|
|
62
|
+
/>
|
|
63
|
+
|
|
64
|
+
{hint && (
|
|
65
|
+
<Text style={[styles.hint, { color: activeTextColor }]}>{hint}</Text>
|
|
66
|
+
)}
|
|
67
|
+
|
|
68
|
+
{onDismiss && (
|
|
69
|
+
<TouchableOpacity
|
|
70
|
+
style={[
|
|
71
|
+
styles.dismissButton,
|
|
72
|
+
{ backgroundColor: dismissButtonColor || tokens.colors.primary },
|
|
73
|
+
]}
|
|
74
|
+
onPress={onDismiss}
|
|
75
|
+
>
|
|
76
|
+
<Text style={styles.dismissText}>{dismissLabel}</Text>
|
|
77
|
+
</TouchableOpacity>
|
|
78
|
+
)}
|
|
79
|
+
</View>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const styles = StyleSheet.create({
|
|
84
|
+
modal: {
|
|
85
|
+
width: "100%",
|
|
86
|
+
maxWidth: 400,
|
|
87
|
+
borderRadius: 24,
|
|
88
|
+
padding: 32,
|
|
89
|
+
alignItems: "center",
|
|
90
|
+
},
|
|
91
|
+
title: {
|
|
92
|
+
fontSize: 20,
|
|
93
|
+
fontWeight: "700",
|
|
94
|
+
marginBottom: 12,
|
|
95
|
+
textAlign: "center",
|
|
96
|
+
},
|
|
97
|
+
message: {
|
|
98
|
+
fontSize: 16,
|
|
99
|
+
marginBottom: 24,
|
|
100
|
+
textAlign: "center",
|
|
101
|
+
opacity: 0.8,
|
|
102
|
+
},
|
|
103
|
+
hint: {
|
|
104
|
+
fontSize: 14,
|
|
105
|
+
textAlign: "center",
|
|
106
|
+
fontStyle: "italic",
|
|
107
|
+
opacity: 0.6,
|
|
108
|
+
marginBottom: 16,
|
|
109
|
+
},
|
|
110
|
+
dismissButton: {
|
|
111
|
+
marginTop: 8,
|
|
112
|
+
paddingVertical: 14,
|
|
113
|
+
paddingHorizontal: 32,
|
|
114
|
+
borderRadius: 12,
|
|
115
|
+
minWidth: 140,
|
|
116
|
+
alignItems: "center",
|
|
117
|
+
},
|
|
118
|
+
dismissText: {
|
|
119
|
+
color: "#FFFFFF",
|
|
120
|
+
fontSize: 16,
|
|
121
|
+
fontWeight: "600",
|
|
122
|
+
},
|
|
123
|
+
});
|
|
@@ -4,39 +4,29 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React from "react";
|
|
7
|
+
import { Modal, View, StyleSheet } from "react-native";
|
|
8
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
7
9
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
TouchableOpacity,
|
|
12
|
-
StyleSheet,
|
|
13
|
-
} from "react-native";
|
|
10
|
+
GenerationProgressContent,
|
|
11
|
+
GenerationProgressContentProps,
|
|
12
|
+
} from "./GenerationProgressContent";
|
|
14
13
|
|
|
15
|
-
export interface
|
|
16
|
-
visible: boolean;
|
|
14
|
+
export interface GenerationProgressRenderProps {
|
|
17
15
|
progress: number;
|
|
18
16
|
title?: string;
|
|
19
17
|
message?: string;
|
|
20
18
|
hint?: string;
|
|
21
|
-
dismissLabel?: string;
|
|
22
19
|
onDismiss?: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface GenerationProgressModalProps
|
|
23
|
+
extends Omit<GenerationProgressContentProps, "backgroundColor"> {
|
|
24
|
+
visible: boolean;
|
|
23
25
|
overlayColor?: string;
|
|
24
26
|
modalBackgroundColor?: string;
|
|
25
|
-
textColor?: string;
|
|
26
|
-
progressColor?: string;
|
|
27
|
-
progressBackgroundColor?: string;
|
|
28
|
-
dismissButtonColor?: string;
|
|
29
27
|
renderContent?: (props: GenerationProgressRenderProps) => React.ReactNode;
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
export interface GenerationProgressRenderProps {
|
|
33
|
-
progress: number;
|
|
34
|
-
title?: string;
|
|
35
|
-
message?: string;
|
|
36
|
-
hint?: string;
|
|
37
|
-
onDismiss?: () => void;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
30
|
export const GenerationProgressModal: React.FC<
|
|
41
31
|
GenerationProgressModalProps
|
|
42
32
|
> = ({
|
|
@@ -45,19 +35,45 @@ export const GenerationProgressModal: React.FC<
|
|
|
45
35
|
title,
|
|
46
36
|
message,
|
|
47
37
|
hint,
|
|
48
|
-
dismissLabel
|
|
38
|
+
dismissLabel,
|
|
49
39
|
onDismiss,
|
|
50
40
|
overlayColor = "rgba(0, 0, 0, 0.7)",
|
|
51
|
-
modalBackgroundColor
|
|
52
|
-
textColor
|
|
53
|
-
progressColor
|
|
54
|
-
progressBackgroundColor
|
|
55
|
-
dismissButtonColor
|
|
41
|
+
modalBackgroundColor,
|
|
42
|
+
textColor,
|
|
43
|
+
progressColor,
|
|
44
|
+
progressBackgroundColor,
|
|
45
|
+
dismissButtonColor,
|
|
56
46
|
renderContent,
|
|
57
47
|
}) => {
|
|
58
|
-
|
|
48
|
+
const tokens = useAppDesignTokens();
|
|
49
|
+
const clampedProgress = Math.max(0, Math.min(100, progress));
|
|
50
|
+
|
|
51
|
+
const content = renderContent ? (
|
|
52
|
+
renderContent({
|
|
53
|
+
progress: clampedProgress,
|
|
54
|
+
title,
|
|
55
|
+
message,
|
|
56
|
+
hint,
|
|
57
|
+
onDismiss,
|
|
58
|
+
})
|
|
59
|
+
) : (
|
|
60
|
+
<GenerationProgressContent
|
|
61
|
+
progress={clampedProgress}
|
|
62
|
+
title={title}
|
|
63
|
+
message={message}
|
|
64
|
+
hint={hint}
|
|
65
|
+
dismissLabel={dismissLabel}
|
|
66
|
+
onDismiss={onDismiss}
|
|
67
|
+
backgroundColor={modalBackgroundColor || tokens.colors.surface}
|
|
68
|
+
textColor={textColor || tokens.colors.textPrimary}
|
|
69
|
+
progressColor={progressColor || tokens.colors.primary}
|
|
70
|
+
progressBackgroundColor={
|
|
71
|
+
progressBackgroundColor || tokens.colors.borderLight
|
|
72
|
+
}
|
|
73
|
+
dismissButtonColor={dismissButtonColor || tokens.colors.primary}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
59
76
|
|
|
60
|
-
if (renderContent) {
|
|
61
77
|
return (
|
|
62
78
|
<Modal
|
|
63
79
|
visible={visible}
|
|
@@ -66,81 +82,11 @@ export const GenerationProgressModal: React.FC<
|
|
|
66
82
|
statusBarTranslucent
|
|
67
83
|
>
|
|
68
84
|
<View style={[styles.overlay, { backgroundColor: overlayColor }]}>
|
|
69
|
-
{
|
|
70
|
-
progress: clampedProgress,
|
|
71
|
-
title,
|
|
72
|
-
message,
|
|
73
|
-
hint,
|
|
74
|
-
onDismiss,
|
|
75
|
-
})}
|
|
85
|
+
{content}
|
|
76
86
|
</View>
|
|
77
87
|
</Modal>
|
|
78
88
|
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return (
|
|
82
|
-
<Modal
|
|
83
|
-
visible={visible}
|
|
84
|
-
transparent
|
|
85
|
-
animationType="fade"
|
|
86
|
-
statusBarTranslucent
|
|
87
|
-
>
|
|
88
|
-
<View style={[styles.overlay, { backgroundColor: overlayColor }]}>
|
|
89
|
-
<View
|
|
90
|
-
style={[styles.modal, { backgroundColor: modalBackgroundColor }]}
|
|
91
|
-
>
|
|
92
|
-
{title && (
|
|
93
|
-
<Text style={[styles.title, { color: textColor }]}>{title}</Text>
|
|
94
|
-
)}
|
|
95
|
-
|
|
96
|
-
{message && (
|
|
97
|
-
<Text style={[styles.message, { color: textColor }]}>
|
|
98
|
-
{message}
|
|
99
|
-
</Text>
|
|
100
|
-
)}
|
|
101
|
-
|
|
102
|
-
<View style={styles.progressContainer}>
|
|
103
|
-
<View
|
|
104
|
-
style={[
|
|
105
|
-
styles.progressBackground,
|
|
106
|
-
{ backgroundColor: progressBackgroundColor },
|
|
107
|
-
]}
|
|
108
|
-
>
|
|
109
|
-
<View
|
|
110
|
-
style={[
|
|
111
|
-
styles.progressFill,
|
|
112
|
-
{
|
|
113
|
-
backgroundColor: progressColor,
|
|
114
|
-
width: `${clampedProgress}%`,
|
|
115
|
-
},
|
|
116
|
-
]}
|
|
117
|
-
/>
|
|
118
|
-
</View>
|
|
119
|
-
<Text style={[styles.progressText, { color: textColor }]}>
|
|
120
|
-
{Math.round(clampedProgress)}%
|
|
121
|
-
</Text>
|
|
122
|
-
</View>
|
|
123
|
-
|
|
124
|
-
{hint && (
|
|
125
|
-
<Text style={[styles.hint, { color: textColor }]}>{hint}</Text>
|
|
126
|
-
)}
|
|
127
|
-
|
|
128
|
-
{onDismiss && (
|
|
129
|
-
<TouchableOpacity
|
|
130
|
-
style={[
|
|
131
|
-
styles.dismissButton,
|
|
132
|
-
{ backgroundColor: dismissButtonColor },
|
|
133
|
-
]}
|
|
134
|
-
onPress={onDismiss}
|
|
135
|
-
>
|
|
136
|
-
<Text style={styles.dismissText}>{dismissLabel}</Text>
|
|
137
|
-
</TouchableOpacity>
|
|
138
|
-
)}
|
|
139
|
-
</View>
|
|
140
|
-
</View>
|
|
141
|
-
</Modal>
|
|
142
|
-
);
|
|
143
|
-
};
|
|
89
|
+
};
|
|
144
90
|
|
|
145
91
|
const styles = StyleSheet.create({
|
|
146
92
|
overlay: {
|
|
@@ -149,63 +95,4 @@ const styles = StyleSheet.create({
|
|
|
149
95
|
alignItems: "center",
|
|
150
96
|
padding: 20,
|
|
151
97
|
},
|
|
152
|
-
modal: {
|
|
153
|
-
width: "100%",
|
|
154
|
-
maxWidth: 400,
|
|
155
|
-
borderRadius: 24,
|
|
156
|
-
padding: 32,
|
|
157
|
-
alignItems: "center",
|
|
158
|
-
},
|
|
159
|
-
title: {
|
|
160
|
-
fontSize: 20,
|
|
161
|
-
fontWeight: "700",
|
|
162
|
-
marginBottom: 12,
|
|
163
|
-
textAlign: "center",
|
|
164
|
-
},
|
|
165
|
-
message: {
|
|
166
|
-
fontSize: 16,
|
|
167
|
-
marginBottom: 24,
|
|
168
|
-
textAlign: "center",
|
|
169
|
-
opacity: 0.8,
|
|
170
|
-
},
|
|
171
|
-
progressContainer: {
|
|
172
|
-
width: "100%",
|
|
173
|
-
marginBottom: 16,
|
|
174
|
-
alignItems: "center",
|
|
175
|
-
},
|
|
176
|
-
progressBackground: {
|
|
177
|
-
width: "100%",
|
|
178
|
-
height: 8,
|
|
179
|
-
borderRadius: 4,
|
|
180
|
-
overflow: "hidden",
|
|
181
|
-
},
|
|
182
|
-
progressFill: {
|
|
183
|
-
height: "100%",
|
|
184
|
-
borderRadius: 4,
|
|
185
|
-
},
|
|
186
|
-
progressText: {
|
|
187
|
-
fontSize: 14,
|
|
188
|
-
fontWeight: "600",
|
|
189
|
-
marginTop: 8,
|
|
190
|
-
},
|
|
191
|
-
hint: {
|
|
192
|
-
fontSize: 14,
|
|
193
|
-
textAlign: "center",
|
|
194
|
-
fontStyle: "italic",
|
|
195
|
-
opacity: 0.6,
|
|
196
|
-
marginBottom: 16,
|
|
197
|
-
},
|
|
198
|
-
dismissButton: {
|
|
199
|
-
marginTop: 8,
|
|
200
|
-
paddingVertical: 14,
|
|
201
|
-
paddingHorizontal: 32,
|
|
202
|
-
borderRadius: 12,
|
|
203
|
-
minWidth: 140,
|
|
204
|
-
alignItems: "center",
|
|
205
|
-
},
|
|
206
|
-
dismissText: {
|
|
207
|
-
color: "#FFFFFF",
|
|
208
|
-
fontSize: 16,
|
|
209
|
-
fontWeight: "600",
|
|
210
|
-
},
|
|
211
98
|
});
|
|
@@ -4,36 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React from "react";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
Text,
|
|
10
|
-
TouchableOpacity,
|
|
11
|
-
ActivityIndicator,
|
|
12
|
-
StyleSheet,
|
|
13
|
-
} from "react-native";
|
|
7
|
+
import { View, Text, ActivityIndicator, StyleSheet } from "react-native";
|
|
8
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
14
9
|
import type { BackgroundJob } from "../../domain/entities/job.types";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
readonly card?: object;
|
|
18
|
-
readonly content?: object;
|
|
19
|
-
readonly header?: object;
|
|
20
|
-
readonly typeText?: object;
|
|
21
|
-
readonly statusText?: object;
|
|
22
|
-
readonly progressBar?: object;
|
|
23
|
-
readonly progressFill?: object;
|
|
24
|
-
readonly actions?: object;
|
|
25
|
-
readonly actionButton?: object;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface PendingJobCardColors {
|
|
29
|
-
readonly background?: string;
|
|
30
|
-
readonly text?: string;
|
|
31
|
-
readonly textSecondary?: string;
|
|
32
|
-
readonly error?: string;
|
|
33
|
-
readonly progressBackground?: string;
|
|
34
|
-
readonly progressFill?: string;
|
|
35
|
-
readonly actionBackground?: string;
|
|
36
|
-
}
|
|
10
|
+
import { PendingJobProgressBar } from "./PendingJobProgressBar";
|
|
11
|
+
import { PendingJobCardActions } from "./PendingJobCardActions";
|
|
37
12
|
|
|
38
13
|
export interface StatusLabels {
|
|
39
14
|
readonly queued?: string;
|
|
@@ -49,10 +24,12 @@ export interface PendingJobCardProps<TInput = unknown, TResult = unknown> {
|
|
|
49
24
|
readonly onRetry?: (id: string) => void;
|
|
50
25
|
readonly typeLabel?: string;
|
|
51
26
|
readonly statusLabels?: StatusLabels;
|
|
52
|
-
readonly
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
readonly renderActions?: (
|
|
27
|
+
readonly renderThumbnail?: (
|
|
28
|
+
job: BackgroundJob<TInput, TResult>,
|
|
29
|
+
) => React.ReactNode;
|
|
30
|
+
readonly renderActions?: (
|
|
31
|
+
job: BackgroundJob<TInput, TResult>,
|
|
32
|
+
) => React.ReactNode;
|
|
56
33
|
}
|
|
57
34
|
|
|
58
35
|
const DEFAULT_STATUS_LABELS: StatusLabels = {
|
|
@@ -63,28 +40,16 @@ const DEFAULT_STATUS_LABELS: StatusLabels = {
|
|
|
63
40
|
failed: "Failed",
|
|
64
41
|
};
|
|
65
42
|
|
|
66
|
-
const DEFAULT_COLORS: Required<PendingJobCardColors> = {
|
|
67
|
-
background: "#1C1C1E",
|
|
68
|
-
text: "#FFFFFF",
|
|
69
|
-
textSecondary: "#8E8E93",
|
|
70
|
-
error: "#FF453A",
|
|
71
|
-
progressBackground: "#3A3A3C",
|
|
72
|
-
progressFill: "#007AFF",
|
|
73
|
-
actionBackground: "#2C2C2E",
|
|
74
|
-
};
|
|
75
|
-
|
|
76
43
|
export function PendingJobCard<TInput = unknown, TResult = unknown>({
|
|
77
44
|
job,
|
|
78
45
|
onCancel,
|
|
79
46
|
onRetry,
|
|
80
47
|
typeLabel,
|
|
81
48
|
statusLabels = DEFAULT_STATUS_LABELS,
|
|
82
|
-
colors = {},
|
|
83
|
-
styles: customStyles = {},
|
|
84
49
|
renderThumbnail,
|
|
85
50
|
renderActions,
|
|
86
51
|
}: PendingJobCardProps<TInput, TResult>): React.ReactElement {
|
|
87
|
-
const
|
|
52
|
+
const tokens = useAppDesignTokens();
|
|
88
53
|
const isFailed = job.status === "failed";
|
|
89
54
|
|
|
90
55
|
const statusText =
|
|
@@ -95,86 +60,44 @@ export function PendingJobCard<TInput = unknown, TResult = unknown>({
|
|
|
95
60
|
const styles = StyleSheet.create({
|
|
96
61
|
card: {
|
|
97
62
|
flexDirection: "row",
|
|
98
|
-
backgroundColor:
|
|
63
|
+
backgroundColor: tokens.colors.surface,
|
|
99
64
|
borderRadius: 16,
|
|
100
65
|
overflow: "hidden",
|
|
101
66
|
opacity: isFailed ? 0.7 : 1,
|
|
102
|
-
|
|
67
|
+
},
|
|
68
|
+
thumbnailWrapper: {
|
|
69
|
+
width: 80,
|
|
70
|
+
height: 80,
|
|
71
|
+
justifyContent: "center",
|
|
72
|
+
alignItems: "center",
|
|
73
|
+
backgroundColor: tokens.colors.backgroundSecondary,
|
|
103
74
|
},
|
|
104
75
|
content: {
|
|
105
76
|
flex: 1,
|
|
106
77
|
padding: 12,
|
|
107
78
|
justifyContent: "space-between",
|
|
108
|
-
...((customStyles.content as object) || {}),
|
|
109
|
-
},
|
|
110
|
-
header: {
|
|
111
|
-
flexDirection: "row",
|
|
112
|
-
alignItems: "center",
|
|
113
|
-
gap: 8,
|
|
114
|
-
...((customStyles.header as object) || {}),
|
|
115
79
|
},
|
|
116
80
|
typeText: {
|
|
117
81
|
fontSize: 14,
|
|
118
82
|
fontWeight: "600",
|
|
119
|
-
color:
|
|
120
|
-
...((customStyles.typeText as object) || {}),
|
|
83
|
+
color: tokens.colors.textPrimary,
|
|
121
84
|
},
|
|
122
85
|
statusText: {
|
|
123
86
|
fontSize: 12,
|
|
124
|
-
color: isFailed ?
|
|
87
|
+
color: isFailed ? tokens.colors.error : tokens.colors.textSecondary,
|
|
125
88
|
marginTop: 4,
|
|
126
|
-
...((customStyles.statusText as object) || {}),
|
|
127
|
-
},
|
|
128
|
-
progressBar: {
|
|
129
|
-
height: 4,
|
|
130
|
-
backgroundColor: mergedColors.progressBackground,
|
|
131
|
-
borderRadius: 2,
|
|
132
|
-
marginTop: 8,
|
|
133
|
-
overflow: "hidden",
|
|
134
|
-
...((customStyles.progressBar as object) || {}),
|
|
135
|
-
},
|
|
136
|
-
progressFill: {
|
|
137
|
-
height: "100%",
|
|
138
|
-
backgroundColor: mergedColors.progressFill,
|
|
139
|
-
borderRadius: 2,
|
|
140
|
-
width: `${job.progress}%`,
|
|
141
|
-
...((customStyles.progressFill as object) || {}),
|
|
142
|
-
},
|
|
143
|
-
actions: {
|
|
144
|
-
flexDirection: "row",
|
|
145
|
-
gap: 8,
|
|
146
|
-
marginTop: 8,
|
|
147
|
-
...((customStyles.actions as object) || {}),
|
|
148
|
-
},
|
|
149
|
-
actionButton: {
|
|
150
|
-
width: 32,
|
|
151
|
-
height: 32,
|
|
152
|
-
borderRadius: 16,
|
|
153
|
-
backgroundColor: mergedColors.actionBackground,
|
|
154
|
-
justifyContent: "center",
|
|
155
|
-
alignItems: "center",
|
|
156
|
-
...((customStyles.actionButton as object) || {}),
|
|
157
|
-
},
|
|
158
|
-
thumbnail: {
|
|
159
|
-
width: 80,
|
|
160
|
-
height: 80,
|
|
161
|
-
justifyContent: "center",
|
|
162
|
-
alignItems: "center",
|
|
163
|
-
backgroundColor: mergedColors.progressBackground,
|
|
164
|
-
},
|
|
165
|
-
loader: {
|
|
166
|
-
position: "absolute",
|
|
167
89
|
},
|
|
90
|
+
loader: { position: "absolute" },
|
|
168
91
|
});
|
|
169
92
|
|
|
170
93
|
return (
|
|
171
94
|
<View style={styles.card}>
|
|
172
95
|
{renderThumbnail && (
|
|
173
|
-
<View style={styles.
|
|
96
|
+
<View style={styles.thumbnailWrapper}>
|
|
174
97
|
{renderThumbnail(job)}
|
|
175
98
|
{!isFailed && (
|
|
176
99
|
<ActivityIndicator
|
|
177
|
-
color={
|
|
100
|
+
color={tokens.colors.primary}
|
|
178
101
|
size="small"
|
|
179
102
|
style={styles.loader}
|
|
180
103
|
/>
|
|
@@ -183,39 +106,21 @@ export function PendingJobCard<TInput = unknown, TResult = unknown>({
|
|
|
183
106
|
)}
|
|
184
107
|
<View style={styles.content}>
|
|
185
108
|
<View>
|
|
186
|
-
<
|
|
187
|
-
{typeLabel && <Text style={styles.typeText}>{typeLabel}</Text>}
|
|
188
|
-
</View>
|
|
109
|
+
{typeLabel && <Text style={styles.typeText}>{typeLabel}</Text>}
|
|
189
110
|
<Text style={styles.statusText} numberOfLines={1}>
|
|
190
111
|
{statusText}
|
|
191
112
|
</Text>
|
|
192
|
-
{!isFailed &&
|
|
193
|
-
<View style={styles.progressBar}>
|
|
194
|
-
<View style={styles.progressFill} />
|
|
195
|
-
</View>
|
|
196
|
-
)}
|
|
113
|
+
{!isFailed && <PendingJobProgressBar progress={job.progress} />}
|
|
197
114
|
</View>
|
|
198
115
|
{renderActions ? (
|
|
199
116
|
renderActions(job)
|
|
200
117
|
) : (
|
|
201
|
-
<
|
|
202
|
-
{
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
<Text style={{ color: mergedColors.text }}>↻</Text>
|
|
208
|
-
</TouchableOpacity>
|
|
209
|
-
)}
|
|
210
|
-
{onCancel && (
|
|
211
|
-
<TouchableOpacity
|
|
212
|
-
style={styles.actionButton}
|
|
213
|
-
onPress={() => onCancel(job.id)}
|
|
214
|
-
>
|
|
215
|
-
<Text style={{ color: mergedColors.error }}>✕</Text>
|
|
216
|
-
</TouchableOpacity>
|
|
217
|
-
)}
|
|
218
|
-
</View>
|
|
118
|
+
<PendingJobCardActions
|
|
119
|
+
id={job.id}
|
|
120
|
+
isFailed={isFailed}
|
|
121
|
+
onCancel={onCancel}
|
|
122
|
+
onRetry={onRetry}
|
|
123
|
+
/>
|
|
219
124
|
)}
|
|
220
125
|
</View>
|
|
221
126
|
</View>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PendingJobCardActions
|
|
3
|
+
* Action buttons for the PendingJobCard
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, TouchableOpacity, Text, StyleSheet } from "react-native";
|
|
8
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
9
|
+
|
|
10
|
+
export interface PendingJobCardActionsProps {
|
|
11
|
+
id: string;
|
|
12
|
+
isFailed: boolean;
|
|
13
|
+
onCancel?: (id: string) => void;
|
|
14
|
+
onRetry?: (id: string) => void;
|
|
15
|
+
textColor?: string;
|
|
16
|
+
errorColor?: string;
|
|
17
|
+
backgroundColor?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const PendingJobCardActions: React.FC<PendingJobCardActionsProps> = ({
|
|
21
|
+
id,
|
|
22
|
+
isFailed,
|
|
23
|
+
onCancel,
|
|
24
|
+
onRetry,
|
|
25
|
+
textColor,
|
|
26
|
+
errorColor,
|
|
27
|
+
backgroundColor,
|
|
28
|
+
}) => {
|
|
29
|
+
const tokens = useAppDesignTokens();
|
|
30
|
+
|
|
31
|
+
const styles = StyleSheet.create({
|
|
32
|
+
actions: {
|
|
33
|
+
flexDirection: "row",
|
|
34
|
+
gap: 8,
|
|
35
|
+
marginTop: 8,
|
|
36
|
+
},
|
|
37
|
+
actionButton: {
|
|
38
|
+
width: 32,
|
|
39
|
+
height: 32,
|
|
40
|
+
borderRadius: 16,
|
|
41
|
+
backgroundColor: backgroundColor || tokens.colors.backgroundSecondary,
|
|
42
|
+
justifyContent: "center",
|
|
43
|
+
alignItems: "center",
|
|
44
|
+
},
|
|
45
|
+
text: {
|
|
46
|
+
color: textColor || tokens.colors.textPrimary,
|
|
47
|
+
},
|
|
48
|
+
errorText: {
|
|
49
|
+
color: errorColor || tokens.colors.error,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<View style={styles.actions}>
|
|
55
|
+
{isFailed && onRetry && (
|
|
56
|
+
<TouchableOpacity
|
|
57
|
+
style={styles.actionButton}
|
|
58
|
+
onPress={() => onRetry(id)}
|
|
59
|
+
>
|
|
60
|
+
<Text style={styles.text}>↻</Text>
|
|
61
|
+
</TouchableOpacity>
|
|
62
|
+
)}
|
|
63
|
+
{onCancel && (
|
|
64
|
+
<TouchableOpacity
|
|
65
|
+
style={styles.actionButton}
|
|
66
|
+
onPress={() => onCancel(id)}
|
|
67
|
+
>
|
|
68
|
+
<Text style={styles.errorText}>✕</Text>
|
|
69
|
+
</TouchableOpacity>
|
|
70
|
+
)}
|
|
71
|
+
</View>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PendingJobProgressBar
|
|
3
|
+
* Individual progress bar for the PendingJobCard
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, StyleSheet } from "react-native";
|
|
8
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
9
|
+
|
|
10
|
+
export interface PendingJobProgressBarProps {
|
|
11
|
+
progress: number;
|
|
12
|
+
backgroundColor?: string;
|
|
13
|
+
fillColor?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const PendingJobProgressBar: React.FC<PendingJobProgressBarProps> = ({
|
|
17
|
+
progress,
|
|
18
|
+
backgroundColor,
|
|
19
|
+
fillColor,
|
|
20
|
+
}) => {
|
|
21
|
+
const tokens = useAppDesignTokens();
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<View
|
|
25
|
+
style={[
|
|
26
|
+
styles.progressContainer,
|
|
27
|
+
{ backgroundColor: backgroundColor || tokens.colors.borderLight },
|
|
28
|
+
]}
|
|
29
|
+
>
|
|
30
|
+
<View
|
|
31
|
+
style={[
|
|
32
|
+
styles.progressFill,
|
|
33
|
+
{
|
|
34
|
+
backgroundColor: fillColor || tokens.colors.primary,
|
|
35
|
+
width: `${Math.max(0, Math.min(100, progress))}%`,
|
|
36
|
+
},
|
|
37
|
+
]}
|
|
38
|
+
/>
|
|
39
|
+
</View>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const styles = StyleSheet.create({
|
|
44
|
+
progressContainer: {
|
|
45
|
+
height: 4,
|
|
46
|
+
borderRadius: 2,
|
|
47
|
+
marginTop: 8,
|
|
48
|
+
overflow: "hidden",
|
|
49
|
+
},
|
|
50
|
+
progressFill: {
|
|
51
|
+
height: "100%",
|
|
52
|
+
borderRadius: 2,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export {
|
|
6
|
-
|
|
7
|
-
} from "./GenerationProgressModal";
|
|
1
|
+
export { GenerationProgressModal } from "./GenerationProgressModal";
|
|
2
|
+
export { GenerationProgressContent } from "./GenerationProgressContent";
|
|
3
|
+
export { GenerationProgressBar } from "./GenerationProgressBar";
|
|
4
|
+
export { PendingJobCard } from "./PendingJobCard";
|
|
5
|
+
export { PendingJobProgressBar } from "./PendingJobProgressBar";
|
|
6
|
+
export { PendingJobCardActions } from "./PendingJobCardActions";
|
|
8
7
|
|
|
9
8
|
export type {
|
|
10
9
|
GenerationProgressModalProps,
|
|
11
10
|
GenerationProgressRenderProps,
|
|
12
11
|
} from "./GenerationProgressModal";
|
|
13
12
|
|
|
14
|
-
export {
|
|
13
|
+
export type { GenerationProgressContentProps } from "./GenerationProgressContent";
|
|
14
|
+
export type { GenerationProgressBarProps } from "./GenerationProgressBar";
|
|
15
15
|
|
|
16
16
|
export type {
|
|
17
17
|
PendingJobCardProps,
|
|
18
|
-
PendingJobCardStyles,
|
|
19
|
-
PendingJobCardColors,
|
|
20
18
|
StatusLabels,
|
|
21
19
|
} from "./PendingJobCard";
|
|
20
|
+
|
|
21
|
+
export type { PendingJobProgressBarProps } from "./PendingJobProgressBar";
|
|
22
|
+
export type { PendingJobCardActionsProps } from "./PendingJobCardActions";
|
|
@@ -159,7 +159,7 @@ export function useBackgroundGeneration<TInput = unknown, TResult = unknown>(
|
|
|
159
159
|
});
|
|
160
160
|
|
|
161
161
|
activeJobsRef.current.add(jobId);
|
|
162
|
-
executeJob(jobId, input);
|
|
162
|
+
void executeJob(jobId, input);
|
|
163
163
|
|
|
164
164
|
return jobId;
|
|
165
165
|
},
|
|
@@ -180,7 +180,7 @@ export function useBackgroundGeneration<TInput = unknown, TResult = unknown>(
|
|
|
180
180
|
const jobData = jobInputsRef.current.get(id);
|
|
181
181
|
if (!jobData) return;
|
|
182
182
|
removeJob(id);
|
|
183
|
-
startJob(jobData.input, jobData.type);
|
|
183
|
+
void startJob(jobData.input, jobData.type);
|
|
184
184
|
},
|
|
185
185
|
[removeJob, startJob],
|
|
186
186
|
);
|
|
@@ -43,7 +43,7 @@ export function usePendingJobs<TInput = unknown, TResult = unknown>(
|
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
const addJobMutation = useMutation({
|
|
46
|
-
mutationFn:
|
|
46
|
+
mutationFn: (input: AddJobInput<TInput>) => {
|
|
47
47
|
const newJob: BackgroundJob<TInput, TResult> = {
|
|
48
48
|
id: input.id,
|
|
49
49
|
input: input.input,
|
|
@@ -52,7 +52,7 @@ export function usePendingJobs<TInput = unknown, TResult = unknown>(
|
|
|
52
52
|
progress: input.progress ?? 0,
|
|
53
53
|
createdAt: new Date(),
|
|
54
54
|
};
|
|
55
|
-
return newJob;
|
|
55
|
+
return Promise.resolve(newJob);
|
|
56
56
|
},
|
|
57
57
|
onSuccess: (newJob) => {
|
|
58
58
|
queryClient.setQueryData<BackgroundJob<TInput, TResult>[]>(
|
|
@@ -63,7 +63,8 @@ export function usePendingJobs<TInput = unknown, TResult = unknown>(
|
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
const updateJobMutation = useMutation({
|
|
66
|
-
mutationFn:
|
|
66
|
+
mutationFn: ({ id, updates }: UpdateJobInput) =>
|
|
67
|
+
Promise.resolve({ id, updates }),
|
|
67
68
|
onSuccess: ({ id, updates }) => {
|
|
68
69
|
queryClient.setQueryData<BackgroundJob<TInput, TResult>[]>(
|
|
69
70
|
queryKey,
|
|
@@ -78,7 +79,7 @@ export function usePendingJobs<TInput = unknown, TResult = unknown>(
|
|
|
78
79
|
});
|
|
79
80
|
|
|
80
81
|
const removeJobMutation = useMutation({
|
|
81
|
-
mutationFn:
|
|
82
|
+
mutationFn: (id: string) => Promise.resolve(id),
|
|
82
83
|
onSuccess: (id) => {
|
|
83
84
|
queryClient.setQueryData<BackgroundJob<TInput, TResult>[]>(
|
|
84
85
|
queryKey,
|
|
@@ -88,7 +89,7 @@ export function usePendingJobs<TInput = unknown, TResult = unknown>(
|
|
|
88
89
|
});
|
|
89
90
|
|
|
90
91
|
const clearCompletedMutation = useMutation({
|
|
91
|
-
mutationFn:
|
|
92
|
+
mutationFn: () => Promise.resolve(null),
|
|
92
93
|
onSuccess: () => {
|
|
93
94
|
queryClient.setQueryData<BackgroundJob<TInput, TResult>[]>(
|
|
94
95
|
queryKey,
|
|
@@ -98,7 +99,7 @@ export function usePendingJobs<TInput = unknown, TResult = unknown>(
|
|
|
98
99
|
});
|
|
99
100
|
|
|
100
101
|
const clearFailedMutation = useMutation({
|
|
101
|
-
mutationFn:
|
|
102
|
+
mutationFn: () => Promise.resolve(null),
|
|
102
103
|
onSuccess: () => {
|
|
103
104
|
queryClient.setQueryData<BackgroundJob<TInput, TResult>[]>(
|
|
104
105
|
queryKey,
|