runlab 0.1.0 → 1.0.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 +256 -0
- package/dist/app/core/editor.js +120 -0
- package/dist/app/core/navbar.js +154 -0
- package/dist/app/fileExplorer/FSNode.js +10 -0
- package/dist/app/fileExplorer/File.js +12 -0
- package/dist/app/fileExplorer/FileExplorer.js +405 -0
- package/dist/app/fileExplorer/Folder.js +12 -0
- package/dist/app/terminal/commands.js +117 -0
- package/dist/app/terminal/terminal.js +126 -0
- package/dist/index.js +45 -13
- package/package.json +36 -10
- package/dist/runlab.wasm +0 -0
- package/dist/wasm_exec.js +0 -575
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import { Folder } from "./Folder.js";
|
|
2
|
+
import { File } from "./File.js";
|
|
3
|
+
import { updateEditorContentById, setActiveFile } from "../core/editor.js";
|
|
4
|
+
import Swal from "sweetalert2";
|
|
5
|
+
import { setEditorActive, setViewActive } from "../app.js";
|
|
6
|
+
|
|
7
|
+
export class FileExplorer {
|
|
8
|
+
constructor(parentId) {
|
|
9
|
+
this.container = document.getElementById(parentId);
|
|
10
|
+
this.root = new Folder("root", null);
|
|
11
|
+
this.execFiles = [];
|
|
12
|
+
this.filesPath = [];
|
|
13
|
+
this.path = [];
|
|
14
|
+
this.render();
|
|
15
|
+
}
|
|
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
|
+
|
|
45
|
+
/* =========================
|
|
46
|
+
RENDER
|
|
47
|
+
========================== */
|
|
48
|
+
|
|
49
|
+
render() {
|
|
50
|
+
this.container.innerHTML = "";
|
|
51
|
+
|
|
52
|
+
const rootDiv = document.createElement("div");
|
|
53
|
+
rootDiv.style.width = "100%";
|
|
54
|
+
rootDiv.style.height = "100%";
|
|
55
|
+
rootDiv.style.display = "block";
|
|
56
|
+
rootDiv.style.pointerEvents = "auto";
|
|
57
|
+
rootDiv.style.position = "relative";
|
|
58
|
+
|
|
59
|
+
rootDiv.oncontextmenu = e => {
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
this.showRootMenu(e);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
this.renderFolder(this.root, rootDiv);
|
|
66
|
+
|
|
67
|
+
this.container.append(rootDiv);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
renderFolder(folder, parentUl) {
|
|
71
|
+
folder.children.forEach(node => {
|
|
72
|
+
if (node instanceof Folder) {
|
|
73
|
+
const btn = document.createElement("button");
|
|
74
|
+
btn.textContent = `📁 ${node.name}`;
|
|
75
|
+
btn.style.width = "100%";
|
|
76
|
+
btn.style.textAlign = "left";
|
|
77
|
+
btn.style.border = "none";
|
|
78
|
+
btn.style.background = "none";
|
|
79
|
+
btn.style.color = "white";
|
|
80
|
+
btn.style.paddingTop = "8px";
|
|
81
|
+
btn.style.paddingBottom = "8px";
|
|
82
|
+
btn.style.cursor = "pointer";
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
btn.onmouseover = () => btn.style.backgroundColor = "#555";
|
|
87
|
+
btn.onmouseout = () => btn.style.backgroundColor = "transparent";
|
|
88
|
+
|
|
89
|
+
const ul = document.createElement("ul");
|
|
90
|
+
ul.style.display = "none";
|
|
91
|
+
ul.style.listStyle = "none";
|
|
92
|
+
ul.style.margin = "0";
|
|
93
|
+
ul.style.padding = "0 0 0 24px";
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
btn.onclick = () => this.toggle(ul);
|
|
97
|
+
btn.oncontextmenu = e => {
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
e.stopPropagation();
|
|
100
|
+
this.showFolderMenu(e, node);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
parentUl.append(btn, ul);
|
|
104
|
+
this.renderFolder(node, ul);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (node instanceof File) {
|
|
108
|
+
const li = document.createElement("li");
|
|
109
|
+
li.textContent = `📄 ${node.name}.${node.ext}`;
|
|
110
|
+
li.style.width = "100%";
|
|
111
|
+
li.style.textAlign = "left";
|
|
112
|
+
li.style.border = "none";
|
|
113
|
+
li.style.background = "none";
|
|
114
|
+
li.style.color = "white";
|
|
115
|
+
li.style.paddingTop = "8px";
|
|
116
|
+
li.style.paddingBottom = "8px";
|
|
117
|
+
li.style.cursor = "pointer";
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
li.onclick = () => {
|
|
121
|
+
setActiveFile(node);
|
|
122
|
+
setViewActive(false);
|
|
123
|
+
setEditorActive(true);
|
|
124
|
+
updateEditorContentById("runlab-editor", node.content);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
li.oncontextmenu = e => {
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
e.stopPropagation();
|
|
130
|
+
this.showFileMenu(e, node);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
parentUl.appendChild(li);
|
|
134
|
+
this.filesPath.push({ name: `${node.name}.${node.ext}`, path: node.parent.path });
|
|
135
|
+
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
this.setExecutables(this.filesPath);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
toggle(el) {
|
|
143
|
+
el.style.display = el.style.display === "none" ? "block" : "none";
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* =========================
|
|
147
|
+
AÇÕES (MODEL)
|
|
148
|
+
========================== */
|
|
149
|
+
|
|
150
|
+
addFolder(parent, name) {
|
|
151
|
+
parent.children.push(new Folder(name, parent));
|
|
152
|
+
this.render();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
addFile(parent, name, ext=".txt") {
|
|
156
|
+
parent.children.push(new File(name, parent, ext));
|
|
157
|
+
this.render();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
rename(node, newName) {
|
|
161
|
+
node.name = newName;
|
|
162
|
+
this.render();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
delete(node) {
|
|
166
|
+
const parent = node.parent;
|
|
167
|
+
parent.children = parent.children.filter(c => c !== node);
|
|
168
|
+
this.render();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* =========================
|
|
172
|
+
MENUS
|
|
173
|
+
========================== */
|
|
174
|
+
|
|
175
|
+
showRootMenu(e) {
|
|
176
|
+
const menu = this.createMenu([
|
|
177
|
+
{
|
|
178
|
+
label: "New Folder",
|
|
179
|
+
action: () => Swal.fire({
|
|
180
|
+
title: 'Folder name',
|
|
181
|
+
input: 'text',
|
|
182
|
+
showCancelButton: true,
|
|
183
|
+
confirmButtonText: 'Create',
|
|
184
|
+
draggable: true,
|
|
185
|
+
preConfirm: (name) => {
|
|
186
|
+
if (name) {
|
|
187
|
+
this.addFolder(this.root, name);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
label: "New File",
|
|
194
|
+
action: () => Swal.fire({
|
|
195
|
+
title: 'Choose extension and filename',
|
|
196
|
+
html:
|
|
197
|
+
'<select id="swal-select" class="swal2-input">' +
|
|
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>' +
|
|
209
|
+
'</select>' +
|
|
210
|
+
'<input id="swal-input" class="swal2-input" placeholder="filename">',
|
|
211
|
+
focusConfirm: false,
|
|
212
|
+
draggable: true,
|
|
213
|
+
preConfirm: () => {
|
|
214
|
+
return [
|
|
215
|
+
document.getElementById('swal-select').value,
|
|
216
|
+
document.getElementById('swal-input').value
|
|
217
|
+
];
|
|
218
|
+
}
|
|
219
|
+
}).then((result) => {
|
|
220
|
+
if (result.value) {
|
|
221
|
+
const [selection, text] = result.value;
|
|
222
|
+
this.addFile(this.root, text, selection);
|
|
223
|
+
}
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
]);
|
|
227
|
+
|
|
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
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
showFolderMenu(e, folder) {
|
|
243
|
+
const menu = this.createMenu([
|
|
244
|
+
{ label: "New Folder", action: () => Swal.fire({
|
|
245
|
+
title: 'Folder name',
|
|
246
|
+
input: 'text',
|
|
247
|
+
showCancelButton: true,
|
|
248
|
+
confirmButtonText: 'Create',
|
|
249
|
+
draggable: true,
|
|
250
|
+
preConfirm: (name) => {
|
|
251
|
+
if (name) {
|
|
252
|
+
this.addFolder(folder, name);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
})},
|
|
256
|
+
{ label: "New File", action: () => Swal.fire({
|
|
257
|
+
title: 'Choose extension and filename',
|
|
258
|
+
html:
|
|
259
|
+
'<select id="swal-select" class="swal2-input">' +
|
|
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>' +
|
|
271
|
+
'</select>' +
|
|
272
|
+
'<input id="swal-input" class="swal2-input" placeholder="filename">',
|
|
273
|
+
focusConfirm: false,
|
|
274
|
+
draggable: true,
|
|
275
|
+
preConfirm: () => {
|
|
276
|
+
return [
|
|
277
|
+
document.getElementById('swal-select').value,
|
|
278
|
+
document.getElementById('swal-input').value
|
|
279
|
+
];
|
|
280
|
+
}
|
|
281
|
+
}).then((result) => {
|
|
282
|
+
if (result.value) {
|
|
283
|
+
const [selection, text] = result.value;
|
|
284
|
+
this.addFile(folder, text, selection);
|
|
285
|
+
}
|
|
286
|
+
})
|
|
287
|
+
},
|
|
288
|
+
{ label: "Rename", action: () => Swal.fire({
|
|
289
|
+
title: 'New name',
|
|
290
|
+
input: 'text',
|
|
291
|
+
showCancelButton: true,
|
|
292
|
+
confirmButtonText: 'Rename',
|
|
293
|
+
draggable: true,
|
|
294
|
+
preConfirm: (name) => {
|
|
295
|
+
if (name) {
|
|
296
|
+
this.rename(folder, name);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}) },
|
|
300
|
+
{ label: "Delete", action: () => this.delete(folder) }
|
|
301
|
+
]);
|
|
302
|
+
|
|
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
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
showFileMenu(e, file) {
|
|
318
|
+
const menu = this.createMenu([
|
|
319
|
+
{ label: "Rename", action: () => Swal.fire({
|
|
320
|
+
title: 'New name',
|
|
321
|
+
input: 'text',
|
|
322
|
+
showCancelButton: true,
|
|
323
|
+
confirmButtonText: 'Rename',
|
|
324
|
+
draggable: true,
|
|
325
|
+
preConfirm: (name) => {
|
|
326
|
+
if (name) {
|
|
327
|
+
this.rename(file, name);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}) },
|
|
331
|
+
{ label: "Delete", action: () => Swal.fire({
|
|
332
|
+
title: '',
|
|
333
|
+
showCancelButton: true,
|
|
334
|
+
confirmButtonText: 'Delete',
|
|
335
|
+
draggable: true,
|
|
336
|
+
preConfirm: (name) => {
|
|
337
|
+
if (name) {
|
|
338
|
+
this.delete(file);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}) }
|
|
342
|
+
]);
|
|
343
|
+
|
|
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
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/* =========================
|
|
359
|
+
HELPERS
|
|
360
|
+
========================== */
|
|
361
|
+
|
|
362
|
+
prompt(label, callback) {
|
|
363
|
+
const value = prompt(label);
|
|
364
|
+
if (value) callback(value);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
createMenu(items) {
|
|
368
|
+
const div = document.createElement("div");
|
|
369
|
+
div.className = "context-menu";
|
|
370
|
+
div.style.position = "absolute";
|
|
371
|
+
div.style.backgroundColor = "#333";
|
|
372
|
+
div.style.border = "1px solid #222";
|
|
373
|
+
div.style.padding = "8px";
|
|
374
|
+
div.style.zIndex = "3000";
|
|
375
|
+
div.style.color = "white";
|
|
376
|
+
div.style.minWidth = "100px";
|
|
377
|
+
|
|
378
|
+
items.forEach(i => {
|
|
379
|
+
const item = document.createElement("div");
|
|
380
|
+
item.textContent = i.label;
|
|
381
|
+
item.style.padding = "4px 8px";
|
|
382
|
+
item.style.cursor = "pointer";
|
|
383
|
+
item.onmouseover = () => item.style.backgroundColor = "#555";
|
|
384
|
+
item.onmouseout = () => item.style.backgroundColor = "transparent";
|
|
385
|
+
item.style.border = "1px solid #444";
|
|
386
|
+
item.onclick = () => {
|
|
387
|
+
i.action();
|
|
388
|
+
div.remove();
|
|
389
|
+
};
|
|
390
|
+
div.appendChild(item);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
return div;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
openMenu(e, menu) {
|
|
397
|
+
document.querySelectorAll(".context-menu").forEach(m => m.remove());
|
|
398
|
+
|
|
399
|
+
menu.style.position = "absolute";
|
|
400
|
+
menu.style.top = `${e.clientY}px`;
|
|
401
|
+
menu.style.left = `${e.clientX}px`;
|
|
402
|
+
|
|
403
|
+
document.body.appendChild(menu);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
@@ -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
|
+
}
|