From dc3c0f81a8e16d065038bd77d9fdb7fd49144f82 Mon Sep 17 00:00:00 2001 From: Jeet Burman Date: Sat, 20 Dec 2025 13:26:31 +0530 Subject: [PATCH 1/4] feat: add live mouse coordinate display to shape builder canvas - Add real-time coordinate tracking on mouse movement - Display normalized coordinates (-1 to 1 range) matching polygon output - Show pixel coordinates for reference - Coordinate display only visible when mouse hovers over canvas - Theme-aware styling adapts to light/dark modes - Non-intrusive positioning in top-right corner - Smooth show/hide transitions on mouse enter/leave Fixes #95 --- site/src/components/ShapeBuilder/index.js | 64 ++++++++++++++++++- .../ShapeBuilder/shapeBuilder.styles.js | 49 ++++++++++++++ 2 files changed, 112 insertions(+), 1 deletion(-) diff --git a/site/src/components/ShapeBuilder/index.js b/site/src/components/ShapeBuilder/index.js index 82b022f..b5eee05 100644 --- a/site/src/components/ShapeBuilder/index.js +++ b/site/src/components/ShapeBuilder/index.js @@ -1,6 +1,6 @@ // /* global window */ import React, { useEffect, useRef, useState } from "react"; -import { Wrapper, CanvasContainer, OutputBox, StyledSVG, CopyButton } from "./shapeBuilder.styles"; +import { Wrapper, CanvasContainer, OutputBox, StyledSVG, CopyButton, CoordinateDisplay } from "./shapeBuilder.styles"; import { Button, Typography, Box, CopyIcon } from "@sistent/sistent"; import { SVG, extend as SVGextend } from "@svgdotjs/svg.js"; import draw from "@svgdotjs/svg.draw.js"; @@ -15,6 +15,9 @@ const ShapeBuilder = () => { const [error, setError] = useState(null); const [showCopied, setShowCopied] = useState(false); + const [mouseCoords, setMouseCoords] = useState({ x: 0, y: 0, normalized: { x: 0, y: 0 } }); + const [isMouseInCanvas, setIsMouseInCanvas] = useState(false); + const handleCopyToClipboard = async () => { if (!result.trim()) return; @@ -70,6 +73,36 @@ const ShapeBuilder = () => { poly.move(0, 0); showCytoArray(); }; + + const handleMouseMove = (e) => { + const svg = boardRef.current; + if (!svg) return; + + const rect = svg.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + const centerX = rect.width / 2; + const centerY = rect.height / 2; + const normalizedX = (x - centerX) / centerX; + const normalizedY = (y - centerY) / centerY; + + setMouseCoords({ + x: Math.round(x), + y: Math.round(y), + normalized: { + x: parseFloat(normalizedX.toFixed(3)), + y: parseFloat(normalizedY.toFixed(3)) + } + }); +}; + + const handleMouseEnter = () => { + setIsMouseInCanvas(true); + }; + + const handleMouseLeave = () => { + setIsMouseInCanvas(false); + }; const handleKeyDown = (e) => { const poly = polyRef.current; @@ -177,6 +210,9 @@ const ShapeBuilder = () => { width="100%" height="100%" onDoubleClick={closeShape} + onMouseMove={handleMouseMove} + onMouseEnter={handleMouseEnter} + onMouseLeave={handleMouseLeave} > @@ -185,6 +221,32 @@ const ShapeBuilder = () => { + + {isMouseInCanvas && ( + +
Mouse Position
+
+
+ X: + {mouseCoords.normalized.x} +
+
+ Y: + {mouseCoords.normalized.y} +
+
+
+ Pixel: ({mouseCoords.x}, {mouseCoords.y}) +
+
+ )} + {error && (
theme?.mode === "light" ? "rgba(255, 255, 255, 0.95)" : "rgba(43, 43, 43, 0.95)"}; + border: 2px solid #00B39F; + border-radius: 8px; + padding: 12px 16px; + font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; + font-size: 14px; + font-weight: 600; + color: ${({ theme }) => theme?.mode === "light" ? "#111" : "#fff"}; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + z-index: 10; + user-select: none; + min-width: 150px; + + .coordinate-label { + color: #00B39F; + font-size: 12px; + margin-bottom: 4px; + text-transform: uppercase; + letter-spacing: 0.5px; + } + + .coordinate-values { + display: flex; + gap: 16px; + margin-top: 8px; + } + + .coordinate-item { + display: flex; + flex-direction: column; + gap: 2px; + } + + .axis-label { + font-size: 11px; + color: ${({ theme }) => theme?.mode === "light" ? "#666" : "#aaa"}; + } + + .axis-value { + font-size: 16px; + color: ${({ theme }) => theme?.mode === "light" ? "#111" : "#fff"}; + font-weight: 700; + } +`; + // export const Wrapper = styled.div` // padding: 2rem; // background-color: ${({ theme }) => theme.palette.background.default}; From 426c9c0f4770f6d423cba5fb27fb72b422401c35 Mon Sep 17 00:00:00 2001 From: Jeet Burman Date: Tue, 23 Dec 2025 18:42:15 +0530 Subject: [PATCH 2/4] refactor: make coordinate display follow cursor - Coordinate display now follows mouse cursor instead of fixed position - Removed pixel coordinates, showing only normalized X,Y values - Changed from fixed top-right position to cursor-relative positioning - Uses SVG-relative coordinates (x,y) instead of screen coordinates - Maintains smooth 15px offset to prevent blocking cursor - Simplified to single-line format: 'X: 0.234, Y: -0.567' Based on team feedback from code review --- site/src/components/ShapeBuilder/index.js | 77 ++++++++----------- .../ShapeBuilder/shapeBuilder.styles.js | 47 ++--------- 2 files changed, 41 insertions(+), 83 deletions(-) diff --git a/site/src/components/ShapeBuilder/index.js b/site/src/components/ShapeBuilder/index.js index b5eee05..6a3ccac 100644 --- a/site/src/components/ShapeBuilder/index.js +++ b/site/src/components/ShapeBuilder/index.js @@ -75,26 +75,29 @@ const ShapeBuilder = () => { }; const handleMouseMove = (e) => { - const svg = boardRef.current; - if (!svg) return; - - const rect = svg.getBoundingClientRect(); - const x = e.clientX - rect.left; - const y = e.clientY - rect.top; - const centerX = rect.width / 2; - const centerY = rect.height / 2; - const normalizedX = (x - centerX) / centerX; - const normalizedY = (y - centerY) / centerY; - - setMouseCoords({ - x: Math.round(x), - y: Math.round(y), - normalized: { - x: parseFloat(normalizedX.toFixed(3)), - y: parseFloat(normalizedY.toFixed(3)) - } - }); -}; + const svg = boardRef.current; + if (!svg) return; + + const rect = svg.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + const centerX = rect.width / 2; + const centerY = rect.height / 2; + const normalizedX = (x - centerX) / centerX; + const normalizedY = (y - centerY) / centerY; + + setMouseCoords({ + x: Math.round(x), + y: Math.round(y), + normalized: { + x: parseFloat(normalizedX.toFixed(3)), + y: parseFloat(normalizedY.toFixed(3)) + }, + + screenX: e.clientX, + screenY: e.clientY + }); + }; const handleMouseEnter = () => { setIsMouseInCanvas(true); @@ -222,30 +225,16 @@ const ShapeBuilder = () => { - {isMouseInCanvas && ( - -
Mouse Position
-
-
- X: - {mouseCoords.normalized.x} -
-
- Y: - {mouseCoords.normalized.y} -
-
-
- Pixel: ({mouseCoords.x}, {mouseCoords.y}) -
-
- )} + {isMouseInCanvas && ( + + X: {mouseCoords.normalized.x}, Y: {mouseCoords.normalized.y} + + )} {error && (
theme?.mode === "light" ? "rgba(255, 255, 255, 0.95)" : "rgba(43, 43, 43, 0.95)"}; border: 2px solid #00B39F; - border-radius: 8px; - padding: 12px 16px; + border-radius: 6px; + padding: 6px 10px; font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; - font-size: 14px; + font-size: 12px; font-weight: 600; color: ${({ theme }) => theme?.mode === "light" ? "#111" : "#fff"}; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); - z-index: 10; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + z-index: 1000; user-select: none; - min-width: 150px; - - .coordinate-label { - color: #00B39F; - font-size: 12px; - margin-bottom: 4px; - text-transform: uppercase; - letter-spacing: 0.5px; - } - - .coordinate-values { - display: flex; - gap: 16px; - margin-top: 8px; - } - - .coordinate-item { - display: flex; - flex-direction: column; - gap: 2px; - } - - .axis-label { - font-size: 11px; - color: ${({ theme }) => theme?.mode === "light" ? "#666" : "#aaa"}; - } - - .axis-value { - font-size: 16px; - color: ${({ theme }) => theme?.mode === "light" ? "#111" : "#fff"}; - font-weight: 700; - } + white-space: nowrap; + transition: left 0.05s ease-out, top 0.05s ease-out; `; // export const Wrapper = styled.div` From 822dee458903fe46d62fe087e1a10d8218b32d7c Mon Sep 17 00:00:00 2001 From: Jeet Burman Date: Thu, 25 Dec 2025 14:15:58 +0530 Subject: [PATCH 3/4] Removed Transitional delay --- site/src/components/ShapeBuilder/shapeBuilder.styles.js | 1 - 1 file changed, 1 deletion(-) diff --git a/site/src/components/ShapeBuilder/shapeBuilder.styles.js b/site/src/components/ShapeBuilder/shapeBuilder.styles.js index 90f87ab..f65882d 100644 --- a/site/src/components/ShapeBuilder/shapeBuilder.styles.js +++ b/site/src/components/ShapeBuilder/shapeBuilder.styles.js @@ -153,7 +153,6 @@ export const CoordinateDisplay = styled.div` z-index: 1000; user-select: none; white-space: nowrap; - transition: left 0.05s ease-out, top 0.05s ease-out; `; // export const Wrapper = styled.div` From b0c826971567539721bc692b7bef86fd225bff17 Mon Sep 17 00:00:00 2001 From: Jeet Burman Date: Thu, 25 Dec 2025 14:51:17 +0530 Subject: [PATCH 4/4] feat: add toggle button to enable/disable coordinate display - Added showCoordinates state (default: true) - Added toggle button in toolbar with contained variant - Button label updates dynamically between Hide/Show Coordinates - Coordinates only display when both hovering and toggle enabled - Preserves all existing functionality --- site/src/components/ShapeBuilder/index.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/site/src/components/ShapeBuilder/index.js b/site/src/components/ShapeBuilder/index.js index 6a3ccac..2302927 100644 --- a/site/src/components/ShapeBuilder/index.js +++ b/site/src/components/ShapeBuilder/index.js @@ -17,6 +17,7 @@ const ShapeBuilder = () => { const [mouseCoords, setMouseCoords] = useState({ x: 0, y: 0, normalized: { x: 0, y: 0 } }); const [isMouseInCanvas, setIsMouseInCanvas] = useState(false); + const [showCoordinates, setShowCoordinates] = useState(true); const handleCopyToClipboard = async () => { if (!result.trim()) return; @@ -107,6 +108,10 @@ const ShapeBuilder = () => { setIsMouseInCanvas(false); }; + const toggleCoordinates = () => { + setShowCoordinates(prev => !prev); + }; + const handleKeyDown = (e) => { const poly = polyRef.current; if (!poly) return; @@ -225,7 +230,7 @@ const ShapeBuilder = () => { - {isMouseInCanvas && ( + {isMouseInCanvas && showCoordinates && ( { +