File
Blob: src/client/components/sidebar/page-tree.tsx
| 1 | import { useMemo } from "react"; |
| 2 | import { useParams } from "@tanstack/react-router"; |
| 3 | import { useWorkspacePages, useWorkspaceRole } from "@/client/components/workspace/use-workspace-view"; |
| 4 | import { useOnline } from "@/client/hooks/use-online"; |
| 5 | import { buildPageTreeIndex } from "@/client/lib/page-tree-model"; |
| 6 | import { PageTreeItem } from "./page-tree-item"; |
| 7 | |
| 8 | interface PageTreeProps { |
| 9 | alwaysShowActions?: boolean; |
| 10 | menuZIndex?: number; |
| 11 | } |
| 12 | |
| 13 | const EMPTY_CHILDREN: readonly never[] = []; |
| 14 | |
| 15 | export function PageTree({ alwaysShowActions = false, menuZIndex }: PageTreeProps) { |
| 16 | const pages = useWorkspacePages(); |
| 17 | const params = useParams({ strict: false }) as { pageId?: string }; |
| 18 | const online = useOnline(); |
| 19 | const workspaceRole = useWorkspaceRole() ?? "none"; |
| 20 | |
| 21 | const index = useMemo(() => buildPageTreeIndex(pages), [pages]); |
| 22 | const activePages = index.activePages; |
| 23 | |
| 24 | const activeAncestorIds = useMemo(() => { |
| 25 | const ids = new Set<string>(); |
| 26 | if (!params.pageId) return ids; |
| 27 | let cur = index.byId.get(params.pageId); |
| 28 | while (cur?.parent_id) { |
| 29 | ids.add(cur.parent_id); |
| 30 | cur = index.byId.get(cur.parent_id); |
| 31 | } |
| 32 | return ids; |
| 33 | }, [params.pageId, index]); |
| 34 | |
| 35 | const rootPages = index.childrenByParent.get(null) ?? EMPTY_CHILDREN; |
| 36 | |
| 37 | if (activePages.length === 0) { |
| 38 | return <div className="px-3 py-4 text-center text-xs text-zinc-400">No pages yet</div>; |
| 39 | } |
| 40 | |
| 41 | return ( |
| 42 | <div className="relative min-h-full space-y-0.5 py-1"> |
| 43 | {rootPages.map((page, i) => ( |
| 44 | <div |
| 45 | key={page.id} |
| 46 | className="opacity-0 animate-slide-up" |
| 47 | style={{ animationDelay: `${Math.min(i, 7) * 60}ms` }} |
| 48 | > |
| 49 | <PageTreeItem |
| 50 | page={page} |
| 51 | depth={0} |
| 52 | index={index} |
| 53 | allPages={activePages} |
| 54 | alwaysShowActions={alwaysShowActions} |
| 55 | activeAncestorIds={activeAncestorIds} |
| 56 | menuZIndex={menuZIndex} |
| 57 | workspaceRole={workspaceRole} |
| 58 | online={online} |
| 59 | /> |
| 60 | </div> |
| 61 | ))} |
| 62 | </div> |
| 63 | ); |
| 64 | } |