diff --git a/frontend/package.json b/frontend/package.json index 6b85ccc2..16758294 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,6 +6,7 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "d3": "^7.9.0", "get-starknet": "^3.0.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/frontend/src/canvas/Canvas.js b/frontend/src/canvas/Canvas.js index 43302efb..1e0f88eb 100644 --- a/frontend/src/canvas/Canvas.js +++ b/frontend/src/canvas/Canvas.js @@ -1,4 +1,5 @@ import React, { useCallback, useRef, useEffect, useState } from 'react' +import { select, zoom, zoomIdentity } from "d3" import useWebSocket, { ReadyState } from 'react-use-websocket' import './Canvas.css'; // import TemplateOverlay from './TemplateOverlay.js'; @@ -7,20 +8,17 @@ import backendConfig from "../configs/backend.config.json" const Canvas = props => { const backendUrl = "http://" + backendConfig.host + ":" + backendConfig.port - // TODO: Pressing "Canvas" resets the view / positioning + //TODO: Pressing "Canvas" resets the view / positioning + //TODO: Way to configure tick rates to give smooth xp for all users + + //Todo: Make this dynamic + const minScale = 1; + const maxScale = 40; - const [canvasPositionX, setCanvasPositionX] = useState(0) - const [canvasPositionY, setCanvasPositionY] = useState(0) - const [isDragging, setIsDragging] = useState(false) - const [dragStartX, setDragStartX] = useState(0) - const [dragStartY, setDragStartY] = useState(0) - const [canvasScale, setCanvasScale] = useState(6) - const minScale = 1 // TODO: To config - const maxScale = 40 - //TODO: Way to configure tick rates to give smooth xp for all users - const canvasRef = useRef(null) + const canvasPositionRef = useRef(null) + const canvasScaleRef = useRef(null) // Read canvas config from environment variable file json const width = canvasConfig.canvas.width @@ -35,58 +33,33 @@ const Canvas = props => { shouldReconnect: () => true, }, ) - - // TODO: Weird positioning behavior when clicking into devtools - - // Handle wheel event for zooming - const handleWheel = (e) => { - let newScale = canvasScale - if (e.deltaY < 0) { - newScale = Math.min(maxScale, newScale + 0.2) - } else { - newScale = Math.max(minScale, newScale - 0.2) - } - // TODO: Smart positioning of canvas zoom ( zoom to center of mouse pointer ) - //let newCanvasPositionX = canvasPositionX - //let newCanvasPositionY = canvasPositionY - //const canvasOriginX = canvasPositionX + width / 2 - //const canvasOriginY = canvasPositionY + height / 2 - //setCanvasPositionX(newCanvasPositionX) - //setCanvasPositionY(newCanvasPositionY) - setCanvasScale(newScale) - } + // TODO: Weird positioning behavior when clicking into devtools + useEffect(() => { + const canvas = select(canvasPositionRef.current) + const Dzoom = zoom().scaleExtent([minScale, maxScale]).on("zoom", zoomHandler) - const handlePointerDown = (e) => { - setIsDragging(true) - setDragStartX(e.clientX) - setDragStartY(e.clientY) - } + // Set default zoom level and center the canvas + canvas + .call(Dzoom) + .call(Dzoom.transform, zoomIdentity.translate(0, 0).scale(4)) - const handlePointerUp = () => { - setIsDragging(false) - setDragStartX(0) - setDragStartY(0) - } + return () => { + canvas.on(".zoom", null); // Clean up zoom event listeners + }; + }, []); - const handlePointerMove = (e) => { - if (isDragging) { - // TODO: Prevent dragging outside of canvas container - setCanvasPositionX(canvasPositionX + e.clientX - dragStartX) - setCanvasPositionY(canvasPositionY + e.clientY - dragStartY) - setDragStartX(e.clientX) - setDragStartY(e.clientY) - } + const zoomHandler = (event) => { + const ele = canvasScaleRef.current + const { + k: newScale, + x: newCanvasPositionX, + y: newCanvasPositionY, + } = event.transform; + const transformValue = `translate(${newCanvasPositionX}px, ${newCanvasPositionY}px) scale(${newScale})` + ele.style.transform = transformValue } - useEffect(() => { - document.addEventListener('pointerup', handlePointerUp) - - return () => { - document.removeEventListener('pointerup', handlePointerUp) - } - }, []) - const [setup, setSetup] = useState(false) const draw = useCallback((ctx, imageData) => { @@ -104,7 +77,7 @@ const Canvas = props => { const context = canvas.getContext('2d') let getCanvasEndpoint = backendUrl + "/getCanvas" - fetch(getCanvasEndpoint, {mode: 'cors'}).then(response => { + fetch(getCanvasEndpoint, { mode: 'cors' }).then(response => { return response.arrayBuffer() }).then(data => { let colorData = new Uint8Array(data, 0, data.byteLength) @@ -205,7 +178,7 @@ const Canvas = props => { if (props.selectedPositionX === null || props.selectedPositionY === null) { return } - + const position = props.selectedPositionX + props.selectedPositionY * width const colorIdx = props.selectedColorId let placePixelEndpoint = backendUrl + "/placePixelDevnet" @@ -228,7 +201,7 @@ const Canvas = props => { props.setSelectedColorId(-1) // TODO: Optimistic update } - + // TODO: Deselect pixel when clicking outside of color palette or pixel // TODO: Show small position vec in bottom right corner of canvas const getSelectedColor = () => { @@ -287,19 +260,19 @@ const Canvas = props => { // TODO: both place options return ( -
-
-
- { props.pixelSelectedMode && ( -
-
+
+
+
+ {props.pixelSelectedMode && ( +
+
)} - +
); } -export default Canvas \ No newline at end of file +export default Canvas