Show nested canvas directory and reduce canvas loading overhead
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
"use client";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { ChevronRight, Network } from "lucide-react";
|
||||
import { activeBoardAncestors, boardDirectory, filterBoardDirectory, type BoardBranch } from "@/lib/board-directory";
|
||||
import { boardHref, matchesRef, type Board } from "@/lib/workspace";
|
||||
import styles from "./workspace.module.css";
|
||||
|
||||
export function BoardDirectory({ boards, workspace, active, query, demo }: { boards: Board[]; workspace: string; active?: string; query: string; demo: boolean }) {
|
||||
const [closed, setClosed] = useState(new Set<string>());
|
||||
const branches = useMemo(() => filterBoardDirectory(boardDirectory(boards), query), [boards, query]);
|
||||
useEffect(() => {
|
||||
const path = activeBoardAncestors(boards, active);
|
||||
setClosed((previous) => { const next = new Set(previous); for (const id of path) next.delete(id); return next; });
|
||||
}, [active, boards]);
|
||||
function render(items: BoardBranch[], depth = 0) {
|
||||
return <ul className={styles.directoryList}>{items.map(({ board, children }) => {
|
||||
const expanded = !!query.trim() || !closed.has(board.id);
|
||||
return <li key={board.id}><div className={styles.directoryRow} style={{ paddingLeft: Math.min(depth, 8) * 16 }}>
|
||||
{children.length ? <button type="button" className={styles.directoryToggle} aria-label={`${expanded ? "收起" : "展开"}${board.title}的子画布`} aria-expanded={expanded} onClick={() => setClosed((previous) => { const next = new Set(previous); if (expanded) next.add(board.id); else next.delete(board.id); return next; })}><ChevronRight size={13} style={{ transform: expanded ? "rotate(90deg)" : undefined }} /></button> : <span className={styles.directorySpacer} />}
|
||||
<Link className={styles.pageLink} aria-current={matchesRef(board, active) ? "page" : undefined} href={boardHref(workspace, board.slug || board.id, demo)}><Network size={15} strokeWidth={1.6} aria-hidden="true" /><span>{board.title}</span></Link>
|
||||
</div>{expanded && children.length > 0 && render(children, depth + 1)}</li>;
|
||||
})}</ul>;
|
||||
}
|
||||
return branches.length ? render(branches) : query ? <p className={styles.hint}>没有找到相关页面</p> : null;
|
||||
}
|
||||
@@ -9,7 +9,8 @@ import { loadBoardCanvas } from "@/lib/canvas-loader";
|
||||
import type { CanvasSnapshot } from "@/lib/canvas-sync";
|
||||
import styles from "./workspace.module.css";
|
||||
import { useActiveBoard } from "./active-board";
|
||||
import GraphCanvas from "@/components/canvas/graph-canvas";
|
||||
import dynamic from "next/dynamic";
|
||||
const GraphCanvas = dynamic(() => import("@/components/canvas/graph-canvas"), { ssr: false, loading: () => <p role="status">正在准备画布…</p> });
|
||||
|
||||
type Loaded = { board: Board; snapshot?: CanvasSnapshot; route: string };
|
||||
export default function GraphPage({ workspaceId, boardId }: { workspaceId: string; boardId: string }) {
|
||||
@@ -22,6 +23,8 @@ export default function GraphPage({ workspaceId, boardId }: { workspaceId: strin
|
||||
const [retry, setRetry] = useState(0);
|
||||
useEffect(() => {
|
||||
setLoaded(null); setError("");
|
||||
// Fetch data while the much larger editor chunk loads.
|
||||
void import("@/components/canvas/graph-canvas").catch(() => {});
|
||||
if (demo) {
|
||||
const item = readDemoBoards(workspaceId).find((board) => matchesRef(board, boardId));
|
||||
if (item) setLoaded({ board: item, route });
|
||||
|
||||
@@ -124,3 +124,12 @@
|
||||
.floatingAvatarButton .avatar { flex-basis: var(--toolbar-icon-size, 20px); width: var(--toolbar-icon-size, 20px); height: var(--toolbar-icon-size, 20px); font-size: 14px; }
|
||||
.floatingTitle { flex: 1 1 auto; min-width: 0; max-width: 240px; margin: 0; color: #37352f; font-size: 14px; line-height: var(--toolbar-control-height, 32px); font-weight: 500; overflow: visible; }
|
||||
@media (max-width: 700px) { .floatingTitle { max-width: 180px; } }
|
||||
|
||||
.directoryList { list-style: none; padding: 0; margin: 0; }
|
||||
.directoryRow { display: flex; align-items: center; min-width: 0; }
|
||||
.directoryRow .pageLink { flex: 1; min-width: 0; padding-left: 3px; }
|
||||
.directoryToggle, .directorySpacer { flex: 0 0 20px; width: 20px; height: 26px; }
|
||||
.directoryToggle { display: grid; place-items: center; border: 0; border-radius: 4px; background: transparent; color: #87837c; cursor: pointer; }
|
||||
.directoryToggle:hover { background: #eae7e2; }
|
||||
.directoryToggle:focus-visible { outline: 2px solid #9b877a; outline-offset: -2px; }
|
||||
.directoryToggle svg { transition: transform 120ms ease; }
|
||||
|
||||
Reference in New Issue
Block a user