@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/src/image-editor.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file image-editor.js
|
|
3
3
|
* @module image-editor
|
|
4
|
-
* @version 1.
|
|
4
|
+
* @version 1.5.0
|
|
5
5
|
* @author Ben Situ
|
|
6
6
|
* @license MIT
|
|
7
7
|
* @description Lightweight canvas-based image editor with masking/transform/export support.
|
|
@@ -284,7 +284,7 @@ function ensureFabric() {
|
|
|
284
284
|
this._disposed = false;
|
|
285
285
|
this._initialized = false;
|
|
286
286
|
|
|
287
|
-
this.onImageLoaded = typeof options.onImageLoaded === 'function' ? options.onImageLoaded : null;
|
|
287
|
+
this.onImageLoaded = typeof this.options.onImageLoaded === 'function' ? this.options.onImageLoaded : null;
|
|
288
288
|
|
|
289
289
|
this.animationQueue = new AnimationQueue();
|
|
290
290
|
this.historyManager = new HistoryManager(this.maxHistorySize);
|
|
@@ -338,10 +338,12 @@ function ensureFabric() {
|
|
|
338
338
|
* Use this method to set up the editor UI before interacting with it.
|
|
339
339
|
*
|
|
340
340
|
* @param {Object} [idMap={}] - Optional mapping from logical element names to actual DOM element IDs.
|
|
341
|
-
* Supported keys include: canvas, canvasContainer,
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
341
|
+
* Supported keys include: canvas, canvasContainer, imagePlaceholder, scalePercentageInput,
|
|
342
|
+
* rotateLeftDegreesInput, rotateRightDegreesInput, rotateLeftButton, rotateRightButton,
|
|
343
|
+
* createMaskButton, removeSelectedMaskButton, removeAllMasksButton, mergeMasksButton,
|
|
344
|
+
* downloadImageButton, maskList, zoomInButton, zoomOutButton, resetImageTransformButton,
|
|
345
|
+
* undoButton, redoButton, imageInput, uploadArea, enterCropModeButton, applyCropButton,
|
|
346
|
+
* and cancelCropButton. Deprecated 1.x names remain supported as aliases.
|
|
345
347
|
*
|
|
346
348
|
* @returns {void}
|
|
347
349
|
*
|
|
@@ -350,7 +352,7 @@ function ensureFabric() {
|
|
|
350
352
|
* @example
|
|
351
353
|
* editor.init({
|
|
352
354
|
* canvas: 'myFabricCanvasId',
|
|
353
|
-
*
|
|
355
|
+
* downloadImageButton: 'myDownloadButtonId'
|
|
354
356
|
* });
|
|
355
357
|
*/
|
|
356
358
|
init(idMap = {}) {
|
|
@@ -369,34 +371,54 @@ function ensureFabric() {
|
|
|
369
371
|
this._containerOriginalOverflow = null;
|
|
370
372
|
this._lastContainerViewportSize = null;
|
|
371
373
|
this._canvasElementOriginalStyle = null;
|
|
374
|
+
this._deprecatedElementKeyWarnings = new Set();
|
|
372
375
|
|
|
373
376
|
const defaults = {
|
|
374
377
|
canvas: 'fabricCanvas',
|
|
375
378
|
canvasContainer: null, // Pass an ID here if you have a scrollable viewport container
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
379
|
+
imagePlaceholder: 'imagePlaceholder',
|
|
380
|
+
imgPlaceholder: null,
|
|
381
|
+
scalePercentageInput: 'scalePercentageInput',
|
|
382
|
+
scaleRate: null,
|
|
383
|
+
rotateLeftDegreesInput: 'rotateLeftDegreesInput',
|
|
384
|
+
rotationLeftInput: null,
|
|
385
|
+
rotateRightDegreesInput: 'rotateRightDegreesInput',
|
|
386
|
+
rotationRightInput: null,
|
|
387
|
+
rotateLeftButton: 'rotateLeftButton',
|
|
388
|
+
rotateLeftBtn: null,
|
|
389
|
+
rotateRightButton: 'rotateRightButton',
|
|
390
|
+
rotateRightBtn: null,
|
|
391
|
+
createMaskButton: 'createMaskButton',
|
|
392
|
+
addMaskBtn: null,
|
|
393
|
+
removeSelectedMaskButton: 'removeSelectedMaskButton',
|
|
394
|
+
removeMaskBtn: null,
|
|
395
|
+
removeAllMasksButton: 'removeAllMasksButton',
|
|
396
|
+
removeAllMasksBtn: null,
|
|
397
|
+
mergeMasksButton: 'mergeMasksButton',
|
|
398
|
+
mergeBtn: null,
|
|
399
|
+
downloadImageButton: 'downloadImageButton',
|
|
400
|
+
downloadBtn: null,
|
|
387
401
|
maskList: 'maskList',
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
402
|
+
zoomInButton: 'zoomInButton',
|
|
403
|
+
zoomInBtn: null,
|
|
404
|
+
zoomOutButton: 'zoomOutButton',
|
|
405
|
+
zoomOutBtn: null,
|
|
406
|
+
resetImageTransformButton: 'resetImageTransformButton',
|
|
407
|
+
resetBtn: null,
|
|
408
|
+
undoButton: 'undoButton',
|
|
409
|
+
undoBtn: null,
|
|
410
|
+
redoButton: 'redoButton',
|
|
411
|
+
redoBtn: null,
|
|
393
412
|
imageInput: 'imageInput',
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
413
|
+
enterCropModeButton: 'enterCropModeButton',
|
|
414
|
+
cropBtn: null,
|
|
415
|
+
applyCropButton: 'applyCropButton',
|
|
416
|
+
applyCropBtn: null,
|
|
417
|
+
cancelCropButton: 'cancelCropButton',
|
|
418
|
+
cancelCropBtn: null
|
|
397
419
|
};
|
|
398
420
|
|
|
399
|
-
this.elements = {
|
|
421
|
+
this.elements = this._resolveElementIdMap(idMap || {}, defaults);
|
|
400
422
|
this._elementCache = {};
|
|
401
423
|
|
|
402
424
|
this._initCanvas();
|
|
@@ -413,6 +435,73 @@ function ensureFabric() {
|
|
|
413
435
|
}
|
|
414
436
|
}
|
|
415
437
|
|
|
438
|
+
_resolveElementIdMap(idMap, defaults) {
|
|
439
|
+
const resolved = { ...defaults, ...idMap };
|
|
440
|
+
|
|
441
|
+
this._resolveElementAliases(resolved, idMap, defaults, 'imagePlaceholder', ['imgPlaceholder']);
|
|
442
|
+
this._resolveElementAliases(resolved, idMap, defaults, 'scalePercentageInput', ['scaleRate']);
|
|
443
|
+
this._resolveElementAliases(resolved, idMap, defaults, 'rotateLeftDegreesInput', ['rotationLeftInput']);
|
|
444
|
+
this._resolveElementAliases(resolved, idMap, defaults, 'rotateRightDegreesInput', ['rotationRightInput']);
|
|
445
|
+
this._resolveElementAlias(resolved, idMap, defaults, 'rotateLeftButton', 'rotateLeftBtn');
|
|
446
|
+
this._resolveElementAlias(resolved, idMap, defaults, 'rotateRightButton', 'rotateRightBtn');
|
|
447
|
+
this._resolveElementAlias(resolved, idMap, defaults, 'createMaskButton', 'addMaskBtn');
|
|
448
|
+
this._resolveElementAliases(resolved, idMap, defaults, 'removeSelectedMaskButton', ['removeMaskBtn']);
|
|
449
|
+
this._resolveElementAlias(resolved, idMap, defaults, 'removeAllMasksButton', 'removeAllMasksBtn');
|
|
450
|
+
this._resolveElementAlias(resolved, idMap, defaults, 'mergeMasksButton', 'mergeBtn');
|
|
451
|
+
this._resolveElementAliases(resolved, idMap, defaults, 'downloadImageButton', ['downloadBtn']);
|
|
452
|
+
this._resolveElementAlias(resolved, idMap, defaults, 'zoomInButton', 'zoomInBtn');
|
|
453
|
+
this._resolveElementAlias(resolved, idMap, defaults, 'zoomOutButton', 'zoomOutBtn');
|
|
454
|
+
this._resolveElementAlias(resolved, idMap, defaults, 'resetImageTransformButton', 'resetBtn');
|
|
455
|
+
this._resolveElementAlias(resolved, idMap, defaults, 'undoButton', 'undoBtn');
|
|
456
|
+
this._resolveElementAlias(resolved, idMap, defaults, 'redoButton', 'redoBtn');
|
|
457
|
+
this._resolveElementAliases(resolved, idMap, defaults, 'enterCropModeButton', ['cropBtn']);
|
|
458
|
+
this._resolveElementAlias(resolved, idMap, defaults, 'applyCropButton', 'applyCropBtn');
|
|
459
|
+
this._resolveElementAlias(resolved, idMap, defaults, 'cancelCropButton', 'cancelCropBtn');
|
|
460
|
+
|
|
461
|
+
return resolved;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
_resolveElementAlias(resolved, idMap, defaults, canonicalKey, deprecatedKey) {
|
|
465
|
+
this._resolveElementAliases(resolved, idMap, defaults, canonicalKey, [deprecatedKey]);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
_resolveElementAliases(resolved, idMap, defaults, canonicalKey, deprecatedKeys) {
|
|
469
|
+
const hasCanonicalKey = Object.prototype.hasOwnProperty.call(idMap, canonicalKey);
|
|
470
|
+
|
|
471
|
+
if (hasCanonicalKey) {
|
|
472
|
+
resolved[canonicalKey] = idMap[canonicalKey];
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
let deprecatedValue;
|
|
477
|
+
let hasDeprecatedValue = false;
|
|
478
|
+
for (const deprecatedKey of deprecatedKeys) {
|
|
479
|
+
if (Object.prototype.hasOwnProperty.call(idMap, deprecatedKey)) {
|
|
480
|
+
if (!hasDeprecatedValue) {
|
|
481
|
+
deprecatedValue = idMap[deprecatedKey];
|
|
482
|
+
hasDeprecatedValue = true;
|
|
483
|
+
}
|
|
484
|
+
this._warnDeprecatedElementIdKey(deprecatedKey, canonicalKey);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (hasDeprecatedValue) {
|
|
489
|
+
resolved[canonicalKey] = deprecatedValue;
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
resolved[canonicalKey] = defaults[canonicalKey];
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
_warnDeprecatedElementIdKey(deprecatedKey, canonicalKey) {
|
|
497
|
+
if (!this._deprecatedElementKeyWarnings) this._deprecatedElementKeyWarnings = new Set();
|
|
498
|
+
if (this._deprecatedElementKeyWarnings.has(deprecatedKey)) return;
|
|
499
|
+
this._deprecatedElementKeyWarnings.add(deprecatedKey);
|
|
500
|
+
this._reportWarning(
|
|
501
|
+
`ElementIdMap.${deprecatedKey} is deprecated. Use ${canonicalKey} instead. This alias will be removed in v2.0.0.`
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
|
|
416
505
|
_reportError(message, error = null) {
|
|
417
506
|
const handler = this.options && this.options.onError;
|
|
418
507
|
if (typeof handler !== 'function') return;
|
|
@@ -435,6 +524,12 @@ function ensureFabric() {
|
|
|
435
524
|
}
|
|
436
525
|
}
|
|
437
526
|
|
|
527
|
+
_notifyImageLoaded() {
|
|
528
|
+
const optionsCallback = this.options && this.options.onImageLoaded;
|
|
529
|
+
const callback = typeof optionsCallback === 'function' ? optionsCallback : this.onImageLoaded;
|
|
530
|
+
if (typeof callback === 'function') callback();
|
|
531
|
+
}
|
|
532
|
+
|
|
438
533
|
/**
|
|
439
534
|
* Initializes the Fabric canvas, viewport elements, and selection event handlers.
|
|
440
535
|
*
|
|
@@ -460,7 +555,7 @@ function ensureFabric() {
|
|
|
460
555
|
this.containerElement = canvasElement.parentElement;
|
|
461
556
|
}
|
|
462
557
|
|
|
463
|
-
this.placeholderElement = this._getElement('
|
|
558
|
+
this.placeholderElement = this._getElement('imagePlaceholder') || null;
|
|
464
559
|
|
|
465
560
|
// Prefer a measured container size when it is available.
|
|
466
561
|
let initialWidth = this.options.canvasWidth;
|
|
@@ -628,23 +723,23 @@ function ensureFabric() {
|
|
|
628
723
|
}
|
|
629
724
|
});
|
|
630
725
|
// Zoom & reset
|
|
631
|
-
this._bindIfExists('
|
|
632
|
-
this._bindIfExists('
|
|
633
|
-
this._bindIfExists('
|
|
726
|
+
this._bindIfExists('zoomInButton', 'click', () => this.scaleImage(this.currentScale + this.options.scaleStep).catch(error => this._reportError('scaleImage failed', error)));
|
|
727
|
+
this._bindIfExists('zoomOutButton', 'click', () => this.scaleImage(this.currentScale - this.options.scaleStep).catch(error => this._reportError('scaleImage failed', error)));
|
|
728
|
+
this._bindIfExists('resetImageTransformButton', 'click', () => { this.resetImageTransform().catch(error => this._reportError('resetImageTransform failed', error)); });
|
|
634
729
|
// Mask management
|
|
635
|
-
this._bindIfExists('
|
|
636
|
-
this._bindIfExists('
|
|
637
|
-
this._bindIfExists('
|
|
730
|
+
this._bindIfExists('createMaskButton', 'click', () => this.createMask());
|
|
731
|
+
this._bindIfExists('removeSelectedMaskButton', 'click', () => this.removeSelectedMask());
|
|
732
|
+
this._bindIfExists('removeAllMasksButton', 'click', () => this.removeAllMasks());
|
|
638
733
|
// Merge + download
|
|
639
|
-
this._bindIfExists('
|
|
640
|
-
this._bindIfExists('
|
|
734
|
+
this._bindIfExists('mergeMasksButton', 'click', () => this.mergeMasks().catch(error => this._reportError('merge error', error)));
|
|
735
|
+
this._bindIfExists('downloadImageButton', 'click', () => this.downloadImage());
|
|
641
736
|
// Undo + Redo
|
|
642
|
-
this._bindIfExists('
|
|
643
|
-
this._bindIfExists('
|
|
737
|
+
this._bindIfExists('undoButton', 'click', () => this.undo().catch(error => this._reportError('undo failed', error)));
|
|
738
|
+
this._bindIfExists('redoButton', 'click', () => this.redo().catch(error => this._reportError('redo failed', error)));
|
|
644
739
|
|
|
645
740
|
// Rotation buttons (step can be overridden by two input fields)
|
|
646
|
-
this._bindIfExists('
|
|
647
|
-
const rotationInputElement = this._getElement('
|
|
741
|
+
this._bindIfExists('rotateLeftButton', 'click', () => {
|
|
742
|
+
const rotationInputElement = this._getElement('rotateLeftDegreesInput');
|
|
648
743
|
let step = this.options.rotationStep;
|
|
649
744
|
if (rotationInputElement) {
|
|
650
745
|
const parsedStep = parseFloat(rotationInputElement.value);
|
|
@@ -652,8 +747,8 @@ function ensureFabric() {
|
|
|
652
747
|
}
|
|
653
748
|
this.rotateImage(this.currentRotation - step).catch(error => this._reportError('rotateImage failed', error));
|
|
654
749
|
});
|
|
655
|
-
this._bindIfExists('
|
|
656
|
-
const rotationInputElement = this._getElement('
|
|
750
|
+
this._bindIfExists('rotateRightButton', 'click', () => {
|
|
751
|
+
const rotationInputElement = this._getElement('rotateRightDegreesInput');
|
|
657
752
|
let step = this.options.rotationStep;
|
|
658
753
|
if (rotationInputElement) {
|
|
659
754
|
const parsedStep = parseFloat(rotationInputElement.value);
|
|
@@ -663,9 +758,9 @@ function ensureFabric() {
|
|
|
663
758
|
});
|
|
664
759
|
|
|
665
760
|
// Crop bindings (optional: bound only if element IDs exist in elements)
|
|
666
|
-
this._bindIfExists('
|
|
667
|
-
this._bindIfExists('
|
|
668
|
-
this._bindIfExists('
|
|
761
|
+
this._bindIfExists('enterCropModeButton', 'click', () => this.enterCropMode());
|
|
762
|
+
this._bindIfExists('applyCropButton', 'click', () => { this.applyCrop().catch(error => this._reportError('applyCrop failed', error)); });
|
|
763
|
+
this._bindIfExists('cancelCropButton', 'click', () => this.cancelCrop());
|
|
669
764
|
this._bindIfExists('maskList', 'click', (event) => this._handleMaskListClick(event));
|
|
670
765
|
}
|
|
671
766
|
|
|
@@ -860,9 +955,7 @@ function ensureFabric() {
|
|
|
860
955
|
this.canvas.renderAll();
|
|
861
956
|
this._lastSnapshot = this._captureCanvasStateOrThrow('loadImage');
|
|
862
957
|
|
|
863
|
-
|
|
864
|
-
this.onImageLoaded();
|
|
865
|
-
}
|
|
958
|
+
this._notifyImageLoaded();
|
|
866
959
|
} catch (error) {
|
|
867
960
|
await this._rollbackLoadImageTransaction(transaction);
|
|
868
961
|
throw error;
|
|
@@ -929,7 +1022,7 @@ function ensureFabric() {
|
|
|
929
1022
|
};
|
|
930
1023
|
timerId = setTimeout(() => {
|
|
931
1024
|
settle(() => reject(new Error('Image load timed out')));
|
|
932
|
-
try { imageElement.src = ''; } catch (error) {
|
|
1025
|
+
try { imageElement.src = ''; } catch (error) { this._reportWarning('Image timeout cleanup failed', error); }
|
|
933
1026
|
}, safeTimeoutMs);
|
|
934
1027
|
imageElement.onload = () => settle(() => resolve(imageElement));
|
|
935
1028
|
imageElement.onerror = (error) => settle(() => reject(error));
|
|
@@ -1002,6 +1095,7 @@ function ensureFabric() {
|
|
|
1002
1095
|
async _rollbackLoadImageTransaction(transaction) {
|
|
1003
1096
|
if (!transaction || !this.canvas || this._disposed) return;
|
|
1004
1097
|
let didRestoreCanvasState = false;
|
|
1098
|
+
let didFailCanvasRestore = false;
|
|
1005
1099
|
try {
|
|
1006
1100
|
if (transaction.canvasState) {
|
|
1007
1101
|
await this.loadFromState(transaction.canvasState);
|
|
@@ -1009,23 +1103,28 @@ function ensureFabric() {
|
|
|
1009
1103
|
}
|
|
1010
1104
|
} catch (error) {
|
|
1011
1105
|
this._lastMask = null;
|
|
1106
|
+
didFailCanvasRestore = true;
|
|
1012
1107
|
this._reportError('loadImage rollback failed', error);
|
|
1013
1108
|
}
|
|
1014
1109
|
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
this.currentRotation = transaction.currentRotation;
|
|
1018
|
-
this.maskCounter = transaction.maskCounter;
|
|
1019
|
-
this.isImageLoadedToCanvas = transaction.isImageLoadedToCanvas;
|
|
1020
|
-
this._lastSnapshot = transaction.lastSnapshot;
|
|
1021
|
-
if (didRestoreCanvasState) {
|
|
1022
|
-
this._restoreLastMaskReference(transaction.lastMask);
|
|
1110
|
+
if (didFailCanvasRestore) {
|
|
1111
|
+
this._reconcileEditorStateFromCanvas();
|
|
1023
1112
|
} else {
|
|
1024
|
-
this.
|
|
1113
|
+
this.baseImageScale = transaction.baseImageScale;
|
|
1114
|
+
this.currentScale = transaction.currentScale;
|
|
1115
|
+
this.currentRotation = transaction.currentRotation;
|
|
1116
|
+
this.maskCounter = transaction.maskCounter;
|
|
1117
|
+
this.isImageLoadedToCanvas = transaction.isImageLoadedToCanvas;
|
|
1118
|
+
this._lastSnapshot = transaction.lastSnapshot;
|
|
1119
|
+
if (didRestoreCanvasState) {
|
|
1120
|
+
this._restoreLastMaskReference(transaction.lastMask);
|
|
1121
|
+
} else {
|
|
1122
|
+
this._lastMask = null;
|
|
1123
|
+
}
|
|
1124
|
+
this._lastMaskInitialLeft = transaction.lastMaskInitialLeft;
|
|
1125
|
+
this._lastMaskInitialTop = transaction.lastMaskInitialTop;
|
|
1126
|
+
this._lastMaskInitialWidth = transaction.lastMaskInitialWidth;
|
|
1025
1127
|
}
|
|
1026
|
-
this._lastMaskInitialLeft = transaction.lastMaskInitialLeft;
|
|
1027
|
-
this._lastMaskInitialTop = transaction.lastMaskInitialTop;
|
|
1028
|
-
this._lastMaskInitialWidth = transaction.lastMaskInitialWidth;
|
|
1029
1128
|
this._restoreElementVisibility(this.placeholderElement, transaction.placeholderVisibility);
|
|
1030
1129
|
this._restoreElementVisibility(this._getCanvasVisibilityElement(), transaction.canvasVisibility);
|
|
1031
1130
|
if (this.containerElement) {
|
|
@@ -1039,6 +1138,49 @@ function ensureFabric() {
|
|
|
1039
1138
|
if (this.canvas) this.canvas.renderAll();
|
|
1040
1139
|
}
|
|
1041
1140
|
|
|
1141
|
+
_reconcileEditorStateFromCanvas() {
|
|
1142
|
+
if (!this.canvas) {
|
|
1143
|
+
this.originalImage = null;
|
|
1144
|
+
this.baseImageScale = 1;
|
|
1145
|
+
this.currentScale = 1;
|
|
1146
|
+
this.currentRotation = 0;
|
|
1147
|
+
this.maskCounter = 0;
|
|
1148
|
+
this.isImageLoadedToCanvas = false;
|
|
1149
|
+
this._lastSnapshot = null;
|
|
1150
|
+
this._clearMaskPlacementMemory();
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
const canvasObjects = this.canvas.getObjects();
|
|
1155
|
+
this.originalImage = canvasObjects.find(object => object.type === 'image' && !object.maskId) || null;
|
|
1156
|
+
if (this.originalImage) {
|
|
1157
|
+
const imageScale = Number(this.originalImage.scaleX) || 1;
|
|
1158
|
+
this.baseImageScale = imageScale;
|
|
1159
|
+
this.currentScale = 1;
|
|
1160
|
+
this.currentRotation = Number(this.originalImage.angle) || 0;
|
|
1161
|
+
} else {
|
|
1162
|
+
this.baseImageScale = 1;
|
|
1163
|
+
this.currentScale = 1;
|
|
1164
|
+
this.currentRotation = 0;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
const masks = canvasObjects.filter(object => object.maskId);
|
|
1168
|
+
this.maskCounter = masks.reduce((max, mask) => Math.max(max, Number(mask.maskId) || 0), 0);
|
|
1169
|
+
this._lastMask = masks[masks.length - 1] || null;
|
|
1170
|
+
if (!this._lastMask) {
|
|
1171
|
+
this._lastMaskInitialLeft = null;
|
|
1172
|
+
this._lastMaskInitialTop = null;
|
|
1173
|
+
this._lastMaskInitialWidth = null;
|
|
1174
|
+
}
|
|
1175
|
+
this.isImageLoadedToCanvas = !!this.originalImage;
|
|
1176
|
+
try {
|
|
1177
|
+
this._lastSnapshot = this._serializeCanvasState();
|
|
1178
|
+
} catch (error) {
|
|
1179
|
+
this._lastSnapshot = null;
|
|
1180
|
+
this._reportWarning('loadImage rollback: failed to reconcile canvas snapshot', error);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1042
1184
|
_restoreLastMaskReference(previousLastMask) {
|
|
1043
1185
|
if (!this.canvas) {
|
|
1044
1186
|
this._lastMask = null;
|
|
@@ -1116,6 +1258,7 @@ function ensureFabric() {
|
|
|
1116
1258
|
* @private
|
|
1117
1259
|
*/
|
|
1118
1260
|
_setCanvasSizeInt(width, height) {
|
|
1261
|
+
if (!this.canvas) return;
|
|
1119
1262
|
const integerWidth = Math.max(1, Math.round(Number(width) || 1));
|
|
1120
1263
|
const integerHeight = Math.max(1, Math.round(Number(height) || 1));
|
|
1121
1264
|
// Set fabric internal and also style attributes to keep DOM consistent
|
|
@@ -1427,7 +1570,7 @@ function ensureFabric() {
|
|
|
1427
1570
|
/**
|
|
1428
1571
|
* Captures editor-owned runtime state that Fabric does not include in canvas JSON.
|
|
1429
1572
|
*
|
|
1430
|
-
* @returns {{version:number, baseImageScale:number, currentScale:number, currentRotation:number, maskCounter:number}} Serializable editor metadata.
|
|
1573
|
+
* @returns {{version:number, baseImageScale:number, currentScale:number, currentRotation:number, maskCounter:number, canvasWidth:number, canvasHeight:number}} Serializable editor metadata.
|
|
1431
1574
|
* @private
|
|
1432
1575
|
*/
|
|
1433
1576
|
_serializeEditorMetadata() {
|
|
@@ -1435,13 +1578,17 @@ function ensureFabric() {
|
|
|
1435
1578
|
const currentScale = Number(this.currentScale);
|
|
1436
1579
|
const currentRotation = Number(this.currentRotation);
|
|
1437
1580
|
const maskCounter = Number(this.maskCounter);
|
|
1581
|
+
const canvasWidth = this.canvas ? Number(this.canvas.getWidth()) : NaN;
|
|
1582
|
+
const canvasHeight = this.canvas ? Number(this.canvas.getHeight()) : NaN;
|
|
1438
1583
|
|
|
1439
1584
|
return {
|
|
1440
1585
|
version: 1,
|
|
1441
1586
|
baseImageScale: Number.isFinite(baseImageScale) && baseImageScale > 0 ? baseImageScale : 1,
|
|
1442
1587
|
currentScale: Number.isFinite(currentScale) && currentScale > 0 ? currentScale : 1,
|
|
1443
1588
|
currentRotation: Number.isFinite(currentRotation) ? currentRotation : 0,
|
|
1444
|
-
maskCounter: Number.isFinite(maskCounter) && maskCounter > 0 ? Math.floor(maskCounter) : 0
|
|
1589
|
+
maskCounter: Number.isFinite(maskCounter) && maskCounter > 0 ? Math.floor(maskCounter) : 0,
|
|
1590
|
+
canvasWidth: Number.isFinite(canvasWidth) && canvasWidth > 0 ? Math.round(canvasWidth) : 1,
|
|
1591
|
+
canvasHeight: Number.isFinite(canvasHeight) && canvasHeight > 0 ? Math.round(canvasHeight) : 1
|
|
1445
1592
|
};
|
|
1446
1593
|
}
|
|
1447
1594
|
|
|
@@ -1813,19 +1960,14 @@ function ensureFabric() {
|
|
|
1813
1960
|
requiredWidth = Math.max(requiredWidth, Math.ceil(boundingRect.left + boundingRect.width + padding));
|
|
1814
1961
|
requiredHeight = Math.max(requiredHeight, Math.ceil(boundingRect.top + boundingRect.height + padding));
|
|
1815
1962
|
});
|
|
1816
|
-
const shouldUseScrollSafeViewport = this.options.fitImageToCanvas || this.options.coverImageToCanvas;
|
|
1817
|
-
|
|
1818
1963
|
let minWidth = 0;
|
|
1819
1964
|
let minHeight = 0;
|
|
1820
|
-
if (
|
|
1965
|
+
if (this.containerElement) {
|
|
1821
1966
|
const viewport = this._getContainerViewportSize();
|
|
1822
1967
|
const safetyMargin = this._getScrollSafetyMargin();
|
|
1823
1968
|
|
|
1824
1969
|
minWidth = Math.max(1, viewport.width - safetyMargin);
|
|
1825
1970
|
minHeight = Math.max(1, viewport.height - safetyMargin);
|
|
1826
|
-
} else if (this.containerElement) {
|
|
1827
|
-
minWidth = Math.floor(this.containerElement.clientWidth || 0);
|
|
1828
|
-
minHeight = Math.floor(this.containerElement.clientHeight || 0);
|
|
1829
1971
|
}
|
|
1830
1972
|
const newWidth = Math.max(currentWidth, minWidth, requiredWidth);
|
|
1831
1973
|
const newHeight = Math.max(currentHeight, minHeight, requiredHeight);
|
|
@@ -1904,9 +2046,16 @@ function ensureFabric() {
|
|
|
1904
2046
|
if (this._disposed || !this.canvas) throw new Error(`${operationName} cannot run after the editor has been disposed`);
|
|
1905
2047
|
}
|
|
1906
2048
|
|
|
2049
|
+
_isCropModeAllowedOperation(operationName) {
|
|
2050
|
+
return operationName === 'applyCrop' || operationName === 'cancelCrop';
|
|
2051
|
+
}
|
|
2052
|
+
|
|
1907
2053
|
_assertIdleForOperation(operationName, options = {}) {
|
|
1908
2054
|
this._assertEditorAvailable(operationName);
|
|
1909
2055
|
const isOwnInternalOperation = this._isOwnInternalOperation(options);
|
|
2056
|
+
if (this._cropMode && !this._isCropModeAllowedOperation(operationName) && !isOwnInternalOperation) {
|
|
2057
|
+
throw new Error(`${operationName} cannot run while crop mode is active`);
|
|
2058
|
+
}
|
|
1910
2059
|
if (this.isAnimating || (this.animationQueue && this.animationQueue.isBusy())) {
|
|
1911
2060
|
throw new Error(`${operationName} cannot run while an animation is running`);
|
|
1912
2061
|
}
|
|
@@ -1920,10 +2069,14 @@ function ensureFabric() {
|
|
|
1920
2069
|
|
|
1921
2070
|
_assertCanQueueAnimation(operationName, options = {}) {
|
|
1922
2071
|
this._assertEditorAvailable(operationName);
|
|
1923
|
-
|
|
2072
|
+
const isOwnInternalOperation = this._isOwnInternalOperation(options);
|
|
2073
|
+
if (this._cropMode && !this._isCropModeAllowedOperation(operationName) && !isOwnInternalOperation) {
|
|
2074
|
+
throw new Error(`${operationName} cannot run while crop mode is active`);
|
|
2075
|
+
}
|
|
2076
|
+
if (this._isLoading && !isOwnInternalOperation) {
|
|
1924
2077
|
throw new Error(`${operationName} cannot run while an image is loading`);
|
|
1925
2078
|
}
|
|
1926
|
-
if (this._activeOperationToken && !
|
|
2079
|
+
if (this._activeOperationToken && !isOwnInternalOperation) {
|
|
1927
2080
|
throw new Error(`${operationName} cannot run while ${this._activeOperationName || 'another operation'} is running`);
|
|
1928
2081
|
}
|
|
1929
2082
|
}
|
|
@@ -2130,10 +2283,19 @@ function ensureFabric() {
|
|
|
2130
2283
|
|
|
2131
2284
|
return this.animationQueue.add(async () => {
|
|
2132
2285
|
const before = this._lastSnapshot || this._captureCanvasStateOrThrow('resetImageTransform');
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2286
|
+
try {
|
|
2287
|
+
await this._scaleImageImpl(1, { saveHistory: false });
|
|
2288
|
+
await this._rotateImageImpl(0, { saveHistory: false });
|
|
2289
|
+
const after = this._captureCanvasStateOrThrow('resetImageTransform');
|
|
2290
|
+
this._pushStateTransition(before, after);
|
|
2291
|
+
} catch (error) {
|
|
2292
|
+
try {
|
|
2293
|
+
await this.loadFromState(before);
|
|
2294
|
+
} catch (restoreError) {
|
|
2295
|
+
this._reportError('resetImageTransform rollback failed', restoreError);
|
|
2296
|
+
}
|
|
2297
|
+
throw error;
|
|
2298
|
+
}
|
|
2137
2299
|
}).finally(() => {
|
|
2138
2300
|
if (!this._disposed && this.canvas) this._updateUI();
|
|
2139
2301
|
}).catch(error => {
|
|
@@ -2177,6 +2339,13 @@ function ensureFabric() {
|
|
|
2177
2339
|
? JSON.parse(serializedState)
|
|
2178
2340
|
: serializedState;
|
|
2179
2341
|
const editorMetadata = state && state.imageEditorMetadata ? state.imageEditorMetadata : null;
|
|
2342
|
+
const restoredCanvasWidth = Number(editorMetadata && editorMetadata.canvasWidth);
|
|
2343
|
+
const restoredCanvasHeight = Number(editorMetadata && editorMetadata.canvasHeight);
|
|
2344
|
+
const hasRestoredCanvasSize =
|
|
2345
|
+
Number.isFinite(restoredCanvasWidth) &&
|
|
2346
|
+
restoredCanvasWidth > 0 &&
|
|
2347
|
+
Number.isFinite(restoredCanvasHeight) &&
|
|
2348
|
+
restoredCanvasHeight > 0;
|
|
2180
2349
|
if (
|
|
2181
2350
|
editorMetadata &&
|
|
2182
2351
|
Object.prototype.hasOwnProperty.call(editorMetadata, 'version') &&
|
|
@@ -2185,7 +2354,7 @@ function ensureFabric() {
|
|
|
2185
2354
|
this._reportWarning(`loadFromState: unsupported editor metadata version ${editorMetadata.version}`);
|
|
2186
2355
|
}
|
|
2187
2356
|
|
|
2188
|
-
|
|
2357
|
+
const finishLoad = async () => {
|
|
2189
2358
|
try {
|
|
2190
2359
|
if (this._disposed || !this.canvas) {
|
|
2191
2360
|
reject(new Error('Editor was disposed while loading state'));
|
|
@@ -2228,6 +2397,12 @@ function ensureFabric() {
|
|
|
2228
2397
|
this.currentRotation = 0;
|
|
2229
2398
|
}
|
|
2230
2399
|
|
|
2400
|
+
if (hasRestoredCanvasSize) {
|
|
2401
|
+
this._setCanvasSizeInt(restoredCanvasWidth, restoredCanvasHeight);
|
|
2402
|
+
} else if (this.originalImage && this._shouldResizeCanvasToContentBounds()) {
|
|
2403
|
+
this._updateCanvasSizeToImageBounds();
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2231
2406
|
const masks = canvasObjects.filter(object => object.maskId);
|
|
2232
2407
|
masks.forEach(mask => {
|
|
2233
2408
|
this._restoreMaskControls(mask);
|
|
@@ -2259,7 +2434,9 @@ function ensureFabric() {
|
|
|
2259
2434
|
this._reportError('loadFromState() failed', callbackError);
|
|
2260
2435
|
reject(callbackError);
|
|
2261
2436
|
}
|
|
2262
|
-
}
|
|
2437
|
+
};
|
|
2438
|
+
|
|
2439
|
+
this.canvas.loadFromJSON(state, () => { void finishLoad(); });
|
|
2263
2440
|
|
|
2264
2441
|
} catch (error) {
|
|
2265
2442
|
this._reportError('loadFromState() failed', error);
|
|
@@ -2420,12 +2597,7 @@ function ensureFabric() {
|
|
|
2420
2597
|
|
|
2421
2598
|
_rebindMaskEvents(mask) {
|
|
2422
2599
|
if (!mask) return;
|
|
2423
|
-
|
|
2424
|
-
try {
|
|
2425
|
-
mask.off('mouseover', mask.__imageEditorMaskHandlers.mouseover);
|
|
2426
|
-
mask.off('mouseout', mask.__imageEditorMaskHandlers.mouseout);
|
|
2427
|
-
} catch (error) { void error; }
|
|
2428
|
-
}
|
|
2600
|
+
this._cleanupMaskEvents(mask);
|
|
2429
2601
|
|
|
2430
2602
|
const metadata = {};
|
|
2431
2603
|
if (!Number.isFinite(Number(mask.originalAlpha))) {
|
|
@@ -2456,6 +2628,19 @@ function ensureFabric() {
|
|
|
2456
2628
|
mask.__imageEditorMaskHandlers = { mouseover, mouseout };
|
|
2457
2629
|
}
|
|
2458
2630
|
|
|
2631
|
+
_cleanupMaskEvents(mask) {
|
|
2632
|
+
if (!mask || !mask.__imageEditorMaskHandlers) return;
|
|
2633
|
+
try {
|
|
2634
|
+
if (typeof mask.off === 'function') {
|
|
2635
|
+
mask.off('mouseover', mask.__imageEditorMaskHandlers.mouseover);
|
|
2636
|
+
mask.off('mouseout', mask.__imageEditorMaskHandlers.mouseout);
|
|
2637
|
+
}
|
|
2638
|
+
} catch (error) {
|
|
2639
|
+
this._reportWarning('Mask event cleanup failed', error);
|
|
2640
|
+
}
|
|
2641
|
+
try { delete mask.__imageEditorMaskHandlers; } catch (error) { this._reportWarning('Mask event metadata cleanup failed', error); }
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2459
2644
|
/**
|
|
2460
2645
|
* Creates a mask and adds it to the canvas.
|
|
2461
2646
|
*
|
|
@@ -2686,6 +2871,7 @@ function ensureFabric() {
|
|
|
2686
2871
|
this.canvas.discardActiveObject();
|
|
2687
2872
|
selectedMasks.forEach(mask => {
|
|
2688
2873
|
this._removeLabelForMask(mask);
|
|
2874
|
+
this._cleanupMaskEvents(mask);
|
|
2689
2875
|
this.canvas.remove(mask);
|
|
2690
2876
|
});
|
|
2691
2877
|
|
|
@@ -2712,7 +2898,10 @@ function ensureFabric() {
|
|
|
2712
2898
|
const saveHistory = options.saveHistory !== false;
|
|
2713
2899
|
const masks = this.canvas.getObjects().filter(object => object.maskId);
|
|
2714
2900
|
masks.forEach(mask => this._removeLabelForMask(mask));
|
|
2715
|
-
masks.forEach(mask =>
|
|
2901
|
+
masks.forEach(mask => {
|
|
2902
|
+
this._cleanupMaskEvents(mask);
|
|
2903
|
+
this.canvas.remove(mask);
|
|
2904
|
+
});
|
|
2716
2905
|
this.canvas.discardActiveObject();
|
|
2717
2906
|
this._lastMask = null;
|
|
2718
2907
|
this._lastMaskInitialLeft = null;
|
|
@@ -2777,7 +2966,9 @@ function ensureFabric() {
|
|
|
2777
2966
|
if (backup.visible !== undefined) backup.label.set({ visible: backup.visible });
|
|
2778
2967
|
if (backup.labelInCanvas) this.canvas.bringToFront(backup.label);
|
|
2779
2968
|
this._syncMaskLabel(backup.mask);
|
|
2780
|
-
} catch (error) {
|
|
2969
|
+
} catch (error) {
|
|
2970
|
+
this._reportWarning('restoreMaskLabelBackups: failed to restore mask label', error);
|
|
2971
|
+
}
|
|
2781
2972
|
});
|
|
2782
2973
|
}
|
|
2783
2974
|
|
|
@@ -2919,7 +3110,6 @@ function ensureFabric() {
|
|
|
2919
3110
|
try {
|
|
2920
3111
|
if (canvasObjectSet.has(label)) {
|
|
2921
3112
|
this.canvas.remove(label);
|
|
2922
|
-
canvasObjectSet.delete(label);
|
|
2923
3113
|
}
|
|
2924
3114
|
} catch (error) { void error; }
|
|
2925
3115
|
});
|
|
@@ -3217,7 +3407,6 @@ function ensureFabric() {
|
|
|
3217
3407
|
const labelBackups = this._captureMaskLabelBackups(masks);
|
|
3218
3408
|
const activeObjectBackup = this._captureActiveObjectBackup();
|
|
3219
3409
|
|
|
3220
|
-
let finalBase64;
|
|
3221
3410
|
try {
|
|
3222
3411
|
// Labels are UI overlays and should not be part of the flattened export.
|
|
3223
3412
|
masks.forEach(mask => this._removeLabelForMask(mask));
|
|
@@ -3236,8 +3425,7 @@ function ensureFabric() {
|
|
|
3236
3425
|
const imageBounds = this.originalImage.getBoundingRect(true, true);
|
|
3237
3426
|
const exportRegion = this._getClampedCanvasRegion(imageBounds);
|
|
3238
3427
|
|
|
3239
|
-
|
|
3240
|
-
finalBase64 = await this._exportCanvasRegionToDataURL({
|
|
3428
|
+
return await this._exportCanvasRegionToDataURL({
|
|
3241
3429
|
...exportRegion,
|
|
3242
3430
|
multiplier,
|
|
3243
3431
|
quality,
|
|
@@ -3250,8 +3438,6 @@ function ensureFabric() {
|
|
|
3250
3438
|
this._restoreActiveObjectBackup(activeObjectBackup);
|
|
3251
3439
|
this.canvas.renderAll();
|
|
3252
3440
|
}
|
|
3253
|
-
|
|
3254
|
-
return finalBase64;
|
|
3255
3441
|
}
|
|
3256
3442
|
|
|
3257
3443
|
/**
|
|
@@ -3391,20 +3577,21 @@ function ensureFabric() {
|
|
|
3391
3577
|
}
|
|
3392
3578
|
|
|
3393
3579
|
_removeCropRect() {
|
|
3394
|
-
if (
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
targetHandlers.handlers.forEach(handlerRecord => {
|
|
3580
|
+
if (this._cropHandlers && this._cropHandlers.length) {
|
|
3581
|
+
this._cropHandlers.forEach(targetHandlers => {
|
|
3582
|
+
(targetHandlers.handlers || []).forEach(handlerRecord => {
|
|
3583
|
+
try {
|
|
3399
3584
|
if (targetHandlers.target && typeof targetHandlers.target.off === 'function') {
|
|
3400
3585
|
targetHandlers.target.off(handlerRecord.eventName, handlerRecord.handler);
|
|
3401
3586
|
}
|
|
3402
|
-
})
|
|
3587
|
+
} catch (error) {
|
|
3588
|
+
this._reportWarning('Crop handler cleanup failed', error);
|
|
3589
|
+
}
|
|
3403
3590
|
});
|
|
3404
|
-
}
|
|
3405
|
-
}
|
|
3591
|
+
});
|
|
3592
|
+
}
|
|
3406
3593
|
|
|
3407
|
-
try { if (this.canvas) this.canvas.remove(this._cropRect); } catch (error) { void error; }
|
|
3594
|
+
try { if (this.canvas && this._cropRect) this.canvas.remove(this._cropRect); } catch (error) { void error; }
|
|
3408
3595
|
this._cropRect = null;
|
|
3409
3596
|
this._cropHandlers = [];
|
|
3410
3597
|
}
|
|
@@ -3595,9 +3782,13 @@ function ensureFabric() {
|
|
|
3595
3782
|
try {
|
|
3596
3783
|
beforeJson = this._serializeCanvasState();
|
|
3597
3784
|
} catch (error) {
|
|
3598
|
-
this.
|
|
3785
|
+
this._reportError('applyCrop: failed to capture rollback state', error);
|
|
3599
3786
|
beforeJson = null;
|
|
3600
3787
|
}
|
|
3788
|
+
if (!beforeJson) {
|
|
3789
|
+
this.cancelCrop();
|
|
3790
|
+
return;
|
|
3791
|
+
}
|
|
3601
3792
|
|
|
3602
3793
|
const preservedMasks = [];
|
|
3603
3794
|
|
|
@@ -3613,6 +3804,7 @@ function ensureFabric() {
|
|
|
3613
3804
|
maskBounds.top < cropRegion.sourceY + cropRegion.sourceHeight &&
|
|
3614
3805
|
maskBounds.top + maskBounds.height > cropRegion.sourceY;
|
|
3615
3806
|
this._removeLabelForMask(mask);
|
|
3807
|
+
this._cleanupMaskEvents(mask);
|
|
3616
3808
|
this.canvas.remove(mask);
|
|
3617
3809
|
if (shouldPreserveMasks && intersectsCrop) {
|
|
3618
3810
|
this._translateObjectByCanvasOffset(mask, -cropRegion.sourceX, -cropRegion.sourceY);
|
|
@@ -3698,7 +3890,7 @@ function ensureFabric() {
|
|
|
3698
3890
|
* @private
|
|
3699
3891
|
*/
|
|
3700
3892
|
_updateInputs() {
|
|
3701
|
-
const scaleInputElement = this._getElement('
|
|
3893
|
+
const scaleInputElement = this._getElement('scalePercentageInput');
|
|
3702
3894
|
if (scaleInputElement) scaleInputElement.value = Math.round(this.currentScale * 100);
|
|
3703
3895
|
}
|
|
3704
3896
|
|
|
@@ -3725,7 +3917,7 @@ function ensureFabric() {
|
|
|
3725
3917
|
for (const key of Object.keys(this.elements || {})) {
|
|
3726
3918
|
const element = this._getElement(key);
|
|
3727
3919
|
if (!element) continue;
|
|
3728
|
-
if (key === 'applyCropBtn' || key === 'cancelCropBtn') {
|
|
3920
|
+
if (key === 'applyCropButton' || key === 'cancelCropButton' || key === 'applyCropBtn' || key === 'cancelCropBtn') {
|
|
3729
3921
|
this._setDisabled(key, false);
|
|
3730
3922
|
} else {
|
|
3731
3923
|
this._setDisabled(key, true);
|
|
@@ -3734,24 +3926,24 @@ function ensureFabric() {
|
|
|
3734
3926
|
return;
|
|
3735
3927
|
}
|
|
3736
3928
|
|
|
3737
|
-
this._setDisabled('
|
|
3738
|
-
this._setDisabled('
|
|
3739
|
-
this._setDisabled('
|
|
3740
|
-
this._setDisabled('
|
|
3741
|
-
this._setDisabled('
|
|
3742
|
-
this._setDisabled('
|
|
3743
|
-
this._setDisabled('
|
|
3744
|
-
this._setDisabled('
|
|
3745
|
-
this._setDisabled('
|
|
3746
|
-
this._setDisabled('
|
|
3747
|
-
this._setDisabled('
|
|
3748
|
-
this._setDisabled('
|
|
3749
|
-
this._setDisabled('
|
|
3750
|
-
this._setDisabled('
|
|
3751
|
-
this._setDisabled('
|
|
3752
|
-
this._setDisabled('
|
|
3753
|
-
this._setDisabled('
|
|
3754
|
-
this._setDisabled('
|
|
3929
|
+
this._setDisabled('zoomInButton', !hasImage || isBusy || this.currentScale >= this.options.maxScale);
|
|
3930
|
+
this._setDisabled('zoomOutButton', !hasImage || isBusy || this.currentScale <= this.options.minScale);
|
|
3931
|
+
this._setDisabled('rotateLeftButton', !hasImage || isBusy);
|
|
3932
|
+
this._setDisabled('rotateRightButton', !hasImage || isBusy);
|
|
3933
|
+
this._setDisabled('createMaskButton', !hasImage || isBusy);
|
|
3934
|
+
this._setDisabled('removeSelectedMaskButton', !hasSelectedMask || isBusy);
|
|
3935
|
+
this._setDisabled('removeAllMasksButton', !hasMasks || isBusy);
|
|
3936
|
+
this._setDisabled('mergeMasksButton', !hasImage || !hasMasks || isBusy);
|
|
3937
|
+
this._setDisabled('downloadImageButton', !hasImage || isBusy);
|
|
3938
|
+
this._setDisabled('resetImageTransformButton', !hasImage || isDefaultTransform || isBusy);
|
|
3939
|
+
this._setDisabled('undoButton', !hasImage || isBusy || !canUndo);
|
|
3940
|
+
this._setDisabled('redoButton', !hasImage || isBusy || !canRedo);
|
|
3941
|
+
this._setDisabled('enterCropModeButton', !hasImage || isBusy);
|
|
3942
|
+
this._setDisabled('applyCropButton', true);
|
|
3943
|
+
this._setDisabled('cancelCropButton', true);
|
|
3944
|
+
this._setDisabled('scalePercentageInput', !hasImage || isBusy);
|
|
3945
|
+
this._setDisabled('rotateLeftDegreesInput', !hasImage || isBusy);
|
|
3946
|
+
this._setDisabled('rotateRightDegreesInput', !hasImage || isBusy);
|
|
3755
3947
|
this._setDisabled('maskList', !hasImage || isBusy);
|
|
3756
3948
|
this._setDisabled('imageInput', isBusy);
|
|
3757
3949
|
this._setDisabled('uploadArea', isBusy);
|
|
@@ -3760,7 +3952,7 @@ function ensureFabric() {
|
|
|
3760
3952
|
/**
|
|
3761
3953
|
* Enables or disables a specific UI element (typically a button) by its key.
|
|
3762
3954
|
*
|
|
3763
|
-
* @param {string} key - Key of the element in this.elements (e.g. '
|
|
3955
|
+
* @param {string} key - Key of the element in this.elements (e.g. 'zoomInButton').
|
|
3764
3956
|
* @param {boolean} disabled - If true, disables the element; otherwise enables.
|
|
3765
3957
|
* @private
|
|
3766
3958
|
*/
|
|
@@ -3895,10 +4087,7 @@ function ensureFabric() {
|
|
|
3895
4087
|
}
|
|
3896
4088
|
} catch (error) { void error; }
|
|
3897
4089
|
|
|
3898
|
-
if (this._cropRect)
|
|
3899
|
-
try { this.canvas.remove(this._cropRect); } catch (error) { void error; }
|
|
3900
|
-
this._cropRect = null;
|
|
3901
|
-
}
|
|
4090
|
+
if (this._cropRect) this._removeCropRect();
|
|
3902
4091
|
|
|
3903
4092
|
if (this.containerElement && this._containerOriginalOverflow) {
|
|
3904
4093
|
try { this._restoreContainerOverflowState(); } catch (error) { void error; }
|
|
@@ -3918,10 +4107,16 @@ function ensureFabric() {
|
|
|
3918
4107
|
this.canvasElement.style.display = this._canvasElementOriginalStyle.display;
|
|
3919
4108
|
this.canvasElement.style.width = this._canvasElementOriginalStyle.width;
|
|
3920
4109
|
this.canvasElement.style.height = this._canvasElementOriginalStyle.height;
|
|
4110
|
+
this.canvasElement.style.maxWidth = this._canvasElementOriginalStyle.maxWidth;
|
|
3921
4111
|
} catch (error) { void error; }
|
|
3922
4112
|
}
|
|
3923
4113
|
|
|
3924
4114
|
if (this.canvas) {
|
|
4115
|
+
try {
|
|
4116
|
+
this.canvas.getObjects().forEach(object => {
|
|
4117
|
+
if (object && object.maskId) this._cleanupMaskEvents(object);
|
|
4118
|
+
});
|
|
4119
|
+
} catch (error) { void error; }
|
|
3925
4120
|
try { this.canvas.dispose(); } catch (error) { void error; }
|
|
3926
4121
|
this.canvas = null;
|
|
3927
4122
|
this.canvasElement = null;
|
|
@@ -4063,7 +4258,7 @@ function ensureFabric() {
|
|
|
4063
4258
|
task.reject(error);
|
|
4064
4259
|
}
|
|
4065
4260
|
} finally {
|
|
4066
|
-
if (
|
|
4261
|
+
if (this.currentTask === task) this.currentTask = null;
|
|
4067
4262
|
}
|
|
4068
4263
|
}
|
|
4069
4264
|
} finally {
|