fix: canvas fits image exactly, chained annotation loading, arrow move tracking
- Canvas now sizes to match the scaled image dimensions (zero empty space) instead of using a fixed aspect ratio, fixing coordinate mismatch between editor and viewer - Annotations load only after background image is ready via _pendingAnnotations pattern, preventing placement at wrong coordinates - Arrow endpoints (arrowX1/Y1/X2/Y2) update on object:modified using transform delta, so moved arrows serialize at correct position - Coordinate scaling on load: coordScale = currentImageScale / savedImageScale handles annotations saved at different screen widths Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -64,6 +64,13 @@ function annotationEditor() {
|
||||
/** Whether the canvas has unsaved changes */
|
||||
isDirty: false,
|
||||
|
||||
/** Natural (unscaled) dimensions of the loaded background image */
|
||||
_bgImageNatW: 0,
|
||||
_bgImageNatH: 0,
|
||||
|
||||
/** Annotations JSON to load after background image is ready */
|
||||
_pendingAnnotations: null,
|
||||
|
||||
/** Current color for new annotations */
|
||||
currentColor: '#2563EB',
|
||||
|
||||
@@ -93,17 +100,118 @@ function annotationEditor() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Objects are move-only: no resize/rotate handles, just selection border
|
||||
fabric.Object.prototype.hasControls = false;
|
||||
fabric.Object.prototype.hasBorders = true;
|
||||
fabric.Object.prototype.borderColor = '#2563EB';
|
||||
fabric.Object.prototype.lockRotation = true;
|
||||
fabric.Object.prototype.lockScalingX = true;
|
||||
fabric.Object.prototype.lockScalingY = true;
|
||||
// Disable object caching to avoid stale render states
|
||||
fabric.Object.prototype.objectCaching = false;
|
||||
|
||||
try {
|
||||
this.canvas = new fabric.Canvas(this.canvasEl, {
|
||||
selection: true,
|
||||
preserveObjectStacking: true,
|
||||
backgroundColor: '#f1f5f9',
|
||||
enableRetinaScaling: false,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('AnnotationEditor: failed to create canvas:', err);
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var canvas = this.canvas;
|
||||
|
||||
// ---- Fabric.js v5 controls-visibility workaround ----
|
||||
//
|
||||
// Fabric v5.3.1 draws object controls (resize/rotate handles) on the
|
||||
// LOWER canvas. The UPPER canvas (contextTop) sits on top and can
|
||||
// obscure those controls if it has stale content.
|
||||
// Fix: force contextTopDirty = true before every renderAll().
|
||||
|
||||
var _origRenderAll = canvas.renderAll.bind(canvas);
|
||||
canvas.renderAll = function () {
|
||||
canvas.contextTopDirty = true;
|
||||
return _origRenderAll();
|
||||
};
|
||||
|
||||
// ---- Reliable pointer override ----
|
||||
//
|
||||
// Fabric.js v5.3.1 getPointer can produce wrong coordinates due to
|
||||
// retina-scaling arithmetic, stale _offset cache, or scroll-offset
|
||||
// mismatches. Replace with a minimal BCR-based calculation that is
|
||||
// always correct: mouse-client-position minus canvas-BCR, scaled to
|
||||
// canvas coordinate space.
|
||||
|
||||
canvas.getPointer = function (e, ignoreVpt) {
|
||||
// Honour Fabric's per-event cache (set by _cacheTransformEventData)
|
||||
if (canvas._absolutePointer && !ignoreVpt) {
|
||||
return canvas._absolutePointer;
|
||||
}
|
||||
if (canvas._pointer && ignoreVpt) {
|
||||
return canvas._pointer;
|
||||
}
|
||||
|
||||
// Extract clientX/Y from mouse or touch event
|
||||
var clientX = 0, clientY = 0;
|
||||
if (e) {
|
||||
if (e.clientX != null) {
|
||||
clientX = e.clientX;
|
||||
clientY = e.clientY;
|
||||
} else if (e.changedTouches && e.changedTouches[0]) {
|
||||
clientX = e.changedTouches[0].clientX;
|
||||
clientY = e.changedTouches[0].clientY;
|
||||
} else if (e.touches && e.touches[0]) {
|
||||
clientX = e.touches[0].clientX;
|
||||
clientY = e.touches[0].clientY;
|
||||
}
|
||||
}
|
||||
|
||||
// BCR of the upper canvas (where events are captured)
|
||||
var rect = canvas.upperCanvasEl.getBoundingClientRect();
|
||||
|
||||
// Position relative to canvas element in CSS pixels
|
||||
var x = clientX - rect.left;
|
||||
var y = clientY - rect.top;
|
||||
|
||||
// Scale from CSS pixels to canvas internal coordinate space
|
||||
// With enableRetinaScaling:false these should be 1:1, but handle
|
||||
// any mismatch defensively.
|
||||
if (rect.width > 0 && rect.height > 0) {
|
||||
x = x * (canvas.width / rect.width);
|
||||
y = y * (canvas.height / rect.height);
|
||||
}
|
||||
|
||||
// When ignoreVpt is false, return object-space coordinates
|
||||
if (!ignoreVpt) {
|
||||
return canvas.restorePointerVpt({ x: x, y: y });
|
||||
}
|
||||
return { x: x, y: y };
|
||||
};
|
||||
|
||||
// ---- Keep canvas offset fresh ----
|
||||
canvas.on('mouse:down:before', function () {
|
||||
canvas.calcOffset();
|
||||
});
|
||||
|
||||
// Force move-only properties on every object added to the canvas
|
||||
this.canvas.on('object:added', function (e) {
|
||||
var obj = e.target;
|
||||
if (obj && obj.objectType !== '_temp') {
|
||||
obj.hasControls = false;
|
||||
obj.hasBorders = true;
|
||||
obj.selectable = true;
|
||||
obj.evented = true;
|
||||
obj.lockRotation = true;
|
||||
obj.lockScalingX = true;
|
||||
obj.lockScalingY = true;
|
||||
obj.setCoords();
|
||||
}
|
||||
});
|
||||
|
||||
this.setupEvents();
|
||||
this.setupKeyboard();
|
||||
|
||||
@@ -111,7 +219,6 @@ function annotationEditor() {
|
||||
window.addEventListener('resize', this._debouncedResize);
|
||||
|
||||
// ---- Bridge: listen for commands from recipeEditor ----
|
||||
var self = this;
|
||||
|
||||
this._onToolChange = function (e) {
|
||||
if (e.detail && e.detail.tool) {
|
||||
@@ -125,6 +232,11 @@ function annotationEditor() {
|
||||
};
|
||||
window.addEventListener('anno-delete-selected', this._onDeleteSelected);
|
||||
|
||||
this._onClearAll = function () {
|
||||
self.clearAll();
|
||||
};
|
||||
window.addEventListener('anno-clear-all', this._onClearAll);
|
||||
|
||||
this._onColorChange = function (e) {
|
||||
if (e.detail && e.detail.color) {
|
||||
self.currentColor = e.detail.color;
|
||||
@@ -253,12 +365,12 @@ function annotationEditor() {
|
||||
|
||||
/**
|
||||
* Internal: load background image and optionally annotations.
|
||||
* Annotations are deferred until the image callback fires so that
|
||||
* canvas dimensions and imageScale are known before placing objects.
|
||||
*/
|
||||
_loadImageAndAnnotations(imageUrl, annotationsJson) {
|
||||
this._pendingAnnotations = annotationsJson || null;
|
||||
this.loadBackgroundImage(imageUrl);
|
||||
if (annotationsJson) {
|
||||
this.loadAnnotationsJson(annotationsJson);
|
||||
}
|
||||
},
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
@@ -325,6 +437,7 @@ function annotationEditor() {
|
||||
|
||||
/**
|
||||
* Load a raster image URL as the canvas background.
|
||||
* The canvas is resized to fit the image exactly (no empty space).
|
||||
*/
|
||||
_loadImageBackground(imageUrl) {
|
||||
var self = this;
|
||||
@@ -337,35 +450,64 @@ function annotationEditor() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self.canvas.width) {
|
||||
self.resizeCanvas();
|
||||
}
|
||||
|
||||
var cw = self.canvas.width || 800;
|
||||
var ch = self.canvas.height || 480;
|
||||
|
||||
var scale = Math.min(
|
||||
cw / img.width,
|
||||
ch / img.height,
|
||||
1 // never upscale
|
||||
);
|
||||
|
||||
self.canvas.setBackgroundImage(
|
||||
img,
|
||||
self.canvas.renderAll.bind(self.canvas),
|
||||
{
|
||||
scaleX: scale,
|
||||
scaleY: scale,
|
||||
originX: 'left',
|
||||
originY: 'top',
|
||||
}
|
||||
);
|
||||
self._bgImageNatW = img.width;
|
||||
self._bgImageNatH = img.height;
|
||||
|
||||
self._fitCanvasToImage(img);
|
||||
self.imageLoaded = true;
|
||||
|
||||
// Load pending annotations now that canvas is sized to the image
|
||||
if (self._pendingAnnotations) {
|
||||
self.loadAnnotationsJson(self._pendingAnnotations);
|
||||
self._pendingAnnotations = null;
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Resize canvas to fit the background image exactly within the
|
||||
* container width. Canvas dimensions = image dimensions * scale,
|
||||
* so there is zero empty space and canvas coords map 1:1 to
|
||||
* scaled-image coords.
|
||||
*
|
||||
* @param {fabric.Image} img - Fabric image object (background)
|
||||
*/
|
||||
_fitCanvasToImage(img) {
|
||||
var fabricWrapper = this.canvasEl.parentElement;
|
||||
var container = fabricWrapper ? fabricWrapper.parentElement : null;
|
||||
if (!container) return;
|
||||
|
||||
var containerWidth = container.clientWidth;
|
||||
if (!containerWidth) return;
|
||||
|
||||
var natW = img.width || this._bgImageNatW;
|
||||
var natH = img.height || this._bgImageNatH;
|
||||
if (!natW || !natH) return;
|
||||
|
||||
// Scale to fit container width; never upscale
|
||||
var scale = Math.min(containerWidth / natW, 1);
|
||||
var canvasW = Math.round(natW * scale);
|
||||
var canvasH = Math.round(natH * scale);
|
||||
|
||||
this.canvas.setDimensions({ width: canvasW, height: canvasH });
|
||||
|
||||
this.canvas.setBackgroundImage(
|
||||
img,
|
||||
this.canvas.renderAll.bind(this.canvas),
|
||||
{
|
||||
scaleX: scale,
|
||||
scaleY: scale,
|
||||
originX: 'left',
|
||||
originY: 'top',
|
||||
}
|
||||
);
|
||||
|
||||
this.canvas.calcOffset();
|
||||
this.canvas.forEachObject(function (obj) { obj.setCoords(); });
|
||||
this.canvas.renderAll();
|
||||
},
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Object creation - Markers
|
||||
// ----------------------------------------------------------------
|
||||
@@ -403,7 +545,12 @@ function annotationEditor() {
|
||||
left: x,
|
||||
top: y,
|
||||
selectable: true,
|
||||
evented: true,
|
||||
hasControls: false,
|
||||
hasBorders: true,
|
||||
lockRotation: true,
|
||||
lockScalingX: true,
|
||||
lockScalingY: true,
|
||||
// Custom properties
|
||||
objectType: 'marker',
|
||||
markerNumber: markerNumber,
|
||||
@@ -411,6 +558,7 @@ function annotationEditor() {
|
||||
});
|
||||
|
||||
this.canvas.add(marker);
|
||||
marker.setCoords();
|
||||
this.canvas.setActiveObject(marker);
|
||||
this.isDirty = true;
|
||||
window.dispatchEvent(new CustomEvent('annotations-changed', { detail: { json: this.getAnnotationsJson() } }));
|
||||
@@ -454,6 +602,12 @@ function annotationEditor() {
|
||||
|
||||
var arrow = new fabric.Group([line, arrowHead], {
|
||||
selectable: true,
|
||||
evented: true,
|
||||
hasControls: false,
|
||||
hasBorders: true,
|
||||
lockRotation: true,
|
||||
lockScalingX: true,
|
||||
lockScalingY: true,
|
||||
objectType: 'arrow',
|
||||
// Store original endpoints for serialization
|
||||
arrowX1: x1,
|
||||
@@ -466,6 +620,7 @@ function annotationEditor() {
|
||||
});
|
||||
|
||||
this.canvas.add(arrow);
|
||||
arrow.setCoords();
|
||||
this.canvas.setActiveObject(arrow);
|
||||
this.isDirty = true;
|
||||
window.dispatchEvent(new CustomEvent('annotations-changed', { detail: { json: this.getAnnotationsJson() } }));
|
||||
@@ -496,6 +651,12 @@ function annotationEditor() {
|
||||
strokeWidth: this.currentStrokeWidth,
|
||||
strokeDashArray: this.currentLineDash.length > 0 ? this.currentLineDash.slice() : null,
|
||||
selectable: true,
|
||||
evented: true,
|
||||
hasControls: false,
|
||||
hasBorders: true,
|
||||
lockRotation: true,
|
||||
lockScalingX: true,
|
||||
lockScalingY: true,
|
||||
objectType: 'area',
|
||||
areaColor: this.currentColor,
|
||||
areaStrokeWidth: this.currentStrokeWidth,
|
||||
@@ -503,6 +664,7 @@ function annotationEditor() {
|
||||
});
|
||||
|
||||
this.canvas.add(rect);
|
||||
rect.setCoords();
|
||||
this.canvas.setActiveObject(rect);
|
||||
this.isDirty = true;
|
||||
window.dispatchEvent(new CustomEvent('annotations-changed', { detail: { json: this.getAnnotationsJson() } }));
|
||||
@@ -527,8 +689,6 @@ function annotationEditor() {
|
||||
this.canvas.forEachObject(function (o) {
|
||||
o.selectable = true;
|
||||
o.evented = true;
|
||||
o.hasControls = true;
|
||||
o.hasBorders = true;
|
||||
});
|
||||
} else {
|
||||
// marker, arrow, rect — disable object interaction during drawing
|
||||
@@ -562,6 +722,9 @@ function annotationEditor() {
|
||||
// ---- mouse:down ----
|
||||
|
||||
this.canvas.on('mouse:down', function (opt) {
|
||||
// In select mode, let Fabric.js handle clicks without interference
|
||||
if (self.activeMode === 'select') return;
|
||||
|
||||
var pointer = self.canvas.getPointer(opt.e);
|
||||
|
||||
if (self.activeMode === 'marker') {
|
||||
@@ -677,6 +840,12 @@ function annotationEditor() {
|
||||
detail: { markerNumber: selected[0].markerNumber }
|
||||
}));
|
||||
}
|
||||
// Force coords + re-render so controls are visible on lower canvas
|
||||
var active = self.canvas.getActiveObject();
|
||||
if (active) {
|
||||
active.setCoords();
|
||||
self.canvas.requestRenderAll();
|
||||
}
|
||||
});
|
||||
|
||||
this.canvas.on('selection:updated', function (e) {
|
||||
@@ -689,6 +858,12 @@ function annotationEditor() {
|
||||
detail: { markerNumber: selected[0].markerNumber }
|
||||
}));
|
||||
}
|
||||
// Force coords + re-render so controls are visible on lower canvas
|
||||
var active = self.canvas.getActiveObject();
|
||||
if (active) {
|
||||
active.setCoords();
|
||||
self.canvas.requestRenderAll();
|
||||
}
|
||||
});
|
||||
|
||||
this.canvas.on('selection:cleared', function () {
|
||||
@@ -698,12 +873,18 @@ function annotationEditor() {
|
||||
|
||||
// ---- Object modification ----
|
||||
|
||||
this.canvas.on('object:modified', function () {
|
||||
self.isDirty = true;
|
||||
window.dispatchEvent(new CustomEvent('annotations-changed', { detail: { json: self.getAnnotationsJson() } }));
|
||||
});
|
||||
|
||||
this.canvas.on('object:moved', function () {
|
||||
this.canvas.on('object:modified', function (e) {
|
||||
// Update arrow stored endpoints after a move so serialization
|
||||
// reflects the current position (not the original creation coords).
|
||||
var obj = e.target;
|
||||
if (obj && obj.objectType === 'arrow' && e.transform) {
|
||||
var dx = obj.left - e.transform.original.left;
|
||||
var dy = obj.top - e.transform.original.top;
|
||||
obj.arrowX1 = (obj.arrowX1 || 0) + dx;
|
||||
obj.arrowY1 = (obj.arrowY1 || 0) + dy;
|
||||
obj.arrowX2 = (obj.arrowX2 || 0) + dx;
|
||||
obj.arrowY2 = (obj.arrowY2 || 0) + dy;
|
||||
}
|
||||
self.isDirty = true;
|
||||
window.dispatchEvent(new CustomEvent('annotations-changed', { detail: { json: self.getAnnotationsJson() } }));
|
||||
});
|
||||
@@ -794,7 +975,13 @@ function annotationEditor() {
|
||||
resizeCanvas() {
|
||||
if (!this.canvasEl || !this.canvas) return;
|
||||
|
||||
// Fabric wrapper → .canvas-container (or whatever holds the canvas)
|
||||
// If a background image is loaded, fit canvas to its dimensions
|
||||
if (this.canvas.backgroundImage && this._bgImageNatW && this._bgImageNatH) {
|
||||
this._fitCanvasToImage(this.canvas.backgroundImage);
|
||||
return;
|
||||
}
|
||||
|
||||
// No image yet — use placeholder size so canvas is visible
|
||||
var fabricWrapper = this.canvasEl.parentElement;
|
||||
var container = fabricWrapper ? fabricWrapper.parentElement : null;
|
||||
if (!container) return;
|
||||
@@ -802,8 +989,7 @@ function annotationEditor() {
|
||||
var width = container.clientWidth;
|
||||
var height = Math.max(400, Math.round(width * 0.6));
|
||||
|
||||
this.canvas.setWidth(width);
|
||||
this.canvas.setHeight(height);
|
||||
this.canvas.setDimensions({ width: width, height: height });
|
||||
this.canvas.calcOffset();
|
||||
this.canvas.renderAll();
|
||||
},
|
||||
@@ -825,9 +1011,6 @@ function annotationEditor() {
|
||||
top: Math.round(obj.top * 100) / 100,
|
||||
width: Math.round((obj.width || 0) * 100) / 100,
|
||||
height: Math.round((obj.height || 0) * 100) / 100,
|
||||
scaleX: Math.round((obj.scaleX || 1) * 1000) / 1000,
|
||||
scaleY: Math.round((obj.scaleY || 1) * 1000) / 1000,
|
||||
angle: Math.round((obj.angle || 0) * 100) / 100,
|
||||
markerNumber: obj.markerNumber || null,
|
||||
};
|
||||
|
||||
@@ -851,10 +1034,16 @@ function annotationEditor() {
|
||||
return data;
|
||||
});
|
||||
|
||||
// Save background image scale so the preview can convert canvas coords
|
||||
// to image coords correctly regardless of editor canvas dimensions.
|
||||
var bgImg = this.canvas.backgroundImage;
|
||||
var imageScale = bgImg ? (bgImg.scaleX || 1) : 1;
|
||||
|
||||
return JSON.stringify({
|
||||
version: 1,
|
||||
version: 2,
|
||||
width: this.canvas.width,
|
||||
height: this.canvas.height,
|
||||
imageScale: imageScale,
|
||||
objects: objects,
|
||||
});
|
||||
},
|
||||
@@ -876,6 +1065,15 @@ function annotationEditor() {
|
||||
var data =
|
||||
typeof jsonString === 'string' ? JSON.parse(jsonString) : jsonString;
|
||||
|
||||
// Coordinate scale factor: annotations may have been saved at a
|
||||
// different canvas size (different screen width). Since canvas
|
||||
// coords = imageCoords * imageScale, we convert:
|
||||
// currentCoord = savedCoord * (currentImageScale / savedImageScale)
|
||||
var savedImageScale = data.imageScale || 1;
|
||||
var bgImg = this.canvas.backgroundImage;
|
||||
var currentImageScale = bgImg ? (bgImg.scaleX || 1) : 1;
|
||||
var cs = currentImageScale / savedImageScale; // coordinate scale
|
||||
|
||||
// Remove existing objects
|
||||
this.canvas.getObjects().slice().forEach(
|
||||
function (obj) {
|
||||
@@ -891,15 +1089,8 @@ function annotationEditor() {
|
||||
if (obj.type === 'marker') {
|
||||
var savedColor = this.currentColor;
|
||||
if (obj.fill) this.currentColor = obj.fill;
|
||||
var created = this.addMarker(obj.left, obj.top, obj.markerNumber);
|
||||
this.addMarker(obj.left * cs, obj.top * cs, obj.markerNumber);
|
||||
this.currentColor = savedColor;
|
||||
// Restore transforms (scale + rotation)
|
||||
created.set({
|
||||
angle: obj.angle || 0,
|
||||
scaleX: obj.scaleX || 1,
|
||||
scaleY: obj.scaleY || 1,
|
||||
});
|
||||
created.setCoords();
|
||||
} else if (obj.type === 'arrow') {
|
||||
var savedColor2 = this.currentColor;
|
||||
var savedWidth = this.currentStrokeWidth;
|
||||
@@ -908,24 +1099,15 @@ function annotationEditor() {
|
||||
if (obj.strokeWidth) this.currentStrokeWidth = obj.strokeWidth;
|
||||
if (obj.lineDash) this.currentLineDash = obj.lineDash;
|
||||
else this.currentLineDash = [];
|
||||
// Reconstruct arrow from stored endpoints or from position/size
|
||||
var x1 = obj.x1 != null ? obj.x1 : obj.left;
|
||||
var y1 = obj.y1 != null ? obj.y1 : obj.top;
|
||||
var x2 = obj.x2 != null ? obj.x2 : obj.left + (obj.width || 100);
|
||||
var y2 = obj.y2 != null ? obj.y2 : obj.top + (obj.height || 50);
|
||||
var created2 = this.addArrow(x1, y1, x2, y2);
|
||||
// Reconstruct arrow from stored endpoints, scaled to current canvas
|
||||
var x1 = (obj.x1 != null ? obj.x1 : obj.left) * cs;
|
||||
var y1 = (obj.y1 != null ? obj.y1 : obj.top) * cs;
|
||||
var x2 = (obj.x2 != null ? obj.x2 : obj.left + (obj.width || 100)) * cs;
|
||||
var y2 = (obj.y2 != null ? obj.y2 : obj.top + (obj.height || 50)) * cs;
|
||||
this.addArrow(x1, y1, x2, y2);
|
||||
this.currentColor = savedColor2;
|
||||
this.currentStrokeWidth = savedWidth;
|
||||
this.currentLineDash = savedDash;
|
||||
// Restore transforms (position + scale + rotation)
|
||||
created2.set({
|
||||
left: obj.left,
|
||||
top: obj.top,
|
||||
angle: obj.angle || 0,
|
||||
scaleX: obj.scaleX || 1,
|
||||
scaleY: obj.scaleY || 1,
|
||||
});
|
||||
created2.setCoords();
|
||||
} else if (obj.type === 'area') {
|
||||
var savedColor3 = this.currentColor;
|
||||
var savedWidth2 = this.currentStrokeWidth;
|
||||
@@ -934,17 +1116,10 @@ function annotationEditor() {
|
||||
if (obj.strokeWidth) this.currentStrokeWidth = obj.strokeWidth;
|
||||
if (obj.lineDash) this.currentLineDash = obj.lineDash;
|
||||
else this.currentLineDash = [];
|
||||
var created3 = this.addRect(obj.left, obj.top, obj.width || 150, obj.height || 100);
|
||||
this.addRect(obj.left * cs, obj.top * cs, (obj.width || 150) * cs, (obj.height || 100) * cs);
|
||||
this.currentColor = savedColor3;
|
||||
this.currentStrokeWidth = savedWidth2;
|
||||
this.currentLineDash = savedDash2;
|
||||
// Restore transforms (scale + rotation)
|
||||
created3.set({
|
||||
angle: obj.angle || 0,
|
||||
scaleX: obj.scaleX || 1,
|
||||
scaleY: obj.scaleY || 1,
|
||||
});
|
||||
created3.setCoords();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -961,6 +1136,7 @@ function annotationEditor() {
|
||||
markerNumbers.length > 0 ? Math.max.apply(null, markerNumbers) + 1 : 1;
|
||||
|
||||
this.isDirty = false;
|
||||
this.canvas.discardActiveObject();
|
||||
this.canvas.renderAll();
|
||||
} catch (e) {
|
||||
console.error('AnnotationEditor: failed to load annotations:', e);
|
||||
@@ -1032,6 +1208,9 @@ function annotationEditor() {
|
||||
if (this._onDeleteSelected) {
|
||||
window.removeEventListener('anno-delete-selected', this._onDeleteSelected);
|
||||
}
|
||||
if (this._onClearAll) {
|
||||
window.removeEventListener('anno-clear-all', this._onClearAll);
|
||||
}
|
||||
if (this._onColorChange) {
|
||||
window.removeEventListener('anno-color-change', this._onColorChange);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user