Skip to content
CanvasMeta.tsx 3.55 KiB
Newer Older
Grant's avatar
Grant committed
import {
  Modal,
  ModalBody,
  ModalContent,
  ModalHeader,
  useDisclosure,
} from "@nextui-org/react";
Grant's avatar
Grant committed
import { CanvasLib } from "@sc07-canvas/lib/src/canvas";
Grant's avatar
Grant committed
import { useAppContext } from "../../contexts/AppContext";
import { Canvas } from "../../lib/canvas";
Grant's avatar
Grant committed
import { useEffect, useState } from "react";
import { ClientConfig } from "@sc07-canvas/lib/src/net";
Grant's avatar
Grant committed
import network from "../../lib/network";
Grant's avatar
Grant committed

const getTimeLeft = (pixels: { available: number }, config: ClientConfig) => {
  // this implementation matches the server's implementation

  const cooldown = CanvasLib.getPixelCooldown(pixels.available + 1, config);
  const pixelExpiresAt =
    Canvas.instance?.lastPlace && Canvas.instance.lastPlace + cooldown * 1000;
Grant's avatar
Grant committed
  const pixelCooldown = pixelExpiresAt && (Date.now() - pixelExpiresAt) / 1000;

  if (!pixelCooldown) return undefined;
  if (pixelCooldown > 0) return 0;

  return Math.abs(pixelCooldown).toFixed(1);
};

const PlaceCountdown = () => {
Grant's avatar
Grant committed
  const { pixels, config } = useAppContext<true>();
Grant's avatar
Grant committed
  const [timeLeft, setTimeLeft] = useState(getTimeLeft(pixels, config));

  useEffect(() => {
    const timer = setInterval(() => {
      setTimeLeft(getTimeLeft(pixels, config));
    }, 100);

    return () => {
      clearInterval(timer);
    };
  }, [pixels]);

  return (
Grant's avatar
Grant committed
    <>
      {timeLeft
        ? pixels.available < config.canvas.pixel.maxStack && timeLeft + "s"
Grant's avatar
Grant committed
        : ""}
    </>
Grant's avatar
Grant committed
  );
};
Grant's avatar
Grant committed

Grant's avatar
Grant committed
const OnlineCount = () => {
  const [online, setOnline] = useState<number>();

  useEffect(() => {
    function handleOnline(count: number) {
      setOnline(count);
    }

    network.waitForState("online").then(([count]) => setOnline(count));
Grant's avatar
Grant committed
    network.on("online", handleOnline);

    return () => {
      network.off("online", handleOnline);
    };
  }, []);

  return <>{typeof online === "number" ? online : "???"}</>;
};

Grant's avatar
Grant committed
export const CanvasMeta = () => {
  const { canvasPosition, cursor, pixels, config } = useAppContext<true>();
Grant's avatar
Grant committed
  const { isOpen, onOpen, onOpenChange } = useDisclosure();

  return (
    <>
      <div id="canvas-meta" className="toolbar-box">
Grant's avatar
Grant committed
        {canvasPosition && (
          <span>
            <button className="btn-link" onClick={onOpen}>
              ({canvasPosition.x}, {canvasPosition.y})
            </button>
            {cursor.x !== undefined && cursor.y !== undefined && (
Grant's avatar
Grant committed
              <>
                {" "}
                <span className="canvas-meta--cursor-pos">
                  (Cursor: {cursor.x}, {cursor.y})
Grant's avatar
Grant committed
                </span>
              </>
            )}
Grant's avatar
Grant committed
          </span>
        )}
        <span>
Grant's avatar
Grant committed
          Pixels:{" "}
          <span>
            {pixels.available}/{config.canvas.pixel.maxStack}
          </span>{" "}
          <PlaceCountdown />
Grant's avatar
Grant committed
        </span>
        <span>
Grant's avatar
Grant committed
          Users Online:{" "}
          <span>
            <OnlineCount />
          </span>
Grant's avatar
Grant committed
        </span>
      </div>
      <ShareModal isOpen={isOpen} onOpenChange={onOpenChange} />
    </>
  );
};

const ShareModal = ({
  isOpen,
  onOpenChange,
}: {
  isOpen: boolean;
  onOpenChange: () => void;
}) => {
  return (
    <Modal isOpen={isOpen} onOpenChange={onOpenChange}>
      <ModalContent>
        {() => (
          <>
            <ModalHeader className="flex flex-col gap-1">
              share modal
            </ModalHeader>
            <ModalBody>
              <p>share the current zoom level & position as a url</p>
              <p>
                params would be not a hash so the server can generate an oembed
              </p>
            </ModalBody>
          </>
        )}
      </ModalContent>
    </Modal>
  );
};