runlab 0.1.0 → 0.1.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.
@@ -0,0 +1,80 @@
1
+ import { createEditor } from "./core/editor.js";
2
+ import { createNavbar } from "./core/navbar.js";
3
+ import { FileExplorer } from "./fileExplorer/FileExplorer.js";
4
+
5
+ export function generateContainer(parentId,w,h) {
6
+ const container = document.createElement("div");
7
+ container.id = "runlab-container";
8
+ Object.assign(container.style, {
9
+ width: w + "px",
10
+ height: h + "px",
11
+ border: "1px solid #000",
12
+ display: "flex",
13
+ justifyContent: "center",
14
+ alignItems: "center",
15
+ overflow: "hidden"
16
+ });
17
+ document.getElementById(parentId).appendChild(container);
18
+ gridTemplate("runlab-container", w, h);
19
+ createNavbar("runlab-navbar");
20
+ createEditor("runlab-editor","");
21
+
22
+ new FileExplorer("runlab-file-explorer");
23
+ }
24
+
25
+ function gridTemplate(parent) {
26
+ const grid = document.createElement('div');
27
+ grid.id = "runlab-grid";
28
+ Object.assign(grid.style, {
29
+ display: 'grid',
30
+ width: "100%",
31
+ height: "100%",
32
+ gridTemplateColumns: "1fr 4fr", // coluna esquerda menor
33
+ gridTemplateRows: "0.15fr 2.5fr 1fr", // top maior, bottom menor
34
+ gap: "2px",
35
+ padding: "2px",
36
+ boxSizing: "border-box",
37
+ backgroundColor: "#303030"
38
+ });
39
+
40
+ const navbar = document.createElement("div");
41
+ navbar.id = "runlab-navbar";
42
+ Object.assign(navbar.style, {
43
+ gridColumn: "1 / 3", // ocupa as duas colunas
44
+ gridRow: "1 / 2", // fica na linha 1
45
+ border: "1px solid #09090b"
46
+ });
47
+
48
+ //Left
49
+ const fileExplorer = document.createElement("div");
50
+ fileExplorer.id = "runlab-file-explorer";
51
+ Object.assign(fileExplorer.style, {
52
+ gridColumn: "1 / 2",
53
+ gridRow: "2 / 4", // ocupa a linha 2 e 3
54
+ border: "1px solid #09090b"
55
+ });
56
+
57
+ //Right Top
58
+ const editor = document.createElement("div");
59
+ editor.id = "runlab-editor";
60
+ Object.assign(editor.style, {
61
+ gridColumn: "2 / 3",
62
+ gridRow: "2 / 3", // linha logo após a navbar
63
+ border: "1px solid #09090b"
64
+ });
65
+
66
+ // Right Bottom
67
+ const terminal = document.createElement("div");
68
+ terminal.id = "runlab-terminal";
69
+ Object.assign(terminal.style, {
70
+ gridColumn: "2 / 3",
71
+ gridRow: "3 / 4", // última linha
72
+ border: "1px solid #09090b"
73
+ });
74
+
75
+ grid.appendChild(navbar);
76
+ grid.appendChild(fileExplorer);
77
+ grid.appendChild(editor);
78
+ grid.appendChild(terminal);
79
+ document.getElementById(parent).appendChild(grid);
80
+ }
@@ -0,0 +1,108 @@
1
+ // Core CM6
2
+ import { EditorState } from "@codemirror/state";
3
+ import { EditorView, keymap } from "@codemirror/view";
4
+ import { Compartment } from "@codemirror/state";
5
+
6
+ // Language
7
+ import { javascript } from "@codemirror/lang-javascript";
8
+ import { html } from "@codemirror/lang-html";
9
+ import { css } from "@codemirror/lang-css";
10
+ import { json } from "@codemirror/lang-json";
11
+ import { markdown } from "@codemirror/lang-markdown";
12
+ import { xml } from "@codemirror/lang-xml";
13
+ import { yaml } from "@codemirror/lang-yaml";
14
+ import { go } from "@codemirror/lang-go";
15
+
16
+ // VS Code look & feel
17
+ import { vscodeDark } from "@uiw/codemirror-theme-vscode";
18
+ import { vscodeKeymap } from "@replit/codemirror-vscode-keymap";
19
+
20
+ // IDE features
21
+ import { autocompletion, closeBrackets } from "@codemirror/autocomplete";
22
+ import { foldGutter, codeFolding } from "@codemirror/language";
23
+
24
+ const editors = new Map();
25
+
26
+ let activeFile = null;
27
+
28
+ export const languageCompartment = new Compartment();
29
+
30
+ export function setActiveFile(file) {
31
+ activeFile = file;
32
+ }
33
+
34
+ function getActiveFile() {
35
+ return activeFile;
36
+ }
37
+
38
+ export function languageFromExtension(ext) {
39
+ switch (ext) {
40
+ case "txt": return [];
41
+ case "md": return markdown();
42
+ case "json": return json();
43
+ case "yaml": return yaml();
44
+ case "yml": return yaml();
45
+ case "toml":return [];
46
+ case "html":return html();
47
+ case "xml":return xml();
48
+ case "css":return css();
49
+ case "js":return javascript({ typescript: false });
50
+ case "ts":return javascript({ typescript: true });
51
+ case "go":return go();
52
+ default:return [];
53
+ }
54
+ }
55
+
56
+
57
+ export function createEditor(parentId, initialCode = "") {
58
+ const parent = document.getElementById(parentId);
59
+ if (!parent) throw new Error(`Elemento #${parentId} não encontrado`);
60
+
61
+ const state = EditorState.create({
62
+ doc: initialCode,
63
+ extensions: [
64
+ keymap.of(vscodeKeymap),
65
+ vscodeDark,
66
+ languageCompartment.of(languageFromExtension(getActiveFile() ? getActiveFile().ext : "")),
67
+ autocompletion(),
68
+ closeBrackets(),
69
+ codeFolding(),
70
+ foldGutter(),
71
+ EditorView.updateListener.of(update => {
72
+ if (update.docChanged) {
73
+ const file = getActiveFile();
74
+ if (file) {
75
+ file.content = update.state.doc.toString();
76
+ }
77
+ }
78
+ })
79
+ ]
80
+ });
81
+
82
+ const view = new EditorView({
83
+ state,
84
+ parent
85
+ });
86
+
87
+ editors.set(parentId, view);
88
+ return view;
89
+ }
90
+
91
+ export function updateEditorContentById(parentId, content) {
92
+ const view = editors.get(parentId);
93
+ if (!view) {
94
+ console.warn(`Editor ${parentId} não inicializado`);
95
+ return;
96
+ }
97
+
98
+ const langFactory = languageFromExtension(getActiveFile() ? getActiveFile().ext : "");
99
+
100
+ view.dispatch({
101
+ effects: languageCompartment.reconfigure(langFactory),
102
+ changes: {
103
+ from: 0,
104
+ to: view.state.doc.length,
105
+ insert: content
106
+ }
107
+ });
108
+ }
@@ -0,0 +1,35 @@
1
+
2
+
3
+ export function createNavbar(parentId) {
4
+ const navbar = document.createElement("nav");
5
+ navbar.id = "runlab-navbar-div";
6
+ Object.assign(navbar.style, {
7
+ width: "100%",
8
+ height: "100%"
9
+ });
10
+
11
+ const ul = document.createElement("ul");
12
+ Object.assign(ul.style, {
13
+ listStyleType: "none",
14
+ margin: "0",
15
+ padding: "0",
16
+ display: "flex"
17
+ });
18
+ navbar.appendChild(ul);
19
+
20
+ const menuItems = ["File", "Edit", "View", "Help"];
21
+ menuItems.forEach(item => {
22
+ const li = document.createElement("li");
23
+ li.textContent = item;
24
+ ul.appendChild(li);
25
+ Object.assign(li.style, {
26
+ marginRight: "20px",
27
+ padding: "10px 14px",
28
+ cursor: "pointer",
29
+ color: "#fff",
30
+ textDecoration: "none"
31
+ });
32
+ });
33
+
34
+ document.getElementById(parentId).appendChild(navbar);
35
+ }
@@ -0,0 +1,10 @@
1
+ class FSNode {
2
+ constructor(name) {
3
+ this.id = name;
4
+ this.name = name;
5
+ this.parent = null;
6
+ }
7
+
8
+ }
9
+
10
+ export { FSNode };
@@ -0,0 +1,12 @@
1
+ import { FSNode } from "./FSNode.js";
2
+
3
+ class File extends FSNode {
4
+ constructor(name, parent, ext) {
5
+ super(name);
6
+ this.parent = parent;
7
+ this.content = "";
8
+ this.ext = ext;
9
+ }
10
+ }
11
+
12
+ export { File };
@@ -0,0 +1,322 @@
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
+
6
+ export class FileExplorer {
7
+ constructor(parentId) {
8
+ this.container = document.getElementById(parentId);
9
+ this.root = new Folder("root", null);
10
+
11
+ this.render();
12
+ }
13
+
14
+ /* =========================
15
+ RENDER
16
+ ========================== */
17
+
18
+ render() {
19
+ this.container.innerHTML = "";
20
+
21
+ const rootDiv = document.createElement("div");
22
+ rootDiv.style.width = "100%";
23
+ rootDiv.style.height = "100%";
24
+ rootDiv.style.display = "block";
25
+ rootDiv.style.pointerEvents = "auto";
26
+ rootDiv.style.position = "relative";
27
+
28
+ rootDiv.oncontextmenu = e => {
29
+ e.preventDefault();
30
+ this.showRootMenu(e);
31
+ };
32
+
33
+
34
+ this.renderFolder(this.root, rootDiv);
35
+
36
+ this.container.append(rootDiv);
37
+ }
38
+
39
+ renderFolder(folder, parentUl) {
40
+ folder.children.forEach(node => {
41
+ if (node instanceof Folder) {
42
+ const btn = document.createElement("button");
43
+ btn.textContent = `📁 ${node.name}`;
44
+ btn.style.width = "100%";
45
+ btn.style.textAlign = "left";
46
+ btn.style.border = "none";
47
+ btn.style.background = "none";
48
+ btn.style.color = "white";
49
+ btn.style.paddingTop = "8px";
50
+ btn.style.paddingBottom = "8px";
51
+ btn.style.cursor = "pointer";
52
+
53
+
54
+ btn.onmouseover = () => btn.style.backgroundColor = "#555";
55
+ btn.onmouseout = () => btn.style.backgroundColor = "transparent";
56
+
57
+ const ul = document.createElement("ul");
58
+ ul.style.display = "none";
59
+
60
+ btn.onclick = () => this.toggle(ul);
61
+ btn.oncontextmenu = e => {
62
+ e.preventDefault();
63
+ e.stopPropagation();
64
+ this.showFolderMenu(e, node);
65
+ };
66
+
67
+ parentUl.append(btn, ul);
68
+ this.renderFolder(node, ul);
69
+ }
70
+
71
+ if (node instanceof File) {
72
+ const li = document.createElement("li");
73
+ li.textContent = `📄 ${node.name}.${node.ext}`;
74
+ li.style.width = "100%";
75
+ li.style.textAlign = "left";
76
+ li.style.border = "none";
77
+ li.style.background = "none";
78
+ li.style.color = "white";
79
+ li.style.paddingTop = "8px";
80
+ li.style.paddingBottom = "8px";
81
+ li.style.cursor = "pointer";
82
+
83
+ li.onclick = () => {
84
+ setActiveFile(node);
85
+ updateEditorContentById("runlab-editor", node.content);
86
+ };
87
+
88
+ li.oncontextmenu = e => {
89
+ e.preventDefault();
90
+ e.stopPropagation();
91
+ this.showFileMenu(e, node);
92
+ };
93
+
94
+ parentUl.appendChild(li);
95
+ }
96
+ });
97
+ }
98
+
99
+ toggle(el) {
100
+ el.style.display = el.style.display === "none" ? "block" : "none";
101
+ }
102
+
103
+ /* =========================
104
+ AÇÕES (MODEL)
105
+ ========================== */
106
+
107
+ addFolder(parent, name) {
108
+ parent.children.push(new Folder(name, parent));
109
+ this.render();
110
+ }
111
+
112
+ addFile(parent, name, ext=".txt") {
113
+ parent.children.push(new File(name, parent, ext));
114
+ this.render();
115
+ }
116
+
117
+ rename(node, newName) {
118
+ node.name = newName;
119
+ this.render();
120
+ }
121
+
122
+ delete(node) {
123
+ const parent = node.parent;
124
+ parent.children = parent.children.filter(c => c !== node);
125
+ this.render();
126
+ }
127
+
128
+ /* =========================
129
+ MENUS
130
+ ========================== */
131
+
132
+ showRootMenu(e) {
133
+ const menu = this.createMenu([
134
+ {
135
+ label: "New Folder",
136
+ action: () => Swal.fire({
137
+ title: 'Folder name',
138
+ input: 'text',
139
+ showCancelButton: true,
140
+ confirmButtonText: 'Create',
141
+ preConfirm: (name) => {
142
+ if (name) {
143
+ this.addFolder(this.root, name);
144
+ }
145
+ }
146
+ })
147
+ },
148
+ {
149
+ label: "New File",
150
+ action: () => Swal.fire({
151
+ title: 'Choose extension and filename',
152
+ html:
153
+ '<select id="swal-select" class="swal2-input">' +
154
+ '<option value="txt">.txt - Text</option>' +
155
+ '<option value="md">.md - Text</option>' +
156
+ '<option value="json">.json - Data</option>' +
157
+ '<option value="yaml">.yaml - Data</option>' +
158
+ '<option value="toml">.toml - Data</option>' +
159
+ '<option value="html">.html - Markup</option>' +
160
+ '<option value="xml">.xml - Markup</option>' +
161
+ '<option value="css">.css - Style</option>' +
162
+ '<option value="js">.js - JavaScript</option>' +
163
+ '<option value="ts">.ts - TypeScript</option>' +
164
+ '<option value="go">.go - GO</option>' +
165
+ '</select>' +
166
+ '<input id="swal-input" class="swal2-input" placeholder="filename">',
167
+ focusConfirm: false,
168
+ preConfirm: () => {
169
+ return [
170
+ document.getElementById('swal-select').value,
171
+ document.getElementById('swal-input').value
172
+ ];
173
+ }
174
+ }).then((result) => {
175
+ if (result.value) {
176
+ const [selection, text] = result.value;
177
+ this.addFile(this.root, text, selection);
178
+ }
179
+ })
180
+ }
181
+ ]);
182
+
183
+ this.openMenu(e, menu);
184
+ }
185
+
186
+ showFolderMenu(e, folder) {
187
+ const menu = this.createMenu([
188
+ { label: "New Folder", action: () => Swal.fire({
189
+ title: 'Folder name',
190
+ input: 'text',
191
+ showCancelButton: true,
192
+ confirmButtonText: 'Create',
193
+ preConfirm: (name) => {
194
+ if (name) {
195
+ this.addFolder(folder, name);
196
+ }
197
+ }
198
+ })},
199
+ { label: "New File", action: () => Swal.fire({
200
+ title: 'Choose extension and filename',
201
+ html:
202
+ '<select id="swal-select" class="swal2-input">' +
203
+ '<option value="txt">.txt - Text</option>' +
204
+ '<option value="md">.md - Text</option>' +
205
+ '<option value="json">.json - Data</option>' +
206
+ '<option value="yaml">.yaml - Data</option>' +
207
+ '<option value="toml">.toml - Data</option>' +
208
+ '<option value="html">.html - Markup</option>' +
209
+ '<option value="xml">.xml - Markup</option>' +
210
+ '<option value="css">.css - Style</option>' +
211
+ '<option value="js">.js - JavaScript</option>' +
212
+ '<option value="ts">.ts - TypeScript</option>' +
213
+ '<option value="go">.go - GO</option>' +
214
+ '</select>' +
215
+ '<input id="swal-input" class="swal2-input" placeholder="filename">',
216
+ focusConfirm: false,
217
+ preConfirm: () => {
218
+ return [
219
+ document.getElementById('swal-select').value,
220
+ document.getElementById('swal-input').value
221
+ ];
222
+ }
223
+ }).then((result) => {
224
+ if (result.value) {
225
+ const [selection, text] = result.value;
226
+ this.addFile(this.root, text, selection);
227
+ }
228
+ })
229
+ },
230
+ { label: "Rename", action: () => Swal.fire({
231
+ title: 'New name',
232
+ input: 'text',
233
+ showCancelButton: true,
234
+ confirmButtonText: 'Rename',
235
+ preConfirm: (name) => {
236
+ if (name) {
237
+ this.rename(folder, name);
238
+ }
239
+ }
240
+ }) },
241
+ { label: "Delete", action: () => this.delete(folder) }
242
+ ]);
243
+
244
+ this.openMenu(e, menu);
245
+ }
246
+
247
+ showFileMenu(e, file) {
248
+ const menu = this.createMenu([
249
+ { label: "Rename", action: () => Swal.fire({
250
+ title: 'New name',
251
+ input: 'text',
252
+ showCancelButton: true,
253
+ confirmButtonText: 'Rename',
254
+ preConfirm: (name) => {
255
+ if (name) {
256
+ this.rename(file, name);
257
+ }
258
+ }
259
+ }) },
260
+ { label: "Delete", action: () => Swal.fire({
261
+ title: '',
262
+ showCancelButton: true,
263
+ confirmButtonText: 'Delete',
264
+ preConfirm: (name) => {
265
+ if (name) {
266
+ this.delete(file);
267
+ }
268
+ }
269
+ }) }
270
+ ]);
271
+
272
+ this.openMenu(e, menu);
273
+ }
274
+
275
+ /* =========================
276
+ HELPERS
277
+ ========================== */
278
+
279
+ prompt(label, callback) {
280
+ const value = prompt(label);
281
+ if (value) callback(value);
282
+ }
283
+
284
+ createMenu(items) {
285
+ const div = document.createElement("div");
286
+ div.className = "context-menu";
287
+ div.style.position = "absolute";
288
+ div.style.backgroundColor = "#333";
289
+ div.style.border = "1px solid #222";
290
+ div.style.padding = "8px";
291
+ div.style.zIndex = "3000";
292
+ div.style.color = "white";
293
+ div.style.minWidth = "100px";
294
+
295
+ items.forEach(i => {
296
+ const item = document.createElement("div");
297
+ item.textContent = i.label;
298
+ item.style.padding = "4px 8px";
299
+ item.style.cursor = "pointer";
300
+ item.onmouseover = () => item.style.backgroundColor = "#555";
301
+ item.onmouseout = () => item.style.backgroundColor = "transparent";
302
+ item.style.border = "1px solid #444";
303
+ item.onclick = () => {
304
+ i.action();
305
+ div.remove();
306
+ };
307
+ div.appendChild(item);
308
+ });
309
+
310
+ return div;
311
+ }
312
+
313
+ openMenu(e, menu) {
314
+ document.querySelectorAll(".context-menu").forEach(m => m.remove());
315
+
316
+ menu.style.position = "absolute";
317
+ menu.style.top = `${e.clientY}px`;
318
+ menu.style.left = `${e.clientX}px`;
319
+
320
+ document.body.appendChild(menu);
321
+ }
322
+ }
@@ -0,0 +1,11 @@
1
+ import { FSNode } from "./FSNode.js";
2
+
3
+ class Folder extends FSNode {
4
+ constructor(name, parent) {
5
+ super(name);
6
+ this.parent = parent;
7
+ this.children = [];
8
+ }
9
+ }
10
+
11
+ export { Folder };
package/dist/index.js CHANGED
@@ -1,17 +1,27 @@
1
- import "./wasm_exec.js";
1
+ import "./wasm/wasm_exec.js";
2
+ import wasmUrl from "./wasm/runlab.wasm?url";
3
+ import { generateContainer } from "./app/app.js";
2
4
 
3
5
  const go = new Go();
4
6
 
5
- /**
6
- * Executa o WASM.
7
- * @param {string} wasmPath Caminho do arquivo .wasm
8
- */
9
- export async function run(wasmPath = "./runlab.wasm") {
10
- console.log("Carregando WASM:", wasmPath);
11
-
12
- const response = await fetch(wasmPath);
7
+ export async function run(parentId, width = 1200, height = 800) {
8
+ console.log("WASM URL resolvido:", wasmUrl);
9
+ const response = await fetch(wasmUrl);
13
10
  const bytes = await response.arrayBuffer();
14
11
 
15
12
  const result = await WebAssembly.instantiate(bytes, go.importObject);
13
+ generateContainer(parentId, width, height);
16
14
  go.run(result.instance);
15
+ }
16
+
17
+ async function updateFileExplorer() {
18
+
19
+ }
20
+
21
+ async function loadTerminal() {
22
+
23
+ }
24
+
25
+ async function loadEditor() {
26
+
17
27
  }
package/package.json CHANGED
@@ -1,15 +1,42 @@
1
1
  {
2
2
  "name": "runlab",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
- "main": "dist/index.js",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+
6
8
  "exports": {
7
- "./dist/index.js": "./dist/index.js",
8
- "./dist/runlab.wasm": "./dist/runlab.wasm",
9
- "./dist/wasm_exec.js": "./dist/wasm_exec.js"
9
+ ".": "./dist/index.js",
10
+ "./wasm": "./dist/runlab.wasm",
11
+ "./wasm_exec": "./dist/wasm_exec.js"
10
12
  },
11
- "files": [
12
- "dist/**/*",
13
- "LICENSE"
14
- ]
15
- }
13
+
14
+ "files": ["dist/**/*", "LICENSE"],
15
+
16
+ "peerDependencies": {
17
+ "@codemirror/state": "^6",
18
+ "@codemirror/view": "^6",
19
+ "@codemirror/language": "^6",
20
+ "@codemirror/autocomplete": "^6",
21
+
22
+ "@codemirror/lang-javascript": "^6",
23
+ "@codemirror/lang-html": "^6",
24
+ "@codemirror/lang-css": "^6",
25
+ "@codemirror/lang-json": "^6",
26
+ "@codemirror/lang-markdown": "^6",
27
+ "@codemirror/lang-xml": "^6",
28
+ "@codemirror/lang-yaml": "^6",
29
+ "@codemirror/lang-go": "^6",
30
+
31
+ "@uiw/codemirror-theme-vscode": "^4.21.0",
32
+ "@replit/codemirror-vscode-keymap": "^6.0.0"
33
+ },
34
+ "dependencies": {
35
+ "@babel/runtime": "^7.24.0",
36
+ "sweetalert2": "^11.10.0"
37
+ },
38
+ "devDependencies": {
39
+ "@uiw/codemirror-theme-vscode": "^4.21.0",
40
+ "@replit/codemirror-vscode-keymap": "^6.0.0"
41
+ }
42
+ }
File without changes
File without changes