@bensitu/image-editor 2.0.0 → 2.1.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 +118 -16
- package/dist/cjs/index.cjs +1800 -330
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/animation/animation-queue.js +16 -9
- package/dist/esm/animation/animation-queue.js.map +1 -1
- package/dist/esm/core/default-options.js +216 -9
- package/dist/esm/core/default-options.js.map +1 -1
- package/dist/esm/core/operation-guard.js +28 -0
- package/dist/esm/core/operation-guard.js.map +1 -1
- package/dist/esm/core/public-types.js.map +1 -1
- package/dist/esm/core/state-serializer.js +5 -4
- package/dist/esm/core/state-serializer.js.map +1 -1
- package/dist/esm/crop/crop-controller.js +4 -2
- package/dist/esm/crop/crop-controller.js.map +1 -1
- package/dist/esm/export/export-service.js +21 -10
- package/dist/esm/export/export-service.js.map +1 -1
- package/dist/esm/fabric/fabric-animation.js +56 -4
- package/dist/esm/fabric/fabric-animation.js.map +1 -1
- package/dist/esm/image/image-loader.js +9 -16
- package/dist/esm/image/image-loader.js.map +1 -1
- package/dist/esm/image/image-resampler.js +7 -2
- package/dist/esm/image/image-resampler.js.map +1 -1
- package/dist/esm/image/layout-manager.js +2 -20
- package/dist/esm/image/layout-manager.js.map +1 -1
- package/dist/esm/image/transform-controller.js.map +1 -1
- package/dist/esm/image-editor.js +383 -47
- package/dist/esm/image-editor.js.map +1 -1
- package/dist/esm/mask/mask-factory.js +53 -29
- package/dist/esm/mask/mask-factory.js.map +1 -1
- package/dist/esm/mask/mask-list.js +9 -3
- package/dist/esm/mask/mask-list.js.map +1 -1
- package/dist/esm/mosaic/mosaic-controller.js +670 -0
- package/dist/esm/mosaic/mosaic-controller.js.map +1 -0
- package/dist/esm/mosaic/mosaic-geometry.js +81 -0
- package/dist/esm/mosaic/mosaic-geometry.js.map +1 -0
- package/dist/esm/mosaic/mosaic-pixelate.js +71 -0
- package/dist/esm/mosaic/mosaic-pixelate.js.map +1 -0
- package/dist/esm/ui/dom-bindings.js +10 -3
- package/dist/esm/ui/dom-bindings.js.map +1 -1
- package/dist/esm/utils/number.js.map +1 -1
- package/dist/types/animation/animation-queue.d.ts.map +1 -1
- package/dist/types/core/default-options.d.ts +34 -6
- package/dist/types/core/default-options.d.ts.map +1 -1
- package/dist/types/core/errors.d.ts +1 -1
- package/dist/types/core/operation-guard.d.ts +2 -0
- package/dist/types/core/operation-guard.d.ts.map +1 -1
- package/dist/types/core/public-types.d.ts +123 -13
- package/dist/types/core/public-types.d.ts.map +1 -1
- package/dist/types/core/state-serializer.d.ts +3 -1
- package/dist/types/core/state-serializer.d.ts.map +1 -1
- package/dist/types/crop/crop-controller.d.ts.map +1 -1
- package/dist/types/export/export-service.d.ts.map +1 -1
- package/dist/types/fabric/fabric-animation.d.ts.map +1 -1
- package/dist/types/image/image-loader.d.ts +2 -4
- package/dist/types/image/image-loader.d.ts.map +1 -1
- package/dist/types/image/image-resampler.d.ts +1 -1
- package/dist/types/image/image-resampler.d.ts.map +1 -1
- package/dist/types/image/layout-manager.d.ts +5 -49
- package/dist/types/image/layout-manager.d.ts.map +1 -1
- package/dist/types/image/transform-controller.d.ts +1 -2
- package/dist/types/image/transform-controller.d.ts.map +1 -1
- package/dist/types/image-editor.d.ts +20 -9
- package/dist/types/image-editor.d.ts.map +1 -1
- package/dist/types/index.d.cts +1 -1
- package/dist/types/index.d.cts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/mask/mask-factory.d.ts +24 -21
- package/dist/types/mask/mask-factory.d.ts.map +1 -1
- package/dist/types/mask/mask-list.d.ts.map +1 -1
- package/dist/types/mosaic/mosaic-controller.d.ts +82 -0
- package/dist/types/mosaic/mosaic-controller.d.ts.map +1 -0
- package/dist/types/mosaic/mosaic-geometry.d.ts +29 -0
- package/dist/types/mosaic/mosaic-geometry.d.ts.map +1 -0
- package/dist/types/mosaic/mosaic-pixelate.d.ts +23 -0
- package/dist/types/mosaic/mosaic-pixelate.d.ts.map +1 -0
- package/dist/types/ui/dom-bindings.d.ts +3 -1
- package/dist/types/ui/dom-bindings.d.ts.map +1 -1
- package/dist/types/utils/number.d.ts +1 -2
- package/dist/types/utils/number.d.ts.map +1 -1
- package/dist/umd/image-editor.umd.js +1 -1
- package/dist/umd/image-editor.umd.js.map +1 -1
- package/package.json +1 -1
package/dist/esm/image-editor.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { AnimationQueue } from './animation/animation-queue.js';
|
|
2
2
|
import { reportError, reportWarning } from './core/callback-reporter.js';
|
|
3
|
-
import { resolveOptions } from './core/default-options.js';
|
|
3
|
+
import { areResolvedMosaicConfigsEqual, cloneResolvedMosaicConfig, getInvalidMosaicConfigFields, isLayoutMode, mergeMosaicConfigPatch, resolveOptions, } from './core/default-options.js';
|
|
4
4
|
import { OperationGuard } from './core/operation-guard.js';
|
|
5
5
|
import { loadFromState as loadFromStateImpl, saveState as saveStateImpl, } from './core/state-serializer.js';
|
|
6
6
|
import { Command, HistoryManager } from './history/history-manager.js';
|
|
7
7
|
import { detectFabric } from './fabric/fabric-adapter.js';
|
|
8
8
|
import { isMaskObject } from './core/public-types.js';
|
|
9
9
|
import { applyCrop as applyCropImpl, cancelCrop as cancelCropImpl, enterCropMode as enterCropModeImpl, } from './crop/crop-controller.js';
|
|
10
|
+
import { enterMosaicMode as enterMosaicModeImpl, exitMosaicMode as exitMosaicModeImpl, updateMosaicPreview, } from './mosaic/mosaic-controller.js';
|
|
10
11
|
import { downloadImage as downloadImageImpl, exportImageBase64 as exportImageBase64Impl, exportImageFile as exportImageFileImpl, mergeMasks as mergeMasksImpl, } from './export/export-service.js';
|
|
11
12
|
import { loadImage as loadImageImpl } from './image/image-loader.js';
|
|
12
|
-
import { ViewportCache, applyCanvasDimensions, computeScrollableCanvasSize,
|
|
13
|
+
import { ViewportCache, applyCanvasDimensions, computeScrollableCanvasSize, measureScrollbarSize, } from './image/layout-manager.js';
|
|
13
14
|
import { TransformController } from './image/transform-controller.js';
|
|
14
15
|
import { createMask as createMaskImpl, removeAllMasks as removeAllMasksImpl, removeSelectedMask as removeSelectedMaskImpl, } from './mask/mask-factory.js';
|
|
15
16
|
import { createLabelForMask, hideAllMaskLabels, removeLabelForMask, showLabelForMask, syncMaskLabel, } from './mask/mask-label-manager.js';
|
|
@@ -20,8 +21,8 @@ import { setPlaceholderVisible as setPlaceholderVisibleImpl } from './ui/visibil
|
|
|
20
21
|
import { inferImageMimeType, readFileAsDataUrl, resetFileInput } from './utils/file.js';
|
|
21
22
|
import { detectSourceMimeType } from './image/image-resampler.js';
|
|
22
23
|
const LAYOUT_EPSILON = 0.5;
|
|
23
|
-
const INTERNAL_OPERATION_TOKEN = Symbol
|
|
24
|
-
const INTERNAL_ALLOW_DURING_ANIMATION_QUEUE = Symbol
|
|
24
|
+
const INTERNAL_OPERATION_TOKEN = Symbol('ImageEditorInternalOperation');
|
|
25
|
+
const INTERNAL_ALLOW_DURING_ANIMATION_QUEUE = Symbol('ImageEditorAllowDuringAnimationQueue');
|
|
25
26
|
const CROP_MODE_CONTROL_KEYS = [
|
|
26
27
|
'scalePercentageInput',
|
|
27
28
|
'rotateLeftDegreesInput',
|
|
@@ -42,9 +43,85 @@ const CROP_MODE_CONTROL_KEYS = [
|
|
|
42
43
|
'enterCropModeButton',
|
|
43
44
|
'applyCropButton',
|
|
44
45
|
'cancelCropButton',
|
|
46
|
+
'enterMosaicModeButton',
|
|
47
|
+
'exitMosaicModeButton',
|
|
48
|
+
'mosaicBrushSizeInput',
|
|
49
|
+
'mosaicBlockSizeInput',
|
|
45
50
|
];
|
|
46
51
|
const CROP_MODE_ENABLED_KEYS = ['applyCropButton', 'cancelCropButton'];
|
|
47
52
|
const CROP_SESSION_ALLOWED_OPERATIONS = new Set(['applyCrop', 'cancelCrop']);
|
|
53
|
+
const MOSAIC_MODE_CONTROL_KEYS = [
|
|
54
|
+
'scalePercentageInput',
|
|
55
|
+
'rotateLeftDegreesInput',
|
|
56
|
+
'rotateRightDegreesInput',
|
|
57
|
+
'rotateLeftButton',
|
|
58
|
+
'rotateRightButton',
|
|
59
|
+
'createMaskButton',
|
|
60
|
+
'removeSelectedMaskButton',
|
|
61
|
+
'removeAllMasksButton',
|
|
62
|
+
'mergeMasksButton',
|
|
63
|
+
'downloadImageButton',
|
|
64
|
+
'zoomInButton',
|
|
65
|
+
'zoomOutButton',
|
|
66
|
+
'resetImageTransformButton',
|
|
67
|
+
'undoButton',
|
|
68
|
+
'redoButton',
|
|
69
|
+
'imageInput',
|
|
70
|
+
'enterCropModeButton',
|
|
71
|
+
'applyCropButton',
|
|
72
|
+
'cancelCropButton',
|
|
73
|
+
'enterMosaicModeButton',
|
|
74
|
+
'exitMosaicModeButton',
|
|
75
|
+
'mosaicBrushSizeInput',
|
|
76
|
+
'mosaicBlockSizeInput',
|
|
77
|
+
];
|
|
78
|
+
const MOSAIC_MODE_ENABLED_KEYS = [
|
|
79
|
+
'exitMosaicModeButton',
|
|
80
|
+
'mosaicBrushSizeInput',
|
|
81
|
+
'mosaicBlockSizeInput',
|
|
82
|
+
];
|
|
83
|
+
const MOSAIC_SESSION_ALLOWED_OPERATIONS = new Set([
|
|
84
|
+
'exitMosaicMode',
|
|
85
|
+
'applyMosaic',
|
|
86
|
+
'setMosaicConfig',
|
|
87
|
+
'resetMosaicConfig',
|
|
88
|
+
'setMosaicBrushSize',
|
|
89
|
+
'setMosaicBlockSize',
|
|
90
|
+
'saveState',
|
|
91
|
+
]);
|
|
92
|
+
const SCROLLBAR_SETTLE_EPSILON = 1;
|
|
93
|
+
const IMAGE_EDITOR_OPERATIONS = new Set([
|
|
94
|
+
'init',
|
|
95
|
+
'loadImage',
|
|
96
|
+
'loadFromState',
|
|
97
|
+
'saveState',
|
|
98
|
+
'scaleImage',
|
|
99
|
+
'rotateImage',
|
|
100
|
+
'resetImageTransform',
|
|
101
|
+
'createMask',
|
|
102
|
+
'removeSelectedMask',
|
|
103
|
+
'removeAllMasks',
|
|
104
|
+
'mergeMasks',
|
|
105
|
+
'enterCropMode',
|
|
106
|
+
'applyCrop',
|
|
107
|
+
'cancelCrop',
|
|
108
|
+
'enterMosaicMode',
|
|
109
|
+
'exitMosaicMode',
|
|
110
|
+
'applyMosaic',
|
|
111
|
+
'setMosaicConfig',
|
|
112
|
+
'resetMosaicConfig',
|
|
113
|
+
'setMosaicBrushSize',
|
|
114
|
+
'setMosaicBlockSize',
|
|
115
|
+
'undo',
|
|
116
|
+
'redo',
|
|
117
|
+
'exportImageBase64',
|
|
118
|
+
'exportImageFile',
|
|
119
|
+
'downloadImage',
|
|
120
|
+
'dispose',
|
|
121
|
+
]);
|
|
122
|
+
function isImageEditorOperation(value) {
|
|
123
|
+
return value !== null && IMAGE_EDITOR_OPERATIONS.has(value);
|
|
124
|
+
}
|
|
48
125
|
export class ImageEditor {
|
|
49
126
|
constructor(fabricModuleOrOptions = {}, options = {}) {
|
|
50
127
|
var _a;
|
|
@@ -66,6 +143,24 @@ export class ImageEditor {
|
|
|
66
143
|
writable: true,
|
|
67
144
|
value: void 0
|
|
68
145
|
});
|
|
146
|
+
Object.defineProperty(this, "currentLayoutMode", {
|
|
147
|
+
enumerable: true,
|
|
148
|
+
configurable: true,
|
|
149
|
+
writable: true,
|
|
150
|
+
value: 'expand'
|
|
151
|
+
});
|
|
152
|
+
Object.defineProperty(this, "defaultMosaicConfig", {
|
|
153
|
+
enumerable: true,
|
|
154
|
+
configurable: true,
|
|
155
|
+
writable: true,
|
|
156
|
+
value: void 0
|
|
157
|
+
});
|
|
158
|
+
Object.defineProperty(this, "currentMosaicConfig", {
|
|
159
|
+
enumerable: true,
|
|
160
|
+
configurable: true,
|
|
161
|
+
writable: true,
|
|
162
|
+
value: void 0
|
|
163
|
+
});
|
|
69
164
|
Object.defineProperty(this, "canvas", {
|
|
70
165
|
enumerable: true,
|
|
71
166
|
configurable: true,
|
|
@@ -204,6 +299,12 @@ export class ImageEditor {
|
|
|
204
299
|
writable: true,
|
|
205
300
|
value: null
|
|
206
301
|
});
|
|
302
|
+
Object.defineProperty(this, "mosaicSession", {
|
|
303
|
+
enumerable: true,
|
|
304
|
+
configurable: true,
|
|
305
|
+
writable: true,
|
|
306
|
+
value: null
|
|
307
|
+
});
|
|
207
308
|
Object.defineProperty(this, "domBindings", {
|
|
208
309
|
enumerable: true,
|
|
209
310
|
configurable: true,
|
|
@@ -244,9 +345,15 @@ export class ImageEditor {
|
|
|
244
345
|
this.fabricModule = (_a = detected.fabric) !== null && _a !== void 0 ? _a : {};
|
|
245
346
|
this.isFabricLoaded = detected.isFabricLoaded;
|
|
246
347
|
this.options = resolveOptions(detected.options);
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
348
|
+
this.currentLayoutMode = this.options.layoutMode;
|
|
349
|
+
this.defaultMosaicConfig = this.options.defaultMosaicConfig;
|
|
350
|
+
this.currentMosaicConfig = cloneResolvedMosaicConfig(this.defaultMosaicConfig);
|
|
351
|
+
const rawDefaultLayoutMode = detected.options
|
|
352
|
+
.defaultLayoutMode;
|
|
353
|
+
if (rawDefaultLayoutMode !== undefined && !isLayoutMode(rawDefaultLayoutMode)) {
|
|
354
|
+
reportWarning(this.options, new TypeError(`[ImageEditor] Unsupported defaultLayoutMode ` +
|
|
355
|
+
`${JSON.stringify(rawDefaultLayoutMode)}. ` +
|
|
356
|
+
'Expected "fit", "cover", or "expand".'), 'Invalid defaultLayoutMode fell back to "expand".');
|
|
250
357
|
}
|
|
251
358
|
this.operationGuard = new OperationGuard();
|
|
252
359
|
this.animQueue = new AnimationQueue();
|
|
@@ -288,10 +395,14 @@ export class ImageEditor {
|
|
|
288
395
|
enterCropModeButton: 'enterCropModeButton',
|
|
289
396
|
applyCropButton: 'applyCropButton',
|
|
290
397
|
cancelCropButton: 'cancelCropButton',
|
|
398
|
+
enterMosaicModeButton: 'enterMosaicModeButton',
|
|
399
|
+
exitMosaicModeButton: 'exitMosaicModeButton',
|
|
400
|
+
mosaicBrushSizeInput: 'mosaicBrushSizeInput',
|
|
401
|
+
mosaicBlockSizeInput: 'mosaicBlockSizeInput',
|
|
291
402
|
uploadArea: 'uploadArea',
|
|
292
403
|
};
|
|
293
404
|
this.elements = { ...defaults, ...idMap };
|
|
294
|
-
this.domBindings = new DomBindings((key) => this.elements[key], () => this.isDisposed);
|
|
405
|
+
this.domBindings = new DomBindings((key) => this.elements[key], () => this.isDisposed, () => { var _a, _b; return (_b = (_a = this.canvasElement) === null || _a === void 0 ? void 0 : _a.ownerDocument) !== null && _b !== void 0 ? _b : document; });
|
|
295
406
|
this.initCanvas();
|
|
296
407
|
this.transformController = new TransformController(this.buildTransformContext());
|
|
297
408
|
this.bindDomEvents();
|
|
@@ -442,6 +553,26 @@ export class ImageEditor {
|
|
|
442
553
|
this.bindElementIfExists('cancelCropButton', 'click', () => {
|
|
443
554
|
this.cancelCrop();
|
|
444
555
|
});
|
|
556
|
+
this.bindElementIfExists('enterMosaicModeButton', 'click', () => {
|
|
557
|
+
this.enterMosaicMode();
|
|
558
|
+
});
|
|
559
|
+
this.bindElementIfExists('exitMosaicModeButton', 'click', () => {
|
|
560
|
+
this.exitMosaicMode();
|
|
561
|
+
});
|
|
562
|
+
const bindMosaicSizeInput = (key, applyValue) => {
|
|
563
|
+
const handler = (event) => {
|
|
564
|
+
const parsed = parseFloat(event.target.value);
|
|
565
|
+
applyValue(parsed);
|
|
566
|
+
};
|
|
567
|
+
this.bindElementIfExists(key, 'input', handler);
|
|
568
|
+
this.bindElementIfExists(key, 'change', handler);
|
|
569
|
+
};
|
|
570
|
+
bindMosaicSizeInput('mosaicBrushSizeInput', (value) => {
|
|
571
|
+
this.setMosaicBrushSize(value);
|
|
572
|
+
});
|
|
573
|
+
bindMosaicSizeInput('mosaicBlockSizeInput', (value) => {
|
|
574
|
+
this.setMosaicBlockSize(value);
|
|
575
|
+
});
|
|
445
576
|
}
|
|
446
577
|
bindElementIfExists(key, event, handler) {
|
|
447
578
|
var _a;
|
|
@@ -477,6 +608,9 @@ export class ImageEditor {
|
|
|
477
608
|
}
|
|
478
609
|
}
|
|
479
610
|
async loadImage(base64, options = {}) {
|
|
611
|
+
return this.loadImageInternal(base64, options);
|
|
612
|
+
}
|
|
613
|
+
async loadImageInternal(base64, options = {}) {
|
|
480
614
|
if (!this.isFabricLoaded || !this.canvas)
|
|
481
615
|
return;
|
|
482
616
|
if (this.isDisposed)
|
|
@@ -496,7 +630,7 @@ export class ImageEditor {
|
|
|
496
630
|
const loadImageContext = {
|
|
497
631
|
fabric: this.fabricModule,
|
|
498
632
|
canvas: this.canvas,
|
|
499
|
-
options: this.
|
|
633
|
+
options: this.getRuntimeOptions(),
|
|
500
634
|
containerElement: this.containerElement,
|
|
501
635
|
placeholderElement: this.placeholderElement,
|
|
502
636
|
viewportCache: this.viewportCache,
|
|
@@ -588,6 +722,11 @@ export class ImageEditor {
|
|
|
588
722
|
!CROP_SESSION_ALLOWED_OPERATIONS.has(operationName)) {
|
|
589
723
|
throw new Error(`[ImageEditor] Cannot run "${operationName}" while crop mode is active.`);
|
|
590
724
|
}
|
|
725
|
+
if (this.mosaicSession &&
|
|
726
|
+
!this.operationGuard.isOwnOperation(token) &&
|
|
727
|
+
!MOSAIC_SESSION_ALLOWED_OPERATIONS.has(operationName)) {
|
|
728
|
+
throw new Error(`[ImageEditor] Cannot run "${operationName}" while mosaic mode is active.`);
|
|
729
|
+
}
|
|
591
730
|
if (this.animQueue.isBusy() && !this.canRunDuringAnimationQueue(options)) {
|
|
592
731
|
throw new Error(`[ImageEditor] Cannot run "${operationName}" while an animation is queued.`);
|
|
593
732
|
}
|
|
@@ -597,10 +736,17 @@ export class ImageEditor {
|
|
|
597
736
|
this.assertIdleForOperation(operationName, options);
|
|
598
737
|
return true;
|
|
599
738
|
}
|
|
600
|
-
catch {
|
|
739
|
+
catch (error) {
|
|
740
|
+
if (!this.isExpectedIdleGuardError(error, operationName)) {
|
|
741
|
+
throw error;
|
|
742
|
+
}
|
|
601
743
|
return false;
|
|
602
744
|
}
|
|
603
745
|
}
|
|
746
|
+
isExpectedIdleGuardError(error, operationName) {
|
|
747
|
+
return (error instanceof Error &&
|
|
748
|
+
error.message.startsWith(`[ImageEditor] Cannot run "${operationName}" `));
|
|
749
|
+
}
|
|
604
750
|
assertCanQueueAnimation(operationName, options) {
|
|
605
751
|
this.operationGuard.assertCanQueueAnimation(operationName, this.getInternalOperationToken(options));
|
|
606
752
|
}
|
|
@@ -612,17 +758,26 @@ export class ImageEditor {
|
|
|
612
758
|
((_b = this.originalImage.height) !== null && _b !== void 0 ? _b : 0) > 0);
|
|
613
759
|
}
|
|
614
760
|
isBusy() {
|
|
615
|
-
return this.operationGuard.isBusy() ||
|
|
761
|
+
return (this.operationGuard.isBusy() ||
|
|
762
|
+
this.animQueue.isBusy() ||
|
|
763
|
+
this.cropSession !== null ||
|
|
764
|
+
this.mosaicSession !== null);
|
|
616
765
|
}
|
|
617
766
|
setLayoutMode(mode) {
|
|
618
|
-
if (mode
|
|
767
|
+
if (!isLayoutMode(mode)) {
|
|
619
768
|
reportWarning(this.options, new TypeError(`[ImageEditor] Unsupported layout mode ${JSON.stringify(mode)}. ` +
|
|
620
769
|
'Expected "fit", "cover", or "expand".'), 'Ignored invalid layout mode.');
|
|
621
770
|
return;
|
|
622
771
|
}
|
|
623
|
-
this.
|
|
624
|
-
|
|
625
|
-
|
|
772
|
+
this.currentLayoutMode = mode;
|
|
773
|
+
}
|
|
774
|
+
getRuntimeOptions() {
|
|
775
|
+
if (this.currentLayoutMode === this.options.layoutMode)
|
|
776
|
+
return this.options;
|
|
777
|
+
return Object.freeze({
|
|
778
|
+
...this.options,
|
|
779
|
+
layoutMode: this.currentLayoutMode,
|
|
780
|
+
});
|
|
626
781
|
}
|
|
627
782
|
buildCallbackContext(operation, isInternalOperation = false) {
|
|
628
783
|
return { operation, isInternalOperation };
|
|
@@ -631,7 +786,7 @@ export class ImageEditor {
|
|
|
631
786
|
const internal = this.getInternalOperationToken(options);
|
|
632
787
|
const activeOperation = this.operationGuard.activeOperationName();
|
|
633
788
|
if (internal && activeOperation) {
|
|
634
|
-
return this.buildCallbackContext(activeOperation, true);
|
|
789
|
+
return this.buildCallbackContext(isImageEditorOperation(activeOperation) ? activeOperation : fallback, true);
|
|
635
790
|
}
|
|
636
791
|
return this.buildCallbackContext(fallback, false);
|
|
637
792
|
}
|
|
@@ -698,6 +853,7 @@ export class ImageEditor {
|
|
|
698
853
|
currentRotation: this.currentRotation,
|
|
699
854
|
isBusy: this.isBusy(),
|
|
700
855
|
isCropMode: this.cropSession !== null,
|
|
856
|
+
isMosaicMode: this.mosaicSession !== null,
|
|
701
857
|
canUndo: this.historyManager.canUndo(),
|
|
702
858
|
canRedo: this.historyManager.canRedo(),
|
|
703
859
|
canvasWidth,
|
|
@@ -785,7 +941,7 @@ export class ImageEditor {
|
|
|
785
941
|
const boundingRect = this.originalImage.getBoundingRect();
|
|
786
942
|
const scrollbarSize = measureScrollbarSize((_b = (_a = this.containerElement) === null || _a === void 0 ? void 0 : _a.ownerDocument) !== null && _b !== void 0 ? _b : null);
|
|
787
943
|
const viewport = this.measureLayoutViewport(scrollbarSize);
|
|
788
|
-
if (this.
|
|
944
|
+
if (this.currentLayoutMode === 'fit' || this.currentLayoutMode === 'cover') {
|
|
789
945
|
const canvasSize = computeScrollableCanvasSize(boundingRect.width, boundingRect.height, viewport, scrollbarSize);
|
|
790
946
|
this.setCanvasSizePx(canvasSize.width, canvasSize.height);
|
|
791
947
|
return;
|
|
@@ -807,14 +963,14 @@ export class ImageEditor {
|
|
|
807
963
|
const canvasH = Math.ceil(this.canvas.getHeight());
|
|
808
964
|
const clipsImage = boundingRect.width > canvasW + LAYOUT_EPSILON ||
|
|
809
965
|
boundingRect.height > canvasH + LAYOUT_EPSILON;
|
|
810
|
-
if (this.
|
|
966
|
+
if (this.currentLayoutMode === 'fit' || this.currentLayoutMode === 'cover') {
|
|
811
967
|
const staleOverflowWidth = canvasW > viewport.width + LAYOUT_EPSILON &&
|
|
812
968
|
boundingRect.width <= viewport.width + LAYOUT_EPSILON;
|
|
813
969
|
const staleOverflowHeight = canvasH > viewport.height + LAYOUT_EPSILON &&
|
|
814
970
|
boundingRect.height <= viewport.height + LAYOUT_EPSILON;
|
|
815
971
|
return clipsImage || staleOverflowWidth || staleOverflowHeight;
|
|
816
972
|
}
|
|
817
|
-
if (this.
|
|
973
|
+
if (this.currentLayoutMode === 'expand') {
|
|
818
974
|
const expectedW = Math.max(viewport.width, Math.ceil(boundingRect.width));
|
|
819
975
|
const expectedH = Math.max(viewport.height, Math.ceil(boundingRect.height));
|
|
820
976
|
return (Math.abs(canvasW - expectedW) > LAYOUT_EPSILON ||
|
|
@@ -822,6 +978,33 @@ export class ImageEditor {
|
|
|
822
978
|
}
|
|
823
979
|
return clipsImage;
|
|
824
980
|
}
|
|
981
|
+
settleFitCoverScrollbarsAfterStateRestore() {
|
|
982
|
+
if (!this.canvas ||
|
|
983
|
+
!this.containerElement ||
|
|
984
|
+
(this.currentLayoutMode !== 'fit' && this.currentLayoutMode !== 'cover')) {
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
const canvasW = Math.ceil(this.canvas.getWidth());
|
|
988
|
+
const canvasH = Math.ceil(this.canvas.getHeight());
|
|
989
|
+
if (canvasW <= 1 || canvasH <= 1)
|
|
990
|
+
return;
|
|
991
|
+
const clientW = Math.floor(this.containerElement.clientWidth || 0);
|
|
992
|
+
const clientH = Math.floor(this.containerElement.clientHeight || 0);
|
|
993
|
+
if (clientW <= 0 || clientH <= 0)
|
|
994
|
+
return;
|
|
995
|
+
const scrollW = Math.ceil(this.containerElement.scrollWidth || 0);
|
|
996
|
+
const scrollH = Math.ceil(this.containerElement.scrollHeight || 0);
|
|
997
|
+
const hasHorizontalScrollbar = scrollW > clientW + LAYOUT_EPSILON;
|
|
998
|
+
const hasVerticalScrollbar = scrollH > clientH + LAYOUT_EPSILON;
|
|
999
|
+
if (!hasHorizontalScrollbar && !hasVerticalScrollbar)
|
|
1000
|
+
return;
|
|
1001
|
+
const nudgeWidth = hasVerticalScrollbar && Math.abs(canvasW - clientW) <= SCROLLBAR_SETTLE_EPSILON;
|
|
1002
|
+
const nudgeHeight = hasHorizontalScrollbar && Math.abs(canvasH - clientH) <= SCROLLBAR_SETTLE_EPSILON;
|
|
1003
|
+
if (!nudgeWidth && !nudgeHeight)
|
|
1004
|
+
return;
|
|
1005
|
+
this.setCanvasSizePx(nudgeWidth ? canvasW - 1 : canvasW, nudgeHeight ? canvasH - 1 : canvasH);
|
|
1006
|
+
this.setCanvasSizePx(canvasW, canvasH);
|
|
1007
|
+
}
|
|
825
1008
|
captureImageDisplayGeometry() {
|
|
826
1009
|
if (!this.canvas || !this.originalImage)
|
|
827
1010
|
return null;
|
|
@@ -886,11 +1069,7 @@ export class ImageEditor {
|
|
|
886
1069
|
afterTransformSnap: () => {
|
|
887
1070
|
if (this.isDisposed || !this.canvas || !this.originalImage)
|
|
888
1071
|
return;
|
|
889
|
-
|
|
890
|
-
this.options.coverImageToCanvas ||
|
|
891
|
-
this.options.fitImageToCanvas) {
|
|
892
|
-
this.updateCanvasSizeToImageBounds();
|
|
893
|
-
}
|
|
1072
|
+
this.updateCanvasSizeToImageBounds();
|
|
894
1073
|
this.alignObjectBoundingBoxToCanvasTopLeft(this.originalImage);
|
|
895
1074
|
this.canvas
|
|
896
1075
|
.getObjects()
|
|
@@ -1058,14 +1237,13 @@ export class ImageEditor {
|
|
|
1058
1237
|
this.currentImageMimeType = null;
|
|
1059
1238
|
}
|
|
1060
1239
|
this.isImageLoadedToCanvas = !!this.originalImage;
|
|
1061
|
-
if (this.originalImage &&
|
|
1062
|
-
(this.options.expandCanvasToImage ||
|
|
1063
|
-
this.options.coverImageToCanvas ||
|
|
1064
|
-
this.options.fitImageToCanvas) &&
|
|
1065
|
-
this.shouldNormalizeCanvasSizeAfterStateRestore()) {
|
|
1240
|
+
if (this.originalImage && this.shouldNormalizeCanvasSizeAfterStateRestore()) {
|
|
1066
1241
|
this.updateCanvasSizeToImageBounds();
|
|
1067
1242
|
this.alignObjectBoundingBoxToCanvasTopLeft(this.originalImage);
|
|
1068
1243
|
}
|
|
1244
|
+
if (this.originalImage) {
|
|
1245
|
+
this.settleFitCoverScrollbarsAfterStateRestore();
|
|
1246
|
+
}
|
|
1069
1247
|
const restoredMasks = restoredState.objects.filter(isMaskObject);
|
|
1070
1248
|
this.lastMask = restoredMasks.reduce((lastMask, maskObject) => !lastMask || maskObject.maskId > lastMask.maskId ? maskObject : lastMask, null);
|
|
1071
1249
|
restoredMasks.forEach((maskObject) => {
|
|
@@ -1125,16 +1303,12 @@ export class ImageEditor {
|
|
|
1125
1303
|
if (after === before) {
|
|
1126
1304
|
return;
|
|
1127
1305
|
}
|
|
1128
|
-
let executedOnce = false;
|
|
1129
1306
|
const cmd = new Command(async () => {
|
|
1130
|
-
|
|
1131
|
-
await this.loadFromStateInternal(after, this.withAnimationQueueBypass());
|
|
1132
|
-
}
|
|
1133
|
-
executedOnce = true;
|
|
1307
|
+
await this.loadFromStateInternal(after, this.withAnimationQueueBypass());
|
|
1134
1308
|
}, async () => {
|
|
1135
1309
|
await this.loadFromStateInternal(before, this.withAnimationQueueBypass());
|
|
1136
1310
|
});
|
|
1137
|
-
this.historyManager.
|
|
1311
|
+
this.historyManager.push(cmd);
|
|
1138
1312
|
this.lastSnapshot = after;
|
|
1139
1313
|
}
|
|
1140
1314
|
catch (error) {
|
|
@@ -1249,7 +1423,7 @@ export class ImageEditor {
|
|
|
1249
1423
|
return {
|
|
1250
1424
|
fabric: this.fabricModule,
|
|
1251
1425
|
canvas: this.canvas,
|
|
1252
|
-
options: this.
|
|
1426
|
+
options: this.getRuntimeOptions(),
|
|
1253
1427
|
getLastMask: () => this.lastMask,
|
|
1254
1428
|
setLastMask: (maskObject) => {
|
|
1255
1429
|
this.lastMask = maskObject;
|
|
@@ -1450,7 +1624,7 @@ export class ImageEditor {
|
|
|
1450
1624
|
containerElement: this.containerElement,
|
|
1451
1625
|
loadImage: async (base64, providedOptions) => {
|
|
1452
1626
|
const geometry = this.captureImageDisplayGeometry();
|
|
1453
|
-
await this.
|
|
1627
|
+
await this.loadImageInternal(base64, this.withInternalOperationOptions(operationToken, providedOptions !== null && providedOptions !== void 0 ? providedOptions : {}));
|
|
1454
1628
|
this.restoreMergedImageDisplayGeometry(geometry);
|
|
1455
1629
|
},
|
|
1456
1630
|
saveState: () => this.captureSnapshotInternal(),
|
|
@@ -1463,8 +1637,9 @@ export class ImageEditor {
|
|
|
1463
1637
|
}
|
|
1464
1638
|
captureSnapshotInternal() {
|
|
1465
1639
|
var _a;
|
|
1466
|
-
if (!this.canvas)
|
|
1467
|
-
|
|
1640
|
+
if (!this.canvas) {
|
|
1641
|
+
throw new Error('[ImageEditor] Cannot capture canvas snapshot before init or after dispose.');
|
|
1642
|
+
}
|
|
1468
1643
|
const activeMask = this.getActiveMaskForSnapshot();
|
|
1469
1644
|
this.hideAllMaskLabels();
|
|
1470
1645
|
return saveStateImpl({
|
|
@@ -1483,9 +1658,134 @@ export class ImageEditor {
|
|
|
1483
1658
|
const activeObject = this.canvas.getActiveObject();
|
|
1484
1659
|
if (activeObject && isMaskObject(activeObject))
|
|
1485
1660
|
return activeObject;
|
|
1486
|
-
|
|
1661
|
+
const labeledMasks = this.canvas
|
|
1487
1662
|
.getObjects()
|
|
1488
|
-
.
|
|
1663
|
+
.filter((object) => isMaskObject(object) && !!object.labelObject);
|
|
1664
|
+
return labeledMasks.length === 1 ? ((_a = labeledMasks[0]) !== null && _a !== void 0 ? _a : null) : null;
|
|
1665
|
+
}
|
|
1666
|
+
enterMosaicMode() {
|
|
1667
|
+
if (!this.canvas || !this.originalImage)
|
|
1668
|
+
return;
|
|
1669
|
+
if (this.mosaicSession)
|
|
1670
|
+
return;
|
|
1671
|
+
if (!this.isImageLoaded())
|
|
1672
|
+
return;
|
|
1673
|
+
if (!this.canRunIdleOperation('enterMosaicMode'))
|
|
1674
|
+
return;
|
|
1675
|
+
enterMosaicModeImpl(this.buildMosaicControllerContext());
|
|
1676
|
+
this.updateInputs();
|
|
1677
|
+
this.updateUi();
|
|
1678
|
+
const callbackContext = this.buildCallbackContext('enterMosaicMode', false);
|
|
1679
|
+
this.emitBusyChangeIfChanged(callbackContext);
|
|
1680
|
+
this.emitImageChanged(callbackContext);
|
|
1681
|
+
}
|
|
1682
|
+
exitMosaicMode() {
|
|
1683
|
+
if (!this.canvas || !this.mosaicSession)
|
|
1684
|
+
return;
|
|
1685
|
+
if (!this.canRunIdleOperation('exitMosaicMode'))
|
|
1686
|
+
return;
|
|
1687
|
+
exitMosaicModeImpl(this.buildMosaicControllerContext());
|
|
1688
|
+
this.updateInputs();
|
|
1689
|
+
this.updateUi();
|
|
1690
|
+
const callbackContext = this.buildCallbackContext('exitMosaicMode', false);
|
|
1691
|
+
this.emitBusyChangeIfChanged(callbackContext);
|
|
1692
|
+
this.emitImageChanged(callbackContext);
|
|
1693
|
+
}
|
|
1694
|
+
isMosaicMode() {
|
|
1695
|
+
return this.mosaicSession !== null;
|
|
1696
|
+
}
|
|
1697
|
+
getMosaicConfig() {
|
|
1698
|
+
return cloneResolvedMosaicConfig(this.currentMosaicConfig);
|
|
1699
|
+
}
|
|
1700
|
+
setMosaicConfig(config) {
|
|
1701
|
+
this.applyMosaicConfigPatch(config, 'setMosaicConfig');
|
|
1702
|
+
}
|
|
1703
|
+
resetMosaicConfig() {
|
|
1704
|
+
if (this.isDisposed)
|
|
1705
|
+
return;
|
|
1706
|
+
const nextConfig = cloneResolvedMosaicConfig(this.defaultMosaicConfig);
|
|
1707
|
+
if (areResolvedMosaicConfigsEqual(this.currentMosaicConfig, nextConfig))
|
|
1708
|
+
return;
|
|
1709
|
+
this.currentMosaicConfig = nextConfig;
|
|
1710
|
+
if (this.mosaicSession && this.canvas) {
|
|
1711
|
+
updateMosaicPreview(this.buildMosaicControllerContext());
|
|
1712
|
+
}
|
|
1713
|
+
this.updateInputs();
|
|
1714
|
+
this.updateUi();
|
|
1715
|
+
this.emitImageChanged(this.buildCallbackContext('resetMosaicConfig', false));
|
|
1716
|
+
}
|
|
1717
|
+
setMosaicBrushSize(size) {
|
|
1718
|
+
this.applyMosaicConfigPatch({ brushSize: size }, 'setMosaicBrushSize');
|
|
1719
|
+
}
|
|
1720
|
+
setMosaicBlockSize(size) {
|
|
1721
|
+
this.applyMosaicConfigPatch({ blockSize: size }, 'setMosaicBlockSize');
|
|
1722
|
+
}
|
|
1723
|
+
applyMosaicConfigPatch(config, operation) {
|
|
1724
|
+
if (this.isDisposed)
|
|
1725
|
+
return;
|
|
1726
|
+
if (config === null || typeof config !== 'object' || Array.isArray(config)) {
|
|
1727
|
+
reportWarning(this.options, new TypeError('[ImageEditor] Invalid Mosaic config object.'), 'Ignored invalid Mosaic config.');
|
|
1728
|
+
return;
|
|
1729
|
+
}
|
|
1730
|
+
const invalidFields = getInvalidMosaicConfigFields(config);
|
|
1731
|
+
if (invalidFields.length > 0) {
|
|
1732
|
+
reportWarning(this.options, new TypeError(`[ImageEditor] Ignored invalid Mosaic config field(s): ` +
|
|
1733
|
+
`${invalidFields.join(', ')}.`), 'Ignored invalid Mosaic config fields.');
|
|
1734
|
+
}
|
|
1735
|
+
const nextConfig = mergeMosaicConfigPatch(this.currentMosaicConfig, config);
|
|
1736
|
+
if (areResolvedMosaicConfigsEqual(this.currentMosaicConfig, nextConfig))
|
|
1737
|
+
return;
|
|
1738
|
+
this.currentMosaicConfig = nextConfig;
|
|
1739
|
+
if (this.mosaicSession && this.canvas) {
|
|
1740
|
+
updateMosaicPreview(this.buildMosaicControllerContext());
|
|
1741
|
+
}
|
|
1742
|
+
this.updateInputs();
|
|
1743
|
+
this.updateUi();
|
|
1744
|
+
this.emitImageChanged(this.buildCallbackContext(operation, false));
|
|
1745
|
+
}
|
|
1746
|
+
buildMosaicControllerContext() {
|
|
1747
|
+
return {
|
|
1748
|
+
fabric: this.fabricModule,
|
|
1749
|
+
canvas: this.canvas,
|
|
1750
|
+
options: this.options,
|
|
1751
|
+
historyManager: this.historyManager,
|
|
1752
|
+
getMosaicConfig: () => cloneResolvedMosaicConfig(this.currentMosaicConfig),
|
|
1753
|
+
isImageLoaded: () => this.isImageLoaded(),
|
|
1754
|
+
getOriginalImage: () => this.originalImage,
|
|
1755
|
+
setOriginalImage: (image) => {
|
|
1756
|
+
this.originalImage = image;
|
|
1757
|
+
},
|
|
1758
|
+
getCurrentImageMimeType: () => this.currentImageMimeType,
|
|
1759
|
+
setCurrentImageMimeType: (mimeType) => {
|
|
1760
|
+
this.currentImageMimeType = mimeType;
|
|
1761
|
+
},
|
|
1762
|
+
getLastSnapshot: () => this.lastSnapshot,
|
|
1763
|
+
setLastSnapshot: (snapshot) => {
|
|
1764
|
+
this.lastSnapshot = snapshot;
|
|
1765
|
+
},
|
|
1766
|
+
captureSnapshot: () => this.captureSnapshotInternal(),
|
|
1767
|
+
loadFromState: (snapshot) => this.loadFromStateInternal(snapshot, this.withAnimationQueueBypass()),
|
|
1768
|
+
updateUi: () => {
|
|
1769
|
+
this.updateUi();
|
|
1770
|
+
},
|
|
1771
|
+
updateInputs: () => {
|
|
1772
|
+
this.updateInputs();
|
|
1773
|
+
},
|
|
1774
|
+
hideAllMaskLabels: () => {
|
|
1775
|
+
this.hideAllMaskLabels();
|
|
1776
|
+
},
|
|
1777
|
+
emitImageChanged: (context) => {
|
|
1778
|
+
this.emitImageChanged(context);
|
|
1779
|
+
},
|
|
1780
|
+
emitBusyChangeIfChanged: (context) => {
|
|
1781
|
+
this.emitBusyChangeIfChanged(context);
|
|
1782
|
+
},
|
|
1783
|
+
buildCallbackContext: (operation, isInternal) => this.buildCallbackContext(operation, isInternal),
|
|
1784
|
+
getMosaicSession: () => this.mosaicSession,
|
|
1785
|
+
setMosaicSession: (session) => {
|
|
1786
|
+
this.mosaicSession = session;
|
|
1787
|
+
},
|
|
1788
|
+
};
|
|
1489
1789
|
}
|
|
1490
1790
|
enterCropMode() {
|
|
1491
1791
|
if (!this.canvas || !this.originalImage)
|
|
@@ -1558,7 +1858,7 @@ export class ImageEditor {
|
|
|
1558
1858
|
},
|
|
1559
1859
|
saveState: () => this.captureSnapshotInternal(),
|
|
1560
1860
|
loadFromState: (snapshot) => this.loadFromStateInternal(snapshot, this.withInternalOperationOptions(operationToken, this.withAnimationQueueBypass())),
|
|
1561
|
-
loadImage: (base64, providedOptions) => this.
|
|
1861
|
+
loadImage: (base64, providedOptions) => this.loadImageInternal(base64, this.withInternalOperationOptions(operationToken, providedOptions !== null && providedOptions !== void 0 ? providedOptions : {})),
|
|
1562
1862
|
getMaskCounter: () => this.maskCounter,
|
|
1563
1863
|
setMaskCounter: (n) => {
|
|
1564
1864
|
this.maskCounter = n;
|
|
@@ -1570,13 +1870,28 @@ export class ImageEditor {
|
|
|
1570
1870
|
}
|
|
1571
1871
|
updateInputs() {
|
|
1572
1872
|
const scaleId = this.elements.scalePercentageInput;
|
|
1573
|
-
if (
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1873
|
+
if (scaleId) {
|
|
1874
|
+
const scaleInputElement = document.getElementById(scaleId);
|
|
1875
|
+
if (scaleInputElement) {
|
|
1876
|
+
scaleInputElement.value = String(Math.round(this.currentScale * 100));
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
const mosaicConfig = this.getMosaicConfig();
|
|
1880
|
+
const mosaicBrushSizeInputId = this.elements.mosaicBrushSizeInput;
|
|
1881
|
+
if (mosaicBrushSizeInputId) {
|
|
1882
|
+
const brushInput = document.getElementById(mosaicBrushSizeInputId);
|
|
1883
|
+
if (brushInput)
|
|
1884
|
+
brushInput.value = String(mosaicConfig.brushSize);
|
|
1885
|
+
}
|
|
1886
|
+
const mosaicBlockSizeInputId = this.elements.mosaicBlockSizeInput;
|
|
1887
|
+
if (mosaicBlockSizeInputId) {
|
|
1888
|
+
const blockInput = document.getElementById(mosaicBlockSizeInputId);
|
|
1889
|
+
if (blockInput)
|
|
1890
|
+
blockInput.value = String(mosaicConfig.blockSize);
|
|
1891
|
+
}
|
|
1578
1892
|
}
|
|
1579
1893
|
updateUi() {
|
|
1894
|
+
var _a;
|
|
1580
1895
|
if (!this.canvas)
|
|
1581
1896
|
return;
|
|
1582
1897
|
const hasImage = !!this.originalImage;
|
|
@@ -1588,13 +1903,22 @@ export class ImageEditor {
|
|
|
1588
1903
|
const canUndo = this.historyManager.canUndo();
|
|
1589
1904
|
const canRedo = this.historyManager.canRedo();
|
|
1590
1905
|
const isInCropMode = this.cropSession !== null;
|
|
1906
|
+
const isInMosaicMode = this.mosaicSession !== null;
|
|
1591
1907
|
const isBusy = this.operationGuard.isBusy() || this.animQueue.isBusy();
|
|
1908
|
+
const isMosaicApplying = ((_a = this.mosaicSession) === null || _a === void 0 ? void 0 : _a.isApplying) === true;
|
|
1592
1909
|
if (isInCropMode) {
|
|
1593
1910
|
CROP_MODE_CONTROL_KEYS.forEach((key) => {
|
|
1594
1911
|
this.setControlEnabled(key, !isBusy && CROP_MODE_ENABLED_KEYS.includes(key));
|
|
1595
1912
|
});
|
|
1596
1913
|
return;
|
|
1597
1914
|
}
|
|
1915
|
+
if (isInMosaicMode) {
|
|
1916
|
+
MOSAIC_MODE_CONTROL_KEYS.forEach((key) => {
|
|
1917
|
+
this.setControlEnabled(key, !isBusy && !isMosaicApplying && MOSAIC_MODE_ENABLED_KEYS.includes(key));
|
|
1918
|
+
});
|
|
1919
|
+
this.setControlEnabled('imageInput', false);
|
|
1920
|
+
return;
|
|
1921
|
+
}
|
|
1598
1922
|
this.setControlEnabled('scalePercentageInput', hasImage && !isBusy);
|
|
1599
1923
|
this.setControlEnabled('rotateLeftDegreesInput', hasImage && !isBusy);
|
|
1600
1924
|
this.setControlEnabled('rotateRightDegreesInput', hasImage && !isBusy);
|
|
@@ -1611,6 +1935,10 @@ export class ImageEditor {
|
|
|
1611
1935
|
this.setControlEnabled('undoButton', hasImage && !isBusy && canUndo);
|
|
1612
1936
|
this.setControlEnabled('redoButton', hasImage && !isBusy && canRedo);
|
|
1613
1937
|
this.setControlEnabled('enterCropModeButton', hasImage && !isBusy);
|
|
1938
|
+
this.setControlEnabled('enterMosaicModeButton', hasImage && !isBusy);
|
|
1939
|
+
this.setControlEnabled('exitMosaicModeButton', false);
|
|
1940
|
+
this.setControlEnabled('mosaicBrushSizeInput', !this.isDisposed);
|
|
1941
|
+
this.setControlEnabled('mosaicBlockSizeInput', !this.isDisposed);
|
|
1614
1942
|
this.setControlEnabled('imageInput', !isBusy);
|
|
1615
1943
|
this.setControlEnabled('applyCropButton', false);
|
|
1616
1944
|
this.setControlEnabled('cancelCropButton', false);
|
|
@@ -1708,6 +2036,14 @@ export class ImageEditor {
|
|
|
1708
2036
|
}
|
|
1709
2037
|
this.cropSession = null;
|
|
1710
2038
|
}
|
|
2039
|
+
if (this.mosaicSession && this.canvas) {
|
|
2040
|
+
try {
|
|
2041
|
+
exitMosaicModeImpl(this.buildMosaicControllerContext());
|
|
2042
|
+
}
|
|
2043
|
+
catch {
|
|
2044
|
+
}
|
|
2045
|
+
this.mosaicSession = null;
|
|
2046
|
+
}
|
|
1711
2047
|
if (this.canvas) {
|
|
1712
2048
|
try {
|
|
1713
2049
|
void Promise.resolve(this.canvas.dispose()).catch(() => {
|