Skip to content
CanvasWrapper.tsx 3.65 KiB
Newer Older
Grant's avatar
Grant committed
import { createRef, useContext, useEffect } from "react";
Grant's avatar
Grant committed
import { Canvas } from "../lib/canvas";
import { useAppContext } from "../contexts/AppContext";
import { PanZoomWrapper } from "@sc07-canvas/lib/src/renderer";
Grant's avatar
Grant committed
import { RendererContext } from "@sc07-canvas/lib/src/renderer/RendererContext";
Grant's avatar
Grant committed
import { ViewportMoveEvent } from "@sc07-canvas/lib/src/renderer/PanZoom";
import throttle from "lodash.throttle";
Grant's avatar
Grant committed
import { ICanvasPosition, IPosition } from "../types";
Grant's avatar
Grant committed
import { Routes } from "../lib/routes";
Grant's avatar
Grant committed

export const CanvasWrapper = () => {
  // to prevent safari from blurring things, use the zoom css property
  return (
    <main>
      <PanZoomWrapper>
        <CanvasInner />
      </PanZoomWrapper>
Grant's avatar
Grant committed
    </main>
  );
};

const parseHashParams = (canvas: Canvas) => {
  // maybe move this to a utility inside routes.ts

  let { hash } = new URL(window.location.href);
  if (hash.indexOf("#") === 0) {
    hash = hash.slice(1);
  }
  let params = new URLSearchParams(hash);

  let position: {
    x?: number;
    y?: number;
    zoom?: number;
  } = {};

  if (params.has("x") && !isNaN(parseInt(params.get("x")!)))
    position.x = parseInt(params.get("x")!);
  if (params.has("y") && !isNaN(parseInt(params.get("y")!)))
    position.y = parseInt(params.get("y")!);
  if (params.has("zoom") && !isNaN(parseInt(params.get("zoom")!)))
    position.zoom = parseInt(params.get("zoom")!);

  if (
    typeof position.x === "number" &&
    typeof position.y === "number" &&
    typeof position.zoom === "number"
  ) {
    const { transformX, transformY } = canvas.canvasToPanZoomTransform(
      position.x,
      position.y
    );

    return {
      x: transformX,
      y: transformY,
      zoom: position.zoom,
    };
  }
};

Grant's avatar
Grant committed
const CanvasInner = () => {
  const canvasRef = createRef<HTMLCanvasElement>();
Grant's avatar
Grant committed
  const { config, setCanvasPosition, setCursorPosition } = useAppContext();
Grant's avatar
Grant committed
  const PanZoom = useContext(RendererContext);
Grant's avatar
Grant committed

  useEffect(() => {
    if (!config.canvas || !canvasRef.current) return;
    const canvas = canvasRef.current!;
Grant's avatar
Grant committed
    const canvasInstance = new Canvas(config, canvas, PanZoom);

    {
      // TODO: handle hash changes and move viewport
      // NOTE: this will need to be cancelled if handleViewportMove was executed recently

      const position = parseHashParams(canvasInstance);
      if (position) {
        PanZoom.setPosition(position, { suppressEmit: true });
      }
    }
Grant's avatar
Grant committed

Grant's avatar
Grant committed
    const handleViewportMove = throttle((state: ViewportMoveEvent) => {
      const pos = canvasInstance.panZoomTransformToCanvas();

      const canvasPosition: ICanvasPosition = {
        x: pos.canvasX,
        y: pos.canvasY,
        zoom: state.scale >> 0,
      };

      setCanvasPosition(canvasPosition);

      window.location.replace(Routes.canvas(canvasPosition));
    }, 1000);

    const handleCursorPos = throttle((pos: IPosition) => {
Grant's avatar
Grant committed
      if (
        pos.x < 0 ||
        pos.y < 0 ||
        pos.x > config.canvas.size[0] ||
        pos.y > config.canvas.size[1]
      ) {
        setCursorPosition();
      } else {
        // fixes not passing the current value
        setCursorPosition({ ...pos });
Grant's avatar
Grant committed
      }
Grant's avatar
Grant committed
    PanZoom.addListener("viewportMove", handleViewportMove);
Grant's avatar
Grant committed
    canvasInstance.on("cursorPos", handleCursorPos);
Grant's avatar
Grant committed

Grant's avatar
Grant committed
    return () => {
      canvasInstance.destroy();
Grant's avatar
Grant committed
      PanZoom.removeListener("viewportMove", handleViewportMove);
Grant's avatar
Grant committed
      canvasInstance.off("cursorPos", handleCursorPos);
Grant's avatar
Grant committed
    };

    // do not include canvasRef, it causes infinite re-renders
  }, [PanZoom, config, setCanvasPosition, setCursorPosition]);
Grant's avatar
Grant committed

  return (
    <canvas
      id="board"
      width="1000"
      height="1000"
      className="pixelate"
      ref={canvasRef}
    ></canvas>
  );
};