diff --git a/ui/src/image_annotator.test.tsx b/ui/src/image_annotator.test.tsx index d0b352f02d..ee248952d5 100644 --- a/ui/src/image_annotator.test.tsx +++ b/ui/src/image_annotator.test.tsx @@ -129,6 +129,17 @@ describe('ImageAnnotator.tsx', () => { expect(wave.args[name]).toMatchObject(items) }) + it('Does not draw a new rect if dimensions too small', async () => { + const { container, getByText } = render() + await waitForLoad() + const canvasEl = container.querySelectorAll('canvas')[1] + fireEvent.click(getByText('Rectangle')) + fireEvent.mouseDown(canvasEl, { clientX: 110, clientY: 110, buttons: 1 }) + fireEvent.click(canvasEl, { clientX: 115, clientY: 115 }) + + expect(wave.args[name]).toMatchObject(items) + }) + it('Removes rect after clicking remove btn', async () => { const { container, getByText } = render() await waitForLoad() @@ -207,6 +218,30 @@ describe('ImageAnnotator.tsx', () => { expect(wave.args[name]).toMatchObject([{ tag: 'person', shape: { rect: { x1: 20, x2: 110, y1: 20, y2: 110 } } }, polygon]) }) + it('Moves rect correctly if click happened outside the canvas', async () => { + const { container } = render() + await waitForLoad() + const canvasEl = container.querySelectorAll('canvas')[1] + fireEvent.click(canvasEl, { clientX: 50, clientY: 50 }) + fireEvent.mouseDown(canvasEl, { clientX: 50, clientY: 50, buttons: 1 }) + fireEvent.mouseMove(canvasEl, { clientX: 45, clientY: 50, buttons: 1 }) + fireEvent.mouseLeave(canvasEl, { clientX: -10, clientY: 60, buttons: 1 }) + + expect(wave.args[name]).toMatchObject([{ tag: 'person', shape: { rect: { x1: 5, x2: 95, y1: 10, y2: 100 } } }, polygon]) + }) + + it('Does not move rect if click happened outside the canvas but left mouse btn not pressed', async () => { + const { container } = render() + await waitForLoad() + const canvasEl = container.querySelectorAll('canvas')[1] + fireEvent.click(canvasEl, { clientX: 50, clientY: 50 }) + fireEvent.mouseDown(canvasEl, { clientX: 50, clientY: 50, buttons: 1 }) + fireEvent.mouseMove(canvasEl, { clientX: 45, clientY: 50, buttons: 1 }) + fireEvent.mouseLeave(canvasEl, { clientX: -10, clientY: 60 }) + + expect(wave.args[name]).toMatchObject(items) + }) + it('Does not move rect if left mouse btn not pressed (dragging)', async () => { const { container } = render() await waitForLoad() @@ -542,6 +577,18 @@ describe('ImageAnnotator.tsx', () => { expect(pushMock).toBeCalledTimes(1) }) + it('Calls sync after moving rect and finishing outside of canvas', async () => { + const { container } = render() + await waitForLoad() + const canvasEl = container.querySelectorAll('canvas')[1] + fireEvent.click(canvasEl, { clientX: 50, clientY: 50 }) + fireEvent.mouseDown(canvasEl, { clientX: 50, clientY: 50, buttons: 1 }) + fireEvent.mouseMove(canvasEl, { clientX: 60, clientY: 60, buttons: 1 }) + fireEvent.click(canvasEl, { clientX: 60, clientY: 60 }) + + expect(pushMock).toBeCalledTimes(1) + }) + it('Calls sync after resizing rect', async () => { const { container } = render() await waitForLoad() @@ -560,12 +607,11 @@ describe('ImageAnnotator.tsx', () => { const canvasEl = container.querySelectorAll('canvas')[1] fireEvent.click(canvasEl, { clientX: 50, clientY: 50 }) await waitForLoad() - fireEvent.click(getByText('Remove selection').parentElement?.parentElement?.parentElement!) + fireEvent.click(getByText('Remove selection').parentElement!.parentElement!.parentElement!) expect(pushMock).toBeCalledTimes(1) }) - it('Calls sync after removing polygon', async () => { const { container, getByText } = render() await waitForLoad() diff --git a/ui/src/image_annotator.tsx b/ui/src/image_annotator.tsx index f1955d90f3..5b0ba76ca6 100644 --- a/ui/src/image_annotator.tsx +++ b/ui/src/image_annotator.tsx @@ -204,6 +204,17 @@ export const XImageAnnotator = ({ model }: { model: ImageAnnotator }) => { redrawExistingShapes() // eslint-disable-next-line react-hooks/exhaustive-deps }, [redrawExistingShapes]), + + onMouseLeave = (e: React.MouseEvent) => { + const canvas = canvasRef.current + if (!canvas || e.buttons !== 1) return + + setWaveArgs(drawnShapes) + + polygonRef.current?.resetDragging() + rectRef.current?.resetDragging() + redrawExistingShapes() + }, onMouseDown = (e: React.MouseEvent) => { const canvas = canvasRef.current if (!canvas) return @@ -462,6 +473,7 @@ export const XImageAnnotator = ({ model }: { model: ImageAnnotator }) => { className={css.canvas} onMouseMove={onMouseMove} onMouseDown={onMouseDown} + onMouseLeave={onMouseLeave} onKeyDown={onKeyDown} // Do not show context menu on right click. onContextMenu={e => e.preventDefault()} diff --git a/ui/src/image_annotator_rect.ts b/ui/src/image_annotator_rect.ts index 37553b408a..cd3d614551 100644 --- a/ui/src/image_annotator_rect.ts +++ b/ui/src/image_annotator_rect.ts @@ -59,7 +59,9 @@ export class RectAnnotator { onClick = (cursor_x: U, cursor_y: U, tag: S, start?: Position): DrawnShape | undefined => { let newRect if (!this.resizedCorner && start?.dragging) { - newRect = { shape: { rect: this.createRect(start.x, cursor_x, start.y, cursor_y) }, tag } + const rect = this.createRect(start.x, cursor_x, start.y, cursor_y) + if (!rect) return + newRect = { shape: { rect }, tag } } this.resetDragging()