new file: app/ide/page.tsx
This commit is contained in:
parent
4bc31a32e2
commit
ea5ba62c54
48 changed files with 567 additions and 417 deletions
79
README.md
79
README.md
|
|
@ -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
4
app/dashboard/page.tsx
Normal 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
4
app/ide/page.tsx
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
import App from "../../src/App";
|
||||||
|
export default function Page() {
|
||||||
|
return <App />;
|
||||||
|
}
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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 />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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
125
package-lock.json
generated
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
122
src/App.tsx
122
src/App.tsx
|
|
@ -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!'),
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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},
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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> {
|
|
||||||
|
|
|
||||||
|
|
@ -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
34
src/app/landing-page.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
src/components/StudioBottomPanel.tsx
Normal file
1
src/components/StudioBottomPanel.tsx
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export default function StudioBottomPanel() { return <footer>Bottom Panel</footer>; }
|
||||||
1
src/components/StudioEditor.tsx
Normal file
1
src/components/StudioEditor.tsx
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export default function StudioEditor() { return <div>Editor</div>; }
|
||||||
1
src/components/StudioNetworkViz.tsx
Normal file
1
src/components/StudioNetworkViz.tsx
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export default function StudioNetworkViz() { return <div>Network Viz</div>; }
|
||||||
1
src/components/StudioRightPanel.tsx
Normal file
1
src/components/StudioRightPanel.tsx
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export default function StudioRightPanel() { return <aside>Right Panel</aside>; }
|
||||||
1
src/components/StudioSidebar.tsx
Normal file
1
src/components/StudioSidebar.tsx
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export default function StudioSidebar() { return <aside>Sidebar</aside>; }
|
||||||
1
src/components/Suspense.tsx
Normal file
1
src/components/Suspense.tsx
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export default function Suspense({ children }: { children: React.ReactNode }) { return <>{children}</>; }
|
||||||
2
src/components/aethex/aethex-studio.d.ts
vendored
Normal file
2
src/components/aethex/aethex-studio.d.ts
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
// TypeScript declaration for AethexStudio module
|
||||||
|
export * from "./aethex-studio";
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
1
src/components/aethex/index.ts
Normal file
1
src/components/aethex/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { AethexStudio } from "./aethex-studio";
|
||||||
1
src/components/aethex/index.tsx
Normal file
1
src/components/aethex/index.tsx
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { AethexStudio } from "./aethex-studio";
|
||||||
|
|
@ -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 (
|
||||||
|
|
|
||||||
1
src/components/aethex/main-view.tsx
Normal file
1
src/components/aethex/main-view.tsx
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export function MainView() { return <main>Main View (stub)</main>; }
|
||||||
1
src/components/aethex/navbar.tsx
Normal file
1
src/components/aethex/navbar.tsx
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export function Navbar() { return <nav>Navbar (stub)</nav>; }
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
1
src/components/ui/CommandPalette.tsx
Normal file
1
src/components/ui/CommandPalette.tsx
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export default function CommandPalette() { return <div>Command Palette</div>; }
|
||||||
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
3
src/components/ui/toaster.tsx
Normal file
3
src/components/ui/toaster.tsx
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export default function Toaster() {
|
||||||
|
return <div>Toaster (stub)</div>;
|
||||||
|
}
|
||||||
11
src/hooks/use-toast.ts
Normal file
11
src/hooks/use-toast.ts
Normal 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
1
src/lib/captureEvent.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export function captureEvent(event: string) { console.log('Event:', event); }
|
||||||
|
|
@ -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
1
src/lib/toast.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export const toast = { success: (msg: string) => { console.log('Toast:', msg); } }
|
||||||
|
|
@ -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
1
src/lib/utils/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./utils";
|
||||||
|
|
@ -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),
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -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"],
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue