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 React, { useEffect, useRef } from "react"; import clsx from "clsx"; import classes 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 useWorkspacePageOrder from "@/features/page/tree/hooks/use-workspace-page-order"; import { useNavigate, useParams } from "react-router-dom"; import { convertToTree, updateTreeNodeIcon } from "@/features/page/tree/utils"; import { useGetPagesQuery, useUpdatePageMutation, } from "@/features/page/queries/page-query"; import EmojiPicker from "@/components/ui/emoji-picker.tsx"; import { treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom"; export default function PageTree() { const { data, setData, controllers } = usePersistence>(); const [tree, setTree] = useAtom>(treeApiAtom); const { data: pageOrderData } = useWorkspacePageOrder(); const { data: pagesData, isLoading } = useGetPagesQuery(); const rootElement = useRef(); const { pageId } = useParams(); const fetchAndSetTreeData = async () => { if (pageOrderData?.childrenIds) { try { if (!isLoading) { const treeData = convertToTree(pagesData, pageOrderData.childrenIds); setData(treeData); } } catch (err) { console.error("Error fetching tree data: ", err); } } }; useEffect(() => { fetchAndSetTreeData(); }, [pageOrderData?.childrenIds, isLoading]); useEffect(() => { setTimeout(() => { tree?.select(pageId); tree?.scrollTo(pageId, "center"); }, 200); }, [tree, pageId]); return (
{(dimens) => ( setTree(t)} openByDefault={false} disableMultiSelection={true} className={classes.tree} rowClassName={classes.row} padding={15} rowHeight={30} overscanCount={5} dndRootElement={rootElement.current} > {Node} )}
); } function Node({ node, style, dragHandle }: NodeRendererProps) { const navigate = useNavigate(); const updatePageMutation = useUpdatePageMutation(); const [treeData, setTreeData] = useAtom(treeDataAtom); const handleClick = () => { navigate(`/p/${node.id}`); }; const handleUpdateNodeIcon = (nodeId, newIcon) => { const updatedTreeData = updateTreeNodeIcon(treeData, nodeId, newIcon); setTreeData(updatedTreeData); }; const handleEmojiIconClick = (e) => { e.preventDefault(); e.stopPropagation(); }; const handleEmojiSelect = (emoji) => { handleUpdateNodeIcon(node.id, emoji.native); updatePageMutation.mutateAsync({ id: node.id, icon: emoji.native }); }; const handleRemoveEmoji = () => { handleUpdateNodeIcon(node.id, null); updatePageMutation.mutateAsync({ id: node.id, icon: null }); }; if (node.willReceiveDrop && node.isClosed) { setTimeout(() => { if (node.state.willReceiveDrop) node.open(); }, 650); } return ( <>
) } removeEmojiAction={handleRemoveEmoji} />
{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 ( { e.preventDefault(); e.stopPropagation(); handleCreate(); }} > ); } function NodeMenu({ node }: { node: NodeApi }) { const [tree] = useAtom(treeApiAtom); function handleDelete() { tree?.delete(node); } return ( { e.preventDefault(); e.stopPropagation(); }} > } onClick={(e) => { e.preventDefault(); e.stopPropagation(); 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); }} /> ); }