@bensitu/image-editor 1.4.1 → 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 +537 -190
- package/dist/image-editor.esm.js.map +3 -3
- package/dist/image-editor.esm.min.js +3 -3
- package/dist/image-editor.esm.min.js.map +3 -3
- package/dist/image-editor.esm.min.mjs +3 -3
- package/dist/image-editor.esm.min.mjs.map +3 -3
- package/dist/image-editor.esm.mjs +537 -190
- package/dist/image-editor.esm.mjs.map +3 -3
- package/dist/image-editor.js +537 -190
- package/dist/image-editor.js.map +3 -3
- package/dist/image-editor.min.js +2 -2
- package/dist/image-editor.min.js.map +3 -3
- package/image-editor.d.ts +61 -19
- package/package.json +2 -1
- package/src/image-editor.js +588 -191
|
@@ -5,13 +5,13 @@ import fabricModule from "fabric";
|
|
|
5
5
|
/**
|
|
6
6
|
* @file image-editor.js
|
|
7
7
|
* @module image-editor
|
|
8
|
-
* @version 1.
|
|
8
|
+
* @version 1.5.0
|
|
9
9
|
* @author Ben Situ
|
|
10
10
|
* @license MIT
|
|
11
11
|
* @description Lightweight canvas-based image editor with masking/transform/export support.
|
|
12
12
|
*/
|
|
13
13
|
var fabric = null;
|
|
14
|
-
var INTERNAL_OPERATION_TOKEN = /* @__PURE__ */ Symbol("ImageEditorInternalOperation");
|
|
14
|
+
var INTERNAL_OPERATION_TOKEN = /* @__PURE__ */ Symbol.for("ImageEditorInternalOperation");
|
|
15
15
|
function getGlobalScope() {
|
|
16
16
|
if (typeof globalThis !== "undefined") return globalThis;
|
|
17
17
|
if (typeof self !== "undefined") return self;
|
|
@@ -146,7 +146,7 @@ var ImageEditor = class {
|
|
|
146
146
|
this._activeAnimationRejectors = /* @__PURE__ */ new Set();
|
|
147
147
|
this._disposed = false;
|
|
148
148
|
this._initialized = false;
|
|
149
|
-
this.onImageLoaded = typeof options.onImageLoaded === "function" ? options.onImageLoaded : null;
|
|
149
|
+
this.onImageLoaded = typeof this.options.onImageLoaded === "function" ? this.options.onImageLoaded : null;
|
|
150
150
|
this.animationQueue = new AnimationQueue();
|
|
151
151
|
this.historyManager = new HistoryManager(this.maxHistorySize);
|
|
152
152
|
}
|
|
@@ -192,10 +192,12 @@ var ImageEditor = class {
|
|
|
192
192
|
* Use this method to set up the editor UI before interacting with it.
|
|
193
193
|
*
|
|
194
194
|
* @param {Object} [idMap={}] - Optional mapping from logical element names to actual DOM element IDs.
|
|
195
|
-
* Supported keys include: canvas, canvasContainer,
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
195
|
+
* Supported keys include: canvas, canvasContainer, imagePlaceholder, scalePercentageInput,
|
|
196
|
+
* rotateLeftDegreesInput, rotateRightDegreesInput, rotateLeftButton, rotateRightButton,
|
|
197
|
+
* createMaskButton, removeSelectedMaskButton, removeAllMasksButton, mergeMasksButton,
|
|
198
|
+
* downloadImageButton, maskList, zoomInButton, zoomOutButton, resetImageTransformButton,
|
|
199
|
+
* undoButton, redoButton, imageInput, uploadArea, enterCropModeButton, applyCropButton,
|
|
200
|
+
* and cancelCropButton. Deprecated 1.x names remain supported as aliases.
|
|
199
201
|
*
|
|
200
202
|
* @returns {void}
|
|
201
203
|
*
|
|
@@ -204,7 +206,7 @@ var ImageEditor = class {
|
|
|
204
206
|
* @example
|
|
205
207
|
* editor.init({
|
|
206
208
|
* canvas: 'myFabricCanvasId',
|
|
207
|
-
*
|
|
209
|
+
* downloadImageButton: 'myDownloadButtonId'
|
|
208
210
|
* });
|
|
209
211
|
*/
|
|
210
212
|
init(idMap = {}) {
|
|
@@ -223,33 +225,53 @@ var ImageEditor = class {
|
|
|
223
225
|
this._containerOriginalOverflow = null;
|
|
224
226
|
this._lastContainerViewportSize = null;
|
|
225
227
|
this._canvasElementOriginalStyle = null;
|
|
228
|
+
this._deprecatedElementKeyWarnings = /* @__PURE__ */ new Set();
|
|
226
229
|
const defaults = {
|
|
227
230
|
canvas: "fabricCanvas",
|
|
228
231
|
canvasContainer: null,
|
|
229
232
|
// Pass an ID here if you have a scrollable viewport container
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
233
|
+
imagePlaceholder: "imagePlaceholder",
|
|
234
|
+
imgPlaceholder: null,
|
|
235
|
+
scalePercentageInput: "scalePercentageInput",
|
|
236
|
+
scaleRate: null,
|
|
237
|
+
rotateLeftDegreesInput: "rotateLeftDegreesInput",
|
|
238
|
+
rotationLeftInput: null,
|
|
239
|
+
rotateRightDegreesInput: "rotateRightDegreesInput",
|
|
240
|
+
rotationRightInput: null,
|
|
241
|
+
rotateLeftButton: "rotateLeftButton",
|
|
242
|
+
rotateLeftBtn: null,
|
|
243
|
+
rotateRightButton: "rotateRightButton",
|
|
244
|
+
rotateRightBtn: null,
|
|
245
|
+
createMaskButton: "createMaskButton",
|
|
246
|
+
addMaskBtn: null,
|
|
247
|
+
removeSelectedMaskButton: "removeSelectedMaskButton",
|
|
248
|
+
removeMaskBtn: null,
|
|
249
|
+
removeAllMasksButton: "removeAllMasksButton",
|
|
250
|
+
removeAllMasksBtn: null,
|
|
251
|
+
mergeMasksButton: "mergeMasksButton",
|
|
252
|
+
mergeBtn: null,
|
|
253
|
+
downloadImageButton: "downloadImageButton",
|
|
254
|
+
downloadBtn: null,
|
|
241
255
|
maskList: "maskList",
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
256
|
+
zoomInButton: "zoomInButton",
|
|
257
|
+
zoomInBtn: null,
|
|
258
|
+
zoomOutButton: "zoomOutButton",
|
|
259
|
+
zoomOutBtn: null,
|
|
260
|
+
resetImageTransformButton: "resetImageTransformButton",
|
|
261
|
+
resetBtn: null,
|
|
262
|
+
undoButton: "undoButton",
|
|
263
|
+
undoBtn: null,
|
|
264
|
+
redoButton: "redoButton",
|
|
265
|
+
redoBtn: null,
|
|
247
266
|
imageInput: "imageInput",
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
267
|
+
enterCropModeButton: "enterCropModeButton",
|
|
268
|
+
cropBtn: null,
|
|
269
|
+
applyCropButton: "applyCropButton",
|
|
270
|
+
applyCropBtn: null,
|
|
271
|
+
cancelCropButton: "cancelCropButton",
|
|
272
|
+
cancelCropBtn: null
|
|
251
273
|
};
|
|
252
|
-
this.elements = {
|
|
274
|
+
this.elements = this._resolveElementIdMap(idMap || {}, defaults);
|
|
253
275
|
this._elementCache = {};
|
|
254
276
|
this._initCanvas();
|
|
255
277
|
this._bindEvents();
|
|
@@ -262,6 +284,63 @@ var ImageEditor = class {
|
|
|
262
284
|
this._updatePlaceholderStatus();
|
|
263
285
|
}
|
|
264
286
|
}
|
|
287
|
+
_resolveElementIdMap(idMap, defaults) {
|
|
288
|
+
const resolved = { ...defaults, ...idMap };
|
|
289
|
+
this._resolveElementAliases(resolved, idMap, defaults, "imagePlaceholder", ["imgPlaceholder"]);
|
|
290
|
+
this._resolveElementAliases(resolved, idMap, defaults, "scalePercentageInput", ["scaleRate"]);
|
|
291
|
+
this._resolveElementAliases(resolved, idMap, defaults, "rotateLeftDegreesInput", ["rotationLeftInput"]);
|
|
292
|
+
this._resolveElementAliases(resolved, idMap, defaults, "rotateRightDegreesInput", ["rotationRightInput"]);
|
|
293
|
+
this._resolveElementAlias(resolved, idMap, defaults, "rotateLeftButton", "rotateLeftBtn");
|
|
294
|
+
this._resolveElementAlias(resolved, idMap, defaults, "rotateRightButton", "rotateRightBtn");
|
|
295
|
+
this._resolveElementAlias(resolved, idMap, defaults, "createMaskButton", "addMaskBtn");
|
|
296
|
+
this._resolveElementAliases(resolved, idMap, defaults, "removeSelectedMaskButton", ["removeMaskBtn"]);
|
|
297
|
+
this._resolveElementAlias(resolved, idMap, defaults, "removeAllMasksButton", "removeAllMasksBtn");
|
|
298
|
+
this._resolveElementAlias(resolved, idMap, defaults, "mergeMasksButton", "mergeBtn");
|
|
299
|
+
this._resolveElementAliases(resolved, idMap, defaults, "downloadImageButton", ["downloadBtn"]);
|
|
300
|
+
this._resolveElementAlias(resolved, idMap, defaults, "zoomInButton", "zoomInBtn");
|
|
301
|
+
this._resolveElementAlias(resolved, idMap, defaults, "zoomOutButton", "zoomOutBtn");
|
|
302
|
+
this._resolveElementAlias(resolved, idMap, defaults, "resetImageTransformButton", "resetBtn");
|
|
303
|
+
this._resolveElementAlias(resolved, idMap, defaults, "undoButton", "undoBtn");
|
|
304
|
+
this._resolveElementAlias(resolved, idMap, defaults, "redoButton", "redoBtn");
|
|
305
|
+
this._resolveElementAliases(resolved, idMap, defaults, "enterCropModeButton", ["cropBtn"]);
|
|
306
|
+
this._resolveElementAlias(resolved, idMap, defaults, "applyCropButton", "applyCropBtn");
|
|
307
|
+
this._resolveElementAlias(resolved, idMap, defaults, "cancelCropButton", "cancelCropBtn");
|
|
308
|
+
return resolved;
|
|
309
|
+
}
|
|
310
|
+
_resolveElementAlias(resolved, idMap, defaults, canonicalKey, deprecatedKey) {
|
|
311
|
+
this._resolveElementAliases(resolved, idMap, defaults, canonicalKey, [deprecatedKey]);
|
|
312
|
+
}
|
|
313
|
+
_resolveElementAliases(resolved, idMap, defaults, canonicalKey, deprecatedKeys) {
|
|
314
|
+
const hasCanonicalKey = Object.prototype.hasOwnProperty.call(idMap, canonicalKey);
|
|
315
|
+
if (hasCanonicalKey) {
|
|
316
|
+
resolved[canonicalKey] = idMap[canonicalKey];
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
let deprecatedValue;
|
|
320
|
+
let hasDeprecatedValue = false;
|
|
321
|
+
for (const deprecatedKey of deprecatedKeys) {
|
|
322
|
+
if (Object.prototype.hasOwnProperty.call(idMap, deprecatedKey)) {
|
|
323
|
+
if (!hasDeprecatedValue) {
|
|
324
|
+
deprecatedValue = idMap[deprecatedKey];
|
|
325
|
+
hasDeprecatedValue = true;
|
|
326
|
+
}
|
|
327
|
+
this._warnDeprecatedElementIdKey(deprecatedKey, canonicalKey);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (hasDeprecatedValue) {
|
|
331
|
+
resolved[canonicalKey] = deprecatedValue;
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
resolved[canonicalKey] = defaults[canonicalKey];
|
|
335
|
+
}
|
|
336
|
+
_warnDeprecatedElementIdKey(deprecatedKey, canonicalKey) {
|
|
337
|
+
if (!this._deprecatedElementKeyWarnings) this._deprecatedElementKeyWarnings = /* @__PURE__ */ new Set();
|
|
338
|
+
if (this._deprecatedElementKeyWarnings.has(deprecatedKey)) return;
|
|
339
|
+
this._deprecatedElementKeyWarnings.add(deprecatedKey);
|
|
340
|
+
this._reportWarning(
|
|
341
|
+
`ElementIdMap.${deprecatedKey} is deprecated. Use ${canonicalKey} instead. This alias will be removed in v2.0.0.`
|
|
342
|
+
);
|
|
343
|
+
}
|
|
265
344
|
_reportError(message, error = null) {
|
|
266
345
|
const handler = this.options && this.options.onError;
|
|
267
346
|
if (typeof handler !== "function") return;
|
|
@@ -278,6 +357,11 @@ var ImageEditor = class {
|
|
|
278
357
|
} catch {
|
|
279
358
|
}
|
|
280
359
|
}
|
|
360
|
+
_notifyImageLoaded() {
|
|
361
|
+
const optionsCallback = this.options && this.options.onImageLoaded;
|
|
362
|
+
const callback = typeof optionsCallback === "function" ? optionsCallback : this.onImageLoaded;
|
|
363
|
+
if (typeof callback === "function") callback();
|
|
364
|
+
}
|
|
281
365
|
/**
|
|
282
366
|
* Initializes the Fabric canvas, viewport elements, and selection event handlers.
|
|
283
367
|
*
|
|
@@ -300,7 +384,7 @@ var ImageEditor = class {
|
|
|
300
384
|
} else {
|
|
301
385
|
this.containerElement = canvasElement.parentElement;
|
|
302
386
|
}
|
|
303
|
-
this.placeholderElement = this._getElement("
|
|
387
|
+
this.placeholderElement = this._getElement("imagePlaceholder") || null;
|
|
304
388
|
let initialWidth = this.options.canvasWidth;
|
|
305
389
|
let initialHeight = this.options.canvasHeight;
|
|
306
390
|
if (this.containerElement) {
|
|
@@ -450,20 +534,20 @@ var ImageEditor = class {
|
|
|
450
534
|
});
|
|
451
535
|
}
|
|
452
536
|
});
|
|
453
|
-
this._bindIfExists("
|
|
454
|
-
this._bindIfExists("
|
|
455
|
-
this._bindIfExists("
|
|
537
|
+
this._bindIfExists("zoomInButton", "click", () => this.scaleImage(this.currentScale + this.options.scaleStep).catch((error) => this._reportError("scaleImage failed", error)));
|
|
538
|
+
this._bindIfExists("zoomOutButton", "click", () => this.scaleImage(this.currentScale - this.options.scaleStep).catch((error) => this._reportError("scaleImage failed", error)));
|
|
539
|
+
this._bindIfExists("resetImageTransformButton", "click", () => {
|
|
456
540
|
this.resetImageTransform().catch((error) => this._reportError("resetImageTransform failed", error));
|
|
457
541
|
});
|
|
458
|
-
this._bindIfExists("
|
|
459
|
-
this._bindIfExists("
|
|
460
|
-
this._bindIfExists("
|
|
461
|
-
this._bindIfExists("
|
|
462
|
-
this._bindIfExists("
|
|
463
|
-
this._bindIfExists("
|
|
464
|
-
this._bindIfExists("
|
|
465
|
-
this._bindIfExists("
|
|
466
|
-
const rotationInputElement = this._getElement("
|
|
542
|
+
this._bindIfExists("createMaskButton", "click", () => this.createMask());
|
|
543
|
+
this._bindIfExists("removeSelectedMaskButton", "click", () => this.removeSelectedMask());
|
|
544
|
+
this._bindIfExists("removeAllMasksButton", "click", () => this.removeAllMasks());
|
|
545
|
+
this._bindIfExists("mergeMasksButton", "click", () => this.mergeMasks().catch((error) => this._reportError("merge error", error)));
|
|
546
|
+
this._bindIfExists("downloadImageButton", "click", () => this.downloadImage());
|
|
547
|
+
this._bindIfExists("undoButton", "click", () => this.undo().catch((error) => this._reportError("undo failed", error)));
|
|
548
|
+
this._bindIfExists("redoButton", "click", () => this.redo().catch((error) => this._reportError("redo failed", error)));
|
|
549
|
+
this._bindIfExists("rotateLeftButton", "click", () => {
|
|
550
|
+
const rotationInputElement = this._getElement("rotateLeftDegreesInput");
|
|
467
551
|
let step = this.options.rotationStep;
|
|
468
552
|
if (rotationInputElement) {
|
|
469
553
|
const parsedStep = parseFloat(rotationInputElement.value);
|
|
@@ -471,8 +555,8 @@ var ImageEditor = class {
|
|
|
471
555
|
}
|
|
472
556
|
this.rotateImage(this.currentRotation - step).catch((error) => this._reportError("rotateImage failed", error));
|
|
473
557
|
});
|
|
474
|
-
this._bindIfExists("
|
|
475
|
-
const rotationInputElement = this._getElement("
|
|
558
|
+
this._bindIfExists("rotateRightButton", "click", () => {
|
|
559
|
+
const rotationInputElement = this._getElement("rotateRightDegreesInput");
|
|
476
560
|
let step = this.options.rotationStep;
|
|
477
561
|
if (rotationInputElement) {
|
|
478
562
|
const parsedStep = parseFloat(rotationInputElement.value);
|
|
@@ -480,11 +564,11 @@ var ImageEditor = class {
|
|
|
480
564
|
}
|
|
481
565
|
this.rotateImage(this.currentRotation + step).catch((error) => this._reportError("rotateImage failed", error));
|
|
482
566
|
});
|
|
483
|
-
this._bindIfExists("
|
|
484
|
-
this._bindIfExists("
|
|
567
|
+
this._bindIfExists("enterCropModeButton", "click", () => this.enterCropMode());
|
|
568
|
+
this._bindIfExists("applyCropButton", "click", () => {
|
|
485
569
|
this.applyCrop().catch((error) => this._reportError("applyCrop failed", error));
|
|
486
570
|
});
|
|
487
|
-
this._bindIfExists("
|
|
571
|
+
this._bindIfExists("cancelCropButton", "click", () => this.cancelCrop());
|
|
488
572
|
this._bindIfExists("maskList", "click", (event) => this._handleMaskListClick(event));
|
|
489
573
|
}
|
|
490
574
|
/**
|
|
@@ -575,12 +659,14 @@ var ImageEditor = class {
|
|
|
575
659
|
const imageElement = await this._createImageElement(imageBase64);
|
|
576
660
|
if (this._disposed || !this.canvas) throw new Error("Editor was disposed while loading image");
|
|
577
661
|
let loadSource = imageBase64;
|
|
578
|
-
|
|
579
|
-
|
|
662
|
+
const downsampleMaxWidth = Number(this.options.downsampleMaxWidth);
|
|
663
|
+
const downsampleMaxHeight = Number(this.options.downsampleMaxHeight);
|
|
664
|
+
if (this.options.downsampleOnLoad && downsampleMaxWidth > 0 && downsampleMaxHeight > 0) {
|
|
665
|
+
const shouldResize = imageElement.naturalWidth > downsampleMaxWidth || imageElement.naturalHeight > downsampleMaxHeight;
|
|
580
666
|
if (shouldResize) {
|
|
581
667
|
const ratio = Math.min(
|
|
582
|
-
|
|
583
|
-
|
|
668
|
+
downsampleMaxWidth / imageElement.naturalWidth,
|
|
669
|
+
downsampleMaxHeight / imageElement.naturalHeight
|
|
584
670
|
);
|
|
585
671
|
const targetWidth = Math.round(imageElement.naturalWidth * ratio);
|
|
586
672
|
const targetHeight = Math.round(imageElement.naturalHeight * ratio);
|
|
@@ -592,6 +678,8 @@ var ImageEditor = class {
|
|
|
592
678
|
imageBase64
|
|
593
679
|
);
|
|
594
680
|
}
|
|
681
|
+
} else if (this.options.downsampleOnLoad) {
|
|
682
|
+
this._reportWarning("loadImage: downsample limits must be positive numbers; using the original image");
|
|
595
683
|
}
|
|
596
684
|
const fabricImage = await this._createFabricImageFromURL(loadSource);
|
|
597
685
|
if (this._disposed || !this.canvas) throw new Error("Editor was disposed while loading image");
|
|
@@ -650,9 +738,7 @@ var ImageEditor = class {
|
|
|
650
738
|
this._updateUI();
|
|
651
739
|
this.canvas.renderAll();
|
|
652
740
|
this._lastSnapshot = this._captureCanvasStateOrThrow("loadImage");
|
|
653
|
-
|
|
654
|
-
this.onImageLoaded();
|
|
655
|
-
}
|
|
741
|
+
this._notifyImageLoaded();
|
|
656
742
|
} catch (error) {
|
|
657
743
|
await this._rollbackLoadImageTransaction(transaction);
|
|
658
744
|
throw error;
|
|
@@ -669,6 +755,15 @@ var ImageEditor = class {
|
|
|
669
755
|
const fabricInstance2 = ensureFabric();
|
|
670
756
|
return !!(this.originalImage && fabricInstance2 && this.originalImage instanceof fabricInstance2.Image && this.originalImage.width > 0 && this.originalImage.height > 0);
|
|
671
757
|
}
|
|
758
|
+
/**
|
|
759
|
+
* Checks whether the editor is in a temporary non-mutating state.
|
|
760
|
+
*
|
|
761
|
+
* @returns {boolean} True while loading, animating, cropping, or running a compound operation.
|
|
762
|
+
* @public
|
|
763
|
+
*/
|
|
764
|
+
isBusy() {
|
|
765
|
+
return !!(this.isAnimating || this._cropMode || this._isLoading || this._activeOperationToken || this.animationQueue && this.animationQueue.isBusy());
|
|
766
|
+
}
|
|
672
767
|
/**
|
|
673
768
|
* Creates an HTMLImageElement from a given data URL.
|
|
674
769
|
*
|
|
@@ -696,7 +791,7 @@ var ImageEditor = class {
|
|
|
696
791
|
try {
|
|
697
792
|
imageElement.src = "";
|
|
698
793
|
} catch (error) {
|
|
699
|
-
|
|
794
|
+
this._reportWarning("Image timeout cleanup failed", error);
|
|
700
795
|
}
|
|
701
796
|
}, safeTimeoutMs);
|
|
702
797
|
imageElement.onload = () => settle(() => resolve(imageElement));
|
|
@@ -740,7 +835,6 @@ var ImageEditor = class {
|
|
|
740
835
|
_captureLoadImageTransaction() {
|
|
741
836
|
return {
|
|
742
837
|
canvasState: this._serializeCanvasState(),
|
|
743
|
-
originalImage: this.originalImage,
|
|
744
838
|
baseImageScale: this.baseImageScale,
|
|
745
839
|
currentScale: this.currentScale,
|
|
746
840
|
currentRotation: this.currentRotation,
|
|
@@ -765,6 +859,7 @@ var ImageEditor = class {
|
|
|
765
859
|
async _rollbackLoadImageTransaction(transaction) {
|
|
766
860
|
if (!transaction || !this.canvas || this._disposed) return;
|
|
767
861
|
let didRestoreCanvasState = false;
|
|
862
|
+
let didFailCanvasRestore = false;
|
|
768
863
|
try {
|
|
769
864
|
if (transaction.canvasState) {
|
|
770
865
|
await this.loadFromState(transaction.canvasState);
|
|
@@ -772,22 +867,27 @@ var ImageEditor = class {
|
|
|
772
867
|
}
|
|
773
868
|
} catch (error) {
|
|
774
869
|
this._lastMask = null;
|
|
870
|
+
didFailCanvasRestore = true;
|
|
775
871
|
this._reportError("loadImage rollback failed", error);
|
|
776
872
|
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
this.currentRotation = transaction.currentRotation;
|
|
780
|
-
this.maskCounter = transaction.maskCounter;
|
|
781
|
-
this.isImageLoadedToCanvas = transaction.isImageLoadedToCanvas;
|
|
782
|
-
this._lastSnapshot = transaction.lastSnapshot;
|
|
783
|
-
if (didRestoreCanvasState) {
|
|
784
|
-
this._restoreLastMaskReference(transaction.lastMask);
|
|
873
|
+
if (didFailCanvasRestore) {
|
|
874
|
+
this._reconcileEditorStateFromCanvas();
|
|
785
875
|
} else {
|
|
786
|
-
this.
|
|
876
|
+
this.baseImageScale = transaction.baseImageScale;
|
|
877
|
+
this.currentScale = transaction.currentScale;
|
|
878
|
+
this.currentRotation = transaction.currentRotation;
|
|
879
|
+
this.maskCounter = transaction.maskCounter;
|
|
880
|
+
this.isImageLoadedToCanvas = transaction.isImageLoadedToCanvas;
|
|
881
|
+
this._lastSnapshot = transaction.lastSnapshot;
|
|
882
|
+
if (didRestoreCanvasState) {
|
|
883
|
+
this._restoreLastMaskReference(transaction.lastMask);
|
|
884
|
+
} else {
|
|
885
|
+
this._lastMask = null;
|
|
886
|
+
}
|
|
887
|
+
this._lastMaskInitialLeft = transaction.lastMaskInitialLeft;
|
|
888
|
+
this._lastMaskInitialTop = transaction.lastMaskInitialTop;
|
|
889
|
+
this._lastMaskInitialWidth = transaction.lastMaskInitialWidth;
|
|
787
890
|
}
|
|
788
|
-
this._lastMaskInitialLeft = transaction.lastMaskInitialLeft;
|
|
789
|
-
this._lastMaskInitialTop = transaction.lastMaskInitialTop;
|
|
790
|
-
this._lastMaskInitialWidth = transaction.lastMaskInitialWidth;
|
|
791
891
|
this._restoreElementVisibility(this.placeholderElement, transaction.placeholderVisibility);
|
|
792
892
|
this._restoreElementVisibility(this._getCanvasVisibilityElement(), transaction.canvasVisibility);
|
|
793
893
|
if (this.containerElement) {
|
|
@@ -800,6 +900,46 @@ var ImageEditor = class {
|
|
|
800
900
|
this._updateUI();
|
|
801
901
|
if (this.canvas) this.canvas.renderAll();
|
|
802
902
|
}
|
|
903
|
+
_reconcileEditorStateFromCanvas() {
|
|
904
|
+
if (!this.canvas) {
|
|
905
|
+
this.originalImage = null;
|
|
906
|
+
this.baseImageScale = 1;
|
|
907
|
+
this.currentScale = 1;
|
|
908
|
+
this.currentRotation = 0;
|
|
909
|
+
this.maskCounter = 0;
|
|
910
|
+
this.isImageLoadedToCanvas = false;
|
|
911
|
+
this._lastSnapshot = null;
|
|
912
|
+
this._clearMaskPlacementMemory();
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
const canvasObjects = this.canvas.getObjects();
|
|
916
|
+
this.originalImage = canvasObjects.find((object) => object.type === "image" && !object.maskId) || null;
|
|
917
|
+
if (this.originalImage) {
|
|
918
|
+
const imageScale = Number(this.originalImage.scaleX) || 1;
|
|
919
|
+
this.baseImageScale = imageScale;
|
|
920
|
+
this.currentScale = 1;
|
|
921
|
+
this.currentRotation = Number(this.originalImage.angle) || 0;
|
|
922
|
+
} else {
|
|
923
|
+
this.baseImageScale = 1;
|
|
924
|
+
this.currentScale = 1;
|
|
925
|
+
this.currentRotation = 0;
|
|
926
|
+
}
|
|
927
|
+
const masks = canvasObjects.filter((object) => object.maskId);
|
|
928
|
+
this.maskCounter = masks.reduce((max, mask) => Math.max(max, Number(mask.maskId) || 0), 0);
|
|
929
|
+
this._lastMask = masks[masks.length - 1] || null;
|
|
930
|
+
if (!this._lastMask) {
|
|
931
|
+
this._lastMaskInitialLeft = null;
|
|
932
|
+
this._lastMaskInitialTop = null;
|
|
933
|
+
this._lastMaskInitialWidth = null;
|
|
934
|
+
}
|
|
935
|
+
this.isImageLoadedToCanvas = !!this.originalImage;
|
|
936
|
+
try {
|
|
937
|
+
this._lastSnapshot = this._serializeCanvasState();
|
|
938
|
+
} catch (error) {
|
|
939
|
+
this._lastSnapshot = null;
|
|
940
|
+
this._reportWarning("loadImage rollback: failed to reconcile canvas snapshot", error);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
803
943
|
_restoreLastMaskReference(previousLastMask) {
|
|
804
944
|
if (!this.canvas) {
|
|
805
945
|
this._lastMask = null;
|
|
@@ -826,12 +966,19 @@ var ImageEditor = class {
|
|
|
826
966
|
* @private
|
|
827
967
|
*/
|
|
828
968
|
_resampleImageToDataURL(imageElement, targetWidth, targetHeight, quality = 0.92, sourceDataUrl = null) {
|
|
969
|
+
const sourceWidth = Math.max(1, Number(imageElement && (imageElement.naturalWidth || imageElement.width)) || 0);
|
|
970
|
+
const sourceHeight = Math.max(1, Number(imageElement && (imageElement.naturalHeight || imageElement.height)) || 0);
|
|
971
|
+
const safeTargetWidth = Math.round(Number(targetWidth));
|
|
972
|
+
const safeTargetHeight = Math.round(Number(targetHeight));
|
|
973
|
+
if (!Number.isFinite(safeTargetWidth) || !Number.isFinite(safeTargetHeight) || safeTargetWidth <= 0 || safeTargetHeight <= 0) {
|
|
974
|
+
throw new Error("Invalid image resample target dimensions");
|
|
975
|
+
}
|
|
829
976
|
const offscreenCanvas = document.createElement("canvas");
|
|
830
|
-
offscreenCanvas.width =
|
|
831
|
-
offscreenCanvas.height =
|
|
977
|
+
offscreenCanvas.width = safeTargetWidth;
|
|
978
|
+
offscreenCanvas.height = safeTargetHeight;
|
|
832
979
|
const context = offscreenCanvas.getContext("2d");
|
|
833
980
|
if (!context) throw new Error("2D canvas context is unavailable");
|
|
834
|
-
context.drawImage(imageElement, 0, 0,
|
|
981
|
+
context.drawImage(imageElement, 0, 0, sourceWidth, sourceHeight, 0, 0, safeTargetWidth, safeTargetHeight);
|
|
835
982
|
return offscreenCanvas.toDataURL(this._getDownsampleMimeType(sourceDataUrl), quality);
|
|
836
983
|
}
|
|
837
984
|
_getDataUrlMimeType(dataUrl) {
|
|
@@ -863,6 +1010,7 @@ var ImageEditor = class {
|
|
|
863
1010
|
* @private
|
|
864
1011
|
*/
|
|
865
1012
|
_setCanvasSizeInt(width, height) {
|
|
1013
|
+
if (!this.canvas) return;
|
|
866
1014
|
const integerWidth = Math.max(1, Math.round(Number(width) || 1));
|
|
867
1015
|
const integerHeight = Math.max(1, Math.round(Number(height) || 1));
|
|
868
1016
|
this.canvas.setWidth(integerWidth);
|
|
@@ -1135,7 +1283,7 @@ var ImageEditor = class {
|
|
|
1135
1283
|
/**
|
|
1136
1284
|
* Captures editor-owned runtime state that Fabric does not include in canvas JSON.
|
|
1137
1285
|
*
|
|
1138
|
-
* @returns {{version:number, baseImageScale:number, currentScale:number, currentRotation:number, maskCounter:number}} Serializable editor metadata.
|
|
1286
|
+
* @returns {{version:number, baseImageScale:number, currentScale:number, currentRotation:number, maskCounter:number, canvasWidth:number, canvasHeight:number}} Serializable editor metadata.
|
|
1139
1287
|
* @private
|
|
1140
1288
|
*/
|
|
1141
1289
|
_serializeEditorMetadata() {
|
|
@@ -1143,12 +1291,16 @@ var ImageEditor = class {
|
|
|
1143
1291
|
const currentScale = Number(this.currentScale);
|
|
1144
1292
|
const currentRotation = Number(this.currentRotation);
|
|
1145
1293
|
const maskCounter = Number(this.maskCounter);
|
|
1294
|
+
const canvasWidth = this.canvas ? Number(this.canvas.getWidth()) : NaN;
|
|
1295
|
+
const canvasHeight = this.canvas ? Number(this.canvas.getHeight()) : NaN;
|
|
1146
1296
|
return {
|
|
1147
1297
|
version: 1,
|
|
1148
1298
|
baseImageScale: Number.isFinite(baseImageScale) && baseImageScale > 0 ? baseImageScale : 1,
|
|
1149
1299
|
currentScale: Number.isFinite(currentScale) && currentScale > 0 ? currentScale : 1,
|
|
1150
1300
|
currentRotation: Number.isFinite(currentRotation) ? currentRotation : 0,
|
|
1151
|
-
maskCounter: Number.isFinite(maskCounter) && maskCounter > 0 ? Math.floor(maskCounter) : 0
|
|
1301
|
+
maskCounter: Number.isFinite(maskCounter) && maskCounter > 0 ? Math.floor(maskCounter) : 0,
|
|
1302
|
+
canvasWidth: Number.isFinite(canvasWidth) && canvasWidth > 0 ? Math.round(canvasWidth) : 1,
|
|
1303
|
+
canvasHeight: Number.isFinite(canvasHeight) && canvasHeight > 0 ? Math.round(canvasHeight) : 1
|
|
1152
1304
|
};
|
|
1153
1305
|
}
|
|
1154
1306
|
_serializeCanvasState() {
|
|
@@ -1332,10 +1484,42 @@ var ImageEditor = class {
|
|
|
1332
1484
|
}
|
|
1333
1485
|
_getJpegBackgroundColor() {
|
|
1334
1486
|
const backgroundColor = String(this.options.backgroundColor || "").trim();
|
|
1335
|
-
if (!backgroundColor || backgroundColor
|
|
1336
|
-
if (/^rgba\([^)]*,\s*0(?:\.0+)?\s*\)$/i.test(backgroundColor)) return "#ffffff";
|
|
1487
|
+
if (!backgroundColor || this._isTransparentCssColor(backgroundColor)) return "#ffffff";
|
|
1337
1488
|
return backgroundColor;
|
|
1338
1489
|
}
|
|
1490
|
+
_isTransparentCssColor(color) {
|
|
1491
|
+
const normalizedColor = String(color || "").trim().toLowerCase();
|
|
1492
|
+
if (!normalizedColor || normalizedColor === "transparent") return true;
|
|
1493
|
+
const hexAlphaMatch = normalizedColor.match(/^#(?:[0-9a-f]{3}([0-9a-f])|[0-9a-f]{6}([0-9a-f]{2}))$/i);
|
|
1494
|
+
if (hexAlphaMatch) {
|
|
1495
|
+
const alpha = hexAlphaMatch[1] || hexAlphaMatch[2];
|
|
1496
|
+
return alpha === "0" || alpha === "00";
|
|
1497
|
+
}
|
|
1498
|
+
const slashAlphaMatch = normalizedColor.match(/^(?:rgba?|hsla?)\([^)]*\/\s*([^)]+)\)$/i);
|
|
1499
|
+
if (slashAlphaMatch) return this._isZeroCssAlpha(slashAlphaMatch[1]);
|
|
1500
|
+
const commaAlphaMatch = normalizedColor.match(/^(?:rgba|hsla)\((.*)\)$/i);
|
|
1501
|
+
if (commaAlphaMatch) {
|
|
1502
|
+
const parts = commaAlphaMatch[1].split(",");
|
|
1503
|
+
if (parts.length >= 4) return this._isZeroCssAlpha(parts[parts.length - 1]);
|
|
1504
|
+
}
|
|
1505
|
+
return false;
|
|
1506
|
+
}
|
|
1507
|
+
_isZeroCssAlpha(alphaValue) {
|
|
1508
|
+
const normalizedAlpha = String(alphaValue || "").trim();
|
|
1509
|
+
if (!normalizedAlpha) return false;
|
|
1510
|
+
if (normalizedAlpha.endsWith("%")) return Number.parseFloat(normalizedAlpha) === 0;
|
|
1511
|
+
return Number(normalizedAlpha) === 0;
|
|
1512
|
+
}
|
|
1513
|
+
_decodeBase64Payload(base64Payload) {
|
|
1514
|
+
const payload = String(base64Payload || "");
|
|
1515
|
+
if (typeof atob === "function") {
|
|
1516
|
+
return Uint8Array.from(atob(payload), (char) => char.charCodeAt(0));
|
|
1517
|
+
}
|
|
1518
|
+
if (typeof Buffer !== "undefined" && typeof Buffer.from === "function") {
|
|
1519
|
+
return new Uint8Array(Buffer.from(payload, "base64"));
|
|
1520
|
+
}
|
|
1521
|
+
throw new Error("Base64 decoding is unavailable");
|
|
1522
|
+
}
|
|
1339
1523
|
/**
|
|
1340
1524
|
* Gets the top-left corner coordinates of the given object.
|
|
1341
1525
|
* Used for geometry calculations (e.g., scale, rotate).
|
|
@@ -1452,17 +1636,13 @@ var ImageEditor = class {
|
|
|
1452
1636
|
requiredWidth = Math.max(requiredWidth, Math.ceil(boundingRect.left + boundingRect.width + padding));
|
|
1453
1637
|
requiredHeight = Math.max(requiredHeight, Math.ceil(boundingRect.top + boundingRect.height + padding));
|
|
1454
1638
|
});
|
|
1455
|
-
const shouldUseScrollSafeViewport = this.options.fitImageToCanvas || this.options.coverImageToCanvas;
|
|
1456
1639
|
let minWidth = 0;
|
|
1457
1640
|
let minHeight = 0;
|
|
1458
|
-
if (
|
|
1641
|
+
if (this.containerElement) {
|
|
1459
1642
|
const viewport = this._getContainerViewportSize();
|
|
1460
1643
|
const safetyMargin = this._getScrollSafetyMargin();
|
|
1461
1644
|
minWidth = Math.max(1, viewport.width - safetyMargin);
|
|
1462
1645
|
minHeight = Math.max(1, viewport.height - safetyMargin);
|
|
1463
|
-
} else if (this.containerElement) {
|
|
1464
|
-
minWidth = Math.floor(this.containerElement.clientWidth || 0);
|
|
1465
|
-
minHeight = Math.floor(this.containerElement.clientHeight || 0);
|
|
1466
1646
|
}
|
|
1467
1647
|
const newWidth = Math.max(currentWidth, minWidth, requiredWidth);
|
|
1468
1648
|
const newHeight = Math.max(currentHeight, minHeight, requiredHeight);
|
|
@@ -1531,9 +1711,15 @@ var ImageEditor = class {
|
|
|
1531
1711
|
_assertEditorAvailable(operationName) {
|
|
1532
1712
|
if (this._disposed || !this.canvas) throw new Error(`${operationName} cannot run after the editor has been disposed`);
|
|
1533
1713
|
}
|
|
1714
|
+
_isCropModeAllowedOperation(operationName) {
|
|
1715
|
+
return operationName === "applyCrop" || operationName === "cancelCrop";
|
|
1716
|
+
}
|
|
1534
1717
|
_assertIdleForOperation(operationName, options = {}) {
|
|
1535
1718
|
this._assertEditorAvailable(operationName);
|
|
1536
1719
|
const isOwnInternalOperation = this._isOwnInternalOperation(options);
|
|
1720
|
+
if (this._cropMode && !this._isCropModeAllowedOperation(operationName) && !isOwnInternalOperation) {
|
|
1721
|
+
throw new Error(`${operationName} cannot run while crop mode is active`);
|
|
1722
|
+
}
|
|
1537
1723
|
if (this.isAnimating || this.animationQueue && this.animationQueue.isBusy()) {
|
|
1538
1724
|
throw new Error(`${operationName} cannot run while an animation is running`);
|
|
1539
1725
|
}
|
|
@@ -1546,10 +1732,14 @@ var ImageEditor = class {
|
|
|
1546
1732
|
}
|
|
1547
1733
|
_assertCanQueueAnimation(operationName, options = {}) {
|
|
1548
1734
|
this._assertEditorAvailable(operationName);
|
|
1549
|
-
|
|
1735
|
+
const isOwnInternalOperation = this._isOwnInternalOperation(options);
|
|
1736
|
+
if (this._cropMode && !this._isCropModeAllowedOperation(operationName) && !isOwnInternalOperation) {
|
|
1737
|
+
throw new Error(`${operationName} cannot run while crop mode is active`);
|
|
1738
|
+
}
|
|
1739
|
+
if (this._isLoading && !isOwnInternalOperation) {
|
|
1550
1740
|
throw new Error(`${operationName} cannot run while an image is loading`);
|
|
1551
1741
|
}
|
|
1552
|
-
if (this._activeOperationToken && !
|
|
1742
|
+
if (this._activeOperationToken && !isOwnInternalOperation) {
|
|
1553
1743
|
throw new Error(`${operationName} cannot run while ${this._activeOperationName || "another operation"} is running`);
|
|
1554
1744
|
}
|
|
1555
1745
|
}
|
|
@@ -1736,10 +1926,19 @@ var ImageEditor = class {
|
|
|
1736
1926
|
}
|
|
1737
1927
|
return this.animationQueue.add(async () => {
|
|
1738
1928
|
const before = this._lastSnapshot || this._captureCanvasStateOrThrow("resetImageTransform");
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1929
|
+
try {
|
|
1930
|
+
await this._scaleImageImpl(1, { saveHistory: false });
|
|
1931
|
+
await this._rotateImageImpl(0, { saveHistory: false });
|
|
1932
|
+
const after = this._captureCanvasStateOrThrow("resetImageTransform");
|
|
1933
|
+
this._pushStateTransition(before, after);
|
|
1934
|
+
} catch (error) {
|
|
1935
|
+
try {
|
|
1936
|
+
await this.loadFromState(before);
|
|
1937
|
+
} catch (restoreError) {
|
|
1938
|
+
this._reportError("resetImageTransform rollback failed", restoreError);
|
|
1939
|
+
}
|
|
1940
|
+
throw error;
|
|
1941
|
+
}
|
|
1743
1942
|
}).finally(() => {
|
|
1744
1943
|
if (!this._disposed && this.canvas) this._updateUI();
|
|
1745
1944
|
}).catch((error) => {
|
|
@@ -1778,7 +1977,13 @@ var ImageEditor = class {
|
|
|
1778
1977
|
try {
|
|
1779
1978
|
const state = typeof serializedState === "string" ? JSON.parse(serializedState) : serializedState;
|
|
1780
1979
|
const editorMetadata = state && state.imageEditorMetadata ? state.imageEditorMetadata : null;
|
|
1781
|
-
|
|
1980
|
+
const restoredCanvasWidth = Number(editorMetadata && editorMetadata.canvasWidth);
|
|
1981
|
+
const restoredCanvasHeight = Number(editorMetadata && editorMetadata.canvasHeight);
|
|
1982
|
+
const hasRestoredCanvasSize = Number.isFinite(restoredCanvasWidth) && restoredCanvasWidth > 0 && Number.isFinite(restoredCanvasHeight) && restoredCanvasHeight > 0;
|
|
1983
|
+
if (editorMetadata && Object.prototype.hasOwnProperty.call(editorMetadata, "version") && Number(editorMetadata.version) !== 1) {
|
|
1984
|
+
this._reportWarning(`loadFromState: unsupported editor metadata version ${editorMetadata.version}`);
|
|
1985
|
+
}
|
|
1986
|
+
const finishLoad = async () => {
|
|
1782
1987
|
try {
|
|
1783
1988
|
if (this._disposed || !this.canvas) {
|
|
1784
1989
|
reject(new Error("Editor was disposed while loading state"));
|
|
@@ -1814,6 +2019,11 @@ var ImageEditor = class {
|
|
|
1814
2019
|
this.currentScale = 1;
|
|
1815
2020
|
this.currentRotation = 0;
|
|
1816
2021
|
}
|
|
2022
|
+
if (hasRestoredCanvasSize) {
|
|
2023
|
+
this._setCanvasSizeInt(restoredCanvasWidth, restoredCanvasHeight);
|
|
2024
|
+
} else if (this.originalImage && this._shouldResizeCanvasToContentBounds()) {
|
|
2025
|
+
this._updateCanvasSizeToImageBounds();
|
|
2026
|
+
}
|
|
1817
2027
|
const masks = canvasObjects.filter((object) => object.maskId);
|
|
1818
2028
|
masks.forEach((mask) => {
|
|
1819
2029
|
this._restoreMaskControls(mask);
|
|
@@ -1841,6 +2051,9 @@ var ImageEditor = class {
|
|
|
1841
2051
|
this._reportError("loadFromState() failed", callbackError);
|
|
1842
2052
|
reject(callbackError);
|
|
1843
2053
|
}
|
|
2054
|
+
};
|
|
2055
|
+
this.canvas.loadFromJSON(state, () => {
|
|
2056
|
+
void finishLoad();
|
|
1844
2057
|
});
|
|
1845
2058
|
} catch (error) {
|
|
1846
2059
|
this._reportError("loadFromState() failed", error);
|
|
@@ -1856,12 +2069,12 @@ var ImageEditor = class {
|
|
|
1856
2069
|
}
|
|
1857
2070
|
_waitForImageElementReady(imageElement) {
|
|
1858
2071
|
if (!imageElement) return Promise.resolve();
|
|
1859
|
-
|
|
2072
|
+
const hasLoadedDimensions = (Number(imageElement.naturalWidth) > 0 || Number(imageElement.width) > 0) && (Number(imageElement.naturalHeight) > 0 || Number(imageElement.height) > 0);
|
|
2073
|
+
if (hasLoadedDimensions) return Promise.resolve();
|
|
2074
|
+
if (imageElement.complete) return Promise.reject(new Error("Image could not be loaded while restoring state"));
|
|
1860
2075
|
return new Promise((resolve, reject) => {
|
|
1861
2076
|
let isSettled = false;
|
|
1862
|
-
|
|
1863
|
-
settle(() => reject(new Error("Image load timed out while restoring state")));
|
|
1864
|
-
}, this._getSafeTimeoutMs(this.options.imageLoadTimeoutMs));
|
|
2077
|
+
let timerId;
|
|
1865
2078
|
const settle = (callback) => {
|
|
1866
2079
|
if (isSettled) return;
|
|
1867
2080
|
isSettled = true;
|
|
@@ -1875,8 +2088,20 @@ var ImageEditor = class {
|
|
|
1875
2088
|
}
|
|
1876
2089
|
callback();
|
|
1877
2090
|
};
|
|
1878
|
-
const handleLoad = () =>
|
|
1879
|
-
|
|
2091
|
+
const handleLoad = () => {
|
|
2092
|
+
const didLoad = (Number(imageElement.naturalWidth) > 0 || Number(imageElement.width) > 0) && (Number(imageElement.naturalHeight) > 0 || Number(imageElement.height) > 0);
|
|
2093
|
+
settle(() => {
|
|
2094
|
+
if (didLoad) {
|
|
2095
|
+
resolve();
|
|
2096
|
+
} else {
|
|
2097
|
+
reject(new Error("Image could not be loaded while restoring state"));
|
|
2098
|
+
}
|
|
2099
|
+
});
|
|
2100
|
+
};
|
|
2101
|
+
const handleError = (error) => settle(() => reject(error instanceof Error ? error : new Error("Image could not be loaded while restoring state")));
|
|
2102
|
+
timerId = setTimeout(() => {
|
|
2103
|
+
settle(() => reject(new Error("Image load timed out while restoring state")));
|
|
2104
|
+
}, this._getSafeTimeoutMs(this.options.imageLoadTimeoutMs));
|
|
1880
2105
|
if (typeof imageElement.addEventListener === "function") {
|
|
1881
2106
|
imageElement.addEventListener("load", handleLoad, { once: true });
|
|
1882
2107
|
imageElement.addEventListener("error", handleError, { once: true });
|
|
@@ -1976,14 +2201,7 @@ var ImageEditor = class {
|
|
|
1976
2201
|
}
|
|
1977
2202
|
_rebindMaskEvents(mask) {
|
|
1978
2203
|
if (!mask) return;
|
|
1979
|
-
|
|
1980
|
-
try {
|
|
1981
|
-
mask.off("mouseover", mask.__imageEditorMaskHandlers.mouseover);
|
|
1982
|
-
mask.off("mouseout", mask.__imageEditorMaskHandlers.mouseout);
|
|
1983
|
-
} catch (error) {
|
|
1984
|
-
void error;
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
2204
|
+
this._cleanupMaskEvents(mask);
|
|
1987
2205
|
const metadata = {};
|
|
1988
2206
|
if (!Number.isFinite(Number(mask.originalAlpha))) {
|
|
1989
2207
|
metadata.originalAlpha = Number.isFinite(Number(mask.opacity)) ? Number(mask.opacity) : 0.5;
|
|
@@ -2010,6 +2228,22 @@ var ImageEditor = class {
|
|
|
2010
2228
|
mask.on("mouseout", mouseout);
|
|
2011
2229
|
mask.__imageEditorMaskHandlers = { mouseover, mouseout };
|
|
2012
2230
|
}
|
|
2231
|
+
_cleanupMaskEvents(mask) {
|
|
2232
|
+
if (!mask || !mask.__imageEditorMaskHandlers) return;
|
|
2233
|
+
try {
|
|
2234
|
+
if (typeof mask.off === "function") {
|
|
2235
|
+
mask.off("mouseover", mask.__imageEditorMaskHandlers.mouseover);
|
|
2236
|
+
mask.off("mouseout", mask.__imageEditorMaskHandlers.mouseout);
|
|
2237
|
+
}
|
|
2238
|
+
} catch (error) {
|
|
2239
|
+
this._reportWarning("Mask event cleanup failed", error);
|
|
2240
|
+
}
|
|
2241
|
+
try {
|
|
2242
|
+
delete mask.__imageEditorMaskHandlers;
|
|
2243
|
+
} catch (error) {
|
|
2244
|
+
this._reportWarning("Mask event metadata cleanup failed", error);
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2013
2247
|
/**
|
|
2014
2248
|
* Creates a mask and adds it to the canvas.
|
|
2015
2249
|
*
|
|
@@ -2148,6 +2382,10 @@ var ImageEditor = class {
|
|
|
2148
2382
|
});
|
|
2149
2383
|
}
|
|
2150
2384
|
}
|
|
2385
|
+
if (!mask || typeof mask.set !== "function" || typeof mask.setCoords !== "function") {
|
|
2386
|
+
this._reportWarning("fabricGenerator returned an invalid Fabric object");
|
|
2387
|
+
return null;
|
|
2388
|
+
}
|
|
2151
2389
|
const styles = maskConfig.styles || {};
|
|
2152
2390
|
const hasStyle = (property) => Object.prototype.hasOwnProperty.call(styles, property);
|
|
2153
2391
|
const maskSettings = {
|
|
@@ -2216,6 +2454,7 @@ var ImageEditor = class {
|
|
|
2216
2454
|
this.canvas.discardActiveObject();
|
|
2217
2455
|
selectedMasks.forEach((mask) => {
|
|
2218
2456
|
this._removeLabelForMask(mask);
|
|
2457
|
+
this._cleanupMaskEvents(mask);
|
|
2219
2458
|
this.canvas.remove(mask);
|
|
2220
2459
|
});
|
|
2221
2460
|
const masks = this.canvas.getObjects().filter((object) => object.maskId);
|
|
@@ -2240,7 +2479,10 @@ var ImageEditor = class {
|
|
|
2240
2479
|
const saveHistory = options.saveHistory !== false;
|
|
2241
2480
|
const masks = this.canvas.getObjects().filter((object) => object.maskId);
|
|
2242
2481
|
masks.forEach((mask) => this._removeLabelForMask(mask));
|
|
2243
|
-
masks.forEach((mask) =>
|
|
2482
|
+
masks.forEach((mask) => {
|
|
2483
|
+
this._cleanupMaskEvents(mask);
|
|
2484
|
+
this.canvas.remove(mask);
|
|
2485
|
+
});
|
|
2244
2486
|
this.canvas.discardActiveObject();
|
|
2245
2487
|
this._lastMask = null;
|
|
2246
2488
|
this._lastMaskInitialLeft = null;
|
|
@@ -2275,6 +2517,93 @@ var ImageEditor = class {
|
|
|
2275
2517
|
}
|
|
2276
2518
|
}
|
|
2277
2519
|
}
|
|
2520
|
+
_captureMaskLabelBackups(masks) {
|
|
2521
|
+
if (!this.canvas) return [];
|
|
2522
|
+
const canvasObjects = new Set(this.canvas.getObjects());
|
|
2523
|
+
return (masks || []).map((mask) => {
|
|
2524
|
+
const label = mask && mask.__label ? mask.__label : null;
|
|
2525
|
+
return {
|
|
2526
|
+
mask,
|
|
2527
|
+
label,
|
|
2528
|
+
hadLabel: !!label,
|
|
2529
|
+
labelInCanvas: !!label && canvasObjects.has(label),
|
|
2530
|
+
visible: label ? label.visible : void 0
|
|
2531
|
+
};
|
|
2532
|
+
});
|
|
2533
|
+
}
|
|
2534
|
+
_restoreMaskLabelBackups(labelBackups) {
|
|
2535
|
+
if (!this.canvas || !Array.isArray(labelBackups)) return;
|
|
2536
|
+
const canvasObjects = new Set(this.canvas.getObjects());
|
|
2537
|
+
labelBackups.forEach((backup) => {
|
|
2538
|
+
if (!backup || !backup.mask) return;
|
|
2539
|
+
try {
|
|
2540
|
+
if (!backup.hadLabel) {
|
|
2541
|
+
if (backup.mask.__label) this._removeLabelForMask(backup.mask);
|
|
2542
|
+
return;
|
|
2543
|
+
}
|
|
2544
|
+
backup.mask.__label = backup.label;
|
|
2545
|
+
if (!backup.label) return;
|
|
2546
|
+
if (backup.labelInCanvas && !canvasObjects.has(backup.label)) {
|
|
2547
|
+
this.canvas.add(backup.label);
|
|
2548
|
+
canvasObjects.add(backup.label);
|
|
2549
|
+
}
|
|
2550
|
+
if (backup.visible !== void 0) backup.label.set({ visible: backup.visible });
|
|
2551
|
+
if (backup.labelInCanvas) this.canvas.bringToFront(backup.label);
|
|
2552
|
+
this._syncMaskLabel(backup.mask);
|
|
2553
|
+
} catch (error) {
|
|
2554
|
+
this._reportWarning("restoreMaskLabelBackups: failed to restore mask label", error);
|
|
2555
|
+
}
|
|
2556
|
+
});
|
|
2557
|
+
}
|
|
2558
|
+
_captureActiveObjectBackup() {
|
|
2559
|
+
if (!this.canvas) return null;
|
|
2560
|
+
const activeObject = this.canvas.getActiveObject();
|
|
2561
|
+
if (!activeObject) return null;
|
|
2562
|
+
const selectedObjects = typeof activeObject.getObjects === "function" ? activeObject.getObjects() : [activeObject];
|
|
2563
|
+
return { activeObject, selectedObjects };
|
|
2564
|
+
}
|
|
2565
|
+
_restoreActiveObjectBackup(activeObjectBackup) {
|
|
2566
|
+
if (!this.canvas || !activeObjectBackup || !activeObjectBackup.activeObject) return;
|
|
2567
|
+
const canvasObjects = this.canvas.getObjects();
|
|
2568
|
+
const selectedObjects = Array.isArray(activeObjectBackup.selectedObjects) ? activeObjectBackup.selectedObjects : [];
|
|
2569
|
+
const canRestore = selectedObjects.length ? selectedObjects.every((object) => canvasObjects.includes(object)) : canvasObjects.includes(activeObjectBackup.activeObject);
|
|
2570
|
+
if (!canRestore) return;
|
|
2571
|
+
try {
|
|
2572
|
+
this.canvas.setActiveObject(activeObjectBackup.activeObject);
|
|
2573
|
+
} catch (error) {
|
|
2574
|
+
void error;
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
_captureMaskExportBackups(masks) {
|
|
2578
|
+
return (masks || []).map((mask) => ({
|
|
2579
|
+
object: mask,
|
|
2580
|
+
visible: mask.visible,
|
|
2581
|
+
opacity: mask.opacity,
|
|
2582
|
+
fill: mask.fill,
|
|
2583
|
+
strokeWidth: mask.strokeWidth,
|
|
2584
|
+
stroke: mask.stroke,
|
|
2585
|
+
selectable: mask.selectable,
|
|
2586
|
+
lockRotation: mask.lockRotation
|
|
2587
|
+
}));
|
|
2588
|
+
}
|
|
2589
|
+
_restoreMaskExportBackups(maskBackups) {
|
|
2590
|
+
(maskBackups || []).forEach((backup) => {
|
|
2591
|
+
try {
|
|
2592
|
+
backup.object.set({
|
|
2593
|
+
visible: backup.visible,
|
|
2594
|
+
opacity: backup.opacity,
|
|
2595
|
+
fill: backup.fill,
|
|
2596
|
+
strokeWidth: backup.strokeWidth,
|
|
2597
|
+
stroke: backup.stroke,
|
|
2598
|
+
selectable: backup.selectable,
|
|
2599
|
+
lockRotation: backup.lockRotation
|
|
2600
|
+
});
|
|
2601
|
+
backup.object.setCoords();
|
|
2602
|
+
} catch (error) {
|
|
2603
|
+
void error;
|
|
2604
|
+
}
|
|
2605
|
+
});
|
|
2606
|
+
}
|
|
2278
2607
|
/**
|
|
2279
2608
|
* Returns a stable zero-based creation index for label callbacks.
|
|
2280
2609
|
*
|
|
@@ -2347,10 +2676,13 @@ var ImageEditor = class {
|
|
|
2347
2676
|
_hideAllMaskLabels() {
|
|
2348
2677
|
if (!this.canvas) return;
|
|
2349
2678
|
const canvasObjects = this.canvas.getObjects();
|
|
2679
|
+
const canvasObjectSet = new Set(canvasObjects);
|
|
2350
2680
|
const labels = canvasObjects.filter((object) => object.maskLabel);
|
|
2351
2681
|
labels.forEach((label) => {
|
|
2352
2682
|
try {
|
|
2353
|
-
if (
|
|
2683
|
+
if (canvasObjectSet.has(label)) {
|
|
2684
|
+
this.canvas.remove(label);
|
|
2685
|
+
}
|
|
2354
2686
|
} catch (error) {
|
|
2355
2687
|
void error;
|
|
2356
2688
|
}
|
|
@@ -2520,6 +2852,9 @@ var ImageEditor = class {
|
|
|
2520
2852
|
fileType: "png"
|
|
2521
2853
|
}));
|
|
2522
2854
|
this.removeAllMasks(this._withInternalOperationOptions(operationToken, { saveHistory: false }));
|
|
2855
|
+
if (this.canvas.getObjects().some((object) => object.maskId)) {
|
|
2856
|
+
throw new Error("Masks could not be removed during merge");
|
|
2857
|
+
}
|
|
2523
2858
|
await this.loadImage(merged, this._withInternalOperationOptions(operationToken, {
|
|
2524
2859
|
preserveScroll: true,
|
|
2525
2860
|
resetMaskCounter: false
|
|
@@ -2593,7 +2928,11 @@ var ImageEditor = class {
|
|
|
2593
2928
|
const format = this._normalizeImageFormat(options.fileType || options.format);
|
|
2594
2929
|
if (!exportImageArea) {
|
|
2595
2930
|
const masks2 = this.canvas.getObjects().filter((object) => object.maskId || object.maskLabel);
|
|
2931
|
+
const editableMasks = this.canvas.getObjects().filter((object) => object.maskId);
|
|
2596
2932
|
const maskVisibilityBackups = masks2.map((mask) => ({ object: mask, visible: mask.visible }));
|
|
2933
|
+
const maskStyleBackups2 = this._captureMaskExportBackups(editableMasks);
|
|
2934
|
+
const labelBackups2 = this._captureMaskLabelBackups(editableMasks);
|
|
2935
|
+
const activeObjectBackup2 = this._captureActiveObjectBackup();
|
|
2597
2936
|
try {
|
|
2598
2937
|
masks2.forEach((mask) => {
|
|
2599
2938
|
mask.set({ visible: false });
|
|
@@ -2618,20 +2957,16 @@ var ImageEditor = class {
|
|
|
2618
2957
|
void error;
|
|
2619
2958
|
}
|
|
2620
2959
|
});
|
|
2960
|
+
this._restoreMaskExportBackups(maskStyleBackups2);
|
|
2961
|
+
this._restoreMaskLabelBackups(labelBackups2);
|
|
2962
|
+
this._restoreActiveObjectBackup(activeObjectBackup2);
|
|
2621
2963
|
this.canvas.renderAll();
|
|
2622
2964
|
}
|
|
2623
2965
|
}
|
|
2624
2966
|
const masks = this.canvas.getObjects().filter((object) => object.maskId);
|
|
2625
|
-
const maskStyleBackups =
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
fill: mask.fill,
|
|
2629
|
-
strokeWidth: mask.strokeWidth,
|
|
2630
|
-
stroke: mask.stroke,
|
|
2631
|
-
selectable: mask.selectable,
|
|
2632
|
-
lockRotation: mask.lockRotation
|
|
2633
|
-
}));
|
|
2634
|
-
let finalBase64;
|
|
2967
|
+
const maskStyleBackups = this._captureMaskExportBackups(masks);
|
|
2968
|
+
const labelBackups = this._captureMaskLabelBackups(masks);
|
|
2969
|
+
const activeObjectBackup = this._captureActiveObjectBackup();
|
|
2635
2970
|
try {
|
|
2636
2971
|
masks.forEach((mask) => this._removeLabelForMask(mask));
|
|
2637
2972
|
this.canvas.discardActiveObject();
|
|
@@ -2644,7 +2979,7 @@ var ImageEditor = class {
|
|
|
2644
2979
|
this.originalImage.setCoords();
|
|
2645
2980
|
const imageBounds = this.originalImage.getBoundingRect(true, true);
|
|
2646
2981
|
const exportRegion = this._getClampedCanvasRegion(imageBounds);
|
|
2647
|
-
|
|
2982
|
+
return await this._exportCanvasRegionToDataURL({
|
|
2648
2983
|
...exportRegion,
|
|
2649
2984
|
multiplier,
|
|
2650
2985
|
quality,
|
|
@@ -2652,24 +2987,11 @@ var ImageEditor = class {
|
|
|
2652
2987
|
sealPartialEdges: this._getPartialExportEdges(imageBounds)
|
|
2653
2988
|
});
|
|
2654
2989
|
} finally {
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
opacity: backup.opacity,
|
|
2659
|
-
fill: backup.fill,
|
|
2660
|
-
strokeWidth: backup.strokeWidth,
|
|
2661
|
-
stroke: backup.stroke,
|
|
2662
|
-
selectable: backup.selectable,
|
|
2663
|
-
lockRotation: backup.lockRotation
|
|
2664
|
-
});
|
|
2665
|
-
backup.object.setCoords();
|
|
2666
|
-
} catch (error) {
|
|
2667
|
-
void error;
|
|
2668
|
-
}
|
|
2669
|
-
});
|
|
2990
|
+
this._restoreMaskExportBackups(maskStyleBackups);
|
|
2991
|
+
this._restoreMaskLabelBackups(labelBackups);
|
|
2992
|
+
this._restoreActiveObjectBackup(activeObjectBackup);
|
|
2670
2993
|
this.canvas.renderAll();
|
|
2671
2994
|
}
|
|
2672
|
-
return finalBase64;
|
|
2673
2995
|
}
|
|
2674
2996
|
/**
|
|
2675
2997
|
* Backward-compatible alias for {@link ImageEditor#exportImageBase64}.
|
|
@@ -2750,13 +3072,8 @@ var ImageEditor = class {
|
|
|
2750
3072
|
imageElement.src = imageBase64;
|
|
2751
3073
|
});
|
|
2752
3074
|
}
|
|
2753
|
-
const
|
|
3075
|
+
const bytes = this._decodeBase64Payload(imageDataUrl.split(",")[1]);
|
|
2754
3076
|
const mime = `image/${safeFileType}`;
|
|
2755
|
-
let byteIndex = binaryString.length;
|
|
2756
|
-
const bytes = new Uint8Array(byteIndex);
|
|
2757
|
-
while (byteIndex--) {
|
|
2758
|
-
bytes[byteIndex] = binaryString.charCodeAt(byteIndex);
|
|
2759
|
-
}
|
|
2760
3077
|
return new File([bytes], fileName, { type: mime });
|
|
2761
3078
|
}
|
|
2762
3079
|
_clearMaskPlacementMemory() {
|
|
@@ -2801,22 +3118,21 @@ var ImageEditor = class {
|
|
|
2801
3118
|
this._cropPrevEvented = null;
|
|
2802
3119
|
}
|
|
2803
3120
|
_removeCropRect() {
|
|
2804
|
-
if (
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
targetHandlers.handlers.forEach((handlerRecord) => {
|
|
3121
|
+
if (this._cropHandlers && this._cropHandlers.length) {
|
|
3122
|
+
this._cropHandlers.forEach((targetHandlers) => {
|
|
3123
|
+
(targetHandlers.handlers || []).forEach((handlerRecord) => {
|
|
3124
|
+
try {
|
|
2809
3125
|
if (targetHandlers.target && typeof targetHandlers.target.off === "function") {
|
|
2810
3126
|
targetHandlers.target.off(handlerRecord.eventName, handlerRecord.handler);
|
|
2811
3127
|
}
|
|
2812
|
-
})
|
|
3128
|
+
} catch (error) {
|
|
3129
|
+
this._reportWarning("Crop handler cleanup failed", error);
|
|
3130
|
+
}
|
|
2813
3131
|
});
|
|
2814
|
-
}
|
|
2815
|
-
} catch (error) {
|
|
2816
|
-
void error;
|
|
3132
|
+
});
|
|
2817
3133
|
}
|
|
2818
3134
|
try {
|
|
2819
|
-
if (this.canvas) this.canvas.remove(this._cropRect);
|
|
3135
|
+
if (this.canvas && this._cropRect) this.canvas.remove(this._cropRect);
|
|
2820
3136
|
} catch (error) {
|
|
2821
3137
|
void error;
|
|
2822
3138
|
}
|
|
@@ -2903,6 +3219,30 @@ var ImageEditor = class {
|
|
|
2903
3219
|
const nextScaleY = Math.min(maxCropHeight / cropHeight, Math.max(minCropHeight / cropHeight, Number(cropRect.scaleY) || 1));
|
|
2904
3220
|
cropRect.set({ scaleX: nextScaleX, scaleY: nextScaleY });
|
|
2905
3221
|
cropRect.setCoords();
|
|
3222
|
+
const cropBounds = cropRect.getBoundingRect(true, true);
|
|
3223
|
+
const imageLeft = Number(imageBounds.left) || 0;
|
|
3224
|
+
const imageTop = Number(imageBounds.top) || 0;
|
|
3225
|
+
const imageRight = imageLeft + (Number(imageBounds.width) || 0);
|
|
3226
|
+
const imageBottom = imageTop + (Number(imageBounds.height) || 0);
|
|
3227
|
+
let deltaX = 0;
|
|
3228
|
+
let deltaY = 0;
|
|
3229
|
+
if (cropBounds.left < imageLeft) {
|
|
3230
|
+
deltaX = imageLeft - cropBounds.left;
|
|
3231
|
+
} else if (cropBounds.left + cropBounds.width > imageRight) {
|
|
3232
|
+
deltaX = imageRight - (cropBounds.left + cropBounds.width);
|
|
3233
|
+
}
|
|
3234
|
+
if (cropBounds.top < imageTop) {
|
|
3235
|
+
deltaY = imageTop - cropBounds.top;
|
|
3236
|
+
} else if (cropBounds.top + cropBounds.height > imageBottom) {
|
|
3237
|
+
deltaY = imageBottom - (cropBounds.top + cropBounds.height);
|
|
3238
|
+
}
|
|
3239
|
+
if (deltaX || deltaY) {
|
|
3240
|
+
cropRect.set({
|
|
3241
|
+
left: (Number(cropRect.left) || 0) + deltaX,
|
|
3242
|
+
top: (Number(cropRect.top) || 0) + deltaY
|
|
3243
|
+
});
|
|
3244
|
+
cropRect.setCoords();
|
|
3245
|
+
}
|
|
2906
3246
|
this.canvas.requestRenderAll();
|
|
2907
3247
|
} catch (error) {
|
|
2908
3248
|
void error;
|
|
@@ -2962,27 +3302,28 @@ var ImageEditor = class {
|
|
|
2962
3302
|
try {
|
|
2963
3303
|
beforeJson = this._serializeCanvasState();
|
|
2964
3304
|
} catch (error) {
|
|
2965
|
-
this.
|
|
3305
|
+
this._reportError("applyCrop: failed to capture rollback state", error);
|
|
2966
3306
|
beforeJson = null;
|
|
2967
3307
|
}
|
|
3308
|
+
if (!beforeJson) {
|
|
3309
|
+
this.cancelCrop();
|
|
3310
|
+
return;
|
|
3311
|
+
}
|
|
2968
3312
|
const preservedMasks = [];
|
|
2969
3313
|
try {
|
|
2970
3314
|
const masks = this.canvas.getObjects().filter((object) => object.maskId);
|
|
2971
3315
|
if (masks && masks.length) {
|
|
2972
3316
|
masks.forEach((mask) => {
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
}
|
|
2984
|
-
} catch (error) {
|
|
2985
|
-
this._reportWarning("applyCrop: failed to remove mask", error);
|
|
3317
|
+
mask.setCoords();
|
|
3318
|
+
const maskBounds = mask.getBoundingRect(true, true);
|
|
3319
|
+
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;
|
|
3320
|
+
this._removeLabelForMask(mask);
|
|
3321
|
+
this._cleanupMaskEvents(mask);
|
|
3322
|
+
this.canvas.remove(mask);
|
|
3323
|
+
if (shouldPreserveMasks && intersectsCrop) {
|
|
3324
|
+
this._translateObjectByCanvasOffset(mask, -cropRegion.sourceX, -cropRegion.sourceY);
|
|
3325
|
+
mask.set({ visible: true });
|
|
3326
|
+
preservedMasks.push(mask);
|
|
2986
3327
|
}
|
|
2987
3328
|
});
|
|
2988
3329
|
this._clearMaskPlacementMemory();
|
|
@@ -2990,7 +3331,8 @@ var ImageEditor = class {
|
|
|
2990
3331
|
this.canvas.renderAll();
|
|
2991
3332
|
}
|
|
2992
3333
|
} catch (error) {
|
|
2993
|
-
this.
|
|
3334
|
+
await this._restoreStateAfterCropFailure(beforeJson, "applyCrop: failed to prepare masks", error);
|
|
3335
|
+
return;
|
|
2994
3336
|
}
|
|
2995
3337
|
this._removeCropRect();
|
|
2996
3338
|
this._cropMode = false;
|
|
@@ -3047,7 +3389,7 @@ var ImageEditor = class {
|
|
|
3047
3389
|
* @private
|
|
3048
3390
|
*/
|
|
3049
3391
|
_updateInputs() {
|
|
3050
|
-
const scaleInputElement = this._getElement("
|
|
3392
|
+
const scaleInputElement = this._getElement("scalePercentageInput");
|
|
3051
3393
|
if (scaleInputElement) scaleInputElement.value = Math.round(this.currentScale * 100);
|
|
3052
3394
|
}
|
|
3053
3395
|
/**
|
|
@@ -3066,12 +3408,12 @@ var ImageEditor = class {
|
|
|
3066
3408
|
const canUndo = this.historyManager?.canUndo();
|
|
3067
3409
|
const canRedo = this.historyManager?.canRedo();
|
|
3068
3410
|
const isInCropMode = !!this._cropMode;
|
|
3069
|
-
const isBusy = this.
|
|
3411
|
+
const isBusy = this.isBusy();
|
|
3070
3412
|
if (isInCropMode) {
|
|
3071
3413
|
for (const key of Object.keys(this.elements || {})) {
|
|
3072
3414
|
const element = this._getElement(key);
|
|
3073
3415
|
if (!element) continue;
|
|
3074
|
-
if (key === "applyCropBtn" || key === "cancelCropBtn") {
|
|
3416
|
+
if (key === "applyCropButton" || key === "cancelCropButton" || key === "applyCropBtn" || key === "cancelCropBtn") {
|
|
3075
3417
|
this._setDisabled(key, false);
|
|
3076
3418
|
} else {
|
|
3077
3419
|
this._setDisabled(key, true);
|
|
@@ -3079,28 +3421,32 @@ var ImageEditor = class {
|
|
|
3079
3421
|
}
|
|
3080
3422
|
return;
|
|
3081
3423
|
}
|
|
3082
|
-
this._setDisabled("
|
|
3083
|
-
this._setDisabled("
|
|
3084
|
-
this._setDisabled("
|
|
3085
|
-
this._setDisabled("
|
|
3086
|
-
this._setDisabled("
|
|
3087
|
-
this._setDisabled("
|
|
3088
|
-
this._setDisabled("
|
|
3089
|
-
this._setDisabled("
|
|
3090
|
-
this._setDisabled("
|
|
3091
|
-
this._setDisabled("
|
|
3092
|
-
this._setDisabled("
|
|
3093
|
-
this._setDisabled("
|
|
3094
|
-
this._setDisabled("
|
|
3095
|
-
this._setDisabled("
|
|
3096
|
-
this._setDisabled("
|
|
3424
|
+
this._setDisabled("zoomInButton", !hasImage || isBusy || this.currentScale >= this.options.maxScale);
|
|
3425
|
+
this._setDisabled("zoomOutButton", !hasImage || isBusy || this.currentScale <= this.options.minScale);
|
|
3426
|
+
this._setDisabled("rotateLeftButton", !hasImage || isBusy);
|
|
3427
|
+
this._setDisabled("rotateRightButton", !hasImage || isBusy);
|
|
3428
|
+
this._setDisabled("createMaskButton", !hasImage || isBusy);
|
|
3429
|
+
this._setDisabled("removeSelectedMaskButton", !hasSelectedMask || isBusy);
|
|
3430
|
+
this._setDisabled("removeAllMasksButton", !hasMasks || isBusy);
|
|
3431
|
+
this._setDisabled("mergeMasksButton", !hasImage || !hasMasks || isBusy);
|
|
3432
|
+
this._setDisabled("downloadImageButton", !hasImage || isBusy);
|
|
3433
|
+
this._setDisabled("resetImageTransformButton", !hasImage || isDefaultTransform || isBusy);
|
|
3434
|
+
this._setDisabled("undoButton", !hasImage || isBusy || !canUndo);
|
|
3435
|
+
this._setDisabled("redoButton", !hasImage || isBusy || !canRedo);
|
|
3436
|
+
this._setDisabled("enterCropModeButton", !hasImage || isBusy);
|
|
3437
|
+
this._setDisabled("applyCropButton", true);
|
|
3438
|
+
this._setDisabled("cancelCropButton", true);
|
|
3439
|
+
this._setDisabled("scalePercentageInput", !hasImage || isBusy);
|
|
3440
|
+
this._setDisabled("rotateLeftDegreesInput", !hasImage || isBusy);
|
|
3441
|
+
this._setDisabled("rotateRightDegreesInput", !hasImage || isBusy);
|
|
3442
|
+
this._setDisabled("maskList", !hasImage || isBusy);
|
|
3097
3443
|
this._setDisabled("imageInput", isBusy);
|
|
3098
3444
|
this._setDisabled("uploadArea", isBusy);
|
|
3099
3445
|
}
|
|
3100
3446
|
/**
|
|
3101
3447
|
* Enables or disables a specific UI element (typically a button) by its key.
|
|
3102
3448
|
*
|
|
3103
|
-
* @param {string} key - Key of the element in this.elements (e.g. '
|
|
3449
|
+
* @param {string} key - Key of the element in this.elements (e.g. 'zoomInButton').
|
|
3104
3450
|
* @param {boolean} disabled - If true, disables the element; otherwise enables.
|
|
3105
3451
|
* @private
|
|
3106
3452
|
*/
|
|
@@ -3224,14 +3570,7 @@ var ImageEditor = class {
|
|
|
3224
3570
|
} catch (error) {
|
|
3225
3571
|
void error;
|
|
3226
3572
|
}
|
|
3227
|
-
if (this._cropRect)
|
|
3228
|
-
try {
|
|
3229
|
-
this.canvas.remove(this._cropRect);
|
|
3230
|
-
} catch (error) {
|
|
3231
|
-
void error;
|
|
3232
|
-
}
|
|
3233
|
-
this._cropRect = null;
|
|
3234
|
-
}
|
|
3573
|
+
if (this._cropRect) this._removeCropRect();
|
|
3235
3574
|
if (this.containerElement && this._containerOriginalOverflow) {
|
|
3236
3575
|
try {
|
|
3237
3576
|
this._restoreContainerOverflowState();
|
|
@@ -3254,11 +3593,19 @@ var ImageEditor = class {
|
|
|
3254
3593
|
this.canvasElement.style.display = this._canvasElementOriginalStyle.display;
|
|
3255
3594
|
this.canvasElement.style.width = this._canvasElementOriginalStyle.width;
|
|
3256
3595
|
this.canvasElement.style.height = this._canvasElementOriginalStyle.height;
|
|
3596
|
+
this.canvasElement.style.maxWidth = this._canvasElementOriginalStyle.maxWidth;
|
|
3257
3597
|
} catch (error) {
|
|
3258
3598
|
void error;
|
|
3259
3599
|
}
|
|
3260
3600
|
}
|
|
3261
3601
|
if (this.canvas) {
|
|
3602
|
+
try {
|
|
3603
|
+
this.canvas.getObjects().forEach((object) => {
|
|
3604
|
+
if (object && object.maskId) this._cleanupMaskEvents(object);
|
|
3605
|
+
});
|
|
3606
|
+
} catch (error) {
|
|
3607
|
+
void error;
|
|
3608
|
+
}
|
|
3262
3609
|
try {
|
|
3263
3610
|
this.canvas.dispose();
|
|
3264
3611
|
} catch (error) {
|
|
@@ -3355,7 +3702,7 @@ var AnimationQueue = class {
|
|
|
3355
3702
|
task.reject(error);
|
|
3356
3703
|
}
|
|
3357
3704
|
} finally {
|
|
3358
|
-
if (
|
|
3705
|
+
if (this.currentTask === task) this.currentTask = null;
|
|
3359
3706
|
}
|
|
3360
3707
|
}
|
|
3361
3708
|
} finally {
|
|
@@ -3408,9 +3755,9 @@ var HistoryManager = class {
|
|
|
3408
3755
|
execute(command) {
|
|
3409
3756
|
const result = command.execute();
|
|
3410
3757
|
if (result && typeof result.then === "function") {
|
|
3411
|
-
return Promise.resolve(result).then(() => {
|
|
3758
|
+
return this.enqueue(() => Promise.resolve(result).then(() => {
|
|
3412
3759
|
this.push(command);
|
|
3413
|
-
});
|
|
3760
|
+
}));
|
|
3414
3761
|
}
|
|
3415
3762
|
this.push(command);
|
|
3416
3763
|
return result;
|