"use client"; import * as React from "react"; import dynamic from "next/dynamic"; import type { Editor, TLStore } from "tldraw"; import { useCollaboration } from "@/lib/yjs-provider"; import { cn } from "@/lib/utils"; import { WhiteboardToolbar } from "./toolbar"; import { syncTldrawStoreWithYjs } from "./yjs-store"; import { customShapeUtils } from "./shapes"; import "tldraw/tldraw.css"; export type WhiteboardCanvasProps = { documentId?: string; className?: string; readOnly?: boolean; }; function useHtmlDarkClass(): boolean { const [dark, setDark] = React.useState(false); React.useEffect(() => { const el = document.documentElement; const read = () => setDark(el.classList.contains("dark")); read(); const observer = new MutationObserver(read); observer.observe(el, { attributes: true, attributeFilter: ["class"] }); return () => observer.disconnect(); }, []); return dark; } type TldrawModule = typeof import("tldraw"); function TldrawCollabBody({ documentId, readOnly, className, tldraw, }: { documentId: string; readOnly: boolean; className?: string; tldraw: TldrawModule; }) { const { doc, isConnected, isSynced } = useCollaboration(`object:${documentId}`); const dark = useHtmlDarkClass(); const store = React.useMemo( () => tldraw.createTLStore({ shapeUtils: customShapeUtils }), [documentId, tldraw], ); React.useEffect(() => { if (!doc || !isSynced) return; return syncTldrawStoreWithYjs(store, doc); }, [doc, isSynced, store]); const editorRef = React.useRef(null); React.useEffect(() => { editorRef.current?.updateInstanceState({ isReadonly: readOnly }); }, [readOnly]); React.useEffect(() => { editorRef.current?.user.updateUserPreferences({ colorScheme: dark ? "dark" : "light", }); }, [dark]); const { Tldraw } = tldraw; const collab = React.useMemo( () => ({ isConnected, isSynced }), [isConnected, isSynced], ); return ( { editorRef.current = editor; editor.updateInstanceState({ isReadonly: readOnly }); editor.user.updateUserPreferences({ colorScheme: dark ? "dark" : "light", }); }} components={{ InFrontOfTheCanvas: () => ( ), }} /> ); } function TldrawLocalBody({ readOnly, className, tldraw, }: { readOnly: boolean; className?: string; tldraw: TldrawModule; }) { const dark = useHtmlDarkClass(); const editorRef = React.useRef(null); const store = React.useMemo( () => tldraw.createTLStore({ shapeUtils: customShapeUtils }), [tldraw], ); React.useEffect(() => { editorRef.current?.updateInstanceState({ isReadonly: readOnly }); }, [readOnly]); React.useEffect(() => { editorRef.current?.user.updateUserPreferences({ colorScheme: dark ? "dark" : "light", }); }, [dark]); const { Tldraw } = tldraw; return ( { editorRef.current = editor; editor.updateInstanceState({ isReadonly: readOnly }); editor.user.updateUserPreferences({ colorScheme: dark ? "dark" : "light", }); }} components={{ InFrontOfTheCanvas: () => , }} /> ); } const TldynamicCollab = dynamic( () => import("tldraw").then((tldraw) => { function CollabLoaded( props: Omit & { documentId: string; readOnly: boolean; }, ) { return ; } return CollabLoaded; }), { ssr: false, loading: () => }, ); const TldynamicLocal = dynamic( () => import("tldraw").then((tldraw) => { function LocalLoaded(props: { readOnly: boolean; className?: string }) { return ; } return LocalLoaded; }), { ssr: false, loading: () => }, ); function WhiteboardLoading() { return (
Loading whiteboard…
); } export function WhiteboardCanvas({ documentId, className, readOnly = false }: WhiteboardCanvasProps) { const [client, setClient] = React.useState(false); React.useEffect(() => setClient(true), []); if (!client) { return (
); } return (
{documentId ? ( ) : ( )}
); }