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()