@sampleapp.ai/sdk 1.0.29 → 1.0.31
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/guardian/app-layout-no-sidebar.js +8 -0
- package/dist/components/guardian/ask-ai-view.js +249 -0
- package/dist/components/guardian/code-focus-section.d.ts +41 -0
- package/dist/components/guardian/code-focus-section.js +174 -0
- package/dist/components/guardian/context/guardian-context.js +94 -0
- package/dist/components/guardian/context/vm-context.js +28 -0
- package/dist/components/guardian/default-guide-view.js +34 -0
- package/dist/components/guardian/demo/guardian-demo.js +35 -0
- package/dist/components/guardian/demo/left-view/toggle.js +28 -0
- package/dist/components/guardian/demo/left-view.js +49 -0
- package/dist/components/guardian/guardian-component.js +79 -0
- package/dist/components/guardian/guardian-demo.js +35 -0
- package/dist/components/guardian/guardian-home.d.ts +4 -0
- package/dist/components/guardian/guardian-home.js +61 -0
- package/dist/components/guardian/guardian-playground.js +45 -0
- package/dist/components/guardian/guardian-style-wrapper.js +29 -0
- package/dist/components/guardian/guardian-upload-spec.d.ts +14 -0
- package/dist/components/guardian/guardian-upload-spec.js +160 -0
- package/dist/components/guardian/header/glassmorphic-combobox.d.ts +15 -0
- package/dist/components/guardian/header/glassmorphic-combobox.js +30 -0
- package/dist/components/guardian/header.js +61 -0
- package/dist/components/guardian/hooks/use-frame-messages.js +65 -0
- package/dist/components/guardian/hooks/use-frame-params.js +44 -0
- package/dist/components/guardian/hooks/use-sandbox-url-loader.js +101 -0
- package/dist/components/guardian/ide/browser.js +538 -0
- package/dist/components/guardian/index.js +8 -0
- package/dist/components/guardian/layout/app-layout-no-sidebar.js +8 -0
- package/dist/components/guardian/layout/header/glassmorphic-combobox.js +48 -0
- package/dist/components/guardian/layout/header.js +63 -0
- package/dist/components/guardian/right-view/code-view.js +56 -0
- package/dist/components/guardian/right-view/pill-file-selector.js +233 -0
- package/dist/components/guardian/right-view/preview-control-bar.js +25 -0
- package/dist/components/guardian/right-view/right-panel-view.js +38 -0
- package/dist/components/guardian/right-view/right-top-down-view.js +289 -0
- package/dist/components/guardian/right-view/right-view.js +28 -0
- package/dist/components/guardian/right-view/simplified-editor.js +234 -0
- package/dist/components/guardian/types/ide-types.js +162 -0
- package/dist/components/guardian/types.js +3 -0
- package/dist/components/guardian/ui/ai-loader.js +48 -0
- package/dist/components/guardian/ui/badge.js +24 -0
- package/dist/components/guardian/ui/button.js +45 -0
- package/dist/components/guardian/ui/command.js +63 -0
- package/dist/components/guardian/ui/console-with-app.js +17 -0
- package/dist/components/guardian/ui/dialog.js +57 -0
- package/dist/components/guardian/ui/dropdown-menu.js +82 -0
- package/dist/components/guardian/ui/markdown.js +57 -0
- package/dist/components/guardian/ui/popover.js +25 -0
- package/dist/components/guardian/ui/tooltip.js +25 -0
- package/dist/components/guardian/utils.js +88 -0
- package/dist/components/guardian/zip-to-codebase.js +246 -0
- package/dist/components/guardian/zip-to-filetree.js +284 -0
- package/dist/components/icons.js +22 -0
- package/dist/components/sandbox/Sandbox.js +88 -0
- package/dist/components/sandbox/SandboxHome.js +141 -0
- package/dist/components/sandbox/api.js +84 -0
- package/dist/components/sandbox/guardian/app-layout-no-sidebar.js +11 -0
- package/dist/components/sandbox/guardian/ask-ai-view.js +249 -0
- package/dist/components/sandbox/guardian/code-focus-section.js +174 -0
- package/dist/components/sandbox/guardian/context/guardian-context.js +94 -0
- package/dist/components/sandbox/guardian/context/vm-context.js +28 -0
- package/dist/components/sandbox/guardian/default-guide-view.js +34 -0
- package/dist/components/sandbox/guardian/demo/guardian-demo.js +35 -0
- package/dist/components/sandbox/guardian/demo/left-view/toggle.js +28 -0
- package/dist/components/sandbox/guardian/demo/left-view.js +70 -0
- package/dist/components/sandbox/guardian/guardian-component.js +99 -0
- package/dist/components/sandbox/guardian/guardian-demo.js +35 -0
- package/dist/components/sandbox/guardian/guardian-home.d.ts +4 -0
- package/dist/components/sandbox/guardian/guardian-home.js +61 -0
- package/dist/components/sandbox/guardian/guardian-playground.js +45 -0
- package/dist/components/sandbox/guardian/guardian-style-wrapper.js +47 -0
- package/dist/components/sandbox/guardian/guardian-upload-spec.d.ts +14 -0
- package/dist/components/sandbox/guardian/guardian-upload-spec.js +160 -0
- package/dist/components/sandbox/guardian/header/glassmorphic-combobox.js +30 -0
- package/dist/components/sandbox/guardian/header.js +61 -0
- package/dist/components/sandbox/guardian/hooks/use-frame-messages.js +65 -0
- package/dist/components/sandbox/guardian/hooks/use-frame-params.js +44 -0
- package/dist/components/sandbox/guardian/hooks/use-sandbox-url-loader.js +125 -0
- package/dist/components/sandbox/guardian/ide/browser.js +538 -0
- package/dist/components/sandbox/guardian/index.js +8 -0
- package/dist/components/sandbox/guardian/right-view/code-view.js +60 -0
- package/dist/components/sandbox/guardian/right-view/pill-file-selector.js +246 -0
- package/dist/components/sandbox/guardian/right-view/preview-control-bar.js +25 -0
- package/dist/components/sandbox/guardian/right-view/right-panel-view.js +51 -0
- package/dist/components/sandbox/guardian/right-view/right-top-down-view.js +281 -0
- package/dist/components/sandbox/guardian/right-view/right-view.js +28 -0
- package/dist/components/sandbox/guardian/right-view/simplified-editor.js +234 -0
- package/dist/components/sandbox/guardian/types/ide-types.js +162 -0
- package/dist/components/sandbox/guardian/types.js +3 -0
- package/dist/components/sandbox/guardian/ui/ai-loader.js +91 -0
- package/dist/components/sandbox/guardian/ui/badge.js +24 -0
- package/dist/components/sandbox/guardian/ui/button.js +45 -0
- package/dist/components/sandbox/guardian/ui/command.js +63 -0
- package/dist/components/sandbox/guardian/ui/console-with-app.js +17 -0
- package/dist/components/sandbox/guardian/ui/dialog.js +57 -0
- package/dist/components/sandbox/guardian/ui/download-and-open-buttons.js +117 -0
- package/dist/components/sandbox/guardian/ui/dropdown-menu.js +82 -0
- package/dist/components/sandbox/guardian/ui/markdown/accordion-group/accordion.js +62 -0
- package/dist/components/sandbox/guardian/ui/markdown/accordion-group.js +23 -0
- package/dist/components/sandbox/guardian/ui/markdown/callout/callout-check.js +4 -0
- package/dist/components/sandbox/guardian/ui/markdown/callout/callout-error.js +4 -0
- package/dist/components/sandbox/guardian/ui/markdown/callout/callout-info.js +4 -0
- package/dist/components/sandbox/guardian/ui/markdown/callout/callout-note.js +4 -0
- package/dist/components/sandbox/guardian/ui/markdown/callout/callout-tip.js +4 -0
- package/dist/components/sandbox/guardian/ui/markdown/callout/callout-warning.js +4 -0
- package/dist/components/sandbox/guardian/ui/markdown/callout/shared/callout.js +9 -0
- package/dist/components/sandbox/guardian/ui/markdown/callout/shared/types.js +1 -0
- package/dist/components/sandbox/guardian/ui/markdown/card-group/card.js +18 -0
- package/dist/components/sandbox/guardian/ui/markdown/card-group.js +25 -0
- package/dist/components/sandbox/guardian/ui/markdown/code-group/code-block.js +54 -0
- package/dist/components/sandbox/guardian/ui/markdown/code-group.js +101 -0
- package/dist/components/sandbox/guardian/ui/markdown/icon.js +31 -0
- package/dist/components/sandbox/guardian/ui/markdown.js +791 -0
- package/dist/components/sandbox/guardian/ui/popover.js +25 -0
- package/dist/components/sandbox/guardian/ui/tooltip.js +25 -0
- package/dist/components/sandbox/guardian/utils.js +89 -0
- package/dist/components/sandbox/guardian/zip-to-codebase.js +259 -0
- package/dist/components/sandbox/guardian/zip-to-filetree.js +284 -0
- package/dist/components/sandbox/index.js +4 -0
- package/dist/components/sandbox/sandbox-control-bar.js +91 -0
- package/dist/components/sandbox/sandbox-header.js +52 -0
- package/dist/components/sandbox/sandbox-home/SandboxCard.js +65 -0
- package/dist/components/sandbox/sandbox-home/SandboxHome.js +115 -0
- package/dist/components/sandbox/sandbox-home/SearchBar.js +12 -0
- package/dist/components/sandbox/sandbox-home/index.js +3 -0
- package/dist/components/sandbox/sandbox-left-panel.js +248 -0
- package/dist/components/sandbox/sandbox-loading.js +48 -0
- package/dist/components/sandbox/sandbox-right-panel.js +247 -0
- package/dist/components/sandbox/types.js +1 -0
- package/dist/components/sandbox.js +32 -0
- package/dist/components/tailwind-example.js +46 -0
- package/dist/components/ui/skeleton.js +18 -0
- package/dist/index.d.ts +32 -103
- package/dist/index.es.js +90529 -423
- package/dist/index.js +13 -2
- package/dist/index.standalone.js +61 -53
- package/dist/index.standalone.umd.js +17 -24
- package/dist/lib/api-client.example.js +60 -0
- package/dist/lib/api-client.js +140 -0
- package/dist/lib/generated-css.js +4 -0
- package/dist/lib/inject-styles.js +42 -0
- package/dist/lib/shadow-dom-wrapper.js +42 -0
- package/dist/lib/utils.js +5 -0
- package/dist/sdk.css +1 -1
- package/dist/tailwind.css +1 -0
- package/package.json +41 -9
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback } from "react";
|
|
3
|
+
import { useVmContext } from "../context/vm-context";
|
|
4
|
+
import { createApiClient } from "../../../../lib/api-client";
|
|
5
|
+
/**
|
|
6
|
+
* Creates a cache key from sandboxUid and browserUrl.
|
|
7
|
+
* This allows caching different URLs for the same sandbox.
|
|
8
|
+
*/
|
|
9
|
+
function getCacheKey(sandboxUid, browserUrl) {
|
|
10
|
+
return `${sandboxUid}::${browserUrl}`;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Hook that abstracts the logic for loading sandbox URLs.
|
|
14
|
+
* Returns functions to load URLs and check if they're already loaded.
|
|
15
|
+
*/
|
|
16
|
+
export function useSandboxUrlLoader(startSandboxConfig) {
|
|
17
|
+
const { setVmUrl, getVmUrl, setVmError, hasError } = useVmContext();
|
|
18
|
+
/**
|
|
19
|
+
* Loads a sandbox URL. If useVm is true, fetches from the API.
|
|
20
|
+
* Otherwise, uses the browserUrl directly.
|
|
21
|
+
* Returns a promise that resolves to the final URL, or throws if loading fails.
|
|
22
|
+
*
|
|
23
|
+
* Caches by sandboxUid + browserUrl to support multiple URLs per sandbox.
|
|
24
|
+
*/
|
|
25
|
+
const loadSandboxUrl = useCallback(async (config) => {
|
|
26
|
+
const { sandboxUid, browserUrl, useVm } = config;
|
|
27
|
+
const cacheKey = getCacheKey(sandboxUid, browserUrl);
|
|
28
|
+
// Check if URL is already loaded for this specific browserUrl
|
|
29
|
+
const existingUrl = getVmUrl(cacheKey);
|
|
30
|
+
if (existingUrl !== undefined) {
|
|
31
|
+
return existingUrl;
|
|
32
|
+
}
|
|
33
|
+
// Check if we've already attempted and failed for this specific browserUrl
|
|
34
|
+
if (hasError(cacheKey)) {
|
|
35
|
+
throw new Error(`Previously failed to load sandbox URL for ${sandboxUid} with ${browserUrl}`);
|
|
36
|
+
}
|
|
37
|
+
if (useVm) {
|
|
38
|
+
try {
|
|
39
|
+
// Fetch VM URL from API
|
|
40
|
+
// Note: This assumes the API endpoint is available at /api/auto-sandbox
|
|
41
|
+
// You may need to configure this based on your deployment
|
|
42
|
+
const apiBaseUrl = typeof window !== "undefined" ? window.location.origin : "";
|
|
43
|
+
const response = await fetch(`${apiBaseUrl}/api/auto-sandbox?url=${encodeURIComponent(browserUrl)}&mode=json`);
|
|
44
|
+
// Check if response is successful
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
const errorText = await response.text();
|
|
47
|
+
console.error(`Failed to fetch sandbox URL for ${sandboxUid}:`, errorText);
|
|
48
|
+
// Mark as error to prevent infinite retries
|
|
49
|
+
setVmError(cacheKey);
|
|
50
|
+
throw new Error(`Failed to create sandbox: ${response.status} ${response.statusText}`);
|
|
51
|
+
}
|
|
52
|
+
const data = await response.json();
|
|
53
|
+
if (!data.vncUrl) {
|
|
54
|
+
console.error(`No vncUrl in response for ${sandboxUid}:`, data);
|
|
55
|
+
setVmError(cacheKey);
|
|
56
|
+
throw new Error("No vncUrl in sandbox response");
|
|
57
|
+
}
|
|
58
|
+
const vncUrl = data.vncUrl;
|
|
59
|
+
setVmUrl(cacheKey, vncUrl);
|
|
60
|
+
return vncUrl;
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
// Mark as error to prevent infinite retries
|
|
64
|
+
setVmError(cacheKey);
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// Non-VM mode: call startSandbox API to get container URL
|
|
70
|
+
if (startSandboxConfig) {
|
|
71
|
+
try {
|
|
72
|
+
const client = createApiClient({
|
|
73
|
+
apiKey: startSandboxConfig.apiKey,
|
|
74
|
+
});
|
|
75
|
+
const response = await client.sdk.startSandbox({
|
|
76
|
+
env: startSandboxConfig.env,
|
|
77
|
+
apiKey: startSandboxConfig.apiKey,
|
|
78
|
+
chatUid: startSandboxConfig.chatUid,
|
|
79
|
+
});
|
|
80
|
+
// Use the container_url from the response as the browserUrl
|
|
81
|
+
const containerUrl = response.container_url;
|
|
82
|
+
setVmUrl(cacheKey, containerUrl);
|
|
83
|
+
return containerUrl;
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
// Mark as error to prevent infinite retries
|
|
87
|
+
setVmError(cacheKey);
|
|
88
|
+
console.error("Failed to start sandbox:", error);
|
|
89
|
+
throw new Error(`Failed to start sandbox: ${error instanceof Error ? error.message : String(error)}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
// Fallback: Use browserUrl directly if startSandboxConfig is not provided
|
|
94
|
+
setVmUrl(cacheKey, browserUrl);
|
|
95
|
+
return browserUrl;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}, [getVmUrl, setVmUrl, hasError, setVmError, startSandboxConfig]);
|
|
99
|
+
/**
|
|
100
|
+
* Gets the URL for a sandbox + browserUrl combination if it's already loaded.
|
|
101
|
+
* Returns undefined if not loaded.
|
|
102
|
+
*/
|
|
103
|
+
const getSandboxUrl = useCallback((sandboxUid, browserUrl) => {
|
|
104
|
+
const cacheKey = getCacheKey(sandboxUid, browserUrl);
|
|
105
|
+
return getVmUrl(cacheKey);
|
|
106
|
+
}, [getVmUrl]);
|
|
107
|
+
/**
|
|
108
|
+
* Preloads URLs for multiple sandboxes in parallel.
|
|
109
|
+
* Errors are logged but don't stop other URLs from loading.
|
|
110
|
+
*/
|
|
111
|
+
const preloadSandboxUrls = useCallback(async (configs) => {
|
|
112
|
+
// Filter out configs that are already loaded or have errors
|
|
113
|
+
const configsToLoad = configs.filter((config) => {
|
|
114
|
+
const cacheKey = getCacheKey(config.sandboxUid, config.browserUrl);
|
|
115
|
+
return getVmUrl(cacheKey) === undefined && !hasError(cacheKey);
|
|
116
|
+
});
|
|
117
|
+
// Load all URLs in parallel, but catch errors individually
|
|
118
|
+
await Promise.allSettled(configsToLoad.map((config) => loadSandboxUrl(config)));
|
|
119
|
+
}, [getVmUrl, loadSandboxUrl, hasError]);
|
|
120
|
+
return {
|
|
121
|
+
loadSandboxUrl,
|
|
122
|
+
getSandboxUrl,
|
|
123
|
+
preloadSandboxUrls,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { useGuardianContext } from "../context/guardian-context";
|
|
3
|
+
import { ChevronLeft, ChevronRight, RefreshCw, Lock, AlertTriangle, Maximize2, Minimize2, ExternalLink, MoreVertical, } from "lucide-react";
|
|
4
|
+
import { cn } from "../../../../lib/utils";
|
|
5
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "../ui/dropdown-menu";
|
|
6
|
+
export default function Browser({ previewUrl, setPreviewUrl, containerEndpoint, outerContainerClassName, children, reloadSignal, useVm, isGuardian = false, }) {
|
|
7
|
+
const { isBrowserMaximized, setIsBrowserMaximized } = useGuardianContext();
|
|
8
|
+
const iframeRef = useRef(null);
|
|
9
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
10
|
+
const [error, setError] = useState(null);
|
|
11
|
+
const historyRef = useRef([]);
|
|
12
|
+
const historyIndexRef = useRef(-1);
|
|
13
|
+
const suppressHistoryPushRef = useRef(false);
|
|
14
|
+
const suppressIframeSrcUpdateRef = useRef(false);
|
|
15
|
+
const [historyIndex, setHistoryIndex] = useState(-1);
|
|
16
|
+
const [historyLength, setHistoryLength] = useState(0);
|
|
17
|
+
const [urlInput, setUrlInput] = useState("");
|
|
18
|
+
const [iframeSrc, setIframeSrc] = useState("");
|
|
19
|
+
const isIgnorableUrl = useCallback((href) => {
|
|
20
|
+
if (!href)
|
|
21
|
+
return true;
|
|
22
|
+
const value = href.toString();
|
|
23
|
+
return value === "about:blank" || value.startsWith("about:");
|
|
24
|
+
}, []);
|
|
25
|
+
/**
|
|
26
|
+
* Convert displayed URL back to original format. For dev mode you
|
|
27
|
+
* can just show the containerEndpoint if you prefer.
|
|
28
|
+
*/
|
|
29
|
+
const getDisplayUrl = (proxyUrl) => {
|
|
30
|
+
try {
|
|
31
|
+
const currentUrl = new URL(proxyUrl);
|
|
32
|
+
// Return just the pathname
|
|
33
|
+
return currentUrl.pathname || "/";
|
|
34
|
+
}
|
|
35
|
+
catch (_a) {
|
|
36
|
+
return proxyUrl;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const normalizeUrl = useCallback((href) => {
|
|
40
|
+
try {
|
|
41
|
+
const u = new URL(href, window.location.href);
|
|
42
|
+
u.searchParams.delete("_ts");
|
|
43
|
+
return u.toString();
|
|
44
|
+
}
|
|
45
|
+
catch (_a) {
|
|
46
|
+
// Best-effort removal of _ts from plain strings
|
|
47
|
+
const withoutTs = href.replace(/([?&])_ts=\d+(&|$)/, (m, p1, p2) => p2 === "&" ? p1 : "");
|
|
48
|
+
return withoutTs.replace(/[?&]$/, "");
|
|
49
|
+
}
|
|
50
|
+
}, []);
|
|
51
|
+
const updateHistoryState = useCallback(() => {
|
|
52
|
+
setHistoryIndex(historyIndexRef.current);
|
|
53
|
+
setHistoryLength(historyRef.current.length);
|
|
54
|
+
}, []);
|
|
55
|
+
const updateUrlInput = useCallback((href) => {
|
|
56
|
+
try {
|
|
57
|
+
const currentUrlObj = new URL(href, window.location.href);
|
|
58
|
+
setUrlInput(currentUrlObj.pathname || "/");
|
|
59
|
+
}
|
|
60
|
+
catch (_a) {
|
|
61
|
+
setUrlInput(href);
|
|
62
|
+
}
|
|
63
|
+
}, []);
|
|
64
|
+
const pushHistoryEntry = useCallback((href) => {
|
|
65
|
+
const sliceUntil = Math.min(historyRef.current.length, historyIndexRef.current + 1);
|
|
66
|
+
historyRef.current = [...historyRef.current.slice(0, sliceUntil), href];
|
|
67
|
+
historyIndexRef.current = historyRef.current.length - 1;
|
|
68
|
+
updateHistoryState();
|
|
69
|
+
}, [updateHistoryState]);
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (!previewUrl)
|
|
72
|
+
return;
|
|
73
|
+
// Skip iframe src update if we're syncing from a navigation message
|
|
74
|
+
// (to prevent reloading the iframe when it has already navigated internally)
|
|
75
|
+
if (!suppressIframeSrcUpdateRef.current) {
|
|
76
|
+
// Keep iframe src in sync with external previewUrl when it changes meaningfully
|
|
77
|
+
setIframeSrc((prev) => {
|
|
78
|
+
const prevNorm = prev ? normalizeUrl(prev) : "";
|
|
79
|
+
const nextNorm = normalizeUrl(previewUrl);
|
|
80
|
+
return prevNorm === nextNorm ? prev || previewUrl : previewUrl;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// Reset the flag after skipping the update
|
|
85
|
+
suppressIframeSrcUpdateRef.current = false;
|
|
86
|
+
}
|
|
87
|
+
updateUrlInput(previewUrl);
|
|
88
|
+
if (historyRef.current.length === 0) {
|
|
89
|
+
historyRef.current = [previewUrl];
|
|
90
|
+
historyIndexRef.current = 0;
|
|
91
|
+
updateHistoryState();
|
|
92
|
+
suppressHistoryPushRef.current = false;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (suppressHistoryPushRef.current) {
|
|
96
|
+
suppressHistoryPushRef.current = false;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const current = historyRef.current[historyIndexRef.current];
|
|
100
|
+
if (normalizeUrl(current) === normalizeUrl(previewUrl))
|
|
101
|
+
return;
|
|
102
|
+
pushHistoryEntry(normalizeUrl(previewUrl));
|
|
103
|
+
}, [
|
|
104
|
+
previewUrl,
|
|
105
|
+
updateUrlInput,
|
|
106
|
+
pushHistoryEntry,
|
|
107
|
+
updateHistoryState,
|
|
108
|
+
normalizeUrl,
|
|
109
|
+
]);
|
|
110
|
+
/**
|
|
111
|
+
* Force-reload the current iframe without mutating previewUrl.
|
|
112
|
+
* Tries same-origin reload first; falls back to cache-busting the src.
|
|
113
|
+
*/
|
|
114
|
+
const reloadIframe = () => {
|
|
115
|
+
var _a;
|
|
116
|
+
if (!previewUrl)
|
|
117
|
+
return;
|
|
118
|
+
try {
|
|
119
|
+
const base = ((_a = iframeRef.current) === null || _a === void 0 ? void 0 : _a.src) || iframeSrc || previewUrl;
|
|
120
|
+
const url = new URL(base, window.location.href);
|
|
121
|
+
url.searchParams.set("_ts", Date.now().toString());
|
|
122
|
+
setIframeSrc(url.toString());
|
|
123
|
+
}
|
|
124
|
+
catch (_b) {
|
|
125
|
+
try {
|
|
126
|
+
const url = new URL(previewUrl, window.location.href);
|
|
127
|
+
url.searchParams.set("_ts", Date.now().toString());
|
|
128
|
+
setIframeSrc(url.toString());
|
|
129
|
+
}
|
|
130
|
+
catch (_c) {
|
|
131
|
+
setIframeSrc(previewUrl);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
const getFrameWindow = () => {
|
|
136
|
+
const iframe = iframeRef.current;
|
|
137
|
+
if (!iframe)
|
|
138
|
+
return null;
|
|
139
|
+
try {
|
|
140
|
+
return iframe.contentWindow;
|
|
141
|
+
}
|
|
142
|
+
catch (_a) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
const getFrameHref = useCallback(() => {
|
|
147
|
+
var _a, _b;
|
|
148
|
+
const iframe = iframeRef.current;
|
|
149
|
+
if (!iframe)
|
|
150
|
+
return null;
|
|
151
|
+
try {
|
|
152
|
+
const href = (_b = (_a = iframe.contentWindow) === null || _a === void 0 ? void 0 : _a.location) === null || _b === void 0 ? void 0 : _b.href;
|
|
153
|
+
if (href && !isIgnorableUrl(href))
|
|
154
|
+
return href;
|
|
155
|
+
}
|
|
156
|
+
catch (_c) {
|
|
157
|
+
// Ignore cross-origin errors; fall back to iframe.src below.
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
// For cross-origin iframes, we can't read location.href, so use iframe.src
|
|
161
|
+
// But iframe.src might not reflect client-side navigation, so we also check
|
|
162
|
+
// if we have a more recent previewUrl
|
|
163
|
+
const src = iframe.src;
|
|
164
|
+
if (src && !isIgnorableUrl(src)) {
|
|
165
|
+
// If previewUrl is more recent and different, prefer it
|
|
166
|
+
if (previewUrl && normalizeUrl(previewUrl) !== normalizeUrl(src)) {
|
|
167
|
+
return previewUrl;
|
|
168
|
+
}
|
|
169
|
+
return src;
|
|
170
|
+
}
|
|
171
|
+
// Fallback to previewUrl if iframe.src is not available
|
|
172
|
+
if (previewUrl && !isIgnorableUrl(previewUrl)) {
|
|
173
|
+
return previewUrl;
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
catch (_d) {
|
|
178
|
+
// Final fallback to previewUrl
|
|
179
|
+
if (previewUrl && !isIgnorableUrl(previewUrl)) {
|
|
180
|
+
return previewUrl;
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
}, [previewUrl, normalizeUrl, isIgnorableUrl]);
|
|
185
|
+
/**
|
|
186
|
+
* Synchronize state from the iframe's current location (when accessible).
|
|
187
|
+
*/
|
|
188
|
+
const syncFromIframe = useCallback(() => {
|
|
189
|
+
const currentHref = getFrameHref();
|
|
190
|
+
if (!currentHref || isIgnorableUrl(currentHref)) {
|
|
191
|
+
// If we can't get the href, at least try to update from previewUrl
|
|
192
|
+
if (previewUrl && !isIgnorableUrl(previewUrl)) {
|
|
193
|
+
updateUrlInput(previewUrl);
|
|
194
|
+
}
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
const currentUrlObj = new URL(currentHref, window.location.href);
|
|
199
|
+
const pathOnly = currentUrlObj.pathname || "/";
|
|
200
|
+
setUrlInput(pathOnly);
|
|
201
|
+
}
|
|
202
|
+
catch (_a) {
|
|
203
|
+
// If URL parsing fails, try to extract path from the string
|
|
204
|
+
try {
|
|
205
|
+
const pathMatch = currentHref.match(/\/\/[^\/]+(\/.*?)(?:\?|$)/);
|
|
206
|
+
if (pathMatch && pathMatch[1]) {
|
|
207
|
+
setUrlInput(pathMatch[1]);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
setUrlInput(currentHref);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
catch (_b) {
|
|
214
|
+
setUrlInput(currentHref);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const normalizedCurrent = normalizeUrl(currentHref);
|
|
218
|
+
if (!suppressHistoryPushRef.current) {
|
|
219
|
+
const currentFlowLength = historyRef.current.length;
|
|
220
|
+
const sliceUntil = Math.min(currentFlowLength, historyIndexRef.current + 1);
|
|
221
|
+
historyRef.current = [
|
|
222
|
+
...historyRef.current.slice(0, sliceUntil),
|
|
223
|
+
normalizedCurrent,
|
|
224
|
+
];
|
|
225
|
+
historyIndexRef.current = historyRef.current.length - 1;
|
|
226
|
+
setHistoryIndex(historyIndexRef.current);
|
|
227
|
+
setHistoryLength(historyRef.current.length);
|
|
228
|
+
}
|
|
229
|
+
// Only propagate meaningful navigations (ignore cache-busting changes)
|
|
230
|
+
if (normalizeUrl(previewUrl) !== normalizedCurrent) {
|
|
231
|
+
setPreviewUrl(normalizedCurrent);
|
|
232
|
+
}
|
|
233
|
+
}, [
|
|
234
|
+
previewUrl,
|
|
235
|
+
normalizeUrl,
|
|
236
|
+
updateUrlInput,
|
|
237
|
+
setPreviewUrl,
|
|
238
|
+
getFrameHref,
|
|
239
|
+
isIgnorableUrl,
|
|
240
|
+
]);
|
|
241
|
+
/**
|
|
242
|
+
* Install navigation listeners in the iframe (same-origin only) so SPA-style
|
|
243
|
+
* navigations update our UI without a full reload.
|
|
244
|
+
*/
|
|
245
|
+
const attachIframeNavigationSync = () => {
|
|
246
|
+
const win = getFrameWindow();
|
|
247
|
+
if (!win)
|
|
248
|
+
return;
|
|
249
|
+
// Same-origin guard: any property access on cross-origin windows throws.
|
|
250
|
+
// If we can't read location.href, bail out and avoid installing listeners.
|
|
251
|
+
try {
|
|
252
|
+
void win.location.href;
|
|
253
|
+
}
|
|
254
|
+
catch (_a) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const notify = () => {
|
|
258
|
+
// Delay slightly to allow location to settle
|
|
259
|
+
setTimeout(() => syncFromIframe(), 0);
|
|
260
|
+
};
|
|
261
|
+
if (win.__saNavSyncInstalled)
|
|
262
|
+
return;
|
|
263
|
+
win.addEventListener("hashchange", notify);
|
|
264
|
+
win.addEventListener("popstate", notify);
|
|
265
|
+
try {
|
|
266
|
+
const wrapHistoryMethod = (method) => {
|
|
267
|
+
const original = win.history[method];
|
|
268
|
+
if (typeof original !== "function")
|
|
269
|
+
return;
|
|
270
|
+
win.history[method] = ((...args) => {
|
|
271
|
+
const result = original.apply(win.history, args);
|
|
272
|
+
win.dispatchEvent(new Event("sa-locationchange"));
|
|
273
|
+
return result;
|
|
274
|
+
});
|
|
275
|
+
};
|
|
276
|
+
wrapHistoryMethod("pushState");
|
|
277
|
+
wrapHistoryMethod("replaceState");
|
|
278
|
+
win.addEventListener("sa-locationchange", notify);
|
|
279
|
+
}
|
|
280
|
+
catch (_b) {
|
|
281
|
+
// If we cannot patch history, rely on popstate/hashchange only
|
|
282
|
+
}
|
|
283
|
+
win.__saNavSyncInstalled = true;
|
|
284
|
+
};
|
|
285
|
+
// Trigger iframe reload when external signal changes
|
|
286
|
+
useEffect(() => {
|
|
287
|
+
if (typeof reloadSignal === "number") {
|
|
288
|
+
reloadIframe();
|
|
289
|
+
}
|
|
290
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
291
|
+
}, [reloadSignal]);
|
|
292
|
+
// Listen for navigation messages from the iframe
|
|
293
|
+
useEffect(() => {
|
|
294
|
+
const handleMessage = (event) => {
|
|
295
|
+
var _a, _b;
|
|
296
|
+
// Only accept messages from the same origin or trusted origins
|
|
297
|
+
if (event.origin !== window.location.origin &&
|
|
298
|
+
!event.origin.endsWith("sampleapp.love")) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
// Handle navigation updates from the iframe
|
|
302
|
+
if (((_a = event.data) === null || _a === void 0 ? void 0 : _a.type) === "NAVIGATION_UPDATE" && ((_b = event.data) === null || _b === void 0 ? void 0 : _b.url)) {
|
|
303
|
+
const newUrl = event.data.url;
|
|
304
|
+
const normalized = normalizeUrl(newUrl);
|
|
305
|
+
// Update the URL display
|
|
306
|
+
updateUrlInput(newUrl);
|
|
307
|
+
// Update history without triggering iframe src change
|
|
308
|
+
const current = historyRef.current[historyIndexRef.current];
|
|
309
|
+
if (normalizeUrl(current) !== normalized) {
|
|
310
|
+
suppressHistoryPushRef.current = true;
|
|
311
|
+
pushHistoryEntry(normalized);
|
|
312
|
+
}
|
|
313
|
+
// Update previewUrl so syncFromIframe() can use the correct value
|
|
314
|
+
// Use a flag to prevent the useEffect from updating iframe src (which would cause reload)
|
|
315
|
+
if (normalizeUrl(previewUrl) !== normalized) {
|
|
316
|
+
suppressIframeSrcUpdateRef.current = true;
|
|
317
|
+
setPreviewUrl(normalized);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
window.addEventListener("message", handleMessage);
|
|
322
|
+
return () => {
|
|
323
|
+
window.removeEventListener("message", handleMessage);
|
|
324
|
+
};
|
|
325
|
+
}, [
|
|
326
|
+
normalizeUrl,
|
|
327
|
+
updateUrlInput,
|
|
328
|
+
pushHistoryEntry,
|
|
329
|
+
previewUrl,
|
|
330
|
+
setPreviewUrl,
|
|
331
|
+
]);
|
|
332
|
+
/**
|
|
333
|
+
* Navigation logic
|
|
334
|
+
*/
|
|
335
|
+
const handleNavigate = (url) => {
|
|
336
|
+
try {
|
|
337
|
+
let processedUrl;
|
|
338
|
+
const trimmedUrl = url.trim();
|
|
339
|
+
// If the input is blank, navigate to root path
|
|
340
|
+
if (trimmedUrl === "" ||
|
|
341
|
+
trimmedUrl.includes("https://") ||
|
|
342
|
+
trimmedUrl.includes("http://") ||
|
|
343
|
+
trimmedUrl.includes("localhost")) {
|
|
344
|
+
const baseUrl = containerEndpoint || "";
|
|
345
|
+
try {
|
|
346
|
+
const baseUrlObj = new URL(baseUrl);
|
|
347
|
+
processedUrl = `${baseUrlObj.origin}/`;
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
console.error("Error creating URL with base:", baseUrl, error);
|
|
351
|
+
setError(`Invalid base URL: ${baseUrl}`);
|
|
352
|
+
setIsLoading(false);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
// If it starts with /api/browser, use as is
|
|
357
|
+
else if (trimmedUrl.startsWith("/api/browser")) {
|
|
358
|
+
processedUrl = trimmedUrl;
|
|
359
|
+
}
|
|
360
|
+
// If it's a relative path (starts with /)
|
|
361
|
+
else if (trimmedUrl.startsWith("/")) {
|
|
362
|
+
const baseUrl = containerEndpoint || "";
|
|
363
|
+
try {
|
|
364
|
+
const baseUrlObj = new URL(baseUrl);
|
|
365
|
+
processedUrl = `${baseUrlObj.origin}${trimmedUrl}`;
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
console.error("Error creating URL with base:", baseUrl, error);
|
|
369
|
+
setError(`Invalid base URL: ${baseUrl}`);
|
|
370
|
+
setIsLoading(false);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
// Otherwise, treat as external URL
|
|
375
|
+
else {
|
|
376
|
+
const baseUrl = containerEndpoint || "";
|
|
377
|
+
try {
|
|
378
|
+
const baseUrlObj = new URL(baseUrl);
|
|
379
|
+
processedUrl = `${baseUrlObj.origin}/`;
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
console.error("Error creating URL with base:", baseUrl, error);
|
|
383
|
+
setError(`Invalid base URL: ${baseUrl}`);
|
|
384
|
+
setIsLoading(false);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
setIsLoading(true);
|
|
389
|
+
setError(null);
|
|
390
|
+
const newHistory = historyRef.current.slice(0, historyIndex + 1);
|
|
391
|
+
newHistory.push(processedUrl);
|
|
392
|
+
historyRef.current = newHistory;
|
|
393
|
+
setHistoryIndex(newHistory.length - 1);
|
|
394
|
+
// Check if we're navigating to the same URL (ignoring cache-busting)
|
|
395
|
+
const compareAgainst = iframeSrc || previewUrl;
|
|
396
|
+
const isSameUrl = normalizeUrl(processedUrl) === normalizeUrl(compareAgainst);
|
|
397
|
+
// Update the URL input display right away
|
|
398
|
+
setUrlInput(getDisplayUrl(processedUrl));
|
|
399
|
+
// Force a refresh if navigating to the same URL
|
|
400
|
+
if (isSameUrl) {
|
|
401
|
+
setIsLoading(true);
|
|
402
|
+
setError(null);
|
|
403
|
+
reloadIframe();
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
// Normal navigation for different URLs
|
|
407
|
+
setPreviewUrl(processedUrl);
|
|
408
|
+
}
|
|
409
|
+
// Set a timeout to reset loading state if it takes too long
|
|
410
|
+
setTimeout(() => {
|
|
411
|
+
if (isLoading) {
|
|
412
|
+
setIsLoading(false);
|
|
413
|
+
}
|
|
414
|
+
}, 10000);
|
|
415
|
+
}
|
|
416
|
+
catch (error) {
|
|
417
|
+
console.error("Navigation error:", error);
|
|
418
|
+
setError(`Invalid URL: ${error.message || "Unknown error"}`);
|
|
419
|
+
setIsLoading(false);
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
const handleBack = () => {
|
|
423
|
+
var _a, _b;
|
|
424
|
+
setIsLoading(true);
|
|
425
|
+
// Try to use iframe's own history for same-origin navigations
|
|
426
|
+
try {
|
|
427
|
+
(_b = (_a = iframeRef.current) === null || _a === void 0 ? void 0 : _a.contentWindow) === null || _b === void 0 ? void 0 : _b.history.back();
|
|
428
|
+
// onLoad will sync state when navigation completes
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
catch (_c) {
|
|
432
|
+
// Fallback to our own tracked history
|
|
433
|
+
}
|
|
434
|
+
if (historyIndex > 0) {
|
|
435
|
+
const newIndex = historyIndex - 1;
|
|
436
|
+
setHistoryIndex(newIndex);
|
|
437
|
+
const previousUrl = historyRef.current[newIndex];
|
|
438
|
+
setPreviewUrl(previousUrl);
|
|
439
|
+
setUrlInput(getDisplayUrl(previousUrl));
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
setIsLoading(false);
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
const handleForward = () => {
|
|
446
|
+
var _a, _b;
|
|
447
|
+
setIsLoading(true);
|
|
448
|
+
// Try to use iframe's own history for same-origin navigations
|
|
449
|
+
try {
|
|
450
|
+
(_b = (_a = iframeRef.current) === null || _a === void 0 ? void 0 : _a.contentWindow) === null || _b === void 0 ? void 0 : _b.history.forward();
|
|
451
|
+
// onLoad will sync state when navigation completes
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
catch (_c) {
|
|
455
|
+
// Fallback to our own tracked history
|
|
456
|
+
}
|
|
457
|
+
if (historyIndex < historyRef.current.length - 1) {
|
|
458
|
+
const newIndex = historyIndex + 1;
|
|
459
|
+
setHistoryIndex(newIndex);
|
|
460
|
+
const nextUrl = historyRef.current[newIndex];
|
|
461
|
+
setPreviewUrl(nextUrl);
|
|
462
|
+
setUrlInput(getDisplayUrl(nextUrl));
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
setIsLoading(false);
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
const handleRefresh = () => {
|
|
469
|
+
if (previewUrl) {
|
|
470
|
+
setIsLoading(true);
|
|
471
|
+
setError(null);
|
|
472
|
+
reloadIframe();
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
const handleUrlChange = (e) => {
|
|
476
|
+
if (e.key === "Enter") {
|
|
477
|
+
handleNavigate(urlInput);
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
// Render the iframe directly with built-in event handlers
|
|
481
|
+
const renderIframe = () => {
|
|
482
|
+
if (!previewUrl && !iframeSrc)
|
|
483
|
+
return null;
|
|
484
|
+
return (React.createElement("iframe", { ref: iframeRef, src: iframeSrc || previewUrl, className: cn("h-full w-full border-none", outerContainerClassName), sandbox: "allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-modals allow-downloads allow-top-navigation", allow: "fullscreen; payment; geolocation; microphone; camera", onLoad: () => {
|
|
485
|
+
setIsLoading(false);
|
|
486
|
+
setError(null);
|
|
487
|
+
attachIframeNavigationSync();
|
|
488
|
+
syncFromIframe();
|
|
489
|
+
}, onError: () => {
|
|
490
|
+
console.error("Iframe failed to load");
|
|
491
|
+
setIsLoading(false);
|
|
492
|
+
setError("Failed to load page");
|
|
493
|
+
} }));
|
|
494
|
+
};
|
|
495
|
+
return (React.createElement("div", { className: "flex h-full w-full flex-col" },
|
|
496
|
+
!useVm && !isGuardian && (React.createElement("div", { className: "bg-white dark:bg-black border-b border-zinc-200 dark:border-zinc-800 flex-shrink-0" },
|
|
497
|
+
React.createElement("div", { className: "flex items-center gap-1.5 px-2 py-1.5 h-[2.38rem]" },
|
|
498
|
+
React.createElement("div", { className: "flex gap-0.5" },
|
|
499
|
+
React.createElement("button", { onClick: handleBack, disabled: historyIndex === 0, className: "p-1 hover:bg-zinc-100 dark:hover:bg-zinc-900 rounded disabled:opacity-30 disabled:hover:bg-transparent transition-colors", title: "Back" },
|
|
500
|
+
React.createElement(ChevronLeft, { size: 14, className: "text-zinc-600 dark:text-zinc-400" })),
|
|
501
|
+
React.createElement("button", { onClick: handleForward, disabled: historyIndex === historyLength - 1, className: "p-1 hover:bg-zinc-100 dark:hover:bg-zinc-900 rounded disabled:opacity-30 disabled:hover:bg-transparent transition-colors", title: "Forward" },
|
|
502
|
+
React.createElement(ChevronRight, { size: 14, className: "text-zinc-600 dark:text-zinc-400" })),
|
|
503
|
+
React.createElement("button", { onClick: handleRefresh, className: "p-1 hover:bg-zinc-100 dark:hover:bg-zinc-900 rounded transition-colors", title: "Refresh" },
|
|
504
|
+
React.createElement(RefreshCw, { size: 14, className: `text-zinc-600 dark:text-zinc-400 ${isLoading ? "animate-spin" : ""}` }))),
|
|
505
|
+
React.createElement("div", { className: "flex-1 flex items-center gap-1.5 px-2 py-1 bg-zinc-50 dark:bg-zinc-950 rounded text-zinc-600 dark:text-zinc-400 focus-within:bg-white dark:focus-within:bg-zinc-900 transition-colors" },
|
|
506
|
+
error ? (React.createElement(AlertTriangle, { size: 12, className: "text-red-500 flex-shrink-0" })) : (React.createElement(Lock, { size: 12, className: "text-zinc-400 dark:text-zinc-600 flex-shrink-0" })),
|
|
507
|
+
React.createElement("input", { type: "text",
|
|
508
|
+
// value={urlInput}
|
|
509
|
+
value: "/", onChange: (e) => setUrlInput(e.target.value), onKeyDown: handleUrlChange, className: "flex-1 text-xs border-none outline-none bg-transparent text-zinc-700 dark:text-zinc-300 placeholder:text-zinc-400 dark:placeholder:text-zinc-600", placeholder: "Enter URL" }),
|
|
510
|
+
isGuardian && (React.createElement("button", { onClick: () => setIsBrowserMaximized(!isBrowserMaximized), className: "p-1 hover:bg-zinc-100 dark:hover:bg-zinc-900 rounded transition-colors", title: isBrowserMaximized
|
|
511
|
+
? "Exit fullscreen"
|
|
512
|
+
: "Fullscreen preview" }, isBrowserMaximized ? (React.createElement(Minimize2, { size: 14, className: "text-zinc-600 dark:text-zinc-400" })) : (React.createElement(Maximize2, { size: 14, className: "text-zinc-600 dark:text-zinc-400" })))),
|
|
513
|
+
React.createElement("button", { onClick: () => {
|
|
514
|
+
window.open(previewUrl, "_blank", "noopener,noreferrer");
|
|
515
|
+
}, className: "ml-1 p-1 hover:bg-zinc-100 dark:hover:bg-zinc-900 rounded transition-colors", title: "Open in new tab", disabled: !urlInput || !!error, tabIndex: -1, type: "button" },
|
|
516
|
+
React.createElement(ExternalLink, { size: 14, className: "text-zinc-600 dark:text-zinc-400" })),
|
|
517
|
+
React.createElement(DropdownMenu, null,
|
|
518
|
+
React.createElement(DropdownMenuTrigger, { asChild: true },
|
|
519
|
+
React.createElement("button", { className: "ml-1 p-1 hover:bg-zinc-100 dark:hover:bg-zinc-900 rounded transition-colors", title: "More options", type: "button" },
|
|
520
|
+
React.createElement(MoreVertical, { size: 14, className: "text-zinc-600 dark:text-zinc-400" }))),
|
|
521
|
+
React.createElement(DropdownMenuContent, { align: "end", className: "bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800" },
|
|
522
|
+
React.createElement(DropdownMenuItem, { onClick: () => {
|
|
523
|
+
// Restart functionality - can be implemented if needed
|
|
524
|
+
console.log("Restart app");
|
|
525
|
+
}, className: "cursor-pointer text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800" }, "Restart App"))))))),
|
|
526
|
+
error && (React.createElement("div", { className: "p-4 bg-red-50 border-b border-red-100 text-red-700 text-sm dark:bg-red-950 dark:border-red-800 dark:text-red-200" },
|
|
527
|
+
React.createElement("div", { className: "flex items-center gap-2" },
|
|
528
|
+
React.createElement(AlertTriangle, { size: 16 }),
|
|
529
|
+
React.createElement("span", null, error)))),
|
|
530
|
+
React.createElement("div", { className: "flex-1 min-h-0 relative" },
|
|
531
|
+
renderIframe(),
|
|
532
|
+
isLoading && (React.createElement("div", { className: "absolute inset-0 bg-zinc-100 dark:bg-zinc-800 bg-opacity-50 flex items-center justify-center z-10" },
|
|
533
|
+
React.createElement("div", { className: "text-sm text-zinc-600 bg-white dark:bg-black p-3 rounded-lg shadow-md" },
|
|
534
|
+
"Loading ",
|
|
535
|
+
urlInput,
|
|
536
|
+
"..."))),
|
|
537
|
+
children)));
|
|
538
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { default as GuardianPlayground } from "./guardian-playground";
|
|
2
|
+
export { default as GuardianComponent } from "./guardian-component";
|
|
3
|
+
export { VmProvider, useVmContext } from "./context/vm-context";
|
|
4
|
+
export { GuardianProvider, useGuardianContext } from "./context/guardian-context";
|
|
5
|
+
export * from "./types";
|
|
6
|
+
export { default as CodeFocusSection } from "./code-focus-section";
|
|
7
|
+
export { buildGuardianConfig, createSandboxUrlConfigs } from "./utils";
|
|
8
|
+
export { Framework, FrameworkLabel } from "./types";
|