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.
- package/Readme.MD +1515 -0
- package/cli.js +28 -0
- package/frontend/.vscode/settings.json +9 -0
- package/frontend/app/api/chat/route.ts +101 -0
- package/frontend/app/api/check-setup/route.ts +93 -0
- package/frontend/app/api/cleanup/route.ts +14 -0
- package/frontend/app/api/compile/route.ts +95 -0
- package/frontend/app/api/compile-stream/route.ts +98 -0
- package/frontend/app/api/complete/route.ts +86 -0
- package/frontend/app/api/deploy/route.ts +118 -0
- package/frontend/app/api/export-abi/route.ts +58 -0
- package/frontend/app/favicon.ico +0 -0
- package/frontend/app/globals.css +177 -0
- package/frontend/app/layout.tsx +29 -0
- package/frontend/app/ml/page.tsx +694 -0
- package/frontend/app/page.tsx +1132 -0
- package/frontend/app/providers.tsx +18 -0
- package/frontend/app/qlearning/page.tsx +188 -0
- package/frontend/app/raytracing/page.tsx +268 -0
- package/frontend/components/abi/ABIDialog.tsx +132 -0
- package/frontend/components/ai/AICompletionPopup.tsx +76 -0
- package/frontend/components/ai/ChatPanel.tsx +292 -0
- package/frontend/components/ai/QuickActions.tsx +128 -0
- package/frontend/components/blockchain/BlockchainContractBanner.tsx +64 -0
- package/frontend/components/blockchain/BlockchainLoadingDialog.tsx +188 -0
- package/frontend/components/deploy/DeployDialog.tsx +334 -0
- package/frontend/components/editor/FileTabs.tsx +181 -0
- package/frontend/components/editor/MonacoEditor.tsx +306 -0
- package/frontend/components/file-tree/ContextMenu.tsx +110 -0
- package/frontend/components/file-tree/DeleteConfirmDialog.tsx +61 -0
- package/frontend/components/file-tree/FileInputDialog.tsx +97 -0
- package/frontend/components/file-tree/FileNode.tsx +60 -0
- package/frontend/components/file-tree/FileTree.tsx +259 -0
- package/frontend/components/file-tree/FileTreeSkeleton.tsx +26 -0
- package/frontend/components/file-tree/FolderNode.tsx +105 -0
- package/frontend/components/github/GitHubLoadingDialog.tsx +201 -0
- package/frontend/components/github/GitHubMetadataBanner.tsx +61 -0
- package/frontend/components/github/LoadFromGitHubDialog.tsx +125 -0
- package/frontend/components/github/URLCopyButton.tsx +60 -0
- package/frontend/components/interact/ContractInteraction.tsx +323 -0
- package/frontend/components/interact/ContractPlaceholder.tsx +41 -0
- package/frontend/components/orbit/BenchmarkDialog.tsx +342 -0
- package/frontend/components/orbit/OrbitExplorer.tsx +273 -0
- package/frontend/components/project/ProjectActions.tsx +176 -0
- package/frontend/components/q-learning/ContractConfig.tsx +172 -0
- package/frontend/components/q-learning/MazeGrid.tsx +346 -0
- package/frontend/components/q-learning/PathAnimation.tsx +384 -0
- package/frontend/components/q-learning/QTableHeatmap.tsx +300 -0
- package/frontend/components/q-learning/TrainingForm.tsx +349 -0
- package/frontend/components/ray-tracing/ContractConfig.tsx +245 -0
- package/frontend/components/ray-tracing/MintingForm.tsx +280 -0
- package/frontend/components/ray-tracing/RenderCanvas.tsx +228 -0
- package/frontend/components/ray-tracing/RenderingPanel.tsx +259 -0
- package/frontend/components/ray-tracing/StyleControls.tsx +217 -0
- package/frontend/components/setup/SetupGuide.tsx +290 -0
- package/frontend/components/ui/KeyboardShortcutHint.tsx +74 -0
- package/frontend/components/ui/alert-dialog.tsx +157 -0
- package/frontend/components/ui/alert.tsx +66 -0
- package/frontend/components/ui/badge.tsx +46 -0
- package/frontend/components/ui/button.tsx +62 -0
- package/frontend/components/ui/card.tsx +92 -0
- package/frontend/components/ui/context-menu.tsx +252 -0
- package/frontend/components/ui/dialog.tsx +143 -0
- package/frontend/components/ui/dropdown-menu.tsx +257 -0
- package/frontend/components/ui/input.tsx +21 -0
- package/frontend/components/ui/label.tsx +24 -0
- package/frontend/components/ui/progress.tsx +31 -0
- package/frontend/components/ui/scroll-area.tsx +58 -0
- package/frontend/components/ui/select.tsx +190 -0
- package/frontend/components/ui/separator.tsx +28 -0
- package/frontend/components/ui/sheet.tsx +139 -0
- package/frontend/components/ui/skeleton.tsx +13 -0
- package/frontend/components/ui/slider.tsx +63 -0
- package/frontend/components/ui/sonner.tsx +40 -0
- package/frontend/components/ui/tabs.tsx +66 -0
- package/frontend/components/ui/textarea.tsx +18 -0
- package/frontend/components/wallet/ConnectButton.tsx +167 -0
- package/frontend/components/wallet/FaucetButton.tsx +256 -0
- package/frontend/components.json +22 -0
- package/frontend/eslint.config.mjs +18 -0
- package/frontend/hooks/useAICompletion.ts +75 -0
- package/frontend/hooks/useBlockchainLoader.ts +58 -0
- package/frontend/hooks/useChats.ts +137 -0
- package/frontend/hooks/useCompilation.ts +173 -0
- package/frontend/hooks/useFileTabs.ts +178 -0
- package/frontend/hooks/useGitHubLoader.ts +50 -0
- package/frontend/hooks/useKeyboardShortcuts.ts +47 -0
- package/frontend/hooks/usePanelState.ts +115 -0
- package/frontend/hooks/useProjectState.ts +276 -0
- package/frontend/hooks/useResponsive.ts +29 -0
- package/frontend/lib/abi-parser.ts +58 -0
- package/frontend/lib/blockchain-api.ts +374 -0
- package/frontend/lib/blockchain-explorers.ts +75 -0
- package/frontend/lib/blockchain-loader.ts +112 -0
- package/frontend/lib/cargo-template.ts +64 -0
- package/frontend/lib/compilation.ts +529 -0
- package/frontend/lib/constants.ts +31 -0
- package/frontend/lib/deployment.ts +176 -0
- package/frontend/lib/file-utils.ts +83 -0
- package/frontend/lib/github-api.ts +246 -0
- package/frontend/lib/github-loader.ts +369 -0
- package/frontend/lib/ml-contract-template.txt +900 -0
- package/frontend/lib/orbit-chains.ts +181 -0
- package/frontend/lib/output-formatter.ts +68 -0
- package/frontend/lib/project-manager.ts +632 -0
- package/frontend/lib/ray-tracing-abi.ts +206 -0
- package/frontend/lib/storage.ts +189 -0
- package/frontend/lib/templates.ts +1662 -0
- package/frontend/lib/url-parser.ts +188 -0
- package/frontend/lib/utils.ts +6 -0
- package/frontend/lib/wagmi-config.ts +24 -0
- package/frontend/next.config.ts +7 -0
- package/frontend/package-lock.json +16259 -0
- package/frontend/package.json +60 -0
- package/frontend/postcss.config.mjs +7 -0
- package/frontend/public/file.svg +1 -0
- package/frontend/public/globe.svg +1 -0
- package/frontend/public/ml-weights/.gitkeep +0 -0
- package/frontend/public/ml-weights/model.pkl +0 -0
- package/frontend/public/ml-weights/model_weights.json +27102 -0
- package/frontend/public/ml-weights/test_samples.json +7888 -0
- package/frontend/public/next.svg +1 -0
- package/frontend/public/vercel.svg +1 -0
- package/frontend/public/window.svg +1 -0
- package/frontend/scripts/check-env.js +52 -0
- package/frontend/scripts/setup.js +285 -0
- package/frontend/tailwind.config.ts +64 -0
- package/frontend/tsconfig.json +34 -0
- package/frontend/types/blockchain.ts +63 -0
- package/frontend/types/github.ts +54 -0
- package/frontend/types/project.ts +106 -0
- package/ml-training/README.md +56 -0
- package/ml-training/train_tiny_model.py +325 -0
- package/ml-training/update_template.py +59 -0
- package/package.json +30 -0
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Manager
|
|
3
|
+
* Core logic for managing multi-file projects
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { v4 as uuidv4 } from "uuid";
|
|
7
|
+
import {
|
|
8
|
+
ProjectFile,
|
|
9
|
+
FileNode,
|
|
10
|
+
ProjectState,
|
|
11
|
+
CreateFileOptions,
|
|
12
|
+
CreateFolderOptions,
|
|
13
|
+
MoveFileOptions,
|
|
14
|
+
RenameOptions,
|
|
15
|
+
FileLanguage,
|
|
16
|
+
} from "@/types/project";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create a new empty project
|
|
20
|
+
*/
|
|
21
|
+
export function createProject(
|
|
22
|
+
name: string,
|
|
23
|
+
source: "local" | "github" = "local"
|
|
24
|
+
): ProjectState {
|
|
25
|
+
const now = new Date();
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
id: uuidv4(),
|
|
29
|
+
name,
|
|
30
|
+
type: "stylus-contract", // ✅ ADD THIS LINE
|
|
31
|
+
files: [
|
|
32
|
+
// Default starter files
|
|
33
|
+
createDefaultFile("src/lib.rs", "rust", getDefaultRustContent()),
|
|
34
|
+
createDefaultFile("Cargo.toml", "toml", getDefaultCargoToml(name)),
|
|
35
|
+
],
|
|
36
|
+
structure: [
|
|
37
|
+
{
|
|
38
|
+
id: uuidv4(),
|
|
39
|
+
name: "src",
|
|
40
|
+
path: "src",
|
|
41
|
+
type: "folder",
|
|
42
|
+
expanded: true,
|
|
43
|
+
children: [
|
|
44
|
+
{
|
|
45
|
+
id: uuidv4(),
|
|
46
|
+
name: "lib.rs",
|
|
47
|
+
path: "src/lib.rs",
|
|
48
|
+
type: "file",
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: uuidv4(),
|
|
54
|
+
name: "Cargo.toml",
|
|
55
|
+
path: "Cargo.toml",
|
|
56
|
+
type: "file",
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
activeFilePath: "src/lib.rs",
|
|
60
|
+
source: source,
|
|
61
|
+
createdAt: now,
|
|
62
|
+
updatedAt: now,
|
|
63
|
+
metadata: {
|
|
64
|
+
source,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Add a new file to the project
|
|
71
|
+
*/
|
|
72
|
+
export function addFile(
|
|
73
|
+
project: ProjectState,
|
|
74
|
+
options: CreateFileOptions
|
|
75
|
+
): ProjectState {
|
|
76
|
+
const { path, content = "", language } = options;
|
|
77
|
+
|
|
78
|
+
// Check if file already exists
|
|
79
|
+
if (project.files.find((f) => f.path === path)) {
|
|
80
|
+
throw new Error(`File already exists: ${path}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const fileName = path.split("/").pop() || path;
|
|
84
|
+
const detectedLanguage = language || detectLanguage(fileName);
|
|
85
|
+
|
|
86
|
+
const newFile: ProjectFile = {
|
|
87
|
+
id: uuidv4(),
|
|
88
|
+
path,
|
|
89
|
+
name: fileName,
|
|
90
|
+
content,
|
|
91
|
+
language: detectedLanguage,
|
|
92
|
+
modified: false,
|
|
93
|
+
isOpen: false,
|
|
94
|
+
createdAt: new Date(),
|
|
95
|
+
updatedAt: new Date(),
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Add file to files array
|
|
99
|
+
const updatedFiles = [...project.files, newFile];
|
|
100
|
+
|
|
101
|
+
// Update file tree structure
|
|
102
|
+
const updatedStructure = insertFileIntoTree(project.structure, path);
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
...project,
|
|
106
|
+
files: updatedFiles,
|
|
107
|
+
structure: updatedStructure,
|
|
108
|
+
updatedAt: new Date(),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Add a new folder to the project
|
|
114
|
+
*/
|
|
115
|
+
export function addFolder(
|
|
116
|
+
project: ProjectState,
|
|
117
|
+
options: CreateFolderOptions
|
|
118
|
+
): ProjectState {
|
|
119
|
+
const { path } = options;
|
|
120
|
+
|
|
121
|
+
// Check if folder already exists
|
|
122
|
+
if (findNodeByPath(project.structure, path)) {
|
|
123
|
+
throw new Error(`Folder already exists: ${path}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const updatedStructure = insertFolderIntoTree(project.structure, path);
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
...project,
|
|
130
|
+
structure: updatedStructure,
|
|
131
|
+
updatedAt: new Date(),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Delete a file from the project
|
|
137
|
+
*/
|
|
138
|
+
export function deleteFile(
|
|
139
|
+
project: ProjectState,
|
|
140
|
+
filePath: string
|
|
141
|
+
): ProjectState {
|
|
142
|
+
// Remove from files array
|
|
143
|
+
const updatedFiles = project.files.filter((f) => f.path !== filePath);
|
|
144
|
+
|
|
145
|
+
// Remove from tree structure
|
|
146
|
+
const updatedStructure = removeNodeFromTree(project.structure, filePath);
|
|
147
|
+
|
|
148
|
+
// If active file was deleted, clear active file
|
|
149
|
+
const activeFilePath =
|
|
150
|
+
project.activeFilePath === filePath ? null : project.activeFilePath;
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
...project,
|
|
154
|
+
files: updatedFiles,
|
|
155
|
+
structure: updatedStructure,
|
|
156
|
+
activeFilePath,
|
|
157
|
+
updatedAt: new Date(),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Delete a folder and all its contents
|
|
163
|
+
*/
|
|
164
|
+
export function deleteFolder(
|
|
165
|
+
project: ProjectState,
|
|
166
|
+
folderPath: string
|
|
167
|
+
): ProjectState {
|
|
168
|
+
// Remove all files in folder
|
|
169
|
+
const updatedFiles = project.files.filter(
|
|
170
|
+
(f) => !f.path.startsWith(folderPath + "/")
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// Remove folder from tree
|
|
174
|
+
const updatedStructure = removeNodeFromTree(project.structure, folderPath);
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
...project,
|
|
178
|
+
files: updatedFiles,
|
|
179
|
+
structure: updatedStructure,
|
|
180
|
+
updatedAt: new Date(),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Rename a file or folder
|
|
186
|
+
*/
|
|
187
|
+
export function renameFile(
|
|
188
|
+
project: ProjectState,
|
|
189
|
+
options: RenameOptions
|
|
190
|
+
): ProjectState {
|
|
191
|
+
const { oldPath, newPath } = options;
|
|
192
|
+
|
|
193
|
+
// Check if new path already exists
|
|
194
|
+
if (project.files.find((f) => f.path === newPath)) {
|
|
195
|
+
throw new Error(`File already exists: ${newPath}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Update file in files array
|
|
199
|
+
const updatedFiles = project.files.map((file) => {
|
|
200
|
+
if (file.path === oldPath) {
|
|
201
|
+
const newName = newPath.split("/").pop() || newPath;
|
|
202
|
+
return {
|
|
203
|
+
...file,
|
|
204
|
+
path: newPath,
|
|
205
|
+
name: newName,
|
|
206
|
+
updatedAt: new Date(),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
return file;
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Update tree structure
|
|
213
|
+
const updatedStructure = renameNodeInTree(
|
|
214
|
+
project.structure,
|
|
215
|
+
oldPath,
|
|
216
|
+
newPath
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// Update active file path if it was renamed
|
|
220
|
+
const activeFilePath =
|
|
221
|
+
project.activeFilePath === oldPath ? newPath : project.activeFilePath;
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
...project,
|
|
225
|
+
files: updatedFiles,
|
|
226
|
+
structure: updatedStructure,
|
|
227
|
+
activeFilePath,
|
|
228
|
+
updatedAt: new Date(),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Move a file to a different location
|
|
234
|
+
*/
|
|
235
|
+
export function moveFile(
|
|
236
|
+
project: ProjectState,
|
|
237
|
+
options: MoveFileOptions
|
|
238
|
+
): ProjectState {
|
|
239
|
+
const { fromPath, toPath } = options;
|
|
240
|
+
|
|
241
|
+
// This is similar to rename, but handles moving between folders
|
|
242
|
+
return renameFile(project, { oldPath: fromPath, newPath: toPath });
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get a file by its path
|
|
247
|
+
*/
|
|
248
|
+
export function getFileByPath(
|
|
249
|
+
project: ProjectState,
|
|
250
|
+
path: string
|
|
251
|
+
): ProjectFile | undefined {
|
|
252
|
+
return project.files.find((f) => f.path === path);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Update file content
|
|
257
|
+
*/
|
|
258
|
+
export function updateFileContent(
|
|
259
|
+
project: ProjectState,
|
|
260
|
+
filePath: string,
|
|
261
|
+
content: string
|
|
262
|
+
): ProjectState {
|
|
263
|
+
const updatedFiles = project.files.map((file) => {
|
|
264
|
+
if (file.path === filePath) {
|
|
265
|
+
return {
|
|
266
|
+
...file,
|
|
267
|
+
content,
|
|
268
|
+
modified: true,
|
|
269
|
+
updatedAt: new Date(),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
return file;
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
...project,
|
|
277
|
+
files: updatedFiles,
|
|
278
|
+
updatedAt: new Date(),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Mark file as saved (not modified)
|
|
284
|
+
*/
|
|
285
|
+
export function markFileSaved(
|
|
286
|
+
project: ProjectState,
|
|
287
|
+
filePath: string
|
|
288
|
+
): ProjectState {
|
|
289
|
+
const updatedFiles = project.files.map((file) => {
|
|
290
|
+
if (file.path === filePath) {
|
|
291
|
+
return {
|
|
292
|
+
...file,
|
|
293
|
+
modified: false,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
return file;
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
...project,
|
|
301
|
+
files: updatedFiles,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Set active file
|
|
307
|
+
*/
|
|
308
|
+
export function setActiveFile(
|
|
309
|
+
project: ProjectState,
|
|
310
|
+
filePath: string | null
|
|
311
|
+
): ProjectState {
|
|
312
|
+
return {
|
|
313
|
+
...project,
|
|
314
|
+
activeFilePath: filePath,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Toggle file open state
|
|
320
|
+
*/
|
|
321
|
+
export function toggleFileOpen(
|
|
322
|
+
project: ProjectState,
|
|
323
|
+
filePath: string,
|
|
324
|
+
isOpen: boolean
|
|
325
|
+
): ProjectState {
|
|
326
|
+
const updatedFiles = project.files.map((file) => {
|
|
327
|
+
if (file.path === filePath) {
|
|
328
|
+
return {
|
|
329
|
+
...file,
|
|
330
|
+
isOpen,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
return file;
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
...project,
|
|
338
|
+
files: updatedFiles,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Build file tree structure from flat file list
|
|
344
|
+
*/
|
|
345
|
+
export function buildFileTree(files: ProjectFile[]): FileNode[] {
|
|
346
|
+
const root: FileNode[] = [];
|
|
347
|
+
|
|
348
|
+
files.forEach((file) => {
|
|
349
|
+
const pathParts = file.path.split("/");
|
|
350
|
+
let currentLevel = root;
|
|
351
|
+
|
|
352
|
+
pathParts.forEach((part, index) => {
|
|
353
|
+
const isFile = index === pathParts.length - 1;
|
|
354
|
+
const currentPath = pathParts.slice(0, index + 1).join("/");
|
|
355
|
+
|
|
356
|
+
let existingNode = currentLevel.find((node) => node.name === part);
|
|
357
|
+
|
|
358
|
+
if (!existingNode) {
|
|
359
|
+
const newNode: FileNode = {
|
|
360
|
+
id: uuidv4(),
|
|
361
|
+
name: part,
|
|
362
|
+
path: currentPath,
|
|
363
|
+
type: isFile ? "file" : "folder",
|
|
364
|
+
...(isFile ? {} : { children: [], expanded: false }),
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
currentLevel.push(newNode);
|
|
368
|
+
existingNode = newNode;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (!isFile && existingNode.children) {
|
|
372
|
+
currentLevel = existingNode.children;
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
return sortTree(root);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ============================================================================
|
|
381
|
+
// HELPER FUNCTIONS
|
|
382
|
+
// ============================================================================
|
|
383
|
+
|
|
384
|
+
function createDefaultFile(
|
|
385
|
+
path: string,
|
|
386
|
+
language: FileLanguage,
|
|
387
|
+
content: string
|
|
388
|
+
): ProjectFile {
|
|
389
|
+
const name = path.split("/").pop() || path;
|
|
390
|
+
const now = new Date();
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
id: uuidv4(),
|
|
394
|
+
path,
|
|
395
|
+
name,
|
|
396
|
+
content,
|
|
397
|
+
language,
|
|
398
|
+
modified: false,
|
|
399
|
+
isOpen: path === "src/lib.rs", // Open lib.rs by default
|
|
400
|
+
createdAt: now,
|
|
401
|
+
updatedAt: now,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function detectLanguage(fileName: string): FileLanguage {
|
|
406
|
+
if (fileName.endsWith(".rs")) return "rust";
|
|
407
|
+
if (fileName.endsWith(".toml")) return "toml";
|
|
408
|
+
if (fileName.endsWith(".md")) return "markdown";
|
|
409
|
+
if (fileName === ".gitignore") return "gitignore";
|
|
410
|
+
return "text";
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function getDefaultRustContent(): string {
|
|
414
|
+
return `// Welcome to Stylus IDE - Multi-File Project
|
|
415
|
+
|
|
416
|
+
#![cfg_attr(not(feature = "export-abi"), no_main)]
|
|
417
|
+
extern crate alloc;
|
|
418
|
+
|
|
419
|
+
use stylus_sdk::prelude::*;
|
|
420
|
+
use stylus_sdk::alloy_primitives::U256;
|
|
421
|
+
|
|
422
|
+
sol_storage! {
|
|
423
|
+
#[entrypoint]
|
|
424
|
+
pub struct Counter {
|
|
425
|
+
uint256 count;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
#[public]
|
|
430
|
+
impl Counter {
|
|
431
|
+
pub fn get(&self) -> U256 {
|
|
432
|
+
self.count.get()
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
pub fn increment(&mut self) {
|
|
436
|
+
let count = self.count.get();
|
|
437
|
+
self.count.set(count + U256::from(1));
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
`;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function getDefaultCargoToml(projectName: string): string {
|
|
444
|
+
return `[package]
|
|
445
|
+
name = "${projectName.toLowerCase().replace(/[^a-z0-9_-]/g, "_")}"
|
|
446
|
+
version = "0.1.0"
|
|
447
|
+
edition = "2021"
|
|
448
|
+
|
|
449
|
+
[dependencies]
|
|
450
|
+
alloy-primitives = "=0.7.6"
|
|
451
|
+
alloy-sol-types = "=0.7.6"
|
|
452
|
+
stylus-sdk = "0.6.0"
|
|
453
|
+
|
|
454
|
+
[dev-dependencies]
|
|
455
|
+
tokio = { version = "1.12.0", features = ["full"] }
|
|
456
|
+
ethers = "2.0"
|
|
457
|
+
eyre = "0.6.8"
|
|
458
|
+
|
|
459
|
+
[features]
|
|
460
|
+
export-abi = ["stylus-sdk/export-abi"]
|
|
461
|
+
|
|
462
|
+
[lib]
|
|
463
|
+
crate-type = ["lib", "cdylib"]
|
|
464
|
+
|
|
465
|
+
[profile.release]
|
|
466
|
+
codegen-units = 1
|
|
467
|
+
strip = true
|
|
468
|
+
lto = true
|
|
469
|
+
panic = "abort"
|
|
470
|
+
opt-level = "s"
|
|
471
|
+
`;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function insertFileIntoTree(tree: FileNode[], filePath: string): FileNode[] {
|
|
475
|
+
const pathParts = filePath.split("/");
|
|
476
|
+
const fileName = pathParts.pop()!;
|
|
477
|
+
|
|
478
|
+
if (pathParts.length === 0) {
|
|
479
|
+
// Root level file
|
|
480
|
+
return [
|
|
481
|
+
...tree,
|
|
482
|
+
{
|
|
483
|
+
id: uuidv4(),
|
|
484
|
+
name: fileName,
|
|
485
|
+
path: filePath,
|
|
486
|
+
type: "file",
|
|
487
|
+
},
|
|
488
|
+
];
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Nested file - find or create folders
|
|
492
|
+
return tree.map((node) => {
|
|
493
|
+
if (node.name === pathParts[0] && node.type === "folder") {
|
|
494
|
+
const remainingPath = pathParts.slice(1).join("/") + "/" + fileName;
|
|
495
|
+
return {
|
|
496
|
+
...node,
|
|
497
|
+
children: insertFileIntoTree(node.children || [], remainingPath),
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
return node;
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function insertFolderIntoTree(
|
|
505
|
+
tree: FileNode[],
|
|
506
|
+
folderPath: string
|
|
507
|
+
): FileNode[] {
|
|
508
|
+
const pathParts = folderPath.split("/");
|
|
509
|
+
const folderName = pathParts[0];
|
|
510
|
+
|
|
511
|
+
const existingFolder = tree.find((node) => node.name === folderName);
|
|
512
|
+
|
|
513
|
+
if (pathParts.length === 1) {
|
|
514
|
+
if (existingFolder) {
|
|
515
|
+
return tree;
|
|
516
|
+
}
|
|
517
|
+
return [
|
|
518
|
+
...tree,
|
|
519
|
+
{
|
|
520
|
+
id: uuidv4(),
|
|
521
|
+
name: folderName,
|
|
522
|
+
path: folderPath,
|
|
523
|
+
type: "folder",
|
|
524
|
+
children: [],
|
|
525
|
+
expanded: false,
|
|
526
|
+
},
|
|
527
|
+
];
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Nested folder
|
|
531
|
+
const remainingPath = pathParts.slice(1).join("/");
|
|
532
|
+
|
|
533
|
+
if (existingFolder && existingFolder.type === "folder") {
|
|
534
|
+
return tree.map((node) => {
|
|
535
|
+
if (node.name === folderName) {
|
|
536
|
+
return {
|
|
537
|
+
...node,
|
|
538
|
+
children: insertFolderIntoTree(node.children || [], remainingPath),
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
return node;
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Create parent folder if doesn't exist
|
|
546
|
+
return [
|
|
547
|
+
...tree,
|
|
548
|
+
{
|
|
549
|
+
id: uuidv4(),
|
|
550
|
+
name: folderName,
|
|
551
|
+
path: pathParts[0],
|
|
552
|
+
type: "folder",
|
|
553
|
+
children: insertFolderIntoTree([], remainingPath),
|
|
554
|
+
expanded: false,
|
|
555
|
+
},
|
|
556
|
+
];
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function removeNodeFromTree(tree: FileNode[], path: string): FileNode[] {
|
|
560
|
+
return tree
|
|
561
|
+
.filter((node) => node.path !== path)
|
|
562
|
+
.map((node) => {
|
|
563
|
+
if (node.type === "folder" && node.children) {
|
|
564
|
+
return {
|
|
565
|
+
...node,
|
|
566
|
+
children: removeNodeFromTree(node.children, path),
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
return node;
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function renameNodeInTree(
|
|
574
|
+
tree: FileNode[],
|
|
575
|
+
oldPath: string,
|
|
576
|
+
newPath: string
|
|
577
|
+
): FileNode[] {
|
|
578
|
+
return tree.map((node) => {
|
|
579
|
+
if (node.path === oldPath) {
|
|
580
|
+
const newName = newPath.split("/").pop()!;
|
|
581
|
+
return {
|
|
582
|
+
...node,
|
|
583
|
+
name: newName,
|
|
584
|
+
path: newPath,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (node.type === "folder" && node.children) {
|
|
589
|
+
return {
|
|
590
|
+
...node,
|
|
591
|
+
children: renameNodeInTree(node.children, oldPath, newPath),
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return node;
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function findNodeByPath(tree: FileNode[], path: string): FileNode | undefined {
|
|
600
|
+
for (const node of tree) {
|
|
601
|
+
if (node.path === path) {
|
|
602
|
+
return node;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
if (node.type === "folder" && node.children) {
|
|
606
|
+
const found = findNodeByPath(node.children, path);
|
|
607
|
+
if (found) return found;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
return undefined;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function sortTree(tree: FileNode[]): FileNode[] {
|
|
615
|
+
return tree
|
|
616
|
+
.sort((a, b) => {
|
|
617
|
+
// Folders first
|
|
618
|
+
if (a.type === "folder" && b.type === "file") return -1;
|
|
619
|
+
if (a.type === "file" && b.type === "folder") return 1;
|
|
620
|
+
// Then alphabetically
|
|
621
|
+
return a.name.localeCompare(b.name);
|
|
622
|
+
})
|
|
623
|
+
.map((node) => {
|
|
624
|
+
if (node.type === "folder" && node.children) {
|
|
625
|
+
return {
|
|
626
|
+
...node,
|
|
627
|
+
children: sortTree(node.children),
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
return node;
|
|
631
|
+
});
|
|
632
|
+
}
|