"use client"; import { DocumentIcon, FolderIcon } from "@heroicons/react/24/outline"; import { useState } from "react"; import React from "react"; import { wrapFieldsWithMeta } from "tinacms"; import { DRAG_STATE_RESET, type DragState, type FileItem, FileTreeItem, type TreeNode, } from "./file-structure.item"; // Convert flat array to tree structure const buildTree = (items: FileItem[]): TreeNode[] => { const itemMap = new Map(); const rootItems: TreeNode[] = []; for (const item of items) { itemMap.set(item.id, { ...item, children: [], level: 0, }); } for (const item of items) { const node = itemMap.get(item.id); if (!node) continue; if (item.parentId === null) { rootItems.push(node); } else { const parent = itemMap.get(item.parentId); if (parent) { parent.children.push(node); node.level = parent.level + 1; } } } return rootItems; }; export const FileStructureField = wrapFieldsWithMeta(({ input }) => { const items: FileItem[] = input.value || []; const [expandedFolders, setExpandedFolders] = useState>( new Set() ); const [editingId, setEditingId] = useState(null); const [dragState, setDragState] = useState(DRAG_STATE_RESET); const toggleFolder = (id: string) => { setExpandedFolders((prev) => prev.has(id) ? new Set([...prev].filter((item) => item !== id)) : new Set(prev).add(id) ); }; const isValidDrop = (draggedId: string, targetId: string): boolean => { if (draggedId === targetId) return false; const draggedItem = items.find((item) => item.id === draggedId); if (draggedItem?.type !== "folder") return true; // Check if target is a descendant of dragged folder const isDescendant = (parentId: string, checkId: string): boolean => items.some( (item) => item.parentId === parentId && (item.id === checkId || isDescendant(item.id, checkId)) ); return !isDescendant(draggedId, targetId); }; const moveItem = ( draggedId: string, targetId: string | null, position: "before" | "after" | "inside" ) => { if (!isValidDrop(draggedId, targetId || "")) return; const draggedItem = items.find((item) => item.id === draggedId); const targetItem = targetId ? items.find((item) => item.id === targetId) : null; if (!draggedItem || !targetItem) return; const filteredItems = items.filter((item) => item.id !== draggedId); const targetIndex = filteredItems.findIndex((item) => item.id === targetId); if (targetIndex === -1) return; let insertIndex: number; let newParentId: string | null; if (position === "inside" && targetItem.type === "folder" && targetId) { newParentId = targetId; setExpandedFolders((prev) => new Set(prev).add(targetId)); // Insert after last child or after target folder const lastChild = filteredItems.findLastIndex( (item) => item.parentId === targetId ); insertIndex = lastChild !== -1 ? lastChild + 1 : targetIndex + 1; } else { newParentId = targetItem.parentId; insertIndex = position === "before" ? targetIndex : targetIndex + 1; } filteredItems.splice(insertIndex, 0, { ...draggedItem, parentId: newParentId, }); input.onChange(filteredItems); }; const addItem = (parentId: string | null, type: "file" | "folder") => { const newItem: FileItem = { id: Math.random().toString(36).substr(2, 9), name: type === "folder" ? "New Folder" : "new-file.txt", type, parentId, }; input.onChange([...items, newItem]); setEditingId(newItem.id); if (parentId) { setExpandedFolders((prev) => new Set(prev).add(parentId)); } }; const updateItem = (id: string, updates: Partial) => { input.onChange( items.map((item) => (item.id === id ? { ...item, ...updates } : item)) ); }; const deleteItem = (id: string) => { const getChildIds = (parentId: string): string[] => items .filter((item) => item.parentId === parentId) .flatMap((child) => [child.id, ...getChildIds(child.id)]); const idsToDelete = new Set([id, ...getChildIds(id)]); input.onChange(items.filter((item) => !idsToDelete.has(item.id))); }; const treeNodes = buildTree(items); return (
{/* Header */}
{/* File Tree */}
{treeNodes.length === 0 ? (
No files or folders yet
Click "Add Folder" or "Add File" to get started
) : ( treeNodes.map((node) => (
)) )}
{/* Current items count */}
{items.length} item{items.length !== 1 ? "s" : ""} total
); });