From 8d6ebeba3594fb1b86c9a778a1e29a18881b665a Mon Sep 17 00:00:00 2001 From: MrPiglr Date: Sat, 17 Jan 2026 03:02:17 +0000 Subject: [PATCH] Generated by Spark: Create a cloud-based game development IDE called "AeThex Studio" using Next.js and Tailwind. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Layout: Top navbar: AeThex logo (left), project name (center), user menu with avatar (right) Left sidebar (250px): File tree navigator with folders/files, "New File" and "New Folder" buttons Center: Code editor area (use Monaco Editor component if available, or textarea with syntax highlighting) Right sidebar (300px, collapsible): AI Assistant chat panel with message history Bottom panel (200px, collapsible): Console output and terminal Features: File tree supports create/rename/delete operations Tabs for multiple open files Syntax highlighting for Lua, JavaScript, TypeScript Dark theme with purple/cyan accent colors (futuristic gaming aesthetic) Split panes are resizable Auto-save indicator in navbar Use TypeScript and proper state management. Make it feel like VS Code but purpose-built for game development. Create a unified game development workspace that shows the same game running on three platforms simultaneously: Left panel: Roblox viewport (embed mockup) Center panel: Web browser viewport with game canvas Right panel: Mobile device mockup with game Include: Shared state indicator showing synced variables (player position, score, inventory) Platform-specific code tabs that show Lua (Roblox), JavaScript (Web), and React Native (Mobile) "Deploy to Platform" buttons for each Sync status indicator (green = synced, yellow = syncing, red = conflict) Console output panel at bottom showing platform-specific logs Use a dark IDE-like theme. Make it feel like a professional developer tool. Add a "New Project" modal to the AeThex Studio IDE: Modal triggered by "New Project" button in navbar: Step 1: Choose template (card grid layout) Roblox Game Starter Cross-Platform Multiplayer Transmedia Story Project Blank Project Each card shows icon, title, description, and "Popular" badge where applicable Step 2: Project configuration form Project name input Platform checkboxes (Roblox, Web, Mobile) Enable Nexus Engine toggle (with tooltip explaining state sync) Enable Passport Auth toggle (with tooltip explaining unified identity) Step 3: Review summary and "Create Project" button After creation, populate file tree with template structure and open main file in editor. Use a stepper UI component to show progress. Modern, clean design. Build the AI Assistant chat panel for the right sidebar: UI Components: Header: "AeThex AI Assistant" with model selector dropdown (Claude Sonnet, GPT-4o) Chat message history (scrollable, auto-scroll to bottom) Messages alternate left (user) and right (AI) with avatars Code blocks in messages with syntax highlighting and copy button Input textarea at bottom with "Send" button Token usage indicator (shows X / 500K tokens used this month) Quick Actions (buttons above input): "Explain selected code" "Add comments" "Convert to cross-platform" "Generate tests" Message Types: User messages: blue background AI messages: dark gray background with purple border System messages: small, centered, gray text (e.g., "Code inserted into editor") Features: When AI generates code, show "Insert into editor" button Track conversation context (include current file, project structure) Show typing indicator when AI is responding Make the UX smooth and polished. Create a preview panel modal that opens when user clicks "Preview" in navbar: Layout (fullscreen modal with dark backdrop): Close button (top right) Tab navigation: "Roblox" | "Web" | "Mobile" | "All Platforms" "All Platforms" tab shows three columns: Left: Roblox viewport (iframe placeholder with Roblox logo) Center: Web browser viewport (iframe with game canvas) Right: Mobile device mockup (iPhone frame with game view) Shared State Indicator (floating panel at bottom): Shows synced variables in real-time table: Variable | Roblox | Web | Mobile | Status playerX | 120 | 120 | 120 | ✓ Synced health | 100 | 98 | 100 | ⚠ Syncing Color coded: Green (synced), Yellow (syncing), Red (conflict) Controls: "Refresh All" button Individual "Deploy to [Platform]" buttons Latency simulator slider (0-500ms) Use glassmorphism UI style with blur effects. This should look futuristic and impressive. Add user authentication and workspace management: Login page: AeThex logo and tagline "Sign in with AeThex Passport" button (purple gradient) "Sign in with Google" button "Sign in with GitHub" button Illustration of cross-platform game development Dashboard (after login, before opening project): Header: "My Workspaces" with "New Workspace" button Grid of workspace cards showing: Project name Last modified timestamp Platform badges (Roblox, Web, Mobile icons) Thumbnail/preview image Three-dot menu (Rename, Delete, Share) Empty state if no workspaces: "Create your first game" CTA with template previews Each workspace card is clickable and opens the IDE with that project loaded. Use modern card design with hover effects. Show loading skeletons while fetching data. --- PRD.md | 164 +++++++------ src/App.tsx | 248 +++++++++++++++++-- src/components/ConsolePanel.tsx | 193 +++++++++++++++ src/components/FileTabs.tsx | 51 ++++ src/components/FileTree.tsx | 187 +++++++++++++++ src/components/NewProjectModal.tsx | 370 +++++++++++++++++++++++++++++ src/components/PreviewModal.tsx | 207 ++++++++++++++++ src/components/Toolbar.tsx | 83 ++++++- 8 files changed, 1413 insertions(+), 90 deletions(-) create mode 100644 src/components/ConsolePanel.tsx create mode 100644 src/components/FileTabs.tsx create mode 100644 src/components/FileTree.tsx create mode 100644 src/components/NewProjectModal.tsx create mode 100644 src/components/PreviewModal.tsx diff --git a/PRD.md b/PRD.md index 3d4acbb..91df01a 100644 --- a/PRD.md +++ b/PRD.md @@ -1,61 +1,70 @@ # Planning Guide -AeThex Studio is a browser-based Roblox Lua script editor with integrated AI assistance for rapid prototyping and learning. +AeThex Studio is a browser-based cross-platform game development IDE with file management, multi-platform preview, AI assistance, and project templates. **Experience Qualities**: -1. **Professional** - Should feel like a legitimate development tool with clean typography and purposeful spacing -2. **Intelligent** - AI assistance should feel seamlessly integrated, not bolted on -3. **Empowering** - Users should feel confident writing and understanding Lua code +1. **Professional** - Should feel like a complete IDE with file navigation, tabs, and split panels like VS Code +2. **Intelligent** - AI assistance deeply integrated with context awareness and code suggestions +3. **Cross-Platform** - Unified workspace showing Roblox, Web, and Mobile development simultaneously -**Complexity Level**: Light Application (multiple features with basic state) -This is a focused code editor with AI chat, script templates, and syntax validation - not a full-featured IDE with file systems, debugging, or deployment pipelines. +**Complexity Level**: Complex Application (advanced functionality with multiple views and state management) +This is now a full-featured IDE with file tree navigation, multiple open files, tabbed editor, AI chat panel, console output, multi-platform preview, and project templates - creating a complete game development workspace. ## Essential Features -### Monaco Code Editor -- **Functionality**: Full-featured Lua code editor with syntax highlighting, autocomplete, and line numbers -- **Purpose**: Provide a professional coding environment for Roblox scripting -- **Trigger**: Loads on app start with default template -- **Progression**: User opens app → sees editor with starter code → begins typing → receives syntax highlighting and autocomplete -- **Success criteria**: Code is editable, syntax-highlighted, and changes persist between sessions +### File Tree Navigator +- **Functionality**: Left sidebar showing folder/file hierarchy with create, rename, and delete operations +- **Purpose**: Organize multiple scripts and manage project structure like a real IDE +- **Trigger**: Always visible on desktop, accessible via tabs on mobile +- **Progression**: User clicks "New File" → enters name → new file appears in tree → clicks to open +- **Success criteria**: Files persist between sessions, operations work smoothly, selected file highlights -### AI Chat Assistant -- **Functionality**: Conversational AI panel that helps explain code, debug issues, and suggest improvements -- **Purpose**: Lower the learning curve for new Roblox developers -- **Trigger**: User clicks chat button or types a question in the AI panel -- **Progression**: User asks question → AI analyzes current code → provides contextual answer with code examples -- **Success criteria**: AI responses are relevant, helpful, and include code snippets when appropriate +### Multi-File Tabs +- **Functionality**: Horizontal tabs showing open files with close buttons +- **Purpose**: Work with multiple scripts simultaneously without losing context +- **Trigger**: Opening a file from tree adds a tab +- **Progression**: User opens file → tab appears → clicks tab to switch → clicks X to close +- **Success criteria**: Active tab highlighted, tab state persists, smooth switching -### Script Templates -- **Functionality**: Pre-built Roblox script templates (basic part manipulation, player join events, GUI interactions, etc.) -- **Purpose**: Jump-start development and teach common patterns -- **Trigger**: User clicks "Templates" button -- **Progression**: User browses templates → clicks template → code loads into editor → user modifies for their needs -- **Success criteria**: Templates load instantly and represent common Roblox use cases +### Multi-Platform Preview +- **Functionality**: Modal showing Roblox, Web, and Mobile viewports side-by-side with shared state sync +- **Purpose**: Visualize how game runs across all platforms simultaneously +- **Trigger**: User clicks "Preview" button in toolbar +- **Progression**: Click Preview → modal opens → see three viewport mockups → view synced state table → close modal +- **Success criteria**: Modal is visually impressive with glassmorphism, state sync table updates, deploy buttons present -### Code Validation -- **Functionality**: Real-time syntax checking and error highlighting -- **Purpose**: Catch errors before testing in Roblox Studio -- **Trigger**: Automatic as user types -- **Progression**: User types invalid Lua → error highlights appear → user hovers for details → fixes error -- **Success criteria**: Common Lua syntax errors are caught and clearly displayed +### New Project Wizard +- **Functionality**: 3-step modal for creating projects with templates, platform selection, and feature toggles +- **Purpose**: Quick-start new games with appropriate scaffolding +- **Trigger**: User clicks "New Project" button in toolbar +- **Progression**: Click New Project → choose template → configure settings → review → create → file tree populates +- **Success criteria**: Stepper UI shows progress, all template types available, settings persist -### Export/Copy Code -- **Functionality**: One-click copy of current code to clipboard -- **Purpose**: Easy transfer to Roblox Studio -- **Trigger**: User clicks copy button -- **Progression**: User finishes editing → clicks copy → receives confirmation → pastes into Roblox Studio -- **Success criteria**: Code copies to clipboard with proper formatting +### Console Output Panel +- **Functionality**: Bottom panel showing logs filtered by platform (Roblox/Web/Mobile/System) +- **Purpose**: Display runtime output and errors from all platforms in one place +- **Trigger**: Always visible on desktop (collapsible), shows logs automatically +- **Progression**: Code runs → logs appear → filter by platform → clear logs button +- **Success criteria**: Color-coded messages, platform badges, auto-scroll, clear functionality + +### User Profile Menu +- **Functionality**: Avatar dropdown in toolbar showing GitHub profile with sign-out option +- **Purpose**: Display current user identity and provide account actions +- **Trigger**: Click avatar icon in top-right +- **Progression**: User logged in → avatar shows → click opens menu → see profile info +- **Success criteria**: Fetches real GitHub user via spark.user(), displays avatar and email ## Edge Case Handling -- **Empty Editor**: Show helpful placeholder text with getting started tips +- **Empty File Tree**: Show placeholder with "Create your first file" message +- **No Open Files**: Display welcome message in editor area - **AI Errors**: Display user-friendly error messages if AI service is unavailable -- **Large Scripts**: Monaco handles large files, but warn if script exceeds 5000 lines (unusual for Roblox scripts) -- **Invalid Lua**: Show errors inline without blocking the user from continuing to type -- **Lost Work**: Auto-save code every 30 seconds to prevent data loss +- **Large Scripts**: Monaco handles large files, warn if exceeds 10,000 lines +- **Invalid Operations**: Prevent deleting root folder, renaming to empty string +- **Network Issues**: Show offline indicator if can't reach AI or user services +- **Mobile Layout**: Stack panels vertically with tab navigation for Files/Editor/AI ## Design Direction -The design should evoke a sense of technical sophistication meets creative playfulness - this is a tool for building games, not enterprise software. Think dark themes with neon accents, geometric patterns, and smooth transitions that feel modern and game-inspired. +The design should evoke a professional game development IDE with futuristic gaming aesthetics - dark themes with vibrant cyan/purple accents, glassmorphism effects in modals, and smooth panel animations. This is VS Code meets Unreal Engine editor with a playful gaming twist. ## Color Selection A dark, code-focused theme with vibrant accent colors inspired by Roblox's playful brand and gaming aesthetics. @@ -86,40 +95,49 @@ Animations should feel snappy and technical - quick state transitions with subtl ## Component Selection - **Components**: - - `Button` (primary actions like "Run", "Copy", "Ask AI") with custom variants for destructive and accent states - - `Card` for template previews and code snippets - - `Separator` for dividing editor from chat panel - - `ScrollArea` for chat messages and template list - - `Tabs` for switching between templates and settings - - `Dialog` for first-time user onboarding - - `Badge` for Lua syntax error indicators - - `Tooltip` for icon button explanations - - Custom Monaco Editor wrapper component - - Custom AI chat component with message bubbles + - `Button` with accent variant for primary actions (New Project, Preview) + - `Card` for template previews, project configs, and preview mockups + - `Dialog` for Preview Modal, New Project Modal, About dialog + - `DropdownMenu` for file context menus and user profile menu + - `Avatar` for user profile display + - `ScrollArea` for file tree, chat messages, console logs + - `Tabs` for template categories, preview modes, console filters, mobile navigation + - `Input` for file rename, project name entry + - `Checkbox` for platform selection in new project + - `Switch` for Nexus Engine and Passport Auth toggles + - `Badge` for popular templates, platform indicators, status labels + - `Tooltip` for button explanations and feature descriptions + - `ResizablePanel` for adjustable sidebar and panel widths + - Custom Monaco Editor wrapper + - Custom FileTree with nested folders + - Custom FileTabs with close buttons + - Custom ConsolePanel with platform filtering - **Customizations**: - - Monaco editor needs dark theme configuration - - Chat bubbles with distinct styling for user vs AI messages - - Template cards with code preview using syntax-highlighted pre blocks - - Resizable panels using react-resizable-panels + - Monaco editor with dark theme configuration + - File tree with expand/collapse animations + - Preview modal with glassmorphism effects (backdrop-blur) + - Multi-platform viewport mockups with device frames + - Shared state sync table with color-coded status + - Console with platform-specific badge colors - **States**: - - Buttons: Default (solid accent), Hover (brightened +10% lightness), Active (scale 0.97), Disabled (50% opacity) - - Editor: Default (dark background), Focused (subtle glow border) - - Chat input: Default (muted border), Focused (accent border with glow) + - Buttons: Accent primary (New Project), Ghost secondary (Templates, Copy) + - File items: Selected (accent background), Hover (muted background) + - Tabs: Active (accent bottom border), Inactive (transparent) + - Console logs: Color by type (red error, yellow warn, blue info) - **Icon Selection**: - - Code (editor): `` from Phosphor - - Chat: `` from Phosphor - - Copy: `` from Phosphor - - Templates: `` from Phosphor - - AI sparkle: `` from Phosphor - - Play/Run: `` from Phosphor + - File tree: ``, ``, ``, ``, `` + - Toolbar: `` (New Project), `` (Preview), `` (Profile) + - Preview: `` (Refresh), ``, ``, `` + - Console: `` (Console icon) + - All from Phosphor Icons - **Spacing**: - - Container padding: p-4 (16px) - - Component gaps: gap-4 (16px) for major sections, gap-2 (8px) for grouped items - - Button padding: px-6 py-2.5 - - Card padding: p-6 + - Container padding: p-4 + - Panel borders: border-border (1px) + - Component gaps: gap-4 for sections, gap-2 for grouped items + - File tree indent: 12px per level - **Mobile**: - - Stack editor and chat vertically on mobile - - Make chat panel collapsible on small screens - - Reduce font sizes: H1 to 24px, body to 13px - - Full-width buttons with bottom sheet for templates - - Hide Monaco minimap on screens < 768px + - Tabs for Files/Editor/AI switching on mobile + - File tree full-screen on mobile + - Hide console panel on mobile + - Preview modal scrollable on small screens + - Stepper in New Project modal scales down diff --git a/src/App.tsx b/src/App.tsx index 0831574..0457f36 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,52 +5,260 @@ import { AIChat } from '@/components/AIChat'; import { Toolbar } from '@/components/Toolbar'; import { TemplatesDrawer } from '@/components/TemplatesDrawer'; import { WelcomeDialog } from '@/components/WelcomeDialog'; +import { FileTree, FileNode } from '@/components/FileTree'; +import { FileTabs } from '@/components/FileTabs'; +import { PreviewModal } from '@/components/PreviewModal'; +import { NewProjectModal, ProjectConfig } from '@/components/NewProjectModal'; +import { ConsolePanel } from '@/components/ConsolePanel'; import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@/components/ui/resizable'; import { useKV } from '@github/spark/hooks'; import { useIsMobile } from '@/hooks/use-mobile'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { toast } from 'sonner'; function App() { const [currentCode, setCurrentCode] = useState(''); const [showTemplates, setShowTemplates] = useState(false); + const [showPreview, setShowPreview] = useState(false); + const [showNewProject, setShowNewProject] = useState(false); const [code, setCode] = useKV('aethex-current-code', ''); const isMobile = useIsMobile(); + const [files, setFiles] = useKV('aethex-files', [ + { + id: 'root', + name: 'src', + type: 'folder', + children: [ + { + id: 'file-1', + name: 'script.lua', + type: 'file', + content: `-- Welcome to AeThex Studio! +-- Write your Roblox Lua code here + +local Players = game:GetService("Players") + +Players.PlayerAdded:Connect(function(player) + print(player.Name .. " joined the game!") + + local leaderstats = Instance.new("Folder") + leaderstats.Name = "leaderstats" + leaderstats.Parent = player + + local coins = Instance.new("IntValue") + coins.Name = "Coins" + coins.Value = 0 + coins.Parent = leaderstats +end)`, + }, + ], + }, + ]); + + const [openFiles, setOpenFiles] = useKV('aethex-open-files', []); + const [activeFileId, setActiveFileId] = useKV('aethex-active-file', 'file-1'); + const handleTemplateSelect = (templateCode: string) => { setCode(templateCode); setCurrentCode(templateCode); }; + const handleFileSelect = (file: FileNode) => { + if (file.type === 'file') { + setActiveFileId(file.id); + if (!(openFiles || []).find((f) => f.id === file.id)) { + setOpenFiles((prev) => [...(prev || []), file]); + } + setCode(file.content || ''); + setCurrentCode(file.content || ''); + } + }; + + const handleFileCreate = (name: string, parentId?: string) => { + const newFile: FileNode = { + id: `file-${Date.now()}`, + name: name.endsWith('.lua') ? name : `${name}.lua`, + type: 'file', + content: '-- New file\n', + }; + + setFiles((prev) => { + const addToFolder = (nodes: FileNode[]): FileNode[] => { + return nodes.map((node) => { + if (node.id === 'root' && !parentId) { + return { + ...node, + children: [...(node.children || []), newFile], + }; + } + if (node.id === parentId && node.type === 'folder') { + return { + ...node, + children: [...(node.children || []), newFile], + }; + } + if (node.children) { + return { ...node, children: addToFolder(node.children) }; + } + return node; + }); + }; + return addToFolder(prev || []); + }); + + toast.success(`Created ${newFile.name}`); + }; + + const handleFileRename = (id: string, newName: string) => { + setFiles((prev) => { + const rename = (nodes: FileNode[]): FileNode[] => { + return nodes.map((node) => { + if (node.id === id) { + return { ...node, name: newName }; + } + if (node.children) { + return { ...node, children: rename(node.children) }; + } + return node; + }); + }; + return rename(prev || []); + }); + }; + + const handleFileDelete = (id: string) => { + setFiles((prev) => { + const deleteNode = (nodes: FileNode[]): FileNode[] => { + return nodes.filter((node) => { + if (node.id === id) return false; + if (node.children) { + node.children = deleteNode(node.children); + } + return true; + }); + }; + return deleteNode(prev || []); + }); + + setOpenFiles((prev) => (prev || []).filter((f) => f.id !== id)); + if (activeFileId === id) { + setActiveFileId((openFiles || [])[0]?.id || ''); + } + }; + + const handleFileClose = (id: string) => { + setOpenFiles((prev) => (prev || []).filter((f) => f.id !== id)); + if (activeFileId === id) { + const remaining = (openFiles || []).filter((f) => f.id !== id); + setActiveFileId(remaining[0]?.id || ''); + } + }; + + const handleCreateProject = (config: ProjectConfig) => { + const projectFiles: FileNode[] = [ + { + id: 'root', + name: config.name, + type: 'folder', + children: [ + { + id: `file-${Date.now()}`, + name: 'main.lua', + type: 'file', + content: `-- ${config.name}\n-- Template: ${config.template}\n\nprint("Project initialized!")`, + }, + ], + }, + ]; + + setFiles(projectFiles); + setOpenFiles([]); + setActiveFileId(''); + }; + return (
- setShowTemplates(true)} /> + setShowTemplates(true)} + onPreviewClick={() => setShowPreview(true)} + onNewProjectClick={() => setShowNewProject(true)} + /> -
+
{isMobile ? ( + Files Editor - AI Assistant + AI - - + + + + + +
+ +
) : ( - - - - + <> +
+ + + + - + - - - - + +
+ +
+ +
+
+
+ + + + + + + +
+ + )}
@@ -61,6 +269,18 @@ function App() { /> )} + setShowPreview(false)} + code={currentCode} + /> + + setShowNewProject(false)} + onCreateProject={handleCreateProject} + /> +
diff --git a/src/components/ConsolePanel.tsx b/src/components/ConsolePanel.tsx new file mode 100644 index 0000000..1d485b7 --- /dev/null +++ b/src/components/ConsolePanel.tsx @@ -0,0 +1,193 @@ +import { useState, useEffect, useRef } from 'react'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { Button } from '@/components/ui/button'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Badge } from '@/components/ui/badge'; +import { Trash, Terminal } from '@phosphor-icons/react'; + +interface ConsoleLog { + id: string; + timestamp: Date; + type: 'log' | 'warn' | 'error' | 'info'; + platform: 'roblox' | 'web' | 'mobile' | 'system'; + message: string; +} + +interface ConsolePanelProps { + collapsed?: boolean; + onToggle?: () => void; +} + +export function ConsolePanel({ collapsed, onToggle }: ConsolePanelProps) { + const [logs, setLogs] = useState([ + { + id: '1', + timestamp: new Date(), + type: 'info', + platform: 'system', + message: 'AeThex Studio Console initialized', + }, + { + id: '2', + timestamp: new Date(), + type: 'log', + platform: 'roblox', + message: 'Player joined the game!', + }, + ]); + const scrollRef = useRef(null); + + useEffect(() => { + if (scrollRef.current) { + scrollRef.current.scrollTop = scrollRef.current.scrollHeight; + } + }, [logs]); + + const clearLogs = () => { + setLogs([ + { + id: Date.now().toString(), + timestamp: new Date(), + type: 'info', + platform: 'system', + message: 'Console cleared', + }, + ]); + }; + + const getLogColor = (type: ConsoleLog['type']) => { + switch (type) { + case 'error': + return 'text-red-400'; + case 'warn': + return 'text-yellow-400'; + case 'info': + return 'text-blue-400'; + default: + return 'text-foreground'; + } + }; + + const getPlatformBadgeColor = (platform: ConsoleLog['platform']) => { + switch (platform) { + case 'roblox': + return 'bg-blue-500/20 text-blue-400 border-blue-500/30'; + case 'web': + return 'bg-green-500/20 text-green-400 border-green-500/30'; + case 'mobile': + return 'bg-purple-500/20 text-purple-400 border-purple-500/30'; + default: + return 'bg-muted/50 text-muted-foreground'; + } + }; + + if (collapsed) { + return ( +
+
+ + Console + + {logs.length} + +
+
+ ); + } + + return ( +
+
+
+ + Console +
+
+ +
+
+ + + + All + Roblox + Web + Mobile + + + + +
+ {logs.map((log) => ( +
+ + {log.timestamp.toLocaleTimeString()} + + + {log.platform} + + + {log.message} + +
+ ))} +
+
+
+ + + +
+ {logs.filter((log) => log.platform === 'roblox').map((log) => ( +
+ + {log.timestamp.toLocaleTimeString()} + + + {log.message} + +
+ ))} +
+
+
+ + + +
+ {logs.filter((log) => log.platform === 'web').map((log) => ( +
+ + {log.timestamp.toLocaleTimeString()} + + + {log.message} + +
+ ))} +
+
+
+ + + +
+ {logs.filter((log) => log.platform === 'mobile').map((log) => ( +
+ + {log.timestamp.toLocaleTimeString()} + + + {log.message} + +
+ ))} +
+
+
+
+
+ ); +} diff --git a/src/components/FileTabs.tsx b/src/components/FileTabs.tsx new file mode 100644 index 0000000..84f3051 --- /dev/null +++ b/src/components/FileTabs.tsx @@ -0,0 +1,51 @@ +import { X } from '@phosphor-icons/react'; +import { Button } from '@/components/ui/button'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { FileNode } from './FileTree'; + +interface FileTabsProps { + openFiles: FileNode[]; + activeFileId?: string; + onFileSelect: (file: FileNode) => void; + onFileClose: (id: string) => void; +} + +export function FileTabs({ + openFiles, + activeFileId, + onFileSelect, + onFileClose, +}: FileTabsProps) { + if (openFiles.length === 0) return null; + + return ( +
+
+ {openFiles.map((file) => ( +
onFileSelect(file)} + > + {file.name} + +
+ ))} +
+
+ ); +} diff --git a/src/components/FileTree.tsx b/src/components/FileTree.tsx new file mode 100644 index 0000000..9cf2c5d --- /dev/null +++ b/src/components/FileTree.tsx @@ -0,0 +1,187 @@ +import { useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { Input } from '@/components/ui/input'; +import { + File, + Folder, + FolderOpen, + Plus, + DotsThree, + Trash, + PencilSimple, +} from '@phosphor-icons/react'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { toast } from 'sonner'; + +export interface FileNode { + id: string; + name: string; + type: 'file' | 'folder'; + children?: FileNode[]; + content?: string; +} + +interface FileTreeProps { + files: FileNode[]; + onFileSelect: (file: FileNode) => void; + onFileCreate: (name: string, parentId?: string) => void; + onFileRename: (id: string, newName: string) => void; + onFileDelete: (id: string) => void; + selectedFileId?: string; +} + +export function FileTree({ + files, + onFileSelect, + onFileCreate, + onFileRename, + onFileDelete, + selectedFileId, +}: FileTreeProps) { + const [expandedFolders, setExpandedFolders] = useState>(new Set(['root'])); + const [editingId, setEditingId] = useState(null); + const [editingName, setEditingName] = useState(''); + + const toggleFolder = (id: string) => { + setExpandedFolders((prev) => { + const next = new Set(prev); + if (next.has(id)) { + next.delete(id); + } else { + next.add(id); + } + return next; + }); + }; + + const startRename = (file: FileNode) => { + setEditingId(file.id); + setEditingName(file.name); + }; + + const finishRename = (id: string) => { + if (editingName.trim() && editingName !== '') { + onFileRename(id, editingName.trim()); + toast.success('File renamed'); + } + setEditingId(null); + setEditingName(''); + }; + + const handleDelete = (file: FileNode) => { + if (confirm(`Delete ${file.name}?`)) { + onFileDelete(file.id); + toast.success('File deleted'); + } + }; + + const renderNode = (node: FileNode, depth: number = 0) => { + const isExpanded = expandedFolders.has(node.id); + const isSelected = selectedFileId === node.id; + const isEditing = editingId === node.id; + + return ( +
+
{ + if (node.type === 'folder') { + toggleFolder(node.id); + } else { + onFileSelect(node); + } + }} + > + {node.type === 'folder' ? ( + isExpanded ? ( + + ) : ( + + ) + ) : ( + + )} + + {isEditing ? ( + setEditingName(e.target.value)} + onBlur={() => finishRename(node.id)} + onKeyDown={(e) => { + if (e.key === 'Enter') finishRename(node.id); + if (e.key === 'Escape') setEditingId(null); + }} + className="h-6 text-sm" + autoFocus + onClick={(e) => e.stopPropagation()} + /> + ) : ( + {node.name} + )} + + + e.stopPropagation()}> + + + + startRename(node)}> + + Rename + + handleDelete(node)} + className="text-destructive" + > + + Delete + + + +
+ + {node.type === 'folder' && isExpanded && node.children && ( +
{node.children.map((child) => renderNode(child, depth + 1))}
+ )} +
+ ); + }; + + return ( +
+
+ Explorer + +
+ +
{files.map((node) => renderNode(node))}
+
+
+ ); +} diff --git a/src/components/NewProjectModal.tsx b/src/components/NewProjectModal.tsx new file mode 100644 index 0000000..34628c1 --- /dev/null +++ b/src/components/NewProjectModal.tsx @@ -0,0 +1,370 @@ +import { useState } from 'react'; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Card } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Checkbox } from '@/components/ui/checkbox'; +import { Switch } from '@/components/ui/switch'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { GameController, Globe, DeviceMobile, FileCode, Info, Check } from '@phosphor-icons/react'; +import { toast } from 'sonner'; + +interface NewProjectModalProps { + open: boolean; + onClose: () => void; + onCreateProject: (config: ProjectConfig) => void; +} + +export interface ProjectConfig { + name: string; + template: string; + platforms: { + roblox: boolean; + web: boolean; + mobile: boolean; + }; + nexusEngine: boolean; + passportAuth: boolean; +} + +const templates = [ + { + id: 'roblox-starter', + name: 'Roblox Game Starter', + description: 'Basic Roblox game template with player management and leaderboards', + icon: '🎮', + popular: true, + }, + { + id: 'multiplayer', + name: 'Cross-Platform Multiplayer', + description: 'Synchronized multiplayer game across Roblox, web, and mobile', + icon: '🌐', + popular: true, + }, + { + id: 'transmedia', + name: 'Transmedia Story Project', + description: 'Story-driven experience that spans multiple platforms', + icon: '📚', + popular: false, + }, + { + id: 'blank', + name: 'Blank Project', + description: 'Start from scratch with an empty project', + icon: '📄', + popular: false, + }, +]; + +export function NewProjectModal({ open, onClose, onCreateProject }: NewProjectModalProps) { + const [step, setStep] = useState(1); + const [projectName, setProjectName] = useState(''); + const [selectedTemplate, setSelectedTemplate] = useState(''); + const [platforms, setPlatforms] = useState({ + roblox: true, + web: false, + mobile: false, + }); + const [nexusEngine, setNexusEngine] = useState(false); + const [passportAuth, setPassportAuth] = useState(false); + + const handleReset = () => { + setStep(1); + setProjectName(''); + setSelectedTemplate(''); + setPlatforms({ roblox: true, web: false, mobile: false }); + setNexusEngine(false); + setPassportAuth(false); + }; + + const handleCreate = () => { + if (!projectName.trim()) { + toast.error('Please enter a project name'); + return; + } + if (!selectedTemplate) { + toast.error('Please select a template'); + return; + } + + onCreateProject({ + name: projectName, + template: selectedTemplate, + platforms, + nexusEngine, + passportAuth, + }); + + handleReset(); + onClose(); + toast.success(`Project "${projectName}" created!`); + }; + + const handleClose = () => { + handleReset(); + onClose(); + }; + + return ( + + + + Create New Project + + +
+ {[1, 2, 3].map((s) => ( +
+
+ {s < step ? : s} +
+ {s < 3 && ( +
+ )} +
+ ))} +
+ + {step === 1 && ( +
+

Choose a Template

+
+ {templates.map((template) => ( + setSelectedTemplate(template.id)} + > +
+
{template.icon}
+ {template.popular && ( + + Popular + + )} +
+

{template.name}

+

+ {template.description} +

+
+ ))} +
+
+ + +
+
+ )} + + {step === 2 && ( +
+

Project Configuration

+
+
+ + setProjectName(e.target.value)} + placeholder="My Awesome Game" + className="mt-2" + /> +
+ +
+ +
+
+ + setPlatforms((p) => ({ ...p, roblox: checked as boolean })) + } + /> + +
+
+ + setPlatforms((p) => ({ ...p, web: checked as boolean })) + } + /> + +
+
+ + setPlatforms((p) => ({ ...p, mobile: checked as boolean })) + } + /> + +
+
+
+ +
+
+
+ + + + + + + +

+ Nexus Engine synchronizes game state across all platforms in real-time +

+
+
+
+
+ +
+ +
+
+ + + + + + + +

+ AeThex Passport provides unified identity across all your games +

+
+
+
+
+ +
+
+
+ +
+ +
+ + +
+
+
+ )} + + {step === 3 && ( +
+

Review & Create

+ +
+ +

{projectName}

+
+
+ +

+ {templates.find((t) => t.id === selectedTemplate)?.name} +

+
+
+ +
+ {platforms.roblox && Roblox} + {platforms.web && Web} + {platforms.mobile && Mobile} +
+
+
+ +
+ {nexusEngine && Nexus Engine} + {passportAuth && Passport Auth} + {!nexusEngine && !passportAuth && ( + None + )} +
+
+
+ +
+ +
+ + +
+
+
+ )} + +
+ ); +} diff --git a/src/components/PreviewModal.tsx b/src/components/PreviewModal.tsx new file mode 100644 index 0000000..9df7d03 --- /dev/null +++ b/src/components/PreviewModal.tsx @@ -0,0 +1,207 @@ +import { useState } from 'react'; +import { Dialog, DialogContent } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Card } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { X, ArrowsClockwise } from '@phosphor-icons/react'; + +interface PreviewModalProps { + open: boolean; + onClose: () => void; + code: string; +} + +interface SharedState { + variable: string; + roblox: string; + web: string; + mobile: string; + status: 'synced' | 'syncing' | 'conflict'; +} + +export function PreviewModal({ open, onClose, code }: PreviewModalProps) { + const [sharedState] = useState([ + { variable: 'playerX', roblox: '120', web: '120', mobile: '120', status: 'synced' }, + { variable: 'playerY', roblox: '50', web: '50', mobile: '50', status: 'synced' }, + { variable: 'health', roblox: '100', web: '98', mobile: '100', status: 'syncing' }, + { variable: 'score', roblox: '450', web: '450', mobile: '450', status: 'synced' }, + ]); + + const getStatusColor = (status: SharedState['status']) => { + switch (status) { + case 'synced': + return 'text-green-500'; + case 'syncing': + return 'text-yellow-500'; + case 'conflict': + return 'text-red-500'; + } + }; + + const getStatusIcon = (status: SharedState['status']) => { + switch (status) { + case 'synced': + return '✓'; + case 'syncing': + return '⚠'; + case 'conflict': + return '✗'; + } + }; + + return ( + + +
+
+

Preview - All Platforms

+
+ + +
+
+ + + + All Platforms + Roblox + Web + Mobile + + + +
+ +
+

Roblox

+ +
+
+
+
🎮
+

Roblox Viewport

+

Studio integration

+
+
+
+ + +
+

Web Browser

+ +
+
+
+
🌐
+

Web Canvas

+

Browser preview

+
+
+
+ + +
+

Mobile

+ +
+
+
+
+
+
+
📱
+

Mobile View

+
+
+
+
+
+ +
+ + +
+

Shared State Sync

+ + Live Sync Enabled + +
+
+ + + + + + + + + + + + {sharedState.map((state, idx) => ( + + + + + + + + ))} + +
VariableRobloxWebMobileStatus
{state.variable}{state.roblox}{state.web}{state.mobile} + + {getStatusIcon(state.status)} {state.status} + +
+
+
+ + + + +
+
🎮
+

Roblox Preview

+

Full-screen Roblox viewport

+ +
+
+
+ + + +
+
🌐
+

Web Preview

+

Browser-based game preview

+ +
+
+
+ + +
+
+ +
+
📱
+

Mobile Preview

+

iOS/Android view

+ +
+
+
+
+ + +
+ +
+ ); +} diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index c52ebf6..15217bb 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -1,17 +1,32 @@ import { Button } from '@/components/ui/button'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; -import { Copy, FileCode, Download, Info } from '@phosphor-icons/react'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { Copy, FileCode, Download, Info, Play, FolderPlus, User, SignOut } from '@phosphor-icons/react'; import { toast } from 'sonner'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'; interface ToolbarProps { code: string; onTemplatesClick: () => void; + onPreviewClick: () => void; + onNewProjectClick: () => void; } -export function Toolbar({ code, onTemplatesClick }: ToolbarProps) { +export function Toolbar({ code, onTemplatesClick, onPreviewClick, onNewProjectClick }: ToolbarProps) { const [showInfo, setShowInfo] = useState(false); + const [user, setUser] = useState<{ login: string; avatarUrl: string; email: string } | null>(null); + + useEffect(() => { + window.spark.user().then(setUser).catch(() => setUser(null)); + }, []); const handleCopy = async () => { try { @@ -41,10 +56,40 @@ export function Toolbar({ code, onTemplatesClick }: ToolbarProps) {

AeThex

+ Studio
+ + + + + Create a new project + + + + + + + Preview all platforms + + + + + {user && ( + <> +
+

{user.login}

+

{user.email}

+
+ + + )} + toast.info('Profile coming soon!')}> + + Profile + + toast.info('Sign out feature coming soon!')}> + + Sign Out + +
+