import { useMemo, useCallback, useRef, useEffect, useState } from 'react'; import { useEditorStore } from '../../store/editorStore'; import { useProjectStore } from '../../store/projectStore'; import { useCollaborationStore, Collaborator } from '../../store/collaborationStore'; import { useProjectSocket } from '../../hooks/useWebSocket'; import { useParams } from 'react-router-dom'; import { getBlocksForFramework } from '../../blocks/registry'; import { compileWeb } from '../../compilers/web'; import { compileElectron } from '../../compilers/electron'; import { compileMaui } from '../../compilers/maui'; import { compileNodeJS } from '../../compilers/nodejs'; import { SyntaxHighlight } from './SyntaxHighlight'; import { Download, Copy, FileCode, Eye, Code2, SplitSquareHorizontal } from 'lucide-react'; import { saveAs } from 'file-saver'; import JSZip from 'jszip'; import { getColor } from '../Collaboration/CollaboratorAvatars'; export default function CodeView() { const { id } = useParams<{ id: string }>(); const { currentProject } = useProjectStore(); const { activeFileId, fileTree, viewMode, setViewMode, visualElements, activeFileContent, setActiveFileContent } = useEditorStore(); const collaborators = useCollaborationStore((s) => s.collaborators); const { emitCursorMove, emitFileChanged } = useProjectSocket(id); const editorRef = useRef(null); const [isEditing, setIsEditing] = useState(false); const compiledCode = useMemo(() => { if (!currentProject) return ''; if (isEditing) return activeFileContent; const activeFile = fileTree.find(f => f.id === activeFileId); const blocks = getBlocksForFramework(currentProject.framework); const compilerOptions = { blocks, fileTree, visualElements, activeFile, projectName: currentProject.name, }; switch (currentProject.framework) { case 'web': return compileWeb(compilerOptions); case 'electron': return compileElectron(compilerOptions); case 'maui': return compileMaui(compilerOptions); case 'nodejs': return compileNodeJS(compilerOptions); default: return '// Select a framework to generate code'; } }, [currentProject, activeFileId, fileTree, visualElements, isEditing, activeFileContent]); const collaboratorCursors = useMemo(() => { if (!activeFileId) return []; return collaborators.filter((c) => c.activeFileId === activeFileId && c.cursor && c.cursor.line != null); }, [collaborators, activeFileId]); const lines = useMemo(() => compiledCode.split('\n'), [compiledCode]); const handleEditorClick = useCallback((e: React.MouseEvent) => { const rect = editorRef.current?.getBoundingClientRect(); if (!rect) return; const lineHeight = 20; const line = Math.floor((e.clientY - rect.top + editorRef.current!.scrollTop) / lineHeight); const ch = Math.min(0, 0); if (activeFileId) { emitCursorMove(activeFileId, { line: Math.max(0, line), ch }); } }, [activeFileId, emitCursorMove]); const fileChangeTimer = useRef(); const cursorThrottle = useRef(); const handleTextChange = useCallback((e: React.ChangeEvent) => { const content = e.target.value; setActiveFileContent(content); // Debounce file_changed to 300ms to avoid flooding when typing if (activeFileId) { clearTimeout(fileChangeTimer.current); fileChangeTimer.current = window.setTimeout(() => { emitFileChanged(activeFileId, content); }, 300); } // Track cursor position const textarea = e.target; const pos = textarea.selectionStart; const before = content.substring(0, pos); const line = before.split('\n').length - 1; const ch = before.length - before.lastIndexOf('\n') - 1; clearTimeout(cursorThrottle.current); cursorThrottle.current = window.setTimeout(() => { if (activeFileId) emitCursorMove(activeFileId, { line, ch }); }, 100); }, [activeFileId, emitCursorMove, emitFileChanged, setActiveFileContent]); const handleDownload = useCallback(async () => { if (!currentProject) return; const zip = new JSZip(); const addFilesToZip = (files: any[], currentPath: string = '') => { for (const file of files) { if (file.type === 'folder') { if (file.children) addFilesToZip(file.children, currentPath + file.name + '/'); } else { zip.file(currentPath + file.name, file.content || ''); } } }; addFilesToZip(fileTree); const blob = await zip.generateAsync({ type: 'blob' }); saveAs(blob, `${currentProject.name}.zip`); }, [currentProject, fileTree]); const handleCopy = useCallback(() => { navigator.clipboard.writeText(compiledCode); }, [compiledCode]); const scriptFileName = useMemo(() => { const scriptFile = fileTree.find(f => f.type === 'js' || f.type === 'typescript'); return scriptFile?.name || fileTree.find(f => f.id === activeFileId)?.name || 'output'; }, [fileTree, activeFileId]); return (
{/* Code Toolbar */}
{scriptFileName} {currentProject?.framework || 'web'}
{!isEditing && (
)}
{/* Code Content */}
{isEditing ? (