create-stylus-ide 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.
Files changed (135) hide show
  1. package/Readme.MD +1515 -0
  2. package/cli.js +28 -0
  3. package/frontend/.vscode/settings.json +9 -0
  4. package/frontend/app/api/chat/route.ts +101 -0
  5. package/frontend/app/api/check-setup/route.ts +93 -0
  6. package/frontend/app/api/cleanup/route.ts +14 -0
  7. package/frontend/app/api/compile/route.ts +95 -0
  8. package/frontend/app/api/compile-stream/route.ts +98 -0
  9. package/frontend/app/api/complete/route.ts +86 -0
  10. package/frontend/app/api/deploy/route.ts +118 -0
  11. package/frontend/app/api/export-abi/route.ts +58 -0
  12. package/frontend/app/favicon.ico +0 -0
  13. package/frontend/app/globals.css +177 -0
  14. package/frontend/app/layout.tsx +29 -0
  15. package/frontend/app/ml/page.tsx +694 -0
  16. package/frontend/app/page.tsx +1132 -0
  17. package/frontend/app/providers.tsx +18 -0
  18. package/frontend/app/qlearning/page.tsx +188 -0
  19. package/frontend/app/raytracing/page.tsx +268 -0
  20. package/frontend/components/abi/ABIDialog.tsx +132 -0
  21. package/frontend/components/ai/AICompletionPopup.tsx +76 -0
  22. package/frontend/components/ai/ChatPanel.tsx +292 -0
  23. package/frontend/components/ai/QuickActions.tsx +128 -0
  24. package/frontend/components/blockchain/BlockchainContractBanner.tsx +64 -0
  25. package/frontend/components/blockchain/BlockchainLoadingDialog.tsx +188 -0
  26. package/frontend/components/deploy/DeployDialog.tsx +334 -0
  27. package/frontend/components/editor/FileTabs.tsx +181 -0
  28. package/frontend/components/editor/MonacoEditor.tsx +306 -0
  29. package/frontend/components/file-tree/ContextMenu.tsx +110 -0
  30. package/frontend/components/file-tree/DeleteConfirmDialog.tsx +61 -0
  31. package/frontend/components/file-tree/FileInputDialog.tsx +97 -0
  32. package/frontend/components/file-tree/FileNode.tsx +60 -0
  33. package/frontend/components/file-tree/FileTree.tsx +259 -0
  34. package/frontend/components/file-tree/FileTreeSkeleton.tsx +26 -0
  35. package/frontend/components/file-tree/FolderNode.tsx +105 -0
  36. package/frontend/components/github/GitHubLoadingDialog.tsx +201 -0
  37. package/frontend/components/github/GitHubMetadataBanner.tsx +61 -0
  38. package/frontend/components/github/LoadFromGitHubDialog.tsx +125 -0
  39. package/frontend/components/github/URLCopyButton.tsx +60 -0
  40. package/frontend/components/interact/ContractInteraction.tsx +323 -0
  41. package/frontend/components/interact/ContractPlaceholder.tsx +41 -0
  42. package/frontend/components/orbit/BenchmarkDialog.tsx +342 -0
  43. package/frontend/components/orbit/OrbitExplorer.tsx +273 -0
  44. package/frontend/components/project/ProjectActions.tsx +176 -0
  45. package/frontend/components/q-learning/ContractConfig.tsx +172 -0
  46. package/frontend/components/q-learning/MazeGrid.tsx +346 -0
  47. package/frontend/components/q-learning/PathAnimation.tsx +384 -0
  48. package/frontend/components/q-learning/QTableHeatmap.tsx +300 -0
  49. package/frontend/components/q-learning/TrainingForm.tsx +349 -0
  50. package/frontend/components/ray-tracing/ContractConfig.tsx +245 -0
  51. package/frontend/components/ray-tracing/MintingForm.tsx +280 -0
  52. package/frontend/components/ray-tracing/RenderCanvas.tsx +228 -0
  53. package/frontend/components/ray-tracing/RenderingPanel.tsx +259 -0
  54. package/frontend/components/ray-tracing/StyleControls.tsx +217 -0
  55. package/frontend/components/setup/SetupGuide.tsx +290 -0
  56. package/frontend/components/ui/KeyboardShortcutHint.tsx +74 -0
  57. package/frontend/components/ui/alert-dialog.tsx +157 -0
  58. package/frontend/components/ui/alert.tsx +66 -0
  59. package/frontend/components/ui/badge.tsx +46 -0
  60. package/frontend/components/ui/button.tsx +62 -0
  61. package/frontend/components/ui/card.tsx +92 -0
  62. package/frontend/components/ui/context-menu.tsx +252 -0
  63. package/frontend/components/ui/dialog.tsx +143 -0
  64. package/frontend/components/ui/dropdown-menu.tsx +257 -0
  65. package/frontend/components/ui/input.tsx +21 -0
  66. package/frontend/components/ui/label.tsx +24 -0
  67. package/frontend/components/ui/progress.tsx +31 -0
  68. package/frontend/components/ui/scroll-area.tsx +58 -0
  69. package/frontend/components/ui/select.tsx +190 -0
  70. package/frontend/components/ui/separator.tsx +28 -0
  71. package/frontend/components/ui/sheet.tsx +139 -0
  72. package/frontend/components/ui/skeleton.tsx +13 -0
  73. package/frontend/components/ui/slider.tsx +63 -0
  74. package/frontend/components/ui/sonner.tsx +40 -0
  75. package/frontend/components/ui/tabs.tsx +66 -0
  76. package/frontend/components/ui/textarea.tsx +18 -0
  77. package/frontend/components/wallet/ConnectButton.tsx +167 -0
  78. package/frontend/components/wallet/FaucetButton.tsx +256 -0
  79. package/frontend/components.json +22 -0
  80. package/frontend/eslint.config.mjs +18 -0
  81. package/frontend/hooks/useAICompletion.ts +75 -0
  82. package/frontend/hooks/useBlockchainLoader.ts +58 -0
  83. package/frontend/hooks/useChats.ts +137 -0
  84. package/frontend/hooks/useCompilation.ts +173 -0
  85. package/frontend/hooks/useFileTabs.ts +178 -0
  86. package/frontend/hooks/useGitHubLoader.ts +50 -0
  87. package/frontend/hooks/useKeyboardShortcuts.ts +47 -0
  88. package/frontend/hooks/usePanelState.ts +115 -0
  89. package/frontend/hooks/useProjectState.ts +276 -0
  90. package/frontend/hooks/useResponsive.ts +29 -0
  91. package/frontend/lib/abi-parser.ts +58 -0
  92. package/frontend/lib/blockchain-api.ts +374 -0
  93. package/frontend/lib/blockchain-explorers.ts +75 -0
  94. package/frontend/lib/blockchain-loader.ts +112 -0
  95. package/frontend/lib/cargo-template.ts +64 -0
  96. package/frontend/lib/compilation.ts +529 -0
  97. package/frontend/lib/constants.ts +31 -0
  98. package/frontend/lib/deployment.ts +176 -0
  99. package/frontend/lib/file-utils.ts +83 -0
  100. package/frontend/lib/github-api.ts +246 -0
  101. package/frontend/lib/github-loader.ts +369 -0
  102. package/frontend/lib/ml-contract-template.txt +900 -0
  103. package/frontend/lib/orbit-chains.ts +181 -0
  104. package/frontend/lib/output-formatter.ts +68 -0
  105. package/frontend/lib/project-manager.ts +632 -0
  106. package/frontend/lib/ray-tracing-abi.ts +206 -0
  107. package/frontend/lib/storage.ts +189 -0
  108. package/frontend/lib/templates.ts +1662 -0
  109. package/frontend/lib/url-parser.ts +188 -0
  110. package/frontend/lib/utils.ts +6 -0
  111. package/frontend/lib/wagmi-config.ts +24 -0
  112. package/frontend/next.config.ts +7 -0
  113. package/frontend/package-lock.json +16259 -0
  114. package/frontend/package.json +60 -0
  115. package/frontend/postcss.config.mjs +7 -0
  116. package/frontend/public/file.svg +1 -0
  117. package/frontend/public/globe.svg +1 -0
  118. package/frontend/public/ml-weights/.gitkeep +0 -0
  119. package/frontend/public/ml-weights/model.pkl +0 -0
  120. package/frontend/public/ml-weights/model_weights.json +27102 -0
  121. package/frontend/public/ml-weights/test_samples.json +7888 -0
  122. package/frontend/public/next.svg +1 -0
  123. package/frontend/public/vercel.svg +1 -0
  124. package/frontend/public/window.svg +1 -0
  125. package/frontend/scripts/check-env.js +52 -0
  126. package/frontend/scripts/setup.js +285 -0
  127. package/frontend/tailwind.config.ts +64 -0
  128. package/frontend/tsconfig.json +34 -0
  129. package/frontend/types/blockchain.ts +63 -0
  130. package/frontend/types/github.ts +54 -0
  131. package/frontend/types/project.ts +106 -0
  132. package/ml-training/README.md +56 -0
  133. package/ml-training/train_tiny_model.py +325 -0
  134. package/ml-training/update_template.py +59 -0
  135. package/package.json +30 -0
@@ -0,0 +1,306 @@
1
+ 'use client';
2
+
3
+ import Editor, { Monaco } from '@monaco-editor/react';
4
+ import { useEffect, useRef, useState } from 'react';
5
+ import type { editor, languages, IDisposable } from 'monaco-editor';
6
+ import { AICompletionPopup } from '../ai/AICompletionPopup';
7
+ import { useAICompletion } from '@/hooks/useAICompletion';
8
+
9
+ interface MonacoEditorProps {
10
+ value: string;
11
+ onChange: (value: string) => void;
12
+ onSave?: () => void;
13
+ readOnly?: boolean;
14
+ errors?: Array<{ line: number; column: number; message: string }>;
15
+ language?: string;
16
+ }
17
+
18
+ export function MonacoEditor({
19
+ value,
20
+ onChange,
21
+ onSave,
22
+ readOnly = false,
23
+ errors = [],
24
+ language = 'rust',
25
+ }: MonacoEditorProps) {
26
+ const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
27
+ const monacoRef = useRef<Monaco | null>(null);
28
+
29
+ const [showCompletion, setShowCompletion] = useState(false);
30
+ const [completionPosition, setCompletionPosition] = useState({ top: 0, left: 0 });
31
+
32
+ const { isLoading, completion, generateCompletion, clearCompletion } = useAICompletion();
33
+
34
+ // ✅ SSR-safe minimap enablement
35
+ const [minimapEnabled, setMinimapEnabled] = useState(false);
36
+
37
+ // (optional but nice) avoid provider leaks / duplicates across unmounts
38
+ const completionProviderDisposableRef = useRef<IDisposable | null>(null);
39
+
40
+ useEffect(() => {
41
+ const update = () => setMinimapEnabled(window.innerWidth > 768);
42
+ update();
43
+ window.addEventListener('resize', update);
44
+ return () => window.removeEventListener('resize', update);
45
+ }, []);
46
+
47
+ // keep minimap synced after mount + on resize changes
48
+ useEffect(() => {
49
+ editorRef.current?.updateOptions({ minimap: { enabled: minimapEnabled } });
50
+ }, [minimapEnabled]);
51
+
52
+ // cleanup any registered provider on unmount (prevents StrictMode duplicates)
53
+ useEffect(() => {
54
+ return () => {
55
+ completionProviderDisposableRef.current?.dispose?.();
56
+ completionProviderDisposableRef.current = null;
57
+ };
58
+ }, []);
59
+
60
+ // Apply error markers when errors change
61
+ useEffect(() => {
62
+ const monaco = monacoRef.current;
63
+ const ed = editorRef.current;
64
+ if (!monaco || !ed) return;
65
+
66
+ const model = ed.getModel();
67
+ if (!model) return;
68
+
69
+ const owner = 'stylus-errors';
70
+ monaco.editor.setModelMarkers(model, owner, []);
71
+
72
+ if (errors.length > 0) {
73
+ const markers: editor.IMarkerData[] = errors.map((error) => ({
74
+ severity: monaco.MarkerSeverity.Error,
75
+ startLineNumber: error.line,
76
+ startColumn: error.column,
77
+ endLineNumber: error.line,
78
+ endColumn: Math.max(error.column + 1, error.column + 10),
79
+ message: error.message,
80
+ source: language,
81
+ }));
82
+
83
+ monaco.editor.setModelMarkers(model, owner, markers);
84
+ }
85
+ }, [errors, language]);
86
+
87
+ // Handle keyboard shortcuts for completion
88
+ useEffect(() => {
89
+ const handleKeyDown = (e: KeyboardEvent) => {
90
+ // Escape to close
91
+ if (e.key === 'Escape' && showCompletion) {
92
+ setShowCompletion(false);
93
+ clearCompletion();
94
+ }
95
+
96
+ // Tab to accept
97
+ if (e.key === 'Tab' && showCompletion && completion) {
98
+ e.preventDefault();
99
+ handleAcceptCompletion(completion);
100
+ }
101
+ };
102
+
103
+ window.addEventListener('keydown', handleKeyDown);
104
+ return () => window.removeEventListener('keydown', handleKeyDown);
105
+ }, [showCompletion, completion, clearCompletion]);
106
+
107
+ function handleEditorDidMount(ed: editor.IStandaloneCodeEditor, monaco: Monaco) {
108
+ editorRef.current = ed;
109
+ monacoRef.current = monaco;
110
+
111
+ // ✅ ensure minimap is correct immediately after mount
112
+ ed.updateOptions({ minimap: { enabled: minimapEnabled } });
113
+
114
+ // Register Stylus-specific snippets (and avoid duplicates)
115
+ if (!completionProviderDisposableRef.current) {
116
+ const provider: languages.CompletionItemProvider = {
117
+ provideCompletionItems: (model, position) => {
118
+ const word = model.getWordUntilPosition(position);
119
+ const range = {
120
+ startLineNumber: position.lineNumber,
121
+ endLineNumber: position.lineNumber,
122
+ startColumn: word.startColumn,
123
+ endColumn: word.endColumn,
124
+ };
125
+
126
+ const suggestions: languages.CompletionItem[] = [
127
+ {
128
+ label: 'sol_storage',
129
+ kind: monaco.languages.CompletionItemKind.Snippet,
130
+ insertText: [
131
+ 'sol_storage! {',
132
+ ' #[entrypoint]',
133
+ ' pub struct ${1:ContractName} {',
134
+ ' ${2:// Add storage variables}',
135
+ ' }',
136
+ '}',
137
+ ].join('\n'),
138
+ insertTextRules:
139
+ monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
140
+ documentation: 'Stylus storage macro',
141
+ range,
142
+ },
143
+ {
144
+ label: '#[public]',
145
+ kind: monaco.languages.CompletionItemKind.Snippet,
146
+ insertText: [
147
+ '#[public]',
148
+ 'impl ${1:ContractName} {',
149
+ ' pub fn ${2:function_name}(&self) -> ${3:ReturnType} {',
150
+ ' ${4:// Implementation}',
151
+ ' }',
152
+ '}',
153
+ ].join('\n'),
154
+ insertTextRules:
155
+ monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
156
+ documentation: 'Stylus public implementation',
157
+ range,
158
+ },
159
+ ];
160
+
161
+ return { suggestions };
162
+ },
163
+ };
164
+
165
+ completionProviderDisposableRef.current =
166
+ monaco.languages.registerCompletionItemProvider('rust', provider);
167
+ }
168
+
169
+ // AI Completion - Ctrl/Cmd + K
170
+ ed.addAction({
171
+ id: 'ai-complete',
172
+ label: 'AI Complete',
173
+ keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyK],
174
+ run: () => {
175
+ handleAIComplete();
176
+ },
177
+ });
178
+
179
+ // Compile - Ctrl/Cmd + S
180
+ ed.addAction({
181
+ id: 'compile-contract',
182
+ label: 'Compile Contract',
183
+ keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS],
184
+ run: () => onSave?.(),
185
+ });
186
+
187
+ ed.focus();
188
+ }
189
+
190
+ function handleEditorChange(newValue: string | undefined) {
191
+ onChange(newValue || '');
192
+ }
193
+
194
+ async function handleAIComplete() {
195
+ if (!editorRef.current) return;
196
+
197
+ const position = editorRef.current.getPosition();
198
+ if (!position) return;
199
+
200
+ const model = editorRef.current.getModel();
201
+ if (!model) return;
202
+
203
+ // Get current line text
204
+ const lineContent = model.getLineContent(position.lineNumber);
205
+ const currentLineText = lineContent.substring(0, position.column - 1);
206
+
207
+ // Get context (previous 20 lines)
208
+ const startLine = Math.max(1, position.lineNumber - 20);
209
+ const contextRange = {
210
+ startLineNumber: startLine,
211
+ startColumn: 1,
212
+ endLineNumber: position.lineNumber,
213
+ endColumn: position.column,
214
+ };
215
+ const context = model.getValueInRange(contextRange);
216
+
217
+ // Calculate popup position
218
+ const coords = editorRef.current.getScrolledVisiblePosition(position);
219
+ if (coords) {
220
+ const editorDom = editorRef.current.getDomNode();
221
+ if (editorDom) {
222
+ const rect = editorDom.getBoundingClientRect();
223
+ setCompletionPosition({
224
+ top: rect.top + coords.top + coords.height + 5,
225
+ left: rect.left + coords.left,
226
+ });
227
+ }
228
+ }
229
+
230
+ setShowCompletion(true);
231
+ await generateCompletion(currentLineText, context);
232
+ }
233
+
234
+ function handleAcceptCompletion(completionText: string) {
235
+ if (!editorRef.current) return;
236
+
237
+ const position = editorRef.current.getPosition();
238
+ if (!position) return;
239
+
240
+ // Insert completion at current position
241
+ const range = {
242
+ startLineNumber: position.lineNumber,
243
+ startColumn: position.column,
244
+ endLineNumber: position.lineNumber,
245
+ endColumn: position.column,
246
+ };
247
+
248
+ editorRef.current.executeEdits('ai-completion', [
249
+ {
250
+ range,
251
+ text: completionText,
252
+ forceMoveMarkers: true,
253
+ },
254
+ ]);
255
+
256
+ setShowCompletion(false);
257
+ clearCompletion();
258
+ editorRef.current.focus();
259
+ }
260
+
261
+ function handleRejectCompletion() {
262
+ setShowCompletion(false);
263
+ clearCompletion();
264
+ editorRef.current?.focus();
265
+ }
266
+
267
+ return (
268
+ <>
269
+ <Editor
270
+ height="100%"
271
+ defaultLanguage={language}
272
+ language={language}
273
+ value={value}
274
+ onChange={handleEditorChange}
275
+ onMount={handleEditorDidMount}
276
+ theme="vs-dark"
277
+ options={{
278
+ readOnly,
279
+ fontSize: 14,
280
+ fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
281
+ minimap: { enabled: minimapEnabled }, // ✅ no window here
282
+ scrollBeyondLastLine: false,
283
+ automaticLayout: true,
284
+ tabSize: 4,
285
+ formatOnPaste: true,
286
+ formatOnType: true,
287
+ rulers: [80, 100],
288
+ wordWrap: 'on',
289
+ lineNumbers: 'on',
290
+ glyphMargin: true,
291
+ folding: true,
292
+ bracketPairColorization: { enabled: true },
293
+ }}
294
+ />
295
+
296
+ <AICompletionPopup
297
+ visible={showCompletion}
298
+ position={completionPosition}
299
+ onAccept={handleAcceptCompletion}
300
+ onReject={handleRejectCompletion}
301
+ isLoading={isLoading}
302
+ completion={completion}
303
+ />
304
+ </>
305
+ );
306
+ }
@@ -0,0 +1,110 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useCallback } from 'react';
4
+ import { FileNode } from '@/types/project';
5
+ import {
6
+ ContextMenu as ContextMenuPrimitive,
7
+ ContextMenuContent,
8
+ ContextMenuItem,
9
+ ContextMenuSeparator,
10
+ ContextMenuTrigger,
11
+ } from '@/components/ui/context-menu';
12
+ import {
13
+ FilePlus,
14
+ FolderPlus,
15
+ Pencil,
16
+ Copy,
17
+ Trash2,
18
+ } from 'lucide-react';
19
+
20
+ interface ContextMenuProps {
21
+ node: FileNode | null;
22
+ onNewFile: (parentPath?: string) => void;
23
+ onNewFolder: (parentPath?: string) => void;
24
+ onRename: (path: string) => void;
25
+ onDuplicate: (path: string) => void;
26
+ onDelete: (path: string, isFolder: boolean) => void;
27
+ children: React.ReactNode;
28
+ }
29
+
30
+ export function FileTreeContextMenu({
31
+ node,
32
+ onNewFile,
33
+ onNewFolder,
34
+ onRename,
35
+ onDuplicate,
36
+ onDelete,
37
+ children,
38
+ }: ContextMenuProps) {
39
+ const isFolder = node?.type === 'folder';
40
+ const isFile = node?.type === 'file';
41
+
42
+ return (
43
+ <ContextMenuPrimitive>
44
+ <ContextMenuTrigger asChild>
45
+ {children}
46
+ </ContextMenuTrigger>
47
+
48
+ <ContextMenuContent className="w-56">
49
+ {/* New File/Folder - Available for folders and empty space */}
50
+ {(isFolder || !node) && (
51
+ <>
52
+ <ContextMenuItem
53
+ onClick={() => onNewFile(node?.path)}
54
+ className="cursor-pointer"
55
+ >
56
+ <FilePlus className="h-4 w-4 mr-2" />
57
+ New File
58
+ </ContextMenuItem>
59
+
60
+ <ContextMenuItem
61
+ onClick={() => onNewFolder(node?.path)}
62
+ className="cursor-pointer"
63
+ >
64
+ <FolderPlus className="h-4 w-4 mr-2" />
65
+ New Folder
66
+ </ContextMenuItem>
67
+
68
+ {node && <ContextMenuSeparator />}
69
+ </>
70
+ )}
71
+
72
+ {/* File-specific actions */}
73
+ {isFile && (
74
+ <>
75
+ <ContextMenuItem
76
+ onClick={() => onDuplicate(node.path)}
77
+ className="cursor-pointer"
78
+ >
79
+ <Copy className="h-4 w-4 mr-2" />
80
+ Duplicate
81
+ </ContextMenuItem>
82
+
83
+ <ContextMenuSeparator />
84
+ </>
85
+ )}
86
+
87
+ {/* Rename & Delete - Available for both files and folders */}
88
+ {node && (
89
+ <>
90
+ <ContextMenuItem
91
+ onClick={() => onRename(node.path)}
92
+ className="cursor-pointer"
93
+ >
94
+ <Pencil className="h-4 w-4 mr-2" />
95
+ Rename
96
+ </ContextMenuItem>
97
+
98
+ <ContextMenuItem
99
+ onClick={() => onDelete(node.path, isFolder)}
100
+ className="cursor-pointer text-red-600 focus:text-red-600"
101
+ >
102
+ <Trash2 className="h-4 w-4 mr-2" />
103
+ Delete
104
+ </ContextMenuItem>
105
+ </>
106
+ )}
107
+ </ContextMenuContent>
108
+ </ContextMenuPrimitive>
109
+ );
110
+ }
@@ -0,0 +1,61 @@
1
+ 'use client';
2
+
3
+ import {
4
+ AlertDialog,
5
+ AlertDialogAction,
6
+ AlertDialogCancel,
7
+ AlertDialogContent,
8
+ AlertDialogDescription,
9
+ AlertDialogFooter,
10
+ AlertDialogHeader,
11
+ AlertDialogTitle,
12
+ } from '@/components/ui/alert-dialog';
13
+
14
+ interface DeleteConfirmDialogProps {
15
+ open: boolean;
16
+ onOpenChange: (open: boolean) => void;
17
+ itemName: string;
18
+ isFolder: boolean;
19
+ onConfirm: () => void;
20
+ }
21
+
22
+ export function DeleteConfirmDialog({
23
+ open,
24
+ onOpenChange,
25
+ itemName,
26
+ isFolder,
27
+ onConfirm,
28
+ }: DeleteConfirmDialogProps) {
29
+ return (
30
+ <AlertDialog open={open} onOpenChange={onOpenChange}>
31
+ <AlertDialogContent>
32
+ <AlertDialogHeader>
33
+ <AlertDialogTitle>
34
+ Delete {isFolder ? 'Folder' : 'File'}?
35
+ </AlertDialogTitle>
36
+ <AlertDialogDescription>
37
+ Are you sure you want to delete <strong>{itemName}</strong>?
38
+ {isFolder && (
39
+ <span className="block mt-2 text-red-600">
40
+ This will delete all files inside this folder.
41
+ </span>
42
+ )}
43
+ <span className="block mt-2">
44
+ This action cannot be undone.
45
+ </span>
46
+ </AlertDialogDescription>
47
+ </AlertDialogHeader>
48
+
49
+ <AlertDialogFooter>
50
+ <AlertDialogCancel>Cancel</AlertDialogCancel>
51
+ <AlertDialogAction
52
+ onClick={onConfirm}
53
+ className="bg-red-600 hover:bg-red-700 focus:ring-red-600"
54
+ >
55
+ Delete
56
+ </AlertDialogAction>
57
+ </AlertDialogFooter>
58
+ </AlertDialogContent>
59
+ </AlertDialog>
60
+ );
61
+ }
@@ -0,0 +1,97 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect } from 'react';
4
+ import {
5
+ Dialog,
6
+ DialogContent,
7
+ DialogDescription,
8
+ DialogFooter,
9
+ DialogHeader,
10
+ DialogTitle,
11
+ } from '@/components/ui/dialog';
12
+ import { Button } from '@/components/ui/button';
13
+ import { Input } from '@/components/ui/input';
14
+ import { Label } from '@/components/ui/label';
15
+
16
+ interface FileInputDialogProps {
17
+ open: boolean;
18
+ onOpenChange: (open: boolean) => void;
19
+ title: string;
20
+ description: string;
21
+ defaultValue?: string;
22
+ placeholder?: string;
23
+ onConfirm: (value: string) => void;
24
+ }
25
+
26
+ export function FileInputDialog({
27
+ open,
28
+ onOpenChange,
29
+ title,
30
+ description,
31
+ defaultValue = '',
32
+ placeholder = '',
33
+ onConfirm,
34
+ }: FileInputDialogProps) {
35
+ const [value, setValue] = useState(defaultValue);
36
+
37
+ // Reset value when dialog opens
38
+ useEffect(() => {
39
+ if (open) {
40
+ setValue(defaultValue);
41
+ }
42
+ }, [open, defaultValue]);
43
+
44
+ const handleConfirm = () => {
45
+ if (value.trim()) {
46
+ onConfirm(value.trim());
47
+ onOpenChange(false);
48
+ }
49
+ };
50
+
51
+ const handleKeyDown = (e: React.KeyboardEvent) => {
52
+ if (e.key === 'Enter') {
53
+ e.preventDefault();
54
+ handleConfirm();
55
+ }
56
+ };
57
+
58
+ return (
59
+ <Dialog open={open} onOpenChange={onOpenChange}>
60
+ <DialogContent className="sm:max-w-106.25">
61
+ <DialogHeader>
62
+ <DialogTitle>{title}</DialogTitle>
63
+ <DialogDescription>{description}</DialogDescription>
64
+ </DialogHeader>
65
+
66
+ <div className="grid gap-4 py-4">
67
+ <div className="grid gap-2">
68
+ <Label htmlFor="name">Name</Label>
69
+ <Input
70
+ id="name"
71
+ value={value}
72
+ onChange={(e) => setValue(e.target.value)}
73
+ onKeyDown={handleKeyDown}
74
+ placeholder={placeholder}
75
+ autoFocus
76
+ />
77
+ </div>
78
+ </div>
79
+
80
+ <DialogFooter>
81
+ <Button
82
+ variant="outline"
83
+ onClick={() => onOpenChange(false)}
84
+ >
85
+ Cancel
86
+ </Button>
87
+ <Button
88
+ onClick={handleConfirm}
89
+ disabled={!value.trim()}
90
+ >
91
+ Confirm
92
+ </Button>
93
+ </DialogFooter>
94
+ </DialogContent>
95
+ </Dialog>
96
+ );
97
+ }
@@ -0,0 +1,60 @@
1
+ 'use client';
2
+
3
+ import { FileNode as FileNodeType } from '@/types/project';
4
+ import { FileCode, FileText, File } from 'lucide-react';
5
+ import { cn } from '@/lib/utils';
6
+
7
+ interface FileNodeProps {
8
+ node: FileNodeType;
9
+ isActive: boolean;
10
+ onClick: () => void;
11
+ onContextMenu: () => void; // Keep this prop
12
+ depth: number;
13
+ }
14
+
15
+ export function FileNode({
16
+ node,
17
+ isActive,
18
+ onClick,
19
+ onContextMenu,
20
+ depth,
21
+ }: FileNodeProps) {
22
+ const paddingLeft = depth * 12 + 8;
23
+
24
+ const getFileIcon = () => {
25
+ const name = node.name.toLowerCase();
26
+
27
+ if (name.endsWith('.rs')) {
28
+ return <FileCode className="h-4 w-4 text-orange-500" />;
29
+ }
30
+
31
+ if (name.endsWith('.toml')) {
32
+ return <FileText className="h-4 w-4 text-blue-500" />;
33
+ }
34
+
35
+ if (name.endsWith('.md')) {
36
+ return <FileText className="h-4 w-4 text-gray-500" />;
37
+ }
38
+
39
+ return <File className="h-4 w-4 text-gray-400" />;
40
+ };
41
+
42
+ return (
43
+ <div
44
+ className={cn(
45
+ 'flex items-center gap-2 px-2 py-1.5 rounded-md cursor-pointer text-sm',
46
+ 'hover:bg-accent transition-colors select-none',
47
+ isActive && 'bg-accent text-accent-foreground font-medium'
48
+ )}
49
+ style={{ paddingLeft: `${paddingLeft}px` }}
50
+ onClick={onClick}
51
+ onContextMenu={(e) => {
52
+ e.stopPropagation();
53
+ onContextMenu();
54
+ }}
55
+ >
56
+ {getFileIcon()}
57
+ <span className="truncate">{node.name}</span>
58
+ </div>
59
+ );
60
+ }