@bensitu/image-editor 1.4.2 → 1.5.0
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/README.md +445 -131
- package/dist/image-editor.esm.js +304 -130
- package/dist/image-editor.esm.js.map +2 -2
- package/dist/image-editor.esm.min.js +2 -2
- package/dist/image-editor.esm.min.js.map +3 -3
- package/dist/image-editor.esm.min.mjs +2 -2
- package/dist/image-editor.esm.min.mjs.map +3 -3
- package/dist/image-editor.esm.mjs +304 -130
- package/dist/image-editor.esm.mjs.map +2 -2
- package/dist/image-editor.js +304 -130
- package/dist/image-editor.js.map +2 -2
- package/dist/image-editor.min.js +2 -2
- package/dist/image-editor.min.js.map +3 -3
- package/image-editor.d.ts +60 -19
- package/package.json +1 -1
- package/src/image-editor.js +322 -127
package/dist/image-editor.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @file image-editor.js
|
|
5
5
|
* @module image-editor
|
|
6
|
-
* @version 1.
|
|
6
|
+
* @version 1.5.0
|
|
7
7
|
* @author Ben Situ
|
|
8
8
|
* @license MIT
|
|
9
9
|
* @description Lightweight canvas-based image editor with masking/transform/export support.
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
this._activeAnimationRejectors = /* @__PURE__ */ new Set();
|
|
145
145
|
this._disposed = false;
|
|
146
146
|
this._initialized = false;
|
|
147
|
-
this.onImageLoaded = typeof options.onImageLoaded === "function" ? options.onImageLoaded : null;
|
|
147
|
+
this.onImageLoaded = typeof this.options.onImageLoaded === "function" ? this.options.onImageLoaded : null;
|
|
148
148
|
this.animationQueue = new AnimationQueue();
|
|
149
149
|
this.historyManager = new HistoryManager(this.maxHistorySize);
|
|
150
150
|
}
|
|
@@ -190,10 +190,12 @@
|
|
|
190
190
|
* Use this method to set up the editor UI before interacting with it.
|
|
191
191
|
*
|
|
192
192
|
* @param {Object} [idMap={}] - Optional mapping from logical element names to actual DOM element IDs.
|
|
193
|
-
* Supported keys include: canvas, canvasContainer,
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
193
|
+
* Supported keys include: canvas, canvasContainer, imagePlaceholder, scalePercentageInput,
|
|
194
|
+
* rotateLeftDegreesInput, rotateRightDegreesInput, rotateLeftButton, rotateRightButton,
|
|
195
|
+
* createMaskButton, removeSelectedMaskButton, removeAllMasksButton, mergeMasksButton,
|
|
196
|
+
* downloadImageButton, maskList, zoomInButton, zoomOutButton, resetImageTransformButton,
|
|
197
|
+
* undoButton, redoButton, imageInput, uploadArea, enterCropModeButton, applyCropButton,
|
|
198
|
+
* and cancelCropButton. Deprecated 1.x names remain supported as aliases.
|
|
197
199
|
*
|
|
198
200
|
* @returns {void}
|
|
199
201
|
*
|
|
@@ -202,7 +204,7 @@
|
|
|
202
204
|
* @example
|
|
203
205
|
* editor.init({
|
|
204
206
|
* canvas: 'myFabricCanvasId',
|
|
205
|
-
*
|
|
207
|
+
* downloadImageButton: 'myDownloadButtonId'
|
|
206
208
|
* });
|
|
207
209
|
*/
|
|
208
210
|
init(idMap = {}) {
|
|
@@ -221,33 +223,53 @@
|
|
|
221
223
|
this._containerOriginalOverflow = null;
|
|
222
224
|
this._lastContainerViewportSize = null;
|
|
223
225
|
this._canvasElementOriginalStyle = null;
|
|
226
|
+
this._deprecatedElementKeyWarnings = /* @__PURE__ */ new Set();
|
|
224
227
|
const defaults = {
|
|
225
228
|
canvas: "fabricCanvas",
|
|
226
229
|
canvasContainer: null,
|
|
227
230
|
// Pass an ID here if you have a scrollable viewport container
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
231
|
+
imagePlaceholder: "imagePlaceholder",
|
|
232
|
+
imgPlaceholder: null,
|
|
233
|
+
scalePercentageInput: "scalePercentageInput",
|
|
234
|
+
scaleRate: null,
|
|
235
|
+
rotateLeftDegreesInput: "rotateLeftDegreesInput",
|
|
236
|
+
rotationLeftInput: null,
|
|
237
|
+
rotateRightDegreesInput: "rotateRightDegreesInput",
|
|
238
|
+
rotationRightInput: null,
|
|
239
|
+
rotateLeftButton: "rotateLeftButton",
|
|
240
|
+
rotateLeftBtn: null,
|
|
241
|
+
rotateRightButton: "rotateRightButton",
|
|
242
|
+
rotateRightBtn: null,
|
|
243
|
+
createMaskButton: "createMaskButton",
|
|
244
|
+
addMaskBtn: null,
|
|
245
|
+
removeSelectedMaskButton: "removeSelectedMaskButton",
|
|
246
|
+
removeMaskBtn: null,
|
|
247
|
+
removeAllMasksButton: "removeAllMasksButton",
|
|
248
|
+
removeAllMasksBtn: null,
|
|
249
|
+
mergeMasksButton: "mergeMasksButton",
|
|
250
|
+
mergeBtn: null,
|
|
251
|
+
downloadImageButton: "downloadImageButton",
|
|
252
|
+
downloadBtn: null,
|
|
239
253
|
maskList: "maskList",
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
254
|
+
zoomInButton: "zoomInButton",
|
|
255
|
+
zoomInBtn: null,
|
|
256
|
+
zoomOutButton: "zoomOutButton",
|
|
257
|
+
zoomOutBtn: null,
|
|
258
|
+
resetImageTransformButton: "resetImageTransformButton",
|
|
259
|
+
resetBtn: null,
|
|
260
|
+
undoButton: "undoButton",
|
|
261
|
+
undoBtn: null,
|
|
262
|
+
redoButton: "redoButton",
|
|
263
|
+
redoBtn: null,
|
|
245
264
|
imageInput: "imageInput",
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
265
|
+
enterCropModeButton: "enterCropModeButton",
|
|
266
|
+
cropBtn: null,
|
|
267
|
+
applyCropButton: "applyCropButton",
|
|
268
|
+
applyCropBtn: null,
|
|
269
|
+
cancelCropButton: "cancelCropButton",
|
|
270
|
+
cancelCropBtn: null
|
|
249
271
|
};
|
|
250
|
-
this.elements = {
|
|
272
|
+
this.elements = this._resolveElementIdMap(idMap || {}, defaults);
|
|
251
273
|
this._elementCache = {};
|
|
252
274
|
this._initCanvas();
|
|
253
275
|
this._bindEvents();
|
|
@@ -260,6 +282,63 @@
|
|
|
260
282
|
this._updatePlaceholderStatus();
|
|
261
283
|
}
|
|
262
284
|
}
|
|
285
|
+
_resolveElementIdMap(idMap, defaults) {
|
|
286
|
+
const resolved = { ...defaults, ...idMap };
|
|
287
|
+
this._resolveElementAliases(resolved, idMap, defaults, "imagePlaceholder", ["imgPlaceholder"]);
|
|
288
|
+
this._resolveElementAliases(resolved, idMap, defaults, "scalePercentageInput", ["scaleRate"]);
|
|
289
|
+
this._resolveElementAliases(resolved, idMap, defaults, "rotateLeftDegreesInput", ["rotationLeftInput"]);
|
|
290
|
+
this._resolveElementAliases(resolved, idMap, defaults, "rotateRightDegreesInput", ["rotationRightInput"]);
|
|
291
|
+
this._resolveElementAlias(resolved, idMap, defaults, "rotateLeftButton", "rotateLeftBtn");
|
|
292
|
+
this._resolveElementAlias(resolved, idMap, defaults, "rotateRightButton", "rotateRightBtn");
|
|
293
|
+
this._resolveElementAlias(resolved, idMap, defaults, "createMaskButton", "addMaskBtn");
|
|
294
|
+
this._resolveElementAliases(resolved, idMap, defaults, "removeSelectedMaskButton", ["removeMaskBtn"]);
|
|
295
|
+
this._resolveElementAlias(resolved, idMap, defaults, "removeAllMasksButton", "removeAllMasksBtn");
|
|
296
|
+
this._resolveElementAlias(resolved, idMap, defaults, "mergeMasksButton", "mergeBtn");
|
|
297
|
+
this._resolveElementAliases(resolved, idMap, defaults, "downloadImageButton", ["downloadBtn"]);
|
|
298
|
+
this._resolveElementAlias(resolved, idMap, defaults, "zoomInButton", "zoomInBtn");
|
|
299
|
+
this._resolveElementAlias(resolved, idMap, defaults, "zoomOutButton", "zoomOutBtn");
|
|
300
|
+
this._resolveElementAlias(resolved, idMap, defaults, "resetImageTransformButton", "resetBtn");
|
|
301
|
+
this._resolveElementAlias(resolved, idMap, defaults, "undoButton", "undoBtn");
|
|
302
|
+
this._resolveElementAlias(resolved, idMap, defaults, "redoButton", "redoBtn");
|
|
303
|
+
this._resolveElementAliases(resolved, idMap, defaults, "enterCropModeButton", ["cropBtn"]);
|
|
304
|
+
this._resolveElementAlias(resolved, idMap, defaults, "applyCropButton", "applyCropBtn");
|
|
305
|
+
this._resolveElementAlias(resolved, idMap, defaults, "cancelCropButton", "cancelCropBtn");
|
|
306
|
+
return resolved;
|
|
307
|
+
}
|
|
308
|
+
_resolveElementAlias(resolved, idMap, defaults, canonicalKey, deprecatedKey) {
|
|
309
|
+
this._resolveElementAliases(resolved, idMap, defaults, canonicalKey, [deprecatedKey]);
|
|
310
|
+
}
|
|
311
|
+
_resolveElementAliases(resolved, idMap, defaults, canonicalKey, deprecatedKeys) {
|
|
312
|
+
const hasCanonicalKey = Object.prototype.hasOwnProperty.call(idMap, canonicalKey);
|
|
313
|
+
if (hasCanonicalKey) {
|
|
314
|
+
resolved[canonicalKey] = idMap[canonicalKey];
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
let deprecatedValue;
|
|
318
|
+
let hasDeprecatedValue = false;
|
|
319
|
+
for (const deprecatedKey of deprecatedKeys) {
|
|
320
|
+
if (Object.prototype.hasOwnProperty.call(idMap, deprecatedKey)) {
|
|
321
|
+
if (!hasDeprecatedValue) {
|
|
322
|
+
deprecatedValue = idMap[deprecatedKey];
|
|
323
|
+
hasDeprecatedValue = true;
|
|
324
|
+
}
|
|
325
|
+
this._warnDeprecatedElementIdKey(deprecatedKey, canonicalKey);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (hasDeprecatedValue) {
|
|
329
|
+
resolved[canonicalKey] = deprecatedValue;
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
resolved[canonicalKey] = defaults[canonicalKey];
|
|
333
|
+
}
|
|
334
|
+
_warnDeprecatedElementIdKey(deprecatedKey, canonicalKey) {
|
|
335
|
+
if (!this._deprecatedElementKeyWarnings) this._deprecatedElementKeyWarnings = /* @__PURE__ */ new Set();
|
|
336
|
+
if (this._deprecatedElementKeyWarnings.has(deprecatedKey)) return;
|
|
337
|
+
this._deprecatedElementKeyWarnings.add(deprecatedKey);
|
|
338
|
+
this._reportWarning(
|
|
339
|
+
`ElementIdMap.${deprecatedKey} is deprecated. Use ${canonicalKey} instead. This alias will be removed in v2.0.0.`
|
|
340
|
+
);
|
|
341
|
+
}
|
|
263
342
|
_reportError(message, error = null) {
|
|
264
343
|
const handler = this.options && this.options.onError;
|
|
265
344
|
if (typeof handler !== "function") return;
|
|
@@ -276,6 +355,11 @@
|
|
|
276
355
|
} catch {
|
|
277
356
|
}
|
|
278
357
|
}
|
|
358
|
+
_notifyImageLoaded() {
|
|
359
|
+
const optionsCallback = this.options && this.options.onImageLoaded;
|
|
360
|
+
const callback = typeof optionsCallback === "function" ? optionsCallback : this.onImageLoaded;
|
|
361
|
+
if (typeof callback === "function") callback();
|
|
362
|
+
}
|
|
279
363
|
/**
|
|
280
364
|
* Initializes the Fabric canvas, viewport elements, and selection event handlers.
|
|
281
365
|
*
|
|
@@ -298,7 +382,7 @@
|
|
|
298
382
|
} else {
|
|
299
383
|
this.containerElement = canvasElement.parentElement;
|
|
300
384
|
}
|
|
301
|
-
this.placeholderElement = this._getElement("
|
|
385
|
+
this.placeholderElement = this._getElement("imagePlaceholder") || null;
|
|
302
386
|
let initialWidth = this.options.canvasWidth;
|
|
303
387
|
let initialHeight = this.options.canvasHeight;
|
|
304
388
|
if (this.containerElement) {
|
|
@@ -448,20 +532,20 @@
|
|
|
448
532
|
});
|
|
449
533
|
}
|
|
450
534
|
});
|
|
451
|
-
this._bindIfExists("
|
|
452
|
-
this._bindIfExists("
|
|
453
|
-
this._bindIfExists("
|
|
535
|
+
this._bindIfExists("zoomInButton", "click", () => this.scaleImage(this.currentScale + this.options.scaleStep).catch((error) => this._reportError("scaleImage failed", error)));
|
|
536
|
+
this._bindIfExists("zoomOutButton", "click", () => this.scaleImage(this.currentScale - this.options.scaleStep).catch((error) => this._reportError("scaleImage failed", error)));
|
|
537
|
+
this._bindIfExists("resetImageTransformButton", "click", () => {
|
|
454
538
|
this.resetImageTransform().catch((error) => this._reportError("resetImageTransform failed", error));
|
|
455
539
|
});
|
|
456
|
-
this._bindIfExists("
|
|
457
|
-
this._bindIfExists("
|
|
458
|
-
this._bindIfExists("
|
|
459
|
-
this._bindIfExists("
|
|
460
|
-
this._bindIfExists("
|
|
461
|
-
this._bindIfExists("
|
|
462
|
-
this._bindIfExists("
|
|
463
|
-
this._bindIfExists("
|
|
464
|
-
const rotationInputElement = this._getElement("
|
|
540
|
+
this._bindIfExists("createMaskButton", "click", () => this.createMask());
|
|
541
|
+
this._bindIfExists("removeSelectedMaskButton", "click", () => this.removeSelectedMask());
|
|
542
|
+
this._bindIfExists("removeAllMasksButton", "click", () => this.removeAllMasks());
|
|
543
|
+
this._bindIfExists("mergeMasksButton", "click", () => this.mergeMasks().catch((error) => this._reportError("merge error", error)));
|
|
544
|
+
this._bindIfExists("downloadImageButton", "click", () => this.downloadImage());
|
|
545
|
+
this._bindIfExists("undoButton", "click", () => this.undo().catch((error) => this._reportError("undo failed", error)));
|
|
546
|
+
this._bindIfExists("redoButton", "click", () => this.redo().catch((error) => this._reportError("redo failed", error)));
|
|
547
|
+
this._bindIfExists("rotateLeftButton", "click", () => {
|
|
548
|
+
const rotationInputElement = this._getElement("rotateLeftDegreesInput");
|
|
465
549
|
let step = this.options.rotationStep;
|
|
466
550
|
if (rotationInputElement) {
|
|
467
551
|
const parsedStep = parseFloat(rotationInputElement.value);
|
|
@@ -469,8 +553,8 @@
|
|
|
469
553
|
}
|
|
470
554
|
this.rotateImage(this.currentRotation - step).catch((error) => this._reportError("rotateImage failed", error));
|
|
471
555
|
});
|
|
472
|
-
this._bindIfExists("
|
|
473
|
-
const rotationInputElement = this._getElement("
|
|
556
|
+
this._bindIfExists("rotateRightButton", "click", () => {
|
|
557
|
+
const rotationInputElement = this._getElement("rotateRightDegreesInput");
|
|
474
558
|
let step = this.options.rotationStep;
|
|
475
559
|
if (rotationInputElement) {
|
|
476
560
|
const parsedStep = parseFloat(rotationInputElement.value);
|
|
@@ -478,11 +562,11 @@
|
|
|
478
562
|
}
|
|
479
563
|
this.rotateImage(this.currentRotation + step).catch((error) => this._reportError("rotateImage failed", error));
|
|
480
564
|
});
|
|
481
|
-
this._bindIfExists("
|
|
482
|
-
this._bindIfExists("
|
|
565
|
+
this._bindIfExists("enterCropModeButton", "click", () => this.enterCropMode());
|
|
566
|
+
this._bindIfExists("applyCropButton", "click", () => {
|
|
483
567
|
this.applyCrop().catch((error) => this._reportError("applyCrop failed", error));
|
|
484
568
|
});
|
|
485
|
-
this._bindIfExists("
|
|
569
|
+
this._bindIfExists("cancelCropButton", "click", () => this.cancelCrop());
|
|
486
570
|
this._bindIfExists("maskList", "click", (event) => this._handleMaskListClick(event));
|
|
487
571
|
}
|
|
488
572
|
/**
|
|
@@ -652,9 +736,7 @@
|
|
|
652
736
|
this._updateUI();
|
|
653
737
|
this.canvas.renderAll();
|
|
654
738
|
this._lastSnapshot = this._captureCanvasStateOrThrow("loadImage");
|
|
655
|
-
|
|
656
|
-
this.onImageLoaded();
|
|
657
|
-
}
|
|
739
|
+
this._notifyImageLoaded();
|
|
658
740
|
} catch (error) {
|
|
659
741
|
await this._rollbackLoadImageTransaction(transaction);
|
|
660
742
|
throw error;
|
|
@@ -707,7 +789,7 @@
|
|
|
707
789
|
try {
|
|
708
790
|
imageElement.src = "";
|
|
709
791
|
} catch (error) {
|
|
710
|
-
|
|
792
|
+
this._reportWarning("Image timeout cleanup failed", error);
|
|
711
793
|
}
|
|
712
794
|
}, safeTimeoutMs);
|
|
713
795
|
imageElement.onload = () => settle(() => resolve(imageElement));
|
|
@@ -775,6 +857,7 @@
|
|
|
775
857
|
async _rollbackLoadImageTransaction(transaction) {
|
|
776
858
|
if (!transaction || !this.canvas || this._disposed) return;
|
|
777
859
|
let didRestoreCanvasState = false;
|
|
860
|
+
let didFailCanvasRestore = false;
|
|
778
861
|
try {
|
|
779
862
|
if (transaction.canvasState) {
|
|
780
863
|
await this.loadFromState(transaction.canvasState);
|
|
@@ -782,22 +865,27 @@
|
|
|
782
865
|
}
|
|
783
866
|
} catch (error) {
|
|
784
867
|
this._lastMask = null;
|
|
868
|
+
didFailCanvasRestore = true;
|
|
785
869
|
this._reportError("loadImage rollback failed", error);
|
|
786
870
|
}
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
this.currentRotation = transaction.currentRotation;
|
|
790
|
-
this.maskCounter = transaction.maskCounter;
|
|
791
|
-
this.isImageLoadedToCanvas = transaction.isImageLoadedToCanvas;
|
|
792
|
-
this._lastSnapshot = transaction.lastSnapshot;
|
|
793
|
-
if (didRestoreCanvasState) {
|
|
794
|
-
this._restoreLastMaskReference(transaction.lastMask);
|
|
871
|
+
if (didFailCanvasRestore) {
|
|
872
|
+
this._reconcileEditorStateFromCanvas();
|
|
795
873
|
} else {
|
|
796
|
-
this.
|
|
874
|
+
this.baseImageScale = transaction.baseImageScale;
|
|
875
|
+
this.currentScale = transaction.currentScale;
|
|
876
|
+
this.currentRotation = transaction.currentRotation;
|
|
877
|
+
this.maskCounter = transaction.maskCounter;
|
|
878
|
+
this.isImageLoadedToCanvas = transaction.isImageLoadedToCanvas;
|
|
879
|
+
this._lastSnapshot = transaction.lastSnapshot;
|
|
880
|
+
if (didRestoreCanvasState) {
|
|
881
|
+
this._restoreLastMaskReference(transaction.lastMask);
|
|
882
|
+
} else {
|
|
883
|
+
this._lastMask = null;
|
|
884
|
+
}
|
|
885
|
+
this._lastMaskInitialLeft = transaction.lastMaskInitialLeft;
|
|
886
|
+
this._lastMaskInitialTop = transaction.lastMaskInitialTop;
|
|
887
|
+
this._lastMaskInitialWidth = transaction.lastMaskInitialWidth;
|
|
797
888
|
}
|
|
798
|
-
this._lastMaskInitialLeft = transaction.lastMaskInitialLeft;
|
|
799
|
-
this._lastMaskInitialTop = transaction.lastMaskInitialTop;
|
|
800
|
-
this._lastMaskInitialWidth = transaction.lastMaskInitialWidth;
|
|
801
889
|
this._restoreElementVisibility(this.placeholderElement, transaction.placeholderVisibility);
|
|
802
890
|
this._restoreElementVisibility(this._getCanvasVisibilityElement(), transaction.canvasVisibility);
|
|
803
891
|
if (this.containerElement) {
|
|
@@ -810,6 +898,46 @@
|
|
|
810
898
|
this._updateUI();
|
|
811
899
|
if (this.canvas) this.canvas.renderAll();
|
|
812
900
|
}
|
|
901
|
+
_reconcileEditorStateFromCanvas() {
|
|
902
|
+
if (!this.canvas) {
|
|
903
|
+
this.originalImage = null;
|
|
904
|
+
this.baseImageScale = 1;
|
|
905
|
+
this.currentScale = 1;
|
|
906
|
+
this.currentRotation = 0;
|
|
907
|
+
this.maskCounter = 0;
|
|
908
|
+
this.isImageLoadedToCanvas = false;
|
|
909
|
+
this._lastSnapshot = null;
|
|
910
|
+
this._clearMaskPlacementMemory();
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
const canvasObjects = this.canvas.getObjects();
|
|
914
|
+
this.originalImage = canvasObjects.find((object) => object.type === "image" && !object.maskId) || null;
|
|
915
|
+
if (this.originalImage) {
|
|
916
|
+
const imageScale = Number(this.originalImage.scaleX) || 1;
|
|
917
|
+
this.baseImageScale = imageScale;
|
|
918
|
+
this.currentScale = 1;
|
|
919
|
+
this.currentRotation = Number(this.originalImage.angle) || 0;
|
|
920
|
+
} else {
|
|
921
|
+
this.baseImageScale = 1;
|
|
922
|
+
this.currentScale = 1;
|
|
923
|
+
this.currentRotation = 0;
|
|
924
|
+
}
|
|
925
|
+
const masks = canvasObjects.filter((object) => object.maskId);
|
|
926
|
+
this.maskCounter = masks.reduce((max, mask) => Math.max(max, Number(mask.maskId) || 0), 0);
|
|
927
|
+
this._lastMask = masks[masks.length - 1] || null;
|
|
928
|
+
if (!this._lastMask) {
|
|
929
|
+
this._lastMaskInitialLeft = null;
|
|
930
|
+
this._lastMaskInitialTop = null;
|
|
931
|
+
this._lastMaskInitialWidth = null;
|
|
932
|
+
}
|
|
933
|
+
this.isImageLoadedToCanvas = !!this.originalImage;
|
|
934
|
+
try {
|
|
935
|
+
this._lastSnapshot = this._serializeCanvasState();
|
|
936
|
+
} catch (error) {
|
|
937
|
+
this._lastSnapshot = null;
|
|
938
|
+
this._reportWarning("loadImage rollback: failed to reconcile canvas snapshot", error);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
813
941
|
_restoreLastMaskReference(previousLastMask) {
|
|
814
942
|
if (!this.canvas) {
|
|
815
943
|
this._lastMask = null;
|
|
@@ -880,6 +1008,7 @@
|
|
|
880
1008
|
* @private
|
|
881
1009
|
*/
|
|
882
1010
|
_setCanvasSizeInt(width, height) {
|
|
1011
|
+
if (!this.canvas) return;
|
|
883
1012
|
const integerWidth = Math.max(1, Math.round(Number(width) || 1));
|
|
884
1013
|
const integerHeight = Math.max(1, Math.round(Number(height) || 1));
|
|
885
1014
|
this.canvas.setWidth(integerWidth);
|
|
@@ -1152,7 +1281,7 @@
|
|
|
1152
1281
|
/**
|
|
1153
1282
|
* Captures editor-owned runtime state that Fabric does not include in canvas JSON.
|
|
1154
1283
|
*
|
|
1155
|
-
* @returns {{version:number, baseImageScale:number, currentScale:number, currentRotation:number, maskCounter:number}} Serializable editor metadata.
|
|
1284
|
+
* @returns {{version:number, baseImageScale:number, currentScale:number, currentRotation:number, maskCounter:number, canvasWidth:number, canvasHeight:number}} Serializable editor metadata.
|
|
1156
1285
|
* @private
|
|
1157
1286
|
*/
|
|
1158
1287
|
_serializeEditorMetadata() {
|
|
@@ -1160,12 +1289,16 @@
|
|
|
1160
1289
|
const currentScale = Number(this.currentScale);
|
|
1161
1290
|
const currentRotation = Number(this.currentRotation);
|
|
1162
1291
|
const maskCounter = Number(this.maskCounter);
|
|
1292
|
+
const canvasWidth = this.canvas ? Number(this.canvas.getWidth()) : NaN;
|
|
1293
|
+
const canvasHeight = this.canvas ? Number(this.canvas.getHeight()) : NaN;
|
|
1163
1294
|
return {
|
|
1164
1295
|
version: 1,
|
|
1165
1296
|
baseImageScale: Number.isFinite(baseImageScale) && baseImageScale > 0 ? baseImageScale : 1,
|
|
1166
1297
|
currentScale: Number.isFinite(currentScale) && currentScale > 0 ? currentScale : 1,
|
|
1167
1298
|
currentRotation: Number.isFinite(currentRotation) ? currentRotation : 0,
|
|
1168
|
-
maskCounter: Number.isFinite(maskCounter) && maskCounter > 0 ? Math.floor(maskCounter) : 0
|
|
1299
|
+
maskCounter: Number.isFinite(maskCounter) && maskCounter > 0 ? Math.floor(maskCounter) : 0,
|
|
1300
|
+
canvasWidth: Number.isFinite(canvasWidth) && canvasWidth > 0 ? Math.round(canvasWidth) : 1,
|
|
1301
|
+
canvasHeight: Number.isFinite(canvasHeight) && canvasHeight > 0 ? Math.round(canvasHeight) : 1
|
|
1169
1302
|
};
|
|
1170
1303
|
}
|
|
1171
1304
|
_serializeCanvasState() {
|
|
@@ -1501,17 +1634,13 @@
|
|
|
1501
1634
|
requiredWidth = Math.max(requiredWidth, Math.ceil(boundingRect.left + boundingRect.width + padding));
|
|
1502
1635
|
requiredHeight = Math.max(requiredHeight, Math.ceil(boundingRect.top + boundingRect.height + padding));
|
|
1503
1636
|
});
|
|
1504
|
-
const shouldUseScrollSafeViewport = this.options.fitImageToCanvas || this.options.coverImageToCanvas;
|
|
1505
1637
|
let minWidth = 0;
|
|
1506
1638
|
let minHeight = 0;
|
|
1507
|
-
if (
|
|
1639
|
+
if (this.containerElement) {
|
|
1508
1640
|
const viewport = this._getContainerViewportSize();
|
|
1509
1641
|
const safetyMargin = this._getScrollSafetyMargin();
|
|
1510
1642
|
minWidth = Math.max(1, viewport.width - safetyMargin);
|
|
1511
1643
|
minHeight = Math.max(1, viewport.height - safetyMargin);
|
|
1512
|
-
} else if (this.containerElement) {
|
|
1513
|
-
minWidth = Math.floor(this.containerElement.clientWidth || 0);
|
|
1514
|
-
minHeight = Math.floor(this.containerElement.clientHeight || 0);
|
|
1515
1644
|
}
|
|
1516
1645
|
const newWidth = Math.max(currentWidth, minWidth, requiredWidth);
|
|
1517
1646
|
const newHeight = Math.max(currentHeight, minHeight, requiredHeight);
|
|
@@ -1580,9 +1709,15 @@
|
|
|
1580
1709
|
_assertEditorAvailable(operationName) {
|
|
1581
1710
|
if (this._disposed || !this.canvas) throw new Error(`${operationName} cannot run after the editor has been disposed`);
|
|
1582
1711
|
}
|
|
1712
|
+
_isCropModeAllowedOperation(operationName) {
|
|
1713
|
+
return operationName === "applyCrop" || operationName === "cancelCrop";
|
|
1714
|
+
}
|
|
1583
1715
|
_assertIdleForOperation(operationName, options = {}) {
|
|
1584
1716
|
this._assertEditorAvailable(operationName);
|
|
1585
1717
|
const isOwnInternalOperation = this._isOwnInternalOperation(options);
|
|
1718
|
+
if (this._cropMode && !this._isCropModeAllowedOperation(operationName) && !isOwnInternalOperation) {
|
|
1719
|
+
throw new Error(`${operationName} cannot run while crop mode is active`);
|
|
1720
|
+
}
|
|
1586
1721
|
if (this.isAnimating || this.animationQueue && this.animationQueue.isBusy()) {
|
|
1587
1722
|
throw new Error(`${operationName} cannot run while an animation is running`);
|
|
1588
1723
|
}
|
|
@@ -1595,10 +1730,14 @@
|
|
|
1595
1730
|
}
|
|
1596
1731
|
_assertCanQueueAnimation(operationName, options = {}) {
|
|
1597
1732
|
this._assertEditorAvailable(operationName);
|
|
1598
|
-
|
|
1733
|
+
const isOwnInternalOperation = this._isOwnInternalOperation(options);
|
|
1734
|
+
if (this._cropMode && !this._isCropModeAllowedOperation(operationName) && !isOwnInternalOperation) {
|
|
1735
|
+
throw new Error(`${operationName} cannot run while crop mode is active`);
|
|
1736
|
+
}
|
|
1737
|
+
if (this._isLoading && !isOwnInternalOperation) {
|
|
1599
1738
|
throw new Error(`${operationName} cannot run while an image is loading`);
|
|
1600
1739
|
}
|
|
1601
|
-
if (this._activeOperationToken && !
|
|
1740
|
+
if (this._activeOperationToken && !isOwnInternalOperation) {
|
|
1602
1741
|
throw new Error(`${operationName} cannot run while ${this._activeOperationName || "another operation"} is running`);
|
|
1603
1742
|
}
|
|
1604
1743
|
}
|
|
@@ -1785,10 +1924,19 @@
|
|
|
1785
1924
|
}
|
|
1786
1925
|
return this.animationQueue.add(async () => {
|
|
1787
1926
|
const before = this._lastSnapshot || this._captureCanvasStateOrThrow("resetImageTransform");
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1927
|
+
try {
|
|
1928
|
+
await this._scaleImageImpl(1, { saveHistory: false });
|
|
1929
|
+
await this._rotateImageImpl(0, { saveHistory: false });
|
|
1930
|
+
const after = this._captureCanvasStateOrThrow("resetImageTransform");
|
|
1931
|
+
this._pushStateTransition(before, after);
|
|
1932
|
+
} catch (error) {
|
|
1933
|
+
try {
|
|
1934
|
+
await this.loadFromState(before);
|
|
1935
|
+
} catch (restoreError) {
|
|
1936
|
+
this._reportError("resetImageTransform rollback failed", restoreError);
|
|
1937
|
+
}
|
|
1938
|
+
throw error;
|
|
1939
|
+
}
|
|
1792
1940
|
}).finally(() => {
|
|
1793
1941
|
if (!this._disposed && this.canvas) this._updateUI();
|
|
1794
1942
|
}).catch((error) => {
|
|
@@ -1827,10 +1975,13 @@
|
|
|
1827
1975
|
try {
|
|
1828
1976
|
const state = typeof serializedState === "string" ? JSON.parse(serializedState) : serializedState;
|
|
1829
1977
|
const editorMetadata = state && state.imageEditorMetadata ? state.imageEditorMetadata : null;
|
|
1978
|
+
const restoredCanvasWidth = Number(editorMetadata && editorMetadata.canvasWidth);
|
|
1979
|
+
const restoredCanvasHeight = Number(editorMetadata && editorMetadata.canvasHeight);
|
|
1980
|
+
const hasRestoredCanvasSize = Number.isFinite(restoredCanvasWidth) && restoredCanvasWidth > 0 && Number.isFinite(restoredCanvasHeight) && restoredCanvasHeight > 0;
|
|
1830
1981
|
if (editorMetadata && Object.prototype.hasOwnProperty.call(editorMetadata, "version") && Number(editorMetadata.version) !== 1) {
|
|
1831
1982
|
this._reportWarning(`loadFromState: unsupported editor metadata version ${editorMetadata.version}`);
|
|
1832
1983
|
}
|
|
1833
|
-
|
|
1984
|
+
const finishLoad = async () => {
|
|
1834
1985
|
try {
|
|
1835
1986
|
if (this._disposed || !this.canvas) {
|
|
1836
1987
|
reject(new Error("Editor was disposed while loading state"));
|
|
@@ -1866,6 +2017,11 @@
|
|
|
1866
2017
|
this.currentScale = 1;
|
|
1867
2018
|
this.currentRotation = 0;
|
|
1868
2019
|
}
|
|
2020
|
+
if (hasRestoredCanvasSize) {
|
|
2021
|
+
this._setCanvasSizeInt(restoredCanvasWidth, restoredCanvasHeight);
|
|
2022
|
+
} else if (this.originalImage && this._shouldResizeCanvasToContentBounds()) {
|
|
2023
|
+
this._updateCanvasSizeToImageBounds();
|
|
2024
|
+
}
|
|
1869
2025
|
const masks = canvasObjects.filter((object) => object.maskId);
|
|
1870
2026
|
masks.forEach((mask) => {
|
|
1871
2027
|
this._restoreMaskControls(mask);
|
|
@@ -1893,6 +2049,9 @@
|
|
|
1893
2049
|
this._reportError("loadFromState() failed", callbackError);
|
|
1894
2050
|
reject(callbackError);
|
|
1895
2051
|
}
|
|
2052
|
+
};
|
|
2053
|
+
this.canvas.loadFromJSON(state, () => {
|
|
2054
|
+
void finishLoad();
|
|
1896
2055
|
});
|
|
1897
2056
|
} catch (error) {
|
|
1898
2057
|
this._reportError("loadFromState() failed", error);
|
|
@@ -2040,14 +2199,7 @@
|
|
|
2040
2199
|
}
|
|
2041
2200
|
_rebindMaskEvents(mask) {
|
|
2042
2201
|
if (!mask) return;
|
|
2043
|
-
|
|
2044
|
-
try {
|
|
2045
|
-
mask.off("mouseover", mask.__imageEditorMaskHandlers.mouseover);
|
|
2046
|
-
mask.off("mouseout", mask.__imageEditorMaskHandlers.mouseout);
|
|
2047
|
-
} catch (error) {
|
|
2048
|
-
void error;
|
|
2049
|
-
}
|
|
2050
|
-
}
|
|
2202
|
+
this._cleanupMaskEvents(mask);
|
|
2051
2203
|
const metadata = {};
|
|
2052
2204
|
if (!Number.isFinite(Number(mask.originalAlpha))) {
|
|
2053
2205
|
metadata.originalAlpha = Number.isFinite(Number(mask.opacity)) ? Number(mask.opacity) : 0.5;
|
|
@@ -2074,6 +2226,22 @@
|
|
|
2074
2226
|
mask.on("mouseout", mouseout);
|
|
2075
2227
|
mask.__imageEditorMaskHandlers = { mouseover, mouseout };
|
|
2076
2228
|
}
|
|
2229
|
+
_cleanupMaskEvents(mask) {
|
|
2230
|
+
if (!mask || !mask.__imageEditorMaskHandlers) return;
|
|
2231
|
+
try {
|
|
2232
|
+
if (typeof mask.off === "function") {
|
|
2233
|
+
mask.off("mouseover", mask.__imageEditorMaskHandlers.mouseover);
|
|
2234
|
+
mask.off("mouseout", mask.__imageEditorMaskHandlers.mouseout);
|
|
2235
|
+
}
|
|
2236
|
+
} catch (error) {
|
|
2237
|
+
this._reportWarning("Mask event cleanup failed", error);
|
|
2238
|
+
}
|
|
2239
|
+
try {
|
|
2240
|
+
delete mask.__imageEditorMaskHandlers;
|
|
2241
|
+
} catch (error) {
|
|
2242
|
+
this._reportWarning("Mask event metadata cleanup failed", error);
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2077
2245
|
/**
|
|
2078
2246
|
* Creates a mask and adds it to the canvas.
|
|
2079
2247
|
*
|
|
@@ -2284,6 +2452,7 @@
|
|
|
2284
2452
|
this.canvas.discardActiveObject();
|
|
2285
2453
|
selectedMasks.forEach((mask) => {
|
|
2286
2454
|
this._removeLabelForMask(mask);
|
|
2455
|
+
this._cleanupMaskEvents(mask);
|
|
2287
2456
|
this.canvas.remove(mask);
|
|
2288
2457
|
});
|
|
2289
2458
|
const masks = this.canvas.getObjects().filter((object) => object.maskId);
|
|
@@ -2308,7 +2477,10 @@
|
|
|
2308
2477
|
const saveHistory = options.saveHistory !== false;
|
|
2309
2478
|
const masks = this.canvas.getObjects().filter((object) => object.maskId);
|
|
2310
2479
|
masks.forEach((mask) => this._removeLabelForMask(mask));
|
|
2311
|
-
masks.forEach((mask) =>
|
|
2480
|
+
masks.forEach((mask) => {
|
|
2481
|
+
this._cleanupMaskEvents(mask);
|
|
2482
|
+
this.canvas.remove(mask);
|
|
2483
|
+
});
|
|
2312
2484
|
this.canvas.discardActiveObject();
|
|
2313
2485
|
this._lastMask = null;
|
|
2314
2486
|
this._lastMaskInitialLeft = null;
|
|
@@ -2377,7 +2549,7 @@
|
|
|
2377
2549
|
if (backup.labelInCanvas) this.canvas.bringToFront(backup.label);
|
|
2378
2550
|
this._syncMaskLabel(backup.mask);
|
|
2379
2551
|
} catch (error) {
|
|
2380
|
-
|
|
2552
|
+
this._reportWarning("restoreMaskLabelBackups: failed to restore mask label", error);
|
|
2381
2553
|
}
|
|
2382
2554
|
});
|
|
2383
2555
|
}
|
|
@@ -2508,7 +2680,6 @@
|
|
|
2508
2680
|
try {
|
|
2509
2681
|
if (canvasObjectSet.has(label)) {
|
|
2510
2682
|
this.canvas.remove(label);
|
|
2511
|
-
canvasObjectSet.delete(label);
|
|
2512
2683
|
}
|
|
2513
2684
|
} catch (error) {
|
|
2514
2685
|
void error;
|
|
@@ -2794,7 +2965,6 @@
|
|
|
2794
2965
|
const maskStyleBackups = this._captureMaskExportBackups(masks);
|
|
2795
2966
|
const labelBackups = this._captureMaskLabelBackups(masks);
|
|
2796
2967
|
const activeObjectBackup = this._captureActiveObjectBackup();
|
|
2797
|
-
let finalBase64;
|
|
2798
2968
|
try {
|
|
2799
2969
|
masks.forEach((mask) => this._removeLabelForMask(mask));
|
|
2800
2970
|
this.canvas.discardActiveObject();
|
|
@@ -2807,7 +2977,7 @@
|
|
|
2807
2977
|
this.originalImage.setCoords();
|
|
2808
2978
|
const imageBounds = this.originalImage.getBoundingRect(true, true);
|
|
2809
2979
|
const exportRegion = this._getClampedCanvasRegion(imageBounds);
|
|
2810
|
-
|
|
2980
|
+
return await this._exportCanvasRegionToDataURL({
|
|
2811
2981
|
...exportRegion,
|
|
2812
2982
|
multiplier,
|
|
2813
2983
|
quality,
|
|
@@ -2820,7 +2990,6 @@
|
|
|
2820
2990
|
this._restoreActiveObjectBackup(activeObjectBackup);
|
|
2821
2991
|
this.canvas.renderAll();
|
|
2822
2992
|
}
|
|
2823
|
-
return finalBase64;
|
|
2824
2993
|
}
|
|
2825
2994
|
/**
|
|
2826
2995
|
* Backward-compatible alias for {@link ImageEditor#exportImageBase64}.
|
|
@@ -2947,22 +3116,21 @@
|
|
|
2947
3116
|
this._cropPrevEvented = null;
|
|
2948
3117
|
}
|
|
2949
3118
|
_removeCropRect() {
|
|
2950
|
-
if (
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
targetHandlers.handlers.forEach((handlerRecord) => {
|
|
3119
|
+
if (this._cropHandlers && this._cropHandlers.length) {
|
|
3120
|
+
this._cropHandlers.forEach((targetHandlers) => {
|
|
3121
|
+
(targetHandlers.handlers || []).forEach((handlerRecord) => {
|
|
3122
|
+
try {
|
|
2955
3123
|
if (targetHandlers.target && typeof targetHandlers.target.off === "function") {
|
|
2956
3124
|
targetHandlers.target.off(handlerRecord.eventName, handlerRecord.handler);
|
|
2957
3125
|
}
|
|
2958
|
-
})
|
|
3126
|
+
} catch (error) {
|
|
3127
|
+
this._reportWarning("Crop handler cleanup failed", error);
|
|
3128
|
+
}
|
|
2959
3129
|
});
|
|
2960
|
-
}
|
|
2961
|
-
} catch (error) {
|
|
2962
|
-
void error;
|
|
3130
|
+
});
|
|
2963
3131
|
}
|
|
2964
3132
|
try {
|
|
2965
|
-
if (this.canvas) this.canvas.remove(this._cropRect);
|
|
3133
|
+
if (this.canvas && this._cropRect) this.canvas.remove(this._cropRect);
|
|
2966
3134
|
} catch (error) {
|
|
2967
3135
|
void error;
|
|
2968
3136
|
}
|
|
@@ -3132,9 +3300,13 @@
|
|
|
3132
3300
|
try {
|
|
3133
3301
|
beforeJson = this._serializeCanvasState();
|
|
3134
3302
|
} catch (error) {
|
|
3135
|
-
this.
|
|
3303
|
+
this._reportError("applyCrop: failed to capture rollback state", error);
|
|
3136
3304
|
beforeJson = null;
|
|
3137
3305
|
}
|
|
3306
|
+
if (!beforeJson) {
|
|
3307
|
+
this.cancelCrop();
|
|
3308
|
+
return;
|
|
3309
|
+
}
|
|
3138
3310
|
const preservedMasks = [];
|
|
3139
3311
|
try {
|
|
3140
3312
|
const masks = this.canvas.getObjects().filter((object) => object.maskId);
|
|
@@ -3144,6 +3316,7 @@
|
|
|
3144
3316
|
const maskBounds = mask.getBoundingRect(true, true);
|
|
3145
3317
|
const intersectsCrop = maskBounds.left < cropRegion.sourceX + cropRegion.sourceWidth && maskBounds.left + maskBounds.width > cropRegion.sourceX && maskBounds.top < cropRegion.sourceY + cropRegion.sourceHeight && maskBounds.top + maskBounds.height > cropRegion.sourceY;
|
|
3146
3318
|
this._removeLabelForMask(mask);
|
|
3319
|
+
this._cleanupMaskEvents(mask);
|
|
3147
3320
|
this.canvas.remove(mask);
|
|
3148
3321
|
if (shouldPreserveMasks && intersectsCrop) {
|
|
3149
3322
|
this._translateObjectByCanvasOffset(mask, -cropRegion.sourceX, -cropRegion.sourceY);
|
|
@@ -3214,7 +3387,7 @@
|
|
|
3214
3387
|
* @private
|
|
3215
3388
|
*/
|
|
3216
3389
|
_updateInputs() {
|
|
3217
|
-
const scaleInputElement = this._getElement("
|
|
3390
|
+
const scaleInputElement = this._getElement("scalePercentageInput");
|
|
3218
3391
|
if (scaleInputElement) scaleInputElement.value = Math.round(this.currentScale * 100);
|
|
3219
3392
|
}
|
|
3220
3393
|
/**
|
|
@@ -3238,7 +3411,7 @@
|
|
|
3238
3411
|
for (const key of Object.keys(this.elements || {})) {
|
|
3239
3412
|
const element = this._getElement(key);
|
|
3240
3413
|
if (!element) continue;
|
|
3241
|
-
if (key === "applyCropBtn" || key === "cancelCropBtn") {
|
|
3414
|
+
if (key === "applyCropButton" || key === "cancelCropButton" || key === "applyCropBtn" || key === "cancelCropBtn") {
|
|
3242
3415
|
this._setDisabled(key, false);
|
|
3243
3416
|
} else {
|
|
3244
3417
|
this._setDisabled(key, true);
|
|
@@ -3246,24 +3419,24 @@
|
|
|
3246
3419
|
}
|
|
3247
3420
|
return;
|
|
3248
3421
|
}
|
|
3249
|
-
this._setDisabled("
|
|
3250
|
-
this._setDisabled("
|
|
3251
|
-
this._setDisabled("
|
|
3252
|
-
this._setDisabled("
|
|
3253
|
-
this._setDisabled("
|
|
3254
|
-
this._setDisabled("
|
|
3255
|
-
this._setDisabled("
|
|
3256
|
-
this._setDisabled("
|
|
3257
|
-
this._setDisabled("
|
|
3258
|
-
this._setDisabled("
|
|
3259
|
-
this._setDisabled("
|
|
3260
|
-
this._setDisabled("
|
|
3261
|
-
this._setDisabled("
|
|
3262
|
-
this._setDisabled("
|
|
3263
|
-
this._setDisabled("
|
|
3264
|
-
this._setDisabled("
|
|
3265
|
-
this._setDisabled("
|
|
3266
|
-
this._setDisabled("
|
|
3422
|
+
this._setDisabled("zoomInButton", !hasImage || isBusy || this.currentScale >= this.options.maxScale);
|
|
3423
|
+
this._setDisabled("zoomOutButton", !hasImage || isBusy || this.currentScale <= this.options.minScale);
|
|
3424
|
+
this._setDisabled("rotateLeftButton", !hasImage || isBusy);
|
|
3425
|
+
this._setDisabled("rotateRightButton", !hasImage || isBusy);
|
|
3426
|
+
this._setDisabled("createMaskButton", !hasImage || isBusy);
|
|
3427
|
+
this._setDisabled("removeSelectedMaskButton", !hasSelectedMask || isBusy);
|
|
3428
|
+
this._setDisabled("removeAllMasksButton", !hasMasks || isBusy);
|
|
3429
|
+
this._setDisabled("mergeMasksButton", !hasImage || !hasMasks || isBusy);
|
|
3430
|
+
this._setDisabled("downloadImageButton", !hasImage || isBusy);
|
|
3431
|
+
this._setDisabled("resetImageTransformButton", !hasImage || isDefaultTransform || isBusy);
|
|
3432
|
+
this._setDisabled("undoButton", !hasImage || isBusy || !canUndo);
|
|
3433
|
+
this._setDisabled("redoButton", !hasImage || isBusy || !canRedo);
|
|
3434
|
+
this._setDisabled("enterCropModeButton", !hasImage || isBusy);
|
|
3435
|
+
this._setDisabled("applyCropButton", true);
|
|
3436
|
+
this._setDisabled("cancelCropButton", true);
|
|
3437
|
+
this._setDisabled("scalePercentageInput", !hasImage || isBusy);
|
|
3438
|
+
this._setDisabled("rotateLeftDegreesInput", !hasImage || isBusy);
|
|
3439
|
+
this._setDisabled("rotateRightDegreesInput", !hasImage || isBusy);
|
|
3267
3440
|
this._setDisabled("maskList", !hasImage || isBusy);
|
|
3268
3441
|
this._setDisabled("imageInput", isBusy);
|
|
3269
3442
|
this._setDisabled("uploadArea", isBusy);
|
|
@@ -3271,7 +3444,7 @@
|
|
|
3271
3444
|
/**
|
|
3272
3445
|
* Enables or disables a specific UI element (typically a button) by its key.
|
|
3273
3446
|
*
|
|
3274
|
-
* @param {string} key - Key of the element in this.elements (e.g. '
|
|
3447
|
+
* @param {string} key - Key of the element in this.elements (e.g. 'zoomInButton').
|
|
3275
3448
|
* @param {boolean} disabled - If true, disables the element; otherwise enables.
|
|
3276
3449
|
* @private
|
|
3277
3450
|
*/
|
|
@@ -3395,14 +3568,7 @@
|
|
|
3395
3568
|
} catch (error) {
|
|
3396
3569
|
void error;
|
|
3397
3570
|
}
|
|
3398
|
-
if (this._cropRect)
|
|
3399
|
-
try {
|
|
3400
|
-
this.canvas.remove(this._cropRect);
|
|
3401
|
-
} catch (error) {
|
|
3402
|
-
void error;
|
|
3403
|
-
}
|
|
3404
|
-
this._cropRect = null;
|
|
3405
|
-
}
|
|
3571
|
+
if (this._cropRect) this._removeCropRect();
|
|
3406
3572
|
if (this.containerElement && this._containerOriginalOverflow) {
|
|
3407
3573
|
try {
|
|
3408
3574
|
this._restoreContainerOverflowState();
|
|
@@ -3425,11 +3591,19 @@
|
|
|
3425
3591
|
this.canvasElement.style.display = this._canvasElementOriginalStyle.display;
|
|
3426
3592
|
this.canvasElement.style.width = this._canvasElementOriginalStyle.width;
|
|
3427
3593
|
this.canvasElement.style.height = this._canvasElementOriginalStyle.height;
|
|
3594
|
+
this.canvasElement.style.maxWidth = this._canvasElementOriginalStyle.maxWidth;
|
|
3428
3595
|
} catch (error) {
|
|
3429
3596
|
void error;
|
|
3430
3597
|
}
|
|
3431
3598
|
}
|
|
3432
3599
|
if (this.canvas) {
|
|
3600
|
+
try {
|
|
3601
|
+
this.canvas.getObjects().forEach((object) => {
|
|
3602
|
+
if (object && object.maskId) this._cleanupMaskEvents(object);
|
|
3603
|
+
});
|
|
3604
|
+
} catch (error) {
|
|
3605
|
+
void error;
|
|
3606
|
+
}
|
|
3433
3607
|
try {
|
|
3434
3608
|
this.canvas.dispose();
|
|
3435
3609
|
} catch (error) {
|
|
@@ -3526,7 +3700,7 @@
|
|
|
3526
3700
|
task.reject(error);
|
|
3527
3701
|
}
|
|
3528
3702
|
} finally {
|
|
3529
|
-
if (
|
|
3703
|
+
if (this.currentTask === task) this.currentTask = null;
|
|
3530
3704
|
}
|
|
3531
3705
|
}
|
|
3532
3706
|
} finally {
|