@deepnoid/canvas 0.1.70 → 0.1.72

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.
@@ -14,7 +14,6 @@ export type AnnotationCanvasProps = {
14
14
  zoom?: AnnotationCanvasOptionsZoom;
15
15
  ZoomButton?: ComponentType<ZoomButtonType>;
16
16
  resetOnImageChange?: boolean;
17
- timeout?: number;
18
17
  };
19
18
  drawing: AnnotationCanvasDrawing;
20
19
  events: {
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { useEffect, useRef, useState } from 'react';
4
4
  import { AnnotationEngine } from '../engine/public/annotationEngine';
5
5
  import useResizeObserver from './hooks/useResizeObserver';
@@ -8,24 +8,18 @@ import { useHotkeys } from './hooks/useHotkeys';
8
8
  const AnnotationCanvas = ({ image, annotations = [], setAnnotations, options, drawing, events, enableHotkeys = true, editable = true, }) => {
9
9
  const { panZoomEnabled, zoom, ZoomButton, resetOnImageChange = false } = options || {};
10
10
  const { onImageLoadSuccess, onImageLoadError } = events || {};
11
- const imageCanvasRef = useRef(null);
12
- const annotationsCanvasRef = useRef(null);
11
+ const containerRef = useRef(null);
13
12
  const engineRef = useRef(null);
14
13
  const pendingAnnotationsRef = useRef(null);
15
14
  const imageLoadingRef = useRef(false);
16
15
  const currentImageRef = useRef(image);
16
+ const engineIdRef = useRef(0);
17
17
  const [, forceRender] = useState(0);
18
- /* ---------- Resize ---------- */
18
+ /* ---------- Resize Observer ---------- */
19
19
  useResizeObserver({
20
- ref: imageCanvasRef,
21
- onResize: useDebounce((size) => {
22
- const engine = engineRef.current;
23
- const canvas = engine?.getImageCanvas();
24
- if (!engine || !canvas)
25
- return;
26
- if (canvas.width !== size.width || canvas.height !== size.height) {
27
- engine.initImageCanvas(true);
28
- }
20
+ ref: containerRef,
21
+ onResize: useDebounce(() => {
22
+ engineRef.current?.initImageCanvas(true);
29
23
  }, 150),
30
24
  });
31
25
  /* ---------- Hotkeys ---------- */
@@ -36,31 +30,45 @@ const AnnotationCanvas = ({ image, annotations = [], setAnnotations, options, dr
36
30
  enabled: enableHotkeys,
37
31
  });
38
32
  /* ---------- Image / Engine lifecycle ---------- */
39
- const engineIdRef = useRef(0);
40
33
  useEffect(() => {
41
- const isImageChanged = currentImageRef.current !== image;
42
- const prevImageCanvasState = engineRef.current?.getImageCanvasState();
43
- if (isImageChanged) {
44
- engineRef.current?.destroy();
45
- engineRef.current = null;
46
- pendingAnnotationsRef.current = null;
47
- currentImageRef.current = image;
48
- engineIdRef.current++;
49
- setAnnotations?.([]);
50
- }
34
+ if (!containerRef.current)
35
+ return;
36
+ const prevEngine = engineRef.current;
51
37
  imageLoadingRef.current = true;
52
38
  if (!image?.trim()) {
53
- engineRef.current?.destroy();
39
+ if (prevEngine) {
40
+ prevEngine.destroy();
41
+ containerRef.current.removeChild(prevEngine.getImageCanvas());
42
+ containerRef.current.removeChild(prevEngine.getAnnotationsCanvas());
43
+ }
54
44
  engineRef.current = null;
45
+ pendingAnnotationsRef.current = null;
55
46
  imageLoadingRef.current = false;
47
+ currentImageRef.current = '';
56
48
  return;
57
49
  }
50
+ const newEngineId = engineIdRef.current + 1;
51
+ engineIdRef.current = newEngineId;
58
52
  let cancelled = false;
59
53
  const img = new Image();
60
54
  img.onload = () => {
61
- if (cancelled || !imageCanvasRef.current || !annotationsCanvasRef.current) {
55
+ if (cancelled || !containerRef.current)
62
56
  return;
63
- }
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
+ const prevImageCanvasState = prevEngine?.getImageCanvasState();
64
72
  const imageCanvasState = !resetOnImageChange && prevImageCanvasState
65
73
  ? prevImageCanvasState
66
74
  : {
@@ -75,52 +83,61 @@ const AnnotationCanvas = ({ image, annotations = [], setAnnotations, options, dr
75
83
  dh: img.height,
76
84
  initZoom: 1,
77
85
  };
78
- const initialAnnotations = pendingAnnotationsRef.current ?? [];
79
- pendingAnnotationsRef.current = null;
80
- const currentEngineId = engineIdRef.current;
81
- engineRef.current = new AnnotationEngine({
82
- imageCanvas: imageCanvasRef.current,
86
+ const engine = new AnnotationEngine({
87
+ imageCanvas,
88
+ annotationsCanvas,
83
89
  image: img,
84
90
  imageCanvasState,
85
- annotationsCanvas: annotationsCanvasRef.current,
86
- annotations: initialAnnotations,
91
+ annotations: pendingAnnotationsRef.current ?? [],
87
92
  setAnnotations: (next) => {
88
- if (engineIdRef.current === currentEngineId) {
93
+ if (engineIdRef.current === newEngineId)
89
94
  setAnnotations?.(next);
90
- }
91
95
  },
92
96
  drawing,
93
97
  editable,
98
+ enableHotkeys,
94
99
  panZoomEnabled,
95
100
  zoom,
96
- enableHotkeys,
97
101
  onChange: () => forceRender((v) => v + 1),
98
102
  });
99
- engineRef.current.initImageCanvas(resetOnImageChange);
100
- imageLoadingRef.current = false;
101
- onImageLoadSuccess?.();
103
+ requestAnimationFrame(() => {
104
+ containerRef.current.appendChild(imageCanvas);
105
+ containerRef.current.appendChild(annotationsCanvas);
106
+ engine.initImageCanvas(resetOnImageChange);
107
+ if (prevEngine) {
108
+ prevEngine.destroy();
109
+ containerRef.current.removeChild(prevEngine.getImageCanvas());
110
+ containerRef.current.removeChild(prevEngine.getAnnotationsCanvas());
111
+ }
112
+ engineRef.current = engine;
113
+ pendingAnnotationsRef.current = null;
114
+ imageLoadingRef.current = false;
115
+ currentImageRef.current = image;
116
+ onImageLoadSuccess?.();
117
+ });
102
118
  };
103
119
  img.onerror = () => {
104
- if (cancelled)
120
+ if (!containerRef.current)
105
121
  return;
106
- pendingAnnotationsRef.current = null;
107
- engineRef.current?.destroy();
122
+ if (prevEngine) {
123
+ prevEngine.destroy();
124
+ containerRef.current.removeChild(prevEngine.getImageCanvas());
125
+ containerRef.current.removeChild(prevEngine.getAnnotationsCanvas());
126
+ }
108
127
  engineRef.current = null;
128
+ pendingAnnotationsRef.current = null;
109
129
  imageLoadingRef.current = false;
130
+ currentImageRef.current = '';
110
131
  onImageLoadError?.(new Error(`Failed to load image: ${image}`));
111
132
  };
112
133
  img.src = image;
113
134
  return () => {
114
135
  cancelled = true;
115
- img.onload = null;
116
- img.onerror = null;
117
136
  };
118
137
  }, [image, resetOnImageChange]);
119
138
  /* ---------- External annotations ---------- */
120
139
  useEffect(() => {
121
- if (!annotations)
122
- return;
123
- if (imageLoadingRef.current || !engineRef.current) {
140
+ if (!annotations || imageLoadingRef.current || !engineRef.current) {
124
141
  pendingAnnotationsRef.current = annotations;
125
142
  return;
126
143
  }
@@ -131,20 +148,10 @@ const AnnotationCanvas = ({ image, annotations = [], setAnnotations, options, dr
131
148
  }
132
149
  }, [annotations]);
133
150
  /* ---------- Options sync ---------- */
134
- useEffect(() => {
135
- engineRef.current?.setPanZoomEnabled(!!panZoomEnabled);
136
- }, [panZoomEnabled]);
137
- useEffect(() => {
138
- engineRef.current?.setEditable(editable);
139
- }, [editable]);
140
- useEffect(() => {
141
- engineRef.current?.setDrawing(drawing);
142
- }, [drawing]);
151
+ useEffect(() => engineRef.current?.setPanZoomEnabled(!!panZoomEnabled), [panZoomEnabled]);
152
+ useEffect(() => engineRef.current?.setEditable(editable), [editable]);
153
+ useEffect(() => engineRef.current?.setDrawing(drawing), [drawing]);
143
154
  /* ---------- Render ---------- */
144
- return (_jsx("div", { style: { width: '100%', height: '100%', display: 'flex', flex: 1 }, children: _jsxs("div", { 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(), style: {
145
- flex: 1,
146
- position: 'relative',
147
- cursor: !panZoomEnabled || editable ? 'default' : 'grab',
148
- }, children: [_jsx("canvas", { ref: imageCanvasRef, style: { position: 'absolute', width: '100%', height: '100%' } }), _jsx("canvas", { ref: annotationsCanvasRef, style: { position: 'absolute', width: '100%', height: '100%' } }), ZoomButton && (_jsx(ZoomButton, { onClick: () => engineRef.current?.initImageCanvas(true), children: engineRef.current?.getZoomRatioLabel() }))] }) }));
155
+ return (_jsx("div", { ref: containerRef, style: { width: '100%', height: '100%', position: 'relative', display: 'flex', 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() })) }));
149
156
  };
150
157
  export default AnnotationCanvas;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepnoid/canvas",
3
- "version": "0.1.70",
3
+ "version": "0.1.72",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.cjs",