Loading packages/client/src/components/CanvasMeta.tsx +43 −2 Original line number Diff line number Diff line Loading @@ -5,10 +5,47 @@ import { ModalHeader, useDisclosure, } from "@nextui-org/react"; import { CanvasLib } from "@sc07-canvas/lib/src/canvas"; import { useAppContext } from "../contexts/AppContext"; import { Canvas } from "../lib/canvas"; import { useEffect, useState } from "react"; import { ClientConfig } from "@sc07-canvas/lib/src/net"; 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; const pixelCooldown = pixelExpiresAt && (Date.now() - pixelExpiresAt) / 1000; if (!pixelCooldown) return undefined; if (pixelCooldown > 0) return 0; return Math.abs(pixelCooldown).toFixed(1); }; const PlaceCountdown = () => { const { pixels, config } = useAppContext(); const [timeLeft, setTimeLeft] = useState(getTimeLeft(pixels, config)); useEffect(() => { const timer = setInterval(() => { setTimeLeft(getTimeLeft(pixels, config)); }, 100); return () => { clearInterval(timer); }; }, [pixels]); return ( <>{pixels.available + 1 < config.canvas.pixel.maxStack && timeLeft + "s"}</> ); }; export const CanvasMeta = () => { const { canvasPosition, cursorPosition } = useAppContext(); const { canvasPosition, cursorPosition, pixels, config } = useAppContext(); const { isOpen, onOpen, onOpenChange } = useDisclosure(); return ( Loading @@ -30,7 +67,11 @@ export const CanvasMeta = () => { </span> )} <span> Pixels: <span>123</span> Pixels:{" "} <span> {pixels.available}/{config.canvas.pixel.maxStack} </span>{" "} <PlaceCountdown /> </span> <span> Users Online: <span>321</span> Loading packages/client/src/contexts/AppContext.tsx +12 −2 Original line number Diff line number Diff line Loading @@ -6,12 +6,12 @@ import { useState, } from "react"; import { AuthSession, ClientConfig, IAppContext, ICanvasPosition, IPosition, } from "../types"; import { AuthSession } from "@sc07-canvas/lib/src/net"; } from "@sc07-canvas/lib/src/net"; import Network from "../lib/network"; const appContext = createContext<IAppContext>({} as any); Loading @@ -24,6 +24,8 @@ export const AppContext = ({ children }: PropsWithChildren) => { const [canvasPosition, setCanvasPosition] = useState<ICanvasPosition>(); const [cursorPosition, setCursorPosition] = useState<IPosition>(); const [pixels, setPixels] = useState({ available: 0 }); useEffect(() => { function handleConfig(config: ClientConfig) { console.info("Server sent config", config); Loading @@ -34,14 +36,21 @@ export const AppContext = ({ children }: PropsWithChildren) => { setAuth(user); } function handlePixels(pixels: { available: number }) { setPixels(pixels); } Network.on("user", handleUser); Network.on("config", handleConfig); Network.waitFor("pixels").then(([data]) => handlePixels(data)); Network.on("pixels", handlePixels); Network.socket.connect(); return () => { Network.off("user", handleUser); Network.off("config", handleConfig); Network.off("pixels", handlePixels); }; }, []); Loading @@ -54,6 +63,7 @@ export const AppContext = ({ children }: PropsWithChildren) => { setCanvasPosition, cursorPosition, setCursorPosition, pixels, }} > {config ? children : "Loading..."} Loading packages/client/src/lib/canvas.ts +18 −10 Original line number Diff line number Diff line import EventEmitter from "eventemitter3"; import { ClientConfig, IPalleteContext, IPosition, Pixel } from "../types"; import { ClientConfig, IPalleteContext, IPosition, Pixel, } from "@sc07-canvas/lib/src/net"; import Network from "./network"; import { ClickEvent, Loading Loading @@ -30,7 +35,7 @@ export class Canvas extends EventEmitter<CanvasEvents> { private pixels: { [x_y: string]: { color: number; type: "full" | "pending" }; } = {}; private lastPlace: number | undefined; lastPlace: number | undefined; constructor( config: ClientConfig, Loading @@ -52,6 +57,9 @@ export class Canvas extends EventEmitter<CanvasEvents> { this.PanZoom.addListener("click", this.handleMouseDown.bind(this)); Network.waitFor("canvas").then(([pixels]) => this.handleBatch(pixels)); Network.waitFor("pixelLastPlaced").then( ([time]) => (this.lastPlace = time) ); this.draw(); } Loading Loading @@ -133,14 +141,13 @@ export class Canvas extends EventEmitter<CanvasEvents> { place(x: number, y: number) { if (!this.Pallete.getSelectedColor()) return; if (this.lastPlace) { if (this.lastPlace + this.config.pallete.pixel_cooldown > Date.now()) { console.log("cannot place; cooldown"); return; } } this.lastPlace = Date.now(); // TODO: redo this as the server now verifies placements differently // if (this.lastPlace) { // if (this.lastPlace + this.config.pallete.pixel_cooldown > Date.now()) { // console.log("cannot place; cooldown"); // return; // } // } Network.socket .emitWithAck("place", { Loading @@ -150,6 +157,7 @@ export class Canvas extends EventEmitter<CanvasEvents> { }) .then((ack) => { if (ack.success) { this.lastPlace = Date.now(); this.handlePixel(ack.data); } else { // TODO: handle undo pixel Loading packages/client/src/lib/network.ts +10 −0 Original line number Diff line number Diff line Loading @@ -11,6 +11,8 @@ export interface INetworkEvents { user: (user: AuthSession) => void; config: (user: ClientConfig) => void; canvas: (pixels: string[]) => void; pixels: (data: { available: number }) => void; pixelLastPlaced: (time: number) => void; } type SentEventValue<K extends keyof INetworkEvents> = EventEmitter.ArgumentMap< Loading Loading @@ -45,6 +47,14 @@ class Network extends EventEmitter<INetworkEvents> { this._emit("canvas", pixels); }); this.socket.on("availablePixels", (count) => { this._emit("pixels", { available: count }); }); this.socket.on("pixelLastPlaced", (time) => { this._emit("pixelLastPlaced", time); }); // this.socket.on("config", (config) => { // Pallete.load(config.pallete); // Canvas.load(config.canvas); Loading packages/lib/src/canvas.ts 0 → 100644 +21 −0 Original line number Diff line number Diff line import { type ClientConfig } from "./net"; export const CanvasLib = new (class { /** * Get pixel cooldown * * @param pixelNumber What pixel is this * @param config * @returns Seconds to take to give the pixel */ getPixelCooldown(pixelNumber: number, config: ClientConfig) { return pixelNumber * config.canvas.pixel.cooldown; // const factorial = (n: number) => (n == 0 ? 1 : n * factorial(n - 1)); // return ( // config.canvas.pixel.cooldown * // config.canvas.pixel.multiplier * // (2 + pixelNumber + factorial(pixelNumber)) // ); } })(); Loading
packages/client/src/components/CanvasMeta.tsx +43 −2 Original line number Diff line number Diff line Loading @@ -5,10 +5,47 @@ import { ModalHeader, useDisclosure, } from "@nextui-org/react"; import { CanvasLib } from "@sc07-canvas/lib/src/canvas"; import { useAppContext } from "../contexts/AppContext"; import { Canvas } from "../lib/canvas"; import { useEffect, useState } from "react"; import { ClientConfig } from "@sc07-canvas/lib/src/net"; 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; const pixelCooldown = pixelExpiresAt && (Date.now() - pixelExpiresAt) / 1000; if (!pixelCooldown) return undefined; if (pixelCooldown > 0) return 0; return Math.abs(pixelCooldown).toFixed(1); }; const PlaceCountdown = () => { const { pixels, config } = useAppContext(); const [timeLeft, setTimeLeft] = useState(getTimeLeft(pixels, config)); useEffect(() => { const timer = setInterval(() => { setTimeLeft(getTimeLeft(pixels, config)); }, 100); return () => { clearInterval(timer); }; }, [pixels]); return ( <>{pixels.available + 1 < config.canvas.pixel.maxStack && timeLeft + "s"}</> ); }; export const CanvasMeta = () => { const { canvasPosition, cursorPosition } = useAppContext(); const { canvasPosition, cursorPosition, pixels, config } = useAppContext(); const { isOpen, onOpen, onOpenChange } = useDisclosure(); return ( Loading @@ -30,7 +67,11 @@ export const CanvasMeta = () => { </span> )} <span> Pixels: <span>123</span> Pixels:{" "} <span> {pixels.available}/{config.canvas.pixel.maxStack} </span>{" "} <PlaceCountdown /> </span> <span> Users Online: <span>321</span> Loading
packages/client/src/contexts/AppContext.tsx +12 −2 Original line number Diff line number Diff line Loading @@ -6,12 +6,12 @@ import { useState, } from "react"; import { AuthSession, ClientConfig, IAppContext, ICanvasPosition, IPosition, } from "../types"; import { AuthSession } from "@sc07-canvas/lib/src/net"; } from "@sc07-canvas/lib/src/net"; import Network from "../lib/network"; const appContext = createContext<IAppContext>({} as any); Loading @@ -24,6 +24,8 @@ export const AppContext = ({ children }: PropsWithChildren) => { const [canvasPosition, setCanvasPosition] = useState<ICanvasPosition>(); const [cursorPosition, setCursorPosition] = useState<IPosition>(); const [pixels, setPixels] = useState({ available: 0 }); useEffect(() => { function handleConfig(config: ClientConfig) { console.info("Server sent config", config); Loading @@ -34,14 +36,21 @@ export const AppContext = ({ children }: PropsWithChildren) => { setAuth(user); } function handlePixels(pixels: { available: number }) { setPixels(pixels); } Network.on("user", handleUser); Network.on("config", handleConfig); Network.waitFor("pixels").then(([data]) => handlePixels(data)); Network.on("pixels", handlePixels); Network.socket.connect(); return () => { Network.off("user", handleUser); Network.off("config", handleConfig); Network.off("pixels", handlePixels); }; }, []); Loading @@ -54,6 +63,7 @@ export const AppContext = ({ children }: PropsWithChildren) => { setCanvasPosition, cursorPosition, setCursorPosition, pixels, }} > {config ? children : "Loading..."} Loading
packages/client/src/lib/canvas.ts +18 −10 Original line number Diff line number Diff line import EventEmitter from "eventemitter3"; import { ClientConfig, IPalleteContext, IPosition, Pixel } from "../types"; import { ClientConfig, IPalleteContext, IPosition, Pixel, } from "@sc07-canvas/lib/src/net"; import Network from "./network"; import { ClickEvent, Loading Loading @@ -30,7 +35,7 @@ export class Canvas extends EventEmitter<CanvasEvents> { private pixels: { [x_y: string]: { color: number; type: "full" | "pending" }; } = {}; private lastPlace: number | undefined; lastPlace: number | undefined; constructor( config: ClientConfig, Loading @@ -52,6 +57,9 @@ export class Canvas extends EventEmitter<CanvasEvents> { this.PanZoom.addListener("click", this.handleMouseDown.bind(this)); Network.waitFor("canvas").then(([pixels]) => this.handleBatch(pixels)); Network.waitFor("pixelLastPlaced").then( ([time]) => (this.lastPlace = time) ); this.draw(); } Loading Loading @@ -133,14 +141,13 @@ export class Canvas extends EventEmitter<CanvasEvents> { place(x: number, y: number) { if (!this.Pallete.getSelectedColor()) return; if (this.lastPlace) { if (this.lastPlace + this.config.pallete.pixel_cooldown > Date.now()) { console.log("cannot place; cooldown"); return; } } this.lastPlace = Date.now(); // TODO: redo this as the server now verifies placements differently // if (this.lastPlace) { // if (this.lastPlace + this.config.pallete.pixel_cooldown > Date.now()) { // console.log("cannot place; cooldown"); // return; // } // } Network.socket .emitWithAck("place", { Loading @@ -150,6 +157,7 @@ export class Canvas extends EventEmitter<CanvasEvents> { }) .then((ack) => { if (ack.success) { this.lastPlace = Date.now(); this.handlePixel(ack.data); } else { // TODO: handle undo pixel Loading
packages/client/src/lib/network.ts +10 −0 Original line number Diff line number Diff line Loading @@ -11,6 +11,8 @@ export interface INetworkEvents { user: (user: AuthSession) => void; config: (user: ClientConfig) => void; canvas: (pixels: string[]) => void; pixels: (data: { available: number }) => void; pixelLastPlaced: (time: number) => void; } type SentEventValue<K extends keyof INetworkEvents> = EventEmitter.ArgumentMap< Loading Loading @@ -45,6 +47,14 @@ class Network extends EventEmitter<INetworkEvents> { this._emit("canvas", pixels); }); this.socket.on("availablePixels", (count) => { this._emit("pixels", { available: count }); }); this.socket.on("pixelLastPlaced", (time) => { this._emit("pixelLastPlaced", time); }); // this.socket.on("config", (config) => { // Pallete.load(config.pallete); // Canvas.load(config.canvas); Loading
packages/lib/src/canvas.ts 0 → 100644 +21 −0 Original line number Diff line number Diff line import { type ClientConfig } from "./net"; export const CanvasLib = new (class { /** * Get pixel cooldown * * @param pixelNumber What pixel is this * @param config * @returns Seconds to take to give the pixel */ getPixelCooldown(pixelNumber: number, config: ClientConfig) { return pixelNumber * config.canvas.pixel.cooldown; // const factorial = (n: number) => (n == 0 ? 1 : n * factorial(n - 1)); // return ( // config.canvas.pixel.cooldown * // config.canvas.pixel.multiplier * // (2 + pixelNumber + factorial(pixelNumber)) // ); } })();