import { ChevronDown, FilePlus, FileText, Folder, FolderPlus, ListCollapse, RefreshCw, Search, X } from 'lucide-react' import { Fragment } from 'react' import type React from 'react' type WorkspaceTreeItem = | { kind: 'folder'; name: string; path: string; parent?: string; depth: number } | { kind: 'file'; name: string; path: string; parent?: string; depth: number } type NewFileDraft = { kind: 'file' | 'folder' parent: string afterFile: string | null depth: number value: string } type TreeContextMenu = { path: string kind: 'file' | 'folder' x: number y: number } type TreeClipboard = { path: string kind: 'file' | 'folder' mode: 'cut' | 'copy' } type SearchResult = { file: string lineNumber: number lineText: string } type WorkspaceFilePanelProps = { dark: boolean workspaceActive: boolean searchActive: boolean exampleActive: boolean selectedModuleName?: string borderSoft: string textMid: string textDim: string workspaceRootPath: string workspaceRootLabel: string currentSelectedTreePath: string | null currentActiveEditorFile: string | null openProjectFolders: Record visibleProjectTreeItems: WorkspaceTreeItem[] newFileDraft: NewFileDraft | null moduleFileTreeLoading: boolean treeContextMenu: TreeContextMenu | null treeClipboard: TreeClipboard | null moduleSearchInputRef: React.RefObject treeContextMenuRef: React.RefObject moduleSearchQuery: string activeModuleSearchQuery: string moduleSearchRunning: boolean moduleSearchResults: SearchResult[] onStartCreateFile: (kind: 'file' | 'folder') => void onRefreshModuleFiles: () => void onCollapseModuleFolders: () => void onSearchChange: (value: string) => void onOpenSearchResult: (file: string, lineNumber: number) => void onSelectRoot: () => void onRootContextMenu: (x: number, y: number) => void onDraftChange: (value: string) => void onCommitCreateFile: () => void onCancelCreateFile: () => void onSelectFolder: (path: string) => void onFolderContextMenu: (path: string, x: number, y: number) => void onOpenFile: (path: string) => void onFileContextMenu: (path: string, x: number, y: number) => void onOpenTreeContextTarget: () => void onCutTreeTarget: () => void onCopyTreeTarget: () => void onPasteTreeClipboard: () => void onCopyTreePath: () => void onRenameTreeTarget: () => void onDeleteTreeTarget: () => void } function toolbarTooltipClass(dark: boolean): string { return [ 'pointer-events-none absolute right-0 top-7 z-50 hidden whitespace-nowrap rounded-md border px-2 py-1 text-[11px] font-semibold shadow-[0_10px_24px_rgba(0,0,0,0.24)] group-hover:block', dark ? 'border-white/10 bg-[#303030] text-white/78' : 'border-[#d8dee9] bg-white text-[#4f596b]' ].join(' ') } function fileDraftInputClass(dark: boolean): string { return [ 'h-5 min-w-0 flex-1 rounded border px-1 text-[12px] outline-none', dark ? 'border-[#2f8dff]/60 bg-[#1f1f1f] text-white' : 'border-[#2f8dff]/60 bg-white text-[#111520]' ].join(' ') } function FileDraftInput({ dark, draft, onChange, onCommit, onCancel }: { dark: boolean draft: NewFileDraft onChange: (value: string) => void onCommit: () => void onCancel: () => void }): React.JSX.Element { return ( onChange(event.target.value)} onBlur={onCommit} onKeyDown={(event) => { if (event.key === 'Enter') onCommit() if (event.key === 'Escape') onCancel() }} className={fileDraftInputClass(dark)} /> ) } function DraftRow({ dark, draft, depth, onChange, onCommit, onCancel }: { dark: boolean draft: NewFileDraft depth: number onChange: (value: string) => void onCommit: () => void onCancel: () => void }): React.JSX.Element { return (
{draft.kind === 'folder' ? : } {draft.kind === 'folder' ? : }
) } export default function WorkspaceFilePanel({ dark, workspaceActive, searchActive, exampleActive, selectedModuleName, borderSoft, textMid, textDim, workspaceRootPath, workspaceRootLabel, currentSelectedTreePath, currentActiveEditorFile, openProjectFolders, visibleProjectTreeItems, newFileDraft, moduleFileTreeLoading, treeContextMenu, treeClipboard, moduleSearchInputRef, treeContextMenuRef, moduleSearchQuery, activeModuleSearchQuery, moduleSearchRunning, moduleSearchResults, onStartCreateFile, onRefreshModuleFiles, onCollapseModuleFolders, onSearchChange, onOpenSearchResult, onSelectRoot, onRootContextMenu, onDraftChange, onCommitCreateFile, onCancelCreateFile, onSelectFolder, onFolderContextMenu, onOpenFile, onFileContextMenu, onOpenTreeContextTarget, onCutTreeTarget, onCopyTreeTarget, onPasteTreeClipboard, onCopyTreePath, onRenameTreeTarget, onDeleteTreeTarget }: WorkspaceFilePanelProps): React.JSX.Element { return ( ) } function SearchPanel({ dark, textMid, textDim, selectedModuleName, moduleSearchInputRef, moduleSearchQuery, activeModuleSearchQuery, moduleSearchRunning, moduleSearchResults, onSearchChange, onOpenSearchResult }: Pick): React.JSX.Element { return (
onSearchChange(event.target.value)} placeholder="검색" className={['min-w-0 flex-1 bg-transparent text-[12px] font-medium outline-none', dark ? 'text-white/75 placeholder:text-white/30' : 'text-[#111520] placeholder:text-[#8a94a5]'].join(' ')} /> {moduleSearchQuery && ( )}
{!moduleSearchQuery.trim() ? (

검색어를 입력하세요.

) : moduleSearchQuery.trim().length < 2 ? (

2글자 이상 입력하세요.

) : moduleSearchQuery.trim() !== activeModuleSearchQuery ? (

검색 중...

) : moduleSearchRunning ? (

검색 중...

) : moduleSearchResults.length === 0 ? (

검색 결과가 없습니다.

) : ( moduleSearchResults.map((result) => { const query = activeModuleSearchQuery const matchIndex = result.lineText.toLowerCase().indexOf(query.toLowerCase()) const beforeMatch = matchIndex >= 0 ? result.lineText.slice(0, matchIndex) : result.lineText const matchText = matchIndex >= 0 ? result.lineText.slice(matchIndex, matchIndex + query.length) : '' const afterMatch = matchIndex >= 0 ? result.lineText.slice(matchIndex + query.length) : '' return ( ) }) )}
) } function ExplorerPanel(props: Omit): React.JSX.Element { const { dark, textMid, exampleActive, workspaceRootPath, workspaceRootLabel, currentSelectedTreePath, currentActiveEditorFile, openProjectFolders, visibleProjectTreeItems, newFileDraft, moduleFileTreeLoading, treeContextMenu, treeClipboard, treeContextMenuRef, onSelectRoot, onRootContextMenu, onDraftChange, onCommitCreateFile, onCancelCreateFile, onSelectFolder, onFolderContextMenu, onOpenFile, onFileContextMenu, onOpenTreeContextTarget, onCutTreeTarget, onCopyTreeTarget, onPasteTreeClipboard, onCopyTreePath, onRenameTreeTarget, onDeleteTreeTarget } = props return (
{newFileDraft && !newFileDraft.afterFile && newFileDraft.parent === workspaceRootPath && ( )} {moduleFileTreeLoading && (
{[0, 1, 2].map((item) => (
))}
)}
{visibleProjectTreeItems.map((item) => ( item.kind === 'folder' ? ( ) : ( ) ))}
{treeContextMenu && ( )}
) } function FolderRow({ dark, item, active, open, exampleActive, newFileDraft, onSelectFolder, onFolderContextMenu, onDraftChange, onCommitCreateFile, onCancelCreateFile }: { dark: boolean item: Extract active: boolean open: boolean exampleActive: boolean newFileDraft: NewFileDraft | null onSelectFolder: (path: string) => void onFolderContextMenu: (path: string, x: number, y: number) => void onDraftChange: (value: string) => void onCommitCreateFile: () => void onCancelCreateFile: () => void }): React.JSX.Element { return ( {newFileDraft && !newFileDraft.afterFile && newFileDraft.parent === item.path && ( )} ) } function FileRow({ dark, item, active, exampleActive, newFileDraft, onOpenFile, onFileContextMenu, onDraftChange, onCommitCreateFile, onCancelCreateFile }: { dark: boolean item: Extract active: boolean exampleActive: boolean newFileDraft: NewFileDraft | null onOpenFile: (path: string) => void onFileContextMenu: (path: string, x: number, y: number) => void onDraftChange: (value: string) => void onCommitCreateFile: () => void onCancelCreateFile: () => void }): React.JSX.Element { return ( {newFileDraft?.afterFile === item.path && ( )} ) } function TreeContextMenu({ dark, menu, menuRef, treeClipboard, workspaceRootPath, onOpen, onCut, onCopy, onPaste, onCopyPath, onRename, onDelete }: { dark: boolean menu: TreeContextMenu menuRef: React.RefObject treeClipboard: TreeClipboard | null workspaceRootPath: string onOpen: () => void onCut: () => void onCopy: () => void onPaste: () => void onCopyPath: () => void onRename: () => void onDelete: () => void }): React.JSX.Element { const menuItems = [ { label: '열기', action: onOpen, disabled: false, separatorAfter: true }, { label: '자르기', action: onCut, disabled: menu.path === workspaceRootPath }, { label: '복사', action: onCopy, disabled: menu.path === workspaceRootPath }, { label: '붙여넣기', action: onPaste, disabled: !treeClipboard || (treeClipboard ? menu.path === treeClipboard.path || menu.path.startsWith(`${treeClipboard.path}/`) : false), separatorAfter: true }, { label: '경로 복사', action: onCopyPath, disabled: false, separatorAfter: true }, { label: '이름 변경', action: onRename, disabled: menu.path === workspaceRootPath }, { label: '삭제', action: onDelete, disabled: menu.path === workspaceRootPath } ] return (
{menuItems.map(({ label, action, disabled, separatorAfter }) => (
{separatorAfter &&
}
))}
) }