FrederickSundeep commited on
Commit
eb69de5
Β·
1 Parent(s): 5b4b486

commit initial 09-12-2025 017

Browse files
Files changed (2) hide show
  1. src/App.css +43 -0
  2. src/App.js +102 -120
src/App.css CHANGED
@@ -406,3 +406,46 @@
406
  width: auto;
407
  }
408
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
406
  width: auto;
407
  }
408
  }
409
+
410
+
411
+ /* ---------- progress bar under menubar ---------- */
412
+ .ide-progress-wrap {
413
+ height: 3px;
414
+ background: transparent;
415
+ width: 100%;
416
+ position: relative;
417
+ overflow: hidden;
418
+ }
419
+ .ide-progress {
420
+ position: absolute;
421
+ height: 3px;
422
+ width: 30%;
423
+ left: -30%;
424
+ top: 0;
425
+ background: linear-gradient(90deg, #0e9, #08f);
426
+ animation: progress-slide 1.2s linear infinite;
427
+ border-radius: 2px;
428
+ }
429
+
430
+ @keyframes progress-slide {
431
+ 0% { left: -30%; width: 30%; }
432
+ 50% { left: 35%; width: 40%; }
433
+ 100% { left: 100%; width: 30%; }
434
+ }
435
+
436
+ /* ---------- small inline spinner (optional) ---------- */
437
+ .button-spinner {
438
+ display: inline-block;
439
+ width: 12px;
440
+ height: 12px;
441
+ border: 2px solid rgba(255,255,255,0.25);
442
+ border-top-color: rgba(255,255,255,0.95);
443
+ border-radius: 50%;
444
+ animation: spin 0.8s linear infinite;
445
+ margin-left: 6px;
446
+ vertical-align: middle;
447
+ }
448
+
449
+ @keyframes spin {
450
+ to { transform: rotate(360deg); }
451
+ }
src/App.js CHANGED
@@ -49,6 +49,11 @@ function App() {
49
  const editorRef = useRef(null);
50
  const fileInputRef = useRef(null);
51
 
 
 
 
 
 
52
  // Always persist tree on change
53
  useEffect(() => {
54
  saveTree(tree);
@@ -69,8 +74,6 @@ function App() {
69
  };
70
 
71
  // ---------- File / Folder actions ----------
72
-
73
- // Create a new file inside selected folder (or root)
74
  const handleNewFile = () => {
75
  const filename = window.prompt("Filename (with extension):", "untitled.js");
76
  if (!filename) return;
@@ -84,7 +87,6 @@ function App() {
84
  parentPath = parts.join("/");
85
  }
86
 
87
- // Offer user to choose another parent folder
88
  const folders = collectFolderPaths(tree);
89
  const suggestion = parentPath || folders[0] || "";
90
  const chosen = window.prompt(
@@ -96,12 +98,10 @@ function App() {
96
  const updated = addFile(tree, filename, targetParent);
97
  setTree(updated);
98
 
99
- // Set active to newly created file
100
  const newPath = (targetParent ? targetParent + "/" : "") + filename;
101
  setActivePath(newPath);
102
  };
103
 
104
- // Create a folder under selected folder or root
105
  const handleNewFolder = () => {
106
  const name = window.prompt("Folder name:", "new_folder");
107
  if (!name) return;
@@ -111,7 +111,6 @@ function App() {
111
  setTree(updated);
112
  };
113
 
114
- // Rename selected node (file or folder)
115
  const handleRename = () => {
116
  if (!activePath) return;
117
  const node = getNodeByPath(tree, activePath);
@@ -121,7 +120,6 @@ function App() {
121
  const updated = renameNode(tree, activePath, newName);
122
  setTree(updated);
123
 
124
- // compute new activePath (preserve parent)
125
  const parts = activePath.split("/");
126
  parts.pop();
127
  const parent = parts.join("/");
@@ -129,7 +127,6 @@ function App() {
129
  setActivePath(newPath);
130
  };
131
 
132
- // Delete selected node (confirm if folder not empty)
133
  const handleDelete = () => {
134
  if (!activePath) return;
135
  const node = getNodeByPath(tree, activePath);
@@ -145,10 +142,9 @@ function App() {
145
 
146
  const updated = deleteNode(tree, activePath);
147
  setTree(updated);
148
- setActivePath(""); // deselect / fallback
149
  };
150
 
151
- // Download single file
152
  const downloadFile = () => {
153
  const node = getNodeByPath(tree, activePath);
154
  if (!node || node.type !== "file") return;
@@ -167,16 +163,11 @@ function App() {
167
  if (!f) return;
168
  const text = await f.text();
169
 
170
- // decide parent folder
171
  const selected = getNodeByPath(tree, activePath);
172
  let parentPath = "";
173
- if (selected?.type === "folder") parentPath = selected.path; // fallback
174
- // (above line intentionally split to maintain code clarity)
175
- // compute parent path correctly:
176
  if (selected?.type === "folder") parentPath = selected.path;
177
  else if (selected?.type === "file") parentPath = selected.path.split("/").slice(0, -1).join("");
178
 
179
- // create file under parent and write content
180
  const updated = addFile(tree, f.name, parentPath);
181
  const newPath = (parentPath ? parentPath + "/" : "") + f.name;
182
  const finalTree = updateFileContent(updated, newPath, text);
@@ -185,8 +176,7 @@ function App() {
185
  e.target.value = "";
186
  };
187
 
188
- // ---------- Run & Agent ----------
189
-
190
  const handleRun = async () => {
191
  const node = getNodeByPath(tree, activePath);
192
  if (!node || node.type !== "file") {
@@ -199,9 +189,18 @@ function App() {
199
  return;
200
  }
201
 
202
- const res = await runCode(node.content, selectedLang, stdin);
203
- setOutput(res.output || "");
204
- setProblems(res.error ? parseProblems(res.output) : []);
 
 
 
 
 
 
 
 
 
205
  };
206
 
207
  const handleAskFix = async () => {
@@ -210,12 +209,19 @@ function App() {
210
  setOutput("Select a file to apply fix.");
211
  return;
212
  }
213
- const userHint = prompt.trim() ? `User request: ${prompt}` : "";
214
- const reply = await askAgent(
215
- `Improve, debug, or refactor this ${LANGUAGE_OPTIONS.find((l) => node.name.endsWith(l.ext))?.id || "file"} file.\n${userHint}\nReturn ONLY updated code, no explanation.\n\nCODE:\n${node.content}`
216
- );
217
- const updatedTree = updateFileContent(tree, node.path, reply);
218
- setTree(updatedTree);
 
 
 
 
 
 
 
219
  };
220
 
221
  const handleExplainSelection = async () => {
@@ -224,24 +230,35 @@ function App() {
224
  setExplanation("Select a file to explain.");
225
  return;
226
  }
227
- const editor = editorRef.current;
228
- let selectedCode = "";
229
  try {
230
- selectedCode = editor?.getModel()?.getValueInRange(editor.getSelection()) || "";
231
- } catch {}
232
- const code = selectedCode.trim() || node.content;
233
- const userHint = prompt.trim() ? `Focus on: ${prompt}` : "Give a clear and simple explanation.";
234
- const reply = await askAgent(
235
- `Explain what this code does, any risks, and improvements.\n${userHint}\n\nCODE:\n${code}`
236
- );
237
- setExplanation(reply);
 
 
 
 
 
 
 
 
238
  };
239
 
240
  // AI suggestions for continuation (simple)
241
  const fetchAiSuggestions = async (code) => {
242
  if (!code?.trim()) return;
243
- const reply = await askAgent(`Suggest possible next lines for continuation. Return 3 short snippets.\n${code}`);
244
- setAiSuggestions(reply.split("\n").filter((l) => l.trim()));
 
 
 
 
245
  };
246
 
247
  // ---------- Search ----------
@@ -261,7 +278,7 @@ function App() {
261
  setTree(updated);
262
  };
263
 
264
- // ---------- Render Tree (moved inside component so it sees activePath) ----------
265
  const renderTree = (node, depth = 0) => {
266
  const isActive = node.path === activePath;
267
  return (
@@ -280,60 +297,71 @@ function App() {
280
  <span className="ide-file-name">{node.name}</span>
281
  </div>
282
 
283
- {node.children &&
284
- node.children.map((c) => renderTree(c, depth + 1))}
285
  </div>
286
  );
287
  };
288
 
 
 
 
289
  // ---------- JSX UI ----------
290
  return (
291
  <div className={`ide-root ${theme === "vs-dark" ? "ide-dark" : "ide-light"}`}>
292
  {/* Hidden file input for import */}
293
- <input
294
- ref={fileInputRef}
295
- id="file-import-input"
296
- type="file"
297
- style={{ display: "none" }}
298
- onChange={handleFileInputChange}
299
- />
300
 
301
  {/* Top menu */}
302
  <div className="ide-menubar">
303
  <div className="ide-menubar-left">
304
  <span className="ide-logo">βš™οΈ DevMate IDE</span>
305
 
306
- <button onClick={handleNewFile}>πŸ“„ New File</button>
307
- <button onClick={handleNewFolder}>πŸ“ New Folder</button>
308
- <button onClick={handleRename}>✏️ Rename</button>
309
- <button onClick={handleDelete}>πŸ—‘ Delete</button>
310
- <button onClick={downloadFile}>πŸ“₯ Download</button>
311
- <button onClick={() => downloadProjectZip()}>πŸ“¦ ZIP</button>
312
- <button onClick={handleImportFileClick}>πŸ“€ Import File</button>
313
  </div>
314
 
315
  <div className="ide-menubar-right">
316
- <button onClick={handleSearchToggle}>πŸ” Search</button>
317
- <button onClick={handleRun}>β–Ά Run</button>
318
- <button onClick={handleAskFix}>πŸ€– Fix</button>
319
- <button onClick={handleExplainSelection}>πŸ“– Explain</button>
320
- <button onClick={() => setTheme((t) => (t === "vs-dark" ? "light" : "vs-dark"))}>
 
 
 
 
 
 
 
 
 
 
321
  {theme === "vs-dark" ? "β˜€οΈ" : "πŸŒ™"}
322
  </button>
323
  </div>
324
  </div>
325
 
 
 
 
 
 
 
 
326
  {/* Body */}
327
  <div className="ide-body">
328
  {/* Sidebar */}
329
  <div className="ide-sidebar">
330
  <div className="ide-sidebar-header">
331
  <span>EXPLORER</span>
332
- <button className="ide-icon-button" onClick={handleNewFile} title="New File">οΌ‹</button>
333
- </div>
334
- <div className="ide-file-list" style={{ padding: 6 }}>
335
- {renderTree(tree)}
336
  </div>
 
337
  </div>
338
 
339
  {/* Main (editor + bottom panels) */}
@@ -359,15 +387,7 @@ function App() {
359
  {aiSuggestions.length > 0 && (
360
  <div className="ai-popup">
361
  {aiSuggestions.map((s, i) => (
362
- <div
363
- key={i}
364
- className="ai-suggest"
365
- onClick={() =>
366
- updateActiveFileContent((currentNode?.content || "") + "\n" + s)
367
- }
368
- >
369
- {s}
370
- </div>
371
  ))}
372
  </div>
373
  )}
@@ -376,12 +396,7 @@ function App() {
376
  <div className="ide-panels">
377
  <pre className="ide-output">{output}</pre>
378
 
379
- <input
380
- className="ide-input-box"
381
- placeholder="Program input..."
382
- value={stdin}
383
- onChange={(e) => setStdin(e.target.value)}
384
- />
385
 
386
  {problems.length > 0 && (
387
  <div className="ide-problems-panel">
@@ -402,17 +417,12 @@ function App() {
402
 
403
  <div className="ide-ai-section">
404
  <label className="ide-ai-label">Instruction</label>
405
- <textarea
406
- className="ide-agent-textarea"
407
- placeholder="Ask the AI (optimize, add tests, convert, etc.)"
408
- value={prompt}
409
- onChange={(e) => setPrompt(e.target.value)}
410
- />
411
  </div>
412
 
413
  <div className="ide-ai-buttons">
414
- <button onClick={handleAskFix}>πŸ’‘ Apply Fix</button>
415
- <button onClick={handleExplainSelection}>πŸ“– Explain Code</button>
416
  </div>
417
 
418
  {explanation && (
@@ -427,45 +437,17 @@ function App() {
427
  {/* Search dialog */}
428
  {searchOpen && (
429
  <div className="search-dialog">
430
- <input
431
- placeholder="Search text..."
432
- onChange={(e) => setSearchQuery(e.target.value)}
433
- />
434
  <button onClick={handleSearchNow}>Search</button>
435
  </div>
436
  )}
437
 
438
  {/* Context menu */}
439
  {contextMenu && (
440
- <div
441
- className="ide-context-menu"
442
- style={{ top: contextMenu.y, left: contextMenu.x }}
443
- onMouseLeave={() => setContextMenu(null)}
444
- >
445
- <div
446
- onClick={() => {
447
- setContextMenu(null);
448
- handleRename();
449
- }}
450
- >
451
- ✏️ Rename
452
- </div>
453
- <div
454
- onClick={() => {
455
- setContextMenu(null);
456
- handleDelete();
457
- }}
458
- >
459
- πŸ—‘ Delete
460
- </div>
461
- <div
462
- onClick={() => {
463
- setContextMenu(null);
464
- downloadFile();
465
- }}
466
- >
467
- πŸ“₯ Download
468
- </div>
469
  </div>
470
  )}
471
  </div>
 
49
  const editorRef = useRef(null);
50
  const fileInputRef = useRef(null);
51
 
52
+ // NEW: loading states
53
+ const [isRunning, setIsRunning] = useState(false);
54
+ const [isFixing, setIsFixing] = useState(false);
55
+ const [isExplaining, setIsExplaining] = useState(false);
56
+
57
  // Always persist tree on change
58
  useEffect(() => {
59
  saveTree(tree);
 
74
  };
75
 
76
  // ---------- File / Folder actions ----------
 
 
77
  const handleNewFile = () => {
78
  const filename = window.prompt("Filename (with extension):", "untitled.js");
79
  if (!filename) return;
 
87
  parentPath = parts.join("/");
88
  }
89
 
 
90
  const folders = collectFolderPaths(tree);
91
  const suggestion = parentPath || folders[0] || "";
92
  const chosen = window.prompt(
 
98
  const updated = addFile(tree, filename, targetParent);
99
  setTree(updated);
100
 
 
101
  const newPath = (targetParent ? targetParent + "/" : "") + filename;
102
  setActivePath(newPath);
103
  };
104
 
 
105
  const handleNewFolder = () => {
106
  const name = window.prompt("Folder name:", "new_folder");
107
  if (!name) return;
 
111
  setTree(updated);
112
  };
113
 
 
114
  const handleRename = () => {
115
  if (!activePath) return;
116
  const node = getNodeByPath(tree, activePath);
 
120
  const updated = renameNode(tree, activePath, newName);
121
  setTree(updated);
122
 
 
123
  const parts = activePath.split("/");
124
  parts.pop();
125
  const parent = parts.join("/");
 
127
  setActivePath(newPath);
128
  };
129
 
 
130
  const handleDelete = () => {
131
  if (!activePath) return;
132
  const node = getNodeByPath(tree, activePath);
 
142
 
143
  const updated = deleteNode(tree, activePath);
144
  setTree(updated);
145
+ setActivePath("");
146
  };
147
 
 
148
  const downloadFile = () => {
149
  const node = getNodeByPath(tree, activePath);
150
  if (!node || node.type !== "file") return;
 
163
  if (!f) return;
164
  const text = await f.text();
165
 
 
166
  const selected = getNodeByPath(tree, activePath);
167
  let parentPath = "";
 
 
 
168
  if (selected?.type === "folder") parentPath = selected.path;
169
  else if (selected?.type === "file") parentPath = selected.path.split("/").slice(0, -1).join("");
170
 
 
171
  const updated = addFile(tree, f.name, parentPath);
172
  const newPath = (parentPath ? parentPath + "/" : "") + f.name;
173
  const finalTree = updateFileContent(updated, newPath, text);
 
176
  e.target.value = "";
177
  };
178
 
179
+ // ---------- Run & Agent (with progress flags) ----------
 
180
  const handleRun = async () => {
181
  const node = getNodeByPath(tree, activePath);
182
  if (!node || node.type !== "file") {
 
189
  return;
190
  }
191
 
192
+ setIsRunning(true);
193
+ setOutput(""); // clear previous
194
+ setProblems([]);
195
+ try {
196
+ const res = await runCode(node.content, selectedLang, stdin);
197
+ setOutput(res.output || "");
198
+ setProblems(res.error ? parseProblems(res.output) : []);
199
+ } catch (err) {
200
+ setOutput(String(err));
201
+ } finally {
202
+ setIsRunning(false);
203
+ }
204
  };
205
 
206
  const handleAskFix = async () => {
 
209
  setOutput("Select a file to apply fix.");
210
  return;
211
  }
212
+ setIsFixing(true);
213
+ try {
214
+ const userHint = prompt.trim() ? `User request: ${prompt}` : "";
215
+ const reply = await askAgent(
216
+ `Improve, debug, or refactor this ${LANGUAGE_OPTIONS.find((l) => node.name.endsWith(l.ext))?.id || "file"} file.\n${userHint}\nReturn ONLY updated code, no explanation.\n\nCODE:\n${node.content}`
217
+ );
218
+ const updatedTree = updateFileContent(tree, node.path, reply);
219
+ setTree(updatedTree);
220
+ } catch (err) {
221
+ setOutput(String(err));
222
+ } finally {
223
+ setIsFixing(false);
224
+ }
225
  };
226
 
227
  const handleExplainSelection = async () => {
 
230
  setExplanation("Select a file to explain.");
231
  return;
232
  }
233
+ setIsExplaining(true);
 
234
  try {
235
+ const editor = editorRef.current;
236
+ let selectedCode = "";
237
+ try {
238
+ selectedCode = editor?.getModel()?.getValueInRange(editor.getSelection()) || "";
239
+ } catch {}
240
+ const code = selectedCode.trim() || node.content;
241
+ const userHint = prompt.trim() ? `Focus on: ${prompt}` : "Give a clear and simple explanation.";
242
+ const reply = await askAgent(
243
+ `Explain what this code does, any risks, and improvements.\n${userHint}\n\nCODE:\n${code}`
244
+ );
245
+ setExplanation(reply);
246
+ } catch (err) {
247
+ setExplanation(String(err));
248
+ } finally {
249
+ setIsExplaining(false);
250
+ }
251
  };
252
 
253
  // AI suggestions for continuation (simple)
254
  const fetchAiSuggestions = async (code) => {
255
  if (!code?.trim()) return;
256
+ try {
257
+ const reply = await askAgent(`Suggest possible next lines for continuation. Return 3 short snippets.\n${code}`);
258
+ setAiSuggestions(reply.split("\n").filter((l) => l.trim()));
259
+ } catch {
260
+ // ignore suggestion errors
261
+ }
262
  };
263
 
264
  // ---------- Search ----------
 
278
  setTree(updated);
279
  };
280
 
281
+ // ---------- Render Tree (inside component) ----------
282
  const renderTree = (node, depth = 0) => {
283
  const isActive = node.path === activePath;
284
  return (
 
297
  <span className="ide-file-name">{node.name}</span>
298
  </div>
299
 
300
+ {node.children && node.children.map((c) => renderTree(c, depth + 1))}
 
301
  </div>
302
  );
303
  };
304
 
305
+ // any loading?
306
+ const anyLoading = isRunning || isFixing || isExplaining;
307
+
308
  // ---------- JSX UI ----------
309
  return (
310
  <div className={`ide-root ${theme === "vs-dark" ? "ide-dark" : "ide-light"}`}>
311
  {/* Hidden file input for import */}
312
+ <input ref={fileInputRef} id="file-import-input" type="file" style={{ display: "none" }} onChange={handleFileInputChange} />
 
 
 
 
 
 
313
 
314
  {/* Top menu */}
315
  <div className="ide-menubar">
316
  <div className="ide-menubar-left">
317
  <span className="ide-logo">βš™οΈ DevMate IDE</span>
318
 
319
+ <button onClick={handleNewFile} disabled={anyLoading}>πŸ“„ New File</button>
320
+ <button onClick={handleNewFolder} disabled={anyLoading}>πŸ“ New Folder</button>
321
+ <button onClick={handleRename} disabled={anyLoading}>✏️ Rename</button>
322
+ <button onClick={handleDelete} disabled={anyLoading}>πŸ—‘ Delete</button>
323
+ <button onClick={downloadFile} disabled={anyLoading}>πŸ“₯ Download</button>
324
+ <button onClick={() => downloadProjectZip()} disabled={anyLoading}>πŸ“¦ ZIP</button>
325
+ <button onClick={handleImportFileClick} disabled={anyLoading}>πŸ“€ Import File</button>
326
  </div>
327
 
328
  <div className="ide-menubar-right">
329
+ <button onClick={handleSearchToggle} disabled={anyLoading}>πŸ” Search</button>
330
+
331
+ <button onClick={handleRun} disabled={isRunning || anyLoading}>
332
+ {isRunning ? "⏳ Running..." : "β–Ά Run"}
333
+ </button>
334
+
335
+ <button onClick={handleAskFix} disabled={isFixing || anyLoading}>
336
+ {isFixing ? "⏳ Fixing..." : "πŸ€– Fix"}
337
+ </button>
338
+
339
+ <button onClick={handleExplainSelection} disabled={isExplaining || anyLoading}>
340
+ {isExplaining ? "⏳ Explaining..." : "πŸ“– Explain"}
341
+ </button>
342
+
343
+ <button onClick={() => setTheme((t) => (t === "vs-dark" ? "light" : "vs-dark"))} disabled={anyLoading}>
344
  {theme === "vs-dark" ? "β˜€οΈ" : "πŸŒ™"}
345
  </button>
346
  </div>
347
  </div>
348
 
349
+ {/* Indeterminate progress bar under menubar when busy */}
350
+ {anyLoading && (
351
+ <div className="ide-progress-wrap">
352
+ <div className="ide-progress" />
353
+ </div>
354
+ )}
355
+
356
  {/* Body */}
357
  <div className="ide-body">
358
  {/* Sidebar */}
359
  <div className="ide-sidebar">
360
  <div className="ide-sidebar-header">
361
  <span>EXPLORER</span>
362
+ <button className="ide-icon-button" onClick={handleNewFile} title="New File" disabled={anyLoading}>οΌ‹</button>
 
 
 
363
  </div>
364
+ <div className="ide-file-list" style={{ padding: 6 }}>{renderTree(tree)}</div>
365
  </div>
366
 
367
  {/* Main (editor + bottom panels) */}
 
387
  {aiSuggestions.length > 0 && (
388
  <div className="ai-popup">
389
  {aiSuggestions.map((s, i) => (
390
+ <div key={i} className="ai-suggest" onClick={() => updateActiveFileContent((currentNode?.content || "") + "\n" + s)}>{s}</div>
 
 
 
 
 
 
 
 
391
  ))}
392
  </div>
393
  )}
 
396
  <div className="ide-panels">
397
  <pre className="ide-output">{output}</pre>
398
 
399
+ <input className="ide-input-box" placeholder="Program input..." value={stdin} onChange={(e) => setStdin(e.target.value)} />
 
 
 
 
 
400
 
401
  {problems.length > 0 && (
402
  <div className="ide-problems-panel">
 
417
 
418
  <div className="ide-ai-section">
419
  <label className="ide-ai-label">Instruction</label>
420
+ <textarea className="ide-agent-textarea" placeholder="Ask the AI (optimize, add tests, convert, etc.)" value={prompt} onChange={(e) => setPrompt(e.target.value)} />
 
 
 
 
 
421
  </div>
422
 
423
  <div className="ide-ai-buttons">
424
+ <button onClick={handleAskFix} disabled={isFixing || anyLoading}>{isFixing ? "⏳ Apply Fix" : "πŸ’‘ Apply Fix"}</button>
425
+ <button onClick={handleExplainSelection} disabled={isExplaining || anyLoading}>{isExplaining ? "⏳ Explaining" : "πŸ“– Explain Code"}</button>
426
  </div>
427
 
428
  {explanation && (
 
437
  {/* Search dialog */}
438
  {searchOpen && (
439
  <div className="search-dialog">
440
+ <input placeholder="Search text..." onChange={(e) => setSearchQuery(e.target.value)} />
 
 
 
441
  <button onClick={handleSearchNow}>Search</button>
442
  </div>
443
  )}
444
 
445
  {/* Context menu */}
446
  {contextMenu && (
447
+ <div className="ide-context-menu" style={{ top: contextMenu.y, left: contextMenu.x }} onMouseLeave={() => setContextMenu(null)}>
448
+ <div onClick={() => { setContextMenu(null); handleRename(); }}>✏️ Rename</div>
449
+ <div onClick={() => { setContextMenu(null); handleDelete(); }}>πŸ—‘ Delete</div>
450
+ <div onClick={() => { setContextMenu(null); downloadFile(); }}>πŸ“₯ Download</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
451
  </div>
452
  )}
453
  </div>