@sampleapp.ai/sdk 1.0.43 → 1.0.44
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/components/sandbox/Sandbox.js +53 -7
- package/dist/components/sandbox/guardian/code-focus-section.js +4 -2
- package/dist/components/sandbox/guardian/context/theme-context.js +47 -0
- package/dist/components/sandbox/guardian/demo/guardian-demo.js +4 -4
- package/dist/components/sandbox/guardian/demo/left-view.js +13 -7
- package/dist/components/sandbox/guardian/guardian-component.js +34 -5
- package/dist/components/sandbox/guardian/guardian-playground.js +8 -5
- package/dist/components/sandbox/guardian/guardian-style-wrapper.js +8 -6
- package/dist/components/sandbox/guardian/header.js +15 -6
- package/dist/components/sandbox/guardian/hooks/use-frame-messages.js +24 -8
- package/dist/components/sandbox/guardian/hooks/use-frame-params.js +94 -36
- package/dist/components/sandbox/guardian/index.js +1 -0
- package/dist/components/sandbox/guardian/right-view/pill-file-selector.js +4 -4
- package/dist/components/sandbox/guardian/right-view/right-top-down-view/network-requests-view.js +117 -0
- package/dist/components/sandbox/guardian/right-view/right-top-down-view.js +38 -139
- package/dist/components/sandbox/guardian/right-view/right-view.js +2 -2
- package/dist/components/sandbox/guardian/ui/markdown/code-group/code-block.js +7 -33
- package/dist/components/sandbox/guardian/ui/markdown.js +23 -14
- package/dist/components/sandbox/sandbox-home/SandboxCard.js +7 -5
- package/dist/components/sandbox/sandbox-home/SandboxHome.js +32 -13
- package/dist/components/sandbox/sandbox-home/SearchBar.js +5 -3
- package/dist/hooks/use-tree-selector.js +3 -3
- package/dist/index.d.ts +58 -7
- package/dist/index.es.js +21387 -20996
- package/dist/index.js +1 -0
- package/dist/index.standalone.umd.js +8 -8
- package/dist/lib/generated-css.js +1 -1
- package/dist/lib/tree-utils.js +4 -2
- package/dist/lib/types/tree-config.js +18 -0
- package/dist/sdk.css +1 -1
- package/dist/tailwind.css +2 -2
- package/package.json +1 -1
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import React, { useEffect, useState, useCallback } from "react";
|
|
3
|
-
import { fetchSandboxConfigWithContent, buildConfigFromContent
|
|
2
|
+
import React, { useEffect, useState, useCallback, useRef } from "react";
|
|
3
|
+
import { fetchSandboxConfigWithContent, buildConfigFromContent } from "./api";
|
|
4
4
|
import GuardianPlayground from "./guardian/guardian-playground";
|
|
5
5
|
import { GuardianProvider } from "./guardian/context/guardian-context";
|
|
6
6
|
import { VmProvider } from "./guardian/context/vm-context";
|
|
7
7
|
import { Skeleton } from "../ui/skeleton";
|
|
8
8
|
import { useTreeSelector } from "../../hooks/use-tree-selector";
|
|
9
|
+
import { SELECTABLE_NODE_TYPES } from "../../lib/types/tree-config";
|
|
10
|
+
import { useFrameParams } from "./guardian/hooks/use-frame-params";
|
|
9
11
|
/**
|
|
10
12
|
* Inner component that uses the tree selector hook
|
|
11
13
|
* Separated to ensure hooks are called after data is loaded
|
|
12
14
|
*/
|
|
13
|
-
function SandboxInner({ configWithContent, apiKey, env, themeColor, theme, config
|
|
15
|
+
function SandboxInner({ configWithContent, apiKey, env, themeColor, theme, config }) {
|
|
14
16
|
var _a;
|
|
15
17
|
const [currentConfig, setCurrentConfig] = useState(configWithContent.legacyConfig);
|
|
18
|
+
// Get frame params for syncing external selection changes
|
|
19
|
+
const frameParams = useFrameParams();
|
|
20
|
+
const isFrame = (config === null || config === void 0 ? void 0 : config.isFrame) || frameParams.isFrame;
|
|
21
|
+
// Track previous nodeTypes to detect changes
|
|
22
|
+
const prevNodeTypesRef = useRef({});
|
|
16
23
|
// Handle selection changes - update the config when user selects different options
|
|
17
24
|
const handleSelectionChange = useCallback((selection, content) => {
|
|
18
25
|
if (content) {
|
|
@@ -26,12 +33,42 @@ function SandboxInner({ configWithContent, apiKey, env, themeColor, theme, confi
|
|
|
26
33
|
}
|
|
27
34
|
}, [themeColor]);
|
|
28
35
|
// Use the tree selector hook for managing selection state
|
|
29
|
-
const { sections, selectOption, currentContent } = useTreeSelector({
|
|
36
|
+
const { sections, selectOption, currentContent, selection } = useTreeSelector({
|
|
30
37
|
config: configWithContent.techStackConfig,
|
|
31
38
|
allContent: configWithContent.allContent,
|
|
32
39
|
initialPath: configWithContent.initialSelectionPath,
|
|
33
|
-
onSelectionChange: handleSelectionChange
|
|
40
|
+
onSelectionChange: handleSelectionChange
|
|
34
41
|
});
|
|
42
|
+
// Sync frame params with tree selector when in frame mode
|
|
43
|
+
// This effect listens for changes in URL params (via postMessage or direct URL changes)
|
|
44
|
+
// and updates the tree selector state accordingly
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (!isFrame) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const prevNodeTypes = prevNodeTypesRef.current;
|
|
50
|
+
const currentNodeTypes = frameParams.nodeTypes;
|
|
51
|
+
// Check each node type for changes and call selectOption if changed
|
|
52
|
+
for (const nodeType of SELECTABLE_NODE_TYPES) {
|
|
53
|
+
const prevValue = prevNodeTypes[nodeType];
|
|
54
|
+
const currentValue = currentNodeTypes[nodeType];
|
|
55
|
+
// If the value changed and there's a new value, update the selection
|
|
56
|
+
if (currentValue && currentValue !== prevValue) {
|
|
57
|
+
// Only select if this nodeType exists in the current sections
|
|
58
|
+
const sectionExists = sections.some((s) => s.nodeType === nodeType);
|
|
59
|
+
if (sectionExists) {
|
|
60
|
+
// Check if the option exists in the section
|
|
61
|
+
const section = sections.find((s) => s.nodeType === nodeType);
|
|
62
|
+
const optionExists = section === null || section === void 0 ? void 0 : section.options.some((o) => o.key === currentValue);
|
|
63
|
+
if (optionExists) {
|
|
64
|
+
selectOption(nodeType, currentValue);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Update the ref for next comparison
|
|
70
|
+
prevNodeTypesRef.current = Object.assign({}, currentNodeTypes);
|
|
71
|
+
}, [isFrame, frameParams.nodeTypes, sections, selectOption]);
|
|
35
72
|
// Get the first use case and framework from current config
|
|
36
73
|
const firstUseCase = currentConfig.useCases[0];
|
|
37
74
|
const firstFramework = (_a = firstUseCase === null || firstUseCase === void 0 ? void 0 : firstUseCase.frameworks[0]) === null || _a === void 0 ? void 0 : _a.key;
|
|
@@ -42,7 +79,7 @@ function SandboxInner({ configWithContent, apiKey, env, themeColor, theme, confi
|
|
|
42
79
|
return (React.createElement(GuardianProvider, null,
|
|
43
80
|
React.createElement(VmProvider, null,
|
|
44
81
|
React.createElement("div", { className: "h-screen w-screen bg-white dark:bg-black" },
|
|
45
|
-
React.createElement(GuardianPlayground, { sandboxConfig: currentConfig, sections: sections, onSelect: selectOption, useCase: firstUseCase.id, framework: firstFramework, isFrame: config === null || config === void 0 ? void 0 : config.
|
|
82
|
+
React.createElement(GuardianPlayground, { sandboxConfig: currentConfig, sections: sections, onSelect: selectOption, useCase: firstUseCase.id, framework: firstFramework, isFrame: isFrame, apiKey: apiKey, env: env, chatUid: currentConfig.chatUid, theme: theme, hasNetworkView: config === null || config === void 0 ? void 0 : config.hasNetworkView, stationaryPublishedUrl: config === null || config === void 0 ? void 0 : config.stationaryPublishedUrl })))));
|
|
46
83
|
}
|
|
47
84
|
/**
|
|
48
85
|
* Sandbox component - simplified API for embedding sandboxes
|
|
@@ -71,7 +108,16 @@ function SandboxInner({ configWithContent, apiKey, env, themeColor, theme, confi
|
|
|
71
108
|
* />
|
|
72
109
|
* ```
|
|
73
110
|
*/
|
|
74
|
-
|
|
111
|
+
/**
|
|
112
|
+
*
|
|
113
|
+
* Configurations for the config param
|
|
114
|
+
* {
|
|
115
|
+
* isFrame: boolean;
|
|
116
|
+
* }
|
|
117
|
+
*
|
|
118
|
+
*
|
|
119
|
+
*/
|
|
120
|
+
export default function Sandbox({ apiKey, sandboxId, env, themeColor, theme = "dark", config }) {
|
|
75
121
|
// Validate apiKey immediately
|
|
76
122
|
if (!apiKey || apiKey.trim() === "") {
|
|
77
123
|
return (React.createElement("div", { className: "flex items-center justify-center h-screen bg-white dark:bg-black" },
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
|
3
3
|
import { useGuardianContext } from "./context/guardian-context";
|
|
4
|
+
import { useSdkTheme } from "./context/theme-context";
|
|
4
5
|
// Helper function to find a node in the file tree by path
|
|
5
6
|
function getTargetNode(fileTree, targetPath) {
|
|
6
7
|
const parts = targetPath.split("/").filter(Boolean);
|
|
@@ -163,6 +164,7 @@ let sectionIdCounter = 0;
|
|
|
163
164
|
export default function CodeFocusSection({ filePath, lineRange, title, description, children, themeColor, }) {
|
|
164
165
|
const sectionRef = useRef(null);
|
|
165
166
|
const sectionIdRef = useRef(`code-focus-${++sectionIdCounter}`);
|
|
167
|
+
const { isDark } = useSdkTheme();
|
|
166
168
|
const { generatedCode, fileTree, setActiveFilePath, setActiveFileName, setActiveLineNumber, setActiveLineRange, updateCode, setLanguage, filesEdited, activeFilePath, activeLineRange, } = useGuardianContext();
|
|
167
169
|
// Memoize the focus code function to prevent unnecessary recreations
|
|
168
170
|
const focusCode = useCallback(() => {
|
|
@@ -306,7 +308,7 @@ export default function CodeFocusSection({ filePath, lineRange, title, descripti
|
|
|
306
308
|
marginLeft: "-2rem",
|
|
307
309
|
} })),
|
|
308
310
|
React.createElement("div", { className: "flex items-center gap-2" },
|
|
309
|
-
React.createElement("h3", { className:
|
|
310
|
-
description && (React.createElement("p", { className:
|
|
311
|
+
React.createElement("h3", { className: `text-md font-semibold ${isDark ? "text-gray-100" : "text-gray-900"}` }, title)),
|
|
312
|
+
description && (React.createElement("p", { className: `text-sm mt-3 ${isDark ? "text-gray-400" : "text-gray-600"}` }, description)),
|
|
311
313
|
children && React.createElement("div", { className: "mt-3" }, children)));
|
|
312
314
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React, { createContext, useContext, useState, useEffect, } from "react";
|
|
3
|
+
const ThemeContext = createContext(undefined);
|
|
4
|
+
export function SdkThemeProvider({ children, theme = "dark" }) {
|
|
5
|
+
const [resolvedTheme, setResolvedTheme] = useState(theme === "system" ? "dark" : theme);
|
|
6
|
+
// Handle system theme preference
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (theme === "system") {
|
|
9
|
+
// Check if we're in browser environment
|
|
10
|
+
if (typeof window === "undefined")
|
|
11
|
+
return;
|
|
12
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
13
|
+
setResolvedTheme(mediaQuery.matches ? "dark" : "light");
|
|
14
|
+
const handler = (e) => {
|
|
15
|
+
setResolvedTheme(e.matches ? "dark" : "light");
|
|
16
|
+
};
|
|
17
|
+
mediaQuery.addEventListener("change", handler);
|
|
18
|
+
return () => mediaQuery.removeEventListener("change", handler);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
setResolvedTheme(theme);
|
|
22
|
+
}
|
|
23
|
+
}, [theme]);
|
|
24
|
+
const value = {
|
|
25
|
+
themeMode: theme,
|
|
26
|
+
resolvedTheme,
|
|
27
|
+
isDark: resolvedTheme === "dark",
|
|
28
|
+
};
|
|
29
|
+
return (React.createElement(ThemeContext.Provider, { value: value }, children));
|
|
30
|
+
}
|
|
31
|
+
export function useSdkTheme() {
|
|
32
|
+
const context = useContext(ThemeContext);
|
|
33
|
+
if (!context) {
|
|
34
|
+
throw new Error("useSdkTheme must be used within a SdkThemeProvider");
|
|
35
|
+
}
|
|
36
|
+
return context;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Hook that returns theme-aware class names
|
|
40
|
+
* @param lightClass - Class to use in light mode
|
|
41
|
+
* @param darkClass - Class to use in dark mode
|
|
42
|
+
* @returns The appropriate class based on current theme
|
|
43
|
+
*/
|
|
44
|
+
export function useThemeClass(lightClass, darkClass) {
|
|
45
|
+
const { isDark } = useSdkTheme();
|
|
46
|
+
return isDark ? darkClass : lightClass;
|
|
47
|
+
}
|
|
@@ -4,7 +4,7 @@ import ConsoleWithApp from "../ui/console-with-app";
|
|
|
4
4
|
import { useGuardianContext } from "../context/guardian-context";
|
|
5
5
|
import ConsoleWithGuide from "./left-view";
|
|
6
6
|
import RightView from "../right-view/right-view";
|
|
7
|
-
export default function GuardianDemo({ CustomConsole, GuideView, browserUrl, playgroundUid, useVm, codeZipFile, completeCodeZipFile, variant, themeColor, hasPreview, isFrame = false, isGuardian = false, gitUrl, isBrowserUrlReady = false, sandboxId, apiKey, sandboxError, }) {
|
|
7
|
+
export default function GuardianDemo({ CustomConsole, GuideView, browserUrl, playgroundUid, useVm, codeZipFile, completeCodeZipFile, variant, themeColor, hasPreview, isFrame = false, isGuardian = false, gitUrl, isBrowserUrlReady = false, sandboxId, apiKey, sandboxError, hasNetworkView = false, }) {
|
|
8
8
|
const { isBrowserMaximized } = useGuardianContext();
|
|
9
9
|
const { setCurrentView } = useGuardianContext();
|
|
10
10
|
const [reloadCounter, setReloadCounter] = useState(0);
|
|
@@ -24,12 +24,12 @@ export default function GuardianDemo({ CustomConsole, GuideView, browserUrl, pla
|
|
|
24
24
|
}, [setCurrentView]);
|
|
25
25
|
// If isFrame, just return RightView directly
|
|
26
26
|
if (isFrame) {
|
|
27
|
-
return (React.createElement(RightView, { reloadCounter: reloadCounter, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, hasPreview: hasPreview, variant: variant, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, isGuardian: isGuardian, isBrowserMaximized: isBrowserMaximized, isBrowserUrlReady: isBrowserUrlReady, isFrame: isFrame, sandboxId: sandboxId, apiKey: apiKey, sandboxError: sandboxError }));
|
|
27
|
+
return (React.createElement(RightView, { reloadCounter: reloadCounter, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, hasPreview: hasPreview, variant: variant, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, isGuardian: isGuardian, isBrowserMaximized: isBrowserMaximized, isBrowserUrlReady: isBrowserUrlReady, isFrame: isFrame, sandboxId: sandboxId, apiKey: apiKey, sandboxError: sandboxError, hasNetworkView: hasNetworkView }));
|
|
28
28
|
}
|
|
29
29
|
// If browser is maximized, render RightView at full size without the console
|
|
30
30
|
if (isBrowserMaximized && isGuardian) {
|
|
31
31
|
return (React.createElement("div", { className: "w-full h-full rounded-2xl overflow-hidden" },
|
|
32
|
-
React.createElement(RightView, { reloadCounter: reloadCounter, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, hasPreview: hasPreview, variant: variant, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, isGuardian: isGuardian, isBrowserMaximized: true, isBrowserUrlReady: isBrowserUrlReady, isFrame: isFrame, sandboxId: sandboxId, apiKey: apiKey, sandboxError: sandboxError })));
|
|
32
|
+
React.createElement(RightView, { reloadCounter: reloadCounter, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, hasPreview: hasPreview, variant: variant, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, isGuardian: isGuardian, isBrowserMaximized: true, isBrowserUrlReady: isBrowserUrlReady, isFrame: isFrame, sandboxId: sandboxId, apiKey: apiKey, sandboxError: sandboxError, hasNetworkView: hasNetworkView })));
|
|
33
33
|
}
|
|
34
|
-
return (React.createElement(ConsoleWithApp, { containerClassName: "", console: React.createElement(ConsoleWithGuide, { CustomConsole: CustomConsole, GuideView: GuideView, playgroundUid: playgroundUid, onReloadPreview: handleReloadPreview, onStageChange: handleStageChange, codeZipFile: codeZipFile, themeColor: themeColor, browserUrl: browserUrl, gitUrl: gitUrl, sandboxId: sandboxId, apiKey: apiKey }), app: React.createElement(RightView, { reloadCounter: reloadCounter, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, hasPreview: hasPreview, variant: variant, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, isGuardian: isGuardian, isBrowserMaximized: isBrowserMaximized, isBrowserUrlReady: isBrowserUrlReady, isFrame: isFrame, sandboxId: sandboxId, apiKey: apiKey, sandboxError: sandboxError }) }));
|
|
34
|
+
return (React.createElement(ConsoleWithApp, { containerClassName: "", console: React.createElement(ConsoleWithGuide, { CustomConsole: CustomConsole, GuideView: GuideView, playgroundUid: playgroundUid, onReloadPreview: handleReloadPreview, onStageChange: handleStageChange, codeZipFile: codeZipFile, themeColor: themeColor, browserUrl: browserUrl, gitUrl: gitUrl, sandboxId: sandboxId, apiKey: apiKey }), app: React.createElement(RightView, { reloadCounter: reloadCounter, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, hasPreview: hasPreview, variant: variant, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, isGuardian: isGuardian, isBrowserMaximized: isBrowserMaximized, isBrowserUrlReady: isBrowserUrlReady, isFrame: isFrame, sandboxId: sandboxId, apiKey: apiKey, sandboxError: sandboxError, hasNetworkView: hasNetworkView }) }));
|
|
35
35
|
}
|
|
@@ -5,10 +5,12 @@ import React, { useMemo, useState } from "react";
|
|
|
5
5
|
// import DefaultGuideView from "../default-guide-view";
|
|
6
6
|
import { Markdown } from "../ui/markdown";
|
|
7
7
|
import sampleappLogo from "../../../../assets/sampleapp-logo.png";
|
|
8
|
+
import { useSdkTheme } from "../context/theme-context";
|
|
8
9
|
function Logo({ href, width }) {
|
|
10
|
+
const { isDark } = useSdkTheme();
|
|
9
11
|
return (React.createElement("div", { className: "flex justify-center md:justify-start" },
|
|
10
12
|
React.createElement("a", { href: href ? href : "https://sampleapp.ai", className: "flex", target: "_blank", rel: "noopener noreferrer" },
|
|
11
|
-
React.createElement("div", { className: "
|
|
13
|
+
React.createElement("div", { className: isDark ? "" : "invert" },
|
|
12
14
|
React.createElement("img", { src: sampleappLogo, alt: "SampleApp Logo", width: width || 160, height: 40 })))));
|
|
13
15
|
}
|
|
14
16
|
export default function ConsoleWithGuide({ CustomConsole, GuideView, playgroundUid, onReloadPreview, onStageChange, codeZipFile, themeColor, browserUrl, gitUrl, sandboxId, apiKey, }) {
|
|
@@ -20,7 +22,7 @@ export default function ConsoleWithGuide({ CustomConsole, GuideView, playgroundU
|
|
|
20
22
|
if (typeof CustomConsole === "string") {
|
|
21
23
|
return (React.createElement("div", { className: "h-full flex flex-col" },
|
|
22
24
|
React.createElement("div", { className: "flex-1 px-8 " },
|
|
23
|
-
React.createElement(Markdown, {
|
|
25
|
+
React.createElement(Markdown, { themeColor: themeColor, browserUrl: browserUrl, gitUrl: gitUrl, sandboxId: sandboxId, apiKey: apiKey }, CustomConsole))));
|
|
24
26
|
}
|
|
25
27
|
const CustomConsoleComponent = CustomConsole;
|
|
26
28
|
return (React.createElement(CustomConsoleComponent, { onReloadPreview: onReloadPreview !== null && onReloadPreview !== void 0 ? onReloadPreview : (() => { }), onStageChange: onStageChange !== null && onStageChange !== void 0 ? onStageChange : (() => { }), themeColor: themeColor }));
|
|
@@ -56,9 +58,13 @@ export default function ConsoleWithGuide({ CustomConsole, GuideView, playgroundU
|
|
|
56
58
|
return (React.createElement("div", { className: "flex-1 min-h-0 relative flex flex-col" },
|
|
57
59
|
React.createElement("div", { className: "h-[calc(100vh-9.5rem)] min-h-0 overflow-hidden flex-1 flex flex-col" },
|
|
58
60
|
React.createElement("div", { className: "flex-1 min-h-0 overflow-y-auto" }, isConsoleView && renderCustomConsole()),
|
|
59
|
-
React.createElement(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
React.createElement(PoweredByFooter, null))));
|
|
62
|
+
}
|
|
63
|
+
function PoweredByFooter() {
|
|
64
|
+
const { isDark } = useSdkTheme();
|
|
65
|
+
return (React.createElement("div", { className: "relative" },
|
|
66
|
+
React.createElement("div", { className: "flex flex-row w-full items-center text-xs justify-center opacity-30 mt-1 pb-2 bg-background" },
|
|
67
|
+
React.createElement("span", { className: isDark ? "text-gray-300" : "text-zinc-600" }, "Powered by"),
|
|
68
|
+
React.createElement("span", { className: "hover:bg-accent rounded-sm p-1 mx-1" },
|
|
69
|
+
React.createElement(Logo, { width: 100, href: "https://sampleapp.ai" })))));
|
|
64
70
|
}
|
|
@@ -9,7 +9,7 @@ import { useFrameMessages } from "./hooks/use-frame-messages";
|
|
|
9
9
|
import { useVmContext } from "./context/vm-context";
|
|
10
10
|
import { cn } from "../../../lib/utils";
|
|
11
11
|
export default function GuardianComponent({ sections, onSelect, currentFramework, currentUseCase, CustomConsole, GuideView, playgroundLogo, playgroundUid, browserUrl, useVm, sandboxUid, codeZipFile, completeCodeZipFile, variant, themeColor, hasPreview = true, isFrame = false, apiKey, env, chatUid, hideHeader = false, // Hardcoded to true by default, not exposed in Sandbox.tsx
|
|
12
|
-
gitUrl, }) {
|
|
12
|
+
gitUrl, hasNetworkView = false, stationaryPublishedUrl }) {
|
|
13
13
|
const { previewUrl, setPreviewUrl } = useGuardianContext();
|
|
14
14
|
const { vmResolution } = useVmContext();
|
|
15
15
|
const [sandboxError, setSandboxError] = React.useState(null);
|
|
@@ -28,9 +28,20 @@ gitUrl, }) {
|
|
|
28
28
|
useFrameMessages();
|
|
29
29
|
// Track which main sandbox we've loaded to prevent duplicates
|
|
30
30
|
const loadedSandboxRef = useRef("");
|
|
31
|
+
// Track initial URLs per use case for stationaryPublishedUrl feature
|
|
32
|
+
const initialUrlsByUseCaseRef = useRef(new Map());
|
|
31
33
|
useEffect(() => {
|
|
32
34
|
// Create a key for this sandbox load
|
|
33
35
|
const sandboxKey = `${sandboxUid}-${browserUrl}-${useVm}`;
|
|
36
|
+
const useCaseKey = currentUseCase || "default";
|
|
37
|
+
// If stationaryPublishedUrl is enabled and we already have a URL for this use case,
|
|
38
|
+
// use the stored URL and skip loading a new one
|
|
39
|
+
if (stationaryPublishedUrl &&
|
|
40
|
+
initialUrlsByUseCaseRef.current.has(useCaseKey)) {
|
|
41
|
+
const storedUrl = initialUrlsByUseCaseRef.current.get(useCaseKey);
|
|
42
|
+
setPreviewUrl(storedUrl);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
34
45
|
// Check if we've already initiated a load for this exact sandbox config
|
|
35
46
|
if (loadedSandboxRef.current === sandboxKey) {
|
|
36
47
|
return;
|
|
@@ -41,6 +52,11 @@ gitUrl, }) {
|
|
|
41
52
|
// Found cached URL for this exact combination, use it immediately
|
|
42
53
|
setPreviewUrl(existingUrl);
|
|
43
54
|
loadedSandboxRef.current = sandboxKey;
|
|
55
|
+
// Store as initial URL for this use case if stationaryPublishedUrl is enabled
|
|
56
|
+
if (stationaryPublishedUrl &&
|
|
57
|
+
!initialUrlsByUseCaseRef.current.has(useCaseKey)) {
|
|
58
|
+
initialUrlsByUseCaseRef.current.set(useCaseKey, existingUrl);
|
|
59
|
+
}
|
|
44
60
|
return;
|
|
45
61
|
}
|
|
46
62
|
// If useVm is true, wait for resolution from context before loading
|
|
@@ -61,9 +77,14 @@ gitUrl, }) {
|
|
|
61
77
|
browserUrl,
|
|
62
78
|
useVm,
|
|
63
79
|
resolution: vmResolution,
|
|
64
|
-
apiKey
|
|
80
|
+
apiKey
|
|
65
81
|
})
|
|
66
82
|
.then((url) => {
|
|
83
|
+
// Store as initial URL for this use case if stationaryPublishedUrl is enabled
|
|
84
|
+
if (stationaryPublishedUrl &&
|
|
85
|
+
!initialUrlsByUseCaseRef.current.has(useCaseKey)) {
|
|
86
|
+
initialUrlsByUseCaseRef.current.set(useCaseKey, url);
|
|
87
|
+
}
|
|
67
88
|
setPreviewUrl(url);
|
|
68
89
|
setSandboxError(null); // Clear error on success
|
|
69
90
|
})
|
|
@@ -76,13 +97,21 @@ gitUrl, }) {
|
|
|
76
97
|
});
|
|
77
98
|
// Re-run when sandbox parameters change OR when resolution becomes available
|
|
78
99
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
79
|
-
}, [
|
|
100
|
+
}, [
|
|
101
|
+
sandboxUid,
|
|
102
|
+
browserUrl,
|
|
103
|
+
useVm,
|
|
104
|
+
vmResolution,
|
|
105
|
+
apiKey,
|
|
106
|
+
currentUseCase,
|
|
107
|
+
stationaryPublishedUrl
|
|
108
|
+
]);
|
|
80
109
|
// If isFrame, just render GuardianDemo without the header/layout wrapper
|
|
81
110
|
if (isFrame) {
|
|
82
111
|
return (React.createElement("div", { className: "h-[100vh]" },
|
|
83
|
-
React.createElement(GuardianDemo, { CustomConsole: CustomConsole, GuideView: GuideView, browserUrl: previewUrl, playgroundUid: playgroundUid, useVm: useVm, codeZipFile: codeZipFile, completeCodeZipFile: completeCodeZipFile, variant: variant, themeColor: themeColor, hasPreview: hasPreview, isFrame: isFrame, isGuardian: true, gitUrl: gitUrl, isBrowserUrlReady: isBrowserUrlReady, sandboxId: sandboxUid, apiKey: apiKey, sandboxError: sandboxError })));
|
|
112
|
+
React.createElement(GuardianDemo, { CustomConsole: CustomConsole, GuideView: GuideView, browserUrl: previewUrl, playgroundUid: playgroundUid, useVm: useVm, codeZipFile: codeZipFile, completeCodeZipFile: completeCodeZipFile, variant: variant, themeColor: themeColor, hasPreview: hasPreview, isFrame: isFrame, isGuardian: true, gitUrl: gitUrl, isBrowserUrlReady: isBrowserUrlReady, sandboxId: sandboxUid, apiKey: apiKey, sandboxError: sandboxError, hasNetworkView: hasNetworkView })));
|
|
84
113
|
}
|
|
85
114
|
return (React.createElement(AppLayoutNoSidebar, { header: !hideHeader ? (React.createElement(Header, { sections: sections, onSelect: onSelect, playgroundLogo: playgroundLogo, themecolor: themeColor })) : undefined, hasBodyPadding: false },
|
|
86
115
|
React.createElement("div", { className: cn("flex-1 min-h-0 flex flex-col", hideHeader && "pt-4") },
|
|
87
|
-
React.createElement(GuardianDemo, { CustomConsole: CustomConsole, GuideView: GuideView, browserUrl: previewUrl, playgroundUid: playgroundUid, useVm: useVm, codeZipFile: codeZipFile, completeCodeZipFile: completeCodeZipFile, variant: variant, themeColor: themeColor, hasPreview: hasPreview, isFrame: isFrame, isGuardian: true, gitUrl: gitUrl, isBrowserUrlReady: isBrowserUrlReady, sandboxId: sandboxUid, apiKey: apiKey, sandboxError: sandboxError }))));
|
|
116
|
+
React.createElement(GuardianDemo, { CustomConsole: CustomConsole, GuideView: GuideView, browserUrl: previewUrl, playgroundUid: playgroundUid, useVm: useVm, codeZipFile: codeZipFile, completeCodeZipFile: completeCodeZipFile, variant: variant, themeColor: themeColor, hasPreview: hasPreview, isFrame: isFrame, isGuardian: true, gitUrl: gitUrl, isBrowserUrlReady: isBrowserUrlReady, sandboxId: sandboxUid, apiKey: apiKey, sandboxError: sandboxError, hasNetworkView: hasNetworkView }))));
|
|
88
117
|
}
|
|
@@ -7,16 +7,19 @@ import { buildGuardianConfig } from "./utils";
|
|
|
7
7
|
/**
|
|
8
8
|
* Guardian Playground component with tree-based tech stack selection.
|
|
9
9
|
* Uses sections from useTreeSelector for dynamic header rendering.
|
|
10
|
+
*
|
|
11
|
+
* Note: Frame param overrides for node types (architecture, frontend, backend, etc.)
|
|
12
|
+
* are now handled in Sandbox.tsx via the tree selector sync mechanism.
|
|
13
|
+
* This component receives the already-resolved framework from the tree selector.
|
|
10
14
|
*/
|
|
11
|
-
export default function GuardianPlayground({ sandboxConfig, sections, onSelect, useCase, framework
|
|
15
|
+
export default function GuardianPlayground({ sandboxConfig, sections, onSelect, useCase, framework, isFrame = false, apiKey, env, chatUid, theme = "dark", hasNetworkView = false, stationaryPublishedUrl }) {
|
|
12
16
|
var _a;
|
|
13
|
-
//
|
|
17
|
+
// Get frame params for iframeUrl override only
|
|
14
18
|
const frameParams = useFrameParams();
|
|
15
|
-
const framework = isFrame && frameParams.framework ? frameParams.framework : serverFramework;
|
|
16
19
|
// Build the nested config from use cases
|
|
17
20
|
const nestedConfig = buildGuardianConfig(sandboxConfig.useCases, {
|
|
18
21
|
playgroundUid: sandboxConfig.id,
|
|
19
|
-
playgroundLogo: sandboxConfig.playgroundLogo || (React.createElement("div", { className: "text-zinc-900 dark:text-white font-bold text-xl" }, sandboxConfig.name))
|
|
22
|
+
playgroundLogo: sandboxConfig.playgroundLogo || (React.createElement("div", { className: "text-zinc-900 dark:text-white font-bold text-xl" }, sandboxConfig.name))
|
|
20
23
|
});
|
|
21
24
|
const useCaseConfig = nestedConfig[useCase];
|
|
22
25
|
if (!useCaseConfig) {
|
|
@@ -37,5 +40,5 @@ export default function GuardianPlayground({ sandboxConfig, sections, onSelect,
|
|
|
37
40
|
"\"")));
|
|
38
41
|
}
|
|
39
42
|
return (React.createElement(GuardianStyleWrapper, { themeColor: frameworkConfig.themeColor, theme: theme },
|
|
40
|
-
React.createElement(GuardianComponent, { sections: sections, onSelect: onSelect, currentFramework: frameworkConfig.currentFramework, CustomConsole: frameworkConfig.CustomConsole, GuideView: frameworkConfig.GuideView, playgroundUid: frameworkConfig.playgroundUid, browserUrl: (_a = frameParams.iframeUrl) !== null && _a !== void 0 ? _a : frameworkConfig.browserUrl, useVm: frameworkConfig.useVm, playgroundLogo: frameworkConfig.playgroundLogo, sandboxUid: frameworkConfig.sandboxUid, name: frameworkConfig.name, description: frameworkConfig.description, codeZipFile: frameworkConfig.codeZipFile, completeCodeZipFile: frameworkConfig.completeCodeZipFile, variant: frameworkConfig.variant, themeColor: frameworkConfig.themeColor, hasPreview: frameworkConfig.hasPreview, currentUseCase: useCase, isFrame: isFrame, apiKey: apiKey, env: env, chatUid: chatUid, gitUrl: frameworkConfig.gitUrl, hideHeader: false })));
|
|
43
|
+
React.createElement(GuardianComponent, { sections: sections, onSelect: onSelect, currentFramework: frameworkConfig.currentFramework, CustomConsole: frameworkConfig.CustomConsole, GuideView: frameworkConfig.GuideView, playgroundUid: frameworkConfig.playgroundUid, browserUrl: (_a = frameParams.iframeUrl) !== null && _a !== void 0 ? _a : frameworkConfig.browserUrl, useVm: frameworkConfig.useVm, playgroundLogo: frameworkConfig.playgroundLogo, sandboxUid: frameworkConfig.sandboxUid, name: frameworkConfig.name, description: frameworkConfig.description, codeZipFile: frameworkConfig.codeZipFile, completeCodeZipFile: frameworkConfig.completeCodeZipFile, variant: frameworkConfig.variant, themeColor: frameworkConfig.themeColor, hasPreview: frameworkConfig.hasPreview, currentUseCase: useCase, isFrame: isFrame, apiKey: apiKey, env: env, chatUid: chatUid, gitUrl: frameworkConfig.gitUrl, hideHeader: false, hasNetworkView: hasNetworkView, stationaryPublishedUrl: stationaryPublishedUrl })));
|
|
41
44
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import React, { useEffect, useRef, useState } from "react";
|
|
3
3
|
import { TAILWIND_CSS } from "../../../lib/generated-css";
|
|
4
|
+
import { SdkThemeProvider } from "./context/theme-context";
|
|
4
5
|
/**
|
|
5
6
|
* Style wrapper for Guardian components that injects Tailwind CSS
|
|
6
7
|
* into the document without using Shadow DOM (preserves React context)
|
|
7
8
|
*/
|
|
8
|
-
export const GuardianStyleWrapper = ({ children, themeColor = "#3b82f6", theme = "dark"
|
|
9
|
+
export const GuardianStyleWrapper = ({ children, themeColor = "#3b82f6", theme = "dark" }) => {
|
|
9
10
|
const [stylesInjected, setStylesInjected] = useState(false);
|
|
10
11
|
const [resolvedTheme, setResolvedTheme] = useState(theme === "system" ? "dark" : theme);
|
|
11
12
|
const rootRef = useRef(null);
|
|
@@ -58,9 +59,10 @@ export const GuardianStyleWrapper = ({ children, themeColor = "#3b82f6", theme =
|
|
|
58
59
|
rootRef.current.style.setProperty("--colors-secondary", secondaryColor);
|
|
59
60
|
}
|
|
60
61
|
}, [themeColor]);
|
|
61
|
-
return (React.createElement(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
return (React.createElement(SdkThemeProvider, { theme: theme },
|
|
63
|
+
React.createElement("div", { ref: rootRef, className: `guardian-sdk-root bg-background text-foreground ${resolvedTheme === "dark" ? "dark" : ""}`, style: {
|
|
64
|
+
width: "100%",
|
|
65
|
+
height: "100%",
|
|
66
|
+
fontFamily: 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"'
|
|
67
|
+
} }, children)));
|
|
66
68
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import React from "react";
|
|
3
|
+
import { useSdkTheme } from "./context/theme-context";
|
|
3
4
|
/**
|
|
4
5
|
* Header component for dynamic tech stack selection
|
|
5
6
|
*
|
|
@@ -7,24 +8,30 @@ import React from "react";
|
|
|
7
8
|
* based on the tech_stack_config tree structure.
|
|
8
9
|
*/
|
|
9
10
|
export default function Header({ sections, onSelect, playgroundLogo, themecolor }) {
|
|
10
|
-
|
|
11
|
+
const { isDark } = useSdkTheme();
|
|
11
12
|
return (React.createElement("div", { className: "w-full sticky top-0 z-50 px-4 lg:px-8 backdrop-blur-md" },
|
|
12
13
|
React.createElement("div", { className: "flex flex-wrap items-center justify-between py-2" },
|
|
13
14
|
React.createElement("div", { className: "flex flex-wrap items-center gap-6" }, sections.map((section) => {
|
|
14
15
|
var _a;
|
|
15
16
|
return (React.createElement("div", { key: section.nodeType, className: "flex items-center flex-wrap gap-3" },
|
|
16
|
-
React.createElement("span", { className:
|
|
17
|
+
React.createElement("span", { className: `text-sm font-semibold whitespace-nowrap ${isDark ? "text-gray-400" : "text-gray-600"}` }, section.label),
|
|
17
18
|
React.createElement("div", { className: "hidden md:block" },
|
|
18
19
|
React.createElement("div", { className: "flex items-center gap-2" }, section.options.map((option) => {
|
|
19
20
|
const isSelected = option.isSelected;
|
|
20
|
-
|
|
21
|
+
// Only disable non-leaf nodes that have no content
|
|
22
|
+
// Leaf nodes should always be clickable (they represent final selections)
|
|
23
|
+
const isDisabled = option.hasContent === false && !option.isLeaf;
|
|
21
24
|
return (React.createElement("button", { key: option.key, onClick: () => {
|
|
22
25
|
if (!isDisabled) {
|
|
23
26
|
onSelect(section.nodeType, option.key);
|
|
24
27
|
}
|
|
25
28
|
}, disabled: isDisabled, className: `inline-flex items-center px-3 py-1.5 text-xs font-medium rounded-md border transition-all cursor-pointer active:scale-[0.98] ${isSelected
|
|
26
|
-
?
|
|
27
|
-
|
|
29
|
+
? isDark
|
|
30
|
+
? "bg-zinc-900 hover:bg-zinc-800"
|
|
31
|
+
: "bg-white hover:bg-zinc-100"
|
|
32
|
+
: isDark
|
|
33
|
+
? "bg-zinc-900 border-zinc-700 text-zinc-100 hover:bg-zinc-800"
|
|
34
|
+
: "bg-white border-zinc-300 text-zinc-900 hover:bg-zinc-100"} ${isDisabled ? "opacity-50 cursor-not-allowed" : ""}`, style: isSelected && themecolor
|
|
28
35
|
? {
|
|
29
36
|
borderColor: themecolor,
|
|
30
37
|
color: themecolor
|
|
@@ -34,7 +41,9 @@ export default function Header({ sections, onSelect, playgroundLogo, themecolor
|
|
|
34
41
|
React.createElement("div", { className: "block md:hidden relative" },
|
|
35
42
|
React.createElement("select", { value: ((_a = section.options.find((o) => o.isSelected)) === null || _a === void 0 ? void 0 : _a.key) || "", onChange: (e) => {
|
|
36
43
|
onSelect(section.nodeType, e.target.value);
|
|
37
|
-
}, className:
|
|
44
|
+
}, className: `appearance-none border rounded-md px-3 py-1.5 pr-8 text-xs font-medium focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all ${isDark
|
|
45
|
+
? "bg-zinc-900 border-zinc-700 text-zinc-100 hover:bg-zinc-800"
|
|
46
|
+
: "bg-white border-zinc-300 text-zinc-900 hover:bg-zinc-100"}` }, section.options.map((option) => (React.createElement("option", { key: option.key, value: option.key, disabled: option.hasContent === false && !option.isLeaf }, option.label)))),
|
|
38
47
|
React.createElement("svg", { "aria-hidden": "true", width: "16", height: "16", viewBox: "0 0 16 16", xmlns: "http://www.w3.org/2000/svg", className: "absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none text-zinc-400" },
|
|
39
48
|
React.createElement("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M2.35 9.915a.875.875 0 0 1 1.235-.065L8 13.823l4.415-3.973a.875.875 0 0 1 1.17 1.3l-5 4.5a.873.873 0 0 1-1.17 0l-5-4.5a.875.875 0 0 1-.065-1.235ZM7.415.35a.873.873 0 0 1 1.17 0l5 4.5a.875.875 0 1 1-1.17 1.3L8 2.177 3.585 6.15a.875.875 0 0 1-1.17-1.3l5-4.5Z", fill: "currentColor" })))));
|
|
40
49
|
})))));
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useEffect, useRef } from "react";
|
|
3
|
+
import { SELECTABLE_NODE_TYPES } from "../../../../lib/types/tree-config";
|
|
3
4
|
/**
|
|
4
5
|
* Hook to handle postMessage communication with parent window when in iframe mode.
|
|
5
6
|
* - Sends IFRAME_READY when mounted
|
|
@@ -7,10 +8,11 @@ import { useEffect, useRef } from "react";
|
|
|
7
8
|
* - Works with useFrameParams() which reads the updated URL params
|
|
8
9
|
*
|
|
9
10
|
* Supported UPDATE_VIEW message fields:
|
|
10
|
-
* -
|
|
11
|
+
* - Node type selections: product, version, architecture, frontend, backend, framework, integration, platform
|
|
11
12
|
* - activeFilePath: Change the active file in the editor
|
|
12
13
|
* - linesStart/linesEnd: Highlight specific line range in the editor
|
|
13
14
|
* - theme: Override the theme color (hex color)
|
|
15
|
+
* - iframeUrl: Override the preview iframe URL
|
|
14
16
|
*/
|
|
15
17
|
export function useFrameMessages() {
|
|
16
18
|
const hasSignaledReady = useRef(false);
|
|
@@ -18,11 +20,16 @@ export function useFrameMessages() {
|
|
|
18
20
|
if (typeof window === "undefined")
|
|
19
21
|
return;
|
|
20
22
|
// Check if we're in an iframe
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
+
const isInIframe = window.self !== window.top;
|
|
24
|
+
// Also check URL param for isFrame (for testing without actual iframe)
|
|
25
|
+
const searchParams = new URLSearchParams(window.location.search);
|
|
26
|
+
const isFrameParam = searchParams.get("isFrame") === "true";
|
|
27
|
+
// Listen for messages if we're in an iframe OR if isFrame=true in URL
|
|
28
|
+
if (!isInIframe && !isFrameParam) {
|
|
23
29
|
return;
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
}
|
|
31
|
+
// Signal to parent that we're ready to receive messages (only if actually in iframe)
|
|
32
|
+
if (isInIframe && !hasSignaledReady.current && window.parent) {
|
|
26
33
|
window.parent.postMessage({ type: "IFRAME_READY" }, "*");
|
|
27
34
|
hasSignaledReady.current = true;
|
|
28
35
|
}
|
|
@@ -32,10 +39,18 @@ export function useFrameMessages() {
|
|
|
32
39
|
const data = event.data;
|
|
33
40
|
if (data.type === "UPDATE_VIEW") {
|
|
34
41
|
const url = new URL(window.location.href);
|
|
35
|
-
// Update
|
|
42
|
+
// Update all node type params
|
|
43
|
+
for (const nodeType of SELECTABLE_NODE_TYPES) {
|
|
44
|
+
const value = data[nodeType];
|
|
45
|
+
if (value !== undefined && typeof value === "string") {
|
|
46
|
+
url.searchParams.set(nodeType, value);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Legacy framework param (also handled above via SELECTABLE_NODE_TYPES)
|
|
36
50
|
if (data.framework !== undefined) {
|
|
37
51
|
url.searchParams.set("framework", data.framework);
|
|
38
52
|
}
|
|
53
|
+
// Editor-related params
|
|
39
54
|
if (data.activeFilePath !== undefined) {
|
|
40
55
|
url.searchParams.set("activeFilePath", data.activeFilePath);
|
|
41
56
|
}
|
|
@@ -54,12 +69,13 @@ export function useFrameMessages() {
|
|
|
54
69
|
// Update URL without page reload
|
|
55
70
|
window.history.replaceState({}, "", url.toString());
|
|
56
71
|
// Dispatch a custom event so React components can react immediately
|
|
57
|
-
// (useFrameParams may have a slight delay)
|
|
58
72
|
window.dispatchEvent(new CustomEvent("frameParamsUpdated", { detail: data }));
|
|
59
73
|
}
|
|
60
74
|
};
|
|
61
75
|
window.addEventListener("message", handleMessage);
|
|
62
|
-
return () =>
|
|
76
|
+
return () => {
|
|
77
|
+
window.removeEventListener("message", handleMessage);
|
|
78
|
+
};
|
|
63
79
|
}, []);
|
|
64
80
|
return {};
|
|
65
81
|
}
|