new file: app/ide/page.tsx

This commit is contained in:
Anderson 2026-01-28 05:02:13 +00:00 committed by GitHub
parent 4bc31a32e2
commit ea5ba62c54
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 567 additions and 417 deletions

View file

@ -2,8 +2,9 @@
This is a NextJS starter in Firebase Studio. This is a NextJS starter in Firebase Studio.
To get started, take a look at src/app/page.tsx. To get started, take a look at src/app/page.tsx.
**Platform Switching** - Work with Roblox, UEFN, Spatial, or Core
- **Platform Switching** - Work with Roblox, UEFN, Spatial, or Core - **Platform Switching** - Work with Roblox, UEFN, Spatial, or Core
- **Platform-Specific Templates**: - **Platform-Specific Templates**:
- 🎮 **Roblox**: 25 Lua templates - 🎮 **Roblox**: 25 Lua templates
@ -14,26 +15,34 @@ To get started, take a look at src/app/page.tsx.
- **Side-by-Side Comparison** - Compare original and translated code - **Side-by-Side Comparison** - Compare original and translated code
- **Smart Editor** - Language highlighting adapts to selected platform - **Smart Editor** - Language highlighting adapts to selected platform
### 🎨 **Modern Code Editor**
## 🎨 **Modern Code Editor**
- **Monaco Editor** - The same editor that powers VS Code - **Monaco Editor** - The same editor that powers VS Code
- **Multi-language Support** - Lua, Verse, TypeScript - **Multi-language Support** - Lua, Verse, TypeScript
- **Real-time code validation** and linting - **Real-time code validation** and linting
- **Multi-file editing** with tab management - **Multi-file editing** with tab management
- **File tree navigation** with drag-and-drop organization - **File tree navigation** with drag-and-drop organization
### 🤖 **AI-Powered Assistant**
## 🤖 **AI-Powered Assistant**
- Built-in AI chat for code help and debugging - Built-in AI chat for code help and debugging
- Context-aware suggestions - Context-aware suggestions
- Code explanation and documentation - Code explanation and documentation
- Roblox API knowledge - Roblox API knowledge
### 📁 **Project Management**
## 📁 **Project Management**
- **File Tree** - Organize your scripts into folders - **File Tree** - Organize your scripts into folders
- **Drag-and-drop** - Rearrange files easily - **Drag-and-drop** - Rearrange files easily
- **Quick file search** (Cmd/Ctrl+P) - Find files instantly - **Quick file search** (Cmd/Ctrl+P) - Find files instantly
- **Search in files** (Cmd/Ctrl+Shift+F) - Global text search - **Search in files** (Cmd/Ctrl+Shift+F) - Global text search
### 🎯 **Productivity Features**
## 🎯 **Productivity Features**
- **33+ Code Templates** - Ready-made scripts for multiple platforms - **33+ Code Templates** - Ready-made scripts for multiple platforms
- **Roblox** (25 templates): - **Roblox** (25 templates):
- Beginner templates (Hello World, Touch Detectors, etc.) - Beginner templates (Hello World, Touch Detectors, etc.)
@ -49,7 +58,9 @@ To get started, take a look at src/app/page.tsx.
- **Keyboard Shortcuts** - Professional IDE shortcuts - **Keyboard Shortcuts** - Professional IDE shortcuts
- **Code Preview** - Test your scripts instantly - **Code Preview** - Test your scripts instantly
### 💻 **Interactive Terminal & CLI**
## 💻 **Interactive Terminal & CLI**
- **Built-in Terminal** - Full-featured command line interface - **Built-in Terminal** - Full-featured command line interface
- **10+ CLI Commands** for Roblox development: - **10+ CLI Commands** for Roblox development:
- `help` - Display available commands - `help` - Display available commands
@ -67,7 +78,9 @@ To get started, take a look at src/app/page.tsx.
- **Smart Suggestions** - Context-aware command hints - **Smart Suggestions** - Context-aware command hints
- **Toggle with Cmd/Ctrl + `** - Quick terminal access - **Toggle with Cmd/Ctrl + `** - Quick terminal access
### 🎨 **Customization**
## 🎨 **Customization**
- **5 Beautiful Themes**: - **5 Beautiful Themes**:
- **Dark** - Classic dark theme for comfortable coding - **Dark** - Classic dark theme for comfortable coding
- **Light** - Clean light theme for bright environments - **Light** - Clean light theme for bright environments
@ -76,21 +89,27 @@ To get started, take a look at src/app/page.tsx.
- **Ocean** - Deep blue theme - **Ocean** - Deep blue theme
- **Persistent preferences** - Your settings are saved - **Persistent preferences** - Your settings are saved
### 📱 **Mobile Responsive**
## 📱 **Mobile Responsive**
- Optimized layouts for phones and tablets - Optimized layouts for phones and tablets
- Touch-friendly controls - Touch-friendly controls
- Hamburger menu for mobile - Hamburger menu for mobile
- Collapsible panels - Collapsible panels
### 🚀 **Developer Experience**
## 🚀 **Developer Experience**
- **Code splitting** for fast loading - **Code splitting** for fast loading
- **Error boundaries** with graceful error handling - **Error boundaries** with graceful error handling
- **Loading states** with spinners - **Loading states** with spinners
- **Toast notifications** for user feedback - **Toast notifications** for user feedback
- **Testing infrastructure** with Vitest - **Testing infrastructure** with Vitest
## 🎮 Perfect For ## 🎮 Perfect For
- **Multi-Platform Developers** - Build for Roblox, UEFN, Spatial, and Core from one IDE - **Multi-Platform Developers** - Build for Roblox, UEFN, Spatial, and Core from one IDE
- **Game Studios** - Translate games between platforms with AI assistance - **Game Studios** - Translate games between platforms with AI assistance
- **Roblox → UEFN Migration** - Converting existing Roblox games to Fortnite - **Roblox → UEFN Migration** - Converting existing Roblox games to Fortnite
@ -98,10 +117,11 @@ To get started, take a look at src/app/page.tsx.
- **Rapid Prototyping** - Build once, deploy to multiple platforms - **Rapid Prototyping** - Build once, deploy to multiple platforms
- **Web-Based Development** - Code anywhere, no installation needed - **Web-Based Development** - Code anywhere, no installation needed
## ⌨️ Keyboard Shortcuts ## ⌨️ Keyboard Shortcuts
| Shortcut | Action | | Shortcut | Action |
|----------|--------| | :----------------- | :---------------------------- |
| `Cmd/Ctrl + S` | Save file (auto-save enabled) | | `Cmd/Ctrl + S` | Save file (auto-save enabled) |
| `Cmd/Ctrl + P` | Quick file search | | `Cmd/Ctrl + P` | Quick file search |
| `Cmd/Ctrl + K` | Command palette | | `Cmd/Ctrl + K` | Command palette |
@ -110,14 +130,19 @@ To get started, take a look at src/app/page.tsx.
| `Cmd/Ctrl + Shift + F` | Search in all files | | `Cmd/Ctrl + Shift + F` | Search in all files |
| ``Cmd/Ctrl + ` `` | Toggle terminal | | ``Cmd/Ctrl + ` `` | Toggle terminal |
## 🚀 Getting Started ## 🚀 Getting Started
### Prerequisites ### Prerequisites
- Node.js 18+ - Node.js 18+
- npm or yarn - npm or yarn
### Installation ### Installation
#
```bash ```bash
# Clone the repository # Clone the repository
git clone https://github.com/AeThex-LABS/aethex-studio.git git clone https://github.com/AeThex-LABS/aethex-studio.git
@ -134,6 +159,7 @@ npm run dev
Visit `http://localhost:3000` to see the application. Visit `http://localhost:3000` to see the application.
### 🔑 Enabling Cross-Platform Translation ### 🔑 Enabling Cross-Platform Translation
To unlock the **AI-powered code translation** feature, you need a Claude API key: To unlock the **AI-powered code translation** feature, you need a Claude API key:
@ -163,8 +189,10 @@ To unlock the **AI-powered code translation** feature, you need a Claude API key
💡 **Note**: Without an API key, the app works perfectly but shows mock translations instead of real AI conversions. 💡 **Note**: Without an API key, the app works perfectly but shows mock translations instead of real AI conversions.
### Building for Production ### Building for Production
#
```bash ```bash
# Build the application # Build the application
npm run build npm run build
@ -173,8 +201,10 @@ npm run build
npm start npm start
``` ```
## 📖 Usage Guide ## 📖 Usage Guide
### Creating Your First Script ### Creating Your First Script
1. Click **"New File"** in the file tree 1. Click **"New File"** in the file tree
@ -183,6 +213,7 @@ npm start
4. Click **"Preview"** to test 4. Click **"Preview"** to test
5. **Copy** or **Export** your script 5. **Copy** or **Export** your script
### Using Templates ### Using Templates
1. Click the **Templates** button in the toolbar 1. Click the **Templates** button in the toolbar
@ -190,6 +221,7 @@ npm start
3. Click a template to load it into your editor 3. Click a template to load it into your editor
4. Customize the code for your needs 4. Customize the code for your needs
### AI Assistant ### AI Assistant
1. Open the **AI Chat** panel (right side on desktop) 1. Open the **AI Chat** panel (right side on desktop)
@ -200,6 +232,7 @@ npm start
- Best practices - Best practices
3. Get instant, context-aware answers 3. Get instant, context-aware answers
### Organizing Files ### Organizing Files
- **Create folders** - Right-click in file tree - **Create folders** - Right-click in file tree
@ -207,12 +240,14 @@ npm start
- **Rename** - Click the menu (⋯) next to a file - **Rename** - Click the menu (⋯) next to a file
- **Delete** - Use the menu to remove files - **Delete** - Use the menu to remove files
### Searching ### Searching
- **Quick search** - `Cmd/Ctrl+P` to find files by name - **Quick search** - `Cmd/Ctrl+P` to find files by name
- **Global search** - `Cmd/Ctrl+Shift+F` to search text across all files - **Global search** - `Cmd/Ctrl+Shift+F` to search text across all files
- **In-editor search** - `Cmd/Ctrl+F` to find text in current file - **In-editor search** - `Cmd/Ctrl+F` to find text in current file
## 🛠️ Tech Stack ## 🛠️ Tech Stack
- **Next.js 14** - React framework - **Next.js 14** - React framework
@ -226,8 +261,10 @@ npm start
- **PostHog** - Analytics (optional) - **PostHog** - Analytics (optional)
- **Sentry** - Error tracking (optional) - **Sentry** - Error tracking (optional)
## 🧪 Running Tests ## 🧪 Running Tests
#
```bash ```bash
# Run all tests # Run all tests
npm test npm test
@ -242,8 +279,10 @@ npm run test:ui
npm run test:coverage npm run test:coverage
``` ```
## 📂 Project Structure ## 📂 Project Structure
#
``` ```
aethex-studio/ aethex-studio/
├── src/ ├── src/
@ -264,6 +303,7 @@ aethex-studio/
└── tests/ # Test files └── tests/ # Test files
``` ```
## 🤝 Contributing ## 🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request. Contributions are welcome! Please feel free to submit a Pull Request.
@ -274,37 +314,52 @@ Contributions are welcome! Please feel free to submit a Pull Request.
4. Push to the branch (`git push origin feature/AmazingFeature`) 4. Push to the branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request 5. Open a Pull Request
## 📝 Code Templates ## 📝 Code Templates
AeThex Studio includes 25+ production-ready templates: AeThex Studio includes 25+ production-ready templates:
**Beginner:** **Beginner:**
- Hello World, Player Join Handler, Part Touch Detector, etc. - Hello World, Player Join Handler, Part Touch Detector, etc.
**Gameplay:** **Gameplay:**
- DataStore System, Teleport Part, Team System, Combat System, etc. - DataStore System, Teleport Part, Team System, Combat System, etc.
**UI:** **UI:**
- GUI Buttons, Proximity Prompts, Countdown Timers, etc. - GUI Buttons, Proximity Prompts, Countdown Timers, etc.
**Tools:** **Tools:**
- Give Tool, Sound Manager, Admin Commands, Chat Commands, etc. - Give Tool, Sound Manager, Admin Commands, Chat Commands, etc.
**Advanced:** **Advanced:**
- Round System, Inventory System, Pathfinding NPC, Shop System, etc. - Round System, Inventory System, Pathfinding NPC, Shop System, etc.
## 🐛 Bug Reports ## 🐛 Bug Reports
Found a bug? Please open an issue on GitHub with: Found a bug? Please open an issue on GitHub with:
- Description of the bug - Description of the bug
- Steps to reproduce - Steps to reproduce
- Expected vs actual behavior - Expected vs actual behavior
- Screenshots (if applicable) - Screenshots (if applicable)
## 📜 License ## 📜 License
This project is licensed under the MIT License - see the LICENSE file for details. This project is licensed under the MIT License - see the LICENSE file for details.
## 🙏 Acknowledgments ## 🙏 Acknowledgments
- **Monaco Editor** - For the powerful code editor - **Monaco Editor** - For the powerful code editor
@ -312,12 +367,16 @@ This project is licensed under the MIT License - see the LICENSE file for detail
- **Radix UI** - For accessible component primitives - **Radix UI** - For accessible component primitives
- **Vercel** - For Next.js framework - **Vercel** - For Next.js framework
## 📧 Contact ## 📧 Contact
- **Website**: [aethex.com](https://aethex.com) - **Website**: [aethex.com](https://aethex.com)
- **GitHub**: [@AeThex-LABS](https://github.com/AeThex-LABS) - **GitHub**: [@AeThex-LABS](https://github.com/AeThex-LABS)
- **Issues**: [GitHub Issues](https://github.com/AeThex-LABS/aethex-studio/issues) - **Issues**: [GitHub Issues](https://github.com/AeThex-LABS/aethex-studio/issues)
---
--- ---
**Built with ❤️ by the AeThex team** **Built with ❤️ by the AeThex team**

4
app/dashboard/page.tsx Normal file
View file

@ -0,0 +1,4 @@
import { DashboardPage } from "../../src/components/aethex/dashboard-page";
export default function Page() {
return <DashboardPage />;
}

4
app/ide/page.tsx Normal file
View file

@ -0,0 +1,4 @@
import App from "../../src/App";
export default function Page() {
return <App />;
}

View file

@ -1,22 +1,10 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Inter, JetBrains_Mono } from "next/font/google"; import Toaster from "../src/components/ui/toaster";
import "./globals.css"; import "./globals.css";
import "./studio-theme.css";
import StudioLayout from "../components/StudioLayout";
const inter = Inter({
subsets: ["latin"],
variable: "--font-inter",
});
const jetbrainsMono = JetBrains_Mono({
subsets: ["latin"],
variable: "--font-jetbrains-mono",
});
export const metadata: Metadata = { export const metadata: Metadata = {
title: "AeThex Studio - Cross-Platform Game Development IDE", title: "AeThex Studio",
description: "Professional game development IDE for Roblox, Web, Mobile, and Desktop", description: "The Next-Generation Cross-Platform IDE",
}; };
export default function RootLayout({ export default function RootLayout({
@ -26,8 +14,21 @@ export default function RootLayout({
}>) { }>) {
return ( return (
<html lang="en" className="dark"> <html lang="en" className="dark">
<body className={`${inter.variable} ${jetbrainsMono.variable} font-sans antialiased bg-background text-white`}> <head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin="anonymous"
/>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&family=Source+Code+Pro:wght@400&family=Space+Grotesk:wght@400;700&display=swap"
rel="stylesheet"
/>
</head>
<body className="font-body antialiased">
{children} {children}
<Toaster />
</body> </body>
</html> </html>
); );

View file

@ -1,5 +1,5 @@
import { LoginPage } from "@/components/aethex/login-page"; import App from "../src/App";
export default function Page() { export default function Page() {
return <LoginPage />; return <App />;
} }

View file

@ -6,7 +6,7 @@ import { useEditorStore, FileNode } from '@/store/editor-store';
import { cn, getFileIcon, getPlatformIcon } from '@/lib/utils'; import { cn, getFileIcon, getPlatformIcon } from '@/lib/utils';
export function FileTree() { export function FileTree() {
const { files, openFile, moveFile } = useEditorStore(); const { files, openFile } = useEditorStore();
const [expandedFolders, setExpandedFolders] = React.useState<Set<string>>( const [expandedFolders, setExpandedFolders] = React.useState<Set<string>>(
new Set(['roblox', 'web', 'mobile', 'desktop', 'shared']) new Set(['roblox', 'web', 'mobile', 'desktop', 'shared'])
); );
@ -29,10 +29,7 @@ export function FileTree() {
return ( return (
<div key={node.id} draggable onDragStart={e => e.dataTransfer.setData('fileId', node.id)} onDrop={e => { <div key={node.id} draggable onDragStart={e => e.dataTransfer.setData('fileId', node.id)} onDrop={e => {
e.preventDefault(); e.preventDefault();
const fileId = e.dataTransfer.getData('fileId'); // moveFile functionality is not implemented
if (fileId && fileId !== node.id && isFolder) {
moveFile(fileId, node.id);
}
}} onDragOver={e => isFolder && e.preventDefault()}> }} onDragOver={e => isFolder && e.preventDefault()}>
<div <div
className={cn( className={cn(

View file

@ -2,19 +2,19 @@
import { useEditorStore } from "../store/editor-zustand"; import { useEditorStore } from "../store/editor-zustand";
function StudioEditor() { function StudioEditor() {
const openTabs = useEditorStore((s) => s.openTabs); const openTabs = useEditorStore((s: any) => s.openTabs);
const activeTabId = useEditorStore((s) => s.activeTabId); const activeTabId = useEditorStore((s: any) => s.activeTabId);
const setActiveTab = useEditorStore((s) => s.setActiveTab); const setActiveTab = useEditorStore((s: any) => s.setActiveTab);
const closeTab = useEditorStore((s) => s.closeTab); const closeTab = useEditorStore((s: any) => s.closeTab);
// Find active file // Find active file
const activeFile = openTabs.find(f => f.id === activeTabId); const activeFile = openTabs.find((f: any) => f.id === activeTabId);
return ( return (
<div className="editor-area"> <div className="editor-area">
<div className="editor-tabs"> <div className="editor-tabs">
{openTabs.length === 0 && ( {openTabs.length === 0 && (
<div className="editor-tab empty">No files open</div> <div className="editor-tab empty">No files open</div>
)} )}
{openTabs.map((file) => ( {openTabs.map((file: any) => (
<div <div
key={file.id} key={file.id}
className={"editor-tab" + (activeTabId === file.id ? " active" : "")} className={"editor-tab" + (activeTabId === file.id ? " active" : "")}
@ -33,7 +33,7 @@ function StudioEditor() {
</div> </div>
<div className="editor-content"> <div className="editor-content">
{activeFile ? ( {activeFile ? (
activeFile.content.split("\n").map((line, i) => ( activeFile.content.split("\n").map((line: string, i: number) => (
<div className="code-line" key={i}> <div className="code-line" key={i}>
<div className="line-number">{i + 1}</div> <div className="line-number">{i + 1}</div>
<div className="line-content">{line}</div> <div className="line-content">{line}</div>

125
package-lock.json generated
View file

@ -11,6 +11,8 @@
"@genkit-ai/google-genai": "^1.20.0", "@genkit-ai/google-genai": "^1.20.0",
"@genkit-ai/next": "^1.20.0", "@genkit-ai/next": "^1.20.0",
"@hookform/resolvers": "^4.1.3", "@hookform/resolvers": "^4.1.3",
"@monaco-editor/react": "^4.7.0",
"@phosphor-icons/react": "^2.1.10",
"@radix-ui/react-accordion": "^1.2.3", "@radix-ui/react-accordion": "^1.2.3",
"@radix-ui/react-alert-dialog": "^1.1.6", "@radix-ui/react-alert-dialog": "^1.1.6",
"@radix-ui/react-avatar": "^1.1.3", "@radix-ui/react-avatar": "^1.1.3",
@ -51,9 +53,11 @@
"react-hook-form": "^7.54.2", "react-hook-form": "^7.54.2",
"react-syntax-highlighter": "^15.5.0", "react-syntax-highlighter": "^15.5.0",
"recharts": "^2.15.1", "recharts": "^2.15.1",
"sonner": "^2.0.7",
"tailwind-merge": "^3.0.1", "tailwind-merge": "^3.0.1",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"zod": "^3.24.2" "zod": "^3.24.2",
"zustand": "^5.0.10"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20", "@types/node": "^20",
@ -3408,6 +3412,29 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/@monaco-editor/loader": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.7.0.tgz",
"integrity": "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==",
"license": "MIT",
"dependencies": {
"state-local": "^1.0.6"
}
},
"node_modules/@monaco-editor/react": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz",
"integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==",
"license": "MIT",
"dependencies": {
"@monaco-editor/loader": "^1.5.0"
},
"peerDependencies": {
"monaco-editor": ">= 0.25.0 < 1",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@next/env": { "node_modules/@next/env": {
"version": "15.5.9", "version": "15.5.9",
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.9.tgz", "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.9.tgz",
@ -5912,6 +5939,19 @@
"node": ">=14" "node": ">=14"
} }
}, },
"node_modules/@phosphor-icons/react": {
"version": "2.1.10",
"resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.1.10.tgz",
"integrity": "sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": ">= 16.8",
"react-dom": ">= 16.8"
}
},
"node_modules/@pkgjs/parseargs": { "node_modules/@pkgjs/parseargs": {
"version": "0.11.0", "version": "0.11.0",
"license": "MIT", "license": "MIT",
@ -7688,6 +7728,13 @@
"devOptional": true, "devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT",
"optional": true
},
"node_modules/@types/unist": { "node_modules/@types/unist": {
"version": "2.0.11", "version": "2.0.11",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
@ -8980,6 +9027,15 @@
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
}, },
"node_modules/dompurify": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz",
"integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
}
},
"node_modules/dot-prop": { "node_modules/dot-prop": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz",
@ -11511,6 +11567,28 @@
"integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/monaco-editor": {
"version": "0.55.1",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz",
"integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==",
"license": "MIT",
"dependencies": {
"dompurify": "3.2.7",
"marked": "14.0.0"
}
},
"node_modules/monaco-editor/node_modules/marked": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz",
"integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"license": "MIT" "license": "MIT"
@ -13213,6 +13291,16 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/sonner": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
"integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==",
"license": "MIT",
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
}
},
"node_modules/source-map": { "node_modules/source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@ -13262,6 +13350,12 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/state-local": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz",
"integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==",
"license": "MIT"
},
"node_modules/statuses": { "node_modules/statuses": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
@ -14429,6 +14523,35 @@
"peerDependencies": { "peerDependencies": {
"zod": "^3.25 || ^4" "zod": "^3.25 || ^4"
} }
},
"node_modules/zustand": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.10.tgz",
"integrity": "sha512-U1AiltS1O9hSy3rul+Ub82ut2fqIAefiSuwECWt6jlMVUGejvf+5omLcRBSzqbRagSM3hQZbtzdeRc6QVScXTg==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
},
"peerDependencies": {
"@types/react": ">=18.0.0",
"immer": ">=9.0.6",
"react": ">=18.0.0",
"use-sync-external-store": ">=1.2.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
},
"use-sync-external-store": {
"optional": true
}
}
} }
} }
} }

View file

@ -15,6 +15,8 @@
"@genkit-ai/google-genai": "^1.20.0", "@genkit-ai/google-genai": "^1.20.0",
"@genkit-ai/next": "^1.20.0", "@genkit-ai/next": "^1.20.0",
"@hookform/resolvers": "^4.1.3", "@hookform/resolvers": "^4.1.3",
"@monaco-editor/react": "^4.7.0",
"@phosphor-icons/react": "^2.1.10",
"@radix-ui/react-accordion": "^1.2.3", "@radix-ui/react-accordion": "^1.2.3",
"@radix-ui/react-alert-dialog": "^1.1.6", "@radix-ui/react-alert-dialog": "^1.1.6",
"@radix-ui/react-avatar": "^1.1.3", "@radix-ui/react-avatar": "^1.1.3",
@ -55,9 +57,11 @@
"react-hook-form": "^7.54.2", "react-hook-form": "^7.54.2",
"react-syntax-highlighter": "^15.5.0", "react-syntax-highlighter": "^15.5.0",
"recharts": "^2.15.1", "recharts": "^2.15.1",
"sonner": "^2.0.7",
"tailwind-merge": "^3.0.1", "tailwind-merge": "^3.0.1",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"zod": "^3.24.2" "zod": "^3.24.2",
"zustand": "^5.0.10"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20", "@types/node": "^20",

View file

@ -1,77 +1,59 @@
"use client";
import React, { useState } from "react";
import StudioSidebar from "./components/StudioSidebar";
import StudioEditor from "./components/StudioEditor";
import StudioBottomPanel from "./components/StudioBottomPanel";
import StudioRightPanel from "./components/StudioRightPanel";
import StudioNetworkViz from "./components/StudioNetworkViz";
import TemplatesDrawer from "./components/TemplatesDrawer";
import PreviewModal from "./components/PreviewModal";
import NewProjectModal from "./components/NewProjectModal";
import TranslationPanel from "./components/TranslationPanel";
import PassportLogin from "./components/PassportLogin";
import Toaster from "./components/ui/toaster";
import CommandPalette from "./components/ui/CommandPalette";
import { toast } from "./lib/toast";
import { captureEvent } from "./lib/captureEvent";
import Suspense from "./components/Suspense";
import StudioLayout from "../components/StudioLayout";
function App() { function App() {
// TODO: restore all state, handlers, and imports here // Minimal state stubs
const [user] = useState<any>(null);
const [showPassportLogin, setShowPassportLogin] = useState(false);
const [showNewProject, setShowNewProject] = useState(false);
const [showTemplates, setShowTemplates] = useState(false);
const [showTranslation, setShowTranslation] = useState(false);
const [showFileSearch, setShowFileSearch] = useState(false);
const [showCommandPalette, setShowCommandPalette] = useState(false);
const [consoleCollapsed, setConsoleCollapsed] = useState(false);
const [openFiles, setOpenFiles] = useState<any[]>([]);
const [activeFileId, setActiveFileId] = useState<string>("");
const [code, setCode] = useState<string>("");
const [currentCode, setCurrentCode] = useState<string>("");
const [showPreview, setShowPreview] = useState(false);
// Handler stubs
const handleCodeChange = (c: string) => setCode(c);
const handleFileSelect = (id: string) => setActiveFileId(id);
const handleFileClose = (id: string) => setOpenFiles(files => files.filter(f => f.id !== id));
const handleTemplateSelect = () => {};
const handleFileRename = () => {};
const handleFileDelete = () => {};
const handleFileMove = () => {};
return ( return (
<> <StudioLayout>
<Toaster position="bottom-right" /> <Toaster />
<div className="flex h-screen overflow-hidden bg-background"> <Suspense>
<StudioSidebar {showTemplates && <TemplatesDrawer onSelectTemplate={handleTemplateSelect} onClose={() => setShowTemplates(false)} currentPlatform={"roblox"} />}
user={user} {showPreview && <PreviewModal open={showPreview} code={currentCode} onClose={() => setShowPreview(false)} />}
onLogout={() => setShowPassportLogin(true)} {showNewProject && <NewProjectModal open={showNewProject} onClose={() => setShowNewProject(false)} onCreateProject={() => {}} />}
onNewProject={() => setShowNewProject(true)} {showTranslation && <TranslationPanel isOpen={showTranslation} onClose={() => setShowTranslation(false)} currentCode={currentCode} currentPlatform={"roblox"} />}
onOpenTemplates={() => setShowTemplates(true)} {showPassportLogin && <PassportLogin open={showPassportLogin} onClose={() => setShowPassportLogin(false)} onLoginSuccess={() => {}} />}
onOpenTranslation={() => setShowTranslation(true)}
onFileSearch={() => setShowFileSearch(true)}
onCommandPalette={() => setShowCommandPalette(true)}
consoleCollapsed={consoleCollapsed}
onConsoleToggle={() => setConsoleCollapsed((prev) => !prev)}
/>
<div className="flex-1 flex flex-col overflow-hidden">
<StudioEditor
code={code}
onCodeChange={handleCodeChange}
currentFileId={activeFileId}
onFileSelect={handleFileSelect}
onFileClose={handleFileClose}
openFiles={openFiles}
/>
<StudioBottomPanel
onRun={() => {
toast.success('Code executed!');
captureEvent('run_code');
}}
onStop={() => {
toast.success('Code stopped.');
captureEvent('stop_code');
}}
/>
<StudioRightPanel
fileId={activeFileId}
onFileRename={handleFileRename}
onFileDelete={handleFileDelete}
onFileMove={handleFileMove}
/>
<StudioNetworkViz
data={{}} // TODO: wire up actual network data
onNodeClick={(node) => {
setActiveFileId(node.id);
setCode(node.content);
setCurrentCode(node.content);
}}
/>
</div>
</div>
<Suspense fallback={<div className="fixed inset-0 flex items-center justify-center bg-background/80 z-50">Loading</div>}>
{showTemplates && <TemplatesDrawer onSelect={handleTemplateSelect} onClose={() => setShowTemplates(false)} />}
{showPreview && <PreviewModal code={currentCode} onClose={() => setShowPreview(false)} />}
{showNewProject && <NewProjectModal onClose={() => setShowNewProject(false)} />}
{showTranslation && <TranslationPanel onClose={() => setShowTranslation(false)} />}
{showPassportLogin && <PassportLogin onClose={() => setShowPassportLogin(false)} />}
</Suspense> </Suspense>
<CommandPalette />
<CommandPalette </StudioLayout>
open={showCommandPalette}
onClose={() => setShowCommandPalette(false)}
commands={createDefaultCommands({
onNewProject: () => setShowNewProject(true),
onTemplates: () => setShowTemplates(true),
onPreview: () => setShowPreview(true),
onExport: () => toast.success('Exported!'),
onCopy: () => toast.success('Copied!'),
})}
/>
</>
); );
} }

View file

@ -11,7 +11,7 @@ interface ErrorFallbackProps {
export const ErrorFallback = ({ error, resetErrorBoundary }: ErrorFallbackProps) => { export const ErrorFallback = ({ error, resetErrorBoundary }: ErrorFallbackProps) => {
// When encountering an error in the development mode, rethrow it and don't display the boundary. // When encountering an error in the development mode, rethrow it and don't display the boundary.
// The parent UI will take care of showing a more helpful dialog. // The parent UI will take care of showing a more helpful dialog.
if (import.meta.env.DEV) throw error; if (process.env.NODE_ENV === 'development') throw error;
return ( return (
<div className="min-h-screen bg-background flex items-center justify-center p-4"> <div className="min-h-screen bg-background flex items-center justify-center p-4">

View file

@ -1,37 +1,24 @@
'use server'; "use server";
/** import { ai } from "../../ai/genkit";
* @fileOverview This file defines a Genkit flow that helps new users by suggesting an initial set of code files import { z } from "genkit";
* and project structure based on a simple prompt describing the desired application.
*
* - aiHelpFromPrompt - A function that takes a prompt and returns suggested code files and project structure.
* - AIHelpFromPromptInput - The input type for the aiHelpFromPrompt function.
* - AIHelpFromPromptOutput - The return type for the aiHelpFromPrompt function.
*/
import {ai} from '@/ai/genkit';
import {z} from 'genkit';
const AIHelpFromPromptInputSchema = z.object({ const AIHelpFromPromptInputSchema = z.object({
prompt: z.string().describe('A prompt describing the type of application to build.'), prompt: z.string().describe("A prompt describing the type of application to build."),
}); });
export type AIHelpFromPromptInput = z.infer<typeof AIHelpFromPromptInputSchema>; export type AIHelpFromPromptInput = z.infer<typeof AIHelpFromPromptInputSchema>;
const AIHelpFromPromptOutputSchema = z.object({ const AIHelpFromPromptOutputSchema = z.object({
suggestedFiles: z.array(z.object({ suggestedFiles: z.array(z.object({
filePath: z.string().describe('The path for the suggested file.'), filePath: z.string().describe("The path for the suggested file."),
fileContent: z.string().describe('The content of the suggested file.'), fileContent: z.string().describe("The content of the suggested file."),
})).describe('An array of suggested code files and their content.'), })).describe("An array of suggested code files and their content."),
explanation: z.string().describe('An explanation of the suggested file structure and code.'), explanation: z.string().describe("An explanation of the suggested file structure and code."),
}); });
export type AIHelpFromPromptOutput = z.infer<typeof AIHelpFromPromptOutputSchema>; export type AIHelpFromPromptOutput = z.infer<typeof AIHelpFromPromptOutputSchema>;
export async function aiHelpFromPrompt(input: AIHelpFromPromptInput): Promise<AIHelpFromPromptOutput> {
return aiHelpFromPromptFlow(input);
}
const prompt = ai.definePrompt({ const prompt = ai.definePrompt({
name: 'aiHelpFromPromptPrompt', name: "aiHelpFromPromptPrompt",
input: { schema: AIHelpFromPromptInputSchema }, input: { schema: AIHelpFromPromptInputSchema },
output: { schema: AIHelpFromPromptOutputSchema }, output: { schema: AIHelpFromPromptOutputSchema },
prompt: `You are an AI assistant designed to help new users quickly start developing applications. prompt: `You are an AI assistant designed to help new users quickly start developing applications.
@ -62,29 +49,29 @@ const prompt = ai.definePrompt({
const aiHelpFromPromptFlow = ai.defineFlow( const aiHelpFromPromptFlow = ai.defineFlow(
{ {
name: 'aiHelpFromPromptFlow', name: "aiHelpFromPromptFlow",
inputSchema: AIHelpFromPromptInputSchema, inputSchema: AIHelpFromPromptInputSchema,
outputSchema: AIHelpFromPromptOutputSchema, outputSchema: AIHelpFromPromptOutputSchema,
}, },
async input => { async (input) => {
const { output } = await prompt(input, { const { output } = await prompt(input, {
config: { config: {
safetySettings: [ safetySettings: [
{ {
category: 'HARM_CATEGORY_HATE_SPEECH', category: "HARM_CATEGORY_HATE_SPEECH",
threshold: 'BLOCK_ONLY_HIGH', threshold: "BLOCK_ONLY_HIGH",
}, },
{ {
category: 'HARM_CATEGORY_DANGEROUS_CONTENT', category: "HARM_CATEGORY_DANGEROUS_CONTENT",
threshold: 'BLOCK_NONE', threshold: "BLOCK_NONE",
}, },
{ {
category: 'HARM_CATEGORY_HARASSMENT', category: "HARM_CATEGORY_HARASSMENT",
threshold: 'BLOCK_MEDIUM_AND_ABOVE', threshold: "BLOCK_MEDIUM_AND_ABOVE",
}, },
{ {
category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT', category: "HARM_CATEGORY_SEXUALLY_EXPLICIT",
threshold: 'BLOCK_LOW_AND_ABOVE', threshold: "BLOCK_LOW_AND_ABOVE",
}, },
], ],
}, },
@ -92,38 +79,7 @@ const aiHelpFromPromptFlow = ai.defineFlow(
return output!; return output!;
} }
); );
'use server';
/**
* @fileOverview This file defines a Genkit flow that helps new users by suggesting an initial set of code files
* and project structure based on a simple prompt describing the desired application.
*
* - aiHelpFromPrompt - A function that takes a prompt and returns suggested code files and project structure.
* - AIHelpFromPromptInput - The input type for the aiHelpFromPrompt function.
* - AIHelpFromPromptOutput - The return type for the aiHelpFromPrompt function.
*/
import {ai} from '@/ai/genkit';
import {z} from 'genkit';
const AIHelpFromPromptInputSchema = z.object({
prompt: z.string().describe('A prompt describing the type of application to build.'),
});
export type AIHelpFromPromptInput = z.infer<typeof AIHelpFromPromptInputSchema>;
const AIHelpFromPromptOutputSchema = z.object({
suggestedFiles: z.array(z.object({
filePath: z.string().describe('The path for the suggested file.'),
fileContent: z.string().describe('The content of the suggested file.'),
})).describe('An array of suggested code files and their content.'),
explanation: z.string().describe('An explanation of the suggested file structure and code.'),
});
export type AIHelpFromPromptOutput = z.infer<typeof AIHelpFromPromptOutputSchema>;
export async function aiHelpFromPrompt(input: AIHelpFromPromptInput): Promise<AIHelpFromPromptOutput> { export async function aiHelpFromPrompt(input: AIHelpFromPromptInput): Promise<AIHelpFromPromptOutput> {
return aiHelpFromPromptFlow(input); return aiHelpFromPromptFlow(input);
} }
const prompt = ai.definePrompt({
name: 'aiHelpFromPromptPrompt',
input: {schema: AIHelpFromPromptInputSchema},

View file

@ -1,15 +1,5 @@
'use server';
/** import {ai} from '../../ai/genkit';
* @fileOverview An AI agent that detects synchronization conflicts between platform-specific code and data,
* and suggests solutions to resolve them.
*
* - aiSuggestedSyncConflictResolution - A function that handles the conflict resolution process.
* - AISuggestedSyncConflictResolutionInput - The input type for the aiSuggestedSyncConflictResolution function.
* - AISuggestedSyncConflictResolutionOutput - The return type for the aiSuggestedSyncConflictResolution function.
*/
import {ai} from '@/ai/genkit';
import {z} from 'genkit'; import {z} from 'genkit';
const AISuggestedSyncConflictResolutionInputSchema = z.object({ const AISuggestedSyncConflictResolutionInputSchema = z.object({
@ -27,10 +17,6 @@ const AISuggestedSyncConflictResolutionOutputSchema = z.object({
}); });
export type AISuggestedSyncConflictResolutionOutput = z.infer<typeof AISuggestedSyncConflictResolutionOutputSchema>; export type AISuggestedSyncConflictResolutionOutput = z.infer<typeof AISuggestedSyncConflictResolutionOutputSchema>;
export async function aiSuggestedSyncConflictResolution(input: AISuggestedSyncConflictResolutionInput): Promise<AISuggestedSyncConflictResolutionOutput> {
return aiSuggestedSyncConflictResolutionFlow(input);
}
const prompt = ai.definePrompt({ const prompt = ai.definePrompt({
name: 'aiSuggestedSyncConflictResolutionPrompt', name: 'aiSuggestedSyncConflictResolutionPrompt',
input: {schema: AISuggestedSyncConflictResolutionInputSchema}, input: {schema: AISuggestedSyncConflictResolutionInputSchema},
@ -60,60 +46,11 @@ const aiSuggestedSyncConflictResolutionFlow = ai.defineFlow(
inputSchema: AISuggestedSyncConflictResolutionInputSchema, inputSchema: AISuggestedSyncConflictResolutionInputSchema,
outputSchema: AISuggestedSyncConflictResolutionOutputSchema, outputSchema: AISuggestedSyncConflictResolutionOutputSchema,
}, },
async input => { async (input: AISuggestedSyncConflictResolutionInput) => {
const {output} = await prompt(input, { const {output} = await prompt(input);
config: {
safetySettings: [
{
category: 'HARM_CATEGORY_HATE_SPEECH',
threshold: 'BLOCK_ONLY_HIGH',
},
{
category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
threshold: 'BLOCK_NONE',
},
{
category: 'HARM_CATEGORY_HARASSMENT',
threshold: 'BLOCK_MEDIUM_AND_ABOVE',
},
{
category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
threshold: 'BLOCK_LOW_AND_ABOVE',
},
],
},
});
return output!; return output!;
} }
); );
'use server';
/**
* @fileOverview An AI agent that detects synchronization conflicts between platform-specific code and data,
* and suggests solutions to resolve them.
*
* - aiSuggestedSyncConflictResolution - A function that handles the conflict resolution process.
* - AISuggestedSyncConflictResolutionInput - The input type for the aiSuggestedSyncConflictResolution function.
* - AISuggestedSyncConflictResolutionOutput - The return type for the aiSuggestedSyncConflictResolution function.
*/
import {ai} from '@/ai/genkit';
import {z} from 'genkit';
const AISuggestedSyncConflictResolutionInputSchema = z.object({
robloxCode: z.string().describe('The Lua code for the Roblox platform.'),
webCode: z.string().describe('The JavaScript code for the web platform.'),
mobileCode: z.string().describe('The React Native code for the mobile platform.'),
sharedState: z.string().describe('The shared state data in JSON format.'),
});
export type AISuggestedSyncConflictResolutionInput = z.infer<typeof AISuggestedSyncConflictResolutionInputSchema>;
const AISuggestedSyncConflictResolutionOutputSchema = z.object({
conflictDetected: z.boolean().describe('Whether a synchronization conflict was detected.'),
suggestedSolutions: z.array(z.string()).describe('An array of suggested solutions to resolve the conflicts.'),
explanation: z.string().describe('Explanation of the detected conflicts and suggested solutions.'),
});
export type AISuggestedSyncConflictResolutionOutput = z.infer<typeof AISuggestedSyncConflictResolutionOutputSchema>;
export async function aiSuggestedSyncConflictResolution(input: AISuggestedSyncConflictResolutionInput): Promise<AISuggestedSyncConflictResolutionOutput> { export async function aiSuggestedSyncConflictResolution(input: AISuggestedSyncConflictResolutionInput): Promise<AISuggestedSyncConflictResolutionOutput> {
return aiSuggestedSyncConflictResolutionFlow(input); return aiSuggestedSyncConflictResolutionFlow(input);

View file

@ -8,7 +8,7 @@
* - ContextualCodeSuggestionsOutput - The return type for the contextualCodeSuggestions function. * - ContextualCodeSuggestionsOutput - The return type for the contextualCodeSuggestions function.
*/ */
import {ai} from '@/ai/genkit'; import {ai} from '../../ai/genkit';
import {z} from 'genkit'; import {z} from 'genkit';
const ContextualCodeSuggestionsInputSchema = z.object({ const ContextualCodeSuggestionsInputSchema = z.object({
@ -69,43 +69,9 @@ const contextualCodeSuggestionsFlow = ai.defineFlow(
inputSchema: ContextualCodeSuggestionsInputSchema, inputSchema: ContextualCodeSuggestionsInputSchema,
outputSchema: ContextualCodeSuggestionsOutputSchema, outputSchema: ContextualCodeSuggestionsOutputSchema,
}, },
async input => { async (input: ContextualCodeSuggestionsInput) => {
const {output} = await prompt(input); const {output} = await prompt(input);
return output!; return output!;
} }
); );
'use server'; 'use server';
/**
* @fileOverview This file defines a Genkit flow for providing contextual code suggestions.
*
* - contextualCodeSuggestions - A function that takes the current file content and cursor position
* and returns code suggestions.
* - ContextualCodeSuggestionsInput - The input type for the contextualCodeSuggestions function.
* - ContextualCodeSuggestionsOutput - The return type for the contextualCodeSuggestions function.
*/
import {ai} from '@/ai/genkit';
import {z} from 'genkit';
const ContextualCodeSuggestionsInputSchema = z.object({
fileContent: z.string().describe('The content of the currently open file.'),
cursorPosition: z.number().describe('The cursor position within the file.'),
language: z.string().describe('The programming language of the file.'),
context: z.string().optional().describe('Additional context for code suggestions, e.g., error messages or related code snippets.'),
});
export type ContextualCodeSuggestionsInput = z.infer<
typeof ContextualCodeSuggestionsInputSchema
>;
const ContextualCodeSuggestionsOutputSchema = z.object({
suggestions: z
.array(z.string())
.describe('An array of code suggestions based on the context.'),
});
export type ContextualCodeSuggestionsOutput = z.infer<
typeof ContextualCodeSuggestionsOutputSchema
>;
export async function contextualCodeSuggestions(
input: ContextualCodeSuggestionsInput
): Promise<ContextualCodeSuggestionsOutput> {

View file

@ -1,4 +1,4 @@
import { DashboardPage } from "@/components/aethex/dashboard-page"; import { DashboardPage } from "../../components/aethex/dashboard-page";
export default function Page() { export default function Page() {
return <DashboardPage />; return <DashboardPage />;

34
src/app/landing-page.tsx Normal file
View file

@ -0,0 +1,34 @@
import Link from "next/link";
export default function LandingPage() {
return (
<main className="min-h-screen flex flex-col items-center justify-center bg-gradient-to-br from-gray-900 via-gray-800 to-gray-950 text-white p-8">
<div className="max-w-xl w-full flex flex-col items-center">
<img src="/logo.svg" alt="AeThex Studio Logo" className="w-24 h-24 mb-6" />
<h1 className="text-4xl font-bold mb-4 text-center">Welcome to AeThex Studio</h1>
<p className="text-lg mb-8 text-center opacity-80">
The Next-Generation Cross-Platform IDE for Creators, Developers, and Teams.
</p>
<div className="flex flex-col gap-4 w-full">
<a
href="https://aethex.com/download"
className="bg-blue-600 hover:bg-blue-700 text-white font-semibold py-3 rounded-lg text-center transition-colors shadow-lg"
target="_blank"
rel="noopener noreferrer"
>
Download AeThex Studio
</a>
<Link
href="/ide"
className="bg-gray-800 hover:bg-gray-700 text-white font-semibold py-3 rounded-lg text-center transition-colors border border-gray-700"
>
Open in Browser
</Link>
</div>
<p className="mt-8 text-sm text-gray-400 text-center">
Need help? <a href="https://aethex.com/docs" className="underline hover:text-blue-400">Read the Docs</a>
</p>
</div>
</main>
);
}

View file

@ -1,5 +1,5 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Toaster } from "@/components/ui/toaster"; import Toaster from "../components/ui/toaster";
import "./globals.css"; import "./globals.css";
export const metadata: Metadata = { export const metadata: Metadata = {

View file

@ -1,5 +1,34 @@
import { LoginPage } from "@/components/aethex/login-page";
export default function Page() { export default function Page() {
return <LoginPage />; return (
<main className="min-h-screen flex flex-col items-center justify-center bg-gradient-to-br from-gray-950 to-gray-900 text-white">
<div className="max-w-xl w-full px-6 py-12 rounded-xl shadow-2xl bg-black/70 border border-gray-800 flex flex-col items-center">
<h1 className="text-4xl font-bold mb-4 tracking-tight">Welcome to AeThex Studio</h1>
<p className="mb-6 text-lg text-gray-300 text-center">
The next-generation cross-platform IDE for creators, educators, and teams.<br />
Download or launch AeThex Studio below.
</p>
<div className="flex flex-col gap-4 w-full items-center">
<a
href="/ide"
className="w-full py-3 px-6 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-semibold text-lg text-center transition"
>
Launch Web IDE
</a>
<a
href="https://github.com/AeThex-LABS/aethex-studio/releases/latest"
target="_blank"
rel="noopener noreferrer"
className="w-full py-3 px-6 rounded-lg bg-gray-800 hover:bg-gray-700 text-gray-100 font-semibold text-lg text-center border border-gray-700 transition"
>
Download Desktop App
</a>
</div>
<p className="mt-8 text-xs text-gray-500 text-center">
Open source on <a href="https://github.com/AeThex-LABS/aethex-studio" className="underline hover:text-blue-400">GitHub</a>
</p>
</div>
</main>
);
} }

View file

@ -0,0 +1 @@
export default function StudioBottomPanel() { return <footer>Bottom Panel</footer>; }

View file

@ -0,0 +1 @@
export default function StudioEditor() { return <div>Editor</div>; }

View file

@ -0,0 +1 @@
export default function StudioNetworkViz() { return <div>Network Viz</div>; }

View file

@ -0,0 +1 @@
export default function StudioRightPanel() { return <aside>Right Panel</aside>; }

View file

@ -0,0 +1 @@
export default function StudioSidebar() { return <aside>Sidebar</aside>; }

View file

@ -0,0 +1 @@
export default function Suspense({ children }: { children: React.ReactNode }) { return <>{children}</>; }

View file

@ -0,0 +1,2 @@
// TypeScript declaration for AethexStudio module
export * from "./aethex-studio";

View file

@ -1,3 +1,4 @@
"use client"; "use client";
import { useState } from "react"; import { useState } from "react";
@ -5,35 +6,28 @@ import {
ResizableHandle, ResizableHandle,
ResizablePanel, ResizablePanel,
ResizablePanelGroup, ResizablePanelGroup,
} from "@/components/ui/resizable"; } from "../ui/resizable";
import { Navbar } from "./navbar"; import { Navbar } from "./navbar";
import { FileNavigator } from "./file-navigator"; import FileNavigator from "./file-navigator";
import { MainView } from "./main-view"; import { MainView } from "./main-view";
import { BottomPanel } from "./bottom-panel"; import { BottomPanel } from "./bottom-panel";
import { AiAssistant } from "./ai-assistant"; import AiAssistant from "./ai-assistant";
import { import {
openFiles as initialOpenFiles, initialFileTree,
fileTree as initialFileTree, File,
File as OpenFileType,
FolderNode, FolderNode,
FileNode, FileNode,
} from "@/lib/aethex-data"; } from "../../lib/aethex-data";
import { NewProjectModal } from "./new-project-modal"; import { NewProjectModal } from "./new-project-modal";
import { import { ScriptTemplate, getTemplatesForPlatform } from "../../lib/templates";
ProjectTemplate,
generateFileContent,
} from "@/lib/templates";
import type { NewProjectFormValues } from "./new-project-modal";
export type { OpenFileType };
export function AethexStudio() { export function AethexStudio() {
const [openFiles, setOpenFiles] = useState<OpenFileType[]>(initialOpenFiles); const [openFiles, setOpenFiles] = useState<File[]>([]);
const [activeTab, setActiveTab] = useState<string>(openFiles[0]?.id || ""); const [activeTab, setActiveTab] = useState<string>(openFiles[0]?.id || "");
const [fileTree, setFileTree] = useState<FolderNode>(initialFileTree); const [fileTree, setFileTree] = useState<FolderNode>(initialFileTree);
const [isNewProjectModalOpen, setIsNewProjectModalOpen] = useState(false); const [isNewProjectModalOpen, setIsNewProjectModalOpen] = useState(false);
const handleOpenFile = (file: OpenFileType) => { const handleOpenFile = (file: File) => {
if (!openFiles.find((f) => f.id === file.id)) { if (!openFiles.find((f) => f.id === file.id)) {
setOpenFiles((prev) => [...prev, file]); setOpenFiles((prev) => [...prev, file]);
} }
@ -53,57 +47,24 @@ export function AethexStudio() {
} }
}; };
const handleCreateProject = ( const handleCreateProject = (name: string) => {
template: ProjectTemplate, const newFile: File = {
config: NewProjectFormValues id: name + Date.now(),
) => { name,
const newFileTree: FolderNode = { language: "lua",
...template.fileTree, content: "",
name: config.projectName,
}; };
setFileTree(newFileTree); setOpenFiles([...openFiles, newFile]);
let mainFileToOpen: OpenFileType | undefined;
const findMainFile = (node: FolderNode | FileNode, currentPath: string) => {
if (mainFileToOpen) return;
const newPath = currentPath ? `${currentPath}/${node.name}` : node.name;
if (node.type === "file" && node.name === template.mainFile) {
mainFileToOpen = {
id: newPath,
name: node.name,
content: generateFileContent(node, template),
};
} else if (node.type === "folder" && node.children) {
node.children.forEach((child) => findMainFile(child, newPath));
}
};
findMainFile(newFileTree, "");
if (mainFileToOpen) {
setOpenFiles([mainFileToOpen]);
setActiveTab(mainFileToOpen.id);
}
}; };
return ( return (
<div className="aethex-studio"> <div className="aethex-studio">
{/* ...rest of the studio UI... */}
<Navbar /> <Navbar />
<FileNavigator fileTree={fileTree} onOpenFile={handleOpenFile} /> <FileNavigator fileTree={fileTree} onOpenFile={handleOpenFile} />
<MainView <MainView />
openFiles={openFiles}
activeTab={activeTab}
onCloseFile={handleCloseFile}
onOpenFile={handleOpenFile}
/>
<BottomPanel /> <BottomPanel />
<AiAssistant /> <AiAssistant />
<NewProjectModal <NewProjectModal onCreate={handleCreateProject} onClose={() => setIsNewProjectModalOpen(false)} />
open={isNewProjectModalOpen}
onOpenChange={setIsNewProjectModalOpen}
onCreate={handleCreateProject}
/>
</div> </div>
); );
} }

View file

@ -37,4 +37,9 @@ import {
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
// ...rest of the AiAssistant code from backup...
const AiAssistant = () => {
return <aside>AiAssistant (stub)</aside>;
};
export default AiAssistant;

View file

@ -18,7 +18,14 @@ import { Input } from "@/components/ui/input";
import { FileNode, FolderNode, File as OpenFileType } from "@/lib/aethex-data"; import { FileNode, FolderNode, File as OpenFileType } from "@/lib/aethex-data";
import { generateFileContent } from "@/lib/templates"; import { generateFileContent } from "@/lib/templates";
type FileNavigatorProps = { type FileNavigatorProps = {
onOpenFile: (file: OpenFileType) => void; onOpenFile: (file: OpenFileType) => void;
fileTree: FolderNode; fileTree: FolderNode;
}; };
const FileNavigator: React.FC<FileNavigatorProps> = () => {
return <nav>FileNavigator (stub)</nav>;
};
export default FileNavigator;

View file

@ -0,0 +1 @@
export { AethexStudio } from "./aethex-studio";

View file

@ -0,0 +1 @@
export { AethexStudio } from "./aethex-studio";

View file

@ -2,14 +2,14 @@
import Link from "next/link"; import Link from "next/link";
import Image from "next/image"; import Image from "next/image";
import { AethexLogo, GoogleIcon } from "@/components/aethex/icons"; import { AethexLogo, GoogleIcon } from "./icons";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Github } from "lucide-react"; import { Github } from "lucide-react";
import { PlaceHolderImages } from "@/lib/placeholder-images"; import { PlaceHolderImages } from "../../lib/placeholder-images";
export function LoginPage() { export function LoginPage() {
const loginIllustration = PlaceHolderImages.find( const loginIllustration = PlaceHolderImages.find(
(p) => p.id === "login-illustration" (p: { id: string }) => p.id === "login-illustration"
); );
return ( return (

View file

@ -0,0 +1 @@
export function MainView() { return <main>Main View (stub)</main>; }

View file

@ -0,0 +1 @@
export function Navbar() { return <nav>Navbar (stub)</nav>; }

View file

@ -1,14 +1,21 @@
import React from "react"; import React from "react";
import { RobloxIcon, WebIcon, MobileIcon } from "./icons";
interface Workspace { interface Workspace {
id: string; id: string;
name: string; name: string;
lastModified: string; lastModified: string;
platforms: React.ComponentType[]; platforms: string[];
thumbnailUrlId: string; thumbnailUrlId: string;
thumbnailImageHint: string; thumbnailImageHint: string;
} }
const platformIconMap: Record<string, React.FC<React.SVGProps<SVGSVGElement>>> = {
roblox: RobloxIcon,
web: WebIcon,
mobile: MobileIcon,
};
export function WorkspaceCard({ workspace }: { workspace: Workspace }) { export function WorkspaceCard({ workspace }: { workspace: Workspace }) {
return ( return (
<div className="rounded-lg border bg-card p-4 shadow"> <div className="rounded-lg border bg-card p-4 shadow">
@ -18,9 +25,10 @@ export function WorkspaceCard({ workspace }: { workspace: Workspace }) {
<div className="mt-4 font-semibold text-lg">{workspace.name}</div> <div className="mt-4 font-semibold text-lg">{workspace.name}</div>
<div className="mt-2 text-xs text-muted-foreground">{workspace.lastModified}</div> <div className="mt-2 text-xs text-muted-foreground">{workspace.lastModified}</div>
<div className="mt-2 flex gap-2"> <div className="mt-2 flex gap-2">
{workspace.platforms.map((PlatformIcon, i) => ( {workspace.platforms.map((platform, i) => {
<PlatformIcon key={i} className="h-4 w-4" /> const Icon = platformIconMap[platform];
))} return Icon ? <Icon key={i} className="h-4 w-4" /> : null;
})}
</div> </div>
</div> </div>
); );

View file

@ -0,0 +1 @@
export default function CommandPalette() { return <div>Command Palette</div>; }

View file

@ -2,7 +2,7 @@
import { ComponentProps } from "react" import { ComponentProps } from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox" import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import CheckIcon from "lucide-react/dist/esm/icons/check" import { Check } from "lucide-react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
@ -23,7 +23,7 @@ function Checkbox({
data-slot="checkbox-indicator" data-slot="checkbox-indicator"
className="flex items-center justify-center text-current transition-none" className="flex items-center justify-center text-current transition-none"
> >
<CheckIcon className="size-3.5" /> <Check className="size-3.5" />
</CheckboxPrimitive.Indicator> </CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root> </CheckboxPrimitive.Root>
) )

View file

@ -1,6 +1,6 @@
import { ComponentProps } from "react" import { ComponentProps } from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog" import * as DialogPrimitive from "@radix-ui/react-dialog"
import XIcon from "lucide-react/dist/esm/icons/x" import { X } from "lucide-react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
@ -62,7 +62,7 @@ function DialogContent({
> >
{children} {children}
<DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"> <DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
<XIcon /> <X />
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
</DialogPrimitive.Close> </DialogPrimitive.Close>
</DialogPrimitive.Content> </DialogPrimitive.Content>

View file

@ -1,8 +1,6 @@
import { ComponentProps } from "react" import { ComponentProps } from "react"
import * as SelectPrimitive from "@radix-ui/react-select" import * as SelectPrimitive from "@radix-ui/react-select"
import CheckIcon from "lucide-react/dist/esm/icons/check" import { Check, ChevronDown, ChevronUp } from "lucide-react";
import ChevronDownIcon from "lucide-react/dist/esm/icons/chevron-down"
import ChevronUpIcon from "lucide-react/dist/esm/icons/chevron-up"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
@ -44,7 +42,7 @@ function SelectTrigger({
> >
{children} {children}
<SelectPrimitive.Icon asChild> <SelectPrimitive.Icon asChild>
<ChevronDownIcon className="size-4 opacity-50" /> <ChevronDown className="size-4 opacity-50" />
</SelectPrimitive.Icon> </SelectPrimitive.Icon>
</SelectPrimitive.Trigger> </SelectPrimitive.Trigger>
) )
@ -114,7 +112,7 @@ function SelectItem({
> >
<span className="absolute right-2 flex size-3.5 items-center justify-center"> <span className="absolute right-2 flex size-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator> <SelectPrimitive.ItemIndicator>
<CheckIcon className="size-4" /> <Check className="size-4" />
</SelectPrimitive.ItemIndicator> </SelectPrimitive.ItemIndicator>
</span> </span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
@ -148,7 +146,7 @@ function SelectScrollUpButton({
)} )}
{...props} {...props}
> >
<ChevronUpIcon className="size-4" /> <ChevronUp className="size-4" />
</SelectPrimitive.ScrollUpButton> </SelectPrimitive.ScrollUpButton>
) )
} }
@ -166,7 +164,7 @@ function SelectScrollDownButton({
)} )}
{...props} {...props}
> >
<ChevronDownIcon className="size-4" /> <ChevronDown className="size-4" />
</SelectPrimitive.ScrollDownButton> </SelectPrimitive.ScrollDownButton>
) )
} }

View file

@ -1,6 +1,6 @@
import { ComponentProps } from "react" import { ComponentProps } from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog" import * as SheetPrimitive from "@radix-ui/react-dialog"
import XIcon from "lucide-react/dist/esm/icons/x" import { X } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
@ -71,7 +71,7 @@ function SheetContent({
> >
{children} {children}
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none"> <SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
<XIcon className="size-4" /> <X className="size-4" />
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
</SheetPrimitive.Close> </SheetPrimitive.Close>
</SheetPrimitive.Content> </SheetPrimitive.Content>

View file

@ -0,0 +1,3 @@
export default function Toaster() {
return <div>Toaster (stub)</div>;
}

11
src/hooks/use-toast.ts Normal file
View file

@ -0,0 +1,11 @@
import { useCallback } from "react";
export function useToast() {
// Simple stub for toast notifications
return {
toast: useCallback((opts: { title: string; description?: string }) => {
// You can replace this with a real toast implementation
alert(`${opts.title}${opts.description ? ': ' + opts.description : ''}`);
}, []),
};
}

1
src/lib/captureEvent.ts Normal file
View file

@ -0,0 +1 @@
export function captureEvent(event: string) { console.log('Event:', event); }

View file

@ -2,13 +2,13 @@ import { PlatformId } from './platforms';
import { uefnTemplates } from './templates-uefn'; import { uefnTemplates } from './templates-uefn';
import { spatialTemplates } from './templates-spatial'; import { spatialTemplates } from './templates-spatial';
export interface ScriptTemplate {
id: string; id: string;
name: string; name: string;
description: string; description: string;
code: string; code: string;
category: 'beginner' | 'gameplay' | 'ui' | 'tools' | 'advanced'; category: 'beginner' | 'gameplay' | 'ui' | 'tools' | 'advanced';
platform: PlatformId; platform: PlatformId;
badge?: string;
} }

1
src/lib/toast.ts Normal file
View file

@ -0,0 +1 @@
export const toast = { success: (msg: string) => { console.log('Toast:', msg); } }

View file

@ -1,6 +1,48 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge" import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)) return twMerge(clsx(inputs));
}
export function formatTimestamp(date: Date): string {
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
const seconds = date.getSeconds().toString().padStart(2, '0');
const ms = date.getMilliseconds().toString().padStart(3, '0');
return `${hours}:${minutes}:${seconds}.${ms}`;
}
export function getFileIcon(fileName: string): string {
if (fileName.endsWith('.lua')) return '🎮';
if (fileName.endsWith('.js') || fileName.endsWith('.jsx')) return '🌐';
if (fileName.endsWith('.ts') || fileName.endsWith('.tsx')) return '📘';
if (fileName.endsWith('.html')) return '📄';
if (fileName.endsWith('.css')) return '🎨';
if (fileName.endsWith('.json')) return '📋';
if (fileName.endsWith('.md')) return '📝';
return '📄';
}
export function getPlatformIcon(platform: string): string {
switch (platform) {
case 'roblox': return '🎮';
case 'web': return '🌐';
case 'mobile': return '📱';
case 'desktop': return '🖥️';
case 'shared': return '🔗';
default: return '📄';
}
}
export function getPlatformColor(platform: string): string {
switch (platform) {
case 'roblox': return 'text-red-400';
case 'web': return 'text-blue-400';
case 'mobile': return 'text-green-400';
case 'desktop': return 'text-purple-400';
case 'system': return 'text-gray-400';
default: return 'text-gray-400';
}
} }

1
src/lib/utils/index.ts Normal file
View file

@ -0,0 +1 @@
export * from "./utils";

View file

@ -17,17 +17,17 @@ interface EditorState {
updateFileContent: (id: string, content: string) => void; updateFileContent: (id: string, content: string) => void;
} }
export const useEditorStore = create<EditorState>((set, get) => ({ export const useEditorStore = create<EditorState>((set: any, get: any) => ({
openTabs: [], openTabs: [],
activeTabId: null, activeTabId: null,
files: [ files: [
{ id: '1', name: 'main.lua', content: '-- Main Lua file\nprint("Hello, AeThex!")' }, { id: '1', name: 'main.lua', content: '-- Main Lua file\nprint("Hello, AeThex!")' },
{ id: '2', name: 'utils.lua', content: '-- Utility functions\nfunction greet(name)\n print("Hello, " .. name)\nend' }, { id: '2', name: 'utils.lua', content: '-- Utility functions\nfunction greet(name)\n print("Hello, " .. name)\nend' },
], ],
openFile: (file) => { openFile: (file: FileTab) => {
const { openTabs } = get(); const { openTabs } = get();
if (!openTabs.find((f) => f.id === file.id)) { if (!openTabs.find((f: FileTab) => f.id === file.id)) {
set((state) => ({ set((state: EditorState) => ({
openTabs: [...state.openTabs, file], openTabs: [...state.openTabs, file],
activeTabId: file.id, activeTabId: file.id,
})); }));
@ -35,9 +35,9 @@ export const useEditorStore = create<EditorState>((set, get) => ({
set({ activeTabId: file.id }); set({ activeTabId: file.id });
} }
}, },
closeTab: (id) => { closeTab: (id: string) => {
set((state) => { set((state: EditorState) => {
const newTabs = state.openTabs.filter((f) => f.id !== id); const newTabs = state.openTabs.filter((f: FileTab) => f.id !== id);
let newActive = state.activeTabId; let newActive = state.activeTabId;
if (state.activeTabId === id) { if (state.activeTabId === id) {
newActive = newTabs.length > 0 ? newTabs[newTabs.length - 1].id : null; newActive = newTabs.length > 0 ? newTabs[newTabs.length - 1].id : null;
@ -45,12 +45,12 @@ export const useEditorStore = create<EditorState>((set, get) => ({
return { openTabs: newTabs, activeTabId: newActive }; return { openTabs: newTabs, activeTabId: newActive };
}); });
}, },
setActiveTab: (id) => set({ activeTabId: id }), setActiveTab: (id: string) => set({ activeTabId: id }),
setFiles: (files) => set({ files }), setFiles: (files: FileTab[]) => set({ files }),
updateFileContent: (id, content) => { updateFileContent: (id: string, content: string) => {
set((state) => ({ set((state: EditorState) => ({
files: state.files.map((f) => f.id === id ? { ...f, content } : f), files: state.files.map((f: FileTab) => f.id === id ? { ...f, content } : f),
openTabs: state.openTabs.map((f) => f.id === id ? { ...f, content } : f), openTabs: state.openTabs.map((f: FileTab) => f.id === id ? { ...f, content } : f),
})); }));
}, },
})); }));

View file

@ -20,7 +20,10 @@
} }
], ],
"paths": { "paths": {
"@/*": ["./*"] "@/*": ["src/*"],
"@/store/*": ["store/*"],
"@/lib/*": ["src/lib/*"],
"@/hooks/*": ["src/hooks/*"]
} }
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],