'use client' import React, { useState, useEffect } from 'react' import Editor from '@monaco-editor/react' import { Download, SidebarSimple, FileCode, CaretDown, FloppyDisk } from '@phosphor-icons/react' import Window from './Window' interface FlutterRunnerProps { onClose: () => void onMinimize?: () => void onMaximize?: () => void onFocus?: () => void zIndex?: number initialCode?: string } interface FileNode { id: string name: string type: 'file' | 'folder' content?: string children?: FileNode[] isOpen?: boolean } const DEFAULT_FLUTTER_CODE = `` export function FlutterRunner({ onClose, onMinimize, onMaximize, onFocus, zIndex, initialCode }: FlutterRunnerProps) { const [code, setCode] = useState(initialCode || DEFAULT_FLUTTER_CODE) const [showEditor, setShowEditor] = useState(true) const [showFiles, setShowFiles] = useState(true) const [files, setFiles] = useState([]) const [activeFileId, setActiveFileId] = useState('main') const [activeFileName, setActiveFileName] = useState('main.dart') const [lastSaved, setLastSaved] = useState(null) const [passkey, setPasskey] = useState('') const [loading, setLoading] = useState(false) // Load all files for the file tree without changing the active file const loadAllFilesForTree = async (key: string, currentFileName: string) => { try { const response = await fetch(`/api/data?key=${encodeURIComponent(key)}&folder=`) const data = await response.json() if (data.error) { throw new Error(data.error) } // Filter for .dart files const dartFiles = data.files?.filter((f: any) => f.name.endsWith('.dart')) || [] if (dartFiles.length > 0) { const fileNodes: FileNode[] = dartFiles.map((file: any, index: number) => ({ id: `file_${index}`, name: file.name, type: 'file' as const, content: file.content })) setFiles([{ id: 'root', name: 'Dart Files', type: 'folder', isOpen: true, children: fileNodes }]) // Set active file ID to match the current file const currentFileNode = fileNodes.find(f => f.name === currentFileName) if (currentFileNode) { setActiveFileId(currentFileNode.id) } } else { // No files found, just show the current file setFiles([{ id: 'root', name: 'lib', type: 'folder', isOpen: true, children: [ { id: 'main', name: currentFileName, type: 'file', content: '' } ] }]) } } catch (err) { console.error('Error loading file tree:', err) // On error, just show the current file setFiles([{ id: 'root', name: 'lib', type: 'folder', isOpen: true, children: [ { id: 'main', name: currentFileName, type: 'file', content: '' } ] }]) } } // Load files from secure storage (used when no specific file is selected) const loadFiles = async (key: string) => { setLoading(true) try { const response = await fetch(`/api/data?key=${encodeURIComponent(key)}&folder=`) const data = await response.json() if (data.error) { throw new Error(data.error) } // Filter for .dart files const dartFiles = data.files?.filter((f: any) => f.name.endsWith('.dart')) || [] if (dartFiles.length > 0) { const fileNodes: FileNode[] = dartFiles.map((file: any, index: number) => ({ id: `file_${index}`, name: file.name, type: 'file' as const, content: file.content })) setFiles([{ id: 'root', name: 'Dart Files', type: 'folder', isOpen: true, children: fileNodes }]) // Load first file if (fileNodes.length > 0) { setActiveFileId(fileNodes[0].id) setActiveFileName(fileNodes[0].name) setCode(fileNodes[0].content || DEFAULT_FLUTTER_CODE) } } else { // No files found, create default setFiles([{ id: 'root', name: 'lib', type: 'folder', isOpen: true, children: [ { id: 'main', name: 'main.dart', type: 'file', content: DEFAULT_FLUTTER_CODE } ] }]) setCode(DEFAULT_FLUTTER_CODE) setActiveFileName('main.dart') } } catch (err) { console.error('Error loading files:', err) // Create default on error setFiles([{ id: 'root', name: 'lib', type: 'folder', isOpen: true, children: [ { id: 'main', name: 'main.dart', type: 'file', content: DEFAULT_FLUTTER_CODE } ] }]) setCode(DEFAULT_FLUTTER_CODE) setActiveFileName('main.dart') } finally { setLoading(false) } } const handleFileClick = (file: FileNode) => { if (file.type === 'file') { setActiveFileId(file.id) setActiveFileName(file.name) setCode(file.content || '') } } // Check for initial passkey and file content on mount useEffect(() => { const sessionPasskey = sessionStorage.getItem('currentPasskey') const sessionFileContent = sessionStorage.getItem('flutterFileContent') const sessionFileName = sessionStorage.getItem('currentFileName') if (sessionFileContent) { // Load file from session storage (from FileManager) console.log('Loading dart file from session:', sessionFileName, 'Content length:', sessionFileContent.length) setCode(sessionFileContent) setActiveFileName(sessionFileName || 'main.dart') if (sessionPasskey) { setPasskey(sessionPasskey) // Don't call loadFiles here - it would overwrite the file we just loaded // Just load all files for the file tree without changing the active file loadAllFilesForTree(sessionPasskey, sessionFileName || 'main.dart') } else { // For public files, just set up a simple file structure setFiles([{ id: 'root', name: 'lib', type: 'folder', isOpen: true, children: [ { id: 'main', name: sessionFileName || 'main.dart', type: 'file', content: sessionFileContent } ] }]) } // Clear session storage after loading sessionStorage.removeItem('flutterFileContent') } else if (sessionPasskey) { setPasskey(sessionPasskey) loadFiles(sessionPasskey) } else if (initialCode) { setCode(initialCode) } else { // Initialize with default empty file setFiles([{ id: 'root', name: 'lib', type: 'folder', isOpen: true, children: [ { id: 'main', name: 'main.dart', type: 'file', content: DEFAULT_FLUTTER_CODE } ] }]) } }, [initialCode]) // Listen for new files being loaded while the component is already open useEffect(() => { const checkForNewFile = () => { const sessionFileContent = sessionStorage.getItem('flutterFileContent') const sessionFileName = sessionStorage.getItem('currentFileName') const sessionPasskey = sessionStorage.getItem('currentPasskey') if (sessionFileContent) { console.log('Detected new file in session storage:', sessionFileName) setCode(sessionFileContent) setActiveFileName(sessionFileName || 'main.dart') if (sessionPasskey) { setPasskey(sessionPasskey) // Load all files for the file tree without overwriting the current file loadAllFilesForTree(sessionPasskey, sessionFileName || 'main.dart') } else { // For public files, just set up a simple file structure setFiles([{ id: 'root', name: 'lib', type: 'folder', isOpen: true, children: [ { id: 'main', name: sessionFileName || 'main.dart', type: 'file', content: sessionFileContent } ] }]) } // Clear session storage after loading sessionStorage.removeItem('flutterFileContent') } } // Check every 500ms for new files const interval = setInterval(checkForNewFile, 500) return () => clearInterval(interval) }, []) // Manual save function const handleSave = async () => { if (!passkey || !activeFileName) { alert('Cannot save: No passkey set. This file is from public storage.') return } try { setLoading(true) await fetch('/api/data', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key: passkey, passkey: passkey, action: 'save_file', fileName: activeFileName, content: code }) }) setLastSaved(new Date()) } catch (error) { console.error('Save failed:', error) alert('Failed to save file') } finally { setLoading(false) } } const handleDownload = () => { const blob = new Blob([code], { type: 'text/plain' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = activeFileName || 'main.dart' a.click() URL.revokeObjectURL(url) } return (
{/* Code Editor - Conditionally Rendered */} {showEditor && (
{/* Toolbar */}
{activeFileName}
{lastSaved && ( ✓ Saved {lastSaved.toLocaleTimeString()} )}
{/* Monaco Editor */}
setCode(value || '')} options={{ minimap: { enabled: false }, fontSize: 14, fontFamily: "'JetBrains Mono', 'Fira Code', monospace", lineNumbers: 'on', scrollBeyondLastLine: false, automaticLayout: true, padding: { top: 16, bottom: 16 }, renderLineHighlight: 'all', smoothScrolling: true, cursorBlinking: 'smooth', cursorSmoothCaretAnimation: 'on' }} />
)} {/* DartPad Preview - Takes full width when editor is hidden */}
Live Preview {!showEditor && "- Full Screen"}
{/* Spacer for centering */}