Skip to content
File

Blob: src/client/components/canvas/canvas-page.tsx

typescript112 lines
1import { lazy, Suspense, type ReactNode } from "react";
2import type YProvider from "y-partyserver/provider";
3import { PageErrorState } from "@/client/components/ui/page-error-state";
4import { PageTitleSection } from "@/client/components/ui/page-title-section";
5import { Skeleton } from "@/client/components/ui/skeleton";
6import {
7 CANVAS_PAGE_BODY_CENTERED_CLASS,
8 CANVAS_PAGE_BODY_STAGE_CLASS,
9 type CanvasStageLayout,
10 PAGE_SHELL_CLASS,
11 PAGE_STAGE_CLASS,
12 PAGE_STAGE_TRACKS_CLASS,
13} from "@/client/components/ui/page-layout";
14import type { CanvasAffordance } from "@/client/lib/affordance/canvas";
15import { useCanvasSession } from "./use-canvas-session";
16 
17const CANVAS_LOAD_ERROR_MESSAGE = "This canvas didn't load. Your connection might be flaky.";
18 
19const CanvasSurface = lazy(() => import("./canvas-surface").then((mod) => ({ default: mod.CanvasSurface })));
20 
21interface CanvasPageProps {
22 pageId: string;
23 initialTitle: string;
24 onTitleChange?: (title: string) => void;
25 onProvider?: (provider: YProvider | null) => void;
26 shareToken?: string;
27 workspaceId?: string;
28 affordance: CanvasAffordance;
29 layout?: CanvasStageLayout;
30 chrome: ReactNode;
31}
32 
33export function CanvasPage({
34 pageId,
35 initialTitle,
36 onTitleChange,
37 onProvider,
38 shareToken,
39 workspaceId,
40 affordance,
41 layout = "centered",
42 chrome,
43}: CanvasPageProps) {
44 const session = useCanvasSession({
45 pageId,
46 initialTitle,
47 onTitleChange,
48 onProvider,
49 shareToken,
50 workspaceId,
51 });
52 
53 const titleProps = {
54 title: session.title,
55 onInput: session.onTitleInput,
56 disabled: session.kind !== "ready",
57 readOnly: !affordance.canEdit,
58 };
59 
60 const body =
61 session.kind === "error" ? (
62 <div className="mx-auto max-w-3xl">
63 <PageErrorState
64 message={CANVAS_LOAD_ERROR_MESSAGE}
65 className="min-h-[12rem]"
66 action={{ label: "Retry", onClick: session.onRetry }}
67 />
68 </div>
69 ) : session.kind === "ready" ? (
70 <Suspense fallback={<CanvasModuleLoading />}>
71 <CanvasSurface
72 session={session}
73 canEdit={affordance.canEdit}
74 canInsertImages={affordance.canInsertImages}
75 pageId={pageId}
76 workspaceId={workspaceId}
77 shareToken={shareToken}
78 />
79 </Suspense>
80 ) : (
81 <div className="mx-auto max-w-3xl space-y-3 pl-7">
82 <Skeleton className="h-4 w-full" />
83 <Skeleton className="h-4 w-5/6" />
84 <Skeleton className="h-4 w-3/6" />
85 </div>
86 );
87 
88 return (
89 <div className={PAGE_SHELL_CLASS}>
90 <div className={PAGE_STAGE_CLASS}>
91 <div className={layout === "stage" ? PAGE_STAGE_TRACKS_CLASS : undefined}>
92 <div className="min-w-0">
93 {chrome}
94 <PageTitleSection {...titleProps} />
95 </div>
96 </div>
97 <div className={layout === "stage" ? CANVAS_PAGE_BODY_STAGE_CLASS : CANVAS_PAGE_BODY_CENTERED_CLASS}>
98 {body}
99 </div>
100 </div>
101 </div>
102 );
103}
104 
105function CanvasModuleLoading() {
106 return (
107 <div className="mt-6 flex h-[70vh] items-center justify-center rounded-lg border border-zinc-800 bg-zinc-950">
108 <div className="text-sm text-zinc-500">Loading canvas…</div>
109 </div>
110 );
111}