Skip to content

Commit b496d83

Browse files
committed
Update interfaces/tests/utils
1 parent d7fa394 commit b496d83

File tree

9 files changed

+237
-67
lines changed

9 files changed

+237
-67
lines changed

web_ui/packages/smart-tools/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ export { buildIntelligentScissorsInstance, IntelligentScissors } from './src/int
1616

1717
export { Anchor } from './src/edit-bounding-box/anchor.component';
1818
export { ANCHOR_SIZE, ResizeAnchor } from './src/edit-bounding-box/resize-anchor.component';
19+
export { EditBoundingBox } from './src/edit-bounding-box/edit-bounding-box/edit-bounding-box.component';
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copyright (C) 2022-2025 Intel Corporation
2+
// LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE
3+
4+
declare module '*.module.scss' {
5+
const classes: { [key: string]: string };
6+
export default classes;
7+
}

web_ui/packages/smart-tools/src/edit-bounding-box/anchor.component.tsx

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,7 @@ import { CSSProperties, PointerEvent, ReactNode, useState } from 'react';
66
import { isFunction } from 'lodash-es';
77

88
import { Point } from '../shared/interfaces';
9-
10-
interface MouseButton {
11-
button: number;
12-
buttons: number;
13-
}
14-
15-
const BUTTON_LEFT = {
16-
button: 0,
17-
buttons: 1,
18-
};
19-
const isLeftButton = (button: MouseButton): boolean => {
20-
return button.button === BUTTON_LEFT.button || button.buttons === BUTTON_LEFT.buttons;
21-
};
9+
import { isLeftButton } from '../utils/mouse-utils';
2210

2311
interface AnchorProps {
2412
children: ReactNode;

web_ui/packages/smart-tools/src/edit-bounding-box/anchor.test.tsx

Lines changed: 0 additions & 53 deletions
This file was deleted.

web_ui/packages/smart-tools/src/shared/interfaces.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,12 @@ export interface Vector {
5252
x: number;
5353
y: number;
5454
}
55+
56+
export interface Annotation {
57+
readonly id: string;
58+
readonly shape: Shape;
59+
readonly zIndex: number;
60+
readonly isSelected: boolean;
61+
readonly isHidden: boolean;
62+
readonly isLocked: boolean;
63+
}

web_ui/packages/smart-tools/src/utils/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ export {
1212
isPolygonValid,
1313
getPointsFromMat,
1414
getMatFromPoints,
15+
getBoundingBoxInRoi,
16+
getBoundingBoxResizePoints,
17+
getClampedBoundingBox,
1518
} from './tool-utils';
19+
export { isLeftButton, isWheelButton, allowPanning } from './mouse-utils';
1620
export * as Vec2 from './vec2';
1721
export {
1822
degreesToRadians,
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright (C) 2022-2025 Intel Corporation
2+
// LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE
3+
4+
import { PointerEvent, SVGProps } from 'react';
5+
6+
interface MouseButton {
7+
button: number;
8+
buttons: number;
9+
}
10+
11+
const BUTTON_LEFT = {
12+
button: 0,
13+
buttons: 1,
14+
};
15+
const BUTTON_WHEEL = {
16+
button: 1,
17+
buttons: 4,
18+
};
19+
20+
const isButton = (button: MouseButton, buttonToCompare: MouseButton): boolean =>
21+
button.button === buttonToCompare.button || button.buttons === buttonToCompare.buttons;
22+
23+
export const isLeftButton = (button: MouseButton): boolean => {
24+
return button.button === BUTTON_LEFT.button || button.buttons === BUTTON_LEFT.buttons;
25+
};
26+
27+
export const isWheelButton = (button: MouseButton): boolean => {
28+
return isButton(button, BUTTON_WHEEL);
29+
};
30+
31+
type OnPointerDown = SVGProps<SVGElement>['onPointerDown'];
32+
export const allowPanning = (onPointerDown?: OnPointerDown): OnPointerDown | undefined => {
33+
if (onPointerDown === undefined) {
34+
return;
35+
}
36+
37+
return (event: PointerEvent<SVGElement>) => {
38+
const isPressingPanningHotKeys = (isLeftButton(event) && event.ctrlKey) || isWheelButton(event);
39+
40+
if (isPressingPanningHotKeys) {
41+
return;
42+
}
43+
44+
return onPointerDown(event);
45+
};
46+
};
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
// Copyright (C) 2022-2025 Intel Corporation
2+
// LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE
3+
4+
import { getBoundingBoxInRoi, getBoundingBoxResizePoints, getClampedBoundingBox } from './tool-utils';
5+
6+
const mockedRoi = {
7+
x: 0,
8+
y: 0,
9+
width: 100,
10+
height: 100,
11+
};
12+
13+
describe('edit-tool utils', () => {
14+
describe('getClampedBoundingBox', () => {
15+
const mockedBoundingBox = { x: 0, y: 0, width: 20, height: 20 };
16+
test.each([
17+
[{ x: 0, y: 0 }, mockedBoundingBox, { ...mockedBoundingBox, x: mockedRoi.x, y: mockedRoi.y }], // No clamping needed
18+
[{ x: mockedRoi.width, y: mockedRoi.height }, mockedBoundingBox, { ...mockedBoundingBox, x: 80, y: 80 }], // Clamped outside bottom-right
19+
[{ x: -10, y: -10 }, mockedBoundingBox, { ...mockedBoundingBox }], // Clamped outside top-left
20+
])('given point %o move the boundingBox %o to %o', (point, boundingBox, expectedResult) => {
21+
const result = getClampedBoundingBox(point, boundingBox, mockedRoi);
22+
expect(result).toEqual(expectedResult);
23+
});
24+
});
25+
26+
describe('getBoundingBoxInRoi', () => {
27+
const mockedBoundingBox = { x: 0, y: 0, width: 10, height: 10 };
28+
29+
test.each([
30+
[mockedBoundingBox, mockedBoundingBox], // Completely inside ROI
31+
[{ ...mockedBoundingBox, x: -10 }, mockedBoundingBox], // Partially outside left
32+
[
33+
{ ...mockedBoundingBox, x: 95 },
34+
{ ...mockedBoundingBox, x: 95, width: 5 }, // Partially outside right
35+
],
36+
[{ ...mockedBoundingBox, y: -10 }, mockedBoundingBox], // Partially outside top
37+
[
38+
{ ...mockedBoundingBox, y: 95 },
39+
{ ...mockedBoundingBox, y: 95, height: 5 }, // Partially outside bottom
40+
],
41+
])('given boundingBox %o, returns the portion inside the roi %o', (boundingBox, expectedResult) => {
42+
const result = getBoundingBoxInRoi(boundingBox, mockedRoi);
43+
expect(result).toEqual(expectedResult);
44+
});
45+
});
46+
47+
describe('getBoundingBoxResizePoints', () => {
48+
it('should create resize points with correct properties', () => {
49+
const gap = 0;
50+
const boundingBox = { x: 50, y: 50, width: 50, height: 50 };
51+
const resizePoints = getBoundingBoxResizePoints({ boundingBox, gap, onResized: jest.fn() });
52+
53+
const expectedAnchors = [
54+
{ x: 50, y: 50, cursor: 'nw-resize', label: 'North west resize anchor' },
55+
{ x: 75, y: 50, cursor: 'n-resize', label: 'North resize anchor' },
56+
{ x: 100, y: 50, cursor: 'ne-resize', label: 'North east resize anchor' },
57+
{ x: 100, y: 75, cursor: 'e-resize', label: 'East resize anchor' },
58+
{ x: 100, y: 100, cursor: 'se-resize', label: 'South east resize anchor' },
59+
{ x: 75, y: 100, cursor: 's-resize', label: 'South resize anchor' },
60+
{ x: 50, y: 100, cursor: 'sw-resize', label: 'South west resize anchor' },
61+
{ x: 50, y: 75, cursor: 'w-resize', label: 'West resize anchor' },
62+
];
63+
64+
expect(resizePoints).toHaveLength(8);
65+
expectedAnchors.forEach((expected, index) => {
66+
expect(resizePoints[index]).toMatchObject(expected);
67+
});
68+
});
69+
70+
describe('getBoundingBoxResizePoints', () => {
71+
const gap = 0;
72+
const onResized = jest.fn();
73+
const boundingBox = { x: 50, y: 50, width: 50, height: 50 };
74+
const resizePoints = getBoundingBoxResizePoints({ boundingBox, gap, onResized });
75+
const [
76+
topLeftPoint,
77+
topCenterPoint,
78+
topRightPoint,
79+
rightCenterPoint,
80+
bottomRightPoint,
81+
bottomCenterPoint,
82+
bottomLeftPoint,
83+
centerLeftPoint,
84+
] = resizePoints;
85+
86+
const testCases = [
87+
{
88+
index: 0,
89+
moveTo: [topLeftPoint.x - 50, 0], // Move NW anchor 50 units left and up
90+
expected: { x: 0, y: 0, width: 100, height: 100 },
91+
description: 'topLeftPoint',
92+
},
93+
{
94+
index: 1,
95+
moveTo: [topCenterPoint.x - 50, 0], // Move N anchor 50 units up
96+
expected: { x: 50, y: 0, width: 50, height: 100 },
97+
description: 'topCenterPoint',
98+
},
99+
{
100+
index: 2,
101+
moveTo: [topRightPoint.x + 50, 0], // Move NE anchor 50 units up
102+
expected: { x: 50, y: 0, width: 100, height: 100 },
103+
description: 'topRightPoint',
104+
},
105+
{
106+
index: 3,
107+
moveTo: [rightCenterPoint.x + 50, 0], // Move E anchor 50 units right
108+
expected: { x: 50, y: 50, width: 100, height: 50 },
109+
description: 'rightCenterPoint',
110+
},
111+
{
112+
index: 4,
113+
moveTo: [bottomRightPoint.x + 50, bottomRightPoint.y + 50], // Move SE anchor 50 units down and right
114+
expected: { x: 50, y: 50, width: 100, height: 100 },
115+
description: 'bottomRightPoint',
116+
},
117+
{
118+
index: 5,
119+
moveTo: [0, bottomCenterPoint.y + 50], // Move S anchor 50 units down
120+
expected: { x: 50, y: 50, width: 50, height: 100 },
121+
description: 'bottomCenterPoint',
122+
},
123+
{
124+
index: 6,
125+
moveTo: [0, bottomLeftPoint.y + 50], // Move SW anchor 50 units down and left
126+
expected: { x: 0, y: 50, width: 100, height: 100 },
127+
description: 'bottomLeftPoint',
128+
},
129+
130+
{
131+
index: 7,
132+
moveTo: [centerLeftPoint.x - 50, 0], // Move W anchor 50 units left
133+
expected: { x: 0, y: 50, width: 100, height: 50 },
134+
description: 'centerLeftPoint',
135+
},
136+
];
137+
138+
test.each(testCases)('$description moves and resizes correctly', ({ index, moveTo, expected }) => {
139+
const [x, y] = moveTo;
140+
resizePoints[index].moveAnchorTo(x, y);
141+
expect(onResized).toHaveBeenCalledWith(expected);
142+
});
143+
});
144+
145+
it('should not go below gap', () => {
146+
const gap = 20;
147+
const onResized = jest.fn();
148+
const boundingBox = { x: 50, y: 50, width: 50, height: 50 };
149+
const resizePoints = getBoundingBoxResizePoints({ boundingBox, gap, onResized });
150+
151+
resizePoints[3].moveAnchorTo(-100, 0);
152+
153+
expect(onResized).toHaveBeenCalledWith({ ...boundingBox, width: gap });
154+
});
155+
156+
it('clamp to minimum 0 for coordinates', () => {
157+
const gap = 20;
158+
const onResized = jest.fn();
159+
const boundingBox = { x: 50, y: 50, width: 50, height: 50 };
160+
const resizePoints = getBoundingBoxResizePoints({ boundingBox, gap, onResized });
161+
162+
resizePoints[6].moveAnchorTo(-10, -10);
163+
164+
expect(onResized).toHaveBeenCalledWith({ x: 0, width: 100, y: 50, height: gap });
165+
});
166+
});
167+
});

web_ui/packages/smart-tools/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
{
22
"extends": "@geti/config/typescript",
33
"include": [
4-
"src"
4+
"src",
5+
"./src/css-modules.d.ts"
56
],
67
"exclude": [
78
"./src/opencv/4.9.0"

0 commit comments

Comments
 (0)