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.
- package/package.json +36 -24
- package/src/AnnoExampleViewer.jsx +2 -3
- package/src/Annotation/logic/Annotation.ts +39 -0
- package/src/Annotation/logic/AnnotationUtils.ts +30 -0
- package/src/Annotation/ui/AnnotationComponent.tsx +265 -0
- package/src/Annotation/ui/atoms/AnnoBar.tsx +132 -0
- package/src/Annotation/ui/atoms/DaviIcon.tsx +57 -0
- package/src/Annotation/ui/atoms/Edge.tsx +57 -0
- package/src/Annotation/ui/atoms/Node.tsx +126 -0
- package/src/Annotation/ui/atoms/PolygonArea.tsx +75 -0
- package/src/Annotation/ui/tools/BBox.tsx +330 -0
- package/src/Annotation/ui/tools/Line.tsx +220 -0
- package/src/Annotation/ui/tools/Point.tsx +50 -0
- package/src/Annotation/ui/tools/Polygon.tsx +233 -0
- package/src/Canvas/Canvas.tsx +869 -0
- package/src/Canvas/LabelInput.tsx +74 -0
- package/src/InfoBoxes/AnnoDetails.jsx +1 -2
- package/src/InfoBoxes/AnnoStats.jsx +0 -1
- package/src/InfoBoxes/InfoBox.jsx +2 -2
- package/src/InfoBoxes/InfoBoxArea.jsx +1 -4
- package/src/InfoBoxes/LabelInfo.jsx +4 -12
- package/src/SIAFilterButton.jsx +12 -13
- package/src/SIASettingButton.jsx +9 -10
- package/src/Sia.jsx +2 -0
- package/src/Sia2.tsx +392 -0
- package/src/Toolbar/NavigationButtons.tsx +21 -0
- package/src/Toolbar/Toolbar.tsx +76 -0
- package/src/Toolbar/ToolbarItem.jsx +30 -0
- package/src/Toolbar/ToolbarItems/AccessibilityTools.tsx +69 -0
- package/src/Toolbar/ToolbarItems/AnnoToolSelector.tsx +88 -0
- package/src/Toolbar/ToolbarItems/ImageToolItems/ImageLabel.tsx +62 -0
- package/src/Toolbar/ToolbarItems/ImageToolItems/TagLabel.tsx +56 -0
- package/src/Toolbar/ToolbarItems/ImageTools.tsx +63 -0
- package/src/Toolbar/ToolbarItems/Instructions.tsx +124 -0
- package/src/Toolbar/ToolbarItems/InstructionsModal.tsx +20 -0
- package/src/filterTools.js +1 -1
- package/src/index.ts +14 -0
- package/src/models/AllowedTools.tsx +9 -0
- package/src/models/AnnotationMode.tsx +12 -0
- package/src/models/AnnotationSettings.tsx +9 -0
- package/src/models/AnnotationStatus.tsx +9 -0
- package/src/models/AnnotationTool.tsx +8 -0
- package/src/models/CanvasAction.tsx +29 -0
- package/src/models/Direction.tsx +8 -0
- package/src/models/EditorModes.tsx +12 -0
- package/src/models/ExternalAnnotation.ts +15 -0
- package/src/models/KeyAction.tsx +21 -0
- package/src/models/Label.tsx +8 -0
- package/src/models/UiConfig.tsx +6 -0
- package/src/models/index.js +8 -0
- package/src/stories/Canvas/Canvas.stories.tsx +156 -0
- package/src/stories/Canvas/CanvasOffset.tsx +58 -0
- package/src/stories/Canvas/CanvasWithOffset.stories.tsx +156 -0
- package/src/stories/FilterDropdown.stories.ts +23 -0
- package/src/stories/{Sia.stories.jsx → SIA/SIA.stories.tsx} +25 -14
- package/src/stories/SIA2/DemoWrapper.stories.tsx +167 -0
- package/src/stories/SIA2/DemoWrapper.tsx +54 -0
- package/src/stories/SIA2/Sia2.stories.tsx +62 -0
- package/src/stories/Toolbar/ImageTools/ImageLabel.stories.tsx +32 -0
- package/src/stories/Toolbar/ImageTools/TagLabel.stories.tsx +37 -0
- package/src/stories/Toolbar/Instructions.stories.tsx +22 -0
- package/src/stories/Toolbar/Toolbar.stories.tsx +101 -0
- package/src/stories/main.scss +6 -0
- package/src/stories/siaDummyData2.ts +265 -0
- package/src/styles/style.scss +30 -0
- package/src/types/notificationType.js +5 -5
- package/src/types.tsx +11 -0
- package/src/utils/KeyMapper.ts +93 -0
- package/src/utils/annoConversion2.ts +145 -0
- package/src/utils/color.ts +61 -0
- package/src/utils/mouse2.ts +35 -0
- package/src/utils/transform2.ts +343 -0
- package/src/utils/windowViewport2.ts +50 -0
- package/src/index.js +0 -21
- 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;
|