@illuma-ai/code-sandbox 1.2.2 → 1.3.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/dist/components/Terminal.d.ts +6 -15
- package/dist/index.cjs +76 -76
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +7844 -7629
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
- package/src/components/FileTree.tsx +364 -34
- package/src/components/Terminal.tsx +69 -210
- package/src/components/Workbench.tsx +41 -17
- package/src/styles.css +1 -42
- package/src/types.ts +4 -0
|
@@ -1,252 +1,111 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Terminal —
|
|
3
|
-
*
|
|
4
|
-
* Renders process stdout/stderr with color-coded output:
|
|
5
|
-
* - Commands ($ ...) get a green prompt + white command
|
|
6
|
-
* - stderr lines get red text
|
|
7
|
-
* - Success markers (checkmarks) get green
|
|
8
|
-
* - Warnings get yellow
|
|
9
|
-
* - Package names get purple
|
|
10
|
-
* - URLs and paths get cyan
|
|
11
|
-
* - Exit codes get colored by status
|
|
2
|
+
* Terminal — Clean monochrome terminal output panel with minimize toggle.
|
|
12
3
|
*
|
|
13
4
|
* Uses a simple div-based approach (not xterm.js) to keep the bundle lean.
|
|
14
5
|
* Auto-scrolls to bottom as new output arrives.
|
|
6
|
+
* Minimize/expand state is controlled by the parent via props.
|
|
15
7
|
*/
|
|
16
8
|
|
|
17
9
|
import React, { useEffect, useRef } from "react";
|
|
18
10
|
import type { TerminalProps } from "../types";
|
|
19
11
|
|
|
20
|
-
/** Classify a terminal output line for color coding */
|
|
21
|
-
type LineType =
|
|
22
|
-
| "prompt" // $ command
|
|
23
|
-
| "stderr" // [stderr] ...
|
|
24
|
-
| "success" // ✓ ...
|
|
25
|
-
| "error" // ✗ ... or error messages
|
|
26
|
-
| "warning" // warn / Warning
|
|
27
|
-
| "info" // Installing, Dependencies, info lines
|
|
28
|
-
| "exit-ok" // Process exited with code 0
|
|
29
|
-
| "exit-fail" // Process exited with non-zero code
|
|
30
|
-
| "dim" // Empty or separator lines
|
|
31
|
-
| "normal"; // Default output
|
|
32
|
-
|
|
33
12
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* Pattern matching order matters — more specific patterns first.
|
|
37
|
-
*/
|
|
38
|
-
function classifyLine(line: string): LineType {
|
|
39
|
-
const trimmed = line.trim();
|
|
40
|
-
|
|
41
|
-
// Empty or whitespace-only
|
|
42
|
-
if (!trimmed) return "dim";
|
|
43
|
-
|
|
44
|
-
// Explicit stderr prefix from runtime
|
|
45
|
-
if (trimmed.startsWith("[stderr]")) return "stderr";
|
|
46
|
-
|
|
47
|
-
// Command prompt ($ node server.js)
|
|
48
|
-
if (/^\$\s/.test(trimmed)) return "prompt";
|
|
49
|
-
|
|
50
|
-
// Success indicators
|
|
51
|
-
if (/^\s*✓/.test(line) || /^\s*✔/.test(line) || /^done$/i.test(trimmed))
|
|
52
|
-
return "success";
|
|
53
|
-
|
|
54
|
-
// Error indicators
|
|
55
|
-
if (
|
|
56
|
-
/^\s*✗/.test(line) ||
|
|
57
|
-
/^\s*✘/.test(line) ||
|
|
58
|
-
/^error\b/i.test(trimmed) ||
|
|
59
|
-
/^Error:/i.test(trimmed) ||
|
|
60
|
-
/^TypeError:/i.test(trimmed) ||
|
|
61
|
-
/^ReferenceError:/i.test(trimmed) ||
|
|
62
|
-
/^SyntaxError:/i.test(trimmed) ||
|
|
63
|
-
/ENOENT|EACCES|ECONNREFUSED/i.test(trimmed)
|
|
64
|
-
)
|
|
65
|
-
return "error";
|
|
66
|
-
|
|
67
|
-
// Process exit
|
|
68
|
-
if (/^Process exited with code\s+(\d+)/.test(trimmed)) {
|
|
69
|
-
const code = trimmed.match(/code\s+(\d+)/)?.[1];
|
|
70
|
-
return code === "0" ? "exit-ok" : "exit-fail";
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Warning
|
|
74
|
-
if (
|
|
75
|
-
/^warn\b/i.test(trimmed) ||
|
|
76
|
-
/^warning\b/i.test(trimmed) ||
|
|
77
|
-
/^Warning:/i.test(trimmed) ||
|
|
78
|
-
/deprecated/i.test(trimmed)
|
|
79
|
-
)
|
|
80
|
-
return "warning";
|
|
81
|
-
|
|
82
|
-
// Info / install progress
|
|
83
|
-
if (
|
|
84
|
-
/^Installing\b/i.test(trimmed) ||
|
|
85
|
-
/^Dependencies\s+installed/i.test(trimmed) ||
|
|
86
|
-
/^Listening\b/i.test(trimmed) ||
|
|
87
|
-
/^Server\s+(running|started|listening)/i.test(trimmed) ||
|
|
88
|
-
/^Starting\b/i.test(trimmed)
|
|
89
|
-
)
|
|
90
|
-
return "info";
|
|
91
|
-
|
|
92
|
-
return "normal";
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/** CSS class name for each line type */
|
|
96
|
-
const LINE_CLASS: Record<LineType, string> = {
|
|
97
|
-
prompt: "", // handled specially (split into prompt + command)
|
|
98
|
-
stderr: "sb-term-stderr",
|
|
99
|
-
success: "sb-term-success",
|
|
100
|
-
error: "sb-term-stderr",
|
|
101
|
-
warning: "sb-term-warning",
|
|
102
|
-
info: "sb-term-info",
|
|
103
|
-
"exit-ok": "sb-term-exit-ok",
|
|
104
|
-
"exit-fail": "sb-term-exit-fail",
|
|
105
|
-
dim: "sb-term-dim",
|
|
106
|
-
normal: "sb-term-line",
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Render a single terminal line with appropriate styling.
|
|
111
|
-
*
|
|
112
|
-
* Prompt lines are split into a green "$ " prefix and white command text.
|
|
113
|
-
* Stderr lines strip the "[stderr]" prefix and show in red.
|
|
114
|
-
* Package install lines highlight the package name in purple.
|
|
115
|
-
*/
|
|
116
|
-
function TerminalLine({ line }: { line: string }) {
|
|
117
|
-
const type = classifyLine(line);
|
|
118
|
-
|
|
119
|
-
// Prompt line: split into green prompt + white command
|
|
120
|
-
if (type === "prompt") {
|
|
121
|
-
const commandText = line.replace(/^\$\s*/, "");
|
|
122
|
-
return (
|
|
123
|
-
<div className="whitespace-pre-wrap">
|
|
124
|
-
<span className="sb-term-prompt">{"$ "}</span>
|
|
125
|
-
<span className="sb-term-cmd">{commandText}</span>
|
|
126
|
-
</div>
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Stderr: strip prefix, show in red
|
|
131
|
-
if (type === "stderr") {
|
|
132
|
-
const message = line.replace(/^\[stderr\]\s*/, "");
|
|
133
|
-
return (
|
|
134
|
-
<div className="whitespace-pre-wrap sb-term-stderr">
|
|
135
|
-
<span className="sb-term-dim select-none">{"stderr "}</span>
|
|
136
|
-
{message}
|
|
137
|
-
</div>
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Package install success: " ✓ express@4.18.0" → green check + purple pkg
|
|
142
|
-
if (type === "success" && /✓\s+\S+@/.test(line)) {
|
|
143
|
-
const match = line.match(/(.*✓\s*)(\S+@\S+)(.*)/);
|
|
144
|
-
if (match) {
|
|
145
|
-
return (
|
|
146
|
-
<div className="whitespace-pre-wrap sb-term-success">
|
|
147
|
-
{match[1]}
|
|
148
|
-
<span className="sb-term-pkg">{match[2]}</span>
|
|
149
|
-
{match[3]}
|
|
150
|
-
</div>
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Info lines with port numbers: highlight the port
|
|
156
|
-
if (type === "info" && /:\d{4}/.test(line)) {
|
|
157
|
-
const parts = line.split(/(:\d{4,5})/);
|
|
158
|
-
return (
|
|
159
|
-
<div className="whitespace-pre-wrap sb-term-info">
|
|
160
|
-
{parts.map((part, i) =>
|
|
161
|
-
/^:\d{4,5}$/.test(part) ? (
|
|
162
|
-
<span key={i} className="sb-term-path">
|
|
163
|
-
{part}
|
|
164
|
-
</span>
|
|
165
|
-
) : (
|
|
166
|
-
<span key={i}>{part}</span>
|
|
167
|
-
),
|
|
168
|
-
)}
|
|
169
|
-
</div>
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return (
|
|
174
|
-
<div className={`whitespace-pre-wrap ${LINE_CLASS[type]}`}>{line}</div>
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Terminal component — renders process output in a git-bash inspired panel.
|
|
13
|
+
* Terminal component — renders process output in a clean monochrome panel.
|
|
180
14
|
*
|
|
181
15
|
* Features:
|
|
182
|
-
* -
|
|
183
|
-
* - Monospace font stack (Cascadia Code → JetBrains Mono → Fira Code → fallback)
|
|
16
|
+
* - Monospace font with proper line spacing
|
|
184
17
|
* - Auto-scroll to bottom on new output
|
|
18
|
+
* - Minimize/expand toggle via chevron in header (parent-controlled)
|
|
185
19
|
* - Modern thin scrollbar matching the sandbox theme
|
|
186
|
-
* - Dark background separate from editor for visual distinction
|
|
187
20
|
*/
|
|
188
|
-
export function Terminal({
|
|
21
|
+
export function Terminal({
|
|
22
|
+
output,
|
|
23
|
+
className = "",
|
|
24
|
+
minimized = false,
|
|
25
|
+
onToggleMinimize,
|
|
26
|
+
}: TerminalProps) {
|
|
189
27
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
190
28
|
|
|
191
29
|
// Auto-scroll to bottom when new output arrives
|
|
192
30
|
useEffect(() => {
|
|
193
31
|
const el = containerRef.current;
|
|
194
|
-
if (el) {
|
|
32
|
+
if (el && !minimized) {
|
|
195
33
|
el.scrollTop = el.scrollHeight;
|
|
196
34
|
}
|
|
197
|
-
}, [output.length]);
|
|
35
|
+
}, [output.length, minimized]);
|
|
198
36
|
|
|
199
37
|
return (
|
|
200
38
|
<div
|
|
201
|
-
className={`flex flex-col h-full ${className}`}
|
|
39
|
+
className={`flex flex-col ${minimized ? "" : "h-full"} ${className}`}
|
|
202
40
|
style={{ background: "var(--sb-terminal)" }}
|
|
203
41
|
>
|
|
204
|
-
{/* Header
|
|
42
|
+
{/* Header */}
|
|
205
43
|
<div
|
|
206
|
-
className="flex items-center gap-2 px-3 py-1
|
|
44
|
+
className="flex items-center gap-2 px-3 py-1 border-t border-sb-border shrink-0 select-none cursor-pointer"
|
|
207
45
|
style={{ background: "var(--sb-terminal-header)" }}
|
|
46
|
+
onClick={onToggleMinimize}
|
|
208
47
|
>
|
|
209
|
-
{/*
|
|
210
|
-
<
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
48
|
+
{/* Chevron toggle */}
|
|
49
|
+
<svg
|
|
50
|
+
width="14"
|
|
51
|
+
height="14"
|
|
52
|
+
viewBox="0 0 16 16"
|
|
53
|
+
fill="none"
|
|
54
|
+
className="shrink-0 transition-transform duration-150"
|
|
55
|
+
style={{
|
|
56
|
+
transform: minimized ? "rotate(-90deg)" : "rotate(0deg)",
|
|
57
|
+
color: "var(--sb-text-muted)",
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
<path
|
|
61
|
+
d="M4 6l4 4 4-4"
|
|
62
|
+
stroke="currentColor"
|
|
63
|
+
strokeWidth="1.5"
|
|
64
|
+
strokeLinecap="round"
|
|
65
|
+
strokeLinejoin="round"
|
|
222
66
|
/>
|
|
223
|
-
</
|
|
67
|
+
</svg>
|
|
224
68
|
<span
|
|
225
69
|
className="text-[11px] font-medium tracking-wider"
|
|
226
70
|
style={{ color: "var(--sb-text-muted)" }}
|
|
227
71
|
>
|
|
228
72
|
TERMINAL
|
|
229
73
|
</span>
|
|
230
|
-
{/*
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
ref={containerRef}
|
|
239
|
-
className="flex-1 overflow-auto px-3.5 py-2.5 sb-terminal-output overscroll-contain"
|
|
240
|
-
>
|
|
241
|
-
{output.length === 0 ? (
|
|
242
|
-
<div className="flex items-center gap-2 sb-term-dim">
|
|
243
|
-
<span className="sb-term-prompt">$</span>
|
|
244
|
-
<span className="animate-pulse">_</span>
|
|
245
|
-
</div>
|
|
246
|
-
) : (
|
|
247
|
-
output.map((line, i) => <TerminalLine key={i} line={line} />)
|
|
74
|
+
{/* Line count badge */}
|
|
75
|
+
{output.length > 0 && (
|
|
76
|
+
<span
|
|
77
|
+
className="text-[10px] ml-auto tabular-nums"
|
|
78
|
+
style={{ color: "var(--sb-text-muted)", opacity: 0.6 }}
|
|
79
|
+
>
|
|
80
|
+
{output.length} lines
|
|
81
|
+
</span>
|
|
248
82
|
)}
|
|
249
83
|
</div>
|
|
84
|
+
|
|
85
|
+
{/* Output area — hidden when minimized */}
|
|
86
|
+
{!minimized && (
|
|
87
|
+
<div
|
|
88
|
+
ref={containerRef}
|
|
89
|
+
className="flex-1 overflow-auto px-3.5 py-2 sb-terminal-output overscroll-contain min-h-0"
|
|
90
|
+
>
|
|
91
|
+
{output.length === 0 ? (
|
|
92
|
+
<div style={{ color: "var(--sb-text-muted)" }}>
|
|
93
|
+
<span>$ </span>
|
|
94
|
+
<span className="animate-pulse">_</span>
|
|
95
|
+
</div>
|
|
96
|
+
) : (
|
|
97
|
+
output.map((line, i) => (
|
|
98
|
+
<div
|
|
99
|
+
key={i}
|
|
100
|
+
className="whitespace-pre-wrap"
|
|
101
|
+
style={{ color: "var(--sb-text)" }}
|
|
102
|
+
>
|
|
103
|
+
{line}
|
|
104
|
+
</div>
|
|
105
|
+
))
|
|
106
|
+
)}
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
250
109
|
</div>
|
|
251
110
|
);
|
|
252
111
|
}
|
|
@@ -303,6 +303,7 @@ interface CodeViewProps {
|
|
|
303
303
|
/**
|
|
304
304
|
* CodeView — FileTree (left) + Editor (top-right) + Terminal (bottom-right).
|
|
305
305
|
* Uses Allotment for resizable split panes.
|
|
306
|
+
* Terminal can be minimized to just a header bar.
|
|
306
307
|
* Responsive: file tree shrinks on narrow containers.
|
|
307
308
|
*/
|
|
308
309
|
function CodeView({
|
|
@@ -317,6 +318,8 @@ function CodeView({
|
|
|
317
318
|
onCloseFile,
|
|
318
319
|
onFileChange,
|
|
319
320
|
}: CodeViewProps) {
|
|
321
|
+
const [terminalMinimized, setTerminalMinimized] = useState(false);
|
|
322
|
+
|
|
320
323
|
return (
|
|
321
324
|
<div className="h-full w-full">
|
|
322
325
|
<Allotment>
|
|
@@ -332,24 +335,45 @@ function CodeView({
|
|
|
332
335
|
|
|
333
336
|
{/* Editor + Terminal — right */}
|
|
334
337
|
<Allotment.Pane>
|
|
335
|
-
<
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
338
|
+
<div className="h-full flex flex-col">
|
|
339
|
+
{/* Editor takes remaining space */}
|
|
340
|
+
<div
|
|
341
|
+
className={
|
|
342
|
+
terminalMinimized ? "flex-1 min-h-0" : "flex-1 min-h-0"
|
|
343
|
+
}
|
|
344
|
+
style={{ height: terminalMinimized ? "100%" : "70%" }}
|
|
345
|
+
>
|
|
346
|
+
<div className="h-full">
|
|
347
|
+
<CodeEditor
|
|
348
|
+
files={files}
|
|
349
|
+
originalFiles={originalFiles}
|
|
350
|
+
fileChanges={fileChanges}
|
|
351
|
+
activeFile={selectedFile}
|
|
352
|
+
openFiles={openFiles}
|
|
353
|
+
onSelectFile={onSelectFile}
|
|
354
|
+
onCloseFile={onCloseFile}
|
|
355
|
+
onFileChange={onFileChange}
|
|
356
|
+
/>
|
|
357
|
+
</div>
|
|
358
|
+
</div>
|
|
348
359
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
360
|
+
{/* Terminal — collapsible */}
|
|
361
|
+
{terminalMinimized ? (
|
|
362
|
+
<Terminal
|
|
363
|
+
output={terminalOutput}
|
|
364
|
+
minimized={true}
|
|
365
|
+
onToggleMinimize={() => setTerminalMinimized(false)}
|
|
366
|
+
/>
|
|
367
|
+
) : (
|
|
368
|
+
<div style={{ height: "30%", minHeight: 60 }}>
|
|
369
|
+
<Terminal
|
|
370
|
+
output={terminalOutput}
|
|
371
|
+
minimized={false}
|
|
372
|
+
onToggleMinimize={() => setTerminalMinimized(true)}
|
|
373
|
+
/>
|
|
374
|
+
</div>
|
|
375
|
+
)}
|
|
376
|
+
</div>
|
|
353
377
|
</Allotment.Pane>
|
|
354
378
|
</Allotment>
|
|
355
379
|
</div>
|
package/src/styles.css
CHANGED
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
/* ---------------------------------------------------------------------------
|
|
70
|
-
* Terminal
|
|
70
|
+
* Terminal — clean monochrome styling
|
|
71
71
|
* --------------------------------------------------------------------------- */
|
|
72
72
|
|
|
73
73
|
.sb-terminal-output {
|
|
@@ -81,47 +81,6 @@
|
|
|
81
81
|
-moz-osx-font-smoothing: grayscale;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
/* Terminal line colors via data attribute */
|
|
85
|
-
.sb-term-line {
|
|
86
|
-
color: #c9d1d9;
|
|
87
|
-
}
|
|
88
|
-
.sb-term-prompt {
|
|
89
|
-
color: var(--sb-success);
|
|
90
|
-
font-weight: 600;
|
|
91
|
-
}
|
|
92
|
-
.sb-term-cmd {
|
|
93
|
-
color: #f0f6fc;
|
|
94
|
-
font-weight: 500;
|
|
95
|
-
}
|
|
96
|
-
.sb-term-stderr {
|
|
97
|
-
color: var(--sb-error);
|
|
98
|
-
}
|
|
99
|
-
.sb-term-success {
|
|
100
|
-
color: var(--sb-success);
|
|
101
|
-
}
|
|
102
|
-
.sb-term-warning {
|
|
103
|
-
color: var(--sb-warning);
|
|
104
|
-
}
|
|
105
|
-
.sb-term-info {
|
|
106
|
-
color: var(--sb-info);
|
|
107
|
-
}
|
|
108
|
-
.sb-term-dim {
|
|
109
|
-
color: #484f58;
|
|
110
|
-
}
|
|
111
|
-
.sb-term-path {
|
|
112
|
-
color: var(--sb-cyan);
|
|
113
|
-
}
|
|
114
|
-
.sb-term-pkg {
|
|
115
|
-
color: var(--sb-magenta);
|
|
116
|
-
}
|
|
117
|
-
.sb-term-exit-ok {
|
|
118
|
-
color: var(--sb-success);
|
|
119
|
-
}
|
|
120
|
-
.sb-term-exit-fail {
|
|
121
|
-
color: var(--sb-error);
|
|
122
|
-
font-weight: 600;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
84
|
/*
|
|
126
85
|
* Ranger Theme Variables — bridged from ranger/client/src/style.css.
|
|
127
86
|
*
|
package/src/types.ts
CHANGED
|
@@ -343,6 +343,10 @@ export interface CodeEditorProps {
|
|
|
343
343
|
export interface TerminalProps {
|
|
344
344
|
output: string[];
|
|
345
345
|
className?: string;
|
|
346
|
+
/** Whether the terminal is collapsed to just its header bar */
|
|
347
|
+
minimized?: boolean;
|
|
348
|
+
/** Called when the user clicks the minimize/expand toggle */
|
|
349
|
+
onToggleMinimize?: () => void;
|
|
346
350
|
}
|
|
347
351
|
|
|
348
352
|
export interface PreviewProps {
|