From a8b2ffc3fe7f605b2bcc709aa12e5801195ebba4 Mon Sep 17 00:00:00 2001 From: MrPiglr Date: Wed, 28 Jan 2026 23:31:17 +0000 Subject: [PATCH] modified: components/StudioEditor.tsx --- components/FileTree.tsx | 7 +- components/StudioEditor.tsx | 14 +- src/App.tsx | 387 ++++++++++++++++++++++++++++++++---- tsconfig.json | 5 +- 4 files changed, 356 insertions(+), 57 deletions(-) diff --git a/components/FileTree.tsx b/components/FileTree.tsx index 9d71775..639071b 100644 --- a/components/FileTree.tsx +++ b/components/FileTree.tsx @@ -6,7 +6,7 @@ import { useEditorStore, FileNode } from '@/store/editor-store'; import { cn, getFileIcon, getPlatformIcon } from '@/lib/utils'; export function FileTree() { - const { files, openFile } = useEditorStore(); + const { files, openFile, moveFile } = useEditorStore(); const [expandedFolders, setExpandedFolders] = React.useState>( new Set(['roblox', 'web', 'mobile', 'desktop', 'shared']) ); @@ -29,7 +29,10 @@ export function FileTree() { return (
e.dataTransfer.setData('fileId', node.id)} onDrop={e => { e.preventDefault(); - // moveFile functionality is not implemented + const fileId = e.dataTransfer.getData('fileId'); + if (fileId && fileId !== node.id && isFolder) { + moveFile(fileId, node.id); + } }} onDragOver={e => isFolder && e.preventDefault()}>
s.openTabs); - const activeTabId = useEditorStore((s: any) => s.activeTabId); - const setActiveTab = useEditorStore((s: any) => s.setActiveTab); - const closeTab = useEditorStore((s: any) => s.closeTab); + const openTabs = useEditorStore((s) => s.openTabs); + const activeTabId = useEditorStore((s) => s.activeTabId); + const setActiveTab = useEditorStore((s) => s.setActiveTab); + const closeTab = useEditorStore((s) => s.closeTab); // Find active file - const activeFile = openTabs.find((f: any) => f.id === activeTabId); + const activeFile = openTabs.find(f => f.id === activeTabId); return (
{openTabs.length === 0 && (
No files open
)} - {openTabs.map((file: any) => ( + {openTabs.map((file) => (
{activeFile ? ( - activeFile.content.split("\n").map((line: string, i: number) => ( + activeFile.content.split("\n").map((line, i) => (
{i + 1}
{line}
diff --git a/src/App.tsx b/src/App.tsx index 236563d..4e4d462 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,59 +1,358 @@ -"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"; +// ...existing code... +import React, { useState, lazy, Suspense } from 'react'; +import { FileTree } from '../components/FileTree'; +import { FileTabs } from '../components/FileTabs'; +import { CodeEditor } from '../components/CodeEditor'; +import { ConsolePanel } from '../components/ConsolePanel'; +import { Toolbar } from './components/Toolbar'; +import { AIAssistant } from '../components/AIAssistant'; +import { CrossPlatformPreview } from '../components/CrossPlatformPreview'; +import { NexusSyncMonitor } from '../components/NexusSyncMonitor'; +import { Toaster } from './components/ui/sonner'; +import { toast } from 'sonner'; +import { useEditorStore } from '../store/editor-store'; +import { FileNode } from '../store/editor-store'; +import { captureEvent } from './lib/posthog'; +import { captureError } from './lib/sentry'; +import { useIsMobile } from './hooks/use-mobile'; + +const TemplatesDrawer = lazy(() => import('./components/TemplatesDrawer')); +const PreviewModal = lazy(() => import('./components/PreviewModal')); +const NewProjectModal = lazy(() => import('./components/NewProjectModal')); +const PassportLogin = lazy(() => import('./components/PassportLogin')); +const TranslationPanel = lazy(() => import('./components/TranslationPanel')); +const CommandPalette = lazy(() => import('./components/CommandPalette')); +const StudioSidebar = lazy(() => import('../components/StudioSidebar')); +const StudioEditor = lazy(() => import('../components/StudioEditor')); +// const StudioBottomPanel = lazy(() => import('../components/StudioBottomPanel')); +const StudioRightPanel = lazy(() => import('../components/StudioRightPanel')); +const StudioNetworkViz = lazy(() => import('../components/StudioNetworkViz')); function App() { - // Minimal state stubs - const [user] = useState(null); - const [showPassportLogin, setShowPassportLogin] = useState(false); - const [showNewProject, setShowNewProject] = useState(false); + // --- Error/Warning Banner State --- + const [problemsExpanded, setProblemsExpanded] = useState(false); + // --- Right Sidebar Tab State --- + const [rightSidebarTab, setRightSidebarTab] = useState('Copilot'); + // TODO: Connect to real error/warning data source + const problems: Array<{ file: string; line: number; type: string; message: string }> = []; + // ...existing state and hooks... + // --- State --- + const setFiles = useEditorStore((state) => state.setFiles); + const [currentCode, setCurrentCode] = useState(''); const [showTemplates, setShowTemplates] = useState(false); - const [showTranslation, setShowTranslation] = useState(false); + const [showPreview, setShowPreview] = useState(false); + const [showNewProject, setShowNewProject] = useState(false); const [showFileSearch, setShowFileSearch] = useState(false); const [showCommandPalette, setShowCommandPalette] = useState(false); - const [consoleCollapsed, setConsoleCollapsed] = useState(false); - const [openFiles, setOpenFiles] = useState([]); - const [activeFileId, setActiveFileId] = useState(""); - const [code, setCode] = useState(""); - const [currentCode, setCurrentCode] = useState(""); - const [showPreview, setShowPreview] = useState(false); + const [showSearchInFiles, setShowSearchInFiles] = useState(false); + const [showTranslation, setShowTranslation] = useState(false); + const [code, setCode] = useState(''); + const [currentPlatform, setCurrentPlatform] = useState('roblox'); + const isMobile = useIsMobile(); + const [showPassportLogin, setShowPassportLogin] = useState(false); + const [consoleCollapsed, setConsoleCollapsed] = useState(isMobile); + const [user, setUser] = useState<{ login: string; avatarUrl: string; email: string } | null>(() => { + const stored = typeof window !== 'undefined' ? localStorage.getItem('aethex-user') : null; + return stored ? JSON.parse(stored) : null; + }); + const [openFiles, setOpenFiles] = useState([]); + const [activeFileId, setActiveFileId] = useState(''); - // 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 = () => {}; + // --- Handlers --- + const handleTemplateSelect = (templateCode: string) => { + setCode(templateCode); + setCurrentCode(templateCode); + if (activeFileId) { + handleCodeChange(templateCode); + } + }; + const handleCodeChange = (newCode: string) => { + setCurrentCode(newCode); + setCode(newCode); + if (activeFileId) { + const files = useEditorStore.getState().files; + const updateFileContent = (nodes: FileNode[]): FileNode[] => { + return nodes.map((node) => { + if (node.id === activeFileId) { + return { ...node, content: newCode }; + } + if (node.children) { + return { ...node, children: updateFileContent(node.children) }; + } + return node; + }); + }; + setFiles(updateFileContent(files || [])); + setOpenFiles((prev) => (prev || []).map((file) => file.id === activeFileId ? { ...file, content: newCode } : file)); + } + }; + + 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 || ''); + } + captureEvent('file_select', { fileId: file.id }); + }; + + const handleFileClose = (id: string) => { + setOpenFiles((prev) => (prev || []).filter((f) => f.id !== id)); + }; + + const handleFileRename = (id: string, newName: string) => { + if (!newName || newName.trim() === '') { + toast.error('File name cannot be empty'); + return; + } + const files = useEditorStore.getState().files; + 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; + }); + }; + setFiles(rename(files || [])); + captureEvent('file_rename', { id, newName }); + }; + + const handleFileDelete = (id: string) => { + try { + const files = useEditorStore.getState().files; + 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; + }); + }; + setFiles(deleteNode(files || [])); + setOpenFiles((prev) => (prev || []).filter((f) => f.id !== id)); + if (activeFileId === id) { + setActiveFileId((openFiles || [])[0]?.id || ''); + } + captureEvent('file_delete', { id }); + } catch (error) { + console.error('Failed to delete file:', error); + captureError(error as Error, { context: 'file_delete', id }); + toast.error('Failed to delete file. Please try again.'); + } + }; + + const handleFileMove = (fileId: string, targetParentId: string) => { + try { + const files = useEditorStore.getState().files; + let movedNode: FileNode | null = null; + const removeNode = (nodes: FileNode[]): FileNode[] => { + return nodes.filter((node) => { + if (node.id === fileId) { + movedNode = node; + return false; + } + if (node.children) { + node.children = removeNode(node.children); + } + return true; + }); + }; + const addToTarget = (nodes: FileNode[]): FileNode[] => { + return nodes.map((node) => { + if (node.id === targetParentId && node.type === 'folder') { + return { + ...node, + children: [...(node.children || []), movedNode!], + }; + } + if (node.children) { + return { ...node, children: addToTarget(node.children) }; + } + return node; + }); + }; + const withoutMoved = removeNode(files || []); + if (movedNode) { + setFiles(addToTarget(withoutMoved)); + } else { + setFiles(files || []); + } + captureEvent('file_move', { fileId, targetParentId }); + } catch (error) { + console.error('Failed to move file:', error); + captureError(error as Error, { context: 'file_move', fileId, targetParentId }); + toast.error('Failed to move file. Please try again.'); + } + }; + + // --- Render --- return ( - - - - {showTemplates && setShowTemplates(false)} currentPlatform={"roblox"} />} + <> + +
+
+ {/* Left Sidebar: Vertical tabs + Explorer */} +
+ + + + + +
+
+ Loading...
}> + + +
+ {/* Center: Tabs + Editor + Preview */} +
+ {/* File Tabs Row (no workspace tabs above) */} +
+ +
+ {/* Main Editor/Preview Split */} +
+
+ setShowTemplates(true)} /*...existing code...*/ /> +
+ {activeFileId ? ( + + ) : ( +
+
๐Ÿ“‚
+
+

Welcome to AeThex Studio

+

Select a file from the explorer to start editing, or use the quick actions below.

+
+ + + +
+
+
+ )} +
+
+
+
+

Preview

+ +
+ {showPreview && ( +
+ +
+ )} +
+

Sync Monitor

+ +
+ {showTranslation && ( +
+ +
+ )} +
+
+ {/* ConsolePanel moved to top bar. */} +
+ {/* Right Sidebar: Copilot, AI, Inspector, Trinity */} +
+ Loading...
}> +
+
+ {['Copilot', 'AI', 'Inspector', 'Trinity'].map(tab => ( + + ))} +
+
+ {rightSidebarTab === 'Copilot' && } + {rightSidebarTab === 'AI' && } + {rightSidebarTab === 'Inspector' &&
Inspector/Properties coming soonโ€ฆ
} + {rightSidebarTab === 'Trinity' && } +
+
+ +
+
+ {/* Problems Details Floating Panel */} + {problemsExpanded && ( +
+
+ Problems + +
+
    + {problems.map((p, i) => ( +
  • + + {p.type === 'error' ? 'Error:' : 'Warning:'} + + {p.file}:{p.line} + {p.message} +
  • + ))} +
+
+ )} + {/* Modals and Drawers */} + Loadingโ€ฆ
}> + {showTemplates && setShowTemplates(false)} />} + {showPreview && setShowPreview(false)} />} + {showNewProject && setShowNewProject(false)} />} + +
+ + Loadingโ€ฆ
}> + {showTemplates && setShowTemplates(false)} />} {showPreview && setShowPreview(false)} />} {showNewProject && setShowNewProject(false)} onCreateProject={() => {}} />} - {showTranslation && setShowTranslation(false)} currentCode={currentCode} currentPlatform={"roblox"} />} + {showTranslation && setShowTranslation(false)} currentCode={currentCode} currentPlatform={currentPlatform} />} {showPassportLogin && setShowPassportLogin(false)} onLoginSuccess={() => {}} />} - - + + setShowCommandPalette(false)} + commands={[ + { id: 'new-project', label: 'New Project', description: 'Create a new project', icon: '๐Ÿ“', action: () => setShowNewProject(true) }, + { id: 'templates', label: 'Templates', description: 'Open templates drawer', icon: '๐Ÿ“„', action: () => setShowTemplates(true) }, + { id: 'preview', label: 'Preview', description: 'Preview your code', icon: '๐Ÿ‘๏ธ', action: () => setShowPreview(true) }, + { id: 'export', label: 'Export', description: 'Export your project', icon: 'โฌ‡๏ธ', action: () => toast.success('Exported!') }, + { id: 'copy', label: 'Copy', description: 'Copy code', icon: '๐Ÿ“‹', action: () => toast.success('Copied!') }, + ]} + /> + ); } diff --git a/tsconfig.json b/tsconfig.json index 0004183..6e1e4e3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,10 +20,7 @@ } ], "paths": { - "@/*": ["src/*"], - "@/store/*": ["store/*"], - "@/lib/*": ["src/lib/*"], - "@/hooks/*": ["src/hooks/*"] + "@/*": ["./*"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],