From 0ed8551e5407f8208a805c7aee53b456bce28fd2 Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Fri, 12 Jun 2020 22:37:23 +0200 Subject: [PATCH 1/4] draw mode added --- package-lock.json | 24 +++++++++++ package.json | 1 + src/components/DrawArea.tsx | 63 +++++++++++++++++++++++++++-- src/components/Rectangle.jsx | 11 +++-- src/components/TestDetailsModal.tsx | 25 +++++------- 5 files changed, 102 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0bed57f3..240b849c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1336,6 +1336,30 @@ "@babel/runtime": "^7.4.4" } }, + "@material-ui/lab": { + "version": "4.0.0-alpha.56", + "resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.56.tgz", + "integrity": "sha512-xPlkK+z/6y/24ka4gVJgwPfoCF4RCh8dXb1BNE7MtF9bXEBLN/lBxNTK8VAa0qm3V2oinA6xtUIdcRh0aeRtVw==", + "requires": { + "@babel/runtime": "^7.4.4", + "@material-ui/utils": "^4.10.2", + "clsx": "^1.0.4", + "prop-types": "^15.7.2", + "react-is": "^16.8.0" + }, + "dependencies": { + "@material-ui/utils": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.10.2.tgz", + "integrity": "sha512-eg29v74P7W5r6a4tWWDAAfZldXIzfyO1am2fIsC39hdUUHm/33k6pGOKPbgDjg/U/4ifmgAePy/1OjkKN6rFRw==", + "requires": { + "@babel/runtime": "^7.4.4", + "prop-types": "^15.7.2", + "react-is": "^16.8.0" + } + } + } + }, "@material-ui/react-transition-group": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@material-ui/react-transition-group/-/react-transition-group-4.2.0.tgz", diff --git a/package.json b/package.json index aa7ba3c1..1fd25aa6 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "dependencies": { "@material-ui/core": "^4.9.11", "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "^4.0.0-alpha.56", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", "@testing-library/user-event": "^7.2.1", diff --git a/src/components/DrawArea.tsx b/src/components/DrawArea.tsx index c6d7db1e..30bc4a56 100644 --- a/src/components/DrawArea.tsx +++ b/src/components/DrawArea.tsx @@ -1,7 +1,7 @@ import React, { FunctionComponent } from "react"; import { Stage, Layer, Image } from "react-konva"; import { RectConfig } from "konva/types/shapes/Rect"; -import Rectangle from "./Rectangle"; +import Rectangle, { MIN_RECT_SIDE_PIXEL } from "./Rectangle"; import { KonvaEventObject } from "konva/types/Node"; import { IgnoreArea } from "../types/ignoreArea"; @@ -25,6 +25,7 @@ interface IDrawArea { React.Dispatch> ]; stageScaleState: [number, React.Dispatch>]; + drawModeState: [boolean, React.Dispatch>]; } const DrawArea: FunctionComponent = ({ image, @@ -37,6 +38,7 @@ const DrawArea: FunctionComponent = ({ stageOffsetState, stageInitPosState, stagePosState, + drawModeState, }) => { const [stageInitPos, setStageInitPos] = stageInitPosState; const [stageOffset, setStageOffset] = stageOffsetState; @@ -44,13 +46,60 @@ const DrawArea: FunctionComponent = ({ const [stageScale] = stageScaleState; const [isDrag, setIsDrag] = React.useState(false); + const [isDrawMode, setIsDrawMode] = drawModeState; + const [isDrawing, setIsDrawing] = React.useState(isDrawMode); + + const handleContentClick = (e: any) => { + if (!isDrawMode) return; + // if we are drawing a shape, a click finishes the drawing + if (isDrawing) { + setIsDrawing(!isDrawing); + setIsDrawMode(false); + return; + } + + // otherwise, add a new rectangle at the mouse position with 0 width and height, + // and set isDrawing to true + const newArea: IgnoreArea = { + id: Date.now().toString(), + x: e.evt.layerX / stageScale, + y: e.evt.layerY / stageScale, + width: 0, + height: 0, + }; + setIgnoreAreas([...ignoreAreas, newArea]); + setSelectedRectId(newArea.id); + setIsDrawing(true); + }; + + const handleContentMouseMove = (e: any) => { + if (!isDrawMode) return; + + if (isDrawing) { + // update the current rectangle's width and height based on the mouse position + stage scale + const mouseX = e.evt.layerX / stageScale; + const mouseY = e.evt.layerY / stageScale; + + const newShapesList = ignoreAreas.map((i) => { + if (i.id === selectedRectId) { + // new width and height + i.width = Math.max(mouseX - i.x, MIN_RECT_SIDE_PIXEL); + i.height = Math.max(mouseY - i.y, MIN_RECT_SIDE_PIXEL); + return i; + } + return i; + }); + setIgnoreAreas(newShapesList); + } + }; + return (
{ - if (isDrag && !selectedRectId) { + if (!isDrawMode && isDrag && !selectedRectId) { event.preventDefault(); setStagePos({ x: event.clientX - stageInitPos.x, @@ -83,6 +132,8 @@ const DrawArea: FunctionComponent = ({ transform: `scale(${stageScale})`, transformOrigin: "top left", }} + onContentMousedown={handleContentClick} + onContentMouseMove={handleContentMouseMove} > = ({ rects[i].x = Math.round(newAttrs.x || 0); rects[i].y = Math.round(newAttrs.y || 0); - rects[i].width = Math.round(newAttrs.width || 0); - rects[i].height = Math.round(newAttrs.height || 0); + rects[i].width = Math.round( + newAttrs.width || MIN_RECT_SIDE_PIXEL + ); + rects[i].height = Math.round( + newAttrs.height || MIN_RECT_SIDE_PIXEL + ); setIgnoreAreas(rects); }} diff --git a/src/components/Rectangle.jsx b/src/components/Rectangle.jsx index 70017b56..55d2f2da 100644 --- a/src/components/Rectangle.jsx +++ b/src/components/Rectangle.jsx @@ -1,6 +1,8 @@ import React from "react"; import { Rect, Transformer } from "react-konva"; +export const MIN_RECT_SIDE_PIXEL = 5; + const Rectangle = ({ shapeProps, isSelected, onSelect, onChange }) => { const shapeRef = React.useRef(); const trRef = React.useRef(); @@ -59,8 +61,8 @@ const Rectangle = ({ shapeProps, isSelected, onSelect, onChange }) => { x: node.x(), y: node.y(), // set minimal value - width: Math.max(5, node.width() * scaleX), - height: Math.max(node.height() * scaleY), + width: Math.max(MIN_RECT_SIDE_PIXEL, node.width() * scaleX), + height: Math.max(MIN_RECT_SIDE_PIXEL, node.height() * scaleY), }); }} onMouseOver={(event) => { @@ -82,7 +84,10 @@ const Rectangle = ({ shapeProps, isSelected, onSelect, onChange }) => { rotateEnabled={false} boundBoxFunc={(oldBox, newBox) => { // limit resize - if (newBox.width < 5 || newBox.height < 5) { + if ( + newBox.width < MIN_RECT_SIDE_PIXEL || + newBox.height < MIN_RECT_SIDE_PIXEL + ) { return oldBox; } return newBox; diff --git a/src/components/TestDetailsModal.tsx b/src/components/TestDetailsModal.tsx index 2dd8287a..91e178f4 100644 --- a/src/components/TestDetailsModal.tsx +++ b/src/components/TestDetailsModal.tsx @@ -11,6 +11,7 @@ import { Box, makeStyles, } from "@material-ui/core"; +import { ToggleButton } from "@material-ui/lab"; import { TestRun } from "../types"; import { testRunService, @@ -37,11 +38,7 @@ import { TestRunDetails } from "./TestRunDetails"; import useImage from "use-image"; import { routes } from "../constants"; import { NoImagePlaceholder } from "./NoImageAvailable"; -import { - useTestRunDispatch, - updateTestRun, - selectTestRun, -} from "../contexts"; +import { useTestRunDispatch, updateTestRun, selectTestRun } from "../contexts"; const useStyles = makeStyles((theme) => ({ imageContainer: { @@ -89,6 +86,7 @@ const TestDetailsModal: React.FunctionComponent<{ testRun.diffName && staticService.getImage(testRun.diffName) ); + const [isDrawMode, setIsDrawMode] = useState(false); const [isDiffShown, setIsDiffShown] = useState(false); const [selectedRectId, setSelectedRectId] = React.useState(); @@ -219,20 +217,14 @@ const TestDetailsModal: React.FunctionComponent<{ - { - const newArea: IgnoreArea = { - id: Date.now().toString(), - x: 0, - y: 0, - width: 150, - height: 100, - }; - setIgnoreAreas([...ignoreAreas, newArea]); + setIsDrawMode(!isDrawMode); }} > - +
@@ -384,6 +377,7 @@ const TestDetailsModal: React.FunctionComponent<{ stagePosState={[stagePos, setStagePos]} stageInitPosState={[stageInitPos, setStageInitPos]} stageOffsetState={[stageOffset, setStageOffset]} + drawModeState={[isDrawMode, setIsDrawMode]} /> @@ -415,6 +409,7 @@ const TestDetailsModal: React.FunctionComponent<{ stagePosState={[stagePos, setStagePos]} stageInitPosState={[stageInitPos, setStageInitPos]} stageOffsetState={[stageOffset, setStageOffset]} + drawModeState={[isDrawMode, setIsDrawMode]} /> From 77b136c36989bbf33e19a18a30f9ae0147032db5 Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Fri, 12 Jun 2020 22:49:15 +0200 Subject: [PATCH 2/4] cursor updated --- src/components/DrawArea.tsx | 25 +++++++++++++------------ src/components/TestDetailsModal.tsx | 2 +- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/components/DrawArea.tsx b/src/components/DrawArea.tsx index 30bc4a56..7a26ae41 100644 --- a/src/components/DrawArea.tsx +++ b/src/components/DrawArea.tsx @@ -49,29 +49,29 @@ const DrawArea: FunctionComponent = ({ const [isDrawMode, setIsDrawMode] = drawModeState; const [isDrawing, setIsDrawing] = React.useState(isDrawMode); - const handleContentClick = (e: any) => { + const handleContentMousedown = (e: any) => { if (!isDrawMode) return; - // if we are drawing a shape, a click finishes the drawing - if (isDrawing) { - setIsDrawing(!isDrawing); - setIsDrawMode(false); - return; - } - // otherwise, add a new rectangle at the mouse position with 0 width and height, // and set isDrawing to true const newArea: IgnoreArea = { id: Date.now().toString(), x: e.evt.layerX / stageScale, y: e.evt.layerY / stageScale, - width: 0, - height: 0, + width: MIN_RECT_SIDE_PIXEL, + height: MIN_RECT_SIDE_PIXEL, }; setIgnoreAreas([...ignoreAreas, newArea]); setSelectedRectId(newArea.id); setIsDrawing(true); }; + const handleContentMouseup = (e: any) => { + if (isDrawing) { + setIsDrawing(!isDrawing); + setIsDrawMode(false); + } + }; + const handleContentMouseMove = (e: any) => { if (!isDrawMode) return; @@ -132,14 +132,15 @@ const DrawArea: FunctionComponent = ({ transform: `scale(${stageScale})`, transformOrigin: "top left", }} - onContentMousedown={handleContentClick} + onContentMousedown={handleContentMousedown} + onContentMouseup={handleContentMouseup} onContentMouseMove={handleContentMouseMove} > { - document.body.style.cursor = "grab"; + document.body.style.cursor = isDrawMode ? "crosshair" : "grab"; }} onMouseDown={(event) => { document.body.style.cursor = "grabbing"; diff --git a/src/components/TestDetailsModal.tsx b/src/components/TestDetailsModal.tsx index 91e178f4..813098ce 100644 --- a/src/components/TestDetailsModal.tsx +++ b/src/components/TestDetailsModal.tsx @@ -343,7 +343,7 @@ const TestDetailsModal: React.FunctionComponent<{ stagePosState={[stagePos, setStagePos]} stageInitPosState={[stageInitPos, setStageInitPos]} stageOffsetState={[stageOffset, setStageOffset]} - drawModeState={[isDrawMode, setIsDrawMode]} + drawModeState={[false, setIsDrawMode]} /> From 7204e162add2897ef0c60213128914c907b75090 Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Fri, 12 Jun 2020 22:57:50 +0200 Subject: [PATCH 3/4] stage offset fixed --- src/components/DrawArea.tsx | 9 ++++----- src/components/TestDetailsModal.tsx | 1 + 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/DrawArea.tsx b/src/components/DrawArea.tsx index 7a26ae41..124ff2f4 100644 --- a/src/components/DrawArea.tsx +++ b/src/components/DrawArea.tsx @@ -52,11 +52,10 @@ const DrawArea: FunctionComponent = ({ const handleContentMousedown = (e: any) => { if (!isDrawMode) return; - // and set isDrawing to true const newArea: IgnoreArea = { id: Date.now().toString(), - x: e.evt.layerX / stageScale, - y: e.evt.layerY / stageScale, + x: (e.evt.layerX - stageOffset.x) / stageScale, + y: (e.evt.layerY - stageOffset.y) / stageScale, width: MIN_RECT_SIDE_PIXEL, height: MIN_RECT_SIDE_PIXEL, }; @@ -77,8 +76,8 @@ const DrawArea: FunctionComponent = ({ if (isDrawing) { // update the current rectangle's width and height based on the mouse position + stage scale - const mouseX = e.evt.layerX / stageScale; - const mouseY = e.evt.layerY / stageScale; + const mouseX = (e.evt.layerX - stageOffset.x) / stageScale; + const mouseY = (e.evt.layerY - stageOffset.y) / stageScale; const newShapesList = ignoreAreas.map((i) => { if (i.id === selectedRectId) { diff --git a/src/components/TestDetailsModal.tsx b/src/components/TestDetailsModal.tsx index 813098ce..904a329e 100644 --- a/src/components/TestDetailsModal.tsx +++ b/src/components/TestDetailsModal.tsx @@ -218,6 +218,7 @@ const TestDetailsModal: React.FunctionComponent<{ { setIsDrawMode(!isDrawMode); From d78fb6bc3d4aa2562a3073269b256e0e33bfa852 Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Sat, 13 Jun 2020 00:05:10 +0200 Subject: [PATCH 4/4] rounding fixed --- src/components/DrawArea.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/DrawArea.tsx b/src/components/DrawArea.tsx index 124ff2f4..26fcf512 100644 --- a/src/components/DrawArea.tsx +++ b/src/components/DrawArea.tsx @@ -54,8 +54,8 @@ const DrawArea: FunctionComponent = ({ const newArea: IgnoreArea = { id: Date.now().toString(), - x: (e.evt.layerX - stageOffset.x) / stageScale, - y: (e.evt.layerY - stageOffset.y) / stageScale, + x: Math.round((e.evt.layerX - stageOffset.x) / stageScale), + y: Math.round((e.evt.layerY - stageOffset.y) / stageScale), width: MIN_RECT_SIDE_PIXEL, height: MIN_RECT_SIDE_PIXEL, }; @@ -82,8 +82,8 @@ const DrawArea: FunctionComponent = ({ const newShapesList = ignoreAreas.map((i) => { if (i.id === selectedRectId) { // new width and height - i.width = Math.max(mouseX - i.x, MIN_RECT_SIDE_PIXEL); - i.height = Math.max(mouseY - i.y, MIN_RECT_SIDE_PIXEL); + i.width = Math.max(Math.round(mouseX - i.x), MIN_RECT_SIDE_PIXEL); + i.height = Math.max(Math.round(mouseY - i.y), MIN_RECT_SIDE_PIXEL); return i; } return i;