@comergehq/studio 0.1.12 → 0.1.15
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.d.mts +26 -3
- package/dist/index.d.ts +26 -3
- package/dist/index.js +538 -307
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +544 -313
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
- package/src/components/{floating-draggable-button/FloatingDraggableButton.tsx → bubble/Bubble.tsx} +3 -3
- package/src/components/{floating-draggable-button → bubble}/constants.ts +2 -2
- package/src/components/bubble/index.ts +4 -0
- package/src/components/{floating-draggable-button → bubble}/types.ts +3 -3
- package/src/components/index.ts +2 -2
- package/src/core/services/http/baseUrl.ts +1 -1
- package/src/core/services/http/public.ts +7 -7
- package/src/data/apps/bundles/remote.ts +17 -0
- package/src/data/apps/bundles/repository.ts +14 -0
- package/src/data/apps/bundles/types.ts +15 -0
- package/src/studio/ComergeStudio.tsx +17 -9
- package/src/studio/bootstrap/StudioBootstrap.tsx +2 -2
- package/src/studio/bootstrap/useStudioBootstrap.ts +7 -7
- package/src/studio/hooks/useBundleManager.ts +323 -19
- package/src/studio/ui/RuntimeRenderer.tsx +24 -1
- package/src/studio/ui/StudioOverlay.tsx +6 -6
- package/src/components/floating-draggable-button/index.ts +0 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@comergehq/studio",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "Comerge studio",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"@gorhom/bottom-sheet": "*",
|
|
58
58
|
"@comergehq/runtime": "^0.1.1",
|
|
59
59
|
"expo": "*",
|
|
60
|
+
"expo-asset": "*",
|
|
60
61
|
"expo-file-system": "*",
|
|
61
62
|
"expo-haptics": "*",
|
|
62
63
|
"expo-linear-gradient": "*",
|
|
@@ -67,6 +68,7 @@
|
|
|
67
68
|
"react-native": "*",
|
|
68
69
|
"react-native-safe-area-context": "*",
|
|
69
70
|
"react-native-svg": "*",
|
|
71
|
+
"react-native-zip-archive": "*",
|
|
70
72
|
"react-native-view-shot": "*"
|
|
71
73
|
},
|
|
72
74
|
"peerDependenciesMeta": {}
|
package/src/components/{floating-draggable-button/FloatingDraggableButton.tsx → bubble/Bubble.tsx}
RENAMED
|
@@ -23,7 +23,7 @@ import Animated, {
|
|
|
23
23
|
import { isLiquidGlassSupported } from '@callstack/liquid-glass';
|
|
24
24
|
|
|
25
25
|
import { DEFAULT_EDGE_PADDING, DEFAULT_OFFSET, DEFAULT_SIZE, ENTER_ROTATION_FROM_DEG, ENTER_SCALE_FROM, PULSE_DURATION_MS } from './constants';
|
|
26
|
-
import type {
|
|
26
|
+
import type { BubbleProps } from './types';
|
|
27
27
|
import { useTheme } from '../../theme';
|
|
28
28
|
import { ResettableLiquidGlassView } from '../utils/ResettableLiquidGlassView';
|
|
29
29
|
|
|
@@ -53,7 +53,7 @@ function getFinalTranslateY(height: number, size: number, bottomOffset: number)
|
|
|
53
53
|
return height - size - bottomOffset;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
export function
|
|
56
|
+
export function Bubble({
|
|
57
57
|
onPress,
|
|
58
58
|
size = DEFAULT_SIZE,
|
|
59
59
|
disabled = false,
|
|
@@ -69,7 +69,7 @@ export function FloatingDraggableButton({
|
|
|
69
69
|
testID,
|
|
70
70
|
edgePadding = DEFAULT_EDGE_PADDING,
|
|
71
71
|
backgroundColor,
|
|
72
|
-
}:
|
|
72
|
+
}: BubbleProps) {
|
|
73
73
|
const theme = useTheme();
|
|
74
74
|
const { width, height } = useWindowDimensions();
|
|
75
75
|
const isDanger = variant === 'danger';
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { BubbleOffset } from './types';
|
|
2
2
|
|
|
3
3
|
export const DEFAULT_SIZE = 48;
|
|
4
4
|
export const DEFAULT_EDGE_PADDING = 10;
|
|
5
5
|
|
|
6
|
-
export const DEFAULT_OFFSET: Required<
|
|
6
|
+
export const DEFAULT_OFFSET: Required<BubbleOffset> = {
|
|
7
7
|
left: 20,
|
|
8
8
|
bottom: 60,
|
|
9
9
|
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import type * as React from 'react';
|
|
2
2
|
import type { StyleProp, ViewStyle } from 'react-native';
|
|
3
3
|
|
|
4
|
-
export type
|
|
4
|
+
export type BubbleOffset = {
|
|
5
5
|
/** Distance from the left edge (in px). */
|
|
6
6
|
left?: number;
|
|
7
7
|
/** Distance from the bottom edge (in px). */
|
|
8
8
|
bottom?: number;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
export type
|
|
11
|
+
export type BubbleProps = {
|
|
12
12
|
/**
|
|
13
13
|
* Whether the button should be shown. When toggled, the button animates in/out.
|
|
14
14
|
* The component stays mounted to preserve its last drag position.
|
|
@@ -30,7 +30,7 @@ export type FloatingDraggableButtonProps = {
|
|
|
30
30
|
* Initial placement when it animates in.
|
|
31
31
|
* `left` is measured from the left edge; `bottom` from the bottom edge.
|
|
32
32
|
*/
|
|
33
|
-
offset?:
|
|
33
|
+
offset?: BubbleOffset;
|
|
34
34
|
|
|
35
35
|
/** Accessible label for screen readers (kept as `ariaLabel` for compatibility). */
|
|
36
36
|
ariaLabel?: string;
|
package/src/components/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export type {
|
|
1
|
+
export { Bubble } from './bubble';
|
|
2
|
+
export type { BubbleProps, BubbleOffset } from './bubble';
|
|
3
3
|
|
|
4
4
|
export * from './icons/StudioIcons';
|
|
5
5
|
|
|
@@ -3,7 +3,7 @@ import axios from "axios";
|
|
|
3
3
|
import { BASE_URL } from "./baseUrl";
|
|
4
4
|
|
|
5
5
|
const CLIENT_KEY_HEADER = "x-comerge-api-key";
|
|
6
|
-
let
|
|
6
|
+
let clientKey: string | null = null;
|
|
7
7
|
|
|
8
8
|
export const publicApi = axios.create({
|
|
9
9
|
baseURL: BASE_URL,
|
|
@@ -14,19 +14,19 @@ export const publicApi = axios.create({
|
|
|
14
14
|
},
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
-
export function
|
|
18
|
-
const trimmed =
|
|
17
|
+
export function setClientKey(clientKeyInput: string) {
|
|
18
|
+
const trimmed = clientKeyInput?.trim?.() ?? "";
|
|
19
19
|
if (!trimmed) {
|
|
20
|
-
throw new Error("comerge-studio:
|
|
20
|
+
throw new Error("comerge-studio: clientKey is required");
|
|
21
21
|
}
|
|
22
|
-
|
|
22
|
+
clientKey = trimmed;
|
|
23
23
|
publicApi.defaults.headers.common[CLIENT_KEY_HEADER] = trimmed;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
publicApi.interceptors.request.use((config) => {
|
|
27
|
-
if (!
|
|
27
|
+
if (!clientKey) return config;
|
|
28
28
|
config.headers = config.headers ?? {};
|
|
29
|
-
(config.headers as any)[CLIENT_KEY_HEADER] =
|
|
29
|
+
(config.headers as any)[CLIENT_KEY_HEADER] = clientKey;
|
|
30
30
|
return config;
|
|
31
31
|
});
|
|
32
32
|
|
|
@@ -11,6 +11,11 @@ export interface BundlesRemoteDataSource {
|
|
|
11
11
|
bundleId: string,
|
|
12
12
|
options?: { redirect?: boolean }
|
|
13
13
|
): Promise<ServiceResponse<{ url: string; redirect: boolean }>>;
|
|
14
|
+
getSignedAssetsDownloadUrl(
|
|
15
|
+
appId: string,
|
|
16
|
+
bundleId: string,
|
|
17
|
+
options?: { redirect?: boolean; kind?: string }
|
|
18
|
+
): Promise<ServiceResponse<{ url: string; redirect: boolean }>>;
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
class BundlesRemoteDataSourceImpl extends BaseRemote implements BundlesRemoteDataSource {
|
|
@@ -40,6 +45,18 @@ class BundlesRemoteDataSourceImpl extends BaseRemote implements BundlesRemoteDat
|
|
|
40
45
|
);
|
|
41
46
|
return data;
|
|
42
47
|
}
|
|
48
|
+
|
|
49
|
+
async getSignedAssetsDownloadUrl(
|
|
50
|
+
appId: string,
|
|
51
|
+
bundleId: string,
|
|
52
|
+
options?: { redirect?: boolean; kind?: string }
|
|
53
|
+
): Promise<ServiceResponse<{ url: string; redirect: boolean }>> {
|
|
54
|
+
const { data } = await api.get<ServiceResponse<{ url: string; redirect: boolean }>>(
|
|
55
|
+
`/v1/apps/${encodeURIComponent(appId)}/bundles/${encodeURIComponent(bundleId)}/assets/download`,
|
|
56
|
+
{ params: { redirect: options?.redirect ?? false, kind: options?.kind } }
|
|
57
|
+
);
|
|
58
|
+
return data;
|
|
59
|
+
}
|
|
43
60
|
}
|
|
44
61
|
|
|
45
62
|
export const bundlesRemoteDataSource: BundlesRemoteDataSource = new BundlesRemoteDataSourceImpl();
|
|
@@ -7,6 +7,11 @@ export interface BundlesRepository {
|
|
|
7
7
|
initiate(appId: string, payload: InitiateBundleRequest): Promise<Bundle>;
|
|
8
8
|
getById(appId: string, bundleId: string): Promise<Bundle>;
|
|
9
9
|
getSignedDownloadUrl(appId: string, bundleId: string, options?: { redirect?: boolean }): Promise<{ url: string; redirect: boolean }>;
|
|
10
|
+
getSignedAssetsDownloadUrl(
|
|
11
|
+
appId: string,
|
|
12
|
+
bundleId: string,
|
|
13
|
+
options?: { redirect?: boolean; kind?: string }
|
|
14
|
+
): Promise<{ url: string; redirect: boolean }>;
|
|
10
15
|
}
|
|
11
16
|
|
|
12
17
|
class BundlesRepositoryImpl extends BaseRepository implements BundlesRepository {
|
|
@@ -28,6 +33,15 @@ class BundlesRepositoryImpl extends BaseRepository implements BundlesRepository
|
|
|
28
33
|
const res = await this.remote.getSignedDownloadUrl(appId, bundleId, options);
|
|
29
34
|
return this.unwrapOrThrow(res);
|
|
30
35
|
}
|
|
36
|
+
|
|
37
|
+
async getSignedAssetsDownloadUrl(
|
|
38
|
+
appId: string,
|
|
39
|
+
bundleId: string,
|
|
40
|
+
options?: { redirect?: boolean; kind?: string }
|
|
41
|
+
): Promise<{ url: string; redirect: boolean }> {
|
|
42
|
+
const res = await this.remote.getSignedAssetsDownloadUrl(appId, bundleId, options);
|
|
43
|
+
return this.unwrapOrThrow(res);
|
|
44
|
+
}
|
|
31
45
|
}
|
|
32
46
|
|
|
33
47
|
export const bundlesRepository: BundlesRepository = new BundlesRepositoryImpl(bundlesRemoteDataSource);
|
|
@@ -2,6 +2,20 @@ export type Platform = 'ios' | 'android';
|
|
|
2
2
|
|
|
3
3
|
export type BundleStatus = 'pending' | 'building' | 'succeeded' | 'failed';
|
|
4
4
|
|
|
5
|
+
export type BundleAssetKind = 'metro-assets' | string;
|
|
6
|
+
|
|
7
|
+
export type BundleAsset = {
|
|
8
|
+
id: string;
|
|
9
|
+
kind: BundleAssetKind;
|
|
10
|
+
storageBucket: string;
|
|
11
|
+
storageKey: string;
|
|
12
|
+
contentType: string | null;
|
|
13
|
+
size: number | null;
|
|
14
|
+
checksumSha256: string | null;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
updatedAt: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
5
19
|
export type Bundle = {
|
|
6
20
|
id: string;
|
|
7
21
|
appId: string;
|
|
@@ -15,6 +29,7 @@ export type Bundle = {
|
|
|
15
29
|
createdAt: string;
|
|
16
30
|
updatedAt: string;
|
|
17
31
|
expiresAt: string | null;
|
|
32
|
+
assets?: BundleAsset[];
|
|
18
33
|
};
|
|
19
34
|
|
|
20
35
|
export type InitiateBundleRequest = {
|
|
@@ -7,6 +7,7 @@ import { StudioBootstrap } from './bootstrap/StudioBootstrap';
|
|
|
7
7
|
import { useApp } from './hooks/useApp';
|
|
8
8
|
import { useThreadMessages } from './hooks/useThreadMessages';
|
|
9
9
|
import { useBundleManager } from './hooks/useBundleManager';
|
|
10
|
+
import type { EmbeddedBaseBundles } from './hooks/useBundleManager';
|
|
10
11
|
import { useMergeRequests } from './hooks/useMergeRequests';
|
|
11
12
|
import { useAttachmentUpload } from './hooks/useAttachmentUpload';
|
|
12
13
|
import { useStudioActions } from './hooks/useStudioActions';
|
|
@@ -17,22 +18,24 @@ import { LiquidGlassResetProvider } from '../components/utils/liquidGlassReset';
|
|
|
17
18
|
|
|
18
19
|
export type ComergeStudioProps = {
|
|
19
20
|
appId: string;
|
|
20
|
-
|
|
21
|
+
clientKey: string;
|
|
21
22
|
appKey?: string;
|
|
22
23
|
onNavigateHome?: () => void;
|
|
23
24
|
style?: ViewStyle;
|
|
24
|
-
|
|
25
|
+
showBubble?: boolean;
|
|
25
26
|
studioControlOptions?: import('@comergehq/studio-control').StudioControlOptions;
|
|
27
|
+
embeddedBaseBundles?: EmbeddedBaseBundles;
|
|
26
28
|
};
|
|
27
29
|
|
|
28
30
|
export function ComergeStudio({
|
|
29
31
|
appId,
|
|
30
|
-
|
|
32
|
+
clientKey,
|
|
31
33
|
appKey = 'MicroMain',
|
|
32
34
|
onNavigateHome,
|
|
33
35
|
style,
|
|
34
|
-
|
|
36
|
+
showBubble = true,
|
|
35
37
|
studioControlOptions,
|
|
38
|
+
embeddedBaseBundles,
|
|
36
39
|
}: ComergeStudioProps) {
|
|
37
40
|
const [activeAppId, setActiveAppId] = React.useState(appId);
|
|
38
41
|
const [runtimeAppId, setRuntimeAppId] = React.useState(appId);
|
|
@@ -48,7 +51,7 @@ export function ComergeStudio({
|
|
|
48
51
|
const captureTargetRef = React.useRef<View | null>(null);
|
|
49
52
|
|
|
50
53
|
return (
|
|
51
|
-
<StudioBootstrap
|
|
54
|
+
<StudioBootstrap clientKey={clientKey} fallback={<View style={{ flex: 1 }} />}>
|
|
52
55
|
{({ userId }) => (
|
|
53
56
|
<BottomSheetModalProvider>
|
|
54
57
|
<LiquidGlassResetProvider resetTriggers={[appId, activeAppId, runtimeAppId]}>
|
|
@@ -65,8 +68,9 @@ export function ComergeStudio({
|
|
|
65
68
|
onNavigateHome={onNavigateHome}
|
|
66
69
|
captureTargetRef={captureTargetRef}
|
|
67
70
|
style={style}
|
|
68
|
-
|
|
71
|
+
showBubble={showBubble}
|
|
69
72
|
studioControlOptions={studioControlOptions}
|
|
73
|
+
embeddedBaseBundles={embeddedBaseBundles}
|
|
70
74
|
/>
|
|
71
75
|
</LiquidGlassResetProvider>
|
|
72
76
|
</BottomSheetModalProvider>
|
|
@@ -88,8 +92,9 @@ type InnerProps = {
|
|
|
88
92
|
onNavigateHome?: () => void;
|
|
89
93
|
captureTargetRef: React.RefObject<View | null>;
|
|
90
94
|
style?: ViewStyle;
|
|
91
|
-
|
|
95
|
+
showBubble: boolean;
|
|
92
96
|
studioControlOptions?: import('@comergehq/studio-control').StudioControlOptions;
|
|
97
|
+
embeddedBaseBundles?: EmbeddedBaseBundles;
|
|
93
98
|
};
|
|
94
99
|
|
|
95
100
|
function ComergeStudioInner({
|
|
@@ -105,8 +110,9 @@ function ComergeStudioInner({
|
|
|
105
110
|
onNavigateHome,
|
|
106
111
|
captureTargetRef,
|
|
107
112
|
style,
|
|
108
|
-
|
|
113
|
+
showBubble,
|
|
109
114
|
studioControlOptions,
|
|
115
|
+
embeddedBaseBundles,
|
|
110
116
|
}: InnerProps) {
|
|
111
117
|
const { app, loading: appLoading } = useApp(activeAppId);
|
|
112
118
|
const { app: runtimeAppFromHook } = useApp(runtimeAppId, { enabled: runtimeAppId !== activeAppId });
|
|
@@ -136,6 +142,7 @@ function ComergeStudioInner({
|
|
|
136
142
|
base: { appId: runtimeAppId, commitId: runtimeApp?.headCommitId ?? undefined },
|
|
137
143
|
platform,
|
|
138
144
|
canRequestLatest: runtimeApp?.status === 'ready',
|
|
145
|
+
embeddedBaseBundles,
|
|
139
146
|
});
|
|
140
147
|
|
|
141
148
|
const sawEditingOnActiveAppRef = React.useRef(false);
|
|
@@ -219,6 +226,7 @@ function ComergeStudioInner({
|
|
|
219
226
|
bundlePath={bundle.bundlePath}
|
|
220
227
|
forcePreparing={showPostEditPreparing}
|
|
221
228
|
renderToken={bundle.renderToken}
|
|
229
|
+
allowInitialPreparing={!embeddedBaseBundles}
|
|
222
230
|
/>
|
|
223
231
|
|
|
224
232
|
<StudioOverlay
|
|
@@ -276,7 +284,7 @@ function ComergeStudioInner({
|
|
|
276
284
|
chatShowTypingIndicator={chatShowTypingIndicator}
|
|
277
285
|
onSendChat={(text, attachments) => actions.sendEdit({ prompt: text, attachments })}
|
|
278
286
|
onNavigateHome={onNavigateHome}
|
|
279
|
-
|
|
287
|
+
showBubble={showBubble}
|
|
280
288
|
studioControlOptions={studioControlOptions}
|
|
281
289
|
/>
|
|
282
290
|
</View>
|
|
@@ -16,8 +16,8 @@ export type StudioBootstrapProps = UseStudioBootstrapOptions & {
|
|
|
16
16
|
renderError?: (error: Error) => React.ReactNode;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
export function StudioBootstrap({ children, fallback, renderError,
|
|
20
|
-
const { ready, error, userId } = useStudioBootstrap({
|
|
19
|
+
export function StudioBootstrap({ children, fallback, renderError, clientKey }: StudioBootstrapProps) {
|
|
20
|
+
const { ready, error, userId } = useStudioBootstrap({ clientKey });
|
|
21
21
|
|
|
22
22
|
if (error) {
|
|
23
23
|
return (
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { setClientKey } from '../../core/services/http/public';
|
|
4
4
|
import { ensureAuthenticatedSession, ensureAnonymousSession } from '../../core/services/supabase/auth';
|
|
5
5
|
import { isSupabaseClientInjected, setSupabaseConfig } from '../../core/services/supabase/client';
|
|
6
|
-
|
|
6
|
+
const SUPABASE_URL = 'https://xtfxwbckjpfmqubnsusu.supabase.co';
|
|
7
|
+
const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inh0Znh3YmNranBmbXF1Ym5zdXN1Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjA2MDEyMzAsImV4cCI6MjA3NjE3NzIzMH0.dzWGAWrK4CvrmHVHzf8w7JlUZohdap0ZPnLZnABMV8s';
|
|
7
8
|
|
|
8
9
|
export type UseStudioBootstrapOptions = {
|
|
9
|
-
|
|
10
|
+
clientKey: string;
|
|
10
11
|
};
|
|
11
12
|
|
|
12
13
|
export type StudioBootstrapState = {
|
|
@@ -27,11 +28,10 @@ export function useStudioBootstrap(options: UseStudioBootstrapOptions): StudioBo
|
|
|
27
28
|
|
|
28
29
|
(async () => {
|
|
29
30
|
try {
|
|
30
|
-
|
|
31
|
+
setClientKey(options.clientKey);
|
|
31
32
|
const requireAuth = isSupabaseClientInjected();
|
|
32
33
|
if (!requireAuth) {
|
|
33
|
-
|
|
34
|
-
setSupabaseConfig(cfg);
|
|
34
|
+
setSupabaseConfig({ url: SUPABASE_URL, anonKey: SUPABASE_ANON_KEY });
|
|
35
35
|
}
|
|
36
36
|
const { user } = requireAuth ? await ensureAuthenticatedSession() : await ensureAnonymousSession();
|
|
37
37
|
|
|
@@ -47,7 +47,7 @@ export function useStudioBootstrap(options: UseStudioBootstrapOptions): StudioBo
|
|
|
47
47
|
return () => {
|
|
48
48
|
cancelled = true;
|
|
49
49
|
};
|
|
50
|
-
}, [options.
|
|
50
|
+
}, [options.clientKey]);
|
|
51
51
|
|
|
52
52
|
return state;
|
|
53
53
|
}
|