@deepnoid/canvas 0.1.38 → 0.1.39
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/dist/components/{AnnotationViewer → AnnotationCanvas}/_hooks/useImagePanZoom.d.ts +7 -13
- package/dist/components/{AnnotationViewer → AnnotationCanvas}/_hooks/useImagePanZoom.js +33 -23
- package/dist/components/AnnotationCanvas/_utils/createHistory.d.ts +11 -0
- package/dist/components/AnnotationCanvas/_utils/createHistory.js +34 -0
- package/dist/components/AnnotationCanvas/_utils/panZoom.d.ts +10 -0
- package/dist/components/AnnotationCanvas/_utils/panZoom.js +29 -0
- package/dist/components/AnnotationCanvas/index.d.ts +25 -0
- package/dist/components/AnnotationCanvas/index.js +83 -0
- package/dist/components/AnnotationLayer/_hooks/drawEvents/rectangle.d.ts +5 -0
- package/dist/components/AnnotationLayer/_hooks/drawEvents/rectangle.js +75 -0
- package/dist/components/AnnotationLayer/_hooks/drawEvents/rectangleUtils.d.ts +30 -0
- package/dist/components/AnnotationLayer/_hooks/drawEvents/rectangleUtils.js +211 -0
- package/dist/components/AnnotationLayer/_hooks/drawEvents/useDrawEvents.d.ts +25 -0
- package/dist/components/AnnotationLayer/_hooks/drawEvents/useDrawEvents.js +42 -0
- package/dist/components/AnnotationLayer/_hooks/useCanvasDraw.d.ts +13 -0
- package/dist/components/AnnotationLayer/_hooks/useCanvasDraw.js +115 -0
- package/dist/components/AnnotationLayer/_hooks/useHotkeys.d.ts +7 -0
- package/dist/components/AnnotationLayer/_hooks/useHotkeys.js +15 -0
- package/dist/components/AnnotationLayer/_hooks/useMouse.d.ts +21 -0
- package/dist/components/AnnotationLayer/_hooks/useMouse.js +34 -0
- package/dist/components/AnnotationLayer/index.d.ts +10 -13
- package/dist/components/AnnotationLayer/index.js +116 -22
- package/dist/components/index.d.ts +8 -0
- package/dist/components/index.js +8 -0
- package/dist/enum/common.d.ts +13 -0
- package/dist/enum/common.js +15 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +2 -2
- package/dist/types/index.d.ts +58 -0
- package/dist/utils/canvas.d.ts +3 -0
- package/dist/utils/canvas.js +37 -0
- package/dist/utils/common/cloneDeep.d.ts +1 -0
- package/dist/utils/common/cloneDeep.js +18 -0
- package/dist/utils/common/cloneDeepWith.d.ts +1 -0
- package/dist/utils/common/cloneDeepWith.js +34 -0
- package/dist/utils/common/isEqualWith.d.ts +2 -0
- package/dist/utils/common/isEqualWith.js +70 -0
- package/dist/utils/mouseActions.d.ts +8 -0
- package/dist/utils/mouseActions.js +19 -0
- package/dist/utils/pointTransform.d.ts +2 -0
- package/dist/utils/pointTransform.js +46 -0
- package/package.json +2 -1
- package/dist/components/AnnotationViewer/index.d.ts +0 -22
- package/dist/components/AnnotationViewer/index.js +0 -80
- package/dist/constants/graphic.d.ts +0 -1
- package/dist/constants/graphic.js +0 -1
- package/dist/types/coordinate.d.ts +0 -10
- package/dist/utils/graphic.d.ts +0 -48
- package/dist/utils/graphic.js +0 -158
- /package/dist/types/{coordinate.js → index.js} +0 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { MouseAction } from '../../../../utils/mouseActions';
|
|
2
|
+
import { cloneDeep } from '../../../../utils/common/cloneDeep';
|
|
3
|
+
import { DrawMode, RectangleAnchor } from '../../../../enum/common';
|
|
4
|
+
import { getMousePointTransform } from '../../../../utils/pointTransform';
|
|
5
|
+
export const ACTIVE_POINT_SIZE = 10;
|
|
6
|
+
export const getRectangleCorners = ({ x, y, width, height }) => {
|
|
7
|
+
return [
|
|
8
|
+
{ x, y },
|
|
9
|
+
{ x: x + width, y },
|
|
10
|
+
{ x, y: y + height },
|
|
11
|
+
{ x: x + width, y: y + height },
|
|
12
|
+
];
|
|
13
|
+
};
|
|
14
|
+
export const selectRectangleAtPoint = (zoom, mousePoint, coordinatesRef, currentCoordinateRef) => {
|
|
15
|
+
const coordinates = coordinatesRef.current;
|
|
16
|
+
const { x: mouseX, y: mouseY } = mousePoint;
|
|
17
|
+
const padding = ACTIVE_POINT_SIZE / zoom;
|
|
18
|
+
const isPointInBBox = (c) => mouseX >= c.x - padding &&
|
|
19
|
+
mouseX <= c.x + c.width + padding &&
|
|
20
|
+
mouseY >= c.y - padding &&
|
|
21
|
+
mouseY <= c.y + c.height + padding;
|
|
22
|
+
let selectedIndex = -1;
|
|
23
|
+
let minArea = Number.MAX_SAFE_INTEGER;
|
|
24
|
+
const currentIndex = coordinates.findIndex((c) => c.selected);
|
|
25
|
+
if (currentIndex >= 0) {
|
|
26
|
+
const c = coordinates[currentIndex];
|
|
27
|
+
if (c.type === DrawMode.RECTANGLE && isPointInBBox(c)) {
|
|
28
|
+
selectedIndex = currentIndex;
|
|
29
|
+
minArea = c.width * c.height;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (selectedIndex === -1) {
|
|
33
|
+
coordinates.forEach((c, idx) => {
|
|
34
|
+
if (c.type !== DrawMode.RECTANGLE || !isPointInBBox(c))
|
|
35
|
+
return;
|
|
36
|
+
const area = c.width * c.height;
|
|
37
|
+
if (area < minArea) {
|
|
38
|
+
minArea = area;
|
|
39
|
+
selectedIndex = idx;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
coordinates.forEach((c) => (c.selected = false));
|
|
44
|
+
if (selectedIndex >= 0) {
|
|
45
|
+
coordinates[selectedIndex].selected = true;
|
|
46
|
+
currentCoordinateRef.current = coordinates[selectedIndex];
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
currentCoordinateRef.current = null;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
export const updateActiveRectangleAnchor = (zoom, mousePointRef, currentCoordinateRef, rectangleAnchorRef) => {
|
|
53
|
+
if (!currentCoordinateRef.current)
|
|
54
|
+
return;
|
|
55
|
+
const points = getRectangleCorners(currentCoordinateRef.current);
|
|
56
|
+
const pointSize = ACTIVE_POINT_SIZE / zoom;
|
|
57
|
+
const { x: mouseX, y: mouseY } = mousePointRef;
|
|
58
|
+
const { x, y, width: w, height: h } = currentCoordinateRef.current;
|
|
59
|
+
if (mouseX >= points[0].x - pointSize / 2 &&
|
|
60
|
+
mouseX <= points[0].x + pointSize / 2 &&
|
|
61
|
+
mouseY >= points[0].y - pointSize / 2 &&
|
|
62
|
+
mouseY <= points[0].y + pointSize / 2) {
|
|
63
|
+
rectangleAnchorRef.current = RectangleAnchor.LEFT_TOP;
|
|
64
|
+
}
|
|
65
|
+
else if (mouseX >= points[1].x - pointSize / 2 &&
|
|
66
|
+
mouseX <= points[1].x + pointSize / 2 &&
|
|
67
|
+
mouseY >= points[1].y - pointSize / 2 &&
|
|
68
|
+
mouseY <= points[1].y + pointSize / 2) {
|
|
69
|
+
rectangleAnchorRef.current = RectangleAnchor.RIGHT_TOP;
|
|
70
|
+
}
|
|
71
|
+
else if (mouseX >= points[2].x - pointSize / 2 &&
|
|
72
|
+
mouseX <= points[2].x + pointSize / 2 &&
|
|
73
|
+
mouseY >= points[2].y - pointSize / 2 &&
|
|
74
|
+
mouseY <= points[2].y + pointSize / 2) {
|
|
75
|
+
rectangleAnchorRef.current = RectangleAnchor.LEFT_BOTTOM;
|
|
76
|
+
}
|
|
77
|
+
else if (mouseX >= points[3].x - pointSize / 2 &&
|
|
78
|
+
mouseX <= points[3].x + pointSize / 2 &&
|
|
79
|
+
mouseY >= points[3].y - pointSize / 2 &&
|
|
80
|
+
mouseY <= points[3].y + pointSize / 2) {
|
|
81
|
+
rectangleAnchorRef.current = RectangleAnchor.RIGHT_BOTTOM;
|
|
82
|
+
}
|
|
83
|
+
else if (mouseX >= x && mouseX <= x + w && mouseY >= y - pointSize / 2 && mouseY <= y + pointSize / 2) {
|
|
84
|
+
rectangleAnchorRef.current = RectangleAnchor.TOP;
|
|
85
|
+
}
|
|
86
|
+
else if (mouseX >= x && mouseX <= x + w && mouseY >= y + h - pointSize / 2 && mouseY <= y + h + pointSize / 2) {
|
|
87
|
+
rectangleAnchorRef.current = RectangleAnchor.BOTTOM;
|
|
88
|
+
}
|
|
89
|
+
else if (mouseY >= y && mouseY <= y + h && mouseX >= x - pointSize / 2 && mouseX <= x + pointSize / 2) {
|
|
90
|
+
rectangleAnchorRef.current = RectangleAnchor.LEFT;
|
|
91
|
+
}
|
|
92
|
+
else if (mouseY >= y && mouseY <= y + h && mouseX >= x + w - pointSize / 2 && mouseX <= x + w + pointSize / 2) {
|
|
93
|
+
rectangleAnchorRef.current = RectangleAnchor.RIGHT;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
rectangleAnchorRef.current = null;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
export const createCoordinate = (coordinate, setCoordinates, coordinatesRef, historyRef, currentCoordinateRef) => {
|
|
100
|
+
coordinatesRef.current.push(coordinate);
|
|
101
|
+
historyRef.current.add(cloneDeep(coordinatesRef.current));
|
|
102
|
+
setCoordinates(coordinatesRef.current);
|
|
103
|
+
currentCoordinateRef.current = null;
|
|
104
|
+
};
|
|
105
|
+
export const clampBoundingBoxToImage = (x, y, width, height, dw, dh) => {
|
|
106
|
+
let correctedX = x;
|
|
107
|
+
let correctedY = y;
|
|
108
|
+
let correctedWidth = width;
|
|
109
|
+
let correctedHeight = height;
|
|
110
|
+
if (x + width > dw)
|
|
111
|
+
correctedWidth = dw - x;
|
|
112
|
+
if (y + height > dh)
|
|
113
|
+
correctedHeight = dh - y;
|
|
114
|
+
if (x < 0) {
|
|
115
|
+
correctedWidth = width + x;
|
|
116
|
+
correctedX = 0;
|
|
117
|
+
}
|
|
118
|
+
if (y < 0) {
|
|
119
|
+
correctedHeight = height + y;
|
|
120
|
+
correctedY = 0;
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
x: correctedX,
|
|
124
|
+
y: correctedY,
|
|
125
|
+
width: Math.max(0, correctedWidth),
|
|
126
|
+
height: Math.max(0, correctedHeight),
|
|
127
|
+
selected: false,
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
export const getCanvasMousePoint = (event, canvas, canvasState) => {
|
|
131
|
+
const rect = canvas.getBoundingClientRect();
|
|
132
|
+
const { moveX, moveY, zoomX, zoomY, zoom, dx, dy } = canvasState;
|
|
133
|
+
const mouseX = event.clientX - rect.left;
|
|
134
|
+
const mouseY = event.clientY - rect.top;
|
|
135
|
+
return getMousePointTransform({ x: mouseX, y: mouseY, selected: false }, { x: moveX, y: moveY, selected: false }, { x: zoomX, y: zoomY, selected: false }, { x: dx, y: dy, selected: false }, zoom, canvas);
|
|
136
|
+
};
|
|
137
|
+
const moveBBox = ({ coordinatesRef, dw, dh, mousePoint, prevMousePoint, mouseAction, currentCoordinate, rectangleAnchor, }) => {
|
|
138
|
+
if (mouseAction === MouseAction.LEFT &&
|
|
139
|
+
currentCoordinate &&
|
|
140
|
+
rectangleAnchor === null &&
|
|
141
|
+
mousePoint &&
|
|
142
|
+
prevMousePoint) {
|
|
143
|
+
const findIndex = coordinatesRef.current.findIndex((coordinate) => coordinate.selected);
|
|
144
|
+
const findCoordinate = coordinatesRef.current[findIndex];
|
|
145
|
+
if (findIndex >= 0 && findCoordinate) {
|
|
146
|
+
const newX = currentCoordinate.x + mousePoint.x - prevMousePoint.x;
|
|
147
|
+
const newY = currentCoordinate.y + mousePoint.y - prevMousePoint.y;
|
|
148
|
+
findCoordinate.x = Math.max(0, Math.min(newX, dw - findCoordinate.width));
|
|
149
|
+
findCoordinate.y = Math.max(0, Math.min(newY, dh - findCoordinate.height));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const editBBox = ({ dw, dh, mousePoint, prevMousePoint, mouseAction, currentCoordinate, rectangleAnchor, coordinatesRef, }) => {
|
|
154
|
+
if (rectangleAnchor && mouseAction === MouseAction.LEFT && currentCoordinate && mousePoint && prevMousePoint) {
|
|
155
|
+
const findIndex = coordinatesRef.current.findIndex((coordinate) => coordinate.selected);
|
|
156
|
+
const findCoordinate = coordinatesRef.current[findIndex];
|
|
157
|
+
if (findIndex >= 0 && findCoordinate) {
|
|
158
|
+
const dx = mousePoint.x - prevMousePoint.x;
|
|
159
|
+
const dy = mousePoint.y - prevMousePoint.y;
|
|
160
|
+
switch (rectangleAnchor) {
|
|
161
|
+
case RectangleAnchor.LEFT_TOP:
|
|
162
|
+
findCoordinate.x += dx;
|
|
163
|
+
findCoordinate.y += dy;
|
|
164
|
+
findCoordinate.width -= dx;
|
|
165
|
+
findCoordinate.height -= dy;
|
|
166
|
+
break;
|
|
167
|
+
case RectangleAnchor.RIGHT_TOP:
|
|
168
|
+
findCoordinate.y += dy;
|
|
169
|
+
findCoordinate.width += dx;
|
|
170
|
+
findCoordinate.height -= dy;
|
|
171
|
+
break;
|
|
172
|
+
case RectangleAnchor.LEFT_BOTTOM:
|
|
173
|
+
findCoordinate.x += dx;
|
|
174
|
+
findCoordinate.width -= dx;
|
|
175
|
+
findCoordinate.height += dy;
|
|
176
|
+
break;
|
|
177
|
+
case RectangleAnchor.RIGHT_BOTTOM:
|
|
178
|
+
findCoordinate.width += dx;
|
|
179
|
+
findCoordinate.height += dy;
|
|
180
|
+
break;
|
|
181
|
+
case RectangleAnchor.TOP:
|
|
182
|
+
findCoordinate.y += dy;
|
|
183
|
+
findCoordinate.height -= dy;
|
|
184
|
+
break;
|
|
185
|
+
case RectangleAnchor.BOTTOM:
|
|
186
|
+
findCoordinate.height += dy;
|
|
187
|
+
break;
|
|
188
|
+
case RectangleAnchor.LEFT:
|
|
189
|
+
findCoordinate.x += dx;
|
|
190
|
+
findCoordinate.width -= dx;
|
|
191
|
+
break;
|
|
192
|
+
case RectangleAnchor.RIGHT:
|
|
193
|
+
findCoordinate.width += dx;
|
|
194
|
+
break;
|
|
195
|
+
default:
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
findCoordinate.x = Math.max(0, Math.min(findCoordinate.x, dw - findCoordinate.width));
|
|
199
|
+
findCoordinate.y = Math.max(0, Math.min(findCoordinate.y, dh - findCoordinate.height));
|
|
200
|
+
findCoordinate.width = Math.max(0, Math.min(findCoordinate.width, dw - findCoordinate.x));
|
|
201
|
+
findCoordinate.height = Math.max(0, Math.min(findCoordinate.height, dh - findCoordinate.y));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
export const handleBBoxOperations = (params) => {
|
|
206
|
+
moveBBox(params);
|
|
207
|
+
editBBox(params);
|
|
208
|
+
};
|
|
209
|
+
export const isInsideImage = (point, imageWidth, imageHeight) => {
|
|
210
|
+
return point.x >= 0 && point.y >= 0 && point.x <= imageWidth && point.y <= imageHeight;
|
|
211
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
import { Point, Coordinate, EditableCoordinate, CanvasState, Label } from '../../../../types';
|
|
3
|
+
import { CoordinateHistory } from '../../../AnnotationCanvas/_utils/createHistory';
|
|
4
|
+
import { DrawMode, RectangleAnchor } from '../../../../enum/common';
|
|
5
|
+
import { MouseAction } from '../../../../utils/mouseActions';
|
|
6
|
+
export type DrawEventsParams = {
|
|
7
|
+
canvasRef: RefObject<HTMLCanvasElement | null>;
|
|
8
|
+
canvasStateRef: RefObject<CanvasState>;
|
|
9
|
+
mousePointRef: RefObject<Point>;
|
|
10
|
+
setMousePoint: (point: Point) => void;
|
|
11
|
+
mouseActionRef: RefObject<MouseAction | null>;
|
|
12
|
+
startMousePointRef: RefObject<Point | null>;
|
|
13
|
+
prevMousePointRef: RefObject<Point | null>;
|
|
14
|
+
currentCoordinateRef: RefObject<Coordinate | null>;
|
|
15
|
+
coordinatesRef: RefObject<EditableCoordinate[]>;
|
|
16
|
+
setCoordinates: (coordinates: EditableCoordinate[]) => void;
|
|
17
|
+
rectangleAnchorRef: RefObject<RectangleAnchor | null>;
|
|
18
|
+
historyRef: RefObject<CoordinateHistory>;
|
|
19
|
+
maskImageRef: RefObject<HTMLImageElement | null>;
|
|
20
|
+
drawLabel?: Label;
|
|
21
|
+
};
|
|
22
|
+
export declare const useDrawEvents: (params: DrawEventsParams & {
|
|
23
|
+
selectedDrawMode?: DrawMode;
|
|
24
|
+
editable: boolean;
|
|
25
|
+
}) => void;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import * as Rectangle from './rectangle';
|
|
3
|
+
import { DrawMode } from '../../../../enum/common';
|
|
4
|
+
export const useDrawEvents = (params) => {
|
|
5
|
+
const { canvasRef, setMousePoint, startMousePointRef, currentCoordinateRef, coordinatesRef, setCoordinates, maskImageRef, selectedDrawMode, editable, } = params;
|
|
6
|
+
const initSelector = (coordinates) => {
|
|
7
|
+
coordinates.map((coordinate) => {
|
|
8
|
+
coordinate.selected = false;
|
|
9
|
+
return coordinate;
|
|
10
|
+
});
|
|
11
|
+
return coordinates;
|
|
12
|
+
};
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (!canvasRef.current || !editable || !selectedDrawMode)
|
|
15
|
+
return;
|
|
16
|
+
const handlers = selectedDrawMode === DrawMode.RECTANGLE
|
|
17
|
+
? {
|
|
18
|
+
down: Rectangle.handleMouseDownRectangle(params),
|
|
19
|
+
move: Rectangle.handleMouseMoveRectangle(params),
|
|
20
|
+
up: Rectangle.handleMouseUpRectangle(params),
|
|
21
|
+
leave: Rectangle.handleMouseLeaveRectangle(params),
|
|
22
|
+
}
|
|
23
|
+
: null;
|
|
24
|
+
if (!handlers)
|
|
25
|
+
return;
|
|
26
|
+
canvasRef.current.addEventListener('mousedown', handlers.down);
|
|
27
|
+
canvasRef.current.addEventListener('mousemove', handlers.move);
|
|
28
|
+
canvasRef.current.addEventListener('mouseup', handlers.up);
|
|
29
|
+
canvasRef.current.addEventListener('mouseleave', handlers.leave);
|
|
30
|
+
setMousePoint({ x: 0, y: 0, selected: false });
|
|
31
|
+
setCoordinates(initSelector(coordinatesRef.current));
|
|
32
|
+
currentCoordinateRef.current = null;
|
|
33
|
+
startMousePointRef.current = null;
|
|
34
|
+
maskImageRef.current = new Image();
|
|
35
|
+
return () => {
|
|
36
|
+
canvasRef.current?.removeEventListener('mousedown', handlers.down);
|
|
37
|
+
canvasRef.current?.removeEventListener('mousemove', handlers.move);
|
|
38
|
+
canvasRef.current?.removeEventListener('mouseup', handlers.up);
|
|
39
|
+
canvasRef.current?.removeEventListener('mouseleave', handlers.leave);
|
|
40
|
+
};
|
|
41
|
+
}, [canvasRef, selectedDrawMode]);
|
|
42
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
import { EditableCoordinate, Point } from '../../../types';
|
|
3
|
+
type UseCanvasDrawParams = {
|
|
4
|
+
drawLineSize: number;
|
|
5
|
+
drawColor?: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function useCanvasDraw({ drawLineSize, drawColor }: UseCanvasDrawParams): {
|
|
8
|
+
drawCross: (context: CanvasRenderingContext2D, dw: number, dh: number, zoom: number, point: Point) => void;
|
|
9
|
+
drawActiveRect: (context: CanvasRenderingContext2D, coordinate: EditableCoordinate, zoom: number) => void;
|
|
10
|
+
drawRectFromPoints: (context: CanvasRenderingContext2D, startPoint: Point, endPoint: Point, zoom: number, color?: string | undefined) => void;
|
|
11
|
+
setCursorStyle: (canvas: HTMLCanvasElement, mousePointRef: RefObject<Point>, coordinate: EditableCoordinate, zoom: number) => void;
|
|
12
|
+
};
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { ACTIVE_POINT_SIZE, getRectangleCorners } from './drawEvents/rectangleUtils';
|
|
2
|
+
export function useCanvasDraw({ drawLineSize, drawColor }) {
|
|
3
|
+
const drawCross = (context, dw, dh, zoom, point) => {
|
|
4
|
+
if (point.x > 0 && point.y > 0 && point.x < dw && point.y < dh) {
|
|
5
|
+
if (context)
|
|
6
|
+
context.lineWidth = (drawLineSize / 2) * (1 / zoom);
|
|
7
|
+
context?.setLineDash([5]);
|
|
8
|
+
context?.beginPath();
|
|
9
|
+
context?.moveTo(point.x, 0);
|
|
10
|
+
context?.lineTo(point.x, dh);
|
|
11
|
+
if (context)
|
|
12
|
+
context.strokeStyle = '#001f3f';
|
|
13
|
+
context?.stroke();
|
|
14
|
+
context?.beginPath();
|
|
15
|
+
context?.moveTo(0, point.y);
|
|
16
|
+
context?.lineTo(dw, point.y);
|
|
17
|
+
if (context)
|
|
18
|
+
context.strokeStyle = '#001f3f';
|
|
19
|
+
context?.stroke();
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const drawActiveRect = (context, coordinate, zoom) => {
|
|
23
|
+
const { x, y, width, height, selected } = coordinate;
|
|
24
|
+
if (!(selected && x && y && width && height))
|
|
25
|
+
return;
|
|
26
|
+
context.lineWidth = drawLineSize / zoom;
|
|
27
|
+
context.fillStyle = '#FFF';
|
|
28
|
+
const pointSize = ACTIVE_POINT_SIZE / zoom;
|
|
29
|
+
const points = getRectangleCorners({ x, y, width, height });
|
|
30
|
+
points.forEach((point) => {
|
|
31
|
+
context.fillStyle = '#FFF';
|
|
32
|
+
context.fillRect(point.x - pointSize / 2, point.y - pointSize / 2, pointSize, pointSize);
|
|
33
|
+
context.strokeStyle = '#000';
|
|
34
|
+
context.lineWidth = 1 / zoom;
|
|
35
|
+
context.strokeRect(point.x - pointSize / 2, point.y - pointSize / 2, pointSize, pointSize);
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
const drawRectFromPoints = (context, startPoint, endPoint, zoom, color = drawColor) => {
|
|
39
|
+
if (!color)
|
|
40
|
+
return;
|
|
41
|
+
const x = Math.min(endPoint.x, startPoint.x);
|
|
42
|
+
const y = Math.min(endPoint.y, startPoint.y);
|
|
43
|
+
const width = Math.abs(endPoint.x - startPoint.x);
|
|
44
|
+
const height = Math.abs(endPoint.y - startPoint.y);
|
|
45
|
+
context.beginPath();
|
|
46
|
+
context.lineWidth = drawLineSize / zoom;
|
|
47
|
+
context.rect(x, y, width, height);
|
|
48
|
+
context.strokeStyle = color;
|
|
49
|
+
context.stroke();
|
|
50
|
+
};
|
|
51
|
+
const setCursorStyle = (canvas, mousePointRef, coordinate, zoom) => {
|
|
52
|
+
if (coordinate.selected) {
|
|
53
|
+
let cursorStyle = 'default';
|
|
54
|
+
const pointSize = ACTIVE_POINT_SIZE / zoom;
|
|
55
|
+
const mouseX = mousePointRef.current.x;
|
|
56
|
+
const mouseY = mousePointRef.current.y;
|
|
57
|
+
const { x, y, width: w, height: h } = coordinate;
|
|
58
|
+
if (x && y && w && h) {
|
|
59
|
+
const points = getRectangleCorners({ x, y, width: w, height: h });
|
|
60
|
+
if (mouseX >= points[0].x - pointSize / 2 &&
|
|
61
|
+
mouseX <= points[0].x + pointSize / 2 &&
|
|
62
|
+
mouseY >= points[0].y - pointSize / 2 &&
|
|
63
|
+
mouseY <= points[0].y + pointSize / 2) {
|
|
64
|
+
cursorStyle = 'nwse-resize';
|
|
65
|
+
}
|
|
66
|
+
else if (mouseX >= points[1].x - pointSize / 2 &&
|
|
67
|
+
mouseX <= points[1].x + pointSize / 2 &&
|
|
68
|
+
mouseY >= points[1].y - pointSize / 2 &&
|
|
69
|
+
mouseY <= points[1].y + pointSize / 2) {
|
|
70
|
+
cursorStyle = 'nesw-resize';
|
|
71
|
+
}
|
|
72
|
+
else if (mouseX >= points[2].x - pointSize / 2 &&
|
|
73
|
+
mouseX <= points[2].x + pointSize / 2 &&
|
|
74
|
+
mouseY >= points[2].y - pointSize / 2 &&
|
|
75
|
+
mouseY <= points[2].y + pointSize / 2) {
|
|
76
|
+
cursorStyle = 'nesw-resize';
|
|
77
|
+
}
|
|
78
|
+
else if (mouseX >= points[3].x - pointSize / 2 &&
|
|
79
|
+
mouseX <= points[3].x + pointSize / 2 &&
|
|
80
|
+
mouseY >= points[3].y - pointSize / 2 &&
|
|
81
|
+
mouseY <= points[3].y + pointSize / 2) {
|
|
82
|
+
cursorStyle = 'nwse-resize';
|
|
83
|
+
}
|
|
84
|
+
else if (mouseX >= x && mouseX <= x + w && mouseY >= y - pointSize / 2 && mouseY <= y + pointSize / 2) {
|
|
85
|
+
cursorStyle = 'ns-resize';
|
|
86
|
+
}
|
|
87
|
+
else if (mouseX >= x &&
|
|
88
|
+
mouseX <= x + w &&
|
|
89
|
+
mouseY >= y + h - pointSize / 2 &&
|
|
90
|
+
mouseY <= y + h + pointSize / 2) {
|
|
91
|
+
cursorStyle = 'ns-resize';
|
|
92
|
+
}
|
|
93
|
+
else if (mouseY >= y && mouseY <= y + h && mouseX >= x - pointSize / 2 && mouseX <= x + pointSize / 2) {
|
|
94
|
+
cursorStyle = 'ew-resize';
|
|
95
|
+
}
|
|
96
|
+
else if (mouseY >= y &&
|
|
97
|
+
mouseY <= y + h &&
|
|
98
|
+
mouseX >= x + w - pointSize / 2 &&
|
|
99
|
+
mouseX <= x + w + pointSize / 2) {
|
|
100
|
+
cursorStyle = 'ew-resize';
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
cursorStyle = 'default';
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
canvas.style.cursor = cursorStyle;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
drawCross,
|
|
111
|
+
drawActiveRect,
|
|
112
|
+
drawRectFromPoints,
|
|
113
|
+
setCursorStyle,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
export function useHotkeys({ onDelete, toggleSelectionOnly, onUndoRedo }) {
|
|
3
|
+
useEffect(() => {
|
|
4
|
+
const handleKeyDown = (event) => {
|
|
5
|
+
if (event.key === 'Delete')
|
|
6
|
+
onDelete();
|
|
7
|
+
if (event.code === 'KeyX' && !(event.metaKey || event.ctrlKey) && !event.shiftKey)
|
|
8
|
+
toggleSelectionOnly();
|
|
9
|
+
if (event.code === 'KeyZ' && (event.metaKey || event.ctrlKey))
|
|
10
|
+
onUndoRedo(event.shiftKey);
|
|
11
|
+
};
|
|
12
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
13
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
14
|
+
}, [onDelete, toggleSelectionOnly, onUndoRedo]);
|
|
15
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { MouseEvent, RefObject } from 'react';
|
|
2
|
+
import { EditableCoordinate, Point } from '../../../types';
|
|
3
|
+
import { MouseAction } from '../../../utils/mouseActions';
|
|
4
|
+
import { CoordinateHistory } from '../../AnnotationCanvas/_utils/createHistory';
|
|
5
|
+
import { DrawMode } from '../../../enum/common';
|
|
6
|
+
export declare const INIT_POINT: Point;
|
|
7
|
+
type UseMouseParams = {
|
|
8
|
+
canvasRef: RefObject<HTMLCanvasElement | null>;
|
|
9
|
+
mouseActionRef: RefObject<MouseAction | null>;
|
|
10
|
+
setMousePoint: (point: Point) => void;
|
|
11
|
+
coordinatesRef?: RefObject<EditableCoordinate[]>;
|
|
12
|
+
setCoordinates?: (coordinates: EditableCoordinate[]) => void;
|
|
13
|
+
historyRef: RefObject<CoordinateHistory>;
|
|
14
|
+
selectedDrawMode?: DrawMode;
|
|
15
|
+
};
|
|
16
|
+
export declare function useMouse({ canvasRef, mouseActionRef, setMousePoint, coordinatesRef, setCoordinates, historyRef, selectedDrawMode, }: UseMouseParams): {
|
|
17
|
+
handleMouseDown: (event: MouseEvent) => void;
|
|
18
|
+
handleMouseUp: (event: MouseEvent) => void;
|
|
19
|
+
handleMouseLeave: (_event: MouseEvent) => void;
|
|
20
|
+
};
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { isMouseClickAction, mapButtonToMouseAction, MouseAction } from '../../../utils/mouseActions';
|
|
3
|
+
import { DrawMode } from '../../../enum/common';
|
|
4
|
+
export const INIT_POINT = { x: 0, y: 0, selected: false };
|
|
5
|
+
export function useMouse({ canvasRef, mouseActionRef, setMousePoint, coordinatesRef, setCoordinates, historyRef, selectedDrawMode, }) {
|
|
6
|
+
const handleMouseDown = useCallback((event) => {
|
|
7
|
+
if (!canvasRef.current || !selectedDrawMode)
|
|
8
|
+
return;
|
|
9
|
+
const action = mapButtonToMouseAction(event.button);
|
|
10
|
+
if (action)
|
|
11
|
+
mouseActionRef.current = action;
|
|
12
|
+
}, [canvasRef, mouseActionRef]);
|
|
13
|
+
const handleMouseUp = useCallback((event) => {
|
|
14
|
+
if (canvasRef.current && isMouseClickAction(event.button, MouseAction.LEFT) && selectedDrawMode) {
|
|
15
|
+
if (selectedDrawMode === DrawMode.RECTANGLE && coordinatesRef?.current && setCoordinates) {
|
|
16
|
+
setCoordinates(coordinatesRef.current);
|
|
17
|
+
historyRef.current.add(JSON.parse(JSON.stringify(coordinatesRef.current)));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
mouseActionRef.current = null;
|
|
21
|
+
event.preventDefault();
|
|
22
|
+
}, [canvasRef, coordinatesRef, setCoordinates, historyRef, selectedDrawMode, mouseActionRef]);
|
|
23
|
+
const handleMouseLeave = useCallback((_event) => {
|
|
24
|
+
if (!canvasRef.current || !selectedDrawMode)
|
|
25
|
+
return;
|
|
26
|
+
mouseActionRef.current = null;
|
|
27
|
+
setMousePoint(INIT_POINT);
|
|
28
|
+
}, [canvasRef, setMousePoint, mouseActionRef]);
|
|
29
|
+
return {
|
|
30
|
+
handleMouseDown,
|
|
31
|
+
handleMouseUp,
|
|
32
|
+
handleMouseLeave,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Dispatch, RefObject, SetStateAction } from 'react';
|
|
2
|
+
import { CanvasState, EditableCoordinate, AnnotationCanvasDrawing } from '../../types';
|
|
3
|
+
import { CoordinateHistory } from '../AnnotationCanvas/_utils/createHistory';
|
|
2
4
|
type Props = {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
dy: number;
|
|
10
|
-
dw: number;
|
|
11
|
-
dh: number;
|
|
12
|
-
coordinates?: Coordinate[];
|
|
13
|
-
editable?: boolean;
|
|
5
|
+
canvasState: CanvasState;
|
|
6
|
+
coordinates: EditableCoordinate[] | undefined;
|
|
7
|
+
setCoordinates: Dispatch<SetStateAction<EditableCoordinate[] | undefined>>;
|
|
8
|
+
historyRef: RefObject<CoordinateHistory>;
|
|
9
|
+
drawing: AnnotationCanvasDrawing;
|
|
10
|
+
editable: boolean;
|
|
14
11
|
};
|
|
15
|
-
declare const AnnotationLayer: ({
|
|
12
|
+
declare const AnnotationLayer: ({ canvasState, coordinates, setCoordinates: propSetCoordinates, historyRef, drawing, editable, }: Props) => import("react/jsx-runtime").JSX.Element;
|
|
16
13
|
export default AnnotationLayer;
|