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..26fcf512 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,59 @@ const DrawArea: FunctionComponent = ({ const [stageScale] = stageScaleState; const [isDrag, setIsDrag] = React.useState(false); + const [isDrawMode, setIsDrawMode] = drawModeState; + const [isDrawing, setIsDrawing] = React.useState(isDrawMode); + + const handleContentMousedown = (e: any) => { + if (!isDrawMode) return; + + const newArea: IgnoreArea = { + id: Date.now().toString(), + 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, + }; + 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; + + if (isDrawing) { + // update the current rectangle's width and height based on the mouse position + stage scale + 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) { + // new width and height + 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; + }); + setIgnoreAreas(newShapesList); + } + }; + return (
{ - if (isDrag && !selectedRectId) { + if (!isDrawMode && isDrag && !selectedRectId) { event.preventDefault(); setStagePos({ x: event.clientX - stageInitPos.x, @@ -83,12 +131,15 @@ const DrawArea: FunctionComponent = ({ transform: `scale(${stageScale})`, transformOrigin: "top left", }} + 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"; @@ -117,8 +168,12 @@ const DrawArea: FunctionComponent = ({ 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..904a329e 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,15 @@ 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 +378,7 @@ const TestDetailsModal: React.FunctionComponent<{ stagePosState={[stagePos, setStagePos]} stageInitPosState={[stageInitPos, setStageInitPos]} stageOffsetState={[stageOffset, setStageOffset]} + drawModeState={[isDrawMode, setIsDrawMode]} /> @@ -415,6 +410,7 @@ const TestDetailsModal: React.FunctionComponent<{ stagePosState={[stagePos, setStagePos]} stageInitPosState={[stageInitPos, setStageInitPos]} stageOffsetState={[stageOffset, setStageOffset]} + drawModeState={[isDrawMode, setIsDrawMode]} />