modified: components/StudioEditor.tsx
This commit is contained in:
parent
ea5ba62c54
commit
a8b2ffc3fe
4 changed files with 356 additions and 57 deletions
|
|
@ -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<Set<string>>(
|
||||
new Set(['roblox', 'web', 'mobile', 'desktop', 'shared'])
|
||||
);
|
||||
|
|
@ -29,7 +29,10 @@ export function FileTree() {
|
|||
return (
|
||||
<div key={node.id} draggable onDragStart={e => 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()}>
|
||||
<div
|
||||
className={cn(
|
||||
|
|
|
|||
|
|
@ -2,19 +2,19 @@
|
|||
import { useEditorStore } from "../store/editor-zustand";
|
||||
|
||||
function StudioEditor() {
|
||||
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);
|
||||
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 (
|
||||
<div className="editor-area">
|
||||
<div className="editor-tabs">
|
||||
{openTabs.length === 0 && (
|
||||
<div className="editor-tab empty">No files open</div>
|
||||
)}
|
||||
{openTabs.map((file: any) => (
|
||||
{openTabs.map((file) => (
|
||||
<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: string, i: number) => (
|
||||
activeFile.content.split("\n").map((line, i) => (
|
||||
<div className="code-line" key={i}>
|
||||
<div className="line-number">{i + 1}</div>
|
||||
<div className="line-content">{line}</div>
|
||||
|
|
|
|||
387
src/App.tsx
387
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<any>(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<any[]>([]);
|
||||
const [activeFileId, setActiveFileId] = useState<string>("");
|
||||
const [code, setCode] = useState<string>("");
|
||||
const [currentCode, setCurrentCode] = useState<string>("");
|
||||
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<FileNode[]>([]);
|
||||
const [activeFileId, setActiveFileId] = useState<string>('');
|
||||
|
||||
// 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 (
|
||||
<StudioLayout>
|
||||
<Toaster />
|
||||
<Suspense>
|
||||
{showTemplates && <TemplatesDrawer onSelectTemplate={handleTemplateSelect} onClose={() => setShowTemplates(false)} currentPlatform={"roblox"} />}
|
||||
<>
|
||||
<Toaster position="bottom-right" />
|
||||
<div className="h-screen w-screen bg-background flex flex-col">
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{/* Left Sidebar: Vertical tabs + Explorer */}
|
||||
<div className="w-16 h-full bg-[#20232A] border-r border-border flex flex-col items-center py-2 gap-2">
|
||||
<button className="w-10 h-10 flex items-center justify-center rounded hover:bg-[#23272F]" title="Explorer">
|
||||
<span className="text-xl">📁</span>
|
||||
</button>
|
||||
<button className="w-10 h-10 flex items-center justify-center rounded hover:bg-[#23272F]" title="Assets">
|
||||
<span className="text-xl">🗂️</span>
|
||||
</button>
|
||||
<button className="w-10 h-10 flex items-center justify-center rounded hover:bg-[#23272F]" title="Search">
|
||||
<span className="text-xl">🔍</span>
|
||||
</button>
|
||||
<button className="w-10 h-10 flex items-center justify-center rounded hover:bg-[#23272F]" title="Source Control">
|
||||
<span className="text-xl">🔗</span>
|
||||
</button>
|
||||
<button className="w-10 h-10 flex items-center justify-center rounded hover:bg-[#23272F]" title="Extensions">
|
||||
<span className="text-xl">🧩</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="w-56 h-full bg-card border-r border-border flex flex-col shadow-lg p-2">
|
||||
<Suspense fallback={<div className="p-4">Loading...</div>}>
|
||||
<FileTree />
|
||||
</Suspense>
|
||||
</div>
|
||||
{/* Center: Tabs + Editor + Preview */}
|
||||
<div className="flex-1 flex flex-col">
|
||||
{/* File Tabs Row (no workspace tabs above) */}
|
||||
<div className="h-10 flex items-center bg-[#23272F] border-b border-border px-2">
|
||||
<FileTabs />
|
||||
</div>
|
||||
{/* Main Editor/Preview Split */}
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
<div className="flex-1 flex flex-col">
|
||||
<Toolbar code={code} onTemplatesClick={() => setShowTemplates(true)} /*...existing code...*/ />
|
||||
<div className="flex-1 flex flex-col">
|
||||
{activeFileId ? (
|
||||
<CodeEditor />
|
||||
) : (
|
||||
<div className="flex flex-1 flex-col items-center justify-center text-center gap-6 bg-background/80">
|
||||
<div className="text-6xl">📂</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-bold mb-2">Welcome to AeThex Studio</h2>
|
||||
<p className="text-gray-400 mb-4">Select a file from the explorer to start editing, or use the quick actions below.</p>
|
||||
<div className="flex flex-wrap gap-2 justify-center">
|
||||
<button className="px-4 py-2 rounded bg-blue-600 text-white text-sm hover:bg-blue-700" onClick={() => setShowNewProject(true)}>New Project</button>
|
||||
<button className="px-4 py-2 rounded bg-green-600 text-white text-sm hover:bg-green-700" onClick={() => setShowTemplates(true)}>Templates</button>
|
||||
<button className="px-4 py-2 rounded bg-gray-700 text-white text-sm hover:bg-gray-800" onClick={() => setShowFileSearch(true)}>Open File</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col border-l border-border bg-card" style={{ minWidth: 320, maxWidth: 600, width: '40%', resize: 'horizontal', overflow: 'auto' }}>
|
||||
<div className="flex items-center justify-between p-4 border-b border-border">
|
||||
<h4 className="font-bold text-base mb-2">Preview</h4>
|
||||
<button className="text-xs text-gray-400 hover:text-white" onClick={() => setShowPreview((v) => !v)}>{showPreview ? 'Hide' : 'Show'}</button>
|
||||
</div>
|
||||
{showPreview && (
|
||||
<div className="flex-1 p-4">
|
||||
<CrossPlatformPreview />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center justify-between p-4 border-t border-border">
|
||||
<h4 className="font-bold text-base mb-2">Sync Monitor</h4>
|
||||
<button className="text-xs text-gray-400 hover:text-white" onClick={() => setShowTranslation((v) => !v)}>{showTranslation ? 'Hide' : 'Show'}</button>
|
||||
</div>
|
||||
{showTranslation && (
|
||||
<div className="p-4">
|
||||
<NexusSyncMonitor />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* ConsolePanel moved to top bar. */}
|
||||
</div>
|
||||
{/* Right Sidebar: Copilot, AI, Inspector, Trinity */}
|
||||
<div className="w-72 h-full border-l border-border bg-card flex flex-col shadow-lg p-2 gap-4">
|
||||
<Suspense fallback={<div className="p-4">Loading...</div>}>
|
||||
<div className="rounded-xl shadow-lg bg-background p-0 mb-2 h-full flex flex-col">
|
||||
<div className="flex border-b border-border">
|
||||
{['Copilot', 'AI', 'Inspector', 'Trinity'].map(tab => (
|
||||
<button
|
||||
key={tab}
|
||||
className={`flex-1 py-2 text-xs font-bold uppercase tracking-wider border-b-2 transition-colors ${tab === rightSidebarTab ? 'border-blue-500 text-blue-400 bg-background' : 'border-transparent text-gray-400 bg-card hover:bg-muted/30'}`}
|
||||
onClick={() => setRightSidebarTab(tab)}
|
||||
>
|
||||
{tab}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto p-4">
|
||||
{rightSidebarTab === 'Copilot' && <StudioRightPanel />}
|
||||
{rightSidebarTab === 'AI' && <AIAssistant />}
|
||||
{rightSidebarTab === 'Inspector' && <div>Inspector/Properties coming soon…</div>}
|
||||
{rightSidebarTab === 'Trinity' && <StudioNetworkViz />}
|
||||
</div>
|
||||
</div>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
{/* Problems Details Floating Panel */}
|
||||
{problemsExpanded && (
|
||||
<div className="fixed top-16 left-1/2 transform -translate-x-1/2 z-50 bg-card border border-border rounded-xl shadow-xl px-8 py-6 w-[480px]">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="font-bold text-destructive">Problems</span>
|
||||
<button
|
||||
className="ml-4 px-3 py-1 rounded bg-muted text-xs hover:bg-muted/80 transition"
|
||||
onClick={() => setProblemsExpanded(false)}
|
||||
>
|
||||
Hide
|
||||
</button>
|
||||
</div>
|
||||
<ul className="space-y-2">
|
||||
{problems.map((p, i) => (
|
||||
<li key={i} className="flex items-center gap-3 text-sm">
|
||||
<span className={p.type === 'error' ? 'text-destructive' : 'text-yellow-500'}>
|
||||
{p.type === 'error' ? 'Error:' : 'Warning:'}
|
||||
</span>
|
||||
<span className="font-mono text-xs text-muted-foreground">{p.file}:{p.line}</span>
|
||||
<span>{p.message}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{/* Modals and Drawers */}
|
||||
<Suspense fallback={<div className="fixed inset-0 flex items-center justify-center bg-background/80 z-50">Loading…</div>}>
|
||||
{showTemplates && <TemplatesDrawer open={showTemplates} onSelect={handleTemplateSelect} onClose={() => setShowTemplates(false)} />}
|
||||
{showPreview && <PreviewModal open={showPreview} code={currentCode} onClose={() => setShowPreview(false)} />}
|
||||
{showNewProject && <NewProjectModal open={showNewProject} onClose={() => setShowNewProject(false)} />}
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
<Suspense fallback={<div className="fixed inset-0 flex items-center justify-center bg-background/80 z-50">Loading…</div>}>
|
||||
{showTemplates && <TemplatesDrawer open={showTemplates} onSelect={handleTemplateSelect} onClose={() => setShowTemplates(false)} />}
|
||||
{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"} />}
|
||||
{showTranslation && <TranslationPanel isOpen={showTranslation} onClose={() => setShowTranslation(false)} currentCode={currentCode} currentPlatform={currentPlatform} />}
|
||||
{showPassportLogin && <PassportLogin open={showPassportLogin} onClose={() => setShowPassportLogin(false)} onLoginSuccess={() => {}} />}
|
||||
</Suspense>
|
||||
<CommandPalette />
|
||||
</StudioLayout>
|
||||
|
||||
<CommandPalette
|
||||
open={showCommandPalette}
|
||||
onClose={() => 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!') },
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,10 +20,7 @@
|
|||
}
|
||||
],
|
||||
"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