@sampleapp.ai/sdk 1.0.28 → 1.0.30
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/chat-bar.js +1 -2
- 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 +87 -0
- package/dist/components/sandbox/SandboxHome.js +141 -0
- package/dist/components/sandbox/api.js +108 -0
- package/dist/components/sandbox/guardian/app-layout-no-sidebar.js +8 -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 +58 -0
- package/dist/components/sandbox/guardian/guardian-component.js +97 -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 +33 -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 +145 -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 +233 -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 +38 -0
- package/dist/components/sandbox/guardian/right-view/right-top-down-view.js +289 -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 +48 -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/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 +87 -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 +786 -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 +88 -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 +59 -0
- package/dist/components/sandbox/sandbox-home/SandboxHome.js +174 -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/index.d.ts +336 -1
- package/dist/index.es.js +90151 -667
- package/dist/index.js +13 -2
- package/dist/index.standalone.js +61 -53
- package/dist/index.standalone.umd.js +17 -89
- package/dist/index.umd.js +86 -151
- package/dist/lib/api-client.example.js +60 -0
- package/dist/lib/api-client.js +98 -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 +32 -5
|
@@ -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";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { cn } from "../../../lib/utils";
|
|
3
|
+
export default function AppLayoutNoSidebar({ header, hasBodyPadding = true, children, bodyHeight = "h-[calc(100vh-6rem)]", }) {
|
|
4
|
+
return (React.createElement("div", null,
|
|
5
|
+
header && (React.createElement("header", { className: "flex h-24 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12 w-full" },
|
|
6
|
+
React.createElement("div", { className: "flex items-center gap-2 px-4 w-full" }, header))),
|
|
7
|
+
React.createElement("div", { className: cn("flex flex-1 flex-col pt-0", hasBodyPadding && "gap-4 p-4", bodyHeight) }, children)));
|
|
8
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React, { useState, useRef, useEffect } from "react";
|
|
3
|
+
import { Button } from "../../../../components/ui/button";
|
|
4
|
+
import { cn } from "../../../../lib/utils";
|
|
5
|
+
import { CheckIcon, ChevronsUpDown } from "lucide-react";
|
|
6
|
+
export default function GlassmorphicCombobox({ options, value, placeholder, className, searchPlaceholder, onSelect, }) {
|
|
7
|
+
var _a;
|
|
8
|
+
const [open, setOpen] = useState(false);
|
|
9
|
+
const [searchValue, setSearchValue] = useState("");
|
|
10
|
+
const containerRef = useRef(null);
|
|
11
|
+
const selectedLabel = (_a = options.find((o) => o.value === value)) === null || _a === void 0 ? void 0 : _a.label;
|
|
12
|
+
// Filter options based on search
|
|
13
|
+
const filteredOptions = options.filter((option) => option.label.toLowerCase().includes(searchValue.toLowerCase()));
|
|
14
|
+
// Close on outside click
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const handleClickOutside = (event) => {
|
|
17
|
+
if (containerRef.current &&
|
|
18
|
+
!containerRef.current.contains(event.target)) {
|
|
19
|
+
setOpen(false);
|
|
20
|
+
setSearchValue("");
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
if (open) {
|
|
24
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
25
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
26
|
+
}
|
|
27
|
+
}, [open]);
|
|
28
|
+
const handleSelect = (option) => {
|
|
29
|
+
if (option.href && typeof window !== "undefined") {
|
|
30
|
+
window.location.href = option.href;
|
|
31
|
+
}
|
|
32
|
+
else if (onSelect) {
|
|
33
|
+
onSelect(option.value);
|
|
34
|
+
}
|
|
35
|
+
setOpen(false);
|
|
36
|
+
setSearchValue("");
|
|
37
|
+
};
|
|
38
|
+
return (React.createElement("div", { ref: containerRef, className: "relative" },
|
|
39
|
+
React.createElement(Button, { variant: "outline", role: "combobox", "aria-expanded": open, onClick: () => setOpen(!open), className: cn("w-64 justify-between rounded-xl border-white/20 bg-white/10 backdrop-blur-md", "text-white/90 shadow-[inset_0_1px_0_0_rgba(255,255,255,0.25)] hover:bg-white/15", "data-[state=open]:bg-white/15", className) },
|
|
40
|
+
React.createElement("span", { className: "truncate min-w-0" }, selectedLabel !== null && selectedLabel !== void 0 ? selectedLabel : placeholder),
|
|
41
|
+
React.createElement(ChevronsUpDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-70" })),
|
|
42
|
+
open && (React.createElement("div", { className: cn("absolute top-full mt-1 w-64 p-0 rounded-xl border border-white/20 bg-black/40 backdrop-blur-xl", "shadow-xl z-50") },
|
|
43
|
+
React.createElement("div", { className: "p-2" },
|
|
44
|
+
React.createElement("input", { type: "text", placeholder: searchPlaceholder, value: searchValue, onChange: (e) => setSearchValue(e.target.value), className: "w-full px-3 py-2 rounded-md bg-transparent border border-white/20 text-white placeholder:text-white/60 focus:outline-none focus:border-white/40" })),
|
|
45
|
+
React.createElement("div", { className: "max-h-[300px] overflow-y-auto" }, filteredOptions.length === 0 ? (React.createElement("div", { className: "px-3 py-2 text-white/80 text-sm" }, "No results found.")) : (React.createElement("div", { className: "p-1" }, filteredOptions.map((option) => (React.createElement("div", { key: option.value, onClick: () => handleSelect(option), className: cn("flex items-center px-2 py-1.5 rounded-sm cursor-pointer text-sm", "hover:bg-white/10", value === option.value && "bg-white/10") },
|
|
46
|
+
React.createElement(CheckIcon, { className: cn("mr-2 h-4 w-4", value === option.value ? "opacity-100" : "opacity-0") }),
|
|
47
|
+
option.label))))))))));
|
|
48
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Badge } from "../ui/badge";
|
|
4
|
+
import GlassmorphicCombobox from "./header/glassmorphic-combobox";
|
|
5
|
+
export default function Header({ demoOptions, frameworkOptions, firstFrameworkByUseCase, currentFramework, playgroundLogo, currentUseCase, }) {
|
|
6
|
+
var _a, _b, _c, _d;
|
|
7
|
+
// Derive values directly from props (which come from the URL)
|
|
8
|
+
// No internal state - the URL is the source of truth
|
|
9
|
+
const selectedUseCase = currentUseCase || ((_a = demoOptions[0]) === null || _a === void 0 ? void 0 : _a.value);
|
|
10
|
+
const selectedFramework = currentFramework || ((_b = frameworkOptions[0]) === null || _b === void 0 ? void 0 : _b.value);
|
|
11
|
+
// Build options with hrefs
|
|
12
|
+
const demoOptionsWithHrefs = demoOptions.map((opt) => {
|
|
13
|
+
var _a;
|
|
14
|
+
return (Object.assign(Object.assign({}, opt), {
|
|
15
|
+
// Link to first available framework for this use case
|
|
16
|
+
href: `/${opt.value}?framework=${(firstFrameworkByUseCase === null || firstFrameworkByUseCase === void 0 ? void 0 : firstFrameworkByUseCase[opt.value]) || ((_a = frameworkOptions[0]) === null || _a === void 0 ? void 0 : _a.value)}` }));
|
|
17
|
+
});
|
|
18
|
+
const frameworkOptionsWithHrefs = frameworkOptions.map((opt) => (Object.assign(Object.assign({}, opt), { href: `/${selectedUseCase}?framework=${opt.value}` })));
|
|
19
|
+
// Derive environment metadata for badge styling and label
|
|
20
|
+
const environment = ((typeof window !== "undefined" &&
|
|
21
|
+
((_d = (_c = window.__NEXT_DATA__) === null || _c === void 0 ? void 0 : _c.env) === null || _d === void 0 ? void 0 : _d.NEXT_PUBLIC_ENVIRONMENT)) ||
|
|
22
|
+
process.env.NEXT_PUBLIC_ENVIRONMENT ||
|
|
23
|
+
process.env.NODE_ENV ||
|
|
24
|
+
"development").toLowerCase();
|
|
25
|
+
const envMeta = {
|
|
26
|
+
production: {
|
|
27
|
+
label: "Production",
|
|
28
|
+
badgeClass: "border-emerald-500/30 bg-emerald-500/10 text-emerald-200",
|
|
29
|
+
dotClass: "bg-emerald-400 shadow-[0_0_0_3px_rgba(16,185,129,0.25)]",
|
|
30
|
+
},
|
|
31
|
+
staging: {
|
|
32
|
+
label: "Staging",
|
|
33
|
+
badgeClass: "border-amber-500/30 bg-amber-500/10 text-amber-100",
|
|
34
|
+
dotClass: "bg-amber-400 shadow-[0_0_0_3px_rgba(245,158,11,0.25)]",
|
|
35
|
+
},
|
|
36
|
+
test: {
|
|
37
|
+
label: "Test",
|
|
38
|
+
badgeClass: "border-cyan-500/30 bg-cyan-500/10 text-cyan-100",
|
|
39
|
+
dotClass: "bg-cyan-400 shadow-[0_0_0_3px_rgba(34,211,238,0.25)]",
|
|
40
|
+
},
|
|
41
|
+
preview: {
|
|
42
|
+
label: "Preview",
|
|
43
|
+
badgeClass: "border-sky-500/30 bg-sky-500/10 text-sky-100",
|
|
44
|
+
dotClass: "bg-sky-400 shadow-[0_0_0_3px_rgba(56,189,248,0.25)]",
|
|
45
|
+
},
|
|
46
|
+
development: {
|
|
47
|
+
label: "Demo Environment",
|
|
48
|
+
badgeClass: "border-violet-500/30 bg-violet-500/10 text-violet-100",
|
|
49
|
+
dotClass: "bg-violet-400 shadow-[0_0_0_3px_rgba(139,92,246,0.25)]",
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
const meta = envMeta[environment] || envMeta.development;
|
|
53
|
+
return (React.createElement("div", { className: "grid grid-cols-[1fr_auto_1fr] items-center w-full ml-6" },
|
|
54
|
+
React.createElement("div", { className: "justify-self-start" }, playgroundLogo),
|
|
55
|
+
React.createElement("div", { className: "justify-self-center" },
|
|
56
|
+
React.createElement("div", { className: "flex gap-2" },
|
|
57
|
+
React.createElement(GlassmorphicCombobox, { options: demoOptionsWithHrefs, value: selectedUseCase, placeholder: "Select use case...", searchPlaceholder: "Search use case..." }),
|
|
58
|
+
React.createElement(GlassmorphicCombobox, { options: frameworkOptionsWithHrefs, value: selectedFramework, placeholder: "Select framework...", searchPlaceholder: "Search framework...", className: "w-32" }))),
|
|
59
|
+
React.createElement("div", { className: "justify-self-end mr-6" },
|
|
60
|
+
React.createElement(Badge, { className: `rounded-full px-5 py-3 flex items-center gap-2 border ${meta.badgeClass} backdrop-blur-sm shadow-[inset_0_1px_0_rgba(255,255,255,0.04)] text-sm`, "aria-label": `${meta.label} environment` },
|
|
61
|
+
React.createElement("span", { className: `inline-block h-2 w-2 rounded-full ${meta.dotClass}` }),
|
|
62
|
+
meta.label))));
|
|
63
|
+
}
|