@deepnoid/canvas 0.1.18 → 0.1.20
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.
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useRef } from 'react';
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef, useState } from 'react';
|
|
4
4
|
import Canvas from './Canvas';
|
|
5
5
|
import { useImageLoader } from '../hooks/useImageLoader';
|
|
6
6
|
import { usePanZoom } from '../hooks/usePanZoom';
|
|
7
7
|
const AnnotatedCanvas = ({ image, coordinates, panZoomable = false, ZoomButton, resetOnImageChange = true, editable = false, onImageLoadError, onImageLoadSuccess, }) => {
|
|
8
8
|
const canvasRef = useRef(null);
|
|
9
|
+
const [displayCoordinates, setDisplayCoordinates] = useState();
|
|
9
10
|
const { image: loadedImage, isLoading, error } = useImageLoader(image, 10000, onImageLoadSuccess, onImageLoadError);
|
|
10
11
|
const { moveX, moveY, zoom, zoomX, zoomY, dx, dy, dw, dh, handleWheel, handleMouseDown, handleMouseMove, handleMouseUp, handleMouseLeave, resetView, } = usePanZoom({
|
|
11
12
|
canvasRef,
|
|
@@ -13,13 +14,23 @@ const AnnotatedCanvas = ({ image, coordinates, panZoomable = false, ZoomButton,
|
|
|
13
14
|
panZoomable,
|
|
14
15
|
resetOnImageChange,
|
|
15
16
|
});
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (loadedImage && !isLoading) {
|
|
19
|
+
setDisplayCoordinates(coordinates);
|
|
20
|
+
}
|
|
21
|
+
}, [loadedImage, isLoading, coordinates]);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (isLoading) {
|
|
24
|
+
setDisplayCoordinates(undefined);
|
|
25
|
+
}
|
|
26
|
+
}, [isLoading]);
|
|
16
27
|
if (!image || image.trim() === '' || error) {
|
|
17
28
|
return null;
|
|
18
29
|
}
|
|
19
|
-
return (_jsx("div", { style: { width: '100%', height: '100%', display: 'flex', flex: 1 }, children:
|
|
30
|
+
return (_jsx("div", { style: { width: '100%', height: '100%', display: 'flex', flex: 1 }, children: _jsx("div", { onWheel: handleWheel, onMouseMove: handleMouseMove, onMouseDown: handleMouseDown, onMouseUp: handleMouseUp, onMouseLeave: handleMouseLeave, style: {
|
|
20
31
|
flex: 1,
|
|
21
32
|
position: 'relative',
|
|
22
33
|
cursor: panZoomable ? 'grab' : 'default',
|
|
23
|
-
}, children: [_jsx("canvas", { ref: canvasRef, style: { position: 'absolute', width: '100%', height: '100%', left: 0, top: 0 } }),
|
|
34
|
+
}, children: loadedImage && !isLoading && (_jsxs(_Fragment, { children: [_jsx("canvas", { ref: canvasRef, style: { position: 'absolute', width: '100%', height: '100%', left: 0, top: 0 } }), _jsx(Canvas, { moveX: moveX, moveY: moveY, zoomX: zoomX, zoomY: zoomY, zoom: zoom, dx: dx, dy: dy, dw: dw, dh: dh, coordinates: displayCoordinates, editable: editable }), ZoomButton && _jsx(ZoomButton, { onClick: resetView, children: `${Math.round(zoom * 100)}%` })] })) }) }));
|
|
24
35
|
};
|
|
25
36
|
export default AnnotatedCanvas;
|
package/dist/hooks/usePanZoom.js
CHANGED
|
@@ -14,10 +14,38 @@ export const usePanZoom = ({ canvasRef, image, panZoomable = false, resetOnImage
|
|
|
14
14
|
const [dh, setDh] = useState(0);
|
|
15
15
|
const [status, setStatus] = useState();
|
|
16
16
|
const [startMousePoint, setStartMousePoint] = useState();
|
|
17
|
+
const animationFrameRef = useRef(null);
|
|
18
|
+
const currentTransformRef = useRef({
|
|
19
|
+
moveX: 0,
|
|
20
|
+
moveY: 0,
|
|
21
|
+
zoom: 1,
|
|
22
|
+
zoomX: 0,
|
|
23
|
+
zoomY: 0,
|
|
24
|
+
dx: 0,
|
|
25
|
+
dy: 0,
|
|
26
|
+
});
|
|
17
27
|
const ZOOM_UNIT = 0.9;
|
|
18
28
|
const MAX_ZOOM = 4;
|
|
19
29
|
const MIN_ZOOM = 0.5;
|
|
20
30
|
const prevImageRef = useRef(null);
|
|
31
|
+
const immediateRender = useCallback((moveX, moveY, zoomX, zoomY, zoom, dx, dy) => {
|
|
32
|
+
const canvasEl = canvasRef.current;
|
|
33
|
+
if (!canvasEl || !image)
|
|
34
|
+
return;
|
|
35
|
+
const resolvedCanvas = resolutionCanvas(canvasEl);
|
|
36
|
+
if (!resolvedCanvas)
|
|
37
|
+
return;
|
|
38
|
+
drawCanvas(moveX, moveY, zoomX, zoomY, zoom, dx, dy, resolvedCanvas, image, true);
|
|
39
|
+
}, [canvasRef, image]);
|
|
40
|
+
const scheduleRender = useCallback(() => {
|
|
41
|
+
if (animationFrameRef.current) {
|
|
42
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
43
|
+
}
|
|
44
|
+
animationFrameRef.current = requestAnimationFrame(() => {
|
|
45
|
+
const current = currentTransformRef.current;
|
|
46
|
+
immediateRender(current.moveX, current.moveY, current.zoomX, current.zoomY, current.zoom, current.dx, current.dy);
|
|
47
|
+
});
|
|
48
|
+
}, [immediateRender]);
|
|
21
49
|
const initCanvas = useCallback((img) => {
|
|
22
50
|
const canvasEl = canvasRef.current;
|
|
23
51
|
if (!canvasEl)
|
|
@@ -41,6 +69,15 @@ export const usePanZoom = ({ canvasRef, image, panZoomable = false, resetOnImage
|
|
|
41
69
|
setZoomY(0);
|
|
42
70
|
setMoveX(0);
|
|
43
71
|
setMoveY(0);
|
|
72
|
+
currentTransformRef.current = {
|
|
73
|
+
moveX: 0,
|
|
74
|
+
moveY: 0,
|
|
75
|
+
zoom: scale,
|
|
76
|
+
zoomX: 0,
|
|
77
|
+
zoomY: 0,
|
|
78
|
+
dx,
|
|
79
|
+
dy,
|
|
80
|
+
};
|
|
44
81
|
}, [canvasRef]);
|
|
45
82
|
useEffect(() => {
|
|
46
83
|
const canvasEl = canvasRef.current;
|
|
@@ -63,25 +100,36 @@ export const usePanZoom = ({ canvasRef, image, panZoomable = false, resetOnImage
|
|
|
63
100
|
setDh(dh);
|
|
64
101
|
setDx(dx);
|
|
65
102
|
setDy(dy);
|
|
103
|
+
currentTransformRef.current.dx = dx;
|
|
104
|
+
currentTransformRef.current.dy = dy;
|
|
66
105
|
}
|
|
67
106
|
prevImageRef.current = image;
|
|
68
107
|
}, [image, resetOnImageChange, initCanvas, canvasRef]);
|
|
69
108
|
const handleWheel = useCallback((e) => {
|
|
70
|
-
|
|
109
|
+
const canvasEl = canvasRef.current;
|
|
110
|
+
if (!panZoomable || !canvasRef.current || !canvasEl || !image)
|
|
71
111
|
return;
|
|
72
|
-
|
|
112
|
+
const currentZoom = currentTransformRef.current.zoom;
|
|
113
|
+
let nextZoom = e.deltaY < 0 ? currentZoom / ZOOM_UNIT : currentZoom * ZOOM_UNIT;
|
|
73
114
|
nextZoom = Math.min(Math.max(nextZoom, MIN_ZOOM), MAX_ZOOM);
|
|
74
|
-
if ((
|
|
115
|
+
if ((currentZoom <= MIN_ZOOM && e.deltaY > 0) || (currentZoom >= MAX_ZOOM && e.deltaY < 0))
|
|
75
116
|
return;
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
117
|
+
const resolvedCanvas = resolutionCanvas(canvasEl);
|
|
118
|
+
if (!resolvedCanvas)
|
|
119
|
+
return;
|
|
120
|
+
const resolution_ratio_x = resolvedCanvas.width / canvasEl.clientWidth;
|
|
121
|
+
const resolution_ratio_y = resolvedCanvas.height / canvasEl.clientHeight;
|
|
122
|
+
const current = currentTransformRef.current;
|
|
123
|
+
const centerX = resolvedCanvas.width / 2 - current.dx * resolution_ratio_x - current.moveX * resolution_ratio_x;
|
|
124
|
+
const centerY = resolvedCanvas.height / 2 - current.dy * resolution_ratio_y - current.moveY * resolution_ratio_y;
|
|
125
|
+
currentTransformRef.current.zoom = nextZoom;
|
|
126
|
+
currentTransformRef.current.zoomX = centerX;
|
|
127
|
+
currentTransformRef.current.zoomY = centerY;
|
|
128
|
+
immediateRender(current.moveX, current.moveY, centerX, centerY, nextZoom, current.dx, current.dy);
|
|
129
|
+
setZoomX(centerX);
|
|
130
|
+
setZoomY(centerY);
|
|
83
131
|
setZoom(nextZoom);
|
|
84
|
-
}, [canvasRef, panZoomable,
|
|
132
|
+
}, [canvasRef, panZoomable, image, immediateRender]);
|
|
85
133
|
const handleMouseDown = useCallback((e) => {
|
|
86
134
|
if (!panZoomable || !canvasRef.current)
|
|
87
135
|
return;
|
|
@@ -89,25 +137,32 @@ export const usePanZoom = ({ canvasRef, image, panZoomable = false, resetOnImage
|
|
|
89
137
|
const rect = canvasRef.current.getBoundingClientRect();
|
|
90
138
|
const mouseX = e.clientX - rect.left;
|
|
91
139
|
const mouseY = e.clientY - rect.top;
|
|
92
|
-
const
|
|
140
|
+
const current = currentTransformRef.current;
|
|
141
|
+
const transformed = getMousePointTransform({ x: mouseX, y: mouseY }, { x: current.moveX, y: current.moveY }, { x: current.zoomX, y: current.zoomY }, { x: current.dx, y: current.dy }, current.zoom, canvasRef.current);
|
|
93
142
|
setStartMousePoint({ x: transformed.x, y: transformed.y });
|
|
94
143
|
e.preventDefault();
|
|
95
|
-
}, [canvasRef, panZoomable
|
|
144
|
+
}, [canvasRef, panZoomable]);
|
|
96
145
|
const handleMouseMove = useCallback((e) => {
|
|
97
146
|
if (!panZoomable || status !== MouseStatus.MOVE || !canvasRef.current)
|
|
98
147
|
return;
|
|
99
148
|
const rect = canvasRef.current.getBoundingClientRect();
|
|
100
149
|
const mouseX = e.clientX - rect.left;
|
|
101
150
|
const mouseY = e.clientY - rect.top;
|
|
102
|
-
const
|
|
151
|
+
const current = currentTransformRef.current;
|
|
152
|
+
const transformed = getMousePointTransform({ x: mouseX, y: mouseY }, { x: current.moveX, y: current.moveY }, { x: current.zoomX, y: current.zoomY }, { x: current.dx, y: current.dy }, current.zoom, canvasRef.current);
|
|
103
153
|
if (startMousePoint) {
|
|
104
154
|
const deltaX = transformed.x - startMousePoint.x;
|
|
105
155
|
const deltaY = transformed.y - startMousePoint.y;
|
|
106
|
-
|
|
107
|
-
|
|
156
|
+
const newMoveX = current.moveX + deltaX;
|
|
157
|
+
const newMoveY = current.moveY + deltaY;
|
|
158
|
+
currentTransformRef.current.moveX = newMoveX;
|
|
159
|
+
currentTransformRef.current.moveY = newMoveY;
|
|
160
|
+
immediateRender(newMoveX, newMoveY, current.zoomX, current.zoomY, current.zoom, current.dx, current.dy);
|
|
161
|
+
setMoveX(newMoveX);
|
|
162
|
+
setMoveY(newMoveY);
|
|
108
163
|
}
|
|
109
164
|
e.preventDefault();
|
|
110
|
-
}, [canvasRef, panZoomable, status,
|
|
165
|
+
}, [canvasRef, panZoomable, status, startMousePoint, immediateRender]);
|
|
111
166
|
const handleMouseUp = useCallback(() => {
|
|
112
167
|
if (!panZoomable)
|
|
113
168
|
return;
|
|
@@ -121,12 +176,32 @@ export const usePanZoom = ({ canvasRef, image, panZoomable = false, resetOnImage
|
|
|
121
176
|
useEffect(() => {
|
|
122
177
|
if (!canvasRef.current || !image)
|
|
123
178
|
return;
|
|
124
|
-
|
|
125
|
-
|
|
179
|
+
if (dx !== 0 || dy !== 0 || zoom !== 1) {
|
|
180
|
+
scheduleRender();
|
|
181
|
+
}
|
|
182
|
+
}, [canvasRef, image, moveX, moveY, zoomX, zoomY, zoom, dx, dy, scheduleRender]);
|
|
183
|
+
useEffect(() => {
|
|
184
|
+
currentTransformRef.current = {
|
|
185
|
+
moveX,
|
|
186
|
+
moveY,
|
|
187
|
+
zoom,
|
|
188
|
+
zoomX,
|
|
189
|
+
zoomY,
|
|
190
|
+
dx,
|
|
191
|
+
dy,
|
|
192
|
+
};
|
|
193
|
+
}, [moveX, moveY, zoom, zoomX, zoomY, dx, dy]);
|
|
126
194
|
const resetView = useCallback(() => {
|
|
127
195
|
if (image)
|
|
128
196
|
initCanvas(image);
|
|
129
197
|
}, [image, initCanvas]);
|
|
198
|
+
useEffect(() => {
|
|
199
|
+
return () => {
|
|
200
|
+
if (animationFrameRef.current) {
|
|
201
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
}, []);
|
|
130
205
|
return {
|
|
131
206
|
moveX,
|
|
132
207
|
moveY,
|