'use client' import React, { useState, useEffect } from 'react' import Editor from '@monaco-editor/react' import { FloppyDisk, X, Minus, Square, Check, FileText, WarningCircle } from '@phosphor-icons/react' import Window from './Window' interface TextEditorProps { onClose: () => void onMinimize?: () => void onMaximize?: () => void onFocus?: () => void zIndex?: number initialContent?: string fileName?: string filePath?: string passkey?: string } export function TextEditor({ onClose, onMinimize, onMaximize, onFocus, zIndex, initialContent = '', fileName = 'untitled.txt', filePath = '', passkey = '' }: TextEditorProps) { const [code, setCode] = useState(initialContent) const [isSaving, setIsSaving] = useState(false) const [saveStatus, setSaveStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle') const [lastSaved, setLastSaved] = useState(null) const [hasChanges, setHasChanges] = useState(false) // Note: PDF compilation is not available in the web environment // Users should download .tex files and compile locally // Detect file language from extension const getLanguage = (filename: string) => { const ext = filename.split('.').pop()?.toLowerCase() switch (ext) { case 'tex': return 'latex' case 'js': case 'jsx': return 'javascript' case 'ts': case 'tsx': return 'typescript' case 'py': return 'python' case 'java': return 'java' case 'cpp': case 'cc': case 'cxx': return 'cpp' case 'c': case 'h': return 'c' case 'cs': return 'csharp' case 'json': return 'json' case 'xml': return 'xml' case 'html': case 'htm': return 'html' case 'css': return 'css' case 'md': return 'markdown' case 'sh': case 'bash': return 'shell' case 'dart': return 'dart' default: return 'plaintext' } } const language = getLanguage(fileName) const isLatexFile = language === 'latex' // Track changes useEffect(() => { if (code !== initialContent) { setHasChanges(true) setSaveStatus('idle') } }, [code, initialContent]) // Download file const handleDownload = () => { const ext = fileName.split('.').pop()?.toLowerCase() || 'txt' let mimeType = 'text/plain' const mimeTypes: Record = { 'js': 'text/javascript', 'ts': 'text/typescript', 'tsx': 'text/typescript', 'jsx': 'text/javascript', 'json': 'application/json', 'html': 'text/html', 'css': 'text/css', 'md': 'text/markdown', 'py': 'text/x-python', 'java': 'text/x-java', 'c': 'text/x-c', 'cpp': 'text/x-c++', 'dart': 'application/vnd.dart', 'tex': 'application/x-tex', 'xml': 'text/xml', 'yaml': 'text/yaml', 'yml': 'text/yaml', 'sh': 'application/x-sh', 'sql': 'application/sql' } if (mimeTypes[ext]) { mimeType = mimeTypes[ext] } const blob = new Blob([code], { type: mimeType }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = fileName a.click() URL.revokeObjectURL(url) } const handleSave = async () => { // Determine if this is a public file or secure file // If passkey is empty, we assume it's a public file (unless explicitly told otherwise, but we don't have an isPublic prop) // Ideally, we should have an isPublic prop, but for now, empty passkey implies public context in this app structure. const isPublic = !passkey if (!passkey && !isPublic) { alert('Please enter your passkey first!') return } setIsSaving(true) setSaveStatus('saving') try { const endpoint = isPublic ? '/api/public' : '/api/data' const response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'save_file', passkey: passkey, // Optional for public fileName: fileName, content: code, folder: filePath }) }) const result = await response.json() if (result.success) { setSaveStatus('saved') setLastSaved(new Date()) setHasChanges(false) // Reset status after 3 seconds setTimeout(() => { setSaveStatus('idle') }, 3000) } else { setSaveStatus('error') alert(`Error saving file: ${result.error || 'Unknown error'}`) } } catch (error) { console.error('Save error:', error) setSaveStatus('error') alert('Failed to save file. Please try again.') } finally { setIsSaving(false) } } // Keyboard shortcut: Cmd/Ctrl + S to save useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key === 's') { e.preventDefault() handleSave() } } window.addEventListener('keydown', handleKeyDown) return () => window.removeEventListener('keydown', handleKeyDown) }, [code, passkey, fileName, filePath]) return (
{/* Toolbar */}
{fileName}
{hasChanges && ( Modified )} {saveStatus === 'saved' && (
Saved {lastSaved?.toLocaleTimeString()} Saved
)} {saveStatus === 'error' && (
Save failed
)}
{/* Editor */}
setCode(value || '')} options={{ minimap: { enabled: typeof window !== 'undefined' && window.innerWidth >= 768 }, fontSize: typeof window !== 'undefined' && window.innerWidth < 640 ? 12 : 14, fontFamily: "'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace", lineNumbers: 'on', scrollBeyondLastLine: false, automaticLayout: true, padding: { top: 12, bottom: 12 }, renderLineHighlight: 'all', smoothScrolling: true, cursorBlinking: 'smooth', cursorSmoothCaretAnimation: 'on', wordWrap: 'on', wrappingIndent: 'indent', tabSize: 2, insertSpaces: true }} />
{/* Status Bar */}
{language.toUpperCase()} | {code.split('\n').length} lines | {code.length} chars
{isLatexFile && 💡 Download and compile .tex file locally}
) }