create-interview-cockpit 0.13.0 → 0.15.0
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/package.json +1 -1
- package/template/client/src/api.ts +39 -0
- package/template/client/src/browserSecurityTemplates.ts +3242 -0
- package/template/client/src/components/BrowserSecurityLabModal.tsx +1510 -0
- package/template/client/src/components/CodeRunnerModal.tsx +406 -55
- package/template/client/src/components/LabsPanel.tsx +123 -12
- package/template/client/src/components/LinkedConvosPicker.tsx +121 -63
- package/template/client/src/components/Sidebar.tsx +113 -0
- package/template/client/src/reactLab.ts +408 -0
- package/template/client/src/store.ts +15 -1
- package/template/client/src/types.ts +2 -0
- package/template/client/tsconfig.tsbuildinfo +1 -1
- package/template/cockpit.json +1 -1
- package/template/server/src/google-drive.ts +2 -0
- package/template/server/src/index.ts +90 -24
- package/template/server/src/storage.ts +2 -0
|
@@ -53,6 +53,7 @@ import {
|
|
|
53
53
|
startNextjsSandbox,
|
|
54
54
|
stopModuleFederationSandbox,
|
|
55
55
|
streamModuleFederationCommand,
|
|
56
|
+
streamNextjsCommand,
|
|
56
57
|
updateNextjsFiles,
|
|
57
58
|
updateModuleFederationFiles,
|
|
58
59
|
stopNextjsSandbox,
|
|
@@ -161,6 +162,93 @@ const rest = await fetch(SANDBOX_URL + '/ping');
|
|
|
161
162
|
console.log(await rest.json());
|
|
162
163
|
`;
|
|
163
164
|
|
|
165
|
+
function generateScriptPreviewHTML(
|
|
166
|
+
clientCode: string,
|
|
167
|
+
sandboxUrl?: string,
|
|
168
|
+
): string {
|
|
169
|
+
const codeJSON = JSON.stringify(clientCode);
|
|
170
|
+
const sandboxJSON = JSON.stringify(sandboxUrl ?? "");
|
|
171
|
+
|
|
172
|
+
return `<!DOCTYPE html>
|
|
173
|
+
<html>
|
|
174
|
+
<head>
|
|
175
|
+
<meta charset="utf-8">
|
|
176
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
177
|
+
<style>
|
|
178
|
+
*{box-sizing:border-box}
|
|
179
|
+
body{margin:0;background:#020617;color:#e2e8f0;font-family:system-ui,sans-serif;min-height:100vh}
|
|
180
|
+
</style>
|
|
181
|
+
</head>
|
|
182
|
+
<body>
|
|
183
|
+
<script>
|
|
184
|
+
window.__SCRIPT_PREVIEW_CODE__ = ${codeJSON};
|
|
185
|
+
window.__SCRIPT_PREVIEW_SANDBOX_URL__ = ${sandboxJSON};
|
|
186
|
+
window.__scriptPreviewSerialize = function(value) {
|
|
187
|
+
try {
|
|
188
|
+
if (typeof value === 'string') return value;
|
|
189
|
+
return JSON.stringify(value, null, 2);
|
|
190
|
+
} catch {
|
|
191
|
+
return String(value);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
window.__scriptPreviewPost = function(type, payload) {
|
|
195
|
+
try {
|
|
196
|
+
parent.postMessage({ type: type, ...payload }, '*');
|
|
197
|
+
} catch {}
|
|
198
|
+
};
|
|
199
|
+
['log', 'warn', 'error'].forEach(function(kind) {
|
|
200
|
+
var original = console[kind] ? console[kind].bind(console) : null;
|
|
201
|
+
console[kind] = function() {
|
|
202
|
+
var args = Array.prototype.slice.call(arguments);
|
|
203
|
+
if (original) original.apply(console, args);
|
|
204
|
+
window.__scriptPreviewPost('script-preview-log', {
|
|
205
|
+
kind: kind,
|
|
206
|
+
text: args.map(window.__scriptPreviewSerialize).join(' '),
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
});
|
|
210
|
+
window.addEventListener('error', function(event) {
|
|
211
|
+
window.__scriptPreviewPost('script-preview-error', {
|
|
212
|
+
error:
|
|
213
|
+
event.error && event.error.stack
|
|
214
|
+
? event.error.stack
|
|
215
|
+
: event.message || 'Unknown preview error',
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
window.addEventListener('unhandledrejection', function(event) {
|
|
219
|
+
var reason = event.reason;
|
|
220
|
+
window.__scriptPreviewPost('script-preview-error', {
|
|
221
|
+
error:
|
|
222
|
+
reason && reason.stack
|
|
223
|
+
? reason.stack
|
|
224
|
+
: reason && reason.message
|
|
225
|
+
? reason.message
|
|
226
|
+
: window.__scriptPreviewSerialize(reason),
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
</script>
|
|
230
|
+
<script type="module">
|
|
231
|
+
const SANDBOX_URL = window.__SCRIPT_PREVIEW_SANDBOX_URL__;
|
|
232
|
+
window.SANDBOX_URL = SANDBOX_URL;
|
|
233
|
+
const moduleSource =
|
|
234
|
+
'const SANDBOX_URL = ' + JSON.stringify(SANDBOX_URL) + ';\\n' +
|
|
235
|
+
window.__SCRIPT_PREVIEW_CODE__;
|
|
236
|
+
const moduleBlob = new Blob([moduleSource], { type: 'text/javascript' });
|
|
237
|
+
const moduleUrl = URL.createObjectURL(moduleBlob);
|
|
238
|
+
try {
|
|
239
|
+
await import(moduleUrl);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
window.__scriptPreviewPost('script-preview-error', {
|
|
242
|
+
error: error && error.stack ? error.stack : String(error),
|
|
243
|
+
});
|
|
244
|
+
} finally {
|
|
245
|
+
URL.revokeObjectURL(moduleUrl);
|
|
246
|
+
}
|
|
247
|
+
</script>
|
|
248
|
+
</body>
|
|
249
|
+
</html>`;
|
|
250
|
+
}
|
|
251
|
+
|
|
164
252
|
// VS Code Dark+ token colours injected once for prismjs.
|
|
165
253
|
// react-simple-code-editor uses Prism.highlight() which emits class-based spans;
|
|
166
254
|
// these inline styles map those classes to the same palette used in the old theme.
|
|
@@ -633,6 +721,9 @@ export default function CodeRunnerModal() {
|
|
|
633
721
|
const [mfConsoleCommand, setMfConsoleCommand] = useState("npm run build");
|
|
634
722
|
const [mfConsoleCwd, setMfConsoleCwd] = useState("apps/host");
|
|
635
723
|
const [mfConsoleRunning, setMfConsoleRunning] = useState(false);
|
|
724
|
+
const [nxConsoleCommand, setNxConsoleCommand] = useState("npm run build");
|
|
725
|
+
const [nxConsoleOutput, setNxConsoleOutput] = useState<OutputLine[]>([]);
|
|
726
|
+
const [nxConsoleRunning, setNxConsoleRunning] = useState(false);
|
|
636
727
|
const [mfGeneratedFiles, setMfGeneratedFiles] = useState<string[]>([]);
|
|
637
728
|
const [mfGeneratedFileContents, setMfGeneratedFileContents] = useState<
|
|
638
729
|
Record<string, string>
|
|
@@ -684,6 +775,18 @@ export default function CodeRunnerModal() {
|
|
|
684
775
|
// ── Sandbox save state (single combined save) ──────────────────
|
|
685
776
|
const [sbxNaming, setSbxNaming] = useState(false);
|
|
686
777
|
const [sbxName, setSbxName] = useState("My Sandbox");
|
|
778
|
+
const [sbxDefaultName, setSbxDefaultName] = useState(
|
|
779
|
+
runnerInitialSandbox?.label ?? "My Sandbox",
|
|
780
|
+
);
|
|
781
|
+
const [scriptSandboxOrigin, setScriptSandboxOrigin] = useState<
|
|
782
|
+
"sandbox" | "browser-security"
|
|
783
|
+
>(runnerInitialSandbox?.origin ?? "sandbox");
|
|
784
|
+
const [scriptClientTab, setScriptClientTab] = useState<"edit" | "preview">(
|
|
785
|
+
runnerInitialSandbox?.origin === "browser-security" ? "preview" : "edit",
|
|
786
|
+
);
|
|
787
|
+
const [scriptPreviewSrcDoc, setScriptPreviewSrcDoc] = useState<string | null>(
|
|
788
|
+
null,
|
|
789
|
+
);
|
|
687
790
|
const [sbxSaved, setSbxSaved] = useState(false);
|
|
688
791
|
const [sbxSaving, setSbxSaving] = useState(false);
|
|
689
792
|
/** When non-null we are editing a previously saved sandbox — Save overwrites, Save As still creates new */
|
|
@@ -715,6 +818,15 @@ export default function CodeRunnerModal() {
|
|
|
715
818
|
const sbxChatAbortRef = useRef<{ aborted: boolean }>({ aborted: false });
|
|
716
819
|
const [sbxChatCopiedId, setSbxChatCopiedId] = useState<string | null>(null);
|
|
717
820
|
|
|
821
|
+
const openScriptPreview = useCallback(() => {
|
|
822
|
+
setScriptClientTab("preview");
|
|
823
|
+
if (!sandboxUrl) {
|
|
824
|
+
setScriptPreviewSrcDoc(null);
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
setScriptPreviewSrcDoc(generateScriptPreviewHTML(clientCode, sandboxUrl));
|
|
828
|
+
}, [clientCode, sandboxUrl]);
|
|
829
|
+
|
|
718
830
|
// Keep key ref fresh
|
|
719
831
|
useEffect(() => {
|
|
720
832
|
sbxChatKeyRef.current = sbxChatKey;
|
|
@@ -752,13 +864,15 @@ export default function CodeRunnerModal() {
|
|
|
752
864
|
setSbxSaving(true);
|
|
753
865
|
try {
|
|
754
866
|
const origin =
|
|
755
|
-
|
|
756
|
-
? "
|
|
757
|
-
: clientType === "
|
|
758
|
-
? "
|
|
759
|
-
: clientType === "
|
|
760
|
-
? "
|
|
761
|
-
: "
|
|
867
|
+
scriptSandboxOrigin === "browser-security"
|
|
868
|
+
? "browser-security"
|
|
869
|
+
: clientType === "react"
|
|
870
|
+
? "react"
|
|
871
|
+
: clientType === "nextjs"
|
|
872
|
+
? "nextjs"
|
|
873
|
+
: clientType === "module-federation"
|
|
874
|
+
? "module-federation"
|
|
875
|
+
: scriptSandboxOrigin;
|
|
762
876
|
const payload = JSON.stringify(
|
|
763
877
|
clientType === "script"
|
|
764
878
|
? { serverCode, serverLang, clientCode, clientLang }
|
|
@@ -865,6 +979,13 @@ export default function CodeRunnerModal() {
|
|
|
865
979
|
setClientLang((runnerInitialSandbox.clientLang as Lang) ?? "javascript");
|
|
866
980
|
setSandboxOutput([]);
|
|
867
981
|
setActiveSandboxId(runnerInitialSandbox.fileId ?? null);
|
|
982
|
+
setScriptSandboxOrigin(runnerInitialSandbox.origin ?? "sandbox");
|
|
983
|
+
setScriptClientTab(
|
|
984
|
+
runnerInitialSandbox.origin === "browser-security" ? "preview" : "edit",
|
|
985
|
+
);
|
|
986
|
+
setScriptPreviewSrcDoc(null);
|
|
987
|
+
setSbxDefaultName(runnerInitialSandbox.label ?? "My Sandbox");
|
|
988
|
+
setSbxName(runnerInitialSandbox.label ?? "My Sandbox");
|
|
868
989
|
// Restore client type and React/Next.js files
|
|
869
990
|
const ct =
|
|
870
991
|
(runnerInitialSandbox.clientType as FrontendClientType) ?? "script";
|
|
@@ -911,6 +1032,71 @@ export default function CodeRunnerModal() {
|
|
|
911
1032
|
}
|
|
912
1033
|
}, [runnerInitialSandbox]);
|
|
913
1034
|
|
|
1035
|
+
useEffect(() => {
|
|
1036
|
+
if (clientType !== "script" || scriptSandboxOrigin !== "browser-security") {
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
if (scriptClientTab !== "preview") return;
|
|
1040
|
+
if (!sandboxUrl) {
|
|
1041
|
+
setScriptPreviewSrcDoc(null);
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
setScriptPreviewSrcDoc(generateScriptPreviewHTML(clientCode, sandboxUrl));
|
|
1045
|
+
}, [
|
|
1046
|
+
clientCode,
|
|
1047
|
+
clientType,
|
|
1048
|
+
sandboxUrl,
|
|
1049
|
+
scriptClientTab,
|
|
1050
|
+
scriptSandboxOrigin,
|
|
1051
|
+
]);
|
|
1052
|
+
|
|
1053
|
+
useEffect(() => {
|
|
1054
|
+
if (clientType !== "script" || scriptSandboxOrigin !== "browser-security") {
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
const handler = (event: MessageEvent) => {
|
|
1059
|
+
if (
|
|
1060
|
+
event.data?.type === "script-preview-log" &&
|
|
1061
|
+
typeof event.data.text === "string"
|
|
1062
|
+
) {
|
|
1063
|
+
const kind =
|
|
1064
|
+
event.data.kind === "error"
|
|
1065
|
+
? "stderr"
|
|
1066
|
+
: event.data.kind === "warn"
|
|
1067
|
+
? "warn"
|
|
1068
|
+
: "stdout";
|
|
1069
|
+
|
|
1070
|
+
setSandboxOutput((prev) => [
|
|
1071
|
+
...prev,
|
|
1072
|
+
{
|
|
1073
|
+
kind,
|
|
1074
|
+
text: event.data.text,
|
|
1075
|
+
source: "client",
|
|
1076
|
+
},
|
|
1077
|
+
]);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
if (
|
|
1081
|
+
event.data?.type === "script-preview-error" &&
|
|
1082
|
+
typeof event.data.error === "string"
|
|
1083
|
+
) {
|
|
1084
|
+
setSandboxOutput((prev) => [
|
|
1085
|
+
...prev,
|
|
1086
|
+
{
|
|
1087
|
+
kind: "stderr",
|
|
1088
|
+
text: `Preview error: ${event.data.error}`,
|
|
1089
|
+
source: "client",
|
|
1090
|
+
},
|
|
1091
|
+
]);
|
|
1092
|
+
setSbxBottomTab("output");
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
|
|
1096
|
+
window.addEventListener("message", handler);
|
|
1097
|
+
return () => window.removeEventListener("message", handler);
|
|
1098
|
+
}, [clientType, scriptSandboxOrigin]);
|
|
1099
|
+
|
|
914
1100
|
// Auto-focus is handled inside SyntaxEditor via autoFocus prop.
|
|
915
1101
|
|
|
916
1102
|
// Scroll output to bottom whenever it grows
|
|
@@ -1818,6 +2004,36 @@ export default function CodeRunnerModal() {
|
|
|
1818
2004
|
refreshModuleFederationGeneratedFiles,
|
|
1819
2005
|
]);
|
|
1820
2006
|
|
|
2007
|
+
const runNextjsCommand = useCallback(async () => {
|
|
2008
|
+
if (!nxSandboxId || nxConsoleRunning) return;
|
|
2009
|
+
const command = nxConsoleCommand.trim();
|
|
2010
|
+
if (!command) return;
|
|
2011
|
+
setNxConsoleRunning(true);
|
|
2012
|
+
setSbxBottomTab("console");
|
|
2013
|
+
try {
|
|
2014
|
+
await streamNextjsCommand({ id: nxSandboxId, command }, (message) => {
|
|
2015
|
+
if (message.type === "output") {
|
|
2016
|
+
setNxConsoleOutput((prev) => [
|
|
2017
|
+
...prev,
|
|
2018
|
+
{ kind: message.kind, text: message.text, source: "server" },
|
|
2019
|
+
]);
|
|
2020
|
+
} else if (message.type === "error") {
|
|
2021
|
+
setNxConsoleOutput((prev) => [
|
|
2022
|
+
...prev,
|
|
2023
|
+
{ kind: "stderr", text: message.error, source: "server" },
|
|
2024
|
+
]);
|
|
2025
|
+
}
|
|
2026
|
+
});
|
|
2027
|
+
} catch (err: any) {
|
|
2028
|
+
setNxConsoleOutput((prev) => [
|
|
2029
|
+
...prev,
|
|
2030
|
+
{ kind: "stderr", text: err?.message ?? String(err), source: "server" },
|
|
2031
|
+
]);
|
|
2032
|
+
} finally {
|
|
2033
|
+
setNxConsoleRunning(false);
|
|
2034
|
+
}
|
|
2035
|
+
}, [nxConsoleCommand, nxConsoleRunning, nxSandboxId]);
|
|
2036
|
+
|
|
1821
2037
|
// Clean up Next.js server when the modal is closed or mode changes away from nextjs
|
|
1822
2038
|
const prevClientTypeRef = useRef(clientType);
|
|
1823
2039
|
useEffect(() => {
|
|
@@ -3133,7 +3349,7 @@ export default function CodeRunnerModal() {
|
|
|
3133
3349
|
if (activeSandboxId) {
|
|
3134
3350
|
await overwriteSandboxSnippet();
|
|
3135
3351
|
} else {
|
|
3136
|
-
setSbxName(
|
|
3352
|
+
setSbxName(sbxDefaultName);
|
|
3137
3353
|
setSbxNaming(true);
|
|
3138
3354
|
setTimeout(() => sbxNameInputRef.current?.focus(), 30);
|
|
3139
3355
|
}
|
|
@@ -3164,7 +3380,7 @@ export default function CodeRunnerModal() {
|
|
|
3164
3380
|
type="button"
|
|
3165
3381
|
onMouseDown={(e) => e.stopPropagation()}
|
|
3166
3382
|
onClick={() => {
|
|
3167
|
-
setSbxName(
|
|
3383
|
+
setSbxName(sbxDefaultName);
|
|
3168
3384
|
setSbxNaming(true);
|
|
3169
3385
|
setTimeout(() => sbxNameInputRef.current?.focus(), 30);
|
|
3170
3386
|
}}
|
|
@@ -3572,24 +3788,71 @@ export default function CodeRunnerModal() {
|
|
|
3572
3788
|
</button>
|
|
3573
3789
|
))}
|
|
3574
3790
|
</div>
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3791
|
+
{scriptSandboxOrigin === "browser-security" ? (
|
|
3792
|
+
<>
|
|
3793
|
+
<div className="flex items-center rounded overflow-hidden border border-slate-700/50 text-[9px] shrink-0">
|
|
3794
|
+
<button
|
|
3795
|
+
type="button"
|
|
3796
|
+
onClick={() => setScriptClientTab("edit")}
|
|
3797
|
+
className={`flex items-center gap-0.5 px-1.5 py-0.5 transition-colors ${
|
|
3798
|
+
scriptClientTab === "edit"
|
|
3799
|
+
? "bg-slate-700 text-slate-200"
|
|
3800
|
+
: "text-slate-500 hover:text-slate-400"
|
|
3801
|
+
}`}
|
|
3802
|
+
title="Edit client code"
|
|
3803
|
+
>
|
|
3804
|
+
<Code2 className="w-2.5 h-2.5" />
|
|
3805
|
+
</button>
|
|
3806
|
+
<button
|
|
3807
|
+
type="button"
|
|
3808
|
+
onClick={openScriptPreview}
|
|
3809
|
+
className={`flex items-center gap-0.5 px-1.5 py-0.5 transition-colors ${
|
|
3810
|
+
scriptClientTab === "preview"
|
|
3811
|
+
? "bg-slate-700 text-slate-200"
|
|
3812
|
+
: "text-slate-500 hover:text-slate-400"
|
|
3813
|
+
}`}
|
|
3814
|
+
title="Browser preview"
|
|
3815
|
+
>
|
|
3816
|
+
<Eye className="w-2.5 h-2.5" />
|
|
3817
|
+
</button>
|
|
3818
|
+
</div>
|
|
3819
|
+
<button
|
|
3820
|
+
type="button"
|
|
3821
|
+
onClick={openScriptPreview}
|
|
3822
|
+
disabled={!serverRunning}
|
|
3823
|
+
className="flex items-center gap-1 px-2 py-0.5 rounded text-[10px] font-medium bg-cyan-600/20 hover:bg-cyan-600/40 text-cyan-400 disabled:opacity-40 transition-colors shrink-0"
|
|
3824
|
+
title={
|
|
3825
|
+
serverRunning
|
|
3826
|
+
? "Open the browser preview"
|
|
3827
|
+
: "Start the server first"
|
|
3828
|
+
}
|
|
3829
|
+
>
|
|
3830
|
+
<Globe className="w-3 h-3" />
|
|
3831
|
+
{scriptClientTab === "preview"
|
|
3832
|
+
? "Refresh"
|
|
3833
|
+
: "Preview"}
|
|
3834
|
+
</button>
|
|
3835
|
+
</>
|
|
3836
|
+
) : (
|
|
3837
|
+
<button
|
|
3838
|
+
type="button"
|
|
3839
|
+
onClick={() => void runClient()}
|
|
3840
|
+
disabled={clientRunning || !serverRunning}
|
|
3841
|
+
className="flex items-center gap-1 px-2 py-0.5 rounded text-[10px] font-medium bg-cyan-600/20 hover:bg-cyan-600/40 text-cyan-400 disabled:opacity-40 transition-colors shrink-0"
|
|
3842
|
+
title={
|
|
3843
|
+
serverRunning
|
|
3844
|
+
? "Run client (Ctrl+Enter in editor)"
|
|
3845
|
+
: "Start the server first"
|
|
3846
|
+
}
|
|
3847
|
+
>
|
|
3848
|
+
{clientRunning ? (
|
|
3849
|
+
<Loader2 className="w-3 h-3 animate-spin" />
|
|
3850
|
+
) : (
|
|
3851
|
+
<Play className="w-3 h-3" />
|
|
3852
|
+
)}
|
|
3853
|
+
Run
|
|
3854
|
+
</button>
|
|
3855
|
+
)}
|
|
3593
3856
|
</>
|
|
3594
3857
|
)}
|
|
3595
3858
|
{/* React/Next/Webpack mode controls */}
|
|
@@ -4046,19 +4309,75 @@ export default function CodeRunnerModal() {
|
|
|
4046
4309
|
className={`${usesClientExplorer ? "flex-1 min-w-0 relative" : "absolute inset-0"}`}
|
|
4047
4310
|
>
|
|
4048
4311
|
{clientType === "script" ? (
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4312
|
+
scriptSandboxOrigin === "browser-security" &&
|
|
4313
|
+
scriptClientTab === "preview" ? (
|
|
4314
|
+
<div className="w-full h-full flex flex-col bg-slate-950">
|
|
4315
|
+
<div className="flex items-center gap-1 px-2 py-1 bg-slate-800 border-b border-slate-700 shrink-0">
|
|
4316
|
+
<button
|
|
4317
|
+
type="button"
|
|
4318
|
+
onClick={openScriptPreview}
|
|
4319
|
+
disabled={!serverRunning}
|
|
4320
|
+
className="p-0.5 rounded text-slate-500 hover:text-slate-200 disabled:opacity-30 disabled:cursor-not-allowed transition-colors shrink-0"
|
|
4321
|
+
title="Refresh preview"
|
|
4322
|
+
>
|
|
4323
|
+
<svg
|
|
4324
|
+
className="w-3 h-3"
|
|
4325
|
+
viewBox="0 0 16 16"
|
|
4326
|
+
fill="currentColor"
|
|
4327
|
+
>
|
|
4328
|
+
<path d="M13.65 2.35A8 8 0 1 0 15 8h-2a6 6 0 1 1-1.1-3.48L10 6h5V1l-1.35 1.35z" />
|
|
4329
|
+
</svg>
|
|
4330
|
+
</button>
|
|
4331
|
+
<span className="text-slate-600 text-[9px] font-mono select-none shrink-0">
|
|
4332
|
+
{sandboxUrl
|
|
4333
|
+
? sandboxUrl.replace(/^https?:\/\//, "")
|
|
4334
|
+
: "Start server to preview"}
|
|
4335
|
+
</span>
|
|
4336
|
+
<div className="flex-1" />
|
|
4337
|
+
<span className="text-[9px] font-mono text-cyan-400 shrink-0">
|
|
4338
|
+
{serverRunning ? "● browser" : "server offline"}
|
|
4339
|
+
</span>
|
|
4340
|
+
</div>
|
|
4341
|
+
{sandboxUrl && scriptPreviewSrcDoc ? (
|
|
4342
|
+
<iframe
|
|
4343
|
+
srcDoc={scriptPreviewSrcDoc}
|
|
4344
|
+
className="flex-1 min-h-0 w-full border-0 bg-white"
|
|
4345
|
+
style={
|
|
4346
|
+
isDraggingResize
|
|
4347
|
+
? { pointerEvents: "none" }
|
|
4348
|
+
: undefined
|
|
4349
|
+
}
|
|
4350
|
+
title="Browser Security Preview"
|
|
4351
|
+
/>
|
|
4352
|
+
) : (
|
|
4353
|
+
<div className="flex-1 flex flex-col items-center justify-center gap-3 text-slate-400 text-sm bg-slate-950 px-6 text-center">
|
|
4354
|
+
<Globe className="w-8 h-8 text-cyan-400/70" />
|
|
4355
|
+
<p className="text-[12px]">
|
|
4356
|
+
Start the server to open the browser preview.
|
|
4357
|
+
</p>
|
|
4358
|
+
<p className="text-[10px] text-slate-600 max-w-md">
|
|
4359
|
+
Browser security labs render actual pages and
|
|
4360
|
+
iframes, so they use the preview pane instead of
|
|
4361
|
+
the Node client runner.
|
|
4362
|
+
</p>
|
|
4363
|
+
</div>
|
|
4364
|
+
)}
|
|
4365
|
+
</div>
|
|
4366
|
+
) : (
|
|
4367
|
+
<SyntaxEditor
|
|
4368
|
+
value={clientCode}
|
|
4369
|
+
onChange={setClientCode}
|
|
4370
|
+
onCtrlEnter={() => {
|
|
4371
|
+
if (serverRunning) void runClient();
|
|
4372
|
+
}}
|
|
4373
|
+
language={clientLang}
|
|
4374
|
+
fontSize="12px"
|
|
4375
|
+
focusRingClass="ring-cyan-500/30"
|
|
4376
|
+
placeholder={
|
|
4377
|
+
"// SANDBOX_URL is injected automatically\n// Start the server first, then Ctrl+Enter to run"
|
|
4378
|
+
}
|
|
4379
|
+
/>
|
|
4380
|
+
)
|
|
4062
4381
|
) : reactClientTab === "edit" ? (
|
|
4063
4382
|
isActiveModuleFederationGeneratedFile ? (
|
|
4064
4383
|
<div className="absolute inset-0 flex flex-col bg-slate-950">
|
|
@@ -4515,7 +4834,8 @@ export default function CodeRunnerModal() {
|
|
|
4515
4834
|
Output
|
|
4516
4835
|
</button>
|
|
4517
4836
|
{(clientType === "module-federation" ||
|
|
4518
|
-
clientType === "react"
|
|
4837
|
+
clientType === "react" ||
|
|
4838
|
+
clientType === "nextjs") && (
|
|
4519
4839
|
<button
|
|
4520
4840
|
type="button"
|
|
4521
4841
|
onClick={() => setSbxBottomTab("console")}
|
|
@@ -4684,7 +5004,9 @@ export default function CodeRunnerModal() {
|
|
|
4684
5004
|
? "Start Next.js to launch the live preview"
|
|
4685
5005
|
: clientType === "react"
|
|
4686
5006
|
? "Click Run Vite to start the dev server"
|
|
4687
|
-
:
|
|
5007
|
+
: scriptSandboxOrigin === "browser-security"
|
|
5008
|
+
? "Start the server to open the browser preview"
|
|
5009
|
+
: "Start the server, then run the client"}
|
|
4688
5010
|
</span>
|
|
4689
5011
|
)}
|
|
4690
5012
|
{sandboxOutput.map((line, i) => (
|
|
@@ -6036,24 +6358,32 @@ export default function CodeRunnerModal() {
|
|
|
6036
6358
|
value={
|
|
6037
6359
|
clientType === "react"
|
|
6038
6360
|
? viteConsoleCommand
|
|
6039
|
-
:
|
|
6361
|
+
: clientType === "nextjs"
|
|
6362
|
+
? nxConsoleCommand
|
|
6363
|
+
: mfConsoleCommand
|
|
6040
6364
|
}
|
|
6041
6365
|
onChange={(e) =>
|
|
6042
6366
|
clientType === "react"
|
|
6043
6367
|
? setViteConsoleCommand(e.target.value)
|
|
6044
|
-
:
|
|
6368
|
+
: clientType === "nextjs"
|
|
6369
|
+
? setNxConsoleCommand(e.target.value)
|
|
6370
|
+
: setMfConsoleCommand(e.target.value)
|
|
6045
6371
|
}
|
|
6046
6372
|
onKeyDown={(e) => {
|
|
6047
6373
|
if (e.key === "Enter") {
|
|
6048
6374
|
e.preventDefault();
|
|
6049
6375
|
if (clientType === "react") void runViteCommand();
|
|
6376
|
+
else if (clientType === "nextjs")
|
|
6377
|
+
void runNextjsCommand();
|
|
6050
6378
|
else void runModuleFederationCommand();
|
|
6051
6379
|
}
|
|
6052
6380
|
}}
|
|
6053
6381
|
disabled={
|
|
6054
6382
|
clientType === "react"
|
|
6055
6383
|
? !viteSandboxId || viteConsoleRunning
|
|
6056
|
-
:
|
|
6384
|
+
: clientType === "nextjs"
|
|
6385
|
+
? !nxSandboxId || nxConsoleRunning
|
|
6386
|
+
: !mfSandboxId || mfConsoleRunning
|
|
6057
6387
|
}
|
|
6058
6388
|
placeholder={
|
|
6059
6389
|
clientType === "react"
|
|
@@ -6067,6 +6397,7 @@ export default function CodeRunnerModal() {
|
|
|
6067
6397
|
type="button"
|
|
6068
6398
|
onClick={() => {
|
|
6069
6399
|
if (clientType === "react") void runViteCommand();
|
|
6400
|
+
else if (clientType === "nextjs") void runNextjsCommand();
|
|
6070
6401
|
else void runModuleFederationCommand();
|
|
6071
6402
|
}}
|
|
6072
6403
|
disabled={
|
|
@@ -6074,16 +6405,22 @@ export default function CodeRunnerModal() {
|
|
|
6074
6405
|
? !viteSandboxId ||
|
|
6075
6406
|
viteConsoleRunning ||
|
|
6076
6407
|
!viteConsoleCommand.trim()
|
|
6077
|
-
:
|
|
6078
|
-
|
|
6079
|
-
|
|
6408
|
+
: clientType === "nextjs"
|
|
6409
|
+
? !nxSandboxId ||
|
|
6410
|
+
nxConsoleRunning ||
|
|
6411
|
+
!nxConsoleCommand.trim()
|
|
6412
|
+
: !mfSandboxId ||
|
|
6413
|
+
mfConsoleRunning ||
|
|
6414
|
+
!mfConsoleCommand.trim()
|
|
6080
6415
|
}
|
|
6081
6416
|
className="flex items-center gap-1 px-2.5 py-1 rounded text-[11px] font-medium bg-cyan-600/20 hover:bg-cyan-600/35 text-cyan-300 disabled:opacity-50 transition-colors shrink-0"
|
|
6082
6417
|
>
|
|
6083
6418
|
{(
|
|
6084
6419
|
clientType === "react"
|
|
6085
6420
|
? viteConsoleRunning
|
|
6086
|
-
:
|
|
6421
|
+
: clientType === "nextjs"
|
|
6422
|
+
? nxConsoleRunning
|
|
6423
|
+
: mfConsoleRunning
|
|
6087
6424
|
) ? (
|
|
6088
6425
|
<Loader2 className="w-3 h-3 animate-spin" />
|
|
6089
6426
|
) : (
|
|
@@ -6107,30 +6444,44 @@ export default function CodeRunnerModal() {
|
|
|
6107
6444
|
<div className="shrink-0 px-3 py-1.5 text-[10px] text-slate-500 border-b border-slate-800">
|
|
6108
6445
|
{clientType === "react"
|
|
6109
6446
|
? "Run npm commands in the React lab — e.g. npm install lodash. Vite HMR reloads automatically."
|
|
6110
|
-
:
|
|
6447
|
+
: clientType === "nextjs"
|
|
6448
|
+
? "Run npm commands in the Next.js lab — e.g. npm run build. Hot reload applies on file save."
|
|
6449
|
+
: "Run npm scripts in the selected webpack app. Generated dist files appear in the explorer as read-only artifacts."}
|
|
6111
6450
|
</div>
|
|
6112
6451
|
<div className="flex-1 overflow-y-auto px-3 py-2 font-mono text-[12px] leading-relaxed">
|
|
6113
6452
|
{(() => {
|
|
6114
6453
|
const out =
|
|
6115
6454
|
clientType === "react"
|
|
6116
6455
|
? viteConsoleOutput
|
|
6117
|
-
:
|
|
6456
|
+
: clientType === "nextjs"
|
|
6457
|
+
? nxConsoleOutput
|
|
6458
|
+
: mfConsoleOutput;
|
|
6118
6459
|
const running =
|
|
6119
6460
|
clientType === "react"
|
|
6120
6461
|
? viteConsoleRunning
|
|
6121
|
-
:
|
|
6462
|
+
: clientType === "nextjs"
|
|
6463
|
+
? nxConsoleRunning
|
|
6464
|
+
: mfConsoleRunning;
|
|
6122
6465
|
const sandboxActive =
|
|
6123
|
-
clientType === "react"
|
|
6466
|
+
clientType === "react"
|
|
6467
|
+
? !!viteSandboxId
|
|
6468
|
+
: clientType === "nextjs"
|
|
6469
|
+
? !!nxSandboxId
|
|
6470
|
+
: !!mfSandboxId;
|
|
6124
6471
|
if (out.length === 0 && !running) {
|
|
6125
6472
|
return (
|
|
6126
6473
|
<span className="text-slate-600">
|
|
6127
6474
|
{sandboxActive
|
|
6128
6475
|
? clientType === "react"
|
|
6129
6476
|
? "Run npm install <pkg> to add a package."
|
|
6130
|
-
:
|
|
6477
|
+
: clientType === "nextjs"
|
|
6478
|
+
? "Run npm run build or any npm command here."
|
|
6479
|
+
: "Run npm run build in apps/host, apps/profile, or apps/checkout to inspect dist/."
|
|
6131
6480
|
: clientType === "react"
|
|
6132
6481
|
? "Start Vite first, then run npm commands here."
|
|
6133
|
-
:
|
|
6482
|
+
: clientType === "nextjs"
|
|
6483
|
+
? "Start Next.js first, then run npm commands here."
|
|
6484
|
+
: "Start webpack first, then run npm commands here."}
|
|
6134
6485
|
</span>
|
|
6135
6486
|
);
|
|
6136
6487
|
}
|