'use client'; import { NodeApi, NodeRendererProps, Tree, TreeApi } from 'react-arborist'; import { IconArrowsLeftRight, IconChevronDown, IconChevronRight, IconCornerRightUp, IconDotsVertical, IconEdit, IconFileDescription, IconLink, IconPlus, IconStar, IconTrash, } from '@tabler/icons-react'; import { useEffect } from 'react'; import clsx from 'clsx'; import styles from './styles/tree.module.css'; import { ActionIcon, Menu, rem } from '@mantine/core'; import { useAtom } from 'jotai'; import { FillFlexParent } from './components/fill-flex-parent'; import { TreeNode } from './types'; import { treeApiAtom } from './atoms/tree-api-atom'; import { usePersistence } from '@/features/page/tree/hooks/use-persistence'; import { IPage } from '@/features/page/types/page.types'; import { getPages } from '@/features/page/services/page-service'; import useWorkspacePageOrder from '@/features/page/tree/hooks/use-workspace-page-order'; import { usePathname, useRouter } from 'next/navigation'; export default function PageTree() { const { data, setData, controllers } = usePersistence>(); const [tree, setTree] = useAtom>(treeApiAtom); const { data: pageOrderData } = useWorkspacePageOrder(); const pathname = usePathname(); const fetchAndSetTreeData = async () => { if (pageOrderData?.childrenIds) { try { const pages = await getPages(); const treeData = convertToTree(pages, pageOrderData.childrenIds); setData(treeData); } catch (err) { console.error('Error fetching tree data: ', err); } } }; useEffect(() => { fetchAndSetTreeData(); }, [pageOrderData?.childrenIds]); useEffect(() => { const pageId = pathname?.split('/')[2]; setTimeout(() => { tree?.select(pageId); }, 100); }, [tree, pathname]); return (
{(dimens) => ( setTree(t)} openByDefault={false} disableMultiSelection={true} className={styles.tree} rowClassName={styles.row} padding={15} rowHeight={30} overscanCount={5} > {Node} )}
); } function Node({ node, style, dragHandle }: NodeRendererProps) { const router = useRouter(); const handleClick = () => { router.push(`/p/${node.id}`); } if (node.willReceiveDrop && node.isClosed){ setTimeout(() => { if (node.state.willReceiveDrop) node.open(); }, 650); } return ( <>
{node.isEditing ? ( ) : ( node.data.name || 'untitled' )}
); } function CreateNode({ node }: { node: NodeApi }) { const [tree] = useAtom(treeApiAtom); function handleCreate() { tree?.create({ type: 'internal', parentId: node.id, index: 0 }); } return ( ); } function NodeMenu({ node }: { node: NodeApi }) { const [tree] = useAtom(treeApiAtom); function handleDelete() { tree?.delete(node); } return ( } onClick={() => node.edit()} > Rename } > Favorite } > Copy link } > Move } > Archive } onClick={() => handleDelete()} > Delete ); } function PageArrow({ node }: { node: NodeApi }) { return ( { e.preventDefault(); e.stopPropagation(); node.toggle(); }}> {node.isInternal ? ( node.children && node.children.length > 0 ? ( node.isOpen ? ( ) : ( ) ) : ( ) ) : null} ); } function Input({ node }: { node: NodeApi }) { return ( e.currentTarget.select()} onBlur={() => node.reset()} onKeyDown={(e) => { if (e.key === 'Escape') node.reset(); if (e.key === 'Enter') node.submit(e.currentTarget.value); }} /> ); } function convertToTree(pages: IPage[], pageOrder: string[]): TreeNode[] { const pageMap: { [id: string]: IPage } = {}; pages.forEach(page => { pageMap[page.id] = page; }); function buildTreeNode(id: string): TreeNode | undefined { const page = pageMap[id]; if (!page) return; const node: TreeNode = { id: page.id, name: page.title, children: [], }; if (page.icon) node.icon = page.icon; if (page.childrenIds && page.childrenIds.length > 0) { node.children = page.childrenIds.map(childId => buildTreeNode(childId)).filter(Boolean) as TreeNode[]; } return node; } return pageOrder.map(id => buildTreeNode(id)).filter(Boolean) as TreeNode[]; }