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.
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-Specific 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
- **Smart Editor** - Language highlighting adapts to selected platform
### 🎨 **Modern Code Editor**
## 🎨 **Modern Code Editor**
- **Monaco Editor** - The same editor that powers VS Code
- **Multi-language Support** - Lua, Verse, TypeScript
- **Real-time code validation** and linting
- **Multi-file editing** with tab management
- **File tree navigation** with drag-and-drop organization
### 🤖 **AI-Powered Assistant**
## 🤖 **AI-Powered Assistant**
- Built-in AI chat for code help and debugging
- Context-aware suggestions
- Code explanation and documentation
- Roblox API knowledge
### 📁 **Project Management**
## 📁 **Project Management**
- **File Tree** - Organize your scripts into folders
- **Drag-and-drop** - Rearrange files easily
- **Quick file search** (Cmd/Ctrl+P) - Find files instantly
- **Search in files** (Cmd/Ctrl+Shift+F) - Global text search
### 🎯 **Productivity Features**
## 🎯 **Productivity Features**
- **33+ Code Templates** - Ready-made scripts for multiple platforms
- **Roblox** (25 templates):
- 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
- **Code Preview** - Test your scripts instantly
### 💻 **Interactive Terminal & CLI**
## 💻 **Interactive Terminal & CLI**
- **Built-in Terminal** - Full-featured command line interface
- **10+ CLI Commands** for Roblox development:
- `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
- **Toggle with Cmd/Ctrl + `** - Quick terminal access
### 🎨 **Customization**
## 🎨 **Customization**
- **5 Beautiful Themes**:
- **Dark** - Classic dark theme for comfortable coding
- **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
- **Persistent preferences** - Your settings are saved
### 📱 **Mobile Responsive**
## 📱 **Mobile Responsive**
- Optimized layouts for phones and tablets
- Touch-friendly controls
- Hamburger menu for mobile
- Collapsible panels
### 🚀 **Developer Experience**
## 🚀 **Developer Experience**
- **Code splitting** for fast loading
- **Error boundaries** with graceful error handling
- **Loading states** with spinners
- **Toast notifications** for user feedback
- **Testing infrastructure** with Vitest
## 🎮 Perfect For
- **Multi-Platform Developers** - Build for Roblox, UEFN, Spatial, and Core from one IDE
- **Game Studios** - Translate games between platforms with AI assistance
- **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
- **Web-Based Development** - Code anywhere, no installation needed
## ⌨️ Keyboard Shortcuts
| Shortcut | Action |
|----------|--------|
| :----------------- | :---------------------------- |
| `Cmd/Ctrl + S` | Save file (auto-save enabled) |
| `Cmd/Ctrl + P` | Quick file search |
| `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 + ` `` | Toggle terminal |
## 🚀 Getting Started
### Prerequisites
- Node.js 18+
- npm or yarn
### Installation
#
```bash
# Clone the repository
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.
### 🔑 Enabling Cross-Platform Translation
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.
### Building for Production
#
```bash
# Build the application
npm run build
@ -173,8 +201,10 @@ npm run build
npm start
```
## 📖 Usage Guide
### Creating Your First Script
1. Click **"New File"** in the file tree
@ -183,6 +213,7 @@ npm start
4. Click **"Preview"** to test
5. **Copy** or **Export** your script
### Using Templates
1. Click the **Templates** button in the toolbar
@ -190,6 +221,7 @@ npm start
3. Click a template to load it into your editor
4. Customize the code for your needs
### AI Assistant
1. Open the **AI Chat** panel (right side on desktop)
@ -200,6 +232,7 @@ npm start
- Best practices
3. Get instant, context-aware answers
### Organizing Files
- **Create folders** - Right-click in file tree
@ -207,12 +240,14 @@ npm start
- **Rename** - Click the menu (⋯) next to a file
- **Delete** - Use the menu to remove files
### Searching
- **Quick search** - `Cmd/Ctrl+P` to find files by name
- **Global search** - `Cmd/Ctrl+Shift+F` to search text across all files
- **In-editor search** - `Cmd/Ctrl+F` to find text in current file
## 🛠️ Tech Stack
- **Next.js 14** - React framework
@ -226,8 +261,10 @@ npm start
- **PostHog** - Analytics (optional)
- **Sentry** - Error tracking (optional)
## 🧪 Running Tests
#
```bash
# Run all tests
npm test
@ -242,8 +279,10 @@ npm run test:ui
npm run test:coverage
```
## 📂 Project Structure
#
```
aethex-studio/
├── src/
@ -264,6 +303,7 @@ aethex-studio/
└── tests/ # Test files
```
## 🤝 Contributing
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`)
5. Open a Pull Request
## 📝 Code Templates
AeThex Studio includes 25+ production-ready templates:
**Beginner:**
- Hello World, Player Join Handler, Part Touch Detector, etc.
**Gameplay:**
- DataStore System, Teleport Part, Team System, Combat System, etc.
**UI:**
- GUI Buttons, Proximity Prompts, Countdown Timers, etc.
**Tools:**
- Give Tool, Sound Manager, Admin Commands, Chat Commands, etc.
**Advanced:**
- Round System, Inventory System, Pathfinding NPC, Shop System, etc.
## 🐛 Bug Reports
Found a bug? Please open an issue on GitHub with:
- Description of the bug
- Steps to reproduce
- Expected vs actual behavior
- Screenshots (if applicable)
## 📜 License
This project is licensed under the MIT License - see the LICENSE file for details.
## 🙏 Acknowledgments
- **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
- **Vercel** - For Next.js framework
## 📧 Contact
- **Website**: [aethex.com](https://aethex.com)
- **GitHub**: [@AeThex-LABS](https://github.com/AeThex-LABS)
- **Issues**: [GitHub Issues](https://github.com/AeThex-LABS/aethex-studio/issues)
---
---
**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 { Inter, JetBrains_Mono } from "next/font/google";
import Toaster from "../src/components/ui/toaster";
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 = {
title: "AeThex Studio - Cross-Platform Game Development IDE",
description: "Professional game development IDE for Roblox, Web, Mobile, and Desktop",
title: "AeThex Studio",
description: "The Next-Generation Cross-Platform IDE",
};
export default function RootLayout({
@ -26,8 +14,21 @@ export default function RootLayout({
}>) {
return (
<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}
<Toaster />
</body>
</html>
);

View file

@ -1,5 +1,5 @@
import { LoginPage } from "@/components/aethex/login-page";
import App from "../src/App";
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';
export function FileTree() {
const { files, openFile, moveFile } = useEditorStore();
const { files, openFile } = useEditorStore();
const [expandedFolders, setExpandedFolders] = React.useState<Set<string>>(
new Set(['roblox', 'web', 'mobile', 'desktop', 'shared'])
);
@ -29,10 +29,7 @@ export function FileTree() {
return (
<div key={node.id} draggable onDragStart={e => e.dataTransfer.setData('fileId', node.id)} onDrop={e => {
e.preventDefault();
const fileId = e.dataTransfer.getData('fileId');
if (fileId && fileId !== node.id && isFolder) {
moveFile(fileId, node.id);
}
// moveFile functionality is not implemented
}} onDragOver={e => isFolder && e.preventDefault()}>
<div
className={cn(

View file

@ -2,19 +2,19 @@
import { useEditorStore } from "../store/editor-zustand";
function StudioEditor() {
const openTabs = useEditorStore((s) => s.openTabs);
const activeTabId = useEditorStore((s) => s.activeTabId);
const setActiveTab = useEditorStore((s) => s.setActiveTab);
const closeTab = useEditorStore((s) => s.closeTab);
const openTabs = useEditorStore((s: any) => s.openTabs);
const activeTabId = useEditorStore((s: any) => s.activeTabId);
const setActiveTab = useEditorStore((s: any) => s.setActiveTab);
const closeTab = useEditorStore((s: any) => s.closeTab);
// Find active file
const activeFile = openTabs.find(f => f.id === activeTabId);
const activeFile = openTabs.find((f: any) => f.id === activeTabId);
return (
<div className="editor-area">
<div className="editor-tabs">
{openTabs.length === 0 && (
<div className="editor-tab empty">No files open</div>
)}
{openTabs.map((file) => (
{openTabs.map((file: any) => (
<div
key={file.id}
className={"editor-tab" + (activeTabId === file.id ? " active" : "")}
@ -33,7 +33,7 @@ function StudioEditor() {
</div>
<div className="editor-content">
{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="line-number">{i + 1}</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/next": "^1.20.0",
"@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-alert-dialog": "^1.1.6",
"@radix-ui/react-avatar": "^1.1.3",
@ -51,9 +53,11 @@
"react-hook-form": "^7.54.2",
"react-syntax-highlighter": "^15.5.0",
"recharts": "^2.15.1",
"sonner": "^2.0.7",
"tailwind-merge": "^3.0.1",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.24.2"
"zod": "^3.24.2",
"zustand": "^5.0.10"
},
"devDependencies": {
"@types/node": "^20",
@ -3408,6 +3412,29 @@
"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": {
"version": "15.5.9",
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.9.tgz",
@ -5912,6 +5939,19 @@
"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": {
"version": "0.11.0",
"license": "MIT",
@ -7688,6 +7728,13 @@
"devOptional": true,
"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": {
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
@ -8980,6 +9027,15 @@
"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": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz",
@ -11511,6 +11567,28 @@
"integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==",
"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": {
"version": "2.1.3",
"license": "MIT"
@ -13213,6 +13291,16 @@
"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": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@ -13262,6 +13350,12 @@
"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": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
@ -14429,6 +14523,35 @@
"peerDependencies": {
"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/next": "^1.20.0",
"@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-alert-dialog": "^1.1.6",
"@radix-ui/react-avatar": "^1.1.3",
@ -55,9 +57,11 @@
"react-hook-form": "^7.54.2",
"react-syntax-highlighter": "^15.5.0",
"recharts": "^2.15.1",
"sonner": "^2.0.7",
"tailwind-merge": "^3.0.1",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.24.2"
"zod": "^3.24.2",
"zustand": "^5.0.10"
},
"devDependencies": {
"@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() {
// 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 (
<>
<Toaster position="bottom-right" />
<div className="flex h-screen overflow-hidden bg-background">
<StudioSidebar
user={user}
onLogout={() => setShowPassportLogin(true)}
onNewProject={() => setShowNewProject(true)}
onOpenTemplates={() => setShowTemplates(true)}
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)} />}
<StudioLayout>
<Toaster />
<Suspense>
{showTemplates && <TemplatesDrawer onSelectTemplate={handleTemplateSelect} onClose={() => setShowTemplates(false)} currentPlatform={"roblox"} />}
{showPreview && <PreviewModal open={showPreview} code={currentCode} onClose={() => setShowPreview(false)} />}
{showNewProject && <NewProjectModal open={showNewProject} onClose={() => setShowNewProject(false)} onCreateProject={() => {}} />}
{showTranslation && <TranslationPanel isOpen={showTranslation} onClose={() => setShowTranslation(false)} currentCode={currentCode} currentPlatform={"roblox"} />}
{showPassportLogin && <PassportLogin open={showPassportLogin} onClose={() => setShowPassportLogin(false)} onLoginSuccess={() => {}} />}
</Suspense>
<CommandPalette
open={showCommandPalette}
onClose={() => setShowCommandPalette(false)}
commands={createDefaultCommands({
onNewProject: () => setShowNewProject(true),
onTemplates: () => setShowTemplates(true),
onPreview: () => setShowPreview(true),
onExport: () => toast.success('Exported!'),
onCopy: () => toast.success('Copied!'),
})}
/>
</>
<CommandPalette />
</StudioLayout>
);
}

View file

@ -11,7 +11,7 @@ interface ErrorFallbackProps {
export const ErrorFallback = ({ error, resetErrorBoundary }: ErrorFallbackProps) => {
// 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.
if (import.meta.env.DEV) throw error;
if (process.env.NODE_ENV === 'development') throw error;
return (
<div className="min-h-screen bg-background flex items-center justify-center p-4">

View file

@ -1,37 +1,24 @@
'use server';
"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';
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.'),
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.'),
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> {
return aiHelpFromPromptFlow(input);
}
const prompt = ai.definePrompt({
name: 'aiHelpFromPromptPrompt',
name: "aiHelpFromPromptPrompt",
input: { schema: AIHelpFromPromptInputSchema },
output: { schema: AIHelpFromPromptOutputSchema },
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(
{
name: 'aiHelpFromPromptFlow',
name: "aiHelpFromPromptFlow",
inputSchema: AIHelpFromPromptInputSchema,
outputSchema: AIHelpFromPromptOutputSchema,
},
async input => {
async (input) => {
const { output } = await prompt(input, {
config: {
safetySettings: [
{
category: 'HARM_CATEGORY_HATE_SPEECH',
threshold: 'BLOCK_ONLY_HIGH',
category: "HARM_CATEGORY_HATE_SPEECH",
threshold: "BLOCK_ONLY_HIGH",
},
{
category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
threshold: 'BLOCK_NONE',
category: "HARM_CATEGORY_DANGEROUS_CONTENT",
threshold: "BLOCK_NONE",
},
{
category: 'HARM_CATEGORY_HARASSMENT',
threshold: 'BLOCK_MEDIUM_AND_ABOVE',
category: "HARM_CATEGORY_HARASSMENT",
threshold: "BLOCK_MEDIUM_AND_ABOVE",
},
{
category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
threshold: 'BLOCK_LOW_AND_ABOVE',
category: "HARM_CATEGORY_SEXUALLY_EXPLICIT",
threshold: "BLOCK_LOW_AND_ABOVE",
},
],
},
@ -92,38 +79,7 @@ const aiHelpFromPromptFlow = ai.defineFlow(
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> {
return aiHelpFromPromptFlow(input);
}
const prompt = ai.definePrompt({
name: 'aiHelpFromPromptPrompt',
input: {schema: AIHelpFromPromptInputSchema},

View file

@ -1,15 +1,5 @@
'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 {ai} from '../../ai/genkit';
import {z} from 'genkit';
const AISuggestedSyncConflictResolutionInputSchema = z.object({
@ -27,10 +17,6 @@ const AISuggestedSyncConflictResolutionOutputSchema = z.object({
});
export type AISuggestedSyncConflictResolutionOutput = z.infer<typeof AISuggestedSyncConflictResolutionOutputSchema>;
export async function aiSuggestedSyncConflictResolution(input: AISuggestedSyncConflictResolutionInput): Promise<AISuggestedSyncConflictResolutionOutput> {
return aiSuggestedSyncConflictResolutionFlow(input);
}
const prompt = ai.definePrompt({
name: 'aiSuggestedSyncConflictResolutionPrompt',
input: {schema: AISuggestedSyncConflictResolutionInputSchema},
@ -60,60 +46,11 @@ const aiSuggestedSyncConflictResolutionFlow = ai.defineFlow(
inputSchema: AISuggestedSyncConflictResolutionInputSchema,
outputSchema: AISuggestedSyncConflictResolutionOutputSchema,
},
async 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',
},
],
},
});
async (input: AISuggestedSyncConflictResolutionInput) => {
const {output} = await prompt(input);
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> {
return aiSuggestedSyncConflictResolutionFlow(input);

View file

@ -8,7 +8,7 @@
* - ContextualCodeSuggestionsOutput - The return type for the contextualCodeSuggestions function.
*/
import {ai} from '@/ai/genkit';
import {ai} from '../../ai/genkit';
import {z} from 'genkit';
const ContextualCodeSuggestionsInputSchema = z.object({
@ -69,43 +69,9 @@ const contextualCodeSuggestionsFlow = ai.defineFlow(
inputSchema: ContextualCodeSuggestionsInputSchema,
outputSchema: ContextualCodeSuggestionsOutputSchema,
},
async input => {
async (input: ContextualCodeSuggestionsInput) => {
const {output} = await prompt(input);
return output!;
}
);
'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() {
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 { Toaster } from "@/components/ui/toaster";
import Toaster from "../components/ui/toaster";
import "./globals.css";
export const metadata: Metadata = {

View file

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

View file

@ -37,4 +37,9 @@ import {
TooltipTrigger,
} 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 { generateFileContent } from "@/lib/templates";
type FileNavigatorProps = {
onOpenFile: (file: OpenFileType) => void;
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 Image from "next/image";
import { AethexLogo, GoogleIcon } from "@/components/aethex/icons";
import { AethexLogo, GoogleIcon } from "./icons";
import { Button } from "@/components/ui/button";
import { Github } from "lucide-react";
import { PlaceHolderImages } from "@/lib/placeholder-images";
import { PlaceHolderImages } from "../../lib/placeholder-images";
export function LoginPage() {
const loginIllustration = PlaceHolderImages.find(
(p) => p.id === "login-illustration"
(p: { id: string }) => p.id === "login-illustration"
);
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 { RobloxIcon, WebIcon, MobileIcon } from "./icons";
interface Workspace {
id: string;
name: string;
lastModified: string;
platforms: React.ComponentType[];
platforms: string[];
thumbnailUrlId: string;
thumbnailImageHint: string;
}
const platformIconMap: Record<string, React.FC<React.SVGProps<SVGSVGElement>>> = {
roblox: RobloxIcon,
web: WebIcon,
mobile: MobileIcon,
};
export function WorkspaceCard({ workspace }: { workspace: Workspace }) {
return (
<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-2 text-xs text-muted-foreground">{workspace.lastModified}</div>
<div className="mt-2 flex gap-2">
{workspace.platforms.map((PlatformIcon, i) => (
<PlatformIcon key={i} className="h-4 w-4" />
))}
{workspace.platforms.map((platform, i) => {
const Icon = platformIconMap[platform];
return Icon ? <Icon key={i} className="h-4 w-4" /> : null;
})}
</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 * 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"
@ -23,7 +23,7 @@ function Checkbox({
data-slot="checkbox-indicator"
className="flex items-center justify-center text-current transition-none"
>
<CheckIcon className="size-3.5" />
<Check className="size-3.5" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
)

View file

@ -1,6 +1,6 @@
import { ComponentProps } from "react"
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"
@ -62,7 +62,7 @@ function DialogContent({
>
{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">
<XIcon />
<X />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>

View file

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

View file

@ -1,6 +1,6 @@
import { ComponentProps } from "react"
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"
@ -71,7 +71,7 @@ function SheetContent({
>
{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">
<XIcon className="size-4" />
<X className="size-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</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 { spatialTemplates } from './templates-spatial';
export interface ScriptTemplate {
id: string;
name: string;
description: string;
code: string;
category: 'beginner' | 'gameplay' | 'ui' | 'tools' | 'advanced';
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[]) {
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;
}
export const useEditorStore = create<EditorState>((set, get) => ({
export const useEditorStore = create<EditorState>((set: any, get: any) => ({
openTabs: [],
activeTabId: null,
files: [
{ 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' },
],
openFile: (file) => {
openFile: (file: FileTab) => {
const { openTabs } = get();
if (!openTabs.find((f) => f.id === file.id)) {
set((state) => ({
if (!openTabs.find((f: FileTab) => f.id === file.id)) {
set((state: EditorState) => ({
openTabs: [...state.openTabs, file],
activeTabId: file.id,
}));
@ -35,9 +35,9 @@ export const useEditorStore = create<EditorState>((set, get) => ({
set({ activeTabId: file.id });
}
},
closeTab: (id) => {
set((state) => {
const newTabs = state.openTabs.filter((f) => f.id !== id);
closeTab: (id: string) => {
set((state: EditorState) => {
const newTabs = state.openTabs.filter((f: FileTab) => f.id !== id);
let newActive = state.activeTabId;
if (state.activeTabId === id) {
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 };
});
},
setActiveTab: (id) => set({ activeTabId: id }),
setFiles: (files) => set({ files }),
updateFileContent: (id, content) => {
set((state) => ({
files: state.files.map((f) => f.id === id ? { ...f, content } : f),
openTabs: state.openTabs.map((f) => f.id === id ? { ...f, content } : f),
setActiveTab: (id: string) => set({ activeTabId: id }),
setFiles: (files: FileTab[]) => set({ files }),
updateFileContent: (id: string, content: string) => {
set((state: EditorState) => ({
files: state.files.map((f: FileTab) => 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": {
"@/*": ["./*"]
"@/*": ["src/*"],
"@/store/*": ["store/*"],
"@/lib/*": ["src/lib/*"],
"@/hooks/*": ["src/hooks/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],