@comergehq/studio 0.1.1 → 0.1.3
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/dist/index.js +255 -245
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +213 -203
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -6
- package/src/components/chat/ChatComposer.tsx +277 -0
- package/src/components/chat/ChatHeader.tsx +31 -0
- package/src/components/chat/ChatMessageBubble.tsx +69 -0
- package/src/components/chat/ChatMessageList.tsx +137 -0
- package/src/components/chat/ChatPage.tsx +69 -0
- package/src/components/chat/ForkNoticeBanner.tsx +66 -0
- package/src/components/chat/MultilineTextInput.tsx +46 -0
- package/src/components/chat/ScrollToBottomButton.tsx +78 -0
- package/src/components/chat/TypingIndicator.tsx +54 -0
- package/src/components/chat/index.ts +28 -0
- package/src/components/comments/AppCommentsSheet.tsx +213 -0
- package/src/components/comments/CommentRow.tsx +63 -0
- package/src/components/comments/formatTimeAgo.ts +3 -0
- package/src/components/comments/index.ts +3 -0
- package/src/components/comments/useAppComments.ts +74 -0
- package/src/components/comments/useAppDetails.ts +35 -0
- package/src/components/comments/useIosKeyboardSnapFix.ts +24 -0
- package/src/components/dialogs/ConfirmMergeRequestDialog.tsx +156 -0
- package/src/components/dialogs/index.ts +4 -0
- package/src/components/draw/DrawColorPicker.tsx +77 -0
- package/src/components/draw/DrawModeOverlay.tsx +144 -0
- package/src/components/draw/DrawSurface.tsx +127 -0
- package/src/components/draw/DrawToolbar.tsx +253 -0
- package/src/components/draw/index.ts +15 -0
- package/src/components/draw/optionalHaptics.ts +15 -0
- package/src/components/draw/strokes.ts +21 -0
- package/src/components/draw/types.ts +9 -0
- package/src/components/floating-draggable-button/FloatingDraggableButton.tsx +323 -0
- package/src/components/floating-draggable-button/constants.ts +17 -0
- package/src/components/floating-draggable-button/index.ts +4 -0
- package/src/components/floating-draggable-button/types.ts +63 -0
- package/src/components/icons/MergeIcon.tsx +14 -0
- package/src/components/icons/StudioIcons.tsx +66 -0
- package/src/components/index.ts +17 -0
- package/src/components/merge-requests/MergeRequestStatusCard.tsx +179 -0
- package/src/components/merge-requests/ReviewMergeRequestActionButton.tsx +62 -0
- package/src/components/merge-requests/ReviewMergeRequestCard.tsx +192 -0
- package/src/components/merge-requests/ReviewMergeRequestCarousel.tsx +132 -0
- package/src/components/merge-requests/index.ts +7 -0
- package/src/components/merge-requests/mergeRequestStatusDisplay.ts +23 -0
- package/src/components/merge-requests/toIsoString.ts +9 -0
- package/src/components/merge-requests/useControlledExpansion.ts +16 -0
- package/src/components/models/index.ts +9 -0
- package/src/components/models/types.ts +43 -0
- package/src/components/overlays/EdgeGlowFrame.tsx +105 -0
- package/src/components/overlays/index.ts +4 -0
- package/src/components/preview/PreviewHeroCard.tsx +58 -0
- package/src/components/preview/PreviewImage.tsx +22 -0
- package/src/components/preview/PreviewMetaRow.tsx +70 -0
- package/src/components/preview/PreviewPage.tsx +36 -0
- package/src/components/preview/PreviewPlaceholder.tsx +72 -0
- package/src/components/preview/PreviewStatusBadge.tsx +63 -0
- package/src/components/preview/StatsBar.tsx +109 -0
- package/src/components/preview/index.ts +22 -0
- package/src/components/primitives/Avatar.tsx +68 -0
- package/src/components/primitives/Button.tsx +102 -0
- package/src/components/primitives/Card.tsx +30 -0
- package/src/components/primitives/Divider.tsx +17 -0
- package/src/components/primitives/Icon.tsx +40 -0
- package/src/components/primitives/MarkdownText.tsx +72 -0
- package/src/components/primitives/Modal.tsx +53 -0
- package/src/components/primitives/Surface.tsx +42 -0
- package/src/components/primitives/Text.tsx +83 -0
- package/src/components/primitives/index.ts +35 -0
- package/src/components/primitives/types.ts +30 -0
- package/src/components/studio-sheet/StudioBottomSheet.tsx +114 -0
- package/src/components/studio-sheet/StudioSheetBackground.tsx +63 -0
- package/src/components/studio-sheet/StudioSheetHeader.tsx +35 -0
- package/src/components/studio-sheet/StudioSheetHeaderIconButton.tsx +109 -0
- package/src/components/studio-sheet/StudioSheetPager.tsx +66 -0
- package/src/components/studio-sheet/index.ts +18 -0
- package/src/components/studio-sheet/types.ts +5 -0
- package/src/components/utils/color.ts +25 -0
- package/src/components/utils/formatTimeAgo.ts +19 -0
- package/src/core/logger.ts +42 -0
- package/src/core/services/http/baseUrl.ts +3 -0
- package/src/core/services/http/index.ts +128 -0
- package/src/core/services/http/public.ts +14 -0
- package/src/core/services/supabase/auth.ts +41 -0
- package/src/core/services/supabase/client.ts +43 -0
- package/src/core/services/supabase/index.ts +7 -0
- package/src/data/agent/remote.ts +30 -0
- package/src/data/agent/repository.ts +34 -0
- package/src/data/agent/types.ts +28 -0
- package/src/data/apps/bundles/remote.ts +47 -0
- package/src/data/apps/bundles/repository.ts +35 -0
- package/src/data/apps/bundles/types.ts +27 -0
- package/src/data/apps/images/remote.ts +61 -0
- package/src/data/apps/images/repository.ts +47 -0
- package/src/data/apps/remote.ts +97 -0
- package/src/data/apps/repository.ts +185 -0
- package/src/data/apps/types.ts +206 -0
- package/src/data/attachment/remote.ts +32 -0
- package/src/data/attachment/repository.ts +40 -0
- package/src/data/attachment/types.ts +42 -0
- package/src/data/base-remote.ts +3 -0
- package/src/data/base-repository.ts +11 -0
- package/src/data/comments/likes/remote.ts +87 -0
- package/src/data/comments/likes/repository.ts +61 -0
- package/src/data/comments/likes/types.ts +47 -0
- package/src/data/comments/remote.ts +71 -0
- package/src/data/comments/repository.ts +53 -0
- package/src/data/comments/types.ts +60 -0
- package/src/data/github/remote.ts +23 -0
- package/src/data/github/repository.ts +35 -0
- package/src/data/github/types.ts +23 -0
- package/src/data/home/remote.ts +24 -0
- package/src/data/home/repository.ts +28 -0
- package/src/data/home/types.ts +70 -0
- package/src/data/index.ts +3 -0
- package/src/data/likes/remote.ts +57 -0
- package/src/data/likes/repository.ts +47 -0
- package/src/data/likes/types.ts +46 -0
- package/src/data/me/remote.ts +28 -0
- package/src/data/me/repository.ts +30 -0
- package/src/data/me/types.ts +14 -0
- package/src/data/merge-requests/remote.ts +76 -0
- package/src/data/merge-requests/repository.ts +66 -0
- package/src/data/merge-requests/types.ts +33 -0
- package/src/data/messages/remote.ts +21 -0
- package/src/data/messages/repository.ts +104 -0
- package/src/data/messages/types.ts +20 -0
- package/src/data/public/studio-config/remote.ts +19 -0
- package/src/data/public/studio-config/repository.ts +23 -0
- package/src/data/public/studio-config/types.ts +6 -0
- package/src/data/ratings/remote.ts +76 -0
- package/src/data/ratings/repository.ts +63 -0
- package/src/data/ratings/types.ts +57 -0
- package/src/data/threads/remote.ts +40 -0
- package/src/data/threads/repository.ts +41 -0
- package/src/data/threads/types.ts +25 -0
- package/src/data/types.ts +8 -0
- package/src/data/users/remote.ts +31 -0
- package/src/data/users/repository.ts +45 -0
- package/src/data/users/types.ts +15 -0
- package/src/index.ts +6 -0
- package/src/studio/ComergeStudio.tsx +246 -0
- package/src/studio/bootstrap/StudioBootstrap.tsx +45 -0
- package/src/studio/bootstrap/useStudioBootstrap.ts +51 -0
- package/src/studio/hooks/useApp.ts +83 -0
- package/src/studio/hooks/useAppStats.ts +111 -0
- package/src/studio/hooks/useAttachmentUpload.ts +59 -0
- package/src/studio/hooks/useBundleManager.ts +389 -0
- package/src/studio/hooks/useMergeRequests.ts +173 -0
- package/src/studio/hooks/useStudioActions.ts +96 -0
- package/src/studio/hooks/useThreadMessages.ts +85 -0
- package/src/studio/lib/chat.ts +34 -0
- package/src/studio/ui/ChatPanel.tsx +154 -0
- package/src/studio/ui/ConfirmMergeFlow.tsx +55 -0
- package/src/studio/ui/PreviewPanel.tsx +131 -0
- package/src/studio/ui/RuntimeRenderer.tsx +40 -0
- package/src/studio/ui/StudioOverlay.tsx +257 -0
- package/src/studio/ui/preview-panel/PressableCardRow.tsx +49 -0
- package/src/studio/ui/preview-panel/PreviewCollaborateSection.tsx +174 -0
- package/src/studio/ui/preview-panel/PreviewCustomizeSection.tsx +160 -0
- package/src/studio/ui/preview-panel/PreviewHeroSection.tsx +56 -0
- package/src/studio/ui/preview-panel/PreviewMetaSection.tsx +67 -0
- package/src/studio/ui/preview-panel/PreviewPanelHeader.tsx +48 -0
- package/src/studio/ui/preview-panel/SectionTitle.tsx +31 -0
- package/src/studio/ui/preview-panel/usePreviewPanelData.ts +132 -0
- package/src/studio/ui/preview-panel/utils.ts +29 -0
- package/src/theme/index.ts +5 -0
- package/src/theme/tokens.ts +118 -0
- package/src/theme/types.ts +90 -0
- package/src/theme/useTheme.ts +11 -0
- package/dist/assets/images/merge.svg +0 -3
- package/dist/merge-72UG27QV.svg +0 -3
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Keyboard, View, useWindowDimensions } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import type { App } from '../../data/apps/types';
|
|
5
|
+
import type { MergeRequest } from '../../data/merge-requests/types';
|
|
6
|
+
import { StudioBottomSheet } from '../../components/studio-sheet/StudioBottomSheet';
|
|
7
|
+
import { StudioSheetPager } from '../../components/studio-sheet/StudioSheetPager';
|
|
8
|
+
import { FloatingDraggableButton } from '../../components/floating-draggable-button/FloatingDraggableButton';
|
|
9
|
+
import { EdgeGlowFrame } from '../../components/overlays/EdgeGlowFrame';
|
|
10
|
+
import { DrawModeOverlay } from '../../components/draw/DrawModeOverlay';
|
|
11
|
+
import { AppCommentsSheet } from '../../components/comments/AppCommentsSheet';
|
|
12
|
+
import { PreviewPanel } from './PreviewPanel';
|
|
13
|
+
import { ChatPanel } from './ChatPanel';
|
|
14
|
+
import { ConfirmMergeFlow } from './ConfirmMergeFlow';
|
|
15
|
+
import type { MergeRequestSummary } from '../../components/models/types';
|
|
16
|
+
import { useTheme } from '../../theme';
|
|
17
|
+
|
|
18
|
+
import { MergeIcon } from '../../components/icons/MergeIcon';
|
|
19
|
+
|
|
20
|
+
export type StudioOverlayProps = {
|
|
21
|
+
captureTargetRef: React.RefObject<View | null>;
|
|
22
|
+
|
|
23
|
+
app: App | null;
|
|
24
|
+
appLoading?: boolean;
|
|
25
|
+
|
|
26
|
+
// Studio state
|
|
27
|
+
isOwner: boolean;
|
|
28
|
+
shouldForkOnEdit: boolean;
|
|
29
|
+
|
|
30
|
+
// Bundle testing (glow + restore)
|
|
31
|
+
isTesting: boolean;
|
|
32
|
+
onRestoreBase: () => void | Promise<void>;
|
|
33
|
+
|
|
34
|
+
// Merge requests
|
|
35
|
+
incomingMergeRequests: MergeRequest[];
|
|
36
|
+
outgoingMergeRequests: MergeRequest[];
|
|
37
|
+
creatorStatsById: Record<string, import('../../data/users/types').UserStats>;
|
|
38
|
+
processingMrId?: string | null;
|
|
39
|
+
isBuildingMrTest?: boolean;
|
|
40
|
+
testingMrId?: string | null;
|
|
41
|
+
toMergeRequestSummary: (mr: MergeRequest) => MergeRequestSummary;
|
|
42
|
+
onSubmitMergeRequest?: () => void | Promise<void>;
|
|
43
|
+
onApprove?: (mr: MergeRequest) => void | Promise<void>;
|
|
44
|
+
onReject?: (mr: MergeRequest) => void | Promise<void>;
|
|
45
|
+
onTestMr?: (mr: MergeRequest) => void | Promise<void>;
|
|
46
|
+
|
|
47
|
+
// Chat
|
|
48
|
+
chatMessages: import('../../components/models/types').ChatMessage[];
|
|
49
|
+
chatLoading?: boolean;
|
|
50
|
+
chatSendDisabled?: boolean;
|
|
51
|
+
chatForking?: boolean;
|
|
52
|
+
chatSending?: boolean;
|
|
53
|
+
chatShowTypingIndicator?: boolean;
|
|
54
|
+
onSendChat: (text: string, attachments?: string[]) => void | Promise<void>;
|
|
55
|
+
|
|
56
|
+
// Navigation callbacks
|
|
57
|
+
onNavigateHome?: () => void;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
type SheetPage = 'preview' | 'chat';
|
|
61
|
+
|
|
62
|
+
export function StudioOverlay({
|
|
63
|
+
captureTargetRef,
|
|
64
|
+
app,
|
|
65
|
+
appLoading,
|
|
66
|
+
isOwner,
|
|
67
|
+
shouldForkOnEdit,
|
|
68
|
+
isTesting,
|
|
69
|
+
onRestoreBase,
|
|
70
|
+
incomingMergeRequests,
|
|
71
|
+
outgoingMergeRequests,
|
|
72
|
+
creatorStatsById,
|
|
73
|
+
processingMrId,
|
|
74
|
+
isBuildingMrTest,
|
|
75
|
+
testingMrId,
|
|
76
|
+
toMergeRequestSummary,
|
|
77
|
+
onSubmitMergeRequest,
|
|
78
|
+
onApprove,
|
|
79
|
+
onReject,
|
|
80
|
+
onTestMr,
|
|
81
|
+
chatMessages,
|
|
82
|
+
chatLoading,
|
|
83
|
+
chatSendDisabled,
|
|
84
|
+
chatForking,
|
|
85
|
+
chatSending,
|
|
86
|
+
chatShowTypingIndicator,
|
|
87
|
+
onSendChat,
|
|
88
|
+
onNavigateHome,
|
|
89
|
+
}: StudioOverlayProps) {
|
|
90
|
+
const theme = useTheme();
|
|
91
|
+
const { width } = useWindowDimensions();
|
|
92
|
+
|
|
93
|
+
const [sheetOpen, setSheetOpen] = React.useState(false);
|
|
94
|
+
const [activePage, setActivePage] = React.useState<SheetPage>('preview');
|
|
95
|
+
|
|
96
|
+
const [drawing, setDrawing] = React.useState(false);
|
|
97
|
+
const [chatAttachments, setChatAttachments] = React.useState<string[]>([]);
|
|
98
|
+
const [commentsAppId, setCommentsAppId] = React.useState<string | null>(null);
|
|
99
|
+
const [commentsCount, setCommentsCount] = React.useState<number | null>(null);
|
|
100
|
+
|
|
101
|
+
const [confirmMrId, setConfirmMrId] = React.useState<string | null>(null);
|
|
102
|
+
const confirmMr = React.useMemo(
|
|
103
|
+
() => (confirmMrId ? incomingMergeRequests.find((m) => m.id === confirmMrId) ?? null : null),
|
|
104
|
+
[confirmMrId, incomingMergeRequests]
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const closeSheet = React.useCallback(() => {
|
|
108
|
+
setSheetOpen(false);
|
|
109
|
+
Keyboard.dismiss();
|
|
110
|
+
}, []);
|
|
111
|
+
|
|
112
|
+
const openSheet = React.useCallback(() => setSheetOpen(true), []);
|
|
113
|
+
|
|
114
|
+
const goToChat = React.useCallback(() => {
|
|
115
|
+
setActivePage('chat');
|
|
116
|
+
openSheet();
|
|
117
|
+
}, [openSheet]);
|
|
118
|
+
|
|
119
|
+
const backToPreview = React.useCallback(() => {
|
|
120
|
+
Keyboard.dismiss();
|
|
121
|
+
setActivePage('preview');
|
|
122
|
+
}, []);
|
|
123
|
+
|
|
124
|
+
const startDraw = React.useCallback(() => {
|
|
125
|
+
setDrawing(true);
|
|
126
|
+
closeSheet();
|
|
127
|
+
}, [closeSheet]);
|
|
128
|
+
|
|
129
|
+
const handleDrawCapture = React.useCallback(
|
|
130
|
+
(dataUrl: string) => {
|
|
131
|
+
setChatAttachments((prev) => [...prev, dataUrl]);
|
|
132
|
+
setDrawing(false);
|
|
133
|
+
setActivePage('chat');
|
|
134
|
+
openSheet();
|
|
135
|
+
},
|
|
136
|
+
[openSheet]
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const toggleSheet = React.useCallback(async () => {
|
|
140
|
+
if (!sheetOpen) {
|
|
141
|
+
const shouldExitTest = Boolean(testingMrId) || isTesting;
|
|
142
|
+
if (shouldExitTest) {
|
|
143
|
+
void Promise.resolve(onRestoreBase()).catch(() => {});
|
|
144
|
+
}
|
|
145
|
+
setSheetOpen(true);
|
|
146
|
+
} else {
|
|
147
|
+
closeSheet();
|
|
148
|
+
}
|
|
149
|
+
}, [closeSheet, isTesting, onRestoreBase, sheetOpen, testingMrId]);
|
|
150
|
+
|
|
151
|
+
const handleTestMr = React.useCallback(
|
|
152
|
+
async (mr: MergeRequest) => {
|
|
153
|
+
if (!onTestMr) return;
|
|
154
|
+
await onTestMr(mr);
|
|
155
|
+
closeSheet();
|
|
156
|
+
},
|
|
157
|
+
[closeSheet, onTestMr]
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<>
|
|
162
|
+
{/* Testing glow around runtime */}
|
|
163
|
+
<EdgeGlowFrame visible={isTesting} role="accent" thickness={40} intensity={1} />
|
|
164
|
+
|
|
165
|
+
<StudioBottomSheet open={sheetOpen} onOpenChange={setSheetOpen}>
|
|
166
|
+
<StudioSheetPager
|
|
167
|
+
activePage={activePage}
|
|
168
|
+
width={width}
|
|
169
|
+
preview={
|
|
170
|
+
<PreviewPanel
|
|
171
|
+
app={app}
|
|
172
|
+
loading={appLoading}
|
|
173
|
+
isOwner={isOwner}
|
|
174
|
+
shouldForkOnEdit={shouldForkOnEdit}
|
|
175
|
+
incomingMergeRequests={incomingMergeRequests}
|
|
176
|
+
outgoingMergeRequests={outgoingMergeRequests}
|
|
177
|
+
creatorStatsById={creatorStatsById}
|
|
178
|
+
processingMrId={processingMrId}
|
|
179
|
+
isBuildingMrTest={isBuildingMrTest}
|
|
180
|
+
testingMrId={testingMrId}
|
|
181
|
+
toMergeRequestSummary={toMergeRequestSummary}
|
|
182
|
+
onClose={closeSheet}
|
|
183
|
+
onNavigateHome={onNavigateHome}
|
|
184
|
+
onGoToChat={goToChat}
|
|
185
|
+
onStartDraw={isOwner ? startDraw : undefined}
|
|
186
|
+
onSubmitMergeRequest={onSubmitMergeRequest}
|
|
187
|
+
onRequestApprove={(mr) => setConfirmMrId(mr.id)}
|
|
188
|
+
onReject={onReject}
|
|
189
|
+
onTestMr={handleTestMr}
|
|
190
|
+
onOpenComments={() => setCommentsAppId(app?.id ?? null)}
|
|
191
|
+
commentCountOverride={commentsCount ?? undefined}
|
|
192
|
+
/>
|
|
193
|
+
}
|
|
194
|
+
chat={
|
|
195
|
+
<ChatPanel
|
|
196
|
+
messages={chatMessages}
|
|
197
|
+
showTypingIndicator={chatShowTypingIndicator}
|
|
198
|
+
loading={chatLoading}
|
|
199
|
+
sendDisabled={chatSendDisabled}
|
|
200
|
+
forking={chatForking}
|
|
201
|
+
sending={chatSending}
|
|
202
|
+
autoFocusComposer={sheetOpen && activePage === 'chat'}
|
|
203
|
+
shouldForkOnEdit={shouldForkOnEdit}
|
|
204
|
+
attachments={chatAttachments}
|
|
205
|
+
onRemoveAttachment={(idx) => setChatAttachments((prev) => prev.filter((_, i) => i !== idx))}
|
|
206
|
+
onClearAttachments={() => setChatAttachments([])}
|
|
207
|
+
onBack={backToPreview}
|
|
208
|
+
onClose={closeSheet}
|
|
209
|
+
onNavigateHome={onNavigateHome}
|
|
210
|
+
onStartDraw={startDraw}
|
|
211
|
+
onSend={onSendChat}
|
|
212
|
+
/>
|
|
213
|
+
}
|
|
214
|
+
/>
|
|
215
|
+
</StudioBottomSheet>
|
|
216
|
+
|
|
217
|
+
<FloatingDraggableButton
|
|
218
|
+
visible={!sheetOpen && !drawing}
|
|
219
|
+
ariaLabel={sheetOpen ? 'Hide studio' : 'Show studio'}
|
|
220
|
+
badgeCount={incomingMergeRequests.length}
|
|
221
|
+
onPress={toggleSheet}
|
|
222
|
+
isLoading={app?.status === 'editing'}
|
|
223
|
+
>
|
|
224
|
+
<View style={{ width: 28, height: 28, alignItems: 'center', justifyContent: 'center' }}>
|
|
225
|
+
<MergeIcon width={24} height={24} color={theme.colors.floatingContent} />
|
|
226
|
+
</View>
|
|
227
|
+
</FloatingDraggableButton>
|
|
228
|
+
|
|
229
|
+
<DrawModeOverlay
|
|
230
|
+
visible={drawing}
|
|
231
|
+
captureTargetRef={captureTargetRef}
|
|
232
|
+
onCancel={() => setDrawing(false)}
|
|
233
|
+
onCapture={handleDrawCapture}
|
|
234
|
+
/>
|
|
235
|
+
|
|
236
|
+
<ConfirmMergeFlow
|
|
237
|
+
visible={Boolean(confirmMr)}
|
|
238
|
+
onOpenChange={(open) => {
|
|
239
|
+
if (!open) setConfirmMrId(null);
|
|
240
|
+
}}
|
|
241
|
+
mergeRequest={confirmMr}
|
|
242
|
+
toSummary={toMergeRequestSummary}
|
|
243
|
+
onConfirm={(mr) => onApprove?.(mr)}
|
|
244
|
+
onTestFirst={handleTestMr}
|
|
245
|
+
/>
|
|
246
|
+
|
|
247
|
+
<AppCommentsSheet
|
|
248
|
+
appId={commentsAppId}
|
|
249
|
+
onClose={() => setCommentsAppId(null)}
|
|
250
|
+
onCountChange={(count) => setCommentsCount(count)}
|
|
251
|
+
onPlayApp={() => setCommentsAppId(null)}
|
|
252
|
+
/>
|
|
253
|
+
</>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Pressable, View, type ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { Card } from '../../../components/primitives/Card';
|
|
5
|
+
|
|
6
|
+
export type PressableCardRowProps = {
|
|
7
|
+
accessibilityLabel: string;
|
|
8
|
+
onPress: () => void;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
left: React.ReactNode;
|
|
11
|
+
title: React.ReactNode;
|
|
12
|
+
subtitle?: React.ReactNode;
|
|
13
|
+
right?: React.ReactNode;
|
|
14
|
+
style?: ViewStyle;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function PressableCardRow({
|
|
18
|
+
accessibilityLabel,
|
|
19
|
+
onPress,
|
|
20
|
+
disabled,
|
|
21
|
+
left,
|
|
22
|
+
title,
|
|
23
|
+
subtitle,
|
|
24
|
+
right,
|
|
25
|
+
style,
|
|
26
|
+
}: PressableCardRowProps) {
|
|
27
|
+
return (
|
|
28
|
+
<Pressable
|
|
29
|
+
accessibilityRole="button"
|
|
30
|
+
accessibilityLabel={accessibilityLabel}
|
|
31
|
+
disabled={disabled}
|
|
32
|
+
onPress={onPress}
|
|
33
|
+
style={({ pressed }) => ({ opacity: disabled ? 0.6 : pressed ? 0.85 : 1 })}
|
|
34
|
+
>
|
|
35
|
+
<Card padded={false} border={false} style={style}>
|
|
36
|
+
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
37
|
+
{left}
|
|
38
|
+
<View style={{ flex: 1, minWidth: 0 }}>
|
|
39
|
+
{title}
|
|
40
|
+
{subtitle ? subtitle : null}
|
|
41
|
+
</View>
|
|
42
|
+
{right ? <View style={{ marginLeft: 16 }}>{right}</View> : null}
|
|
43
|
+
</View>
|
|
44
|
+
</Card>
|
|
45
|
+
</Pressable>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ActivityIndicator, Alert, View } from 'react-native';
|
|
3
|
+
import { Send } from 'lucide-react-native';
|
|
4
|
+
|
|
5
|
+
import type { MergeRequest } from '../../../data/merge-requests/types';
|
|
6
|
+
import type { UserStats } from '../../../data/users/types';
|
|
7
|
+
import { MergeRequestStatusCard } from '../../../components/merge-requests/MergeRequestStatusCard';
|
|
8
|
+
import { ReviewMergeRequestCarousel } from '../../../components/merge-requests/ReviewMergeRequestCarousel';
|
|
9
|
+
import { Text } from '../../../components/primitives/Text';
|
|
10
|
+
import { withAlpha } from '../../../components/utils/color';
|
|
11
|
+
import { useTheme } from '../../../theme';
|
|
12
|
+
import { PressableCardRow } from './PressableCardRow';
|
|
13
|
+
import { SectionTitle } from './SectionTitle';
|
|
14
|
+
|
|
15
|
+
import { MergeIcon } from '../../../components/icons/MergeIcon';
|
|
16
|
+
|
|
17
|
+
export type PreviewCollaborateSectionProps = {
|
|
18
|
+
canSubmitMergeRequest: boolean;
|
|
19
|
+
incomingMergeRequests: MergeRequest[];
|
|
20
|
+
outgoingMergeRequests: MergeRequest[];
|
|
21
|
+
creatorStatsById: Record<string, UserStats>;
|
|
22
|
+
processingMrId?: string | null;
|
|
23
|
+
isBuildingMrTest?: boolean;
|
|
24
|
+
testingMrId?: string | null;
|
|
25
|
+
toMergeRequestSummary: (mr: MergeRequest) => import('../../../components/models/types').MergeRequestSummary;
|
|
26
|
+
onSubmitMergeRequest?: () => void | Promise<void>;
|
|
27
|
+
onRequestApprove?: (mr: MergeRequest) => void;
|
|
28
|
+
onReject?: (mr: MergeRequest) => void | Promise<void>;
|
|
29
|
+
onTestMr?: (mr: MergeRequest) => void | Promise<void>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export function PreviewCollaborateSection({
|
|
33
|
+
canSubmitMergeRequest,
|
|
34
|
+
incomingMergeRequests,
|
|
35
|
+
outgoingMergeRequests,
|
|
36
|
+
creatorStatsById,
|
|
37
|
+
processingMrId,
|
|
38
|
+
isBuildingMrTest,
|
|
39
|
+
testingMrId,
|
|
40
|
+
toMergeRequestSummary,
|
|
41
|
+
onSubmitMergeRequest,
|
|
42
|
+
onRequestApprove,
|
|
43
|
+
onReject,
|
|
44
|
+
onTestMr,
|
|
45
|
+
}: PreviewCollaborateSectionProps) {
|
|
46
|
+
const theme = useTheme();
|
|
47
|
+
const [submittingMr, setSubmittingMr] = React.useState(false);
|
|
48
|
+
|
|
49
|
+
const hasSection = canSubmitMergeRequest || incomingMergeRequests.length > 0 || outgoingMergeRequests.length > 0;
|
|
50
|
+
if (!hasSection) return null;
|
|
51
|
+
|
|
52
|
+
const showActionsSubtitle = (canSubmitMergeRequest && onSubmitMergeRequest) || (onTestMr && incomingMergeRequests.length > 0);
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<>
|
|
56
|
+
<SectionTitle marginTop={theme.spacing.xl}>Collaborate</SectionTitle>
|
|
57
|
+
|
|
58
|
+
{showActionsSubtitle ? (
|
|
59
|
+
<Text
|
|
60
|
+
style={{
|
|
61
|
+
color: withAlpha(theme.colors.textMuted, 0.7),
|
|
62
|
+
fontSize: 10,
|
|
63
|
+
lineHeight: 14,
|
|
64
|
+
textTransform: 'uppercase',
|
|
65
|
+
letterSpacing: 0.8,
|
|
66
|
+
marginBottom: theme.spacing.sm,
|
|
67
|
+
fontWeight: theme.typography.fontWeight.semibold,
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
Actions
|
|
71
|
+
</Text>
|
|
72
|
+
) : null}
|
|
73
|
+
|
|
74
|
+
{canSubmitMergeRequest && onSubmitMergeRequest ? (
|
|
75
|
+
<PressableCardRow
|
|
76
|
+
accessibilityLabel="Submit merge request"
|
|
77
|
+
disabled={submittingMr}
|
|
78
|
+
onPress={() => {
|
|
79
|
+
Alert.alert(
|
|
80
|
+
'Submit Merge Request',
|
|
81
|
+
'Are you sure you want to submit your changes to the original app?',
|
|
82
|
+
[
|
|
83
|
+
{ text: 'Cancel', style: 'cancel' },
|
|
84
|
+
{
|
|
85
|
+
text: 'Submit',
|
|
86
|
+
style: 'destructive',
|
|
87
|
+
onPress: () => {
|
|
88
|
+
setSubmittingMr(true);
|
|
89
|
+
Promise.resolve(onSubmitMergeRequest())
|
|
90
|
+
.catch(() => {})
|
|
91
|
+
.finally(() => setSubmittingMr(false));
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
]
|
|
95
|
+
);
|
|
96
|
+
}}
|
|
97
|
+
style={{
|
|
98
|
+
padding: theme.spacing.lg,
|
|
99
|
+
borderRadius: theme.radii.lg,
|
|
100
|
+
backgroundColor: withAlpha(theme.colors.surfaceRaised, 0.5),
|
|
101
|
+
borderWidth: 1,
|
|
102
|
+
borderColor: withAlpha('#03DAC6', 0.2),
|
|
103
|
+
marginBottom: theme.spacing.sm,
|
|
104
|
+
}}
|
|
105
|
+
left={
|
|
106
|
+
<View
|
|
107
|
+
style={{
|
|
108
|
+
width: 40,
|
|
109
|
+
height: 40,
|
|
110
|
+
borderRadius: 999,
|
|
111
|
+
alignItems: 'center',
|
|
112
|
+
justifyContent: 'center',
|
|
113
|
+
backgroundColor: withAlpha('#03DAC6', 0.1),
|
|
114
|
+
marginRight: theme.spacing.lg,
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
{submittingMr ? <ActivityIndicator color="#03DAC6" size="small" /> : <MergeIcon width={20} height={20} color="#03DAC6" />}
|
|
118
|
+
</View>
|
|
119
|
+
}
|
|
120
|
+
title={
|
|
121
|
+
<Text style={{ color: theme.colors.text, fontSize: 16, lineHeight: 20, fontWeight: theme.typography.fontWeight.semibold }}>
|
|
122
|
+
Submit your new changes
|
|
123
|
+
</Text>
|
|
124
|
+
}
|
|
125
|
+
subtitle={
|
|
126
|
+
<Text style={{ color: theme.colors.textMuted, fontSize: 12, lineHeight: 16, marginTop: 2 }}>
|
|
127
|
+
Ask to merge this remix to the original app
|
|
128
|
+
</Text>
|
|
129
|
+
}
|
|
130
|
+
right={<Send size={16} color="#03DAC6" />}
|
|
131
|
+
/>
|
|
132
|
+
) : null}
|
|
133
|
+
|
|
134
|
+
{onTestMr && incomingMergeRequests.length > 0 ? (
|
|
135
|
+
<ReviewMergeRequestCarousel
|
|
136
|
+
mergeRequests={incomingMergeRequests}
|
|
137
|
+
creatorStatsById={creatorStatsById}
|
|
138
|
+
processingMrId={processingMrId}
|
|
139
|
+
isBuilding={Boolean(isBuildingMrTest)}
|
|
140
|
+
testingMrId={testingMrId}
|
|
141
|
+
onReject={(mr) => (onReject ? onReject(mr) : undefined)}
|
|
142
|
+
onApprove={(mr) => onRequestApprove?.(mr)}
|
|
143
|
+
onTest={(mr) => (onTestMr ? onTestMr(mr) : undefined)}
|
|
144
|
+
/>
|
|
145
|
+
) : null}
|
|
146
|
+
|
|
147
|
+
{outgoingMergeRequests.length > 0 ? (
|
|
148
|
+
<>
|
|
149
|
+
<Text
|
|
150
|
+
style={{
|
|
151
|
+
color: withAlpha(theme.colors.textMuted, 0.7),
|
|
152
|
+
fontSize: 10,
|
|
153
|
+
lineHeight: 14,
|
|
154
|
+
textTransform: 'uppercase',
|
|
155
|
+
letterSpacing: 0.8,
|
|
156
|
+
marginTop: theme.spacing.lg,
|
|
157
|
+
marginBottom: theme.spacing.sm,
|
|
158
|
+
fontWeight: theme.typography.fontWeight.semibold,
|
|
159
|
+
}}
|
|
160
|
+
>
|
|
161
|
+
History
|
|
162
|
+
</Text>
|
|
163
|
+
{outgoingMergeRequests.map((mr) => (
|
|
164
|
+
<View key={mr.id} style={{ marginBottom: theme.spacing.sm }}>
|
|
165
|
+
<MergeRequestStatusCard mergeRequest={toMergeRequestSummary(mr)} />
|
|
166
|
+
</View>
|
|
167
|
+
))}
|
|
168
|
+
</>
|
|
169
|
+
) : null}
|
|
170
|
+
</>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ActivityIndicator, View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import type { App } from '../../../data/apps/types';
|
|
5
|
+
import { Text } from '../../../components/primitives/Text';
|
|
6
|
+
import { IconChat, IconChevronRight, IconDraw } from '../../../components/icons/StudioIcons';
|
|
7
|
+
import { withAlpha } from '../../../components/utils/color';
|
|
8
|
+
import { useTheme } from '../../../theme';
|
|
9
|
+
import { PressableCardRow } from './PressableCardRow';
|
|
10
|
+
import { SectionTitle } from './SectionTitle';
|
|
11
|
+
import { statusDescription } from './utils';
|
|
12
|
+
|
|
13
|
+
export type PreviewCustomizeSectionProps = {
|
|
14
|
+
app: App;
|
|
15
|
+
isOwner: boolean;
|
|
16
|
+
shouldForkOnEdit: boolean;
|
|
17
|
+
showProcessing: boolean;
|
|
18
|
+
onGoToChat: () => void;
|
|
19
|
+
onStartDraw?: () => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function PreviewCustomizeSection({
|
|
23
|
+
app,
|
|
24
|
+
isOwner,
|
|
25
|
+
shouldForkOnEdit,
|
|
26
|
+
showProcessing,
|
|
27
|
+
onGoToChat,
|
|
28
|
+
onStartDraw,
|
|
29
|
+
}: PreviewCustomizeSectionProps) {
|
|
30
|
+
const theme = useTheme();
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
<SectionTitle>Customize</SectionTitle>
|
|
35
|
+
|
|
36
|
+
{showProcessing ? (
|
|
37
|
+
<View
|
|
38
|
+
style={{
|
|
39
|
+
flexDirection: 'row',
|
|
40
|
+
alignItems: 'center',
|
|
41
|
+
padding: theme.spacing.lg,
|
|
42
|
+
borderRadius: theme.radii.lg,
|
|
43
|
+
backgroundColor: withAlpha(theme.colors.surfaceRaised, 0.5),
|
|
44
|
+
borderWidth: 1,
|
|
45
|
+
borderColor: withAlpha(theme.colors.warning, 0.2),
|
|
46
|
+
marginBottom: theme.spacing.sm,
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
<View
|
|
50
|
+
style={{
|
|
51
|
+
width: 40,
|
|
52
|
+
height: 40,
|
|
53
|
+
borderRadius: 999,
|
|
54
|
+
alignItems: 'center',
|
|
55
|
+
justifyContent: 'center',
|
|
56
|
+
backgroundColor: withAlpha(theme.colors.warning, 0.1),
|
|
57
|
+
marginRight: theme.spacing.lg,
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
<ActivityIndicator color={theme.colors.warning} size="small" />
|
|
61
|
+
</View>
|
|
62
|
+
<View style={{ flex: 1, minWidth: 0 }}>
|
|
63
|
+
<Text style={{ color: theme.colors.text, fontSize: 16, lineHeight: 20, fontWeight: theme.typography.fontWeight.semibold }}>
|
|
64
|
+
{app.status === 'error' ? 'Error' : 'Processing'}
|
|
65
|
+
</Text>
|
|
66
|
+
<Text style={{ color: theme.colors.textMuted, fontSize: 12, lineHeight: 16, marginTop: 2 }}>
|
|
67
|
+
{statusDescription(app.status, app.statusError)}
|
|
68
|
+
</Text>
|
|
69
|
+
</View>
|
|
70
|
+
</View>
|
|
71
|
+
) : null}
|
|
72
|
+
|
|
73
|
+
<PressableCardRow
|
|
74
|
+
accessibilityLabel={isOwner ? 'Edit app' : 'Remix app'}
|
|
75
|
+
onPress={onGoToChat}
|
|
76
|
+
style={{
|
|
77
|
+
padding: theme.spacing.lg,
|
|
78
|
+
borderRadius: theme.radii.lg,
|
|
79
|
+
backgroundColor: withAlpha(theme.colors.surfaceRaised, 0.5),
|
|
80
|
+
borderWidth: 1,
|
|
81
|
+
borderColor: withAlpha(theme.colors.primary, 0.1),
|
|
82
|
+
marginBottom: theme.spacing.sm,
|
|
83
|
+
}}
|
|
84
|
+
left={
|
|
85
|
+
<View
|
|
86
|
+
style={{
|
|
87
|
+
width: 40,
|
|
88
|
+
height: 40,
|
|
89
|
+
borderRadius: 999,
|
|
90
|
+
alignItems: 'center',
|
|
91
|
+
justifyContent: 'center',
|
|
92
|
+
backgroundColor: withAlpha(theme.colors.primary, 0.1),
|
|
93
|
+
marginRight: theme.spacing.lg,
|
|
94
|
+
}}
|
|
95
|
+
>
|
|
96
|
+
<IconChat size={20} colorToken="primary" />
|
|
97
|
+
</View>
|
|
98
|
+
}
|
|
99
|
+
title={
|
|
100
|
+
<Text style={{ color: theme.colors.text, fontSize: 16, lineHeight: 20, fontWeight: theme.typography.fontWeight.semibold }}>
|
|
101
|
+
{isOwner ? (app.forkedFromAppId ? 'Edit your Remix' : 'Edit Your App') : 'Remix App'}
|
|
102
|
+
</Text>
|
|
103
|
+
}
|
|
104
|
+
subtitle={
|
|
105
|
+
<Text style={{ color: theme.colors.textMuted, fontSize: 12, lineHeight: 16, marginTop: 2 }}>
|
|
106
|
+
{isOwner && app.forkedFromAppId
|
|
107
|
+
? 'Make changes to your remix with chat'
|
|
108
|
+
: shouldForkOnEdit
|
|
109
|
+
? 'Chat to create your own copy and edit it'
|
|
110
|
+
: 'Chat to apply changes'}
|
|
111
|
+
</Text>
|
|
112
|
+
}
|
|
113
|
+
right={<IconChevronRight size={20} colorToken="textMuted" />}
|
|
114
|
+
/>
|
|
115
|
+
|
|
116
|
+
{isOwner && onStartDraw ? (
|
|
117
|
+
<PressableCardRow
|
|
118
|
+
accessibilityLabel="Draw changes"
|
|
119
|
+
onPress={onStartDraw}
|
|
120
|
+
style={{
|
|
121
|
+
padding: theme.spacing.lg,
|
|
122
|
+
borderRadius: theme.radii.lg,
|
|
123
|
+
backgroundColor: withAlpha(theme.colors.surfaceRaised, 0.5),
|
|
124
|
+
borderWidth: 1,
|
|
125
|
+
borderColor: withAlpha(theme.colors.danger, 0.1),
|
|
126
|
+
marginBottom: theme.spacing.sm,
|
|
127
|
+
}}
|
|
128
|
+
left={
|
|
129
|
+
<View
|
|
130
|
+
style={{
|
|
131
|
+
width: 40,
|
|
132
|
+
height: 40,
|
|
133
|
+
borderRadius: 999,
|
|
134
|
+
alignItems: 'center',
|
|
135
|
+
justifyContent: 'center',
|
|
136
|
+
backgroundColor: withAlpha(theme.colors.danger, 0.1),
|
|
137
|
+
marginRight: theme.spacing.lg,
|
|
138
|
+
}}
|
|
139
|
+
>
|
|
140
|
+
<IconDraw size={20} colorToken="danger" />
|
|
141
|
+
</View>
|
|
142
|
+
}
|
|
143
|
+
title={
|
|
144
|
+
<Text style={{ color: theme.colors.text, fontSize: 16, lineHeight: 20, fontWeight: theme.typography.fontWeight.semibold }}>
|
|
145
|
+
Draw Changes
|
|
146
|
+
</Text>
|
|
147
|
+
}
|
|
148
|
+
subtitle={
|
|
149
|
+
<Text style={{ color: theme.colors.textMuted, fontSize: 12, lineHeight: 16, marginTop: 2 }}>
|
|
150
|
+
Annotate the app with drawings
|
|
151
|
+
</Text>
|
|
152
|
+
}
|
|
153
|
+
right={<IconChevronRight size={20} colorToken="textMuted" />}
|
|
154
|
+
/>
|
|
155
|
+
) : null}
|
|
156
|
+
</>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import type { App } from '../../../data/apps/types';
|
|
4
|
+
import { PreviewHeroCard } from '../../../components/preview/PreviewHeroCard';
|
|
5
|
+
import { PreviewPlaceholder } from '../../../components/preview/PreviewPlaceholder';
|
|
6
|
+
import { PreviewImage } from '../../../components/preview/PreviewImage';
|
|
7
|
+
import { StatsBar } from '../../../components/preview/StatsBar';
|
|
8
|
+
import { PreviewStatusBadge } from '../../../components/preview/PreviewStatusBadge';
|
|
9
|
+
|
|
10
|
+
export type PreviewHeroSectionProps = {
|
|
11
|
+
appStatus: App['status'];
|
|
12
|
+
showProcessing: boolean;
|
|
13
|
+
imageUrl: string | null;
|
|
14
|
+
imageLoaded: boolean;
|
|
15
|
+
onImageLoad: () => void;
|
|
16
|
+
stats: {
|
|
17
|
+
likeCount: number;
|
|
18
|
+
commentCount: number;
|
|
19
|
+
forkCount: number;
|
|
20
|
+
isLiked: boolean;
|
|
21
|
+
handleLike: () => Promise<void> | void;
|
|
22
|
+
handleOpenComments: () => void;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function PreviewHeroSection({
|
|
27
|
+
appStatus,
|
|
28
|
+
showProcessing,
|
|
29
|
+
imageUrl,
|
|
30
|
+
imageLoaded,
|
|
31
|
+
onImageLoad,
|
|
32
|
+
stats,
|
|
33
|
+
}: PreviewHeroSectionProps) {
|
|
34
|
+
return (
|
|
35
|
+
<PreviewHeroCard
|
|
36
|
+
overlayTopLeft={showProcessing ? <PreviewStatusBadge status={appStatus} /> : null}
|
|
37
|
+
background={<PreviewPlaceholder visible={!imageLoaded} />}
|
|
38
|
+
image={<PreviewImage uri={imageUrl} onLoad={onImageLoad} />}
|
|
39
|
+
overlayBottom={
|
|
40
|
+
<StatsBar
|
|
41
|
+
likeCount={stats.likeCount}
|
|
42
|
+
commentCount={stats.commentCount}
|
|
43
|
+
forkCount={stats.forkCount}
|
|
44
|
+
isLiked={stats.isLiked}
|
|
45
|
+
onPressLike={() => void stats.handleLike()}
|
|
46
|
+
onPressComments={stats.handleOpenComments}
|
|
47
|
+
centered
|
|
48
|
+
fixedWidth={160}
|
|
49
|
+
/>
|
|
50
|
+
}
|
|
51
|
+
style={{ marginBottom: 16 }}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|