runlab 1.1.0 → 1.2.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 CHANGED
@@ -259,4 +259,12 @@ export function getFeExecutables() {
259
259
 
260
260
  export function getFileNode(file) {
261
261
  return feNode.getFileNode(file);
262
+ }
263
+
264
+ export function getNodeFromPath(path, currentPath) {
265
+ return feNode.getNodeFromPath(path, currentPath);
266
+ }
267
+
268
+ export function setMoveNode(node, destNode) {
269
+ feNode.setMoveNode(node, destNode);
262
270
  }
@@ -103,7 +103,9 @@ export function createNavbar(parentId) {
103
103
  <li><span style="font-weight: bold;">pwd</span>: Print current directory</li>
104
104
  <li><span style="font-weight: bold;">run</span>: Run a file (e.g., run script.js)</li>
105
105
  <li><span style="font-weight: bold;">help | h</span>: Show help message</li>
106
+ <li><span style="font-weight: bold;">mv</span>: Move a file or folder (e.g., mv folder1/script.js folder2)</li>
106
107
  </ul>
108
+ <p>If the path starts with / it will be considered absolute (from root), otherwise it will be considered relative to the current path.</p>
107
109
  `,
108
110
  width: 700,
109
111
  confirmButtonText: "Got it!"
@@ -11,6 +11,7 @@ export class FileExplorer {
11
11
  this.execFiles = [];
12
12
  this.filesPath = [];
13
13
  this.path = [];
14
+ this.currentNodeDragged = null;
14
15
  this.render();
15
16
  }
16
17
 
@@ -42,6 +43,106 @@ export class FileExplorer {
42
43
  this.execFiles = files;
43
44
  }
44
45
 
46
+ getNodeFromPath(path, currentPath) {
47
+
48
+ if (path === "/") {
49
+ return this.root;
50
+ }
51
+
52
+ if (path.includes("..")) {
53
+ return null;
54
+ }
55
+ // Absolute path
56
+ if (path.startsWith("/")) {
57
+ path = path.slice(1);
58
+ let currentNode = this.root;
59
+
60
+ if (!path.includes("/")) {
61
+ if (path.includes(".")) { //File
62
+ const [name, ext] = path.split(".");
63
+ return currentNode.children.find(c => c.name === name && c.ext === ext);
64
+ }
65
+ return currentNode.children.find(c => c.name === path);//Folder
66
+ }
67
+
68
+ let pathList = path.split("/").filter(p => p);
69
+
70
+ for (let i = 0; i < pathList.length; i++) {
71
+ const part = pathList[i];
72
+
73
+ if (part.includes(".")) {
74
+ const [name, ext] = part.split(".");
75
+ const nextNode = currentNode.children.find(c => c.name === name && c.ext === ext);
76
+ if (!nextNode) return null;
77
+ currentNode = nextNode;
78
+ }
79
+
80
+ if (!part.includes(".")) {
81
+ const nextNode = currentNode.children.find(c => c.name === part && !c.ext);
82
+ if (!nextNode) return null;
83
+ currentNode = nextNode;
84
+ }
85
+
86
+ }
87
+ return currentNode;
88
+
89
+ }
90
+ // Relative path
91
+ if (!path.startsWith("/")) {
92
+ let currentNode = this.root;
93
+
94
+ if (currentPath.length > 1) {
95
+ for (let i = 1; i < currentPath.length; i++) {
96
+ const part = currentPath[i];
97
+ const nextNode = currentNode.children.find(c => c.name === part && !c.ext);
98
+ if (!nextNode) return null;
99
+ currentNode = nextNode;
100
+ }
101
+ }
102
+
103
+ if (!path.includes("/")) {
104
+ if (path.includes(".")) { //File
105
+ const [name, ext] = path.split(".");
106
+ return currentNode.children.find(c => c.name === name && c.ext === ext);
107
+ }
108
+ return currentNode.children.find(c => c.name === path);//Folder
109
+ }
110
+
111
+ let pathList = path.split("/").filter(p => p);
112
+
113
+ for (let i = 0; i < pathList.length; i++) {
114
+ const part = pathList[i];
115
+
116
+ if (part.includes(".")) {
117
+ const [name, ext] = part.split(".");
118
+ const nextNode = currentNode.children.find(c => c.name === name && c.ext === ext);
119
+ if (!nextNode) return null;
120
+ currentNode = nextNode;
121
+ }
122
+
123
+ if (!part.includes(".")) {
124
+ const nextNode = currentNode.children.find(c => c.name === part && !c.ext);
125
+ if (!nextNode) return null;
126
+ currentNode = nextNode;
127
+ }
128
+
129
+ }
130
+ return currentNode;
131
+
132
+ }
133
+
134
+ }
135
+
136
+ setMoveNode(node, destNode) {
137
+ if (node === this.root) return;
138
+ if (node instanceof File) {
139
+ this.moveFile(node, destNode);
140
+ }
141
+ if (node instanceof Folder) {
142
+ this.moveFolder(node, destNode);
143
+ }
144
+ }
145
+
45
146
  /* =========================
46
147
  RENDER
47
148
  ========================== */
@@ -61,6 +162,19 @@ export class FileExplorer {
61
162
  this.showRootMenu(e);
62
163
  };
63
164
 
165
+ rootDiv.addEventListener("dragover", (e) => {
166
+ rootDiv.style.border = "1px solid #117bd1";
167
+ e.preventDefault();
168
+ });
169
+ rootDiv.addEventListener("dragleave", (e) => {
170
+ rootDiv.style.border = "none";
171
+ });
172
+ rootDiv.addEventListener("drop", (e) => {
173
+ rootDiv.style.border = "none";
174
+ e.preventDefault();
175
+ this.setMoveNode(this.currentNodeDragged, this.root);
176
+ this.currentNodeDragged = null;
177
+ });
64
178
 
65
179
  this.renderFolder(this.root, rootDiv);
66
180
 
@@ -70,6 +184,8 @@ export class FileExplorer {
70
184
  renderFolder(folder, parentUl) {
71
185
  folder.children.forEach(node => {
72
186
  if (node instanceof Folder) {
187
+
188
+ /* ============ BUTTON =============== */
73
189
  const btn = document.createElement("button");
74
190
  btn.textContent = `📁 ${node.name}`;
75
191
  btn.style.width = "100%";
@@ -80,18 +196,18 @@ export class FileExplorer {
80
196
  btn.style.paddingTop = "8px";
81
197
  btn.style.paddingBottom = "8px";
82
198
  btn.style.cursor = "pointer";
83
-
84
-
85
-
86
- btn.onmouseover = () => btn.style.backgroundColor = "#555";
87
- btn.onmouseout = () => btn.style.backgroundColor = "transparent";
88
-
199
+ btn.draggable = true;
200
+ /* ============ UL =============== */
89
201
  const ul = document.createElement("ul");
90
202
  ul.style.display = "none";
91
203
  ul.style.listStyle = "none";
92
204
  ul.style.margin = "0";
93
205
  ul.style.padding = "0 0 0 24px";
94
-
206
+ /* ============ DIV =============== */
207
+ const div = document.createElement("div");
208
+ /* ============ EVENTS =============== */
209
+ btn.onmouseover = () => btn.style.backgroundColor = "#555";
210
+ btn.onmouseout = () => btn.style.backgroundColor = "transparent";
95
211
 
96
212
  btn.onclick = () => this.toggle(ul);
97
213
  btn.oncontextmenu = e => {
@@ -100,11 +216,46 @@ export class FileExplorer {
100
216
  this.showFolderMenu(e, node);
101
217
  };
102
218
 
103
- parentUl.append(btn, ul);
219
+ btn.addEventListener("dragstart", (e) => {
220
+
221
+ this.currentNodeDragged = node;
222
+ });
223
+ btn.addEventListener("dragover", (e) => {
224
+ div.style.border = "1px solid #117bd1";
225
+ e.preventDefault();
226
+ });
227
+ btn.addEventListener("dragleave", (e) => {
228
+ div.style.border = "none";
229
+ });
230
+ btn.addEventListener("drop", (e) => {
231
+ div.style.border = "none";
232
+ e.preventDefault();
233
+ this.setMoveNode(this.currentNodeDragged, node);
234
+ this.currentNodeDragged = null;
235
+ });
236
+
237
+ ul.addEventListener("dragover", (e) => {
238
+ div.style.border = "1px solid #117bd1";
239
+ e.preventDefault();
240
+ });
241
+ ul.addEventListener("dragleave", (e) => {
242
+ div.style.border = "none";
243
+ });
244
+ ul.addEventListener("drop", (e) => {
245
+ div.style.border = "none";
246
+ e.preventDefault();
247
+ this.setMoveNode(this.currentNodeDragged, node);
248
+ this.currentNodeDragged = null;
249
+ });
250
+
251
+ /* ============ APPEND =============== */
252
+ div.append(btn, ul);
253
+ parentUl.append(div);
104
254
  this.renderFolder(node, ul);
105
255
  }
106
256
 
107
257
  if (node instanceof File) {
258
+ /* ============ LI =============== */
108
259
  const li = document.createElement("li");
109
260
  li.textContent = `📄 ${node.name}.${node.ext}`;
110
261
  li.style.width = "100%";
@@ -115,8 +266,8 @@ export class FileExplorer {
115
266
  li.style.paddingTop = "8px";
116
267
  li.style.paddingBottom = "8px";
117
268
  li.style.cursor = "pointer";
118
-
119
-
269
+ li.draggable = true;
270
+ /* ============ EVENTS =============== */
120
271
  li.onclick = () => {
121
272
  setActiveFile(node);
122
273
  setViewActive(false);
@@ -130,6 +281,23 @@ export class FileExplorer {
130
281
  this.showFileMenu(e, node);
131
282
  };
132
283
 
284
+ li.addEventListener("dragstart", (e) => {
285
+ this.currentNodeDragged = node;
286
+ });
287
+ li.addEventListener("dragover", (e) => {
288
+ parentUl.parentElement.style.border = "1px solid #117bd1";
289
+ e.preventDefault();
290
+ });
291
+ li.addEventListener("dragleave", (e) => {
292
+ parentUl.parentElement.style.border = "none";
293
+ });
294
+ li.addEventListener("drop", (e) => {
295
+ parentUl.parentElement.style.border = "none";
296
+ e.preventDefault();
297
+ this.setMoveNode(this.currentNodeDragged, node.parent);
298
+ this.currentNodeDragged = null;
299
+ });
300
+ /* ============ APPEND =============== */
133
301
  parentUl.appendChild(li);
134
302
  this.filesPath.push({ name: `${node.name}.${node.ext}`, path: node.parent.path });
135
303
 
@@ -168,6 +336,29 @@ export class FileExplorer {
168
336
  this.render();
169
337
  }
170
338
 
339
+ moveFile(node, newParent) {
340
+ if (newParent === node) return;
341
+ if (newParent instanceof File) return;
342
+ if (node === this.root) return;
343
+ const oldParent = node.parent;
344
+ oldParent.children = oldParent.children.filter(c => c !== node);
345
+ newParent.children.push(node);
346
+ node.parent = newParent;
347
+ this.render();
348
+ }
349
+
350
+ moveFolder(node, newParent) {
351
+ if (newParent === node) return;
352
+ if (newParent instanceof File) return;
353
+ if (node === this.root) return;
354
+ const oldParent = node.parent;
355
+ oldParent.children = oldParent.children.filter(c => c !== node);
356
+ newParent.children.push(node);
357
+ node.parent = newParent;
358
+ node.path = [...newParent.path, node.name];
359
+ this.render();
360
+ }
361
+
171
362
  /* =========================
172
363
  MENUS
173
364
  ========================== */
@@ -1,6 +1,6 @@
1
1
  import { getCurrentDir, getCurrentPath, setCurrentDir, setCurrentPath } from "./terminal.js"
2
2
  import { sendCode } from "../../index.js";
3
- import { setViewActive, getFeExecutables, getFileNode } from "../app.js";
3
+ import { setViewActive, getFeExecutables, getFileNode, getNodeFromPath, setMoveNode } from "../app.js";
4
4
 
5
5
  export function print(text = "") {
6
6
  const div = document.createElement("div")
@@ -114,4 +114,33 @@ export function handleRun(cmd) {
114
114
  const fileNode = getFileNode(file)
115
115
  sendCode(fileNode.content, fileNode.ext)
116
116
  setViewActive(true)
117
+ }
118
+
119
+ export function handleMove(cmd) {
120
+
121
+ if (cmd.split(" ").length !== 3) {
122
+ print("Invalid move command format! Use: mv <source> <destination>")
123
+ return
124
+ }
125
+
126
+ const nodePath = cmd.split(" ")[1];
127
+ const destPath = cmd.split(" ")[2];
128
+
129
+ const currentPath = getCurrentPath();
130
+
131
+ const node = getNodeFromPath(nodePath, currentPath);
132
+ const destNode = getNodeFromPath(destPath, currentPath);
133
+
134
+ if (!node) {
135
+ print("File or folder to be moved not found!")
136
+ return
137
+ }
138
+
139
+ if (!destNode) {
140
+ print("Folder to move into not found!")
141
+ return
142
+ }
143
+
144
+ setMoveNode(node, destNode)
145
+
117
146
  }
@@ -1,4 +1,4 @@
1
- import {print, handleLS, handleClear, handleChangeDirectory, handleRun} from "./commands.js"
1
+ import {print, handleLS, handleClear, handleChangeDirectory, handleRun, handleMove} from "./commands.js"
2
2
 
3
3
  export let currentDirNode = null
4
4
  export let currentPath = []
@@ -120,7 +120,13 @@ function handleCommand(cmd) {
120
120
  print("- pwd: Print current directory")
121
121
  print("- run: Run a file (e.g., run script.js)")
122
122
  print("- help | h: Show this help message")
123
- } else if (trimmed !== "") {
123
+ print("- mv: Move a file or folder (e.g., mv <source> <destination>)")
124
+ print(" If the path starts with / it will be considered absolute (from root), otherwise it will be considered relative to the current path.")
125
+
126
+ } else if (split[0] === "mv") {
127
+ handleMove(trimmed)
128
+
129
+ }else if (trimmed !== "") {
124
130
  print(`command not found: ${trimmed}`)
125
131
  }
126
132
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runlab",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",