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.
|
||||
|
||||
|
||||
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
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 { 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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { LoginPage } from "@/components/aethex/login-page";
|
||||
import App from "../src/App";
|
||||
|
||||
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';
|
||||
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -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
125
package-lock.json
generated
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
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() {
|
||||
// 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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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
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 { Toaster } from "@/components/ui/toaster";
|
||||
import Toaster from "../components/ui/toaster";
|
||||
import "./globals.css";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
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";
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
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 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 (
|
||||
|
|
|
|||
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 { 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>
|
||||
);
|
||||
|
|
|
|||
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 * 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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
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 { 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
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[]) {
|
||||
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;
|
||||
}
|
||||
|
||||
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),
|
||||
}));
|
||||
},
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@
|
|||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
"@/*": ["src/*"],
|
||||
"@/store/*": ["store/*"],
|
||||
"@/lib/*": ["src/lib/*"],
|
||||
"@/hooks/*": ["src/hooks/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
|
|
|
|||
Loading…
Reference in a new issue