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,206 @@
|
|
|
1
|
+
export const MNN_ABI = [
|
|
2
|
+
{
|
|
3
|
+
inputs: [
|
|
4
|
+
{ internalType: "uint256", name: "warmth", type: "uint256" },
|
|
5
|
+
{ internalType: "uint256", name: "intensity", type: "uint256" },
|
|
6
|
+
{ internalType: "uint256", name: "depth", type: "uint256" },
|
|
7
|
+
],
|
|
8
|
+
name: "viewAesthetic",
|
|
9
|
+
outputs: [
|
|
10
|
+
{ internalType: "uint8", name: "", type: "uint8" },
|
|
11
|
+
{ internalType: "uint8", name: "", type: "uint8" },
|
|
12
|
+
{ internalType: "uint8", name: "", type: "uint8" },
|
|
13
|
+
],
|
|
14
|
+
stateMutability: "view",
|
|
15
|
+
type: "function",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
inputs: [],
|
|
19
|
+
name: "getNetworkInfo",
|
|
20
|
+
outputs: [
|
|
21
|
+
{ internalType: "uint256", name: "", type: "uint256" },
|
|
22
|
+
{ internalType: "uint256", name: "", type: "uint256" },
|
|
23
|
+
{ internalType: "uint256", name: "", type: "uint256" },
|
|
24
|
+
],
|
|
25
|
+
stateMutability: "view",
|
|
26
|
+
type: "function",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
inputs: [],
|
|
30
|
+
name: "getParameterCount",
|
|
31
|
+
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
|
|
32
|
+
stateMutability: "view",
|
|
33
|
+
type: "function",
|
|
34
|
+
},
|
|
35
|
+
] as const;
|
|
36
|
+
|
|
37
|
+
export const RAY_TRACING_ABI = [
|
|
38
|
+
{
|
|
39
|
+
inputs: [
|
|
40
|
+
{ internalType: "uint8", name: "sphere_r", type: "uint8" },
|
|
41
|
+
{ internalType: "uint8", name: "sphere_g", type: "uint8" },
|
|
42
|
+
{ internalType: "uint8", name: "sphere_b", type: "uint8" },
|
|
43
|
+
{ internalType: "uint8", name: "bg_color1_r", type: "uint8" },
|
|
44
|
+
{ internalType: "uint8", name: "bg_color1_g", type: "uint8" },
|
|
45
|
+
{ internalType: "uint8", name: "bg_color1_b", type: "uint8" },
|
|
46
|
+
{ internalType: "uint8", name: "bg_color2_r", type: "uint8" },
|
|
47
|
+
{ internalType: "uint8", name: "bg_color2_g", type: "uint8" },
|
|
48
|
+
{ internalType: "uint8", name: "bg_color2_b", type: "uint8" },
|
|
49
|
+
{ internalType: "int32", name: "cam_x", type: "int32" },
|
|
50
|
+
{ internalType: "int32", name: "cam_y", type: "int32" },
|
|
51
|
+
{ internalType: "int32", name: "cam_z", type: "int32" },
|
|
52
|
+
],
|
|
53
|
+
name: "mint",
|
|
54
|
+
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
|
|
55
|
+
stateMutability: "nonpayable",
|
|
56
|
+
type: "function",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
inputs: [{ internalType: "uint256", name: "token_id", type: "uint256" }],
|
|
60
|
+
name: "renderToken",
|
|
61
|
+
outputs: [{ internalType: "bytes", name: "", type: "bytes" }],
|
|
62
|
+
stateMutability: "view",
|
|
63
|
+
type: "function",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
inputs: [{ internalType: "uint256", name: "token_id", type: "uint256" }],
|
|
67
|
+
name: "ownerOf",
|
|
68
|
+
outputs: [{ internalType: "address", name: "", type: "address" }],
|
|
69
|
+
stateMutability: "view",
|
|
70
|
+
type: "function",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
inputs: [],
|
|
74
|
+
name: "totalSupply",
|
|
75
|
+
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
|
|
76
|
+
stateMutability: "view",
|
|
77
|
+
type: "function",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
inputs: [],
|
|
81
|
+
name: "getResolution",
|
|
82
|
+
outputs: [
|
|
83
|
+
{ internalType: "uint256", name: "", type: "uint256" },
|
|
84
|
+
{ internalType: "uint256", name: "", type: "uint256" },
|
|
85
|
+
],
|
|
86
|
+
stateMutability: "view",
|
|
87
|
+
type: "function",
|
|
88
|
+
},
|
|
89
|
+
] as const;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Convert raw pixel bytes to ImageData
|
|
93
|
+
*/
|
|
94
|
+
export function bytesToImageData(
|
|
95
|
+
bytes: Uint8Array,
|
|
96
|
+
width: number = 32,
|
|
97
|
+
height: number = 32
|
|
98
|
+
): ImageData {
|
|
99
|
+
if (bytes.length !== width * height * 3) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Expected ${width * height * 3} bytes, got ${bytes.length}`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const imageData = new ImageData(width, height);
|
|
106
|
+
|
|
107
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
108
|
+
const pixelIndex = i / 3;
|
|
109
|
+
const dataIndex = pixelIndex * 4;
|
|
110
|
+
|
|
111
|
+
imageData.data[dataIndex] = bytes[i]; // R
|
|
112
|
+
imageData.data[dataIndex + 1] = bytes[i + 1]; // G
|
|
113
|
+
imageData.data[dataIndex + 2] = bytes[i + 2]; // B
|
|
114
|
+
imageData.data[dataIndex + 3] = 255; // A (fully opaque)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return imageData;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Convert ImageData to PNG blob
|
|
122
|
+
*/
|
|
123
|
+
export function imageDataToBlob(imageData: ImageData): Promise<Blob | null> {
|
|
124
|
+
return new Promise((resolve) => {
|
|
125
|
+
const canvas = document.createElement("canvas");
|
|
126
|
+
canvas.width = imageData.width;
|
|
127
|
+
canvas.height = imageData.height;
|
|
128
|
+
|
|
129
|
+
const ctx = canvas.getContext("2d");
|
|
130
|
+
if (!ctx) {
|
|
131
|
+
resolve(null);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
ctx.putImageData(imageData, 0, 0);
|
|
136
|
+
canvas.toBlob(resolve);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Scale up pixel art without blurring
|
|
142
|
+
*/
|
|
143
|
+
export function scalePixelArt(
|
|
144
|
+
sourceCanvas: HTMLCanvasElement,
|
|
145
|
+
scale: number = 8
|
|
146
|
+
): HTMLCanvasElement {
|
|
147
|
+
const scaledCanvas = document.createElement("canvas");
|
|
148
|
+
scaledCanvas.width = sourceCanvas.width * scale;
|
|
149
|
+
scaledCanvas.height = sourceCanvas.height * scale;
|
|
150
|
+
|
|
151
|
+
const ctx = scaledCanvas.getContext("2d");
|
|
152
|
+
if (!ctx) return sourceCanvas;
|
|
153
|
+
|
|
154
|
+
ctx.imageSmoothingEnabled = false;
|
|
155
|
+
ctx.drawImage(
|
|
156
|
+
sourceCanvas,
|
|
157
|
+
0,
|
|
158
|
+
0,
|
|
159
|
+
sourceCanvas.width,
|
|
160
|
+
sourceCanvas.height,
|
|
161
|
+
0,
|
|
162
|
+
0,
|
|
163
|
+
scaledCanvas.width,
|
|
164
|
+
scaledCanvas.height
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
return scaledCanvas;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Parse hex string to Uint8Array
|
|
172
|
+
*/
|
|
173
|
+
export function hexToBytes(hex: string): Uint8Array {
|
|
174
|
+
// Remove '0x' prefix if present
|
|
175
|
+
const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
176
|
+
|
|
177
|
+
const bytes = new Uint8Array(cleanHex.length / 2);
|
|
178
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
179
|
+
bytes[i] = parseInt(cleanHex.substr(i * 2, 2), 16);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return bytes;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Calculate gas cost estimate
|
|
187
|
+
*/
|
|
188
|
+
export function estimateRenderCost(gasUsed: bigint): {
|
|
189
|
+
gasUsed: string;
|
|
190
|
+
gasPriceGwei: number;
|
|
191
|
+
costETH: string;
|
|
192
|
+
costUSD: string;
|
|
193
|
+
} {
|
|
194
|
+
const gasPriceGwei = 0.02; // Typical Arbitrum gas price
|
|
195
|
+
const ethPrice = 2500; // Approximate ETH price
|
|
196
|
+
|
|
197
|
+
const costETH = (Number(gasUsed) * gasPriceGwei) / 1e9;
|
|
198
|
+
const costUSD = costETH * ethPrice;
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
gasUsed: gasUsed.toString(),
|
|
202
|
+
gasPriceGwei,
|
|
203
|
+
costETH: costETH.toFixed(6),
|
|
204
|
+
costUSD: costUSD.toFixed(4),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LocalStorage utilities for project persistence
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ProjectState } from "@/types/project";
|
|
6
|
+
|
|
7
|
+
const STORAGE_KEYS = {
|
|
8
|
+
CURRENT_PROJECT: "stylus-ide-current-project",
|
|
9
|
+
PROJECT_LIST: "stylus-ide-projects",
|
|
10
|
+
LAST_SAVE: "stylus-ide-last-save",
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Save current project to localStorage
|
|
15
|
+
*/
|
|
16
|
+
export function saveProject(project: ProjectState): void {
|
|
17
|
+
try {
|
|
18
|
+
const projectData = JSON.stringify(project);
|
|
19
|
+
localStorage.setItem(STORAGE_KEYS.CURRENT_PROJECT, projectData);
|
|
20
|
+
localStorage.setItem(STORAGE_KEYS.LAST_SAVE, new Date().toISOString());
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error("Failed to save project:", error);
|
|
23
|
+
|
|
24
|
+
// Handle quota exceeded
|
|
25
|
+
if (error instanceof Error && error.name === "QuotaExceededError") {
|
|
26
|
+
alert(
|
|
27
|
+
"Storage quota exceeded. Please clear old projects or export your work."
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Load current project from localStorage
|
|
35
|
+
*/
|
|
36
|
+
export function loadProject(): ProjectState | null {
|
|
37
|
+
try {
|
|
38
|
+
const projectData = localStorage.getItem(STORAGE_KEYS.CURRENT_PROJECT);
|
|
39
|
+
|
|
40
|
+
if (!projectData) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const project = JSON.parse(projectData) as ProjectState;
|
|
45
|
+
|
|
46
|
+
// Validate the loaded project has required fields
|
|
47
|
+
if (!project.id || !project.files || !project.structure) {
|
|
48
|
+
console.warn("Invalid project data in localStorage");
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Convert date strings back to Date objects
|
|
53
|
+
project.createdAt = new Date(project.createdAt);
|
|
54
|
+
project.updatedAt = new Date(project.updatedAt);
|
|
55
|
+
project.files = project.files.map((file) => ({
|
|
56
|
+
...file,
|
|
57
|
+
createdAt: new Date(file.createdAt),
|
|
58
|
+
updatedAt: new Date(file.updatedAt),
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
return project;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error("Failed to load project:", error);
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Clear current project from localStorage
|
|
70
|
+
*/
|
|
71
|
+
export function clearProject(): void {
|
|
72
|
+
try {
|
|
73
|
+
localStorage.removeItem(STORAGE_KEYS.CURRENT_PROJECT);
|
|
74
|
+
localStorage.removeItem(STORAGE_KEYS.LAST_SAVE);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error("Failed to clear project:", error);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get last save timestamp
|
|
82
|
+
*/
|
|
83
|
+
export function getLastSaveTime(): Date | null {
|
|
84
|
+
try {
|
|
85
|
+
if (typeof window === "undefined") {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const timestamp = localStorage.getItem(STORAGE_KEYS.LAST_SAVE);
|
|
90
|
+
return timestamp ? new Date(timestamp) : null;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error("Failed to get last save time:", error);
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Export project as JSON file
|
|
99
|
+
*/
|
|
100
|
+
export function exportProject(project: ProjectState): void {
|
|
101
|
+
try {
|
|
102
|
+
const projectData = JSON.stringify(project, null, 2);
|
|
103
|
+
const blob = new Blob([projectData], { type: "application/json" });
|
|
104
|
+
const url = URL.createObjectURL(blob);
|
|
105
|
+
|
|
106
|
+
const link = document.createElement("a");
|
|
107
|
+
link.href = url;
|
|
108
|
+
link.download = `${project.name}-${
|
|
109
|
+
new Date().toISOString().split("T")[0]
|
|
110
|
+
}.json`;
|
|
111
|
+
document.body.appendChild(link);
|
|
112
|
+
link.click();
|
|
113
|
+
document.body.removeChild(link);
|
|
114
|
+
|
|
115
|
+
URL.revokeObjectURL(url);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error("Failed to export project:", error);
|
|
118
|
+
alert("Failed to export project. Please try again.");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Import project from JSON file
|
|
124
|
+
*/
|
|
125
|
+
export function importProject(file: File): Promise<ProjectState> {
|
|
126
|
+
return new Promise((resolve, reject) => {
|
|
127
|
+
const reader = new FileReader();
|
|
128
|
+
|
|
129
|
+
reader.onload = (e) => {
|
|
130
|
+
try {
|
|
131
|
+
const content = e.target?.result as string;
|
|
132
|
+
const project = JSON.parse(content) as ProjectState;
|
|
133
|
+
|
|
134
|
+
// Validate imported project
|
|
135
|
+
if (!project.id || !project.files || !project.structure) {
|
|
136
|
+
throw new Error("Invalid project file");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Convert date strings to Date objects
|
|
140
|
+
project.createdAt = new Date(project.createdAt);
|
|
141
|
+
project.updatedAt = new Date(project.updatedAt);
|
|
142
|
+
project.files = project.files.map((file) => ({
|
|
143
|
+
...file,
|
|
144
|
+
createdAt: new Date(file.createdAt),
|
|
145
|
+
updatedAt: new Date(file.updatedAt),
|
|
146
|
+
}));
|
|
147
|
+
|
|
148
|
+
resolve(project);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
reject(new Error("Failed to parse project file"));
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
reader.onerror = () => {
|
|
155
|
+
reject(new Error("Failed to read file"));
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
reader.readAsText(file);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get storage usage info
|
|
164
|
+
*/
|
|
165
|
+
export function getStorageInfo(): {
|
|
166
|
+
used: number;
|
|
167
|
+
total: number;
|
|
168
|
+
percentage: number;
|
|
169
|
+
} {
|
|
170
|
+
try {
|
|
171
|
+
let used = 0;
|
|
172
|
+
|
|
173
|
+
// Calculate total size of localStorage
|
|
174
|
+
for (const key in localStorage) {
|
|
175
|
+
if (localStorage.hasOwnProperty(key)) {
|
|
176
|
+
used += localStorage[key].length + key.length;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Most browsers have ~5-10MB limit for localStorage
|
|
181
|
+
const total = 5 * 1024 * 1024; // 5MB estimate
|
|
182
|
+
const percentage = (used / total) * 100;
|
|
183
|
+
|
|
184
|
+
return { used, total, percentage };
|
|
185
|
+
} catch (error) {
|
|
186
|
+
console.error("Failed to get storage info:", error);
|
|
187
|
+
return { used: 0, total: 0, percentage: 0 };
|
|
188
|
+
}
|
|
189
|
+
}
|