runlab 0.1.1 → 1.1.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/app/app.js +190 -8
- package/dist/app/core/editor.js +15 -3
- package/dist/app/core/navbar.js +157 -8
- package/dist/app/fileExplorer/FileExplorer.js +108 -25
- package/dist/app/fileExplorer/Folder.js +1 -0
- package/dist/app/terminal/commands.js +117 -0
- package/dist/app/terminal/terminal.js +126 -0
- package/dist/index.js +43 -18
- package/dist/reactButton.jsx +46 -0
- package/package.json +1 -2
- package/dist/wasm/runlab.wasm +0 -0
- package/dist/wasm/wasm_exec.js +0 -575
|
@@ -2,15 +2,46 @@ import { Folder } from "./Folder.js";
|
|
|
2
2
|
import { File } from "./File.js";
|
|
3
3
|
import { updateEditorContentById, setActiveFile } from "../core/editor.js";
|
|
4
4
|
import Swal from "sweetalert2";
|
|
5
|
+
import { setEditorActive, setViewActive } from "../app.js";
|
|
5
6
|
|
|
6
7
|
export class FileExplorer {
|
|
7
8
|
constructor(parentId) {
|
|
8
9
|
this.container = document.getElementById(parentId);
|
|
9
10
|
this.root = new Folder("root", null);
|
|
10
|
-
|
|
11
|
+
this.execFiles = [];
|
|
12
|
+
this.filesPath = [];
|
|
13
|
+
this.path = [];
|
|
11
14
|
this.render();
|
|
12
15
|
}
|
|
13
16
|
|
|
17
|
+
getRoot() {
|
|
18
|
+
return this.root;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getFileNode(file) {
|
|
22
|
+
const name = file.name.split(".")[0];
|
|
23
|
+
const ext = file.name.split(".")[1];
|
|
24
|
+
const path = file.path;
|
|
25
|
+
let currentNode = this.root;
|
|
26
|
+
|
|
27
|
+
for (let i = 1; i < path.length; i++) {
|
|
28
|
+
const part = path[i];
|
|
29
|
+
const nextNode = currentNode.children.find(c => c.name === part && !c.ext);
|
|
30
|
+
if (!nextNode) return null;
|
|
31
|
+
currentNode = nextNode;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return currentNode.children.find(c => c.name === name && c.ext === ext);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getExecutables() {
|
|
38
|
+
return this.execFiles;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
setExecutables(files) {
|
|
42
|
+
this.execFiles = files;
|
|
43
|
+
}
|
|
44
|
+
|
|
14
45
|
/* =========================
|
|
15
46
|
RENDER
|
|
16
47
|
========================== */
|
|
@@ -50,12 +81,17 @@ export class FileExplorer {
|
|
|
50
81
|
btn.style.paddingBottom = "8px";
|
|
51
82
|
btn.style.cursor = "pointer";
|
|
52
83
|
|
|
84
|
+
|
|
53
85
|
|
|
54
86
|
btn.onmouseover = () => btn.style.backgroundColor = "#555";
|
|
55
87
|
btn.onmouseout = () => btn.style.backgroundColor = "transparent";
|
|
56
88
|
|
|
57
89
|
const ul = document.createElement("ul");
|
|
58
90
|
ul.style.display = "none";
|
|
91
|
+
ul.style.listStyle = "none";
|
|
92
|
+
ul.style.margin = "0";
|
|
93
|
+
ul.style.padding = "0 0 0 24px";
|
|
94
|
+
|
|
59
95
|
|
|
60
96
|
btn.onclick = () => this.toggle(ul);
|
|
61
97
|
btn.oncontextmenu = e => {
|
|
@@ -79,9 +115,12 @@ export class FileExplorer {
|
|
|
79
115
|
li.style.paddingTop = "8px";
|
|
80
116
|
li.style.paddingBottom = "8px";
|
|
81
117
|
li.style.cursor = "pointer";
|
|
118
|
+
|
|
82
119
|
|
|
83
120
|
li.onclick = () => {
|
|
84
121
|
setActiveFile(node);
|
|
122
|
+
setViewActive(false);
|
|
123
|
+
setEditorActive(true);
|
|
85
124
|
updateEditorContentById("runlab-editor", node.content);
|
|
86
125
|
};
|
|
87
126
|
|
|
@@ -92,8 +131,12 @@ export class FileExplorer {
|
|
|
92
131
|
};
|
|
93
132
|
|
|
94
133
|
parentUl.appendChild(li);
|
|
134
|
+
this.filesPath.push({ name: `${node.name}.${node.ext}`, path: node.parent.path });
|
|
135
|
+
|
|
95
136
|
}
|
|
96
137
|
});
|
|
138
|
+
|
|
139
|
+
this.setExecutables(this.filesPath);
|
|
97
140
|
}
|
|
98
141
|
|
|
99
142
|
toggle(el) {
|
|
@@ -138,6 +181,7 @@ export class FileExplorer {
|
|
|
138
181
|
input: 'text',
|
|
139
182
|
showCancelButton: true,
|
|
140
183
|
confirmButtonText: 'Create',
|
|
184
|
+
draggable: true,
|
|
141
185
|
preConfirm: (name) => {
|
|
142
186
|
if (name) {
|
|
143
187
|
this.addFolder(this.root, name);
|
|
@@ -151,20 +195,21 @@ export class FileExplorer {
|
|
|
151
195
|
title: 'Choose extension and filename',
|
|
152
196
|
html:
|
|
153
197
|
'<select id="swal-select" class="swal2-input">' +
|
|
154
|
-
'<option value="txt"
|
|
155
|
-
'<option value="md"
|
|
156
|
-
'<option value="json"
|
|
157
|
-
'<option value="yaml"
|
|
158
|
-
'<option value="toml"
|
|
159
|
-
'<option value="html"
|
|
160
|
-
'<option value="xml"
|
|
161
|
-
'<option value="css"
|
|
162
|
-
'<option value="js"
|
|
163
|
-
'<option value="ts"
|
|
164
|
-
'<option value="
|
|
198
|
+
'<option value="txt">📄 .txt - Text</option>' +
|
|
199
|
+
'<option value="md">📝 .md - Text</option>' +
|
|
200
|
+
'<option value="json">🧩 .json - Data</option>' +
|
|
201
|
+
'<option value="yaml">🧩 .yaml - Data</option>' +
|
|
202
|
+
'<option value="toml">🧩 .toml - Data</option>' +
|
|
203
|
+
'<option value="html">🌐 .html - Markup</option>' +
|
|
204
|
+
'<option value="xml">🌐 .xml - Markup</option>' +
|
|
205
|
+
'<option value="css">🎨 .css - Style</option>' +
|
|
206
|
+
'<option value="js">🟨 .js - JavaScript</option>' +
|
|
207
|
+
'<option value="ts">🟦 .ts - TypeScript</option>' +
|
|
208
|
+
'<option value="py">🐍 .py - Python</option>' +
|
|
165
209
|
'</select>' +
|
|
166
210
|
'<input id="swal-input" class="swal2-input" placeholder="filename">',
|
|
167
211
|
focusConfirm: false,
|
|
212
|
+
draggable: true,
|
|
168
213
|
preConfirm: () => {
|
|
169
214
|
return [
|
|
170
215
|
document.getElementById('swal-select').value,
|
|
@@ -177,10 +222,21 @@ export class FileExplorer {
|
|
|
177
222
|
this.addFile(this.root, text, selection);
|
|
178
223
|
}
|
|
179
224
|
})
|
|
180
|
-
}
|
|
225
|
+
}
|
|
181
226
|
]);
|
|
182
227
|
|
|
183
228
|
this.openMenu(e, menu);
|
|
229
|
+
|
|
230
|
+
const closeMenu = () => {
|
|
231
|
+
menu.remove();
|
|
232
|
+
document.removeEventListener("click", closeMenu);
|
|
233
|
+
document.removeEventListener("contextmenu", closeMenu);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
setTimeout(() => {
|
|
237
|
+
document.addEventListener("click", closeMenu);
|
|
238
|
+
document.addEventListener("contextmenu", closeMenu);
|
|
239
|
+
});
|
|
184
240
|
}
|
|
185
241
|
|
|
186
242
|
showFolderMenu(e, folder) {
|
|
@@ -190,6 +246,7 @@ export class FileExplorer {
|
|
|
190
246
|
input: 'text',
|
|
191
247
|
showCancelButton: true,
|
|
192
248
|
confirmButtonText: 'Create',
|
|
249
|
+
draggable: true,
|
|
193
250
|
preConfirm: (name) => {
|
|
194
251
|
if (name) {
|
|
195
252
|
this.addFolder(folder, name);
|
|
@@ -200,20 +257,21 @@ export class FileExplorer {
|
|
|
200
257
|
title: 'Choose extension and filename',
|
|
201
258
|
html:
|
|
202
259
|
'<select id="swal-select" class="swal2-input">' +
|
|
203
|
-
'<option value="txt"
|
|
204
|
-
'<option value="md"
|
|
205
|
-
'<option value="json"
|
|
206
|
-
'<option value="yaml"
|
|
207
|
-
'<option value="toml"
|
|
208
|
-
'<option value="html"
|
|
209
|
-
'<option value="xml"
|
|
210
|
-
'<option value="css"
|
|
211
|
-
'<option value="js"
|
|
212
|
-
'<option value="ts"
|
|
213
|
-
'<option value="
|
|
260
|
+
'<option value="txt">📄 .txt - Text</option>' +
|
|
261
|
+
'<option value="md">📝 .md - Text</option>' +
|
|
262
|
+
'<option value="json">🧩 .json - Data</option>' +
|
|
263
|
+
'<option value="yaml">🧩 .yaml - Data</option>' +
|
|
264
|
+
'<option value="toml">🧩 .toml - Data</option>' +
|
|
265
|
+
'<option value="html">🌐 .html - Markup</option>' +
|
|
266
|
+
'<option value="xml">🌐 .xml - Markup</option>' +
|
|
267
|
+
'<option value="css">🎨 .css - Style</option>' +
|
|
268
|
+
'<option value="js">🟨 .js - JavaScript</option>' +
|
|
269
|
+
'<option value="ts">🟦 .ts - TypeScript</option>' +
|
|
270
|
+
'<option value="py">🐍 .py - Python</option>' +
|
|
214
271
|
'</select>' +
|
|
215
272
|
'<input id="swal-input" class="swal2-input" placeholder="filename">',
|
|
216
273
|
focusConfirm: false,
|
|
274
|
+
draggable: true,
|
|
217
275
|
preConfirm: () => {
|
|
218
276
|
return [
|
|
219
277
|
document.getElementById('swal-select').value,
|
|
@@ -223,7 +281,7 @@ export class FileExplorer {
|
|
|
223
281
|
}).then((result) => {
|
|
224
282
|
if (result.value) {
|
|
225
283
|
const [selection, text] = result.value;
|
|
226
|
-
this.addFile(
|
|
284
|
+
this.addFile(folder, text, selection);
|
|
227
285
|
}
|
|
228
286
|
})
|
|
229
287
|
},
|
|
@@ -232,6 +290,7 @@ export class FileExplorer {
|
|
|
232
290
|
input: 'text',
|
|
233
291
|
showCancelButton: true,
|
|
234
292
|
confirmButtonText: 'Rename',
|
|
293
|
+
draggable: true,
|
|
235
294
|
preConfirm: (name) => {
|
|
236
295
|
if (name) {
|
|
237
296
|
this.rename(folder, name);
|
|
@@ -242,6 +301,17 @@ export class FileExplorer {
|
|
|
242
301
|
]);
|
|
243
302
|
|
|
244
303
|
this.openMenu(e, menu);
|
|
304
|
+
|
|
305
|
+
const closeMenu = () => {
|
|
306
|
+
menu.remove();
|
|
307
|
+
document.removeEventListener("click", closeMenu);
|
|
308
|
+
document.removeEventListener("contextmenu", closeMenu);
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
setTimeout(() => {
|
|
312
|
+
document.addEventListener("click", closeMenu);
|
|
313
|
+
document.addEventListener("contextmenu", closeMenu);
|
|
314
|
+
});
|
|
245
315
|
}
|
|
246
316
|
|
|
247
317
|
showFileMenu(e, file) {
|
|
@@ -251,6 +321,7 @@ export class FileExplorer {
|
|
|
251
321
|
input: 'text',
|
|
252
322
|
showCancelButton: true,
|
|
253
323
|
confirmButtonText: 'Rename',
|
|
324
|
+
draggable: true,
|
|
254
325
|
preConfirm: (name) => {
|
|
255
326
|
if (name) {
|
|
256
327
|
this.rename(file, name);
|
|
@@ -261,6 +332,7 @@ export class FileExplorer {
|
|
|
261
332
|
title: '',
|
|
262
333
|
showCancelButton: true,
|
|
263
334
|
confirmButtonText: 'Delete',
|
|
335
|
+
draggable: true,
|
|
264
336
|
preConfirm: (name) => {
|
|
265
337
|
if (name) {
|
|
266
338
|
this.delete(file);
|
|
@@ -270,6 +342,17 @@ export class FileExplorer {
|
|
|
270
342
|
]);
|
|
271
343
|
|
|
272
344
|
this.openMenu(e, menu);
|
|
345
|
+
|
|
346
|
+
const closeMenu = () => {
|
|
347
|
+
menu.remove();
|
|
348
|
+
document.removeEventListener("click", closeMenu);
|
|
349
|
+
document.removeEventListener("contextmenu", closeMenu);
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
setTimeout(() => {
|
|
353
|
+
document.addEventListener("click", closeMenu);
|
|
354
|
+
document.addEventListener("contextmenu", closeMenu);
|
|
355
|
+
});
|
|
273
356
|
}
|
|
274
357
|
|
|
275
358
|
/* =========================
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { getCurrentDir, getCurrentPath, setCurrentDir, setCurrentPath } from "./terminal.js"
|
|
2
|
+
import { sendCode } from "../../index.js";
|
|
3
|
+
import { setViewActive, getFeExecutables, getFileNode } from "../app.js";
|
|
4
|
+
|
|
5
|
+
export function print(text = "") {
|
|
6
|
+
const div = document.createElement("div")
|
|
7
|
+
div.textContent = text
|
|
8
|
+
const output = document.getElementById("runlab-terminal-output")
|
|
9
|
+
output.appendChild(div)
|
|
10
|
+
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function handleLS() {
|
|
14
|
+
|
|
15
|
+
const currentDirNode = getCurrentDir()
|
|
16
|
+
|
|
17
|
+
if (!currentDirNode.children || currentDirNode.children.length === 0) {
|
|
18
|
+
print("")
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const names = currentDirNode.children.map(
|
|
23
|
+
child => `${child.ext ? `${child.name}.${child.ext}` : `/${child.name}`}`
|
|
24
|
+
)
|
|
25
|
+
print(names.join(" "))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function handleClear() {
|
|
29
|
+
const output = document.getElementById("runlab-terminal-output")
|
|
30
|
+
output.innerHTML = ""
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function handleChangeDirectory(cmd) {
|
|
34
|
+
const dirName = cmd.split(" ")[1]
|
|
35
|
+
const currentDirNode = getCurrentDir()
|
|
36
|
+
const childrenNames = currentDirNode.children.map(child => child.name)
|
|
37
|
+
|
|
38
|
+
if (dirName === "..") {
|
|
39
|
+
const path = getCurrentPath()
|
|
40
|
+
|
|
41
|
+
if (path.length <= 1) {
|
|
42
|
+
print("Already at root directory.")
|
|
43
|
+
return
|
|
44
|
+
} // já está na raiz
|
|
45
|
+
|
|
46
|
+
path.pop()
|
|
47
|
+
setCurrentPath(path)
|
|
48
|
+
setCurrentDir(getCurrentDir().parent)
|
|
49
|
+
|
|
50
|
+
document.getElementById("runlab-terminal-prompt").textContent = `${path.join("/")}> `
|
|
51
|
+
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
if (dirName && childrenNames.includes(dirName)) {
|
|
55
|
+
const child = currentDirNode.children.find(c => c.name === dirName)
|
|
56
|
+
if (child && !child.ext) {
|
|
57
|
+
setCurrentDir(child)
|
|
58
|
+
setCurrentPath([...getCurrentPath(), dirName])
|
|
59
|
+
|
|
60
|
+
document.getElementById("runlab-terminal-prompt").textContent = `${getCurrentPath().join("/")}> `
|
|
61
|
+
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
print("Directory not found or inaccessible.")
|
|
67
|
+
}`
|
|
68
|
+
`
|
|
69
|
+
function parseRunCommand(cmd) {
|
|
70
|
+
const arg = cmd.split(" ")[1]
|
|
71
|
+
if (!arg) return null
|
|
72
|
+
|
|
73
|
+
const clean = arg.replace(/^\/+/, "")
|
|
74
|
+
const parts = clean.split("/")
|
|
75
|
+
|
|
76
|
+
const fileName = parts.pop()
|
|
77
|
+
const path = parts
|
|
78
|
+
|
|
79
|
+
return { fileName, path }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function resolvePathRelative(parsed, currentPath) {
|
|
83
|
+
return [...currentPath, ...parsed.path]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function samePath(a, b) {
|
|
87
|
+
if (a.length !== b.length) return false
|
|
88
|
+
return a.every((dir, i) => dir === b[i])
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function handleRun(cmd) {
|
|
92
|
+
const parsed = parseRunCommand(cmd)
|
|
93
|
+
if (!parsed) {
|
|
94
|
+
print("Executable file command not valid!")
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const executables = getFeExecutables()
|
|
99
|
+
const currentPath = getCurrentPath()
|
|
100
|
+
|
|
101
|
+
const resolvedPath = resolvePathRelative(parsed, currentPath)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
const file = executables.find(e =>
|
|
105
|
+
e.name === parsed.fileName &&
|
|
106
|
+
samePath(e.path, resolvedPath)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if (!file) {
|
|
110
|
+
print("Executable file not found!")
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const fileNode = getFileNode(file)
|
|
115
|
+
sendCode(fileNode.content, fileNode.ext)
|
|
116
|
+
setViewActive(true)
|
|
117
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import {print, handleLS, handleClear, handleChangeDirectory, handleRun} from "./commands.js"
|
|
2
|
+
|
|
3
|
+
export let currentDirNode = null
|
|
4
|
+
export let currentPath = []
|
|
5
|
+
|
|
6
|
+
export function setCurrentDir(node) {
|
|
7
|
+
currentDirNode = node
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getCurrentDir() {
|
|
11
|
+
return currentDirNode
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function setCurrentPath(path) {
|
|
15
|
+
currentPath = path
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getCurrentPath() {
|
|
19
|
+
return currentPath
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createTerminal(parentId,root) {
|
|
23
|
+
const parent = document.getElementById(parentId)
|
|
24
|
+
|
|
25
|
+
const terminal = document.createElement("div")
|
|
26
|
+
terminal.id = "runlab-inside-terminal"
|
|
27
|
+
|
|
28
|
+
Object.assign(terminal.style, {
|
|
29
|
+
backgroundColor: "#1E1E1E",
|
|
30
|
+
color: "#c7cbd9",
|
|
31
|
+
fontFamily: "monospace",
|
|
32
|
+
padding: "12px",
|
|
33
|
+
height: "100%",
|
|
34
|
+
overflowY: "auto",
|
|
35
|
+
boxSizing: "border-box"
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
terminal.tabIndex = 0
|
|
39
|
+
|
|
40
|
+
// ===== estrutura =====
|
|
41
|
+
const output = document.createElement("div")
|
|
42
|
+
output.id = "runlab-terminal-output"
|
|
43
|
+
|
|
44
|
+
const line = document.createElement("div")
|
|
45
|
+
|
|
46
|
+
const prompt = document.createElement("span")
|
|
47
|
+
prompt.id = "runlab-terminal-prompt"
|
|
48
|
+
prompt.textContent = "root> "
|
|
49
|
+
|
|
50
|
+
const input = document.createElement("span")
|
|
51
|
+
|
|
52
|
+
const cursor = document.createElement("span")
|
|
53
|
+
cursor.textContent = "█"
|
|
54
|
+
cursor.style.marginLeft = "2px"
|
|
55
|
+
|
|
56
|
+
line.append(prompt, input, cursor)
|
|
57
|
+
terminal.append(output, line)
|
|
58
|
+
parent.appendChild(terminal)
|
|
59
|
+
|
|
60
|
+
// ===== estado =====
|
|
61
|
+
const state = {
|
|
62
|
+
buffer: ""
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
currentDirNode = root
|
|
66
|
+
currentPath.push("root")
|
|
67
|
+
|
|
68
|
+
// ===== teclado =====
|
|
69
|
+
terminal.addEventListener("keydown", (e) => {
|
|
70
|
+
e.preventDefault()
|
|
71
|
+
|
|
72
|
+
if (e.key === "Backspace") {
|
|
73
|
+
state.buffer = state.buffer.slice(0, -1)
|
|
74
|
+
}
|
|
75
|
+
else if (e.key === "Enter") {
|
|
76
|
+
print(`${currentPath.join("/")}> ${state.buffer}`)
|
|
77
|
+
handleCommand(state.buffer,currentDirNode)
|
|
78
|
+
state.buffer = ""
|
|
79
|
+
}
|
|
80
|
+
else if (e.key.length === 1) {
|
|
81
|
+
state.buffer += e.key
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
input.textContent = state.buffer
|
|
85
|
+
terminal.scrollTop = terminal.scrollHeight
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
terminal.addEventListener("mousedown", () => {
|
|
89
|
+
terminal.focus()
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
terminal.focus()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function handleCommand(cmd) {
|
|
96
|
+
const trimmed = cmd.trim()
|
|
97
|
+
const split = trimmed.split(" ")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
if (trimmed === "ls") {
|
|
101
|
+
handleLS()
|
|
102
|
+
|
|
103
|
+
} else if (trimmed === "clear") {
|
|
104
|
+
handleClear()
|
|
105
|
+
|
|
106
|
+
} else if (split[0] === "cd") {
|
|
107
|
+
handleChangeDirectory(trimmed)
|
|
108
|
+
|
|
109
|
+
}else if (split[0] === "run") {
|
|
110
|
+
handleRun(trimmed)
|
|
111
|
+
|
|
112
|
+
} else if (trimmed === "pwd") {
|
|
113
|
+
print(currentPath.join("/"))
|
|
114
|
+
|
|
115
|
+
} else if (trimmed === "help" || trimmed === "h") {
|
|
116
|
+
print("Available commands:")
|
|
117
|
+
print("- ls: List directory contents")
|
|
118
|
+
print("- cd: Change directory")
|
|
119
|
+
print("- clear: Clear the terminal")
|
|
120
|
+
print("- pwd: Print current directory")
|
|
121
|
+
print("- run: Run a file (e.g., run script.js)")
|
|
122
|
+
print("- help | h: Show this help message")
|
|
123
|
+
} else if (trimmed !== "") {
|
|
124
|
+
print(`command not found: ${trimmed}`)
|
|
125
|
+
}
|
|
126
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,27 +1,52 @@
|
|
|
1
|
-
import "./wasm/wasm_exec.js";
|
|
2
|
-
import wasmUrl from "./wasm/runlab.wasm?url";
|
|
3
1
|
import { generateContainer } from "./app/app.js";
|
|
2
|
+
import { appendViewContent } from "./app/app.js";
|
|
3
|
+
import { ReactRunlabButton } from "./reactButton.jsx";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
console.log("WASM URL resolvido:", wasmUrl);
|
|
9
|
-
const response = await fetch(wasmUrl);
|
|
10
|
-
const bytes = await response.arrayBuffer();
|
|
11
|
-
|
|
12
|
-
const result = await WebAssembly.instantiate(bytes, go.importObject);
|
|
13
|
-
generateContainer(parentId, width, height);
|
|
14
|
-
go.run(result.instance);
|
|
5
|
+
let runtimeUrl = "";
|
|
6
|
+
function setRuntimeUrl(url) {
|
|
7
|
+
runtimeUrl = url;
|
|
15
8
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
function getRuntimeUrl() {
|
|
10
|
+
return runtimeUrl;
|
|
19
11
|
}
|
|
20
12
|
|
|
21
|
-
async function
|
|
13
|
+
export async function run({
|
|
14
|
+
parentId,
|
|
15
|
+
runtimeUrl = ""
|
|
16
|
+
}) {
|
|
17
|
+
generateContainer(parentId);
|
|
18
|
+
setRuntimeUrl(runtimeUrl);
|
|
19
|
+
}
|
|
22
20
|
|
|
21
|
+
export async function sendCode(code, ext = "txt") {
|
|
22
|
+
|
|
23
|
+
const runnableExtensions = ["js", "ts", "py"];
|
|
24
|
+
if (!runnableExtensions.includes(ext)) {
|
|
25
|
+
appendViewContent(code);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const response = await fetch(getRuntimeUrl(), {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: {
|
|
33
|
+
"Content-Type": "application/json"
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify({ code, ext })
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
throw new Error("Failed to send code");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const data = await response.json();
|
|
43
|
+
|
|
44
|
+
appendViewContent(data.stdout || data.stderr);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error(err);
|
|
47
|
+
}
|
|
23
48
|
}
|
|
24
49
|
|
|
25
|
-
|
|
50
|
+
//=============================== Buttons ===============================
|
|
26
51
|
|
|
27
|
-
}
|
|
52
|
+
export { ReactRunlabButton };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { run } from "./index.js";
|
|
3
|
+
|
|
4
|
+
export function ReactRunlabButton(props = { runConfig: {} }) {
|
|
5
|
+
const { runConfig } = props;
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (typeof window !== 'undefined' && window.__runlab_started) return;
|
|
9
|
+
|
|
10
|
+
if (typeof window !== 'undefined') {
|
|
11
|
+
window.__runlab_started = true;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
run(runConfig);
|
|
15
|
+
}, []);
|
|
16
|
+
|
|
17
|
+
const showApp = () => {
|
|
18
|
+
const app = document.getElementById("runlab-custom-div");
|
|
19
|
+
const btn = document.getElementById("runlab-custom-btn");
|
|
20
|
+
|
|
21
|
+
const displayStyle = window.getComputedStyle(app).display;
|
|
22
|
+
|
|
23
|
+
if (displayStyle === "none") {
|
|
24
|
+
app.style.display = "block";
|
|
25
|
+
btn.textContent = "Hide App";
|
|
26
|
+
}
|
|
27
|
+
if (displayStyle === "block") {
|
|
28
|
+
app.style.display = "none";
|
|
29
|
+
btn.textContent = "Show App";
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<>
|
|
35
|
+
<button
|
|
36
|
+
id="runlab-custom-btn"
|
|
37
|
+
onClick={showApp}
|
|
38
|
+
className="show-app-button"
|
|
39
|
+
>
|
|
40
|
+
Show App
|
|
41
|
+
</button>
|
|
42
|
+
|
|
43
|
+
<div id="runlab-custom-div" style={{ display: "none" }}></div>
|
|
44
|
+
</>
|
|
45
|
+
);
|
|
46
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "runlab",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
"@codemirror/lang-markdown": "^6",
|
|
27
27
|
"@codemirror/lang-xml": "^6",
|
|
28
28
|
"@codemirror/lang-yaml": "^6",
|
|
29
|
-
"@codemirror/lang-go": "^6",
|
|
30
29
|
|
|
31
30
|
"@uiw/codemirror-theme-vscode": "^4.21.0",
|
|
32
31
|
"@replit/codemirror-vscode-keymap": "^6.0.0"
|
package/dist/wasm/runlab.wasm
DELETED
|
Binary file
|