import React, { useState, useRef, useEffect, useCallback } from 'react'; import { useEditorStore } from '../../store/useEditorStore'; import './FileExplorer.css'; // SVG icons — same style as EditorToolbar (stroke-based, 16x16) const IcoFile = () => ( ); const IcoHeader = () => ( ); const IcoNewFile = () => ( ); const IcoSave = () => ( ); function FileIcon({ name }: { name: string }) { const ext = name.split('.').pop()?.toLowerCase() ?? ''; if (['h', 'hpp'].includes(ext)) return ; return ; } interface ContextMenu { fileId: string; x: number; y: number; } interface FileExplorerProps { onSaveClick: () => void; } export const FileExplorer: React.FC = ({ onSaveClick }) => { const { files, activeFileId, openFile, createFile, deleteFile, renameFile } = useEditorStore(); const [contextMenu, setContextMenu] = useState(null); const [renamingId, setRenamingId] = useState(null); const [renameValue, setRenameValue] = useState(''); const [creatingFile, setCreatingFile] = useState(false); const [newFileName, setNewFileName] = useState(''); const renameInputRef = useRef(null); const newFileInputRef = useRef(null); useEffect(() => { if (renamingId && renameInputRef.current) { renameInputRef.current.focus(); renameInputRef.current.select(); } }, [renamingId]); useEffect(() => { if (creatingFile && newFileInputRef.current) { newFileInputRef.current.focus(); } }, [creatingFile]); // Close context menu on click outside useEffect(() => { if (!contextMenu) return; const handler = () => setContextMenu(null); document.addEventListener('click', handler); return () => document.removeEventListener('click', handler); }, [contextMenu]); const handleContextMenu = (e: React.MouseEvent, fileId: string) => { e.preventDefault(); e.stopPropagation(); setContextMenu({ fileId, x: e.clientX, y: e.clientY }); }; const startRename = (fileId: string) => { const file = files.find((f) => f.id === fileId); if (!file) return; setRenamingId(fileId); setRenameValue(file.name); setContextMenu(null); }; const commitRename = useCallback(() => { if (renamingId && renameValue.trim()) { renameFile(renamingId, renameValue.trim()); } setRenamingId(null); }, [renamingId, renameValue, renameFile]); const handleDelete = (fileId: string) => { setContextMenu(null); if (files.length <= 1) return; if (!window.confirm('Delete this file?')) return; deleteFile(fileId); }; const startCreateFile = () => { setCreatingFile(true); setNewFileName(''); setContextMenu(null); }; const commitCreateFile = useCallback(() => { const name = newFileName.trim(); if (name) createFile(name); setCreatingFile(false); setNewFileName(''); }, [newFileName, createFile]); return ( WORKSPACE {files.map((file) => ( openFile(file.id)} onContextMenu={(e) => handleContextMenu(e, file.id)} onDoubleClick={() => startRename(file.id)} title={`${file.name}${file.modified ? ' (unsaved)' : ''}`} > {renamingId === file.id ? ( setRenameValue(e.target.value)} onBlur={commitRename} onKeyDown={(e) => { if (e.key === 'Enter') commitRename(); if (e.key === 'Escape') setRenamingId(null); }} onClick={(e) => e.stopPropagation()} /> ) : ( {file.name} )} {file.modified && ( )} ))} {creatingFile && ( setNewFileName(e.target.value)} onBlur={commitCreateFile} onKeyDown={(e) => { if (e.key === 'Enter') commitCreateFile(); if (e.key === 'Escape') { setCreatingFile(false); setNewFileName(''); } }} onClick={(e) => e.stopPropagation()} /> )} {contextMenu && ( e.stopPropagation()} > startRename(contextMenu.fileId)}> Rename handleDelete(contextMenu.fileId)} disabled={files.length <= 1} > Delete )} ); };