@deepnoid/canvas 0.1.72 → 0.1.74
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 +41 -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,47 @@ 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
|
+
const firstChild = container.firstChild;
|
|
108
|
+
container.insertBefore(imageCanvas, firstChild);
|
|
109
|
+
container.insertBefore(annotationsCanvas, firstChild);
|
|
106
110
|
engine.initImageCanvas(resetOnImageChange);
|
|
107
111
|
if (prevEngine) {
|
|
112
|
+
safeRemove(container, prevEngine.getImageCanvas());
|
|
113
|
+
safeRemove(container, prevEngine.getAnnotationsCanvas());
|
|
108
114
|
prevEngine.destroy();
|
|
109
|
-
containerRef.current.removeChild(prevEngine.getImageCanvas());
|
|
110
|
-
containerRef.current.removeChild(prevEngine.getAnnotationsCanvas());
|
|
111
115
|
}
|
|
112
116
|
engineRef.current = engine;
|
|
113
117
|
pendingAnnotationsRef.current = null;
|
|
114
118
|
imageLoadingRef.current = false;
|
|
115
|
-
currentImageRef.current = image;
|
|
116
119
|
onImageLoadSuccess?.();
|
|
117
120
|
});
|
|
118
121
|
};
|
|
119
122
|
img.onerror = () => {
|
|
120
|
-
if (
|
|
123
|
+
if (engineIdRef.current !== newEngineId || !container)
|
|
121
124
|
return;
|
|
122
125
|
if (prevEngine) {
|
|
126
|
+
safeRemove(container, prevEngine.getImageCanvas());
|
|
127
|
+
safeRemove(container, prevEngine.getAnnotationsCanvas());
|
|
123
128
|
prevEngine.destroy();
|
|
124
|
-
containerRef.current.removeChild(prevEngine.getImageCanvas());
|
|
125
|
-
containerRef.current.removeChild(prevEngine.getAnnotationsCanvas());
|
|
126
129
|
}
|
|
127
130
|
engineRef.current = null;
|
|
128
131
|
pendingAnnotationsRef.current = null;
|
|
129
132
|
imageLoadingRef.current = false;
|
|
130
|
-
currentImageRef.current = '';
|
|
131
133
|
onImageLoadError?.(new Error(`Failed to load image: ${image}`));
|
|
132
134
|
};
|
|
133
135
|
img.src = image;
|
|
134
136
|
return () => {
|
|
135
|
-
|
|
137
|
+
img.onload = null;
|
|
138
|
+
img.onerror = null;
|
|
136
139
|
};
|
|
137
140
|
}, [image, resetOnImageChange]);
|
|
138
141
|
/* ---------- External annotations ---------- */
|
|
139
142
|
useEffect(() => {
|
|
140
|
-
if (
|
|
143
|
+
if (imageLoadingRef.current || !engineRef.current) {
|
|
141
144
|
pendingAnnotationsRef.current = annotations;
|
|
142
145
|
return;
|
|
143
146
|
}
|
|
@@ -152,6 +155,6 @@ const AnnotationCanvas = ({ image, annotations = [], setAnnotations, options, dr
|
|
|
152
155
|
useEffect(() => engineRef.current?.setEditable(editable), [editable]);
|
|
153
156
|
useEffect(() => engineRef.current?.setDrawing(drawing), [drawing]);
|
|
154
157
|
/* ---------- Render ---------- */
|
|
155
|
-
return (_jsx("div", { ref: containerRef, style: { width: '100%', height: '100%', position: 'relative',
|
|
158
|
+
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
159
|
};
|
|
157
160
|
export default AnnotationCanvas;
|