@sampleapp.ai/sdk 1.0.36 → 1.0.38
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 +5 -0
- package/dist/components/sandbox/api.js +2 -2
- package/dist/components/sandbox/guardian/guardian-component.js +7 -38
- package/dist/components/sandbox/guardian/guardian-playground.js +2 -2
- package/dist/components/sandbox/guardian/hooks/use-sandbox-url-loader.js +12 -10
- package/dist/components/sandbox/guardian/index.js +1 -1
- package/dist/components/sandbox/guardian/right-view/right-top-down-view.js +2 -1
- package/dist/components/sandbox/guardian/ui/markdown.js +2 -2
- package/dist/components/sandbox/guardian/utils.js +4 -22
- package/dist/components/sandbox/index.js +1 -1
- package/dist/components/sandbox/sandbox-home/SandboxHome.js +5 -0
- package/dist/index.d.ts +8 -40
- package/dist/index.es.js +834 -870
- package/dist/index.js +1 -1
- package/dist/lib/api-client.js +9 -26
- package/package.json +1 -1
- package/dist/components/guardian/app-layout-no-sidebar.js +0 -8
- package/dist/components/guardian/ask-ai-view.js +0 -249
- package/dist/components/guardian/code-focus-section.d.ts +0 -41
- package/dist/components/guardian/code-focus-section.js +0 -174
- package/dist/components/guardian/context/guardian-context.js +0 -94
- package/dist/components/guardian/context/vm-context.js +0 -28
- package/dist/components/guardian/default-guide-view.js +0 -34
- package/dist/components/guardian/demo/guardian-demo.js +0 -35
- package/dist/components/guardian/demo/left-view/toggle.js +0 -28
- package/dist/components/guardian/demo/left-view.js +0 -49
- package/dist/components/guardian/guardian-component.js +0 -79
- package/dist/components/guardian/guardian-demo.js +0 -35
- package/dist/components/guardian/guardian-home.d.ts +0 -4
- package/dist/components/guardian/guardian-home.js +0 -61
- package/dist/components/guardian/guardian-playground.js +0 -45
- package/dist/components/guardian/guardian-style-wrapper.js +0 -29
- package/dist/components/guardian/guardian-upload-spec.d.ts +0 -14
- package/dist/components/guardian/guardian-upload-spec.js +0 -160
- package/dist/components/guardian/header/glassmorphic-combobox.d.ts +0 -15
- package/dist/components/guardian/header/glassmorphic-combobox.js +0 -30
- package/dist/components/guardian/header.js +0 -61
- package/dist/components/guardian/hooks/use-frame-messages.js +0 -65
- package/dist/components/guardian/hooks/use-frame-params.js +0 -44
- package/dist/components/guardian/hooks/use-sandbox-url-loader.js +0 -101
- package/dist/components/guardian/ide/browser.js +0 -538
- package/dist/components/guardian/index.js +0 -8
- package/dist/components/guardian/layout/app-layout-no-sidebar.js +0 -8
- package/dist/components/guardian/layout/header/glassmorphic-combobox.js +0 -48
- package/dist/components/guardian/layout/header.js +0 -63
- package/dist/components/guardian/right-view/code-view.js +0 -56
- package/dist/components/guardian/right-view/pill-file-selector.js +0 -233
- package/dist/components/guardian/right-view/preview-control-bar.js +0 -25
- package/dist/components/guardian/right-view/right-panel-view.js +0 -38
- package/dist/components/guardian/right-view/right-top-down-view.js +0 -289
- package/dist/components/guardian/right-view/right-view.js +0 -28
- package/dist/components/guardian/right-view/simplified-editor.js +0 -234
- package/dist/components/guardian/types/ide-types.js +0 -162
- package/dist/components/guardian/types.js +0 -3
- package/dist/components/guardian/ui/ai-loader.js +0 -48
- package/dist/components/guardian/ui/badge.js +0 -24
- package/dist/components/guardian/ui/button.js +0 -45
- package/dist/components/guardian/ui/command.js +0 -63
- package/dist/components/guardian/ui/console-with-app.js +0 -17
- package/dist/components/guardian/ui/dialog.js +0 -57
- package/dist/components/guardian/ui/dropdown-menu.js +0 -82
- package/dist/components/guardian/ui/markdown.js +0 -57
- package/dist/components/guardian/ui/popover.js +0 -25
- package/dist/components/guardian/ui/tooltip.js +0 -25
- package/dist/components/guardian/utils.js +0 -88
- package/dist/components/guardian/zip-to-codebase.js +0 -246
- package/dist/components/guardian/zip-to-filetree.js +0 -284
- package/dist/components/sandbox/SandboxHome.js +0 -141
- package/dist/components/sandbox/guardian/guardian-demo.js +0 -35
- package/dist/components/sandbox/guardian/guardian-home.d.ts +0 -4
- package/dist/components/sandbox/guardian/guardian-home.js +0 -61
- package/dist/components/sandbox/guardian/guardian-upload-spec.d.ts +0 -14
- package/dist/components/sandbox/guardian/guardian-upload-spec.js +0 -160
- package/dist/components/sandbox/guardian/ui/theme-color-context.d.ts +0 -6
- package/dist/components/sandbox/sandbox-control-bar.js +0 -91
- package/dist/components/sandbox/sandbox-header.js +0 -52
- package/dist/components/sandbox/sandbox-left-panel.js +0 -248
- package/dist/components/sandbox/sandbox-loading.js +0 -48
- package/dist/components/sandbox/sandbox-right-panel.js +0 -247
- package/dist/components/sandbox.js +0 -32
- package/dist/lib/api-client.example.js +0 -60
- package/dist/lib/ssr-safe-decode-entity.js +0 -16
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import React from "react";
|
|
3
|
-
"use client";
|
|
4
|
-
import { useState, useCallback } from "react";
|
|
5
|
-
import { Upload, FileText, X, Check, AlertCircle } from "lucide-react";
|
|
6
|
-
import { cn } from "../../lib/utils";
|
|
7
|
-
export default function GuardianUploadSpec({ onBack, onContinue, isLoading = false, }) {
|
|
8
|
-
const [uploadedFile, setUploadedFile] = useState(null);
|
|
9
|
-
const [isDragging, setIsDragging] = useState(false);
|
|
10
|
-
const [error, setError] = useState(null);
|
|
11
|
-
const [isValidating, setIsValidating] = useState(false);
|
|
12
|
-
const validateFile = (file) => {
|
|
13
|
-
// Check file size (max 10MB)
|
|
14
|
-
if (file.size > 10 * 1024 * 1024) {
|
|
15
|
-
return "File size must be less than 10MB";
|
|
16
|
-
}
|
|
17
|
-
// Check file type
|
|
18
|
-
const validTypes = [
|
|
19
|
-
"application/json",
|
|
20
|
-
"application/x-yaml",
|
|
21
|
-
"application/yaml",
|
|
22
|
-
"text/yaml",
|
|
23
|
-
"text/x-yaml",
|
|
24
|
-
"text/plain",
|
|
25
|
-
];
|
|
26
|
-
const validExtensions = [".json", ".yaml", ".yml"];
|
|
27
|
-
const hasValidType = validTypes.includes(file.type);
|
|
28
|
-
const hasValidExtension = validExtensions.some((ext) => file.name.toLowerCase().endsWith(ext));
|
|
29
|
-
if (!hasValidType && !hasValidExtension) {
|
|
30
|
-
return "Please upload a JSON or YAML file";
|
|
31
|
-
}
|
|
32
|
-
return null;
|
|
33
|
-
};
|
|
34
|
-
const handleFile = useCallback((file) => {
|
|
35
|
-
setError(null);
|
|
36
|
-
setIsValidating(true);
|
|
37
|
-
const validationError = validateFile(file);
|
|
38
|
-
if (validationError) {
|
|
39
|
-
setError(validationError);
|
|
40
|
-
setIsValidating(false);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
const reader = new FileReader();
|
|
44
|
-
reader.onload = (e) => {
|
|
45
|
-
var _a;
|
|
46
|
-
const content = (_a = e.target) === null || _a === void 0 ? void 0 : _a.result;
|
|
47
|
-
// Basic validation that it's valid JSON or YAML
|
|
48
|
-
try {
|
|
49
|
-
if (file.name.toLowerCase().endsWith(".json")) {
|
|
50
|
-
JSON.parse(content);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
catch (_b) {
|
|
54
|
-
setError("Invalid JSON file");
|
|
55
|
-
setIsValidating(false);
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
setUploadedFile({
|
|
59
|
-
name: file.name,
|
|
60
|
-
size: file.size,
|
|
61
|
-
type: file.type,
|
|
62
|
-
content,
|
|
63
|
-
});
|
|
64
|
-
setIsValidating(false);
|
|
65
|
-
};
|
|
66
|
-
reader.onerror = () => {
|
|
67
|
-
setError("Failed to read file");
|
|
68
|
-
setIsValidating(false);
|
|
69
|
-
};
|
|
70
|
-
reader.readAsText(file);
|
|
71
|
-
}, []);
|
|
72
|
-
const handleDrop = useCallback((e) => {
|
|
73
|
-
e.preventDefault();
|
|
74
|
-
setIsDragging(false);
|
|
75
|
-
const files = Array.from(e.dataTransfer.files);
|
|
76
|
-
if (files.length > 0) {
|
|
77
|
-
handleFile(files[0]);
|
|
78
|
-
}
|
|
79
|
-
}, [handleFile]);
|
|
80
|
-
const handleDragOver = useCallback((e) => {
|
|
81
|
-
e.preventDefault();
|
|
82
|
-
setIsDragging(true);
|
|
83
|
-
}, []);
|
|
84
|
-
const handleDragLeave = useCallback((e) => {
|
|
85
|
-
e.preventDefault();
|
|
86
|
-
setIsDragging(false);
|
|
87
|
-
}, []);
|
|
88
|
-
const handleFileInput = useCallback((e) => {
|
|
89
|
-
const files = e.target.files;
|
|
90
|
-
if (files && files.length > 0) {
|
|
91
|
-
handleFile(files[0]);
|
|
92
|
-
}
|
|
93
|
-
}, [handleFile]);
|
|
94
|
-
const handleRemoveFile = () => {
|
|
95
|
-
setUploadedFile(null);
|
|
96
|
-
setError(null);
|
|
97
|
-
};
|
|
98
|
-
const formatFileSize = (bytes) => {
|
|
99
|
-
if (bytes < 1024)
|
|
100
|
-
return bytes + " B";
|
|
101
|
-
if (bytes < 1024 * 1024)
|
|
102
|
-
return (bytes / 1024).toFixed(1) + " KB";
|
|
103
|
-
return (bytes / (1024 * 1024)).toFixed(1) + " MB";
|
|
104
|
-
};
|
|
105
|
-
return (React.createElement("div", { className: "min-h-screen bg-white flex flex-col items-center justify-center p-8" },
|
|
106
|
-
React.createElement("div", { className: "w-full max-w-xl" },
|
|
107
|
-
React.createElement("div", { className: "mb-8" },
|
|
108
|
-
React.createElement("button", { type: "button", onClick: onBack, disabled: isLoading, className: "text-sm text-gray-500 hover:text-gray-700 font-medium mb-4 flex items-center gap-1 disabled:opacity-50 disabled:cursor-not-allowed" }, isLoading ? (React.createElement(React.Fragment, null,
|
|
109
|
-
React.createElement("div", { className: "h-3 w-3 animate-spin rounded-full border-2 border-gray-300 border-t-gray-700" }),
|
|
110
|
-
React.createElement("span", null, "Back"))) : (React.createElement(React.Fragment, null, "\u2190 Back"))),
|
|
111
|
-
React.createElement("h1", { className: "text-3xl font-semibold text-black mb-3" }, "Upload OpenAPI spec"),
|
|
112
|
-
React.createElement("p", { className: "text-sm text-gray-600" }, "Upload your OpenAPI specification file (JSON or YAML format) to generate sample apps.")),
|
|
113
|
-
!uploadedFile ? (React.createElement("div", { onDrop: handleDrop, onDragOver: handleDragOver, onDragLeave: handleDragLeave, className: cn("relative rounded-xl border-2 border-dashed transition-all", isDragging
|
|
114
|
-
? "border-blue-500 bg-blue-50"
|
|
115
|
-
: "border-gray-200 bg-gray-50", error && "border-red-300 bg-red-50") },
|
|
116
|
-
React.createElement("label", { className: "flex flex-col items-center justify-center px-8 py-12 cursor-pointer" },
|
|
117
|
-
React.createElement("input", { type: "file", className: "hidden", accept: ".json,.yaml,.yml", onChange: handleFileInput, disabled: isValidating }),
|
|
118
|
-
isValidating ? (React.createElement(React.Fragment, null,
|
|
119
|
-
React.createElement("div", { className: "h-12 w-12 mb-4 animate-spin rounded-full border-4 border-gray-200 border-t-blue-600" }),
|
|
120
|
-
React.createElement("p", { className: "text-sm font-medium text-gray-900 mb-1" }, "Validating file..."))) : (React.createElement(React.Fragment, null,
|
|
121
|
-
React.createElement("div", { className: cn("flex h-14 w-14 items-center justify-center rounded-full mb-4 transition-colors", isDragging ? "bg-blue-100" : "bg-white", error && "bg-red-100") }, error ? (React.createElement(AlertCircle, { className: "h-7 w-7 text-red-600" })) : (React.createElement(Upload, { className: cn("h-7 w-7", isDragging ? "text-blue-600" : "text-gray-400") }))),
|
|
122
|
-
React.createElement("p", { className: "text-sm font-medium text-gray-900 mb-1" }, isDragging ? ("Drop your file here") : (React.createElement(React.Fragment, null,
|
|
123
|
-
React.createElement("span", { className: "text-blue-600" }, "Click to upload"),
|
|
124
|
-
" ",
|
|
125
|
-
"or drag and drop"))),
|
|
126
|
-
React.createElement("p", { className: "text-xs text-gray-500 mb-2" }, "JSON or YAML (max 10MB)"))),
|
|
127
|
-
error && (React.createElement("div", { className: "mt-3 text-xs text-red-600 font-medium flex items-center gap-1.5" },
|
|
128
|
-
React.createElement(AlertCircle, { className: "h-3.5 w-3.5" }),
|
|
129
|
-
error))))) : (React.createElement("div", { className: "space-y-4" },
|
|
130
|
-
React.createElement("div", { className: "rounded-xl border border-gray-200 bg-white p-4 transition hover:bg-gray-50" },
|
|
131
|
-
React.createElement("div", { className: "flex items-start justify-between gap-3" },
|
|
132
|
-
React.createElement("div", { className: "flex items-start gap-3 flex-1" },
|
|
133
|
-
React.createElement("div", { className: "flex h-10 w-10 items-center justify-center rounded-full bg-green-50" },
|
|
134
|
-
React.createElement(FileText, { className: "h-5 w-5 text-green-600" })),
|
|
135
|
-
React.createElement("div", { className: "flex-1 min-w-0" },
|
|
136
|
-
React.createElement("div", { className: "flex items-center gap-2 mb-1" },
|
|
137
|
-
React.createElement("p", { className: "text-sm font-medium text-gray-900 truncate" }, uploadedFile.name),
|
|
138
|
-
React.createElement("div", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-green-500 flex-shrink-0" },
|
|
139
|
-
React.createElement(Check, { className: "h-3 w-3 text-white" }))),
|
|
140
|
-
React.createElement("p", { className: "text-xs text-gray-500" }, formatFileSize(uploadedFile.size)))),
|
|
141
|
-
React.createElement("button", { type: "button", onClick: handleRemoveFile, className: "flex h-8 w-8 items-center justify-center rounded-full hover:bg-gray-100 transition-colors text-gray-400 hover:text-gray-600" },
|
|
142
|
-
React.createElement(X, { className: "h-4 w-4" })))),
|
|
143
|
-
React.createElement("div", { className: "rounded-xl bg-blue-50 border border-blue-100 p-4" },
|
|
144
|
-
React.createElement("p", { className: "text-xs text-blue-900 font-medium mb-2" }, "What happens next?"),
|
|
145
|
-
React.createElement("ul", { className: "space-y-1.5 text-xs text-blue-800" },
|
|
146
|
-
React.createElement("li", { className: "flex items-start gap-2" },
|
|
147
|
-
React.createElement("span", null, "\u2022"),
|
|
148
|
-
React.createElement("span", null, "We'll parse your OpenAPI spec and extract all endpoints")),
|
|
149
|
-
React.createElement("li", { className: "flex items-start gap-2" },
|
|
150
|
-
React.createElement("span", null, "\u2022"),
|
|
151
|
-
React.createElement("span", null, "You'll choose frameworks for which to generate sample apps")),
|
|
152
|
-
React.createElement("li", { className: "flex items-start gap-2" },
|
|
153
|
-
React.createElement("span", null, "\u2022"),
|
|
154
|
-
React.createElement("span", null, "Each app will be pre-configured with auth and example calls")))),
|
|
155
|
-
React.createElement("div", { className: "flex items-center justify-end gap-3 pt-2" },
|
|
156
|
-
React.createElement("button", { type: "button", onClick: handleRemoveFile, disabled: isLoading, className: "rounded-full border border-gray-200 bg-white px-5 py-2.5 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors disabled:opacity-50 disabled:cursor-not-allowed" }, "Choose different file"),
|
|
157
|
-
React.createElement("button", { type: "button", onClick: () => onContinue(uploadedFile), disabled: isLoading, className: "inline-flex items-center gap-2 rounded-full bg-[#2563EB] px-6 py-2.5 text-sm font-medium text-white hover:bg-[#1d4ed8] transition-colors disabled:opacity-50 disabled:cursor-not-allowed" }, isLoading ? (React.createElement(React.Fragment, null,
|
|
158
|
-
React.createElement("div", { className: "h-4 w-4 animate-spin rounded-full border-2 border-white/30 border-t-white" }),
|
|
159
|
-
React.createElement("span", null, "Processing..."))) : (React.createElement("span", null, "Continue")))))))));
|
|
160
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
type Option = {
|
|
3
|
-
label: string;
|
|
4
|
-
value: string;
|
|
5
|
-
href: string;
|
|
6
|
-
};
|
|
7
|
-
interface GlassmorphicComboboxProps {
|
|
8
|
-
options: Option[];
|
|
9
|
-
value: string;
|
|
10
|
-
placeholder?: string;
|
|
11
|
-
className?: string;
|
|
12
|
-
searchPlaceholder?: string;
|
|
13
|
-
}
|
|
14
|
-
export default function GlassmorphicCombobox({ options, value, placeholder, className, searchPlaceholder, }: GlassmorphicComboboxProps): React.JSX.Element;
|
|
15
|
-
export {};
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import React, { useState } from "react";
|
|
3
|
-
import { Button } from "../ui/button";
|
|
4
|
-
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
|
5
|
-
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "../ui/command";
|
|
6
|
-
import { CheckIcon, ChevronsUpDown } from "lucide-react";
|
|
7
|
-
import { cn } from "../../../lib/utils";
|
|
8
|
-
export default function GlassmorphicCombobox({ options, value, placeholder, className, searchPlaceholder, }) {
|
|
9
|
-
var _a;
|
|
10
|
-
const [open, setOpen] = useState(false);
|
|
11
|
-
const selectedLabel = (_a = options.find((o) => o.value === value)) === null || _a === void 0 ? void 0 : _a.label;
|
|
12
|
-
return (React.createElement(Popover, { open: open, onOpenChange: setOpen },
|
|
13
|
-
React.createElement(PopoverTrigger, { asChild: true },
|
|
14
|
-
React.createElement(Button, { variant: "outline", role: "combobox", "aria-expanded": 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) },
|
|
15
|
-
React.createElement("span", { className: "truncate min-w-0" }, selectedLabel !== null && selectedLabel !== void 0 ? selectedLabel : placeholder),
|
|
16
|
-
React.createElement(ChevronsUpDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-70" }))),
|
|
17
|
-
React.createElement(PopoverContent, { className: cn("w-64 p-0 rounded-xl border border-white/20 bg-black/40 backdrop-blur-xl", "shadow-xl") },
|
|
18
|
-
React.createElement(Command, { className: "bg-transparent" },
|
|
19
|
-
React.createElement(CommandInput, { placeholder: searchPlaceholder, className: "bg-transparent placeholder:text-white/60" }),
|
|
20
|
-
React.createElement(CommandList, { className: "bg-transparent" },
|
|
21
|
-
React.createElement(CommandEmpty, { className: "text-white/80" }, "No results found."),
|
|
22
|
-
React.createElement(CommandGroup, { className: "bg-transparent" }, options.map((option) => (React.createElement(CommandItem, { key: option.value, value: option.value, className: "cursor-pointer", onSelect: () => {
|
|
23
|
-
setOpen(false);
|
|
24
|
-
if (option.href && typeof window !== "undefined") {
|
|
25
|
-
window.location.href = option.href;
|
|
26
|
-
}
|
|
27
|
-
} },
|
|
28
|
-
React.createElement(CheckIcon, { className: cn("mr-2 h-4 w-4", value === option.value ? "opacity-100" : "opacity-0") }),
|
|
29
|
-
option.label)))))))));
|
|
30
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
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;
|
|
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 = (process.env.NEXT_PUBLIC_ENVIRONMENT ||
|
|
21
|
-
process.env.NODE_ENV ||
|
|
22
|
-
"development").toLowerCase();
|
|
23
|
-
const envMeta = {
|
|
24
|
-
production: {
|
|
25
|
-
label: "Production",
|
|
26
|
-
badgeClass: "border-emerald-500/30 bg-emerald-500/10 text-emerald-200",
|
|
27
|
-
dotClass: "bg-emerald-400 shadow-[0_0_0_3px_rgba(16,185,129,0.25)]",
|
|
28
|
-
},
|
|
29
|
-
staging: {
|
|
30
|
-
label: "Staging",
|
|
31
|
-
badgeClass: "border-amber-500/30 bg-amber-500/10 text-amber-100",
|
|
32
|
-
dotClass: "bg-amber-400 shadow-[0_0_0_3px_rgba(245,158,11,0.25)]",
|
|
33
|
-
},
|
|
34
|
-
test: {
|
|
35
|
-
label: "Test",
|
|
36
|
-
badgeClass: "border-cyan-500/30 bg-cyan-500/10 text-cyan-100",
|
|
37
|
-
dotClass: "bg-cyan-400 shadow-[0_0_0_3px_rgba(34,211,238,0.25)]",
|
|
38
|
-
},
|
|
39
|
-
preview: {
|
|
40
|
-
label: "Preview",
|
|
41
|
-
badgeClass: "border-sky-500/30 bg-sky-500/10 text-sky-100",
|
|
42
|
-
dotClass: "bg-sky-400 shadow-[0_0_0_3px_rgba(56,189,248,0.25)]",
|
|
43
|
-
},
|
|
44
|
-
development: {
|
|
45
|
-
label: "Demo Environment",
|
|
46
|
-
badgeClass: "border-violet-500/30 bg-violet-500/10 text-violet-100",
|
|
47
|
-
dotClass: "bg-violet-400 shadow-[0_0_0_3px_rgba(139,92,246,0.25)]",
|
|
48
|
-
},
|
|
49
|
-
};
|
|
50
|
-
const meta = envMeta[environment] || envMeta.development;
|
|
51
|
-
return (React.createElement("div", { className: "grid grid-cols-[1fr_auto_1fr] items-center w-full ml-6" },
|
|
52
|
-
React.createElement("div", { className: "justify-self-start" }, playgroundLogo),
|
|
53
|
-
React.createElement("div", { className: "justify-self-center" },
|
|
54
|
-
React.createElement("div", { className: "flex gap-2" },
|
|
55
|
-
React.createElement(GlassmorphicCombobox, { options: demoOptionsWithHrefs, value: selectedUseCase, placeholder: "Select use case...", searchPlaceholder: "Search use case..." }),
|
|
56
|
-
React.createElement(GlassmorphicCombobox, { options: frameworkOptionsWithHrefs, value: selectedFramework, placeholder: "Select framework...", searchPlaceholder: "Search framework...", className: "w-32" }))),
|
|
57
|
-
React.createElement("div", { className: "justify-self-end mr-6" },
|
|
58
|
-
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` },
|
|
59
|
-
React.createElement("span", { className: `inline-block h-2 w-2 rounded-full ${meta.dotClass}` }),
|
|
60
|
-
meta.label))));
|
|
61
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { useEffect, useRef } from "react";
|
|
3
|
-
/**
|
|
4
|
-
* Hook to handle postMessage communication with parent window when in iframe mode.
|
|
5
|
-
* - Sends IFRAME_READY when mounted
|
|
6
|
-
* - Listens for UPDATE_VIEW messages and updates URL params accordingly
|
|
7
|
-
* - Works with useFrameParams() which reads the updated URL params
|
|
8
|
-
*
|
|
9
|
-
* Supported UPDATE_VIEW message fields:
|
|
10
|
-
* - framework: Switch between frameworks (e.g., "nextjs", "react")
|
|
11
|
-
* - activeFilePath: Change the active file in the editor
|
|
12
|
-
* - linesStart/linesEnd: Highlight specific line range in the editor
|
|
13
|
-
* - theme: Override the theme color (hex color)
|
|
14
|
-
*/
|
|
15
|
-
export function useFrameMessages() {
|
|
16
|
-
const hasSignaledReady = useRef(false);
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
if (typeof window === "undefined")
|
|
19
|
-
return;
|
|
20
|
-
// Check if we're in an iframe
|
|
21
|
-
const isFrame = window.self !== window.top;
|
|
22
|
-
if (!isFrame)
|
|
23
|
-
return;
|
|
24
|
-
// Signal to parent that we're ready to receive messages
|
|
25
|
-
if (!hasSignaledReady.current && window.parent) {
|
|
26
|
-
window.parent.postMessage({ type: "IFRAME_READY" }, "*");
|
|
27
|
-
hasSignaledReady.current = true;
|
|
28
|
-
}
|
|
29
|
-
const handleMessage = (event) => {
|
|
30
|
-
// Optional: Add origin validation for security in production
|
|
31
|
-
// if (event.origin !== 'https://your-allowed-origin.com') return;
|
|
32
|
-
const data = event.data;
|
|
33
|
-
if (data.type === "UPDATE_VIEW") {
|
|
34
|
-
const url = new URL(window.location.href);
|
|
35
|
-
// Update URL params based on message
|
|
36
|
-
if (data.framework !== undefined) {
|
|
37
|
-
url.searchParams.set("framework", data.framework);
|
|
38
|
-
}
|
|
39
|
-
if (data.activeFilePath !== undefined) {
|
|
40
|
-
url.searchParams.set("activeFilePath", data.activeFilePath);
|
|
41
|
-
}
|
|
42
|
-
if (data.linesStart !== undefined) {
|
|
43
|
-
url.searchParams.set("linesStart", String(data.linesStart));
|
|
44
|
-
}
|
|
45
|
-
if (data.linesEnd !== undefined) {
|
|
46
|
-
url.searchParams.set("linesEnd", String(data.linesEnd));
|
|
47
|
-
}
|
|
48
|
-
if (data.theme !== undefined) {
|
|
49
|
-
url.searchParams.set("theme", data.theme);
|
|
50
|
-
}
|
|
51
|
-
if (data.iframeUrl !== undefined) {
|
|
52
|
-
url.searchParams.set("iframeUrl", data.iframeUrl);
|
|
53
|
-
}
|
|
54
|
-
// Update URL without page reload
|
|
55
|
-
window.history.replaceState({}, "", url.toString());
|
|
56
|
-
// Dispatch a custom event so React components can react immediately
|
|
57
|
-
// (useFrameParams may have a slight delay)
|
|
58
|
-
window.dispatchEvent(new CustomEvent("frameParamsUpdated", { detail: data }));
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
window.addEventListener("message", handleMessage);
|
|
62
|
-
return () => window.removeEventListener("message", handleMessage);
|
|
63
|
-
}, []);
|
|
64
|
-
return {};
|
|
65
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { useMemo } from "react";
|
|
2
|
-
/**
|
|
3
|
-
* Hook to read frame-mode search parameters from URL.
|
|
4
|
-
* These params override config/context when isFrame=true.
|
|
5
|
-
*
|
|
6
|
-
* Note: This is a simplified version that reads from window.location.search.
|
|
7
|
-
* For Next.js apps, you can use the Next.js version that uses useSearchParams.
|
|
8
|
-
*/
|
|
9
|
-
export function useFrameParams() {
|
|
10
|
-
return useMemo(() => {
|
|
11
|
-
if (typeof window === "undefined") {
|
|
12
|
-
return {
|
|
13
|
-
isFrame: false,
|
|
14
|
-
framework: undefined,
|
|
15
|
-
theme: undefined,
|
|
16
|
-
activeFilePath: undefined,
|
|
17
|
-
linesStart: undefined,
|
|
18
|
-
linesEnd: undefined,
|
|
19
|
-
iframeUrl: undefined,
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
const searchParams = new URLSearchParams(window.location.search);
|
|
23
|
-
const isFrame = searchParams.get("isFrame") === "true";
|
|
24
|
-
const framework = searchParams.get("framework") || undefined;
|
|
25
|
-
const theme = searchParams.get("theme") || undefined;
|
|
26
|
-
const activeFilePath = searchParams.get("activeFilePath") || undefined;
|
|
27
|
-
const linesStart = searchParams.get("linesStart")
|
|
28
|
-
? parseInt(searchParams.get("linesStart"), 10)
|
|
29
|
-
: undefined;
|
|
30
|
-
const linesEnd = searchParams.get("linesEnd")
|
|
31
|
-
? parseInt(searchParams.get("linesEnd"), 10)
|
|
32
|
-
: undefined;
|
|
33
|
-
const iframeUrl = searchParams.get("iframeUrl") || undefined;
|
|
34
|
-
return {
|
|
35
|
-
isFrame,
|
|
36
|
-
framework,
|
|
37
|
-
theme,
|
|
38
|
-
activeFilePath,
|
|
39
|
-
linesStart,
|
|
40
|
-
linesEnd,
|
|
41
|
-
iframeUrl,
|
|
42
|
-
};
|
|
43
|
-
}, [typeof window !== "undefined" ? window.location.search : ""]);
|
|
44
|
-
}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { useCallback } from "react";
|
|
3
|
-
import { useVmContext } from "../context/vm-context";
|
|
4
|
-
/**
|
|
5
|
-
* Creates a cache key from sandboxUid and browserUrl.
|
|
6
|
-
* This allows caching different URLs for the same sandbox.
|
|
7
|
-
*/
|
|
8
|
-
function getCacheKey(sandboxUid, browserUrl) {
|
|
9
|
-
return `${sandboxUid}::${browserUrl}`;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Hook that abstracts the logic for loading sandbox URLs.
|
|
13
|
-
* Returns functions to load URLs and check if they're already loaded.
|
|
14
|
-
*/
|
|
15
|
-
export function useSandboxUrlLoader() {
|
|
16
|
-
const { setVmUrl, getVmUrl, setVmError, hasError } = useVmContext();
|
|
17
|
-
/**
|
|
18
|
-
* Loads a sandbox URL. If useVm is true, fetches from the API.
|
|
19
|
-
* Otherwise, uses the browserUrl directly.
|
|
20
|
-
* Returns a promise that resolves to the final URL, or throws if loading fails.
|
|
21
|
-
*
|
|
22
|
-
* Caches by sandboxUid + browserUrl to support multiple URLs per sandbox.
|
|
23
|
-
*/
|
|
24
|
-
const loadSandboxUrl = useCallback(async (config) => {
|
|
25
|
-
const { sandboxUid, browserUrl, useVm } = config;
|
|
26
|
-
const cacheKey = getCacheKey(sandboxUid, browserUrl);
|
|
27
|
-
// Check if URL is already loaded for this specific browserUrl
|
|
28
|
-
const existingUrl = getVmUrl(cacheKey);
|
|
29
|
-
if (existingUrl !== undefined) {
|
|
30
|
-
return existingUrl;
|
|
31
|
-
}
|
|
32
|
-
// Check if we've already attempted and failed for this specific browserUrl
|
|
33
|
-
if (hasError(cacheKey)) {
|
|
34
|
-
throw new Error(`Previously failed to load sandbox URL for ${sandboxUid} with ${browserUrl}`);
|
|
35
|
-
}
|
|
36
|
-
if (useVm) {
|
|
37
|
-
try {
|
|
38
|
-
// Fetch VM URL from API
|
|
39
|
-
// Note: This assumes the API endpoint is available at /api/auto-sandbox
|
|
40
|
-
// You may need to configure this based on your deployment
|
|
41
|
-
const apiBaseUrl = typeof window !== "undefined"
|
|
42
|
-
? window.location.origin
|
|
43
|
-
: "";
|
|
44
|
-
const response = await fetch(`${apiBaseUrl}/api/auto-sandbox?url=${encodeURIComponent(browserUrl)}&mode=json`);
|
|
45
|
-
// Check if response is successful
|
|
46
|
-
if (!response.ok) {
|
|
47
|
-
const errorText = await response.text();
|
|
48
|
-
console.error(`Failed to fetch sandbox URL for ${sandboxUid}:`, errorText);
|
|
49
|
-
// Mark as error to prevent infinite retries
|
|
50
|
-
setVmError(cacheKey);
|
|
51
|
-
throw new Error(`Failed to create sandbox: ${response.status} ${response.statusText}`);
|
|
52
|
-
}
|
|
53
|
-
const data = await response.json();
|
|
54
|
-
if (!data.vncUrl) {
|
|
55
|
-
console.error(`No vncUrl in response for ${sandboxUid}:`, data);
|
|
56
|
-
setVmError(cacheKey);
|
|
57
|
-
throw new Error("No vncUrl in sandbox response");
|
|
58
|
-
}
|
|
59
|
-
const vncUrl = data.vncUrl;
|
|
60
|
-
setVmUrl(cacheKey, vncUrl);
|
|
61
|
-
return vncUrl;
|
|
62
|
-
}
|
|
63
|
-
catch (error) {
|
|
64
|
-
// Mark as error to prevent infinite retries
|
|
65
|
-
setVmError(cacheKey);
|
|
66
|
-
throw error;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
70
|
-
// Use browserUrl directly (non-VM mode)
|
|
71
|
-
setVmUrl(cacheKey, browserUrl);
|
|
72
|
-
return browserUrl;
|
|
73
|
-
}
|
|
74
|
-
}, [getVmUrl, setVmUrl, hasError, setVmError]);
|
|
75
|
-
/**
|
|
76
|
-
* Gets the URL for a sandbox + browserUrl combination if it's already loaded.
|
|
77
|
-
* Returns undefined if not loaded.
|
|
78
|
-
*/
|
|
79
|
-
const getSandboxUrl = useCallback((sandboxUid, browserUrl) => {
|
|
80
|
-
const cacheKey = getCacheKey(sandboxUid, browserUrl);
|
|
81
|
-
return getVmUrl(cacheKey);
|
|
82
|
-
}, [getVmUrl]);
|
|
83
|
-
/**
|
|
84
|
-
* Preloads URLs for multiple sandboxes in parallel.
|
|
85
|
-
* Errors are logged but don't stop other URLs from loading.
|
|
86
|
-
*/
|
|
87
|
-
const preloadSandboxUrls = useCallback(async (configs) => {
|
|
88
|
-
// Filter out configs that are already loaded or have errors
|
|
89
|
-
const configsToLoad = configs.filter((config) => {
|
|
90
|
-
const cacheKey = getCacheKey(config.sandboxUid, config.browserUrl);
|
|
91
|
-
return getVmUrl(cacheKey) === undefined && !hasError(cacheKey);
|
|
92
|
-
});
|
|
93
|
-
// Load all URLs in parallel, but catch errors individually
|
|
94
|
-
await Promise.allSettled(configsToLoad.map((config) => loadSandboxUrl(config)));
|
|
95
|
-
}, [getVmUrl, loadSandboxUrl, hasError]);
|
|
96
|
-
return {
|
|
97
|
-
loadSandboxUrl,
|
|
98
|
-
getSandboxUrl,
|
|
99
|
-
preloadSandboxUrls,
|
|
100
|
-
};
|
|
101
|
-
}
|