import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { CanvasContext } from "./CanvasContext"; import type { CourseCanvasContextValue, CourseCanvasProps, CourseCanvasViewport, } from "./CourseCanvas.types"; import { useCanvasScale } from "./useCanvasScale"; const DEFAULT_WIDTH = 1920; const DEFAULT_HEIGHT = 1080; export function CourseCanvas({ children, width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT, maxScale = 1, className, canvasClassName, portalClassName, }: CourseCanvasProps) { const scale = useCanvasScale({ width, height, maxScale }); const frameRef = useRef(null); const portalContainer = useRef(null); const [viewport, setViewport] = useState({ width, height, scale, originLeft: 0, originTop: 0, }); const measure = useCallback(() => { const frame = frameRef.current; if (!frame) return; const rect = frame.getBoundingClientRect(); const committedScale = rect.width > 0 ? rect.width / width : scale; const next = { width, height, scale: committedScale, originLeft: rect.left, originTop: rect.top, }; setViewport((current) => current.width === next.width && current.height === next.height && current.scale === next.scale && current.originLeft === next.originLeft && current.originTop === next.originTop ? current : next, ); }, [height, scale, width]); useEffect(() => { const frame = frameRef.current; let animationFrame = requestAnimationFrame(measure); const scheduleMeasure = () => { cancelAnimationFrame(animationFrame); animationFrame = requestAnimationFrame(measure); }; const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(scheduleMeasure); if (frame) observer?.observe(frame); observer?.observe(document.documentElement); window.addEventListener("resize", scheduleMeasure); window.visualViewport?.addEventListener("resize", scheduleMeasure); return () => { cancelAnimationFrame(animationFrame); observer?.disconnect(); window.removeEventListener("resize", scheduleMeasure); window.visualViewport?.removeEventListener("resize", scheduleMeasure); }; }, [measure]); const value = useMemo( () => ({ width, height, scale, portalContainer, viewport }), [height, scale, viewport, width], ); return (
{children}
); }