@deepnoid/canvas 0.1.72 → 0.1.73
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/react/AnnotationCanvas.js +40 -38
- package/package.json +1 -1
|
@@ -5,6 +5,13 @@ import { AnnotationEngine } from '../engine/public/annotationEngine';
|
|
|
5
5
|
import useResizeObserver from './hooks/useResizeObserver';
|
|
6
6
|
import { useDebounce } from './hooks/useDebounce';
|
|
7
7
|
import { useHotkeys } from './hooks/useHotkeys';
|
|
8
|
+
function safeRemove(parent, child) {
|
|
9
|
+
if (!child)
|
|
10
|
+
return;
|
|
11
|
+
if (child.parentNode === parent) {
|
|
12
|
+
parent.removeChild(child);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
8
15
|
const AnnotationCanvas = ({ image, annotations = [], setAnnotations, options, drawing, events, enableHotkeys = true, editable = true, }) => {
|
|
9
16
|
const { panZoomEnabled, zoom, ZoomButton, resetOnImageChange = false } = options || {};
|
|
10
17
|
const { onImageLoadSuccess, onImageLoadError } = events || {};
|
|
@@ -12,7 +19,6 @@ const AnnotationCanvas = ({ image, annotations = [], setAnnotations, options, dr
|
|
|
12
19
|
const engineRef = useRef(null);
|
|
13
20
|
const pendingAnnotationsRef = useRef(null);
|
|
14
21
|
const imageLoadingRef = useRef(false);
|
|
15
|
-
const currentImageRef = useRef(image);
|
|
16
22
|
const engineIdRef = useRef(0);
|
|
17
23
|
const [, forceRender] = useState(0);
|
|
18
24
|
/* ---------- Resize Observer ---------- */
|
|
@@ -31,43 +37,27 @@ const AnnotationCanvas = ({ image, annotations = [], setAnnotations, options, dr
|
|
|
31
37
|
});
|
|
32
38
|
/* ---------- Image / Engine lifecycle ---------- */
|
|
33
39
|
useEffect(() => {
|
|
34
|
-
|
|
40
|
+
const container = containerRef.current;
|
|
41
|
+
if (!container)
|
|
35
42
|
return;
|
|
36
|
-
const prevEngine = engineRef.current;
|
|
37
43
|
imageLoadingRef.current = true;
|
|
44
|
+
const newEngineId = ++engineIdRef.current;
|
|
45
|
+
const prevEngine = engineRef.current;
|
|
38
46
|
if (!image?.trim()) {
|
|
39
47
|
if (prevEngine) {
|
|
48
|
+
safeRemove(container, prevEngine.getImageCanvas());
|
|
49
|
+
safeRemove(container, prevEngine.getAnnotationsCanvas());
|
|
40
50
|
prevEngine.destroy();
|
|
41
|
-
containerRef.current.removeChild(prevEngine.getImageCanvas());
|
|
42
|
-
containerRef.current.removeChild(prevEngine.getAnnotationsCanvas());
|
|
43
51
|
}
|
|
44
52
|
engineRef.current = null;
|
|
45
53
|
pendingAnnotationsRef.current = null;
|
|
46
54
|
imageLoadingRef.current = false;
|
|
47
|
-
currentImageRef.current = '';
|
|
48
55
|
return;
|
|
49
56
|
}
|
|
50
|
-
const newEngineId = engineIdRef.current + 1;
|
|
51
|
-
engineIdRef.current = newEngineId;
|
|
52
|
-
let cancelled = false;
|
|
53
57
|
const img = new Image();
|
|
54
58
|
img.onload = () => {
|
|
55
|
-
if (
|
|
59
|
+
if (engineIdRef.current !== newEngineId || !container)
|
|
56
60
|
return;
|
|
57
|
-
const imageCanvas = document.createElement('canvas');
|
|
58
|
-
const annotationsCanvas = document.createElement('canvas');
|
|
59
|
-
imageCanvas.style.position = 'absolute';
|
|
60
|
-
imageCanvas.style.width = '100%';
|
|
61
|
-
imageCanvas.style.height = '100%';
|
|
62
|
-
annotationsCanvas.style.position = 'absolute';
|
|
63
|
-
annotationsCanvas.style.width = '100%';
|
|
64
|
-
annotationsCanvas.style.height = '100%';
|
|
65
|
-
const containerRect = containerRef.current.getBoundingClientRect();
|
|
66
|
-
const dpr = window.devicePixelRatio || 1;
|
|
67
|
-
imageCanvas.width = containerRect.width * dpr;
|
|
68
|
-
imageCanvas.height = containerRect.height * dpr;
|
|
69
|
-
annotationsCanvas.width = containerRect.width * dpr;
|
|
70
|
-
annotationsCanvas.height = containerRect.height * dpr;
|
|
71
61
|
const prevImageCanvasState = prevEngine?.getImageCanvasState();
|
|
72
62
|
const imageCanvasState = !resetOnImageChange && prevImageCanvasState
|
|
73
63
|
? prevImageCanvasState
|
|
@@ -83,15 +73,24 @@ const AnnotationCanvas = ({ image, annotations = [], setAnnotations, options, dr
|
|
|
83
73
|
dh: img.height,
|
|
84
74
|
initZoom: 1,
|
|
85
75
|
};
|
|
76
|
+
const imageCanvas = document.createElement('canvas');
|
|
77
|
+
const annotationsCanvas = document.createElement('canvas');
|
|
78
|
+
imageCanvas.style.position = 'absolute';
|
|
79
|
+
imageCanvas.style.width = '100%';
|
|
80
|
+
imageCanvas.style.height = '100%';
|
|
81
|
+
annotationsCanvas.style.position = 'absolute';
|
|
82
|
+
annotationsCanvas.style.width = '100%';
|
|
83
|
+
annotationsCanvas.style.height = '100%';
|
|
86
84
|
const engine = new AnnotationEngine({
|
|
87
85
|
imageCanvas,
|
|
88
86
|
annotationsCanvas,
|
|
89
87
|
image: img,
|
|
90
88
|
imageCanvasState,
|
|
91
|
-
annotations: pendingAnnotationsRef.current ??
|
|
89
|
+
annotations: pendingAnnotationsRef.current ?? annotations,
|
|
92
90
|
setAnnotations: (next) => {
|
|
93
|
-
if (engineIdRef.current === newEngineId)
|
|
91
|
+
if (engineIdRef.current === newEngineId) {
|
|
94
92
|
setAnnotations?.(next);
|
|
93
|
+
}
|
|
95
94
|
},
|
|
96
95
|
drawing,
|
|
97
96
|
editable,
|
|
@@ -101,43 +100,46 @@ const AnnotationCanvas = ({ image, annotations = [], setAnnotations, options, dr
|
|
|
101
100
|
onChange: () => forceRender((v) => v + 1),
|
|
102
101
|
});
|
|
103
102
|
requestAnimationFrame(() => {
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
if (engineIdRef.current !== newEngineId) {
|
|
104
|
+
engine.destroy();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
container.appendChild(imageCanvas);
|
|
108
|
+
container.appendChild(annotationsCanvas);
|
|
106
109
|
engine.initImageCanvas(resetOnImageChange);
|
|
107
110
|
if (prevEngine) {
|
|
111
|
+
safeRemove(container, prevEngine.getImageCanvas());
|
|
112
|
+
safeRemove(container, prevEngine.getAnnotationsCanvas());
|
|
108
113
|
prevEngine.destroy();
|
|
109
|
-
containerRef.current.removeChild(prevEngine.getImageCanvas());
|
|
110
|
-
containerRef.current.removeChild(prevEngine.getAnnotationsCanvas());
|
|
111
114
|
}
|
|
112
115
|
engineRef.current = engine;
|
|
113
116
|
pendingAnnotationsRef.current = null;
|
|
114
117
|
imageLoadingRef.current = false;
|
|
115
|
-
currentImageRef.current = image;
|
|
116
118
|
onImageLoadSuccess?.();
|
|
117
119
|
});
|
|
118
120
|
};
|
|
119
121
|
img.onerror = () => {
|
|
120
|
-
if (
|
|
122
|
+
if (engineIdRef.current !== newEngineId || !container)
|
|
121
123
|
return;
|
|
122
124
|
if (prevEngine) {
|
|
125
|
+
safeRemove(container, prevEngine.getImageCanvas());
|
|
126
|
+
safeRemove(container, prevEngine.getAnnotationsCanvas());
|
|
123
127
|
prevEngine.destroy();
|
|
124
|
-
containerRef.current.removeChild(prevEngine.getImageCanvas());
|
|
125
|
-
containerRef.current.removeChild(prevEngine.getAnnotationsCanvas());
|
|
126
128
|
}
|
|
127
129
|
engineRef.current = null;
|
|
128
130
|
pendingAnnotationsRef.current = null;
|
|
129
131
|
imageLoadingRef.current = false;
|
|
130
|
-
currentImageRef.current = '';
|
|
131
132
|
onImageLoadError?.(new Error(`Failed to load image: ${image}`));
|
|
132
133
|
};
|
|
133
134
|
img.src = image;
|
|
134
135
|
return () => {
|
|
135
|
-
|
|
136
|
+
img.onload = null;
|
|
137
|
+
img.onerror = null;
|
|
136
138
|
};
|
|
137
139
|
}, [image, resetOnImageChange]);
|
|
138
140
|
/* ---------- External annotations ---------- */
|
|
139
141
|
useEffect(() => {
|
|
140
|
-
if (
|
|
142
|
+
if (imageLoadingRef.current || !engineRef.current) {
|
|
141
143
|
pendingAnnotationsRef.current = annotations;
|
|
142
144
|
return;
|
|
143
145
|
}
|
|
@@ -152,6 +154,6 @@ const AnnotationCanvas = ({ image, annotations = [], setAnnotations, options, dr
|
|
|
152
154
|
useEffect(() => engineRef.current?.setEditable(editable), [editable]);
|
|
153
155
|
useEffect(() => engineRef.current?.setDrawing(drawing), [drawing]);
|
|
154
156
|
/* ---------- Render ---------- */
|
|
155
|
-
return (_jsx("div", { ref: containerRef, style: { width: '100%', height: '100%', position: 'relative',
|
|
157
|
+
return (_jsx("div", { ref: containerRef, style: { width: '100%', height: '100%', position: 'relative', flex: 1 }, onWheel: (e) => engineRef.current?.onWheel(e.nativeEvent), onMouseDown: (e) => engineRef.current?.onMouseDown(e.nativeEvent), onMouseMove: (e) => engineRef.current?.onMouseMove(e.nativeEvent), onMouseUp: (e) => engineRef.current?.onMouseUp(e.nativeEvent), onMouseLeave: (e) => engineRef.current?.onMouseLeave(e.nativeEvent), onContextMenu: (e) => e.preventDefault(), children: ZoomButton && (_jsx(ZoomButton, { onClick: () => engineRef.current?.initImageCanvas(true), children: engineRef.current?.getZoomRatioLabel() })) }));
|
|
156
158
|
};
|
|
157
159
|
export default AnnotationCanvas;
|