lost-sia 2.0.1-alpha2 → 2.0.1-alpha6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/package.json +36 -24
  2. package/src/AnnoExampleViewer.jsx +2 -3
  3. package/src/Annotation/logic/Annotation.ts +39 -0
  4. package/src/Annotation/logic/AnnotationUtils.ts +30 -0
  5. package/src/Annotation/ui/AnnotationComponent.tsx +265 -0
  6. package/src/Annotation/ui/atoms/AnnoBar.tsx +132 -0
  7. package/src/Annotation/ui/atoms/DaviIcon.tsx +57 -0
  8. package/src/Annotation/ui/atoms/Edge.tsx +57 -0
  9. package/src/Annotation/ui/atoms/Node.tsx +126 -0
  10. package/src/Annotation/ui/atoms/PolygonArea.tsx +75 -0
  11. package/src/Annotation/ui/tools/BBox.tsx +330 -0
  12. package/src/Annotation/ui/tools/Line.tsx +220 -0
  13. package/src/Annotation/ui/tools/Point.tsx +50 -0
  14. package/src/Annotation/ui/tools/Polygon.tsx +233 -0
  15. package/src/Canvas/Canvas.tsx +869 -0
  16. package/src/Canvas/LabelInput.tsx +74 -0
  17. package/src/InfoBoxes/AnnoDetails.jsx +1 -2
  18. package/src/InfoBoxes/AnnoStats.jsx +0 -1
  19. package/src/InfoBoxes/InfoBox.jsx +2 -2
  20. package/src/InfoBoxes/InfoBoxArea.jsx +1 -4
  21. package/src/InfoBoxes/LabelInfo.jsx +4 -12
  22. package/src/SIAFilterButton.jsx +12 -13
  23. package/src/SIASettingButton.jsx +9 -10
  24. package/src/Sia.jsx +2 -0
  25. package/src/Sia2.tsx +392 -0
  26. package/src/Toolbar/NavigationButtons.tsx +21 -0
  27. package/src/Toolbar/Toolbar.tsx +76 -0
  28. package/src/Toolbar/ToolbarItem.jsx +30 -0
  29. package/src/Toolbar/ToolbarItems/AccessibilityTools.tsx +69 -0
  30. package/src/Toolbar/ToolbarItems/AnnoToolSelector.tsx +88 -0
  31. package/src/Toolbar/ToolbarItems/ImageToolItems/ImageLabel.tsx +62 -0
  32. package/src/Toolbar/ToolbarItems/ImageToolItems/TagLabel.tsx +56 -0
  33. package/src/Toolbar/ToolbarItems/ImageTools.tsx +63 -0
  34. package/src/Toolbar/ToolbarItems/Instructions.tsx +124 -0
  35. package/src/Toolbar/ToolbarItems/InstructionsModal.tsx +20 -0
  36. package/src/filterTools.js +1 -1
  37. package/src/index.ts +14 -0
  38. package/src/models/AllowedTools.tsx +9 -0
  39. package/src/models/AnnotationMode.tsx +12 -0
  40. package/src/models/AnnotationSettings.tsx +9 -0
  41. package/src/models/AnnotationStatus.tsx +9 -0
  42. package/src/models/AnnotationTool.tsx +8 -0
  43. package/src/models/CanvasAction.tsx +29 -0
  44. package/src/models/Direction.tsx +8 -0
  45. package/src/models/EditorModes.tsx +12 -0
  46. package/src/models/ExternalAnnotation.ts +15 -0
  47. package/src/models/KeyAction.tsx +21 -0
  48. package/src/models/Label.tsx +8 -0
  49. package/src/models/UiConfig.tsx +6 -0
  50. package/src/models/index.js +8 -0
  51. package/src/stories/Canvas/Canvas.stories.tsx +156 -0
  52. package/src/stories/Canvas/CanvasOffset.tsx +58 -0
  53. package/src/stories/Canvas/CanvasWithOffset.stories.tsx +156 -0
  54. package/src/stories/FilterDropdown.stories.ts +23 -0
  55. package/src/stories/{Sia.stories.jsx → SIA/SIA.stories.tsx} +25 -14
  56. package/src/stories/SIA2/DemoWrapper.stories.tsx +167 -0
  57. package/src/stories/SIA2/DemoWrapper.tsx +54 -0
  58. package/src/stories/SIA2/Sia2.stories.tsx +62 -0
  59. package/src/stories/Toolbar/ImageTools/ImageLabel.stories.tsx +32 -0
  60. package/src/stories/Toolbar/ImageTools/TagLabel.stories.tsx +37 -0
  61. package/src/stories/Toolbar/Instructions.stories.tsx +22 -0
  62. package/src/stories/Toolbar/Toolbar.stories.tsx +101 -0
  63. package/src/stories/main.scss +6 -0
  64. package/src/stories/siaDummyData2.ts +265 -0
  65. package/src/styles/style.scss +30 -0
  66. package/src/types/notificationType.js +5 -5
  67. package/src/types.tsx +11 -0
  68. package/src/utils/KeyMapper.ts +93 -0
  69. package/src/utils/annoConversion2.ts +145 -0
  70. package/src/utils/color.ts +61 -0
  71. package/src/utils/mouse2.ts +35 -0
  72. package/src/utils/transform2.ts +343 -0
  73. package/src/utils/windowViewport2.ts +50 -0
  74. package/src/index.js +0 -21
  75. package/src/stories/Configure.mdx +0 -369
@@ -0,0 +1,57 @@
1
+ import { CSSProperties } from "react";
2
+ import { Point } from "../../../types";
3
+ import mouse2 from "../../../utils/mouse2";
4
+
5
+ type EdgeProps = {
6
+ startCoordinate: Point;
7
+ endCoordinate: Point;
8
+ isDisabled?: boolean;
9
+ pageToStageOffset: Point;
10
+ svgScale: number;
11
+ style: CSSProperties;
12
+ onAddNode?: (coordinate: Point) => void;
13
+ onDoubleClick?: (e: MouseEvent) => void;
14
+ onMouseDown: (e: MouseEvent) => void;
15
+ onMouseMove: (e: MouseEvent) => void;
16
+ };
17
+
18
+ const Edge = ({
19
+ startCoordinate,
20
+ endCoordinate,
21
+ isDisabled = false,
22
+ pageToStageOffset,
23
+ style,
24
+ svgScale,
25
+ onAddNode = () => {},
26
+ onDoubleClick = () => {},
27
+ onMouseDown,
28
+ onMouseMove,
29
+ }: EdgeProps) => {
30
+ const addNode = (e: MouseEvent) => {
31
+ const mouseStageCoords: Point = mouse2.getAntiScaledMouseStagePosition(
32
+ e,
33
+ pageToStageOffset,
34
+ svgScale,
35
+ );
36
+
37
+ onAddNode(mouseStageCoords);
38
+ };
39
+
40
+ return (
41
+ <line
42
+ x1={startCoordinate.x}
43
+ y1={startCoordinate.y}
44
+ x2={endCoordinate.x}
45
+ y2={endCoordinate.y}
46
+ style={style}
47
+ onClick={(e) => e.ctrlKey && addNode(e)}
48
+ onDoubleClick={onDoubleClick}
49
+ onMouseDown={onMouseDown}
50
+ onMouseMove={onMouseMove}
51
+ onContextMenu={(e) => e.preventDefault()}
52
+ stroke-dasharray={isDisabled ? "10,5" : "0"}
53
+ />
54
+ );
55
+ };
56
+
57
+ export default Edge;
@@ -0,0 +1,126 @@
1
+ import { CSSProperties, MouseEvent, useEffect, useRef, useState } from "react";
2
+ import { Point } from "../../../types";
3
+ import mouse2 from "../../../utils/mouse2";
4
+ import AnnotationSettings from "../../../models/AnnotationSettings";
5
+
6
+ type NodeProps = {
7
+ index: number;
8
+ coordinates: Point;
9
+ annotationSettings: AnnotationSettings;
10
+ pageToStageOffset: Point;
11
+ svgScale: number;
12
+ style: CSSProperties;
13
+ onDeleteNode: () => void;
14
+ onMoving: (index: number, coordinates: Point) => void;
15
+ onMoved: () => void;
16
+ onIsDraggingStateChanged: (bool) => void;
17
+ };
18
+
19
+ const Node = ({
20
+ index,
21
+ coordinates,
22
+ annotationSettings,
23
+ pageToStageOffset,
24
+ svgScale,
25
+ style,
26
+ onDeleteNode,
27
+ onMoving, // during moving - update coordinates in parent
28
+ onMoved, // moving finished - send annotation changed event
29
+ onIsDraggingStateChanged,
30
+ }: NodeProps) => {
31
+ const [hasHalo, setHasHalo] = useState<boolean>(false);
32
+ const [isDragging, setIsDragging] = useState<boolean>(false);
33
+
34
+ // onMove and onMouseUp events are fired in the same frame
35
+ // use a ref to access the updated value without waiting until the next frame
36
+ const [didItActuallyMove, setDidItActuallyMove] = useState<boolean>(false);
37
+ const didItActuallyMoveRef = useRef<boolean>(didItActuallyMove);
38
+
39
+ useEffect(() => {
40
+ didItActuallyMoveRef.current = didItActuallyMove;
41
+ }, [didItActuallyMove]);
42
+
43
+ const onMouseMove = (e: MouseEvent) => {
44
+ if (!isDragging) return;
45
+
46
+ const antiScaledMousePositionInStageCoordinates =
47
+ mouse2.getAntiScaledMouseStagePosition(e, pageToStageOffset, svgScale);
48
+
49
+ // only escalate event when mouse actually moved
50
+ if (e.movementX !== 0 || e.movementY !== 0) {
51
+ setDidItActuallyMove(true);
52
+ onMoving(index, antiScaledMousePositionInStageCoordinates);
53
+ }
54
+ };
55
+
56
+ useEffect(() => {
57
+ onIsDraggingStateChanged(isDragging);
58
+ if (!isDragging) return;
59
+
60
+ const handleMouseUp = () => {
61
+ setIsDragging(false);
62
+ if (didItActuallyMoveRef.current) onMoved();
63
+ setDidItActuallyMove(false);
64
+ };
65
+
66
+ window.addEventListener("mouseup", handleMouseUp);
67
+
68
+ return () => {
69
+ window.removeEventListener("mouseup", handleMouseUp);
70
+ };
71
+ }, [isDragging]);
72
+
73
+ const onMouseDown = (e: MouseEvent) => {
74
+ if (!annotationSettings.canEdit) return;
75
+
76
+ if (e.ctrlKey) onDeleteNode();
77
+ else setIsDragging(true);
78
+ };
79
+
80
+ const renderHalo = () => {
81
+ return (
82
+ <circle
83
+ cx={coordinates.x}
84
+ cy={coordinates.y}
85
+ r={12 / svgScale}
86
+ onMouseLeave={(e) => annotationSettings.canEdit && setHasHalo(false)}
87
+ onMouseDown={onMouseDown}
88
+ onContextMenu={(e) => e.preventDefault()}
89
+ />
90
+ );
91
+ };
92
+
93
+ const renderInfiniteSelectionArea = () => {
94
+ return (
95
+ <circle
96
+ cx={coordinates.x}
97
+ cy={coordinates.y}
98
+ r={"100%"}
99
+ style={{ opacity: 0 }}
100
+ onMouseMove={(e) => onMouseMove(e)}
101
+ onContextMenu={(e) => e.preventDefault()}
102
+ />
103
+ );
104
+ };
105
+
106
+ return (
107
+ <g>
108
+ {isDragging && renderInfiniteSelectionArea()}
109
+ {hasHalo && renderHalo()}
110
+ <circle
111
+ cx={coordinates.x}
112
+ cy={coordinates.y}
113
+ r={10 / svgScale}
114
+ style={style}
115
+ onMouseOver={() => {
116
+ annotationSettings.canEdit && setHasHalo(true);
117
+ }}
118
+ onMouseDown={onMouseDown}
119
+ onMouseMove={(e) => onMouseMove(e)}
120
+ onContextMenu={(e) => e.preventDefault()}
121
+ />
122
+ </g>
123
+ );
124
+ };
125
+
126
+ export default Node;
@@ -0,0 +1,75 @@
1
+ import { CSSProperties, useEffect, useState } from "react";
2
+ import { Point } from "../../../types";
3
+ import AnnotationMode from "../../../models/AnnotationMode";
4
+ import AnnotationSettings from "../../../models/AnnotationSettings";
5
+
6
+ type PolygonAreaProps = {
7
+ coordinates: Point[];
8
+ isSelected: boolean;
9
+ isDisabled?: boolean;
10
+ annotationMode: AnnotationMode;
11
+ annotationSettings: AnnotationSettings;
12
+ pageToStageOffset: Point;
13
+ svgScale: number;
14
+ style: CSSProperties;
15
+ onFinishAnnoCreate?: () => void;
16
+ onMouseDown: (e: MouseEvent) => void;
17
+ onMouseUp?: (e: MouseEvent) => void;
18
+ onMouseMove: (e: MouseEvent) => void;
19
+ onIsDraggingStateChanged: (bool) => void;
20
+ };
21
+
22
+ const PolygonArea = ({
23
+ coordinates,
24
+ isSelected,
25
+ isDisabled = false,
26
+ annotationMode,
27
+ style,
28
+ onFinishAnnoCreate = () => {},
29
+ onMouseDown,
30
+ onMouseUp = () => {},
31
+ onMouseMove,
32
+ }: PolygonAreaProps) => {
33
+ // draw line between nodes
34
+ const svgLineCoords: string = coordinates
35
+ .map((point: Point) => `${point.x},${point.y}`)
36
+ .join(" ");
37
+
38
+ const [cursorStyle, setCursorStyle] = useState<string>("pointer");
39
+
40
+ useEffect(() => {
41
+ if (isDisabled) return setCursorStyle("not-allowed");
42
+ if (isSelected) setCursorStyle("grab");
43
+ else setCursorStyle("pointer");
44
+ }, [isSelected, isDisabled]);
45
+
46
+ // adjust style for polyline
47
+ const polyLineStyle = { ...style };
48
+ polyLineStyle.cursor = cursorStyle;
49
+ polyLineStyle.fillOpacity = isSelected ? 0 : 0.3;
50
+
51
+ // dont show the polygon edges (the line does what as a stripe if enabled)
52
+ if (isSelected && isDisabled) polyLineStyle.stroke = "none";
53
+
54
+ return (
55
+ <polygon
56
+ points={svgLineCoords}
57
+ style={polyLineStyle}
58
+ onMouseDown={(e) => {
59
+ isSelected && setCursorStyle("grabbing");
60
+ onMouseDown(e);
61
+ }}
62
+ onMouseUp={(e) => {
63
+ setCursorStyle("grab");
64
+ onMouseUp(e);
65
+ }}
66
+ onDoubleClick={() =>
67
+ annotationMode === AnnotationMode.CREATE && onFinishAnnoCreate()
68
+ }
69
+ onMouseMove={onMouseMove}
70
+ onContextMenu={(e) => e.preventDefault()}
71
+ />
72
+ );
73
+ };
74
+
75
+ export default PolygonArea;
@@ -0,0 +1,330 @@
1
+ import { CSSProperties, useEffect, useRef, useState } from "react";
2
+
3
+ // rename type to avoid naming conflict
4
+ import { Point } from "../../../types";
5
+ import Node from "../atoms/Node";
6
+ import AnnotationSettings from "../../../models/AnnotationSettings";
7
+ import AnnotationMode from "../../../models/AnnotationMode";
8
+ import mouse2 from "../../../utils/mouse2";
9
+ import PolygonArea from "../atoms/PolygonArea";
10
+ import Edge from "../atoms/Edge";
11
+
12
+ type BBoxProps = {
13
+ annotationMode: AnnotationMode;
14
+ annotationSettings: AnnotationSettings;
15
+ pageToStageOffset: Point;
16
+ startCoords: Point;
17
+ endCoords: Point;
18
+ svgScale: number;
19
+ isSelected: boolean;
20
+ style: CSSProperties;
21
+ onDeleteNode: (coordinates: Point[]) => void;
22
+ onFinishAnnoCreate: () => void;
23
+ onIsDraggingStateChanged: (bool) => void;
24
+ onMoving: (coordinates: Point[]) => void; // during moving - update coordinates in parent
25
+ onMoved: () => void; // moving finished - send annotation changed event
26
+ };
27
+
28
+ const BBox = ({
29
+ annotationMode,
30
+ annotationSettings,
31
+ pageToStageOffset,
32
+ startCoords,
33
+ endCoords,
34
+ svgScale,
35
+ isSelected,
36
+ style,
37
+ onDeleteNode,
38
+ onFinishAnnoCreate,
39
+ onIsDraggingStateChanged,
40
+ onMoving,
41
+ onMoved,
42
+ }: BBoxProps) => {
43
+ // build up 4 coordinates ("u"-style, 1 for each corner)
44
+ const coordinates: Point[] = [
45
+ startCoords,
46
+ { x: startCoords.x, y: endCoords.y },
47
+ endCoords,
48
+ { x: endCoords.x, y: startCoords.y },
49
+ ];
50
+
51
+ const [isAnnoCreating, setIsAnnoCreating] = useState<boolean>(
52
+ annotationMode === AnnotationMode.CREATE,
53
+ );
54
+ const [isAnnoDragging, setIsAnnoDragging] = useState<boolean>(false);
55
+ const [isEdgeDragging, setIsEdgeDragging] = useState<boolean>(false);
56
+ const [dragSelectedEdgeIndex, setDragSelectedEdgeIndex] = useState<number>(0);
57
+
58
+ // onMove and onMouseUp events are fired in the same frame
59
+ // use a ref to access the updated value without waiting until the next frame
60
+ const [didAnnoActuallyMove, setDidAnnoActuallyMove] =
61
+ useState<boolean>(false);
62
+ const didAnnoActuallyMoveRef = useRef<boolean>(didAnnoActuallyMove);
63
+
64
+ useEffect(() => {
65
+ didAnnoActuallyMoveRef.current = didAnnoActuallyMove;
66
+ }, [didAnnoActuallyMove]);
67
+
68
+ const onMouseDown = (e: MouseEvent) => {
69
+ if (annotationSettings.canEdit === false) return;
70
+
71
+ if (
72
+ isSelected &&
73
+ annotationMode !== AnnotationMode.CREATE &&
74
+ e.button === 0
75
+ )
76
+ setIsAnnoDragging(true);
77
+ };
78
+
79
+ const onMouseEdgeDown = (e: MouseEvent) => {
80
+ if (annotationSettings.canEdit === false) return;
81
+
82
+ if (
83
+ isSelected &&
84
+ annotationMode !== AnnotationMode.CREATE &&
85
+ e.button === 0
86
+ )
87
+ setIsEdgeDragging(true);
88
+ };
89
+
90
+ const onMouseMove = (e: MouseEvent) => {
91
+ if (isAnnoDragging) {
92
+ // we always get 4 coordinates (the rectangle corners)
93
+ // only the top left and bottom right corner are important - other coordinates are redundant
94
+ const newRectangle: Point[] = [
95
+ { ...coordinates[0] },
96
+ { ...coordinates[2] },
97
+ ];
98
+
99
+ // apply mouse move to the rectangle coordinates
100
+ const movedCoordinates: Point[] = newRectangle.map(
101
+ (coordinate: Point) => {
102
+ return {
103
+ // counter the canvas scaling (it will be automatically applied when rendering the annotation coordinates)
104
+ x: (coordinate.x += e.movementX / svgScale),
105
+ y: (coordinate.y += e.movementY / svgScale),
106
+ };
107
+ },
108
+ );
109
+
110
+ // only escalate event when mouse actually moved
111
+ if (e.movementX !== 0 || e.movementY !== 0) {
112
+ setDidAnnoActuallyMove(true);
113
+ onMoving(movedCoordinates);
114
+ }
115
+ }
116
+
117
+ if (annotationMode === AnnotationMode.CREATE) {
118
+ const mousePointInStage = mouse2.getAntiScaledMouseStagePosition(
119
+ e,
120
+ pageToStageOffset,
121
+ svgScale,
122
+ );
123
+
124
+ let newCoords: Point[] = [...coordinates];
125
+
126
+ // we always get 4 coordinates (the rectangle corners)
127
+ // only the top left (start) is important - the end will be our mouse position and the others are redundant
128
+ const newRectangle: Point[] = [newCoords[0], mousePointInStage];
129
+
130
+ onMoving(newRectangle);
131
+ }
132
+ };
133
+
134
+ useEffect(() => {
135
+ if (!isAnnoCreating) return;
136
+
137
+ const handleMouseUp = (e: MouseEvent) => {
138
+ if (e.button === 2) {
139
+ onFinishAnnoCreate();
140
+ setIsAnnoCreating(false);
141
+ }
142
+ };
143
+
144
+ window.addEventListener("mouseup", handleMouseUp);
145
+
146
+ return () => {
147
+ window.removeEventListener("mouseup", handleMouseUp);
148
+ };
149
+ }, [isAnnoCreating]);
150
+
151
+ useEffect(() => {
152
+ onIsDraggingStateChanged(isAnnoDragging);
153
+ if (!isAnnoDragging) return;
154
+
155
+ const handleMouseUp = () => {
156
+ setIsAnnoDragging(false);
157
+
158
+ if (didAnnoActuallyMoveRef.current) onMoved();
159
+ setDidAnnoActuallyMove(false);
160
+ };
161
+
162
+ window.addEventListener("mouseup", handleMouseUp);
163
+
164
+ return () => {
165
+ window.removeEventListener("mouseup", handleMouseUp);
166
+ };
167
+ }, [isAnnoDragging]);
168
+
169
+ useEffect(() => {
170
+ onIsDraggingStateChanged(isEdgeDragging);
171
+ if (!isEdgeDragging) return;
172
+
173
+ const handleMouseUp = () => {
174
+ setIsEdgeDragging(false);
175
+
176
+ if (didAnnoActuallyMoveRef.current) onMoved();
177
+ setDidAnnoActuallyMove(false);
178
+ };
179
+
180
+ window.addEventListener("mouseup", handleMouseUp);
181
+
182
+ return () => {
183
+ window.removeEventListener("mouseup", handleMouseUp);
184
+ };
185
+ }, [isEdgeDragging]);
186
+
187
+ const renderNodes = () => {
188
+ const svgNodes = coordinates.map((coordinate: Point, index: number) => (
189
+ <Node
190
+ key={`node_${index}`}
191
+ index={index}
192
+ annotationSettings={annotationSettings}
193
+ coordinates={coordinate}
194
+ pageToStageOffset={pageToStageOffset}
195
+ svgScale={svgScale}
196
+ style={style}
197
+ onDeleteNode={() => {
198
+ const newCoordinates = [...coordinates];
199
+ newCoordinates.splice(index, 1);
200
+ onDeleteNode(newCoordinates);
201
+ }}
202
+ onMoving={(index: number, newPoint: Point) => {
203
+ // we always get 4 coordinates (the rectangle corners)
204
+ // only the top left and bottom right corner are important - other coordinates are redundant
205
+ const newRectangle: Point[] = [coordinates[0], coordinates[2]];
206
+
207
+ // update start + end coordinates depending on which corner is moved
208
+ switch (index) {
209
+ case 0: // top left corner
210
+ newRectangle[0] = newPoint;
211
+ break;
212
+ case 1: // bottom left corner
213
+ newRectangle[0].x = newPoint.x;
214
+ newRectangle[1].y = newPoint.y;
215
+ break;
216
+ case 2: // bottom right corner
217
+ newRectangle[1] = newPoint;
218
+ break;
219
+ case 3: // top right corner
220
+ newRectangle[1].x = newPoint.x;
221
+ newRectangle[0].y = newPoint.y;
222
+ break;
223
+ }
224
+
225
+ onMoving(newRectangle);
226
+ }}
227
+ onMoved={() => onMoved()}
228
+ onIsDraggingStateChanged={onIsDraggingStateChanged}
229
+ />
230
+ ));
231
+
232
+ return svgNodes;
233
+ };
234
+
235
+ const moveEdge = (edgeIndex: number, e: MouseEvent) => {
236
+ // we always get 4 coordinates (the rectangle corners)
237
+ // only the top left and bottom right corner are important - other coordinates are redundant
238
+ const newRectangle: Point[] = [coordinates[0], coordinates[2]];
239
+ switch (edgeIndex) {
240
+ case 0:
241
+ newRectangle[0].x += e.movementX / svgScale;
242
+ break;
243
+ case 1:
244
+ newRectangle[1].y += e.movementY / svgScale;
245
+ break;
246
+ case 2:
247
+ newRectangle[1].x += e.movementX / svgScale;
248
+ break;
249
+ case 3:
250
+ newRectangle[0].y += e.movementY / svgScale;
251
+ break;
252
+ }
253
+
254
+ // only escalate event when mouse actually moved
255
+ if (e.movementX !== 0 || e.movementY !== 0) {
256
+ setDidAnnoActuallyMove(true);
257
+ onMoving(newRectangle);
258
+ }
259
+ };
260
+
261
+ const renderEdges = () => {
262
+ const svgEdges = coordinates.map((coordinate: Point, index: number) => {
263
+ const endCoordinates: Point =
264
+ index + 1 < coordinates.length
265
+ ? coordinates[index + 1]
266
+ : coordinates[0];
267
+
268
+ // corresponding cursor for horizontal or vertical edges
269
+ const cursor = index % 2 === 0 ? "ew-resize" : "ns-resize";
270
+
271
+ return (
272
+ <Edge
273
+ key={`edge_${index}`}
274
+ startCoordinate={coordinate}
275
+ endCoordinate={endCoordinates}
276
+ pageToStageOffset={pageToStageOffset}
277
+ svgScale={svgScale}
278
+ style={{ ...style, cursor }}
279
+ onMouseDown={onMouseEdgeDown}
280
+ onMouseMove={(e: MouseEvent) => {
281
+ setDragSelectedEdgeIndex(index);
282
+ isEdgeDragging && moveEdge(index, e);
283
+ }}
284
+ />
285
+ );
286
+ });
287
+ return svgEdges;
288
+ };
289
+
290
+ const renderInfiniteSelectionArea = (isForEdge: boolean) => {
291
+ return (
292
+ <circle
293
+ cx={coordinates[0].x}
294
+ cy={coordinates[0].y}
295
+ r={"100%"}
296
+ style={{ opacity: 0 }}
297
+ onMouseDown={onMouseDown}
298
+ onMouseMove={(e: MouseEvent) => {
299
+ isForEdge && isEdgeDragging && moveEdge(dragSelectedEdgeIndex, e);
300
+ !isForEdge && onMouseMove(e);
301
+ }}
302
+ onContextMenu={(e) => e.preventDefault()}
303
+ />
304
+ );
305
+ };
306
+
307
+ return (
308
+ <g>
309
+ {(isAnnoDragging || annotationMode === AnnotationMode.CREATE) &&
310
+ renderInfiniteSelectionArea(false)}
311
+ <PolygonArea
312
+ annotationSettings={annotationSettings}
313
+ coordinates={coordinates}
314
+ isSelected={isSelected}
315
+ annotationMode={annotationMode}
316
+ pageToStageOffset={pageToStageOffset}
317
+ style={style}
318
+ svgScale={svgScale}
319
+ onIsDraggingStateChanged={onIsDraggingStateChanged}
320
+ onMouseDown={onMouseDown}
321
+ onMouseMove={onMouseMove}
322
+ />
323
+ {isEdgeDragging && renderInfiniteSelectionArea(true)}
324
+ {isSelected && annotationSettings.canEdit && renderEdges()}
325
+ {isSelected && annotationMode !== AnnotationMode.CREATE && renderNodes()}
326
+ </g>
327
+ );
328
+ };
329
+
330
+ export default BBox;