@illuma-ai/code-sandbox 1.4.0 → 1.4.1
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/LICENSE +18 -12
- package/README.md +1 -1
- package/package.json +3 -4
- package/src/components/BootOverlay.tsx +0 -145
- package/src/components/CodeEditor.tsx +0 -298
- package/src/components/FileTree.tsx +0 -678
- package/src/components/Preview.tsx +0 -262
- package/src/components/Terminal.tsx +0 -111
- package/src/components/ViewSlider.tsx +0 -87
- package/src/components/Workbench.tsx +0 -382
- package/src/hooks/useRuntime.ts +0 -637
- package/src/index.ts +0 -51
- package/src/services/runtime.ts +0 -775
- package/src/styles.css +0 -178
- package/src/templates/fullstack-starter.ts +0 -3507
- package/src/templates/index.ts +0 -607
- package/src/types.ts +0 -375
package/LICENSE
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
MIT License
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Copyright (c) 2026 Illuma AI
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@illuma-ai/code-sandbox",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"license": "
|
|
5
|
+
"license": "MIT",
|
|
6
6
|
"author": "Illuma AI (https://github.com/illuma-ai)",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
@@ -24,8 +24,7 @@
|
|
|
24
24
|
"./styles.css": "./dist/styles.css"
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
|
-
"dist"
|
|
28
|
-
"src"
|
|
27
|
+
"dist"
|
|
29
28
|
],
|
|
30
29
|
"scripts": {
|
|
31
30
|
"dev": "vite",
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BootOverlay — Loading animation displayed while Nodepod boots.
|
|
3
|
-
*
|
|
4
|
-
* Shows staged progress: initializing → writing files → installing → starting → ready.
|
|
5
|
-
* Includes a progress bar and stage-specific messaging with a pulsing animation.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import React from "react";
|
|
9
|
-
import type { BootOverlayProps } from "../types";
|
|
10
|
-
|
|
11
|
-
/** Stage display configuration */
|
|
12
|
-
const STAGE_CONFIG: Record<
|
|
13
|
-
string,
|
|
14
|
-
{ icon: string; label: string; color: string }
|
|
15
|
-
> = {
|
|
16
|
-
initializing: {
|
|
17
|
-
icon: "⚡",
|
|
18
|
-
label: "Initializing Runtime",
|
|
19
|
-
color: "text-blue-400",
|
|
20
|
-
},
|
|
21
|
-
"writing-files": {
|
|
22
|
-
icon: "📁",
|
|
23
|
-
label: "Writing Project Files",
|
|
24
|
-
color: "text-yellow-400",
|
|
25
|
-
},
|
|
26
|
-
installing: {
|
|
27
|
-
icon: "📦",
|
|
28
|
-
label: "Installing Dependencies",
|
|
29
|
-
color: "text-purple-400",
|
|
30
|
-
},
|
|
31
|
-
starting: {
|
|
32
|
-
icon: "🚀",
|
|
33
|
-
label: "Starting Application",
|
|
34
|
-
color: "text-green-400",
|
|
35
|
-
},
|
|
36
|
-
ready: { icon: "✅", label: "Application Ready", color: "text-green-400" },
|
|
37
|
-
error: { icon: "❌", label: "Error", color: "text-red-400" },
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Full-screen loading overlay with staged boot progress.
|
|
42
|
-
*
|
|
43
|
-
* Renders over the workbench while Nodepod boots. Shows:
|
|
44
|
-
* - Current stage icon + label
|
|
45
|
-
* - Progress bar with percentage
|
|
46
|
-
* - Descriptive message of what's happening
|
|
47
|
-
* - Pulsing dots animation
|
|
48
|
-
*/
|
|
49
|
-
export function BootOverlay({ progress, className = "" }: BootOverlayProps) {
|
|
50
|
-
const config = STAGE_CONFIG[progress.stage] || STAGE_CONFIG.initializing;
|
|
51
|
-
|
|
52
|
-
if (progress.stage === "ready") {
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
<div
|
|
58
|
-
className={`absolute inset-0 z-50 flex items-center justify-center bg-sb-bg/95 backdrop-blur-sm ${className}`}
|
|
59
|
-
>
|
|
60
|
-
<div className="flex flex-col items-center gap-6 max-w-md px-8">
|
|
61
|
-
{/* Animated icon */}
|
|
62
|
-
<div className="text-5xl animate-bounce">{config.icon}</div>
|
|
63
|
-
|
|
64
|
-
{/* Stage label */}
|
|
65
|
-
<h2 className={`text-xl font-semibold ${config.color}`}>
|
|
66
|
-
{config.label}
|
|
67
|
-
{progress.stage !== "error" && <PulsingDots />}
|
|
68
|
-
</h2>
|
|
69
|
-
|
|
70
|
-
{/* Progress bar */}
|
|
71
|
-
<div className="w-64 h-2 bg-sb-bg-active rounded-full overflow-hidden">
|
|
72
|
-
<div
|
|
73
|
-
className="h-full bg-sb-accent rounded-full transition-all duration-500 ease-out"
|
|
74
|
-
style={{ width: `${progress.percent}%` }}
|
|
75
|
-
/>
|
|
76
|
-
</div>
|
|
77
|
-
|
|
78
|
-
{/* Message */}
|
|
79
|
-
<p className="text-sm text-sb-text-muted text-center">
|
|
80
|
-
{progress.message}
|
|
81
|
-
</p>
|
|
82
|
-
|
|
83
|
-
{/* Stage indicators */}
|
|
84
|
-
<div className="flex items-center gap-2 mt-4">
|
|
85
|
-
{["initializing", "writing-files", "installing", "starting"].map(
|
|
86
|
-
(stage, i) => {
|
|
87
|
-
const isCompleted = getStageOrder(progress.stage) > i;
|
|
88
|
-
const isCurrent = progress.stage === stage;
|
|
89
|
-
|
|
90
|
-
return (
|
|
91
|
-
<React.Fragment key={stage}>
|
|
92
|
-
<div
|
|
93
|
-
className={`w-3 h-3 rounded-full transition-colors duration-300 ${
|
|
94
|
-
isCompleted
|
|
95
|
-
? "bg-sb-success"
|
|
96
|
-
: isCurrent
|
|
97
|
-
? "bg-sb-accent animate-pulse"
|
|
98
|
-
: "bg-sb-bg-active"
|
|
99
|
-
}`}
|
|
100
|
-
/>
|
|
101
|
-
{i < 3 && (
|
|
102
|
-
<div
|
|
103
|
-
className={`w-8 h-0.5 transition-colors duration-300 ${
|
|
104
|
-
isCompleted ? "bg-sb-success" : "bg-sb-bg-active"
|
|
105
|
-
}`}
|
|
106
|
-
/>
|
|
107
|
-
)}
|
|
108
|
-
</React.Fragment>
|
|
109
|
-
);
|
|
110
|
-
},
|
|
111
|
-
)}
|
|
112
|
-
</div>
|
|
113
|
-
</div>
|
|
114
|
-
</div>
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/** Get numeric order of a boot stage for progress indicators */
|
|
119
|
-
function getStageOrder(stage: string): number {
|
|
120
|
-
const order = [
|
|
121
|
-
"initializing",
|
|
122
|
-
"writing-files",
|
|
123
|
-
"installing",
|
|
124
|
-
"starting",
|
|
125
|
-
"ready",
|
|
126
|
-
];
|
|
127
|
-
return order.indexOf(stage);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/** Animated "..." dots */
|
|
131
|
-
function PulsingDots() {
|
|
132
|
-
return (
|
|
133
|
-
<span className="inline-flex ml-1">
|
|
134
|
-
<span className="animate-pulse" style={{ animationDelay: "0ms" }}>
|
|
135
|
-
.
|
|
136
|
-
</span>
|
|
137
|
-
<span className="animate-pulse" style={{ animationDelay: "200ms" }}>
|
|
138
|
-
.
|
|
139
|
-
</span>
|
|
140
|
-
<span className="animate-pulse" style={{ animationDelay: "400ms" }}>
|
|
141
|
-
.
|
|
142
|
-
</span>
|
|
143
|
-
</span>
|
|
144
|
-
);
|
|
145
|
-
}
|
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CodeEditor — Monaco editor with file tabs, diff mode, and change indicators.
|
|
3
|
-
*
|
|
4
|
-
* Uses @monaco-editor/react for both the regular editor and diff editor.
|
|
5
|
-
* Renders a tab bar of open files with change status indicators:
|
|
6
|
-
* - Green dot: new file
|
|
7
|
-
* - Orange dot: modified file
|
|
8
|
-
* - Red dot: deleted file (shown as read-only)
|
|
9
|
-
*
|
|
10
|
-
* When diff mode is active, displays Monaco's side-by-side DiffEditor
|
|
11
|
-
* comparing the original file content (left) with the current content (right).
|
|
12
|
-
*
|
|
13
|
-
* @see @monaco-editor/react DiffEditor: https://github.com/suren-atoyan/monaco-react
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import React, { useCallback, useMemo, useState } from "react";
|
|
17
|
-
import type { CodeEditorProps, FileChangeStatus } from "../types";
|
|
18
|
-
|
|
19
|
-
// Lazy import Monaco to keep the bundle splittable
|
|
20
|
-
let MonacoEditor: React.ComponentType<any> | null = null;
|
|
21
|
-
let MonacoDiffEditor: React.ComponentType<any> | null = null;
|
|
22
|
-
let monacoLoadPromise: Promise<void> | null = null;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Load Monaco editor (regular + diff) dynamically.
|
|
26
|
-
* Returns both Editor and DiffEditor from @monaco-editor/react.
|
|
27
|
-
*/
|
|
28
|
-
function loadMonaco(): Promise<void> {
|
|
29
|
-
if (MonacoEditor && MonacoDiffEditor) return Promise.resolve();
|
|
30
|
-
if (monacoLoadPromise) return monacoLoadPromise;
|
|
31
|
-
|
|
32
|
-
monacoLoadPromise = import("@monaco-editor/react").then((mod) => {
|
|
33
|
-
MonacoEditor = mod.default || mod.Editor;
|
|
34
|
-
MonacoDiffEditor = mod.DiffEditor;
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
return monacoLoadPromise;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Get Monaco language from file path.
|
|
42
|
-
*/
|
|
43
|
-
function getLanguage(path: string): string {
|
|
44
|
-
const ext = path.split(".").pop()?.toLowerCase() || "";
|
|
45
|
-
const langMap: Record<string, string> = {
|
|
46
|
-
js: "javascript",
|
|
47
|
-
jsx: "javascript",
|
|
48
|
-
ts: "typescript",
|
|
49
|
-
tsx: "typescript",
|
|
50
|
-
json: "json",
|
|
51
|
-
html: "html",
|
|
52
|
-
htm: "html",
|
|
53
|
-
css: "css",
|
|
54
|
-
scss: "scss",
|
|
55
|
-
less: "less",
|
|
56
|
-
md: "markdown",
|
|
57
|
-
py: "python",
|
|
58
|
-
rb: "ruby",
|
|
59
|
-
go: "go",
|
|
60
|
-
rs: "rust",
|
|
61
|
-
yml: "yaml",
|
|
62
|
-
yaml: "yaml",
|
|
63
|
-
xml: "xml",
|
|
64
|
-
sql: "sql",
|
|
65
|
-
sh: "shell",
|
|
66
|
-
bash: "shell",
|
|
67
|
-
svg: "xml",
|
|
68
|
-
};
|
|
69
|
-
return langMap[ext] || "plaintext";
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** Color for each file change status indicator dot */
|
|
73
|
-
const STATUS_COLORS: Record<FileChangeStatus, string> = {
|
|
74
|
-
new: "#4ade80", // green-400
|
|
75
|
-
modified: "#fb923c", // orange-400
|
|
76
|
-
deleted: "#f87171", // red-400
|
|
77
|
-
unchanged: "transparent",
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
/** Tooltip for each file change status */
|
|
81
|
-
const STATUS_LABELS: Record<FileChangeStatus, string> = {
|
|
82
|
-
new: "New file",
|
|
83
|
-
modified: "Modified",
|
|
84
|
-
deleted: "Deleted",
|
|
85
|
-
unchanged: "",
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* CodeEditor component — Monaco editor with file tabs, diff view, and
|
|
90
|
-
* change status indicators.
|
|
91
|
-
*
|
|
92
|
-
* When `originalFiles` has content for the active file (meaning a prior
|
|
93
|
-
* version exists), a "Diff" toggle button appears in the tab bar. Clicking
|
|
94
|
-
* it switches to Monaco's DiffEditor showing the old (left) vs new (right).
|
|
95
|
-
*/
|
|
96
|
-
export function CodeEditor({
|
|
97
|
-
files,
|
|
98
|
-
originalFiles,
|
|
99
|
-
fileChanges,
|
|
100
|
-
activeFile,
|
|
101
|
-
openFiles,
|
|
102
|
-
onSelectFile,
|
|
103
|
-
onCloseFile,
|
|
104
|
-
onFileChange,
|
|
105
|
-
readOnly = false,
|
|
106
|
-
}: CodeEditorProps) {
|
|
107
|
-
const [loaded, setLoaded] = React.useState(!!MonacoEditor);
|
|
108
|
-
const [diffMode, setDiffMode] = useState(false);
|
|
109
|
-
|
|
110
|
-
// Load Monaco on mount
|
|
111
|
-
React.useEffect(() => {
|
|
112
|
-
if (!MonacoEditor) {
|
|
113
|
-
loadMonaco().then(() => setLoaded(true));
|
|
114
|
-
}
|
|
115
|
-
}, []);
|
|
116
|
-
|
|
117
|
-
const activeContent = activeFile ? files[activeFile] || "" : "";
|
|
118
|
-
const activeOriginalContent = activeFile
|
|
119
|
-
? (originalFiles[activeFile] ?? "")
|
|
120
|
-
: "";
|
|
121
|
-
const language = activeFile ? getLanguage(activeFile) : "plaintext";
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Whether the active file has a diff available.
|
|
125
|
-
* A diff is available when:
|
|
126
|
-
* - The file is marked as "modified" (content differs from original), OR
|
|
127
|
-
* - The file is marked as "new" (no original — diff shows empty left side)
|
|
128
|
-
*/
|
|
129
|
-
const hasDiff = activeFile
|
|
130
|
-
? fileChanges[activeFile] === "modified" ||
|
|
131
|
-
fileChanges[activeFile] === "new"
|
|
132
|
-
: false;
|
|
133
|
-
|
|
134
|
-
// Auto-disable diff mode when switching to a file with no diff
|
|
135
|
-
React.useEffect(() => {
|
|
136
|
-
if (!hasDiff && diffMode) {
|
|
137
|
-
setDiffMode(false);
|
|
138
|
-
}
|
|
139
|
-
}, [hasDiff, diffMode, activeFile]);
|
|
140
|
-
|
|
141
|
-
const handleEditorChange = useCallback(
|
|
142
|
-
(value: string | undefined) => {
|
|
143
|
-
if (activeFile && value !== undefined) {
|
|
144
|
-
onFileChange(activeFile, value);
|
|
145
|
-
}
|
|
146
|
-
},
|
|
147
|
-
[activeFile, onFileChange],
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Handler for Monaco DiffEditor's modified content changes.
|
|
152
|
-
* The DiffEditor fires `onMount` with the editor instance — we
|
|
153
|
-
* listen to the modified editor's onDidChangeModelContent event.
|
|
154
|
-
*/
|
|
155
|
-
const handleDiffEditorMount = useCallback(
|
|
156
|
-
(editor: any) => {
|
|
157
|
-
if (!editor || !activeFile) return;
|
|
158
|
-
const modifiedEditor = editor.getModifiedEditor();
|
|
159
|
-
modifiedEditor.onDidChangeModelContent(() => {
|
|
160
|
-
const value = modifiedEditor.getValue();
|
|
161
|
-
onFileChange(activeFile, value);
|
|
162
|
-
});
|
|
163
|
-
},
|
|
164
|
-
[activeFile, onFileChange],
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
return (
|
|
168
|
-
<div className="flex flex-col h-full bg-sb-editor">
|
|
169
|
-
{/* Tab bar */}
|
|
170
|
-
<div className="flex items-center bg-sb-bg-alt border-b border-sb-border overflow-x-auto">
|
|
171
|
-
{openFiles.map((path) => {
|
|
172
|
-
const fileName = path.split("/").pop() || path;
|
|
173
|
-
const isActive = path === activeFile;
|
|
174
|
-
const changeStatus: FileChangeStatus =
|
|
175
|
-
fileChanges[path] || "unchanged";
|
|
176
|
-
|
|
177
|
-
return (
|
|
178
|
-
<div
|
|
179
|
-
key={path}
|
|
180
|
-
className={`group flex items-center gap-2 px-3 py-1.5 text-xs cursor-pointer border-r border-sb-border
|
|
181
|
-
${
|
|
182
|
-
isActive
|
|
183
|
-
? "bg-sb-editor text-sb-text-active border-t-2 border-t-sb-accent"
|
|
184
|
-
: "bg-sb-bg-alt text-sb-text-muted hover:bg-sb-bg-hover border-t-2 border-t-transparent"
|
|
185
|
-
}`}
|
|
186
|
-
onClick={() => onSelectFile(path)}
|
|
187
|
-
>
|
|
188
|
-
{/* Change status indicator dot */}
|
|
189
|
-
{changeStatus !== "unchanged" && (
|
|
190
|
-
<span
|
|
191
|
-
className="w-2 h-2 rounded-full shrink-0"
|
|
192
|
-
style={{ backgroundColor: STATUS_COLORS[changeStatus] }}
|
|
193
|
-
title={STATUS_LABELS[changeStatus]}
|
|
194
|
-
/>
|
|
195
|
-
)}
|
|
196
|
-
<span className="truncate max-w-[120px]">{fileName}</span>
|
|
197
|
-
<button
|
|
198
|
-
className="opacity-0 group-hover:opacity-100 hover:text-sb-text-active ml-1 text-[10px]"
|
|
199
|
-
onClick={(e) => {
|
|
200
|
-
e.stopPropagation();
|
|
201
|
-
onCloseFile(path);
|
|
202
|
-
}}
|
|
203
|
-
title="Close"
|
|
204
|
-
>
|
|
205
|
-
✕
|
|
206
|
-
</button>
|
|
207
|
-
</div>
|
|
208
|
-
);
|
|
209
|
-
})}
|
|
210
|
-
|
|
211
|
-
{/* Spacer */}
|
|
212
|
-
<div className="flex-1" />
|
|
213
|
-
|
|
214
|
-
{/* Diff toggle button — only shown when a diff is available */}
|
|
215
|
-
{hasDiff && (
|
|
216
|
-
<button
|
|
217
|
-
className={`shrink-0 px-2 py-1 mr-1 text-[10px] font-medium rounded transition-colors ${
|
|
218
|
-
diffMode
|
|
219
|
-
? "bg-sb-accent text-white"
|
|
220
|
-
: "text-sb-text-muted hover:text-sb-text-active hover:bg-sb-bg-hover"
|
|
221
|
-
}`}
|
|
222
|
-
onClick={() => setDiffMode((prev) => !prev)}
|
|
223
|
-
title={diffMode ? "Switch to editor" : "Show diff view"}
|
|
224
|
-
>
|
|
225
|
-
{diffMode ? "Editor" : "Diff"}
|
|
226
|
-
</button>
|
|
227
|
-
)}
|
|
228
|
-
</div>
|
|
229
|
-
|
|
230
|
-
{/* Editor / DiffEditor / placeholder */}
|
|
231
|
-
<div className="flex-1 overflow-hidden">
|
|
232
|
-
{!activeFile && (
|
|
233
|
-
<div className="flex items-center justify-center h-full text-sb-text-muted text-sm">
|
|
234
|
-
Select a file to edit
|
|
235
|
-
</div>
|
|
236
|
-
)}
|
|
237
|
-
|
|
238
|
-
{/* Diff mode: Monaco DiffEditor (side-by-side) */}
|
|
239
|
-
{activeFile && loaded && diffMode && MonacoDiffEditor && (
|
|
240
|
-
<MonacoDiffEditor
|
|
241
|
-
height="100%"
|
|
242
|
-
language={language}
|
|
243
|
-
original={activeOriginalContent}
|
|
244
|
-
modified={activeContent}
|
|
245
|
-
theme="vs-dark"
|
|
246
|
-
onMount={handleDiffEditorMount}
|
|
247
|
-
options={{
|
|
248
|
-
readOnly,
|
|
249
|
-
minimap: { enabled: false },
|
|
250
|
-
fontSize: 13,
|
|
251
|
-
wordWrap: "on",
|
|
252
|
-
scrollBeyondLastLine: false,
|
|
253
|
-
padding: { top: 8 },
|
|
254
|
-
renderWhitespace: "selection",
|
|
255
|
-
automaticLayout: true,
|
|
256
|
-
tabSize: 2,
|
|
257
|
-
// DiffEditor-specific options
|
|
258
|
-
renderSideBySide: true,
|
|
259
|
-
enableSplitViewResizing: true,
|
|
260
|
-
renderIndicators: true,
|
|
261
|
-
renderMarginRevertIcon: false,
|
|
262
|
-
originalEditable: false,
|
|
263
|
-
}}
|
|
264
|
-
/>
|
|
265
|
-
)}
|
|
266
|
-
|
|
267
|
-
{/* Regular mode: Monaco Editor */}
|
|
268
|
-
{activeFile && loaded && !diffMode && MonacoEditor && (
|
|
269
|
-
<MonacoEditor
|
|
270
|
-
height="100%"
|
|
271
|
-
language={language}
|
|
272
|
-
value={activeContent}
|
|
273
|
-
onChange={handleEditorChange}
|
|
274
|
-
theme="vs-dark"
|
|
275
|
-
options={{
|
|
276
|
-
readOnly,
|
|
277
|
-
minimap: { enabled: false },
|
|
278
|
-
fontSize: 13,
|
|
279
|
-
lineNumbers: "on",
|
|
280
|
-
wordWrap: "on",
|
|
281
|
-
scrollBeyondLastLine: false,
|
|
282
|
-
padding: { top: 8 },
|
|
283
|
-
renderWhitespace: "selection",
|
|
284
|
-
automaticLayout: true,
|
|
285
|
-
tabSize: 2,
|
|
286
|
-
}}
|
|
287
|
-
/>
|
|
288
|
-
)}
|
|
289
|
-
|
|
290
|
-
{activeFile && !loaded && (
|
|
291
|
-
<div className="flex items-center justify-center h-full text-sb-text-muted text-sm">
|
|
292
|
-
Loading editor...
|
|
293
|
-
</div>
|
|
294
|
-
)}
|
|
295
|
-
</div>
|
|
296
|
-
</div>
|
|
297
|
-
);
|
|
298
|
-
}
|