"use client"; import React, { useState } from 'react'; import { FileDocument, FolderDocument } from '@/types'; import { FileTreeItem } from './file-tree-item'; import { FolderTreeItem } from './folder-tree-item'; import { DndContext, DragEndEvent, DragOverlay, DragStartEvent, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, } from '@dnd-kit/core'; import { SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy, } from '@dnd-kit/sortable'; import { FileTypeDetector } from '@/lib/file-type-detector'; export interface FileTreeProps { files: FileDocument[]; folders: FolderDocument[]; selectedFileId?: string; selectedFolderId?: string | null; expandedFolders: Set; onFileSelect: (file: FileDocument) => void; onFolderSelect: (folderId: string | null) => void; onFolderToggle: (folderId: string) => void; onFileDelete: (fileId: string) => void; onFileRename: (fileId: string, newName: string) => void; onFileDuplicate: (fileId: string) => void; onFileDrop?: (fileId: string, targetFolderId: string | null) => void; onFolderDrop?: (folderId: string, targetFolderId: string | null) => void; } interface DragItem { id: string; type: 'file' | 'folder'; name: string; } export function FileTree({ files, folders, selectedFileId, selectedFolderId, expandedFolders, onFileSelect, onFolderSelect, onFolderToggle, onFileDelete, onFileRename, onFileDuplicate, onFileDrop, onFolderDrop }: FileTreeProps) { const [activeItem, setActiveItem] = useState(null); const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 8, }, }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }) ); type TreeNode = { type: 'file' | 'folder'; item: FileDocument | FolderDocument; children?: TreeNode[] }; // Build tree structure const buildTree = () => { const tree: TreeNode[] = []; // Add root folders first const rootFolders = folders.filter(folder => !folder.parentFolderId); const addFolderToTree = (folder: FolderDocument, parentArray: TreeNode[]) => { const folderNode = { type: 'folder' as const, item: folder, children: [] as TreeNode[] }; parentArray.push(folderNode); // Add subfolders const subfolders = folders.filter(f => f.parentFolderId === folder._id); subfolders.forEach(subfolder => addFolderToTree(subfolder, folderNode.children)); // Add files in this folder const folderFiles = files.filter(f => f.parentFolderId === folder._id); folderFiles.forEach(file => { folderNode.children.push({ type: 'file' as const, item: file }); }); }; // Add root folders and their contents rootFolders.forEach(folder => addFolderToTree(folder, tree)); // Add root files (files without parent folder) const rootFiles = files.filter(file => !file.parentFolderId); rootFiles.forEach(file => { tree.push({ type: 'file' as const, item: file }); }); return tree; }; const tree = buildTree(); const handleDragStart = (event: DragStartEvent) => { const { active } = event; const [type, id] = (active.id as string).split(':'); let name = ''; if (type === 'file') { const file = files.find(f => f._id === id); name = file?.name || ''; } else if (type === 'folder') { const folder = folders.find(f => f._id === id); name = folder?.name || ''; } setActiveItem({ id, type: type as 'file' | 'folder', name }); }; const handleDragEnd = (event: DragEndEvent) => { const { active, over } = event; setActiveItem(null); if (!over) return; const [activeType, activeId] = (active.id as string).split(':'); const [overType, overId] = (over.id as string).split(':'); // Don't allow dropping on self if (activeId === overId) return; // Handle file drop if (activeType === 'file') { const targetFolderId = overType === 'folder' ? overId : null; onFileDrop?.(activeId, targetFolderId); } // Handle folder drop if (activeType === 'folder') { const targetFolderId = overType === 'folder' ? overId : null; onFolderDrop?.(activeId, targetFolderId); } }; const renderTreeNode = (node: TreeNode, level: number = 0) => { const { type, item, children } = node; if (type === 'folder') { const folder = item as FolderDocument; const isExpanded = expandedFolders.has(folder._id); const isSelected = selectedFolderId === folder._id; return (
onFolderToggle(folder._id)} onSelect={() => onFolderSelect(folder._id)} isDragOver={false} // TODO: implement drag over state /> {isExpanded && children && children.length > 0 && (
{children.map((child: TreeNode) => renderTreeNode(child, level + 1))}
)}
); } else { const file = item as FileDocument; const isSelected = selectedFileId === file._id; return ( onFileSelect(file)} onDelete={() => onFileDelete(file._id)} onRename={(newName) => onFileRename(file._id, newName)} onDuplicate={() => onFileDuplicate(file._id)} /> ); } }; const allItems = [ ...folders.map(f => `folder:${f._id}`), ...files.map(f => `file:${f._id}`) ]; if (tree.length === 0) { return (
No files or folders
Create your first file or folder to get started
); } return (
{tree.map(node => renderTreeNode(node))}
{activeItem ? (
{activeItem.type === 'file' ? FileTypeDetector.getFileIcon( files.find(f => f._id === activeItem.id)?.type || 'txt' ) : '📁' } {activeItem.name}
) : null}
); }