import { NodeApi, NodeRendererProps, Tree, TreeApi } from "react-arborist"; import { useAtom } from "jotai"; import { treeApiAtom } from "@/features/page/tree/atoms/tree-api-atom.ts"; import { useGetRootSidebarPagesQuery, useGetSidebarPagesQuery, useUpdatePageMutation, } from "@/features/page/queries/page-query.ts"; import React, { useEffect, useRef } from "react"; import { useNavigate, useParams } from "react-router-dom"; import classes from "@/features/page/tree/styles/tree.module.css"; import { FillFlexParent } from "@/features/page/tree/components/fill-flex-parent.tsx"; import { ActionIcon, Menu, rem } from "@mantine/core"; import { IconChevronDown, IconChevronRight, IconDotsVertical, IconFileDescription, IconLink, IconPlus, IconPointFilled, IconStar, IconTrash, } from "@tabler/icons-react"; import { treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom.ts"; import clsx from "clsx"; import EmojiPicker from "@/components/ui/emoji-picker.tsx"; import { useTreeMutation } from "@/features/page/tree/hooks/use-tree-mutation.ts"; import { buildTree, updateTreeNodeIcon, } from "@/features/page/tree/utils/utils.ts"; import { SpaceTreeNode } from "@/features/page/tree/types.ts"; import { getSidebarPages } from "@/features/page/services/page-service.ts"; import { QueryClient } from "@tanstack/react-query"; import { SidebarPagesParams } from "@/features/page/types/page.types.ts"; interface SpaceTreeProps { spaceId: string; } export default function SpaceTree({ spaceId }: SpaceTreeProps) { const { data, setData, controllers } = useTreeMutation>(spaceId); const [treeAPi, setTreeApi] = useAtom>(treeApiAtom); const { data: pagesData, hasNextPage, fetchNextPage, isFetching, } = useGetRootSidebarPagesQuery({ spaceId, }); const rootElement = useRef(); const { pageId } = useParams(); useEffect(() => { if (hasNextPage && !isFetching) { fetchNextPage(); } }, [hasNextPage, fetchNextPage, isFetching]); useEffect(() => { if (pagesData?.pages && !hasNextPage) { const allItems = pagesData.pages.flatMap((page) => page.items); const treeData = buildTree(allItems); setData(treeData); } }, [pagesData, hasNextPage]); useEffect(() => { setTimeout(() => { treeAPi?.select(pageId); treeAPi?.scrollTo(pageId, "auto"); }, 200); }, [treeAPi, pageId]); return (
{(dimens) => ( setTreeApi(t)} openByDefault={false} disableMultiSelection={true} className={classes.tree} rowClassName={classes.row} // padding={15} rowHeight={30} overscanCount={8} dndRootElement={rootElement.current} selectionFollowsFocus > {Node} )}
); } const queryClient = new QueryClient(); function Node({ node, style, dragHandle }: NodeRendererProps) { const navigate = useNavigate(); const updatePageMutation = useUpdatePageMutation(); //const use = useGetExpandPageTreeQuery() const [treeData, setTreeData] = useAtom(treeDataAtom); function updateTreeData( treeItems: SpaceTreeNode[], nodeId: string, children: SpaceTreeNode[], ) { return treeItems.map((nodeItem) => { if (nodeItem.id === nodeId) { return { ...nodeItem, children }; } if (nodeItem.children) { return { ...nodeItem, children: updateTreeData(nodeItem.children, nodeId, children), }; } return nodeItem; }); } async function handleLoadChildren(node: NodeApi) { if (!node.data.hasChildren) return; if (node.data.children && node.data.children.length > 0) { return; } try { const params: SidebarPagesParams = { pageId: node.data.id, spaceId: node.data.spaceId, }; const newChildren = await queryClient.fetchQuery({ queryKey: ["sidebar-pages", params], queryFn: () => getSidebarPages(params), }); const childrenTree = buildTree(newChildren.items); const updatedTreeData = updateTreeData( treeData, node.data.id, childrenTree, ); setTreeData(updatedTreeData); } catch (error) { console.error("Failed to fetch children:", error); } } const handleClick = () => { navigate(`/p/${node.id}`); }; const handleUpdateNodeIcon = (nodeId: string, newIcon: string) => { const updatedTree = updateTreeNodeIcon(treeData, node.id, newIcon); setTreeData(updatedTree); }; const handleEmojiIconClick = (e) => { e.preventDefault(); e.stopPropagation(); }; const handleEmojiSelect = (emoji: { native: string }) => { handleUpdateNodeIcon(node.id, emoji.native); updatePageMutation.mutateAsync({ pageId: node.id, icon: emoji.native }); }; const handleRemoveEmoji = () => { handleUpdateNodeIcon(node.id, null); updatePageMutation.mutateAsync({ pageId: node.id, icon: null }); }; if (node.willReceiveDrop && node.isClosed) { handleLoadChildren(node); setTimeout(() => { if (node.state.willReceiveDrop) node.open(); }, 650); } return ( <>
handleLoadChildren(node)} />
) } removeEmojiAction={handleRemoveEmoji} />
{node.data.name || "untitled"}
handleLoadChildren(node)} />
); } interface CreateNodeProps { node: NodeApi; onExpandTree?: () => void; } function CreateNode({ node, onExpandTree }: CreateNodeProps) { const [treeApi] = useAtom(treeApiAtom); function handleCreate() { if (node.data.hasChildren && node.children.length === 0) { node.toggle(); onExpandTree(); setTimeout(() => { treeApi?.create({ type: "internal", parentId: node.id, index: 0 }); }, 500); } else { treeApi?.create({ type: "internal", parentId: node.id }); } } return ( { e.preventDefault(); e.stopPropagation(); handleCreate(); }} > ); } function NodeMenu({ node }: { node: NodeApi }) { const [treeApi] = useAtom(treeApiAtom); return ( { e.preventDefault(); e.stopPropagation(); }} > } > Favorite } > Copy link } onClick={() => treeApi?.delete(node)} > Delete ); } interface PageArrowProps { node: NodeApi; onExpandTree?: () => void; } function PageArrow({ node, onExpandTree }: PageArrowProps) { return ( { e.preventDefault(); e.stopPropagation(); node.toggle(); onExpandTree(); }} > {node.isInternal ? ( node.children && (node.children.length > 0 || node.data.hasChildren) ? ( node.isOpen ? ( ) : ( ) ) : ( ) ) : null} ); }