@bensitu/image-editor 2.1.0 → 2.3.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 +305 -95
- package/dist/cjs/index.cjs +3460 -843
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/annotation/annotation-lock.js +7 -0
- package/dist/esm/annotation/annotation-lock.js.map +1 -0
- package/dist/esm/annotation/annotation-manager.js +217 -0
- package/dist/esm/annotation/annotation-manager.js.map +1 -0
- package/dist/esm/annotation/annotation-style.js +50 -0
- package/dist/esm/annotation/annotation-style.js.map +1 -0
- package/dist/esm/annotation/draw-controller.js +114 -0
- package/dist/esm/annotation/draw-controller.js.map +1 -0
- package/dist/esm/annotation/text-controller.js +234 -0
- package/dist/esm/annotation/text-controller.js.map +1 -0
- package/dist/esm/core/default-options.js +240 -9
- package/dist/esm/core/default-options.js.map +1 -1
- package/dist/esm/core/editor-object-kind.js +37 -0
- package/dist/esm/core/editor-object-kind.js.map +1 -0
- package/dist/esm/core/errors.js +19 -0
- package/dist/esm/core/errors.js.map +1 -1
- package/dist/esm/core/layer-order.js +100 -0
- package/dist/esm/core/layer-order.js.map +1 -0
- package/dist/esm/core/public-types.js +34 -1
- package/dist/esm/core/public-types.js.map +1 -1
- package/dist/esm/core/state-serializer.js +112 -24
- package/dist/esm/core/state-serializer.js.map +1 -1
- package/dist/esm/crop/crop-controller.js +220 -10
- package/dist/esm/crop/crop-controller.js.map +1 -1
- package/dist/esm/export/export-format.js.map +1 -1
- package/dist/esm/export/export-service.js +157 -168
- package/dist/esm/export/export-service.js.map +1 -1
- package/dist/esm/export/overlay-merge-service.js +75 -0
- package/dist/esm/export/overlay-merge-service.js.map +1 -0
- package/dist/esm/history/history-manager.js +2 -2
- package/dist/esm/history/history-manager.js.map +1 -1
- package/dist/esm/image/image-loader.js +20 -51
- package/dist/esm/image/image-loader.js.map +1 -1
- package/dist/esm/image/transform-controller.js +42 -0
- package/dist/esm/image/transform-controller.js.map +1 -1
- package/dist/esm/image-editor.js +1200 -72
- package/dist/esm/image-editor.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/mask/mask-factory.js +39 -14
- package/dist/esm/mask/mask-factory.js.map +1 -1
- package/dist/esm/mask/mask-label-manager.js +2 -0
- package/dist/esm/mask/mask-label-manager.js.map +1 -1
- package/dist/esm/mask/mask-list.js.map +1 -1
- package/dist/esm/mask/mask-style.js.map +1 -1
- package/dist/esm/mosaic/mosaic-controller.js +24 -28
- package/dist/esm/mosaic/mosaic-controller.js.map +1 -1
- package/dist/esm/utils/file.js +10 -0
- package/dist/esm/utils/file.js.map +1 -1
- package/dist/esm/utils/image-element-loader.js +55 -0
- package/dist/esm/utils/image-element-loader.js.map +1 -0
- package/dist/esm/utils/pointer.js +28 -0
- package/dist/esm/utils/pointer.js.map +1 -0
- package/dist/types/annotation/annotation-lock.d.ts +12 -0
- package/dist/types/annotation/annotation-lock.d.ts.map +1 -0
- package/dist/types/annotation/annotation-manager.d.ts +33 -0
- package/dist/types/annotation/annotation-manager.d.ts.map +1 -0
- package/dist/types/annotation/annotation-style.d.ts +13 -0
- package/dist/types/annotation/annotation-style.d.ts.map +1 -0
- package/dist/types/annotation/draw-controller.d.ts +43 -0
- package/dist/types/annotation/draw-controller.d.ts.map +1 -0
- package/dist/types/annotation/text-controller.d.ts +47 -0
- package/dist/types/annotation/text-controller.d.ts.map +1 -0
- package/dist/types/core/default-options.d.ts +14 -2
- package/dist/types/core/default-options.d.ts.map +1 -1
- package/dist/types/core/editor-object-kind.d.ts +29 -0
- package/dist/types/core/editor-object-kind.d.ts.map +1 -0
- package/dist/types/core/errors.d.ts +11 -1
- package/dist/types/core/errors.d.ts.map +1 -1
- package/dist/types/core/layer-order.d.ts +21 -0
- package/dist/types/core/layer-order.d.ts.map +1 -0
- package/dist/types/core/public-types.d.ts +272 -56
- package/dist/types/core/public-types.d.ts.map +1 -1
- package/dist/types/core/state-serializer.d.ts +34 -5
- package/dist/types/core/state-serializer.d.ts.map +1 -1
- package/dist/types/crop/crop-controller.d.ts +18 -14
- package/dist/types/crop/crop-controller.d.ts.map +1 -1
- package/dist/types/export/export-format.d.ts +9 -40
- package/dist/types/export/export-format.d.ts.map +1 -1
- package/dist/types/export/export-service.d.ts +45 -41
- package/dist/types/export/export-service.d.ts.map +1 -1
- package/dist/types/export/overlay-merge-service.d.ts +38 -0
- package/dist/types/export/overlay-merge-service.d.ts.map +1 -0
- package/dist/types/history/history-manager.d.ts +11 -14
- package/dist/types/history/history-manager.d.ts.map +1 -1
- package/dist/types/image/image-loader.d.ts +27 -22
- 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/transform-controller.d.ts +19 -14
- package/dist/types/image/transform-controller.d.ts.map +1 -1
- package/dist/types/image-editor.d.ts +93 -15
- package/dist/types/image-editor.d.ts.map +1 -1
- package/dist/types/index.d.cts +3 -3
- package/dist/types/index.d.cts.map +1 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/mask/mask-factory.d.ts.map +1 -1
- package/dist/types/mask/mask-label-manager.d.ts +10 -9
- package/dist/types/mask/mask-label-manager.d.ts.map +1 -1
- package/dist/types/mask/mask-list.d.ts +11 -12
- package/dist/types/mask/mask-list.d.ts.map +1 -1
- package/dist/types/mask/mask-style.d.ts +19 -20
- package/dist/types/mask/mask-style.d.ts.map +1 -1
- package/dist/types/mosaic/mosaic-controller.d.ts +3 -3
- package/dist/types/mosaic/mosaic-controller.d.ts.map +1 -1
- package/dist/types/ui/visibility-state.d.ts +2 -2
- package/dist/types/utils/file.d.ts +13 -0
- package/dist/types/utils/file.d.ts.map +1 -1
- package/dist/types/utils/image-element-loader.d.ts +19 -0
- package/dist/types/utils/image-element-loader.d.ts.map +1 -0
- package/dist/types/utils/pointer.d.ts +16 -0
- package/dist/types/utils/pointer.d.ts.map +1 -0
- 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,14 +1,20 @@
|
|
|
1
1
|
import { AnimationQueue } from './animation/animation-queue.js';
|
|
2
2
|
import { reportError, reportWarning } from './core/callback-reporter.js';
|
|
3
|
-
import { areResolvedMosaicConfigsEqual, cloneResolvedMosaicConfig, getInvalidMosaicConfigFields, isLayoutMode, mergeMosaicConfigPatch, resolveOptions, } from './core/default-options.js';
|
|
3
|
+
import { areResolvedMosaicConfigsEqual, areResolvedDrawConfigsEqual, areResolvedTextAnnotationConfigsEqual, cloneResolvedMosaicConfig, cloneResolvedDrawConfig, cloneResolvedTextAnnotationConfig, getInvalidDrawConfigFields, getInvalidMosaicConfigFields, getInvalidTextAnnotationConfigFields, isLayoutMode, mergeDrawConfigPatch, mergeMosaicConfigPatch, mergeTextAnnotationConfigPatch, 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
|
-
import { isMaskObject } from './core/public-types.js';
|
|
9
|
-
import {
|
|
8
|
+
import { isAnnotationObject, isDrawAnnotationObject, isEditableOverlayObject, isMaskObject, isTextAnnotationObject, } from './core/public-types.js';
|
|
9
|
+
import { getAnnotations as getAnnotationsImpl, removeAllAnnotations as removeAllAnnotationsImpl, removeAnnotationObjects, removeSelectedAnnotation as removeSelectedAnnotationImpl, renderAnnotationList, updateAnnotation as updateAnnotationImpl, updateAnnotationListSelection, updateSelectedAnnotation as updateSelectedAnnotationImpl, } from './annotation/annotation-manager.js';
|
|
10
|
+
import { attachTextEditingHandlersToAnnotations, createTextAnnotation as createTextAnnotationImpl, enterTextMode as enterTextModeImpl, exitTextMode as exitTextModeImpl, finalizeActiveTextEditing, } from './annotation/text-controller.js';
|
|
11
|
+
import { enterDrawMode as enterDrawModeImpl, exitDrawMode as exitDrawModeImpl, updateDrawBrush, } from './annotation/draw-controller.js';
|
|
12
|
+
import { isAnnotationLocked, isAnnotationUnlocked } from './annotation/annotation-lock.js';
|
|
13
|
+
import { syncAnnotationRuntimeStates } from './annotation/annotation-style.js';
|
|
14
|
+
import { normalizeLayerOrder, getEditableOverlayRange } from './core/layer-order.js';
|
|
15
|
+
import { applyCrop as applyCropImpl, cancelCrop as cancelCropImpl, enterCropMode as enterCropModeImpl, setCropAspectRatio as setCropAspectRatioImpl, } from './crop/crop-controller.js';
|
|
10
16
|
import { enterMosaicMode as enterMosaicModeImpl, exitMosaicMode as exitMosaicModeImpl, updateMosaicPreview, } from './mosaic/mosaic-controller.js';
|
|
11
|
-
import { downloadImage as downloadImageImpl, exportImageBase64 as exportImageBase64Impl, exportImageFile as exportImageFileImpl, mergeMasks as mergeMasksImpl, } from './export/export-service.js';
|
|
17
|
+
import { downloadImage as downloadImageImpl, exportImageBase64 as exportImageBase64Impl, exportImageFile as exportImageFileImpl, mergeAnnotations as mergeAnnotationsImpl, mergeMasks as mergeMasksImpl, } from './export/export-service.js';
|
|
12
18
|
import { loadImage as loadImageImpl } from './image/image-loader.js';
|
|
13
19
|
import { ViewportCache, applyCanvasDimensions, computeScrollableCanvasSize, measureScrollbarSize, } from './image/layout-manager.js';
|
|
14
20
|
import { TransformController } from './image/transform-controller.js';
|
|
@@ -18,7 +24,7 @@ import { renderMaskList, updateMaskListSelection } from './mask/mask-list.js';
|
|
|
18
24
|
import { applyMaskSelectedStyle, applyMaskUnselectedStyle, reattachMaskHoverHandlers, } from './mask/mask-style.js';
|
|
19
25
|
import { DomBindings } from './ui/dom-bindings.js';
|
|
20
26
|
import { setPlaceholderVisible as setPlaceholderVisibleImpl } from './ui/visibility-state.js';
|
|
21
|
-
import { inferImageMimeType, readFileAsDataUrl, resetFileInput } from './utils/file.js';
|
|
27
|
+
import { inferImageMimeType, isSupportedImageDataUrl, readFileAsDataUrl, resetFileInput, } from './utils/file.js';
|
|
22
28
|
import { detectSourceMimeType } from './image/image-resampler.js';
|
|
23
29
|
const LAYOUT_EPSILON = 0.5;
|
|
24
30
|
const INTERNAL_OPERATION_TOKEN = Symbol('ImageEditorInternalOperation');
|
|
@@ -29,10 +35,28 @@ const CROP_MODE_CONTROL_KEYS = [
|
|
|
29
35
|
'rotateRightDegreesInput',
|
|
30
36
|
'rotateLeftButton',
|
|
31
37
|
'rotateRightButton',
|
|
38
|
+
'flipHorizontalButton',
|
|
39
|
+
'flipVerticalButton',
|
|
32
40
|
'createMaskButton',
|
|
33
41
|
'removeSelectedMaskButton',
|
|
34
42
|
'removeAllMasksButton',
|
|
35
43
|
'mergeMasksButton',
|
|
44
|
+
'mergeAnnotationsButton',
|
|
45
|
+
'enterTextModeButton',
|
|
46
|
+
'exitTextModeButton',
|
|
47
|
+
'textColorInput',
|
|
48
|
+
'textFontSizeInput',
|
|
49
|
+
'enterDrawModeButton',
|
|
50
|
+
'exitDrawModeButton',
|
|
51
|
+
'drawColorInput',
|
|
52
|
+
'drawBrushSizeInput',
|
|
53
|
+
'removeSelectedAnnotationButton',
|
|
54
|
+
'removeAllAnnotationsButton',
|
|
55
|
+
'deleteSelectedObjectButton',
|
|
56
|
+
'bringSelectedObjectForwardButton',
|
|
57
|
+
'sendSelectedObjectBackwardButton',
|
|
58
|
+
'bringSelectedObjectToFrontButton',
|
|
59
|
+
'sendSelectedObjectToBackButton',
|
|
36
60
|
'downloadImageButton',
|
|
37
61
|
'zoomInButton',
|
|
38
62
|
'zoomOutButton',
|
|
@@ -41,6 +65,7 @@ const CROP_MODE_CONTROL_KEYS = [
|
|
|
41
65
|
'redoButton',
|
|
42
66
|
'imageInput',
|
|
43
67
|
'enterCropModeButton',
|
|
68
|
+
'cropAspectRatioSelect',
|
|
44
69
|
'applyCropButton',
|
|
45
70
|
'cancelCropButton',
|
|
46
71
|
'enterMosaicModeButton',
|
|
@@ -48,18 +73,50 @@ const CROP_MODE_CONTROL_KEYS = [
|
|
|
48
73
|
'mosaicBrushSizeInput',
|
|
49
74
|
'mosaicBlockSizeInput',
|
|
50
75
|
];
|
|
51
|
-
const CROP_MODE_ENABLED_KEYS = [
|
|
52
|
-
|
|
76
|
+
const CROP_MODE_ENABLED_KEYS = [
|
|
77
|
+
'cropAspectRatioSelect',
|
|
78
|
+
'applyCropButton',
|
|
79
|
+
'cancelCropButton',
|
|
80
|
+
];
|
|
81
|
+
const CROP_SESSION_ALLOWED_OPERATIONS = new Set(['setCropAspectRatio', 'applyCrop', 'cancelCrop']);
|
|
82
|
+
const TEXT_MODE_ENABLED_KEYS = [
|
|
83
|
+
'exitTextModeButton',
|
|
84
|
+
'textColorInput',
|
|
85
|
+
'textFontSizeInput',
|
|
86
|
+
];
|
|
87
|
+
const DRAW_MODE_ENABLED_KEYS = [
|
|
88
|
+
'exitDrawModeButton',
|
|
89
|
+
'drawColorInput',
|
|
90
|
+
'drawBrushSizeInput',
|
|
91
|
+
];
|
|
53
92
|
const MOSAIC_MODE_CONTROL_KEYS = [
|
|
54
93
|
'scalePercentageInput',
|
|
55
94
|
'rotateLeftDegreesInput',
|
|
56
95
|
'rotateRightDegreesInput',
|
|
57
96
|
'rotateLeftButton',
|
|
58
97
|
'rotateRightButton',
|
|
98
|
+
'flipHorizontalButton',
|
|
99
|
+
'flipVerticalButton',
|
|
59
100
|
'createMaskButton',
|
|
60
101
|
'removeSelectedMaskButton',
|
|
61
102
|
'removeAllMasksButton',
|
|
62
103
|
'mergeMasksButton',
|
|
104
|
+
'mergeAnnotationsButton',
|
|
105
|
+
'enterTextModeButton',
|
|
106
|
+
'exitTextModeButton',
|
|
107
|
+
'textColorInput',
|
|
108
|
+
'textFontSizeInput',
|
|
109
|
+
'enterDrawModeButton',
|
|
110
|
+
'exitDrawModeButton',
|
|
111
|
+
'drawColorInput',
|
|
112
|
+
'drawBrushSizeInput',
|
|
113
|
+
'removeSelectedAnnotationButton',
|
|
114
|
+
'removeAllAnnotationsButton',
|
|
115
|
+
'deleteSelectedObjectButton',
|
|
116
|
+
'bringSelectedObjectForwardButton',
|
|
117
|
+
'sendSelectedObjectBackwardButton',
|
|
118
|
+
'bringSelectedObjectToFrontButton',
|
|
119
|
+
'sendSelectedObjectToBackButton',
|
|
63
120
|
'downloadImageButton',
|
|
64
121
|
'zoomInButton',
|
|
65
122
|
'zoomOutButton',
|
|
@@ -68,6 +125,7 @@ const MOSAIC_MODE_CONTROL_KEYS = [
|
|
|
68
125
|
'redoButton',
|
|
69
126
|
'imageInput',
|
|
70
127
|
'enterCropModeButton',
|
|
128
|
+
'cropAspectRatioSelect',
|
|
71
129
|
'applyCropButton',
|
|
72
130
|
'cancelCropButton',
|
|
73
131
|
'enterMosaicModeButton',
|
|
@@ -97,12 +155,38 @@ const IMAGE_EDITOR_OPERATIONS = new Set([
|
|
|
97
155
|
'saveState',
|
|
98
156
|
'scaleImage',
|
|
99
157
|
'rotateImage',
|
|
158
|
+
'flipHorizontal',
|
|
159
|
+
'flipVertical',
|
|
100
160
|
'resetImageTransform',
|
|
101
161
|
'createMask',
|
|
102
162
|
'removeSelectedMask',
|
|
103
163
|
'removeAllMasks',
|
|
104
164
|
'mergeMasks',
|
|
165
|
+
'createTextAnnotation',
|
|
166
|
+
'enterTextMode',
|
|
167
|
+
'exitTextMode',
|
|
168
|
+
'setTextConfig',
|
|
169
|
+
'resetTextConfig',
|
|
170
|
+
'setTextColor',
|
|
171
|
+
'setTextFontSize',
|
|
172
|
+
'enterDrawMode',
|
|
173
|
+
'exitDrawMode',
|
|
174
|
+
'setDrawConfig',
|
|
175
|
+
'resetDrawConfig',
|
|
176
|
+
'setDrawColor',
|
|
177
|
+
'setDrawBrushSize',
|
|
178
|
+
'updateSelectedAnnotation',
|
|
179
|
+
'updateAnnotation',
|
|
180
|
+
'removeSelectedAnnotation',
|
|
181
|
+
'removeAllAnnotations',
|
|
182
|
+
'deleteSelectedObject',
|
|
183
|
+
'mergeAnnotations',
|
|
184
|
+
'bringSelectedObjectForward',
|
|
185
|
+
'sendSelectedObjectBackward',
|
|
186
|
+
'bringSelectedObjectToFront',
|
|
187
|
+
'sendSelectedObjectToBack',
|
|
105
188
|
'enterCropMode',
|
|
189
|
+
'setCropAspectRatio',
|
|
106
190
|
'applyCrop',
|
|
107
191
|
'cancelCrop',
|
|
108
192
|
'enterMosaicMode',
|
|
@@ -119,6 +203,27 @@ const IMAGE_EDITOR_OPERATIONS = new Set([
|
|
|
119
203
|
'downloadImage',
|
|
120
204
|
'dispose',
|
|
121
205
|
]);
|
|
206
|
+
const TOOL_MODE_ALLOWED_OPERATIONS = {
|
|
207
|
+
crop: CROP_SESSION_ALLOWED_OPERATIONS,
|
|
208
|
+
mosaic: MOSAIC_SESSION_ALLOWED_OPERATIONS,
|
|
209
|
+
text: new Set([
|
|
210
|
+
'exitTextMode',
|
|
211
|
+
'createTextAnnotation',
|
|
212
|
+
'setTextConfig',
|
|
213
|
+
'resetTextConfig',
|
|
214
|
+
'setTextColor',
|
|
215
|
+
'setTextFontSize',
|
|
216
|
+
'saveState',
|
|
217
|
+
]),
|
|
218
|
+
draw: new Set([
|
|
219
|
+
'exitDrawMode',
|
|
220
|
+
'setDrawConfig',
|
|
221
|
+
'resetDrawConfig',
|
|
222
|
+
'setDrawColor',
|
|
223
|
+
'setDrawBrushSize',
|
|
224
|
+
'saveState',
|
|
225
|
+
]),
|
|
226
|
+
};
|
|
122
227
|
function isImageEditorOperation(value) {
|
|
123
228
|
return value !== null && IMAGE_EDITOR_OPERATIONS.has(value);
|
|
124
229
|
}
|
|
@@ -161,6 +266,30 @@ export class ImageEditor {
|
|
|
161
266
|
writable: true,
|
|
162
267
|
value: void 0
|
|
163
268
|
});
|
|
269
|
+
Object.defineProperty(this, "defaultTextConfig", {
|
|
270
|
+
enumerable: true,
|
|
271
|
+
configurable: true,
|
|
272
|
+
writable: true,
|
|
273
|
+
value: void 0
|
|
274
|
+
});
|
|
275
|
+
Object.defineProperty(this, "currentTextConfig", {
|
|
276
|
+
enumerable: true,
|
|
277
|
+
configurable: true,
|
|
278
|
+
writable: true,
|
|
279
|
+
value: void 0
|
|
280
|
+
});
|
|
281
|
+
Object.defineProperty(this, "defaultDrawConfig", {
|
|
282
|
+
enumerable: true,
|
|
283
|
+
configurable: true,
|
|
284
|
+
writable: true,
|
|
285
|
+
value: void 0
|
|
286
|
+
});
|
|
287
|
+
Object.defineProperty(this, "currentDrawConfig", {
|
|
288
|
+
enumerable: true,
|
|
289
|
+
configurable: true,
|
|
290
|
+
writable: true,
|
|
291
|
+
value: void 0
|
|
292
|
+
});
|
|
164
293
|
Object.defineProperty(this, "canvas", {
|
|
165
294
|
enumerable: true,
|
|
166
295
|
configurable: true,
|
|
@@ -257,6 +386,12 @@ export class ImageEditor {
|
|
|
257
386
|
writable: true,
|
|
258
387
|
value: null
|
|
259
388
|
});
|
|
389
|
+
Object.defineProperty(this, "annotationCounter", {
|
|
390
|
+
enumerable: true,
|
|
391
|
+
configurable: true,
|
|
392
|
+
writable: true,
|
|
393
|
+
value: 0
|
|
394
|
+
});
|
|
260
395
|
Object.defineProperty(this, "lastSnapshot", {
|
|
261
396
|
enumerable: true,
|
|
262
397
|
configurable: true,
|
|
@@ -305,12 +440,36 @@ export class ImageEditor {
|
|
|
305
440
|
writable: true,
|
|
306
441
|
value: null
|
|
307
442
|
});
|
|
443
|
+
Object.defineProperty(this, "textSession", {
|
|
444
|
+
enumerable: true,
|
|
445
|
+
configurable: true,
|
|
446
|
+
writable: true,
|
|
447
|
+
value: null
|
|
448
|
+
});
|
|
449
|
+
Object.defineProperty(this, "drawSession", {
|
|
450
|
+
enumerable: true,
|
|
451
|
+
configurable: true,
|
|
452
|
+
writable: true,
|
|
453
|
+
value: null
|
|
454
|
+
});
|
|
308
455
|
Object.defineProperty(this, "domBindings", {
|
|
309
456
|
enumerable: true,
|
|
310
457
|
configurable: true,
|
|
311
458
|
writable: true,
|
|
312
459
|
value: null
|
|
313
460
|
});
|
|
461
|
+
Object.defineProperty(this, "keyboardDocument", {
|
|
462
|
+
enumerable: true,
|
|
463
|
+
configurable: true,
|
|
464
|
+
writable: true,
|
|
465
|
+
value: null
|
|
466
|
+
});
|
|
467
|
+
Object.defineProperty(this, "keyboardHandler", {
|
|
468
|
+
enumerable: true,
|
|
469
|
+
configurable: true,
|
|
470
|
+
writable: true,
|
|
471
|
+
value: null
|
|
472
|
+
});
|
|
314
473
|
Object.defineProperty(this, "isDisposed", {
|
|
315
474
|
enumerable: true,
|
|
316
475
|
configurable: true,
|
|
@@ -348,6 +507,10 @@ export class ImageEditor {
|
|
|
348
507
|
this.currentLayoutMode = this.options.layoutMode;
|
|
349
508
|
this.defaultMosaicConfig = this.options.defaultMosaicConfig;
|
|
350
509
|
this.currentMosaicConfig = cloneResolvedMosaicConfig(this.defaultMosaicConfig);
|
|
510
|
+
this.defaultTextConfig = this.options.defaultTextConfig;
|
|
511
|
+
this.currentTextConfig = cloneResolvedTextAnnotationConfig(this.defaultTextConfig);
|
|
512
|
+
this.defaultDrawConfig = this.options.defaultDrawConfig;
|
|
513
|
+
this.currentDrawConfig = cloneResolvedDrawConfig(this.defaultDrawConfig);
|
|
351
514
|
const rawDefaultLayoutMode = detected.options
|
|
352
515
|
.defaultLayoutMode;
|
|
353
516
|
if (rawDefaultLayoutMode !== undefined && !isLayoutMode(rawDefaultLayoutMode)) {
|
|
@@ -380,10 +543,29 @@ export class ImageEditor {
|
|
|
380
543
|
rotateRightDegreesInput: 'rotateRightDegreesInput',
|
|
381
544
|
rotateLeftButton: 'rotateLeftButton',
|
|
382
545
|
rotateRightButton: 'rotateRightButton',
|
|
546
|
+
flipHorizontalButton: 'flipHorizontalButton',
|
|
547
|
+
flipVerticalButton: 'flipVerticalButton',
|
|
383
548
|
createMaskButton: 'createMaskButton',
|
|
384
549
|
removeSelectedMaskButton: 'removeSelectedMaskButton',
|
|
385
550
|
removeAllMasksButton: 'removeAllMasksButton',
|
|
386
551
|
mergeMasksButton: 'mergeMasksButton',
|
|
552
|
+
annotationList: 'annotationList',
|
|
553
|
+
enterTextModeButton: 'enterTextModeButton',
|
|
554
|
+
exitTextModeButton: 'exitTextModeButton',
|
|
555
|
+
textColorInput: 'textColorInput',
|
|
556
|
+
textFontSizeInput: 'textFontSizeInput',
|
|
557
|
+
enterDrawModeButton: 'enterDrawModeButton',
|
|
558
|
+
exitDrawModeButton: 'exitDrawModeButton',
|
|
559
|
+
drawColorInput: 'drawColorInput',
|
|
560
|
+
drawBrushSizeInput: 'drawBrushSizeInput',
|
|
561
|
+
removeSelectedAnnotationButton: 'removeSelectedAnnotationButton',
|
|
562
|
+
removeAllAnnotationsButton: 'removeAllAnnotationsButton',
|
|
563
|
+
deleteSelectedObjectButton: 'deleteSelectedObjectButton',
|
|
564
|
+
mergeAnnotationsButton: 'mergeAnnotationsButton',
|
|
565
|
+
bringSelectedObjectForwardButton: 'bringSelectedObjectForwardButton',
|
|
566
|
+
sendSelectedObjectBackwardButton: 'sendSelectedObjectBackwardButton',
|
|
567
|
+
bringSelectedObjectToFrontButton: 'bringSelectedObjectToFrontButton',
|
|
568
|
+
sendSelectedObjectToBackButton: 'sendSelectedObjectToBackButton',
|
|
387
569
|
downloadImageButton: 'downloadImageButton',
|
|
388
570
|
maskList: 'maskList',
|
|
389
571
|
zoomInButton: 'zoomInButton',
|
|
@@ -393,6 +575,7 @@ export class ImageEditor {
|
|
|
393
575
|
redoButton: 'redoButton',
|
|
394
576
|
imageInput: 'imageInput',
|
|
395
577
|
enterCropModeButton: 'enterCropModeButton',
|
|
578
|
+
cropAspectRatioSelect: 'cropAspectRatioSelect',
|
|
396
579
|
applyCropButton: 'applyCropButton',
|
|
397
580
|
cancelCropButton: 'cancelCropButton',
|
|
398
581
|
enterMosaicModeButton: 'enterMosaicModeButton',
|
|
@@ -402,12 +585,13 @@ export class ImageEditor {
|
|
|
402
585
|
uploadArea: 'uploadArea',
|
|
403
586
|
};
|
|
404
587
|
this.elements = { ...defaults, ...idMap };
|
|
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; });
|
|
406
588
|
this.initCanvas();
|
|
589
|
+
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; });
|
|
407
590
|
this.transformController = new TransformController(this.buildTransformContext());
|
|
408
591
|
this.bindDomEvents();
|
|
409
592
|
this.updateInputs();
|
|
410
593
|
this.updateMaskList();
|
|
594
|
+
this.updateAnnotationList();
|
|
411
595
|
this.updateUi();
|
|
412
596
|
if (this.options.initialImageBase64) {
|
|
413
597
|
void this.loadImage(this.options.initialImageBase64).catch(() => {
|
|
@@ -459,20 +643,24 @@ export class ImageEditor {
|
|
|
459
643
|
});
|
|
460
644
|
this.canvas.on('selection:cleared', () => this.handleSelectionChanged([]));
|
|
461
645
|
const onObjectEvent = (e) => {
|
|
462
|
-
if (e.target
|
|
463
|
-
this.
|
|
646
|
+
if (e.target)
|
|
647
|
+
this.handleObjectMovingScalingRotating(e.target);
|
|
464
648
|
};
|
|
465
649
|
const onObjectModified = (e) => {
|
|
466
|
-
if (
|
|
467
|
-
|
|
468
|
-
this.syncMaskLabel(e.target);
|
|
469
|
-
this.saveState();
|
|
650
|
+
if (e.target)
|
|
651
|
+
this.handleObjectModified(e.target);
|
|
470
652
|
};
|
|
471
653
|
this.canvas.on('object:moving', onObjectEvent);
|
|
472
654
|
this.canvas.on('object:scaling', onObjectEvent);
|
|
473
655
|
this.canvas.on('object:rotating', onObjectEvent);
|
|
474
656
|
this.canvas.on('object:modified', onObjectModified);
|
|
475
657
|
}
|
|
658
|
+
getLiveCanvasOrThrow(operationName) {
|
|
659
|
+
if (this.isDisposed || !this.canvas) {
|
|
660
|
+
throw new Error(`[ImageEditor] Cannot run "${operationName}" after dispose.`);
|
|
661
|
+
}
|
|
662
|
+
return this.canvas;
|
|
663
|
+
}
|
|
476
664
|
bindDomEvents() {
|
|
477
665
|
this.bindElementIfExists('uploadArea', 'click', () => {
|
|
478
666
|
var _a;
|
|
@@ -495,6 +683,12 @@ export class ImageEditor {
|
|
|
495
683
|
this.bindElementIfExists('resetImageTransformButton', 'click', () => {
|
|
496
684
|
void this.resetImageTransform();
|
|
497
685
|
});
|
|
686
|
+
this.bindElementIfExists('flipHorizontalButton', 'click', () => {
|
|
687
|
+
void this.flipHorizontal();
|
|
688
|
+
});
|
|
689
|
+
this.bindElementIfExists('flipVerticalButton', 'click', () => {
|
|
690
|
+
void this.flipVertical();
|
|
691
|
+
});
|
|
498
692
|
this.bindElementIfExists('createMaskButton', 'click', () => {
|
|
499
693
|
this.createMask();
|
|
500
694
|
});
|
|
@@ -507,6 +701,42 @@ export class ImageEditor {
|
|
|
507
701
|
this.bindElementIfExists('mergeMasksButton', 'click', () => {
|
|
508
702
|
void this.mergeMasks();
|
|
509
703
|
});
|
|
704
|
+
this.bindElementIfExists('mergeAnnotationsButton', 'click', () => {
|
|
705
|
+
void this.mergeAnnotations();
|
|
706
|
+
});
|
|
707
|
+
this.bindElementIfExists('enterTextModeButton', 'click', () => {
|
|
708
|
+
this.enterTextMode();
|
|
709
|
+
});
|
|
710
|
+
this.bindElementIfExists('exitTextModeButton', 'click', () => {
|
|
711
|
+
this.exitTextMode();
|
|
712
|
+
});
|
|
713
|
+
this.bindElementIfExists('enterDrawModeButton', 'click', () => {
|
|
714
|
+
this.enterDrawMode();
|
|
715
|
+
});
|
|
716
|
+
this.bindElementIfExists('exitDrawModeButton', 'click', () => {
|
|
717
|
+
this.exitDrawMode();
|
|
718
|
+
});
|
|
719
|
+
this.bindElementIfExists('removeSelectedAnnotationButton', 'click', () => {
|
|
720
|
+
this.removeSelectedAnnotation();
|
|
721
|
+
});
|
|
722
|
+
this.bindElementIfExists('removeAllAnnotationsButton', 'click', () => {
|
|
723
|
+
this.removeAllAnnotations();
|
|
724
|
+
});
|
|
725
|
+
this.bindElementIfExists('deleteSelectedObjectButton', 'click', () => {
|
|
726
|
+
this.deleteSelectedObject();
|
|
727
|
+
});
|
|
728
|
+
this.bindElementIfExists('bringSelectedObjectForwardButton', 'click', () => {
|
|
729
|
+
this.bringSelectedObjectForward();
|
|
730
|
+
});
|
|
731
|
+
this.bindElementIfExists('sendSelectedObjectBackwardButton', 'click', () => {
|
|
732
|
+
this.sendSelectedObjectBackward();
|
|
733
|
+
});
|
|
734
|
+
this.bindElementIfExists('bringSelectedObjectToFrontButton', 'click', () => {
|
|
735
|
+
this.bringSelectedObjectToFront();
|
|
736
|
+
});
|
|
737
|
+
this.bindElementIfExists('sendSelectedObjectToBackButton', 'click', () => {
|
|
738
|
+
this.sendSelectedObjectToBack();
|
|
739
|
+
});
|
|
510
740
|
this.bindElementIfExists('downloadImageButton', 'click', () => {
|
|
511
741
|
this.downloadImage();
|
|
512
742
|
});
|
|
@@ -543,7 +773,11 @@ export class ImageEditor {
|
|
|
543
773
|
void this.rotateImage(this.currentRotation + step);
|
|
544
774
|
});
|
|
545
775
|
this.bindElementIfExists('enterCropModeButton', 'click', () => {
|
|
546
|
-
this.enterCropMode();
|
|
776
|
+
this.enterCropMode({ aspectRatio: this.getSelectedCropAspectRatio() });
|
|
777
|
+
});
|
|
778
|
+
this.bindElementIfExists('cropAspectRatioSelect', 'change', () => {
|
|
779
|
+
if (this.cropSession)
|
|
780
|
+
this.setCropAspectRatio(this.getSelectedCropAspectRatio());
|
|
547
781
|
});
|
|
548
782
|
this.bindElementIfExists('applyCropButton', 'click', () => {
|
|
549
783
|
void this.applyCrop().catch((error) => {
|
|
@@ -573,11 +807,92 @@ export class ImageEditor {
|
|
|
573
807
|
bindMosaicSizeInput('mosaicBlockSizeInput', (value) => {
|
|
574
808
|
this.setMosaicBlockSize(value);
|
|
575
809
|
});
|
|
810
|
+
const bindStringInput = (key, applyValue) => {
|
|
811
|
+
const handler = (event) => {
|
|
812
|
+
applyValue(event.target.value);
|
|
813
|
+
};
|
|
814
|
+
this.bindElementIfExists(key, 'input', handler);
|
|
815
|
+
this.bindElementIfExists(key, 'change', handler);
|
|
816
|
+
};
|
|
817
|
+
const bindNumberInput = (key, applyValue) => {
|
|
818
|
+
const handler = (event) => {
|
|
819
|
+
applyValue(parseFloat(event.target.value));
|
|
820
|
+
};
|
|
821
|
+
this.bindElementIfExists(key, 'input', handler);
|
|
822
|
+
this.bindElementIfExists(key, 'change', handler);
|
|
823
|
+
};
|
|
824
|
+
bindStringInput('textColorInput', (value) => this.applyTextColorInput(value));
|
|
825
|
+
bindNumberInput('textFontSizeInput', (value) => this.applyTextFontSizeInput(value));
|
|
826
|
+
bindStringInput('drawColorInput', (value) => this.applyDrawColorInput(value));
|
|
827
|
+
bindNumberInput('drawBrushSizeInput', (value) => this.applyDrawBrushSizeInput(value));
|
|
828
|
+
this.bindKeyboardEvents();
|
|
576
829
|
}
|
|
577
830
|
bindElementIfExists(key, event, handler) {
|
|
578
831
|
var _a;
|
|
579
832
|
(_a = this.domBindings) === null || _a === void 0 ? void 0 : _a.bindIfExists(key, event, handler);
|
|
580
833
|
}
|
|
834
|
+
bindKeyboardEvents() {
|
|
835
|
+
var _a, _b;
|
|
836
|
+
const ownerDocument = (_b = (_a = this.canvasElement) === null || _a === void 0 ? void 0 : _a.ownerDocument) !== null && _b !== void 0 ? _b : document;
|
|
837
|
+
if (this.keyboardHandler && this.keyboardDocument) {
|
|
838
|
+
this.keyboardDocument.removeEventListener('keydown', this.keyboardHandler);
|
|
839
|
+
}
|
|
840
|
+
this.keyboardDocument = ownerDocument;
|
|
841
|
+
this.keyboardHandler = (event) => this.handleKeyboardEvent(event);
|
|
842
|
+
ownerDocument.addEventListener('keydown', this.keyboardHandler);
|
|
843
|
+
}
|
|
844
|
+
isNativeTextInputActive() {
|
|
845
|
+
var _a;
|
|
846
|
+
const activeElement = (_a = this.keyboardDocument) === null || _a === void 0 ? void 0 : _a.activeElement;
|
|
847
|
+
if (!activeElement)
|
|
848
|
+
return false;
|
|
849
|
+
const tagName = activeElement.tagName.toLowerCase();
|
|
850
|
+
return (tagName === 'input' ||
|
|
851
|
+
tagName === 'textarea' ||
|
|
852
|
+
tagName === 'select' ||
|
|
853
|
+
activeElement.isContentEditable === true);
|
|
854
|
+
}
|
|
855
|
+
isFabricTextEditingActive() {
|
|
856
|
+
var _a;
|
|
857
|
+
const activeObject = (_a = this.canvas) === null || _a === void 0 ? void 0 : _a.getActiveObject();
|
|
858
|
+
return !!(activeObject &&
|
|
859
|
+
isTextAnnotationObject(activeObject) &&
|
|
860
|
+
activeObject.isEditing === true);
|
|
861
|
+
}
|
|
862
|
+
handleKeyboardEvent(event) {
|
|
863
|
+
if (this.isDisposed)
|
|
864
|
+
return;
|
|
865
|
+
if (event.key === 'Delete' || event.key === 'Backspace') {
|
|
866
|
+
if (this.isNativeTextInputActive() || this.isFabricTextEditingActive())
|
|
867
|
+
return;
|
|
868
|
+
this.deleteSelectedObject();
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
if (event.key !== 'Escape')
|
|
872
|
+
return;
|
|
873
|
+
if (this.isFabricTextEditingActive() && this.canvas) {
|
|
874
|
+
finalizeActiveTextEditing(this.buildTextControllerContext(), { commit: false });
|
|
875
|
+
event.preventDefault();
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
if (this.textSession) {
|
|
879
|
+
this.exitTextMode();
|
|
880
|
+
}
|
|
881
|
+
else if (this.drawSession) {
|
|
882
|
+
this.exitDrawMode();
|
|
883
|
+
}
|
|
884
|
+
else if (this.mosaicSession) {
|
|
885
|
+
this.exitMosaicMode();
|
|
886
|
+
}
|
|
887
|
+
else if (this.cropSession) {
|
|
888
|
+
this.cancelCrop();
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
finalizeActiveTextEditingIfNeeded() {
|
|
892
|
+
if (!this.canvas || !this.isFabricTextEditingActive())
|
|
893
|
+
return;
|
|
894
|
+
finalizeActiveTextEditing(this.buildTextControllerContext(), { commit: true });
|
|
895
|
+
}
|
|
581
896
|
async loadImageFile(file) {
|
|
582
897
|
const inputId = this.elements.imageInput;
|
|
583
898
|
const inputEl = inputId
|
|
@@ -615,13 +930,15 @@ export class ImageEditor {
|
|
|
615
930
|
return;
|
|
616
931
|
if (this.isDisposed)
|
|
617
932
|
return;
|
|
618
|
-
if (
|
|
933
|
+
if (!isSupportedImageDataUrl(base64))
|
|
619
934
|
return;
|
|
620
935
|
if (!this.canRunIdleOperation('loadImage', options))
|
|
621
936
|
return;
|
|
937
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
622
938
|
const callbackContext = this.getOperationContext('loadImage', options);
|
|
623
939
|
const previousImage = this.originalImage;
|
|
624
940
|
const hadMasks = this.getMasks().length > 0;
|
|
941
|
+
const hadAnnotations = this.getAnnotations().length > 0;
|
|
625
942
|
this.emitOptionCallback('onImageLoadStart', [callbackContext]);
|
|
626
943
|
this.operationGuard.beginLoading();
|
|
627
944
|
this.emitBusyChangeIfChanged(callbackContext);
|
|
@@ -650,6 +967,10 @@ export class ImageEditor {
|
|
|
650
967
|
setMaskCounter: (v) => {
|
|
651
968
|
this.maskCounter = v;
|
|
652
969
|
},
|
|
970
|
+
getAnnotationCounter: () => this.annotationCounter,
|
|
971
|
+
setAnnotationCounter: (v) => {
|
|
972
|
+
this.annotationCounter = v;
|
|
973
|
+
},
|
|
653
974
|
getCurrentScale: () => this.currentScale,
|
|
654
975
|
setCurrentScale: (v) => {
|
|
655
976
|
this.currentScale = v;
|
|
@@ -682,6 +1003,7 @@ export class ImageEditor {
|
|
|
682
1003
|
this.lastMask = null;
|
|
683
1004
|
this.updateInputs();
|
|
684
1005
|
this.updateMaskList();
|
|
1006
|
+
this.updateAnnotationList();
|
|
685
1007
|
this.updateUi();
|
|
686
1008
|
if (previousImage && previousImage !== this.originalImage) {
|
|
687
1009
|
this.emitOptionCallback('onImageCleared', [previousImage, callbackContext]);
|
|
@@ -693,6 +1015,9 @@ export class ImageEditor {
|
|
|
693
1015
|
if (hadMasks) {
|
|
694
1016
|
this.emitMasksChanged(callbackContext);
|
|
695
1017
|
}
|
|
1018
|
+
if (hadAnnotations) {
|
|
1019
|
+
this.emitAnnotationsChanged(callbackContext);
|
|
1020
|
+
}
|
|
696
1021
|
this.emitImageChanged(callbackContext);
|
|
697
1022
|
}
|
|
698
1023
|
getInternalOperationToken(options) {
|
|
@@ -717,15 +1042,11 @@ export class ImageEditor {
|
|
|
717
1042
|
assertIdleForOperation(operationName, options) {
|
|
718
1043
|
const token = this.getInternalOperationToken(options);
|
|
719
1044
|
this.operationGuard.assertIdleForOperation(operationName, token);
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
!CROP_SESSION_ALLOWED_OPERATIONS.has(operationName)) {
|
|
723
|
-
throw new Error(`[ImageEditor] Cannot run "${operationName}" while crop mode is active.`);
|
|
724
|
-
}
|
|
725
|
-
if (this.mosaicSession &&
|
|
1045
|
+
const activeToolMode = this.getActiveToolMode();
|
|
1046
|
+
if (activeToolMode &&
|
|
726
1047
|
!this.operationGuard.isOwnOperation(token) &&
|
|
727
|
-
!
|
|
728
|
-
throw new Error(`[ImageEditor] Cannot run "${operationName}" while
|
|
1048
|
+
!TOOL_MODE_ALLOWED_OPERATIONS[activeToolMode].has(operationName)) {
|
|
1049
|
+
throw new Error(`[ImageEditor] Cannot run "${operationName}" while ${activeToolMode} mode is active.`);
|
|
729
1050
|
}
|
|
730
1051
|
if (this.animQueue.isBusy() && !this.canRunDuringAnimationQueue(options)) {
|
|
731
1052
|
throw new Error(`[ImageEditor] Cannot run "${operationName}" while an animation is queued.`);
|
|
@@ -743,12 +1064,27 @@ export class ImageEditor {
|
|
|
743
1064
|
return false;
|
|
744
1065
|
}
|
|
745
1066
|
}
|
|
1067
|
+
getSelectedCropAspectRatio() {
|
|
1068
|
+
const inputId = this.elements.cropAspectRatioSelect;
|
|
1069
|
+
const inputEl = inputId
|
|
1070
|
+
? document.getElementById(inputId)
|
|
1071
|
+
: null;
|
|
1072
|
+
const value = inputEl && 'value' in inputEl ? String(inputEl.value).trim() : '';
|
|
1073
|
+
return (value || 'free');
|
|
1074
|
+
}
|
|
746
1075
|
isExpectedIdleGuardError(error, operationName) {
|
|
747
1076
|
return (error instanceof Error &&
|
|
748
1077
|
error.message.startsWith(`[ImageEditor] Cannot run "${operationName}" `));
|
|
749
1078
|
}
|
|
750
1079
|
assertCanQueueAnimation(operationName, options) {
|
|
751
|
-
|
|
1080
|
+
const token = this.getInternalOperationToken(options);
|
|
1081
|
+
this.operationGuard.assertCanQueueAnimation(operationName, token);
|
|
1082
|
+
const activeToolMode = this.getActiveToolMode();
|
|
1083
|
+
if (activeToolMode &&
|
|
1084
|
+
!this.operationGuard.isOwnOperation(token) &&
|
|
1085
|
+
!TOOL_MODE_ALLOWED_OPERATIONS[activeToolMode].has(operationName)) {
|
|
1086
|
+
throw new Error(`[ImageEditor] Cannot run "${operationName}" while ${activeToolMode} mode is active.`);
|
|
1087
|
+
}
|
|
752
1088
|
}
|
|
753
1089
|
isImageLoaded() {
|
|
754
1090
|
var _a, _b;
|
|
@@ -758,10 +1094,7 @@ export class ImageEditor {
|
|
|
758
1094
|
((_b = this.originalImage.height) !== null && _b !== void 0 ? _b : 0) > 0);
|
|
759
1095
|
}
|
|
760
1096
|
isBusy() {
|
|
761
|
-
return
|
|
762
|
-
this.animQueue.isBusy() ||
|
|
763
|
-
this.cropSession !== null ||
|
|
764
|
-
this.mosaicSession !== null);
|
|
1097
|
+
return this.operationGuard.isBusy() || this.animQueue.isBusy() || this.isToolModeActive();
|
|
765
1098
|
}
|
|
766
1099
|
setLayoutMode(mode) {
|
|
767
1100
|
if (!isLayoutMode(mode)) {
|
|
@@ -836,12 +1169,37 @@ export class ImageEditor {
|
|
|
836
1169
|
return [];
|
|
837
1170
|
return this.canvas.getObjects().filter(isMaskObject).slice();
|
|
838
1171
|
}
|
|
1172
|
+
getAnnotations() {
|
|
1173
|
+
if (!this.canvas)
|
|
1174
|
+
return [];
|
|
1175
|
+
return getAnnotationsImpl(this.canvas);
|
|
1176
|
+
}
|
|
839
1177
|
getMaskCollectionSignature() {
|
|
840
1178
|
return this.getMasks()
|
|
841
1179
|
.map((mask) => `${mask.maskId}:${mask.maskName}`)
|
|
842
1180
|
.join('|');
|
|
843
1181
|
}
|
|
1182
|
+
getAnnotationCollectionSignature() {
|
|
1183
|
+
return this.getAnnotations()
|
|
1184
|
+
.map((annotation) => `${annotation.annotationId}:${annotation.annotationName}`)
|
|
1185
|
+
.join('|');
|
|
1186
|
+
}
|
|
1187
|
+
getActiveToolMode() {
|
|
1188
|
+
if (this.cropSession)
|
|
1189
|
+
return 'crop';
|
|
1190
|
+
if (this.mosaicSession)
|
|
1191
|
+
return 'mosaic';
|
|
1192
|
+
if (this.textSession)
|
|
1193
|
+
return 'text';
|
|
1194
|
+
if (this.drawSession)
|
|
1195
|
+
return 'draw';
|
|
1196
|
+
return null;
|
|
1197
|
+
}
|
|
1198
|
+
isToolModeActive() {
|
|
1199
|
+
return this.getActiveToolMode() !== null;
|
|
1200
|
+
}
|
|
844
1201
|
getEditorState() {
|
|
1202
|
+
var _a, _b;
|
|
845
1203
|
const canvasWidth = this.canvas ? this.canvas.getWidth() : 0;
|
|
846
1204
|
const canvasHeight = this.canvas ? this.canvas.getHeight() : 0;
|
|
847
1205
|
const image = this.getImageInfo();
|
|
@@ -849,11 +1207,17 @@ export class ImageEditor {
|
|
|
849
1207
|
hasImage: image !== null,
|
|
850
1208
|
image,
|
|
851
1209
|
maskCount: this.getMasks().length,
|
|
1210
|
+
annotationCount: this.getAnnotations().length,
|
|
852
1211
|
currentScale: this.currentScale,
|
|
853
1212
|
currentRotation: this.currentRotation,
|
|
1213
|
+
isFlippedHorizontally: !!((_a = this.originalImage) === null || _a === void 0 ? void 0 : _a.flipX),
|
|
1214
|
+
isFlippedVertically: !!((_b = this.originalImage) === null || _b === void 0 ? void 0 : _b.flipY),
|
|
854
1215
|
isBusy: this.isBusy(),
|
|
1216
|
+
activeToolMode: this.getActiveToolMode(),
|
|
855
1217
|
isCropMode: this.cropSession !== null,
|
|
856
1218
|
isMosaicMode: this.mosaicSession !== null,
|
|
1219
|
+
isTextMode: this.textSession !== null,
|
|
1220
|
+
isDrawMode: this.drawSession !== null,
|
|
857
1221
|
canUndo: this.historyManager.canUndo(),
|
|
858
1222
|
canRedo: this.historyManager.canRedo(),
|
|
859
1223
|
canvasWidth,
|
|
@@ -866,6 +1230,9 @@ export class ImageEditor {
|
|
|
866
1230
|
emitMasksChanged(context) {
|
|
867
1231
|
this.emitOptionCallback('onMasksChanged', [this.getMasks(), context]);
|
|
868
1232
|
}
|
|
1233
|
+
emitAnnotationsChanged(context) {
|
|
1234
|
+
this.emitOptionCallback('onAnnotationsChanged', [this.getAnnotations(), context]);
|
|
1235
|
+
}
|
|
869
1236
|
emitBusyChangeIfChanged(context) {
|
|
870
1237
|
const isBusy = this.isBusy();
|
|
871
1238
|
if (this.lastEmittedIsBusy === isBusy)
|
|
@@ -874,11 +1241,20 @@ export class ImageEditor {
|
|
|
874
1241
|
this.emitOptionCallback('onBusyChange', [isBusy, context]);
|
|
875
1242
|
}
|
|
876
1243
|
buildSelection(selected) {
|
|
877
|
-
var _a;
|
|
1244
|
+
var _a, _b;
|
|
878
1245
|
const selectedMasks = selected.filter(isMaskObject);
|
|
1246
|
+
const selectedAnnotations = selected.filter(isAnnotationObject);
|
|
1247
|
+
const selectedObjectKind = selectedMasks.length === 1 && selectedAnnotations.length === 0
|
|
1248
|
+
? 'mask'
|
|
1249
|
+
: selectedAnnotations.length === 1 && selectedMasks.length === 0
|
|
1250
|
+
? 'annotation'
|
|
1251
|
+
: null;
|
|
879
1252
|
return {
|
|
880
1253
|
selectedMask: (_a = selectedMasks[0]) !== null && _a !== void 0 ? _a : null,
|
|
881
1254
|
selectedMasks,
|
|
1255
|
+
selectedAnnotation: (_b = selectedAnnotations[0]) !== null && _b !== void 0 ? _b : null,
|
|
1256
|
+
selectedAnnotations,
|
|
1257
|
+
selectedObjectKind,
|
|
882
1258
|
};
|
|
883
1259
|
}
|
|
884
1260
|
withSelectionChangeContext(context, callback) {
|
|
@@ -917,7 +1293,7 @@ export class ImageEditor {
|
|
|
917
1293
|
applyCanvasDimensions(this.canvas, widthPx, heightPx, this.containerElement);
|
|
918
1294
|
}
|
|
919
1295
|
alignObjectBoundingBoxToCanvasTopLeft(object) {
|
|
920
|
-
var _a, _b;
|
|
1296
|
+
var _a, _b, _c;
|
|
921
1297
|
object.setCoords();
|
|
922
1298
|
const boundingRect = object.getBoundingRect();
|
|
923
1299
|
object.set({
|
|
@@ -925,7 +1301,7 @@ export class ImageEditor {
|
|
|
925
1301
|
top: ((_b = object.top) !== null && _b !== void 0 ? _b : 0) - boundingRect.top,
|
|
926
1302
|
});
|
|
927
1303
|
object.setCoords();
|
|
928
|
-
this.canvas.renderAll();
|
|
1304
|
+
(_c = this.canvas) === null || _c === void 0 ? void 0 : _c.renderAll();
|
|
929
1305
|
}
|
|
930
1306
|
measureLayoutViewport(scrollbarSize) {
|
|
931
1307
|
return this.viewportCache.measure(this.containerElement, {
|
|
@@ -933,7 +1309,13 @@ export class ImageEditor {
|
|
|
933
1309
|
height: this.options.canvasHeight,
|
|
934
1310
|
}, scrollbarSize);
|
|
935
1311
|
}
|
|
936
|
-
|
|
1312
|
+
getScrollbarStableViewportCanvasSize(viewport) {
|
|
1313
|
+
return {
|
|
1314
|
+
width: Math.max(1, viewport.width - 1),
|
|
1315
|
+
height: Math.max(1, viewport.height - 1),
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
updateCanvasSizeToImageBounds(options = {}) {
|
|
937
1319
|
var _a, _b;
|
|
938
1320
|
if (!this.originalImage)
|
|
939
1321
|
return;
|
|
@@ -941,13 +1323,26 @@ export class ImageEditor {
|
|
|
941
1323
|
const boundingRect = this.originalImage.getBoundingRect();
|
|
942
1324
|
const scrollbarSize = measureScrollbarSize((_b = (_a = this.containerElement) === null || _a === void 0 ? void 0 : _a.ownerDocument) !== null && _b !== void 0 ? _b : null);
|
|
943
1325
|
const viewport = this.measureLayoutViewport(scrollbarSize);
|
|
1326
|
+
const shouldStabilizeContainedViewport = options.stabilizeContainedViewport !== false;
|
|
1327
|
+
const imageFitsViewport = boundingRect.width <= viewport.width + LAYOUT_EPSILON &&
|
|
1328
|
+
boundingRect.height <= viewport.height + LAYOUT_EPSILON;
|
|
944
1329
|
if (this.currentLayoutMode === 'fit' || this.currentLayoutMode === 'cover') {
|
|
1330
|
+
if (imageFitsViewport) {
|
|
1331
|
+
const canvasSize = shouldStabilizeContainedViewport
|
|
1332
|
+
? this.getScrollbarStableViewportCanvasSize(viewport)
|
|
1333
|
+
: viewport;
|
|
1334
|
+
this.setCanvasSizePx(canvasSize.width, canvasSize.height);
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
945
1337
|
const canvasSize = computeScrollableCanvasSize(boundingRect.width, boundingRect.height, viewport, scrollbarSize);
|
|
946
1338
|
this.setCanvasSizePx(canvasSize.width, canvasSize.height);
|
|
947
1339
|
return;
|
|
948
1340
|
}
|
|
949
|
-
if (
|
|
950
|
-
|
|
1341
|
+
if (imageFitsViewport) {
|
|
1342
|
+
const canvasSize = shouldStabilizeContainedViewport
|
|
1343
|
+
? this.getScrollbarStableViewportCanvasSize(viewport)
|
|
1344
|
+
: viewport;
|
|
1345
|
+
this.setCanvasSizePx(canvasSize.width, canvasSize.height);
|
|
951
1346
|
return;
|
|
952
1347
|
}
|
|
953
1348
|
this.setCanvasSizePx(Math.max(viewport.width, Math.ceil(boundingRect.width)), Math.max(viewport.height, Math.ceil(boundingRect.height)));
|
|
@@ -1047,7 +1442,7 @@ export class ImageEditor {
|
|
|
1047
1442
|
}
|
|
1048
1443
|
buildTransformContext() {
|
|
1049
1444
|
return {
|
|
1050
|
-
canvas: this.
|
|
1445
|
+
canvas: this.getLiveCanvasOrThrow('buildTransformContext'),
|
|
1051
1446
|
options: this.options,
|
|
1052
1447
|
guard: this.operationGuard,
|
|
1053
1448
|
getOriginalImage: () => this.originalImage,
|
|
@@ -1146,6 +1541,70 @@ export class ImageEditor {
|
|
|
1146
1541
|
this.emitBusyChangeIfChanged(context);
|
|
1147
1542
|
});
|
|
1148
1543
|
}
|
|
1544
|
+
flipHorizontal() {
|
|
1545
|
+
if (this.isDisposed || !this.transformController)
|
|
1546
|
+
return Promise.resolve();
|
|
1547
|
+
try {
|
|
1548
|
+
this.assertCanQueueAnimation('flipHorizontal');
|
|
1549
|
+
}
|
|
1550
|
+
catch (error) {
|
|
1551
|
+
return Promise.reject(error);
|
|
1552
|
+
}
|
|
1553
|
+
const controller = this.transformController;
|
|
1554
|
+
const context = this.buildCallbackContext('flipHorizontal', false);
|
|
1555
|
+
const job = this.animQueue.add(async () => {
|
|
1556
|
+
if (this.isDisposed)
|
|
1557
|
+
return;
|
|
1558
|
+
this.updateUi();
|
|
1559
|
+
try {
|
|
1560
|
+
await controller.flipHorizontal();
|
|
1561
|
+
if (!this.isDisposed)
|
|
1562
|
+
this.emitImageChanged(context);
|
|
1563
|
+
}
|
|
1564
|
+
finally {
|
|
1565
|
+
if (!this.isDisposed) {
|
|
1566
|
+
this.updateInputs();
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
});
|
|
1570
|
+
this.emitBusyChangeIfChanged(context);
|
|
1571
|
+
return job.finally(() => {
|
|
1572
|
+
this.refreshUiAfterQueuedAnimation();
|
|
1573
|
+
this.emitBusyChangeIfChanged(context);
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
flipVertical() {
|
|
1577
|
+
if (this.isDisposed || !this.transformController)
|
|
1578
|
+
return Promise.resolve();
|
|
1579
|
+
try {
|
|
1580
|
+
this.assertCanQueueAnimation('flipVertical');
|
|
1581
|
+
}
|
|
1582
|
+
catch (error) {
|
|
1583
|
+
return Promise.reject(error);
|
|
1584
|
+
}
|
|
1585
|
+
const controller = this.transformController;
|
|
1586
|
+
const context = this.buildCallbackContext('flipVertical', false);
|
|
1587
|
+
const job = this.animQueue.add(async () => {
|
|
1588
|
+
if (this.isDisposed)
|
|
1589
|
+
return;
|
|
1590
|
+
this.updateUi();
|
|
1591
|
+
try {
|
|
1592
|
+
await controller.flipVertical();
|
|
1593
|
+
if (!this.isDisposed)
|
|
1594
|
+
this.emitImageChanged(context);
|
|
1595
|
+
}
|
|
1596
|
+
finally {
|
|
1597
|
+
if (!this.isDisposed) {
|
|
1598
|
+
this.updateInputs();
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
});
|
|
1602
|
+
this.emitBusyChangeIfChanged(context);
|
|
1603
|
+
return job.finally(() => {
|
|
1604
|
+
this.refreshUiAfterQueuedAnimation();
|
|
1605
|
+
this.emitBusyChangeIfChanged(context);
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1149
1608
|
resetImageTransform() {
|
|
1150
1609
|
if (this.isDisposed || !this.transformController)
|
|
1151
1610
|
return Promise.resolve();
|
|
@@ -1199,6 +1658,7 @@ export class ImageEditor {
|
|
|
1199
1658
|
const context = this.buildCallbackContext(activeRestoreOperation !== null && activeRestoreOperation !== void 0 ? activeRestoreOperation : 'loadFromState', activeRestoreOperation === 'undo' || activeRestoreOperation === 'redo');
|
|
1200
1659
|
const previousImage = this.originalImage;
|
|
1201
1660
|
const previousMaskSignature = this.getMaskCollectionSignature();
|
|
1661
|
+
const previousAnnotationSignature = this.getAnnotationCollectionSignature();
|
|
1202
1662
|
try {
|
|
1203
1663
|
const restoredState = await loadFromStateImpl({
|
|
1204
1664
|
canvas: this.canvas,
|
|
@@ -1221,6 +1681,7 @@ export class ImageEditor {
|
|
|
1221
1681
|
this.canvas.sendObjectToBack(this.originalImage);
|
|
1222
1682
|
}
|
|
1223
1683
|
this.maskCounter = restoredState.maxMaskId;
|
|
1684
|
+
this.annotationCounter = restoredState.maxAnnotationId;
|
|
1224
1685
|
const editorState = restoredState.editorState;
|
|
1225
1686
|
if (editorState) {
|
|
1226
1687
|
this.currentScale = editorState.currentScale;
|
|
@@ -1238,22 +1699,25 @@ export class ImageEditor {
|
|
|
1238
1699
|
}
|
|
1239
1700
|
this.isImageLoadedToCanvas = !!this.originalImage;
|
|
1240
1701
|
if (this.originalImage && this.shouldNormalizeCanvasSizeAfterStateRestore()) {
|
|
1241
|
-
this.updateCanvasSizeToImageBounds();
|
|
1702
|
+
this.updateCanvasSizeToImageBounds({ stabilizeContainedViewport: false });
|
|
1242
1703
|
this.alignObjectBoundingBoxToCanvasTopLeft(this.originalImage);
|
|
1243
1704
|
}
|
|
1244
1705
|
if (this.originalImage) {
|
|
1245
1706
|
this.settleFitCoverScrollbarsAfterStateRestore();
|
|
1246
1707
|
}
|
|
1247
|
-
const restoredMasks = restoredState.
|
|
1708
|
+
const restoredMasks = restoredState.masks;
|
|
1248
1709
|
this.lastMask = restoredMasks.reduce((lastMask, maskObject) => !lastMask || maskObject.maskId > lastMask.maskId ? maskObject : lastMask, null);
|
|
1249
1710
|
restoredMasks.forEach((maskObject) => {
|
|
1250
1711
|
applyMaskUnselectedStyle(maskObject);
|
|
1251
1712
|
reattachMaskHoverHandlers(maskObject);
|
|
1252
1713
|
});
|
|
1714
|
+
syncAnnotationRuntimeStates(restoredState.annotations);
|
|
1715
|
+
attachTextEditingHandlersToAnnotations(this.buildTextControllerContext(), restoredState.annotations);
|
|
1253
1716
|
this.lastSnapshot = this.captureSnapshotInternal();
|
|
1254
1717
|
this.canvas.renderAll();
|
|
1255
1718
|
this.updateInputs();
|
|
1256
1719
|
this.updateMaskList();
|
|
1720
|
+
this.updateAnnotationList();
|
|
1257
1721
|
this.updateUi();
|
|
1258
1722
|
if (previousImage && previousImage !== this.originalImage) {
|
|
1259
1723
|
this.emitOptionCallback('onImageCleared', [previousImage, context]);
|
|
@@ -1261,17 +1725,32 @@ export class ImageEditor {
|
|
|
1261
1725
|
if (previousMaskSignature !== this.getMaskCollectionSignature()) {
|
|
1262
1726
|
this.emitMasksChanged(context);
|
|
1263
1727
|
}
|
|
1728
|
+
if (previousAnnotationSignature !== this.getAnnotationCollectionSignature()) {
|
|
1729
|
+
this.emitAnnotationsChanged(context);
|
|
1730
|
+
}
|
|
1264
1731
|
this.emitImageChanged(context);
|
|
1732
|
+
const canvas = this.getLiveCanvasOrThrow('loadFromState');
|
|
1265
1733
|
const activeMaskId = editorState === null || editorState === void 0 ? void 0 : editorState.activeMaskId;
|
|
1266
|
-
|
|
1734
|
+
const activeAnnotationId = editorState === null || editorState === void 0 ? void 0 : editorState.activeAnnotationId;
|
|
1735
|
+
if ((editorState === null || editorState === void 0 ? void 0 : editorState.activeObjectKind) === 'mask' && typeof activeMaskId === 'number') {
|
|
1267
1736
|
const activeMask = restoredMasks.find((maskObject) => maskObject.maskId === activeMaskId);
|
|
1268
1737
|
if (activeMask) {
|
|
1269
1738
|
this.withSelectionChangeContext(context, () => {
|
|
1270
|
-
|
|
1739
|
+
canvas.setActiveObject(activeMask);
|
|
1271
1740
|
this.handleSelectionChanged([activeMask]);
|
|
1272
1741
|
});
|
|
1273
1742
|
}
|
|
1274
1743
|
}
|
|
1744
|
+
else if ((editorState === null || editorState === void 0 ? void 0 : editorState.activeObjectKind) === 'annotation' &&
|
|
1745
|
+
typeof activeAnnotationId === 'number') {
|
|
1746
|
+
const activeAnnotation = restoredState.annotations.find((annotation) => annotation.annotationId === activeAnnotationId);
|
|
1747
|
+
if (activeAnnotation) {
|
|
1748
|
+
this.withSelectionChangeContext(context, () => {
|
|
1749
|
+
canvas.setActiveObject(activeAnnotation);
|
|
1750
|
+
this.handleSelectionChanged([activeAnnotation]);
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1275
1754
|
}
|
|
1276
1755
|
catch (error) {
|
|
1277
1756
|
reportError(this.options, error, 'Failed to restore canvas state.');
|
|
@@ -1282,24 +1761,26 @@ export class ImageEditor {
|
|
|
1282
1761
|
this.saveStateInternal();
|
|
1283
1762
|
}
|
|
1284
1763
|
saveStateInternal(options) {
|
|
1285
|
-
var _a, _b;
|
|
1764
|
+
var _a, _b, _c;
|
|
1286
1765
|
if (!this.canvas || this.shouldSuppressSaveState)
|
|
1287
1766
|
return;
|
|
1288
1767
|
if (!this.canRunIdleOperation('saveState', options))
|
|
1289
1768
|
return;
|
|
1290
1769
|
const activeObj = this.canvas.getActiveObject();
|
|
1291
1770
|
const activeMask = this.getActiveMaskForSnapshot();
|
|
1771
|
+
const activeAnnotation = this.getActiveAnnotationForSnapshot();
|
|
1292
1772
|
this.hideAllMaskLabels();
|
|
1293
1773
|
try {
|
|
1294
1774
|
const after = saveStateImpl({
|
|
1295
1775
|
canvas: this.canvas,
|
|
1296
1776
|
activeMaskId: (_a = activeMask === null || activeMask === void 0 ? void 0 : activeMask.maskId) !== null && _a !== void 0 ? _a : null,
|
|
1777
|
+
activeAnnotationId: (_b = activeAnnotation === null || activeAnnotation === void 0 ? void 0 : activeAnnotation.annotationId) !== null && _b !== void 0 ? _b : null,
|
|
1297
1778
|
currentScale: this.currentScale,
|
|
1298
1779
|
currentRotation: this.currentRotation,
|
|
1299
1780
|
baseImageScale: this.baseImageScale,
|
|
1300
1781
|
currentImageMimeType: this.currentImageMimeType,
|
|
1301
1782
|
});
|
|
1302
|
-
const before = (
|
|
1783
|
+
const before = (_c = this.lastSnapshot) !== null && _c !== void 0 ? _c : after;
|
|
1303
1784
|
if (after === before) {
|
|
1304
1785
|
return;
|
|
1305
1786
|
}
|
|
@@ -1315,25 +1796,32 @@ export class ImageEditor {
|
|
|
1315
1796
|
reportWarning(this.options, error, 'Failed to capture canvas snapshot.');
|
|
1316
1797
|
}
|
|
1317
1798
|
finally {
|
|
1318
|
-
this.
|
|
1799
|
+
this.restoreActiveObjectAfterSnapshot(activeObj, activeMask, activeAnnotation);
|
|
1319
1800
|
this.updateUi();
|
|
1320
1801
|
}
|
|
1321
1802
|
}
|
|
1322
|
-
|
|
1803
|
+
restoreActiveObjectAfterSnapshot(activeObj, activeMask, activeAnnotation) {
|
|
1323
1804
|
if (!this.canvas)
|
|
1324
1805
|
return;
|
|
1325
1806
|
const maskToRestore = activeObj && isMaskObject(activeObj) ? activeObj : activeMask;
|
|
1326
|
-
|
|
1807
|
+
const annotationToRestore = activeObj && isAnnotationObject(activeObj) ? activeObj : activeAnnotation;
|
|
1808
|
+
if (maskToRestore && this.canvas.getObjects().includes(maskToRestore)) {
|
|
1809
|
+
this.canvas.setActiveObject(maskToRestore);
|
|
1810
|
+
this.showLabelForMask(maskToRestore);
|
|
1811
|
+
this.updateMaskListSelection(maskToRestore);
|
|
1327
1812
|
return;
|
|
1328
|
-
|
|
1329
|
-
this.
|
|
1330
|
-
|
|
1813
|
+
}
|
|
1814
|
+
if (annotationToRestore && this.canvas.getObjects().includes(annotationToRestore)) {
|
|
1815
|
+
this.canvas.setActiveObject(annotationToRestore);
|
|
1816
|
+
this.updateAnnotationListSelection(annotationToRestore);
|
|
1817
|
+
}
|
|
1331
1818
|
}
|
|
1332
1819
|
undo() {
|
|
1333
1820
|
if (this.isDisposed)
|
|
1334
1821
|
return Promise.resolve();
|
|
1335
1822
|
if (!this.canRunIdleOperation('undo'))
|
|
1336
1823
|
return Promise.resolve();
|
|
1824
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
1337
1825
|
const context = this.buildCallbackContext('undo', true);
|
|
1338
1826
|
const job = this.animQueue.add(async () => {
|
|
1339
1827
|
if (this.isDisposed)
|
|
@@ -1357,6 +1845,7 @@ export class ImageEditor {
|
|
|
1357
1845
|
return Promise.resolve();
|
|
1358
1846
|
if (!this.canRunIdleOperation('redo'))
|
|
1359
1847
|
return Promise.resolve();
|
|
1848
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
1360
1849
|
const context = this.buildCallbackContext('redo', true);
|
|
1361
1850
|
const job = this.animQueue.add(async () => {
|
|
1362
1851
|
if (this.isDisposed)
|
|
@@ -1422,7 +1911,7 @@ export class ImageEditor {
|
|
|
1422
1911
|
buildCreateMaskContext() {
|
|
1423
1912
|
return {
|
|
1424
1913
|
fabric: this.fabricModule,
|
|
1425
|
-
canvas: this.
|
|
1914
|
+
canvas: this.getLiveCanvasOrThrow('createMask'),
|
|
1426
1915
|
options: this.getRuntimeOptions(),
|
|
1427
1916
|
getLastMask: () => this.lastMask,
|
|
1428
1917
|
setLastMask: (maskObject) => {
|
|
@@ -1445,7 +1934,7 @@ export class ImageEditor {
|
|
|
1445
1934
|
}
|
|
1446
1935
|
buildRemoveMaskContext() {
|
|
1447
1936
|
return {
|
|
1448
|
-
canvas: this.
|
|
1937
|
+
canvas: this.getLiveCanvasOrThrow('removeMask'),
|
|
1449
1938
|
removeLabelForMask: (mask) => {
|
|
1450
1939
|
this.removeLabelForMask(mask);
|
|
1451
1940
|
},
|
|
@@ -1495,11 +1984,35 @@ export class ImageEditor {
|
|
|
1495
1984
|
return;
|
|
1496
1985
|
showLabelForMask(context, mask);
|
|
1497
1986
|
}
|
|
1987
|
+
handleObjectMovingScalingRotating(target) {
|
|
1988
|
+
if (isMaskObject(target)) {
|
|
1989
|
+
this.syncMaskLabel(target);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
handleObjectModified(target) {
|
|
1993
|
+
if (isMaskObject(target)) {
|
|
1994
|
+
this.syncMaskLabel(target);
|
|
1995
|
+
const context = this.buildCallbackContext('saveState', false);
|
|
1996
|
+
this.saveState();
|
|
1997
|
+
this.emitMasksChanged(context);
|
|
1998
|
+
this.emitImageChanged(context);
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
2001
|
+
if (isAnnotationObject(target)) {
|
|
2002
|
+
if (isAnnotationLocked(target))
|
|
2003
|
+
return;
|
|
2004
|
+
const context = this.buildCallbackContext('updateAnnotation', false);
|
|
2005
|
+
this.saveState();
|
|
2006
|
+
this.emitAnnotationsChanged(context);
|
|
2007
|
+
this.emitImageChanged(context);
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
1498
2010
|
handleSelectionChanged(selected) {
|
|
1499
|
-
var _a, _b, _c;
|
|
2011
|
+
var _a, _b, _c, _d;
|
|
1500
2012
|
if (!this.canvas)
|
|
1501
2013
|
return;
|
|
1502
2014
|
const selectedMask = (_a = selected.find(isMaskObject)) !== null && _a !== void 0 ? _a : null;
|
|
2015
|
+
const selectedAnnotation = (_b = selected.find(isAnnotationObject)) !== null && _b !== void 0 ? _b : null;
|
|
1503
2016
|
const masks = this.canvas.getObjects().filter(isMaskObject);
|
|
1504
2017
|
masks.forEach((maskObject) => {
|
|
1505
2018
|
if (maskObject !== selectedMask) {
|
|
@@ -1515,9 +2028,10 @@ export class ImageEditor {
|
|
|
1515
2028
|
if (selectedMask)
|
|
1516
2029
|
this.showLabelForMask(selectedMask);
|
|
1517
2030
|
this.updateMaskListSelection(selectedMask);
|
|
2031
|
+
this.updateAnnotationListSelection(selectedAnnotation);
|
|
1518
2032
|
this.canvas.requestRenderAll();
|
|
1519
2033
|
this.updateUi();
|
|
1520
|
-
const context = (
|
|
2034
|
+
const context = (_c = this.nextSelectionChangeContext) !== null && _c !== void 0 ? _c : this.buildCallbackContext((_d = this.activeStateRestoreOperation) !== null && _d !== void 0 ? _d : 'createMask', this.activeStateRestoreOperation === 'undo' ||
|
|
1521
2035
|
this.activeStateRestoreOperation === 'redo');
|
|
1522
2036
|
this.emitOptionCallback('onSelectionChange', [this.buildSelection(selected), context]);
|
|
1523
2037
|
}
|
|
@@ -1534,11 +2048,426 @@ export class ImageEditor {
|
|
|
1534
2048
|
updateMaskListSelection(selectedMask) {
|
|
1535
2049
|
updateMaskListSelection(this.buildMaskListContext(), selectedMask);
|
|
1536
2050
|
}
|
|
2051
|
+
enterTextMode() {
|
|
2052
|
+
if (!this.canvas)
|
|
2053
|
+
return;
|
|
2054
|
+
if (!this.canRunIdleOperation('enterTextMode'))
|
|
2055
|
+
return;
|
|
2056
|
+
if (this.isToolModeActive())
|
|
2057
|
+
return;
|
|
2058
|
+
enterTextModeImpl(this.buildTextControllerContext());
|
|
2059
|
+
const callbackContext = this.buildCallbackContext('enterTextMode', false);
|
|
2060
|
+
this.emitBusyChangeIfChanged(callbackContext);
|
|
2061
|
+
this.emitImageChanged(callbackContext);
|
|
2062
|
+
}
|
|
2063
|
+
exitTextMode() {
|
|
2064
|
+
if (!this.canvas || !this.textSession)
|
|
2065
|
+
return;
|
|
2066
|
+
if (!this.canRunIdleOperation('exitTextMode'))
|
|
2067
|
+
return;
|
|
2068
|
+
exitTextModeImpl(this.buildTextControllerContext());
|
|
2069
|
+
const callbackContext = this.buildCallbackContext('exitTextMode', false);
|
|
2070
|
+
this.emitBusyChangeIfChanged(callbackContext);
|
|
2071
|
+
this.emitImageChanged(callbackContext);
|
|
2072
|
+
}
|
|
2073
|
+
isTextMode() {
|
|
2074
|
+
return this.textSession !== null;
|
|
2075
|
+
}
|
|
2076
|
+
createTextAnnotation(config = {}) {
|
|
2077
|
+
if (!this.canvas)
|
|
2078
|
+
return null;
|
|
2079
|
+
if (!this.canRunIdleOperation('createTextAnnotation'))
|
|
2080
|
+
return null;
|
|
2081
|
+
return createTextAnnotationImpl(this.buildTextControllerContext(), config);
|
|
2082
|
+
}
|
|
2083
|
+
enterDrawMode() {
|
|
2084
|
+
if (!this.canvas)
|
|
2085
|
+
return;
|
|
2086
|
+
if (!this.canRunIdleOperation('enterDrawMode'))
|
|
2087
|
+
return;
|
|
2088
|
+
if (this.isToolModeActive())
|
|
2089
|
+
return;
|
|
2090
|
+
enterDrawModeImpl(this.buildDrawControllerContext());
|
|
2091
|
+
const callbackContext = this.buildCallbackContext('enterDrawMode', false);
|
|
2092
|
+
this.emitBusyChangeIfChanged(callbackContext);
|
|
2093
|
+
this.emitImageChanged(callbackContext);
|
|
2094
|
+
}
|
|
2095
|
+
exitDrawMode() {
|
|
2096
|
+
if (!this.canvas || !this.drawSession)
|
|
2097
|
+
return;
|
|
2098
|
+
if (!this.canRunIdleOperation('exitDrawMode'))
|
|
2099
|
+
return;
|
|
2100
|
+
exitDrawModeImpl(this.buildDrawControllerContext());
|
|
2101
|
+
const callbackContext = this.buildCallbackContext('exitDrawMode', false);
|
|
2102
|
+
this.emitBusyChangeIfChanged(callbackContext);
|
|
2103
|
+
this.emitImageChanged(callbackContext);
|
|
2104
|
+
}
|
|
2105
|
+
isDrawMode() {
|
|
2106
|
+
return this.drawSession !== null;
|
|
2107
|
+
}
|
|
2108
|
+
getTextConfig() {
|
|
2109
|
+
return cloneResolvedTextAnnotationConfig(this.currentTextConfig);
|
|
2110
|
+
}
|
|
2111
|
+
setTextConfig(config) {
|
|
2112
|
+
this.applyTextConfigPatch(config, 'setTextConfig');
|
|
2113
|
+
}
|
|
2114
|
+
resetTextConfig() {
|
|
2115
|
+
this.applyTextConfigPatch(this.defaultTextConfig, 'resetTextConfig');
|
|
2116
|
+
}
|
|
2117
|
+
setTextColor(color) {
|
|
2118
|
+
this.applyTextConfigPatch({ fill: color }, 'setTextColor');
|
|
2119
|
+
}
|
|
2120
|
+
setTextFontSize(size) {
|
|
2121
|
+
this.applyTextConfigPatch({ fontSize: size }, 'setTextFontSize');
|
|
2122
|
+
}
|
|
2123
|
+
getDrawConfig() {
|
|
2124
|
+
return cloneResolvedDrawConfig(this.currentDrawConfig);
|
|
2125
|
+
}
|
|
2126
|
+
setDrawConfig(config) {
|
|
2127
|
+
this.applyDrawConfigPatch(config, 'setDrawConfig');
|
|
2128
|
+
}
|
|
2129
|
+
resetDrawConfig() {
|
|
2130
|
+
this.applyDrawConfigPatch(this.defaultDrawConfig, 'resetDrawConfig');
|
|
2131
|
+
}
|
|
2132
|
+
setDrawColor(color) {
|
|
2133
|
+
this.applyDrawConfigPatch({ color }, 'setDrawColor');
|
|
2134
|
+
}
|
|
2135
|
+
setDrawBrushSize(size) {
|
|
2136
|
+
this.applyDrawConfigPatch({ brushSize: size }, 'setDrawBrushSize');
|
|
2137
|
+
}
|
|
2138
|
+
removeSelectedAnnotation() {
|
|
2139
|
+
if (!this.canvas)
|
|
2140
|
+
return;
|
|
2141
|
+
if (!this.canRunIdleOperation('removeSelectedAnnotation'))
|
|
2142
|
+
return;
|
|
2143
|
+
const before = this.getAnnotations().length;
|
|
2144
|
+
const callbackContext = this.buildCallbackContext('removeSelectedAnnotation', false);
|
|
2145
|
+
this.withSelectionChangeContext(callbackContext, () => {
|
|
2146
|
+
removeSelectedAnnotationImpl(this.buildAnnotationManagerContext());
|
|
2147
|
+
});
|
|
2148
|
+
this.updateAnnotationList();
|
|
2149
|
+
this.updateUi();
|
|
2150
|
+
if (this.getAnnotations().length !== before) {
|
|
2151
|
+
this.emitAnnotationsChanged(callbackContext);
|
|
2152
|
+
this.emitImageChanged(callbackContext);
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
removeAllAnnotations(options = {}) {
|
|
2156
|
+
if (!this.canvas)
|
|
2157
|
+
return;
|
|
2158
|
+
if (!this.canRunIdleOperation('removeAllAnnotations', options))
|
|
2159
|
+
return;
|
|
2160
|
+
const before = this.getAnnotations().length;
|
|
2161
|
+
const callbackContext = this.buildCallbackContext('removeAllAnnotations', false);
|
|
2162
|
+
this.withSelectionChangeContext(callbackContext, () => {
|
|
2163
|
+
removeAllAnnotationsImpl(this.buildAnnotationManagerContext(), options);
|
|
2164
|
+
});
|
|
2165
|
+
this.updateAnnotationList();
|
|
2166
|
+
this.updateUi();
|
|
2167
|
+
if (this.getAnnotations().length !== before) {
|
|
2168
|
+
this.emitAnnotationsChanged(callbackContext);
|
|
2169
|
+
this.emitImageChanged(callbackContext);
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
updateAnnotation(annotationId, config) {
|
|
2173
|
+
if (!this.canvas)
|
|
2174
|
+
return;
|
|
2175
|
+
if (!this.canRunIdleOperation('updateAnnotation'))
|
|
2176
|
+
return;
|
|
2177
|
+
const callbackContext = this.buildCallbackContext('updateAnnotation', false);
|
|
2178
|
+
const changed = updateAnnotationImpl(this.buildAnnotationManagerContext(), annotationId, config);
|
|
2179
|
+
if (changed) {
|
|
2180
|
+
this.updateAnnotationList();
|
|
2181
|
+
this.emitAnnotationsChanged(callbackContext);
|
|
2182
|
+
this.emitImageChanged(callbackContext);
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
updateSelectedAnnotation(config) {
|
|
2186
|
+
if (!this.canvas)
|
|
2187
|
+
return;
|
|
2188
|
+
if (!this.canRunIdleOperation('updateSelectedAnnotation'))
|
|
2189
|
+
return;
|
|
2190
|
+
const callbackContext = this.buildCallbackContext('updateSelectedAnnotation', false);
|
|
2191
|
+
const changed = updateSelectedAnnotationImpl(this.buildAnnotationManagerContext(), config);
|
|
2192
|
+
if (changed) {
|
|
2193
|
+
this.updateAnnotationList();
|
|
2194
|
+
this.emitAnnotationsChanged(callbackContext);
|
|
2195
|
+
this.emitImageChanged(callbackContext);
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
deleteSelectedObject() {
|
|
2199
|
+
if (!this.canvas)
|
|
2200
|
+
return;
|
|
2201
|
+
if (!this.canRunIdleOperation('deleteSelectedObject'))
|
|
2202
|
+
return;
|
|
2203
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
2204
|
+
const selectedObjects = this.getSelectedCanvasObjects();
|
|
2205
|
+
const selectedMasks = selectedObjects.filter(isMaskObject);
|
|
2206
|
+
const selectedAnnotations = selectedObjects.filter((object) => isAnnotationObject(object) && isAnnotationUnlocked(object));
|
|
2207
|
+
if (selectedMasks.length === 0 && selectedAnnotations.length === 0)
|
|
2208
|
+
return;
|
|
2209
|
+
const canvas = this.getLiveCanvasOrThrow('deleteSelectedObject');
|
|
2210
|
+
const callbackContext = this.buildCallbackContext('deleteSelectedObject', false);
|
|
2211
|
+
this.withSelectionChangeContext(callbackContext, () => {
|
|
2212
|
+
for (const mask of selectedMasks) {
|
|
2213
|
+
this.removeLabelForMask(mask);
|
|
2214
|
+
canvas.remove(mask);
|
|
2215
|
+
}
|
|
2216
|
+
removeAnnotationObjects(this.buildAnnotationManagerContext(), selectedAnnotations, {
|
|
2217
|
+
saveHistory: false,
|
|
2218
|
+
force: true,
|
|
2219
|
+
});
|
|
2220
|
+
canvas.discardActiveObject();
|
|
2221
|
+
canvas.renderAll();
|
|
2222
|
+
this.saveState();
|
|
2223
|
+
});
|
|
2224
|
+
this.updateMaskList();
|
|
2225
|
+
this.updateAnnotationList();
|
|
2226
|
+
this.updateUi();
|
|
2227
|
+
if (selectedMasks.length > 0)
|
|
2228
|
+
this.emitMasksChanged(callbackContext);
|
|
2229
|
+
if (selectedAnnotations.length > 0)
|
|
2230
|
+
this.emitAnnotationsChanged(callbackContext);
|
|
2231
|
+
this.emitImageChanged(callbackContext);
|
|
2232
|
+
}
|
|
2233
|
+
bringSelectedObjectForward() {
|
|
2234
|
+
this.moveSelectedEditableObject('bringSelectedObjectForward');
|
|
2235
|
+
}
|
|
2236
|
+
sendSelectedObjectBackward() {
|
|
2237
|
+
this.moveSelectedEditableObject('sendSelectedObjectBackward');
|
|
2238
|
+
}
|
|
2239
|
+
bringSelectedObjectToFront() {
|
|
2240
|
+
this.moveSelectedEditableObject('bringSelectedObjectToFront');
|
|
2241
|
+
}
|
|
2242
|
+
sendSelectedObjectToBack() {
|
|
2243
|
+
this.moveSelectedEditableObject('sendSelectedObjectToBack');
|
|
2244
|
+
}
|
|
2245
|
+
buildAnnotationManagerContext() {
|
|
2246
|
+
return {
|
|
2247
|
+
canvas: this.getLiveCanvasOrThrow('annotationManager'),
|
|
2248
|
+
saveCanvasState: () => this.saveState(),
|
|
2249
|
+
updateUi: () => this.updateUi(),
|
|
2250
|
+
};
|
|
2251
|
+
}
|
|
2252
|
+
buildAnnotationListContext() {
|
|
2253
|
+
return {
|
|
2254
|
+
canvas: this.canvas,
|
|
2255
|
+
getListElementId: () => this.elements.annotationList,
|
|
2256
|
+
onAnnotationSelected: (annotation) => this.handleSelectionChanged([annotation]),
|
|
2257
|
+
};
|
|
2258
|
+
}
|
|
2259
|
+
updateAnnotationList() {
|
|
2260
|
+
renderAnnotationList(this.buildAnnotationListContext());
|
|
2261
|
+
}
|
|
2262
|
+
updateAnnotationListSelection(selectedAnnotation) {
|
|
2263
|
+
updateAnnotationListSelection(this.buildAnnotationListContext(), selectedAnnotation);
|
|
2264
|
+
}
|
|
2265
|
+
buildTextControllerContext() {
|
|
2266
|
+
return {
|
|
2267
|
+
fabric: this.fabricModule,
|
|
2268
|
+
canvas: this.getLiveCanvasOrThrow('textController'),
|
|
2269
|
+
options: this.options,
|
|
2270
|
+
getOriginalImage: () => this.originalImage,
|
|
2271
|
+
getTextConfig: () => this.currentTextConfig,
|
|
2272
|
+
isImageLoaded: () => this.isImageLoaded(),
|
|
2273
|
+
getAnnotationCounter: () => this.annotationCounter,
|
|
2274
|
+
setAnnotationCounter: (value) => {
|
|
2275
|
+
this.annotationCounter = value;
|
|
2276
|
+
},
|
|
2277
|
+
getTextSession: () => this.textSession,
|
|
2278
|
+
setTextSession: (session) => {
|
|
2279
|
+
this.textSession = session;
|
|
2280
|
+
},
|
|
2281
|
+
saveCanvasState: () => this.saveState(),
|
|
2282
|
+
updateAnnotationList: () => this.updateAnnotationList(),
|
|
2283
|
+
updateUi: () => this.updateUi(),
|
|
2284
|
+
emitAnnotationsChanged: (context) => this.emitAnnotationsChanged(context),
|
|
2285
|
+
emitImageChanged: (context) => this.emitImageChanged(context),
|
|
2286
|
+
buildCallbackContext: (operation) => this.buildCallbackContext(operation, false),
|
|
2287
|
+
};
|
|
2288
|
+
}
|
|
2289
|
+
buildDrawControllerContext() {
|
|
2290
|
+
return {
|
|
2291
|
+
fabric: this.fabricModule,
|
|
2292
|
+
canvas: this.getLiveCanvasOrThrow('drawController'),
|
|
2293
|
+
options: this.options,
|
|
2294
|
+
getDrawConfig: () => this.currentDrawConfig,
|
|
2295
|
+
isImageLoaded: () => this.isImageLoaded(),
|
|
2296
|
+
getAnnotationCounter: () => this.annotationCounter,
|
|
2297
|
+
setAnnotationCounter: (value) => {
|
|
2298
|
+
this.annotationCounter = value;
|
|
2299
|
+
},
|
|
2300
|
+
getDrawSession: () => this.drawSession,
|
|
2301
|
+
setDrawSession: (session) => {
|
|
2302
|
+
this.drawSession = session;
|
|
2303
|
+
},
|
|
2304
|
+
saveCanvasState: () => this.saveState(),
|
|
2305
|
+
updateAnnotationList: () => this.updateAnnotationList(),
|
|
2306
|
+
updateUi: () => this.updateUi(),
|
|
2307
|
+
emitAnnotationsChanged: (context) => this.emitAnnotationsChanged(context),
|
|
2308
|
+
emitImageChanged: (context) => this.emitImageChanged(context),
|
|
2309
|
+
buildCallbackContext: (operation) => this.buildCallbackContext(operation, false),
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2312
|
+
applyTextConfigPatch(config, operation) {
|
|
2313
|
+
if (!this.canRunIdleOperation(operation))
|
|
2314
|
+
return;
|
|
2315
|
+
const invalidFields = getInvalidTextAnnotationConfigFields(config);
|
|
2316
|
+
if (invalidFields.length > 0) {
|
|
2317
|
+
reportWarning(this.options, null, `${operation} ignored invalid Text config fields: ${invalidFields.join(', ')}.`);
|
|
2318
|
+
}
|
|
2319
|
+
const next = mergeTextAnnotationConfigPatch(this.currentTextConfig, config, this.defaultTextConfig);
|
|
2320
|
+
if (areResolvedTextAnnotationConfigsEqual(this.currentTextConfig, next))
|
|
2321
|
+
return;
|
|
2322
|
+
this.currentTextConfig = next;
|
|
2323
|
+
this.updateInputs();
|
|
2324
|
+
this.updateUi();
|
|
2325
|
+
this.emitImageChanged(this.buildCallbackContext(operation, false));
|
|
2326
|
+
}
|
|
2327
|
+
applyDrawConfigPatch(config, operation) {
|
|
2328
|
+
if (!this.canRunIdleOperation(operation))
|
|
2329
|
+
return;
|
|
2330
|
+
const invalidFields = getInvalidDrawConfigFields(config);
|
|
2331
|
+
if (invalidFields.length > 0) {
|
|
2332
|
+
reportWarning(this.options, null, `${operation} ignored invalid Draw config fields: ${invalidFields.join(', ')}.`);
|
|
2333
|
+
}
|
|
2334
|
+
const next = mergeDrawConfigPatch(this.currentDrawConfig, config, this.defaultDrawConfig);
|
|
2335
|
+
if (areResolvedDrawConfigsEqual(this.currentDrawConfig, next))
|
|
2336
|
+
return;
|
|
2337
|
+
this.currentDrawConfig = next;
|
|
2338
|
+
updateDrawBrush(this.buildDrawControllerContext());
|
|
2339
|
+
this.updateInputs();
|
|
2340
|
+
this.updateUi();
|
|
2341
|
+
this.emitImageChanged(this.buildCallbackContext(operation, false));
|
|
2342
|
+
}
|
|
2343
|
+
applyTextColorInput(color) {
|
|
2344
|
+
var _a;
|
|
2345
|
+
if (this.isTextMode()) {
|
|
2346
|
+
this.setTextColor(color);
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2349
|
+
const selected = (_a = this.canvas) === null || _a === void 0 ? void 0 : _a.getActiveObject();
|
|
2350
|
+
if (selected && isTextAnnotationObject(selected)) {
|
|
2351
|
+
this.updateSelectedAnnotation({ fill: color });
|
|
2352
|
+
return;
|
|
2353
|
+
}
|
|
2354
|
+
this.setTextColor(color);
|
|
2355
|
+
}
|
|
2356
|
+
applyTextFontSizeInput(size) {
|
|
2357
|
+
var _a;
|
|
2358
|
+
if (this.isTextMode()) {
|
|
2359
|
+
this.setTextFontSize(size);
|
|
2360
|
+
return;
|
|
2361
|
+
}
|
|
2362
|
+
const selected = (_a = this.canvas) === null || _a === void 0 ? void 0 : _a.getActiveObject();
|
|
2363
|
+
if (selected && isTextAnnotationObject(selected)) {
|
|
2364
|
+
this.updateSelectedAnnotation({ fontSize: size });
|
|
2365
|
+
return;
|
|
2366
|
+
}
|
|
2367
|
+
this.setTextFontSize(size);
|
|
2368
|
+
}
|
|
2369
|
+
applyDrawColorInput(color) {
|
|
2370
|
+
var _a;
|
|
2371
|
+
if (this.isDrawMode()) {
|
|
2372
|
+
this.setDrawColor(color);
|
|
2373
|
+
return;
|
|
2374
|
+
}
|
|
2375
|
+
const selected = (_a = this.canvas) === null || _a === void 0 ? void 0 : _a.getActiveObject();
|
|
2376
|
+
if (selected && isDrawAnnotationObject(selected)) {
|
|
2377
|
+
this.updateSelectedAnnotation({ stroke: color });
|
|
2378
|
+
return;
|
|
2379
|
+
}
|
|
2380
|
+
this.setDrawColor(color);
|
|
2381
|
+
}
|
|
2382
|
+
applyDrawBrushSizeInput(size) {
|
|
2383
|
+
var _a;
|
|
2384
|
+
if (this.isDrawMode()) {
|
|
2385
|
+
this.setDrawBrushSize(size);
|
|
2386
|
+
return;
|
|
2387
|
+
}
|
|
2388
|
+
const selected = (_a = this.canvas) === null || _a === void 0 ? void 0 : _a.getActiveObject();
|
|
2389
|
+
if (selected && isDrawAnnotationObject(selected)) {
|
|
2390
|
+
this.updateSelectedAnnotation({ strokeWidth: size });
|
|
2391
|
+
return;
|
|
2392
|
+
}
|
|
2393
|
+
this.setDrawBrushSize(size);
|
|
2394
|
+
}
|
|
2395
|
+
getSelectedCanvasObjects() {
|
|
2396
|
+
var _a, _b, _c;
|
|
2397
|
+
if (!this.canvas)
|
|
2398
|
+
return [];
|
|
2399
|
+
const activeObject = this.canvas.getActiveObject();
|
|
2400
|
+
if (!activeObject)
|
|
2401
|
+
return [];
|
|
2402
|
+
const type = typeof activeObject.type === 'string' ? activeObject.type.toLowerCase() : '';
|
|
2403
|
+
const isActiveSelection = type === 'activeselection' ||
|
|
2404
|
+
((_c = (_b = (_a = activeObject).isType) === null || _b === void 0 ? void 0 : _b.call(_a, 'ActiveSelection')) !== null && _c !== void 0 ? _c : false);
|
|
2405
|
+
if (!isActiveSelection)
|
|
2406
|
+
return [activeObject];
|
|
2407
|
+
const getObjects = activeObject
|
|
2408
|
+
.getObjects;
|
|
2409
|
+
return typeof getObjects === 'function' ? getObjects.call(activeObject) : [];
|
|
2410
|
+
}
|
|
2411
|
+
moveSelectedEditableObject(operation) {
|
|
2412
|
+
if (!this.canvas)
|
|
2413
|
+
return;
|
|
2414
|
+
if (!this.canRunIdleOperation(operation))
|
|
2415
|
+
return;
|
|
2416
|
+
const selected = this.getSelectedCanvasObjects().filter(isEditableOverlayObject);
|
|
2417
|
+
if (selected.length !== 1) {
|
|
2418
|
+
if (selected.length > 1) {
|
|
2419
|
+
reportWarning(this.options, null, `${operation} skipped: ActiveSelection layer moves are not supported.`);
|
|
2420
|
+
}
|
|
2421
|
+
return;
|
|
2422
|
+
}
|
|
2423
|
+
const object = selected[0];
|
|
2424
|
+
const range = getEditableOverlayRange(this.canvas);
|
|
2425
|
+
const overlays = range.overlays;
|
|
2426
|
+
const currentOverlayIndex = overlays.indexOf(object);
|
|
2427
|
+
if (currentOverlayIndex < 0)
|
|
2428
|
+
return;
|
|
2429
|
+
let nextOverlayIndex = currentOverlayIndex;
|
|
2430
|
+
if (operation === 'bringSelectedObjectForward') {
|
|
2431
|
+
nextOverlayIndex = Math.min(overlays.length - 1, currentOverlayIndex + 1);
|
|
2432
|
+
}
|
|
2433
|
+
else if (operation === 'sendSelectedObjectBackward') {
|
|
2434
|
+
nextOverlayIndex = Math.max(0, currentOverlayIndex - 1);
|
|
2435
|
+
}
|
|
2436
|
+
else if (operation === 'bringSelectedObjectToFront') {
|
|
2437
|
+
nextOverlayIndex = overlays.length - 1;
|
|
2438
|
+
}
|
|
2439
|
+
else if (operation === 'sendSelectedObjectToBack') {
|
|
2440
|
+
nextOverlayIndex = 0;
|
|
2441
|
+
}
|
|
2442
|
+
if (nextOverlayIndex === currentOverlayIndex)
|
|
2443
|
+
return;
|
|
2444
|
+
const reordered = overlays.slice();
|
|
2445
|
+
reordered.splice(currentOverlayIndex, 1);
|
|
2446
|
+
reordered.splice(nextOverlayIndex, 0, object);
|
|
2447
|
+
reordered.forEach((overlay, index) => {
|
|
2448
|
+
var _a, _b;
|
|
2449
|
+
(_b = (_a = this.canvas).moveObjectTo) === null || _b === void 0 ? void 0 : _b.call(_a, overlay, range.start + index);
|
|
2450
|
+
});
|
|
2451
|
+
normalizeLayerOrder(this.canvas);
|
|
2452
|
+
this.canvas.setActiveObject(object);
|
|
2453
|
+
this.canvas.renderAll();
|
|
2454
|
+
this.saveState();
|
|
2455
|
+
this.updateMaskList();
|
|
2456
|
+
this.updateAnnotationList();
|
|
2457
|
+
this.updateUi();
|
|
2458
|
+
const context = this.buildCallbackContext(operation, false);
|
|
2459
|
+
if (isMaskObject(object))
|
|
2460
|
+
this.emitMasksChanged(context);
|
|
2461
|
+
if (isAnnotationObject(object))
|
|
2462
|
+
this.emitAnnotationsChanged(context);
|
|
2463
|
+
this.emitImageChanged(context);
|
|
2464
|
+
}
|
|
1537
2465
|
async mergeMasks() {
|
|
1538
2466
|
if (!this.canvas)
|
|
1539
2467
|
return;
|
|
1540
2468
|
if (!this.canRunIdleOperation('mergeMasks'))
|
|
1541
2469
|
return;
|
|
2470
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
1542
2471
|
const hasMasks = this.canvas.getObjects().some(isMaskObject);
|
|
1543
2472
|
if (!hasMasks)
|
|
1544
2473
|
return;
|
|
@@ -1551,7 +2480,11 @@ export class ImageEditor {
|
|
|
1551
2480
|
await mergeMasksImpl(mergeMasksContext);
|
|
1552
2481
|
this.updateInputs();
|
|
1553
2482
|
this.updateMaskList();
|
|
2483
|
+
this.updateAnnotationList();
|
|
1554
2484
|
this.emitMasksChanged(callbackContext);
|
|
2485
|
+
if (this.getAnnotations().length > 0) {
|
|
2486
|
+
this.emitAnnotationsChanged(callbackContext);
|
|
2487
|
+
}
|
|
1555
2488
|
this.emitImageChanged(callbackContext);
|
|
1556
2489
|
}
|
|
1557
2490
|
finally {
|
|
@@ -1560,17 +2493,18 @@ export class ImageEditor {
|
|
|
1560
2493
|
this.updateUi();
|
|
1561
2494
|
}
|
|
1562
2495
|
}
|
|
1563
|
-
downloadImage(
|
|
2496
|
+
async downloadImage(options) {
|
|
1564
2497
|
if (!this.canvas)
|
|
1565
2498
|
return;
|
|
1566
2499
|
if (!this.canRunIdleOperation('downloadImage'))
|
|
1567
2500
|
return;
|
|
2501
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
1568
2502
|
const callbackContext = this.buildCallbackContext('downloadImage', false);
|
|
1569
2503
|
const operationToken = this.operationGuard.beginBusyOperation('downloadImage');
|
|
1570
2504
|
this.emitBusyChangeIfChanged(callbackContext);
|
|
1571
2505
|
const exportContext = this.buildExportServiceContext();
|
|
1572
2506
|
try {
|
|
1573
|
-
downloadImageImpl(exportContext,
|
|
2507
|
+
await downloadImageImpl(exportContext, options);
|
|
1574
2508
|
}
|
|
1575
2509
|
finally {
|
|
1576
2510
|
this.operationGuard.endBusyOperation(operationToken);
|
|
@@ -1582,6 +2516,7 @@ export class ImageEditor {
|
|
|
1582
2516
|
return '';
|
|
1583
2517
|
if (!this.canRunIdleOperation('exportImageBase64', options))
|
|
1584
2518
|
return '';
|
|
2519
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
1585
2520
|
const callbackContext = this.buildCallbackContext('exportImageBase64', false);
|
|
1586
2521
|
const operationToken = this.operationGuard.beginBusyOperation('exportImageBase64');
|
|
1587
2522
|
this.emitBusyChangeIfChanged(callbackContext);
|
|
@@ -1596,6 +2531,7 @@ export class ImageEditor {
|
|
|
1596
2531
|
}
|
|
1597
2532
|
async exportImageFile(options) {
|
|
1598
2533
|
this.assertIdleForOperation('exportImageFile', options);
|
|
2534
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
1599
2535
|
const callbackContext = this.buildCallbackContext('exportImageFile', false);
|
|
1600
2536
|
const operationToken = this.operationGuard.beginBusyOperation('exportImageFile');
|
|
1601
2537
|
this.emitBusyChangeIfChanged(callbackContext);
|
|
@@ -1611,7 +2547,7 @@ export class ImageEditor {
|
|
|
1611
2547
|
buildExportServiceContext() {
|
|
1612
2548
|
return {
|
|
1613
2549
|
fabric: this.fabricModule,
|
|
1614
|
-
canvas: this.
|
|
2550
|
+
canvas: this.getLiveCanvasOrThrow('export'),
|
|
1615
2551
|
options: this.options,
|
|
1616
2552
|
isImageLoaded: () => this.isImageLoaded(),
|
|
1617
2553
|
getOriginalImage: () => this.originalImage,
|
|
@@ -1627,24 +2563,74 @@ export class ImageEditor {
|
|
|
1627
2563
|
await this.loadImageInternal(base64, this.withInternalOperationOptions(operationToken, providedOptions !== null && providedOptions !== void 0 ? providedOptions : {}));
|
|
1628
2564
|
this.restoreMergedImageDisplayGeometry(geometry);
|
|
1629
2565
|
},
|
|
1630
|
-
|
|
2566
|
+
captureSnapshot: () => this.captureSnapshotInternal(),
|
|
1631
2567
|
loadFromState: (snapshot) => this.loadFromStateInternal(snapshot, this.withInternalOperationOptions(operationToken, this.withAnimationQueueBypass())),
|
|
2568
|
+
exportImageBase64: (options) => exportImageBase64Impl(this.buildExportServiceContext(), options),
|
|
2569
|
+
updateUi: () => this.updateUi(),
|
|
2570
|
+
updateInputs: () => this.updateInputs(),
|
|
1632
2571
|
removeAllMasksNoHistory: () => {
|
|
1633
2572
|
const context = this.buildRemoveMaskContext();
|
|
1634
2573
|
removeAllMasksImpl(context, { saveHistory: false });
|
|
1635
2574
|
},
|
|
2575
|
+
getAnnotations: () => this.getAnnotations(),
|
|
2576
|
+
restoreAnnotations: (objects) => {
|
|
2577
|
+
const canvas = this.getLiveCanvasOrThrow('restoreAnnotations');
|
|
2578
|
+
objects.forEach((annotation) => {
|
|
2579
|
+
canvas.add(annotation);
|
|
2580
|
+
});
|
|
2581
|
+
syncAnnotationRuntimeStates(objects);
|
|
2582
|
+
attachTextEditingHandlersToAnnotations(this.buildTextControllerContext(), objects);
|
|
2583
|
+
this.annotationCounter = Math.max(this.annotationCounter, ...objects.map((annotation) => annotation.annotationId), 0);
|
|
2584
|
+
this.updateAnnotationList();
|
|
2585
|
+
},
|
|
2586
|
+
};
|
|
2587
|
+
}
|
|
2588
|
+
buildMergeAnnotationsContext(operationToken) {
|
|
2589
|
+
return {
|
|
2590
|
+
...this.buildExportServiceContext(),
|
|
2591
|
+
historyManager: this.historyManager,
|
|
2592
|
+
containerElement: this.containerElement,
|
|
2593
|
+
loadImage: async (base64, providedOptions) => {
|
|
2594
|
+
const geometry = this.captureImageDisplayGeometry();
|
|
2595
|
+
await this.loadImageInternal(base64, this.withInternalOperationOptions(operationToken, providedOptions !== null && providedOptions !== void 0 ? providedOptions : {}));
|
|
2596
|
+
this.restoreMergedImageDisplayGeometry(geometry);
|
|
2597
|
+
},
|
|
2598
|
+
captureSnapshot: () => this.captureSnapshotInternal(),
|
|
2599
|
+
loadFromState: (snapshot) => this.loadFromStateInternal(snapshot, this.withInternalOperationOptions(operationToken, this.withAnimationQueueBypass())),
|
|
2600
|
+
exportImageBase64: (options) => exportImageBase64Impl(this.buildExportServiceContext(), options),
|
|
2601
|
+
updateUi: () => this.updateUi(),
|
|
2602
|
+
updateInputs: () => this.updateInputs(),
|
|
2603
|
+
removeAllAnnotationsNoHistory: () => {
|
|
2604
|
+
removeAllAnnotationsImpl(this.buildAnnotationManagerContext(), {
|
|
2605
|
+
saveHistory: false,
|
|
2606
|
+
force: true,
|
|
2607
|
+
});
|
|
2608
|
+
},
|
|
2609
|
+
getMasks: () => this.getMasks(),
|
|
2610
|
+
restoreMasks: (objects) => {
|
|
2611
|
+
const canvas = this.getLiveCanvasOrThrow('restoreMasks');
|
|
2612
|
+
objects.forEach((mask) => {
|
|
2613
|
+
canvas.add(mask);
|
|
2614
|
+
reattachMaskHoverHandlers(mask);
|
|
2615
|
+
});
|
|
2616
|
+
this.lastMask = objects.reduce((lastMask, mask) => !lastMask || mask.maskId > lastMask.maskId ? mask : lastMask, null);
|
|
2617
|
+
this.maskCounter = Math.max(this.maskCounter, ...objects.map((mask) => mask.maskId), 0);
|
|
2618
|
+
this.updateMaskList();
|
|
2619
|
+
},
|
|
1636
2620
|
};
|
|
1637
2621
|
}
|
|
1638
2622
|
captureSnapshotInternal() {
|
|
1639
|
-
var _a;
|
|
2623
|
+
var _a, _b;
|
|
1640
2624
|
if (!this.canvas) {
|
|
1641
2625
|
throw new Error('[ImageEditor] Cannot capture canvas snapshot before init or after dispose.');
|
|
1642
2626
|
}
|
|
1643
2627
|
const activeMask = this.getActiveMaskForSnapshot();
|
|
2628
|
+
const activeAnnotation = this.getActiveAnnotationForSnapshot();
|
|
1644
2629
|
this.hideAllMaskLabels();
|
|
1645
2630
|
return saveStateImpl({
|
|
1646
2631
|
canvas: this.canvas,
|
|
1647
2632
|
activeMaskId: (_a = activeMask === null || activeMask === void 0 ? void 0 : activeMask.maskId) !== null && _a !== void 0 ? _a : null,
|
|
2633
|
+
activeAnnotationId: (_b = activeAnnotation === null || activeAnnotation === void 0 ? void 0 : activeAnnotation.annotationId) !== null && _b !== void 0 ? _b : null,
|
|
1648
2634
|
currentScale: this.currentScale,
|
|
1649
2635
|
currentRotation: this.currentRotation,
|
|
1650
2636
|
baseImageScale: this.baseImageScale,
|
|
@@ -1663,6 +2649,12 @@ export class ImageEditor {
|
|
|
1663
2649
|
.filter((object) => isMaskObject(object) && !!object.labelObject);
|
|
1664
2650
|
return labeledMasks.length === 1 ? ((_a = labeledMasks[0]) !== null && _a !== void 0 ? _a : null) : null;
|
|
1665
2651
|
}
|
|
2652
|
+
getActiveAnnotationForSnapshot() {
|
|
2653
|
+
if (!this.canvas)
|
|
2654
|
+
return null;
|
|
2655
|
+
const activeObject = this.canvas.getActiveObject();
|
|
2656
|
+
return activeObject && isAnnotationObject(activeObject) ? activeObject : null;
|
|
2657
|
+
}
|
|
1666
2658
|
enterMosaicMode() {
|
|
1667
2659
|
if (!this.canvas || !this.originalImage)
|
|
1668
2660
|
return;
|
|
@@ -1746,7 +2738,7 @@ export class ImageEditor {
|
|
|
1746
2738
|
buildMosaicControllerContext() {
|
|
1747
2739
|
return {
|
|
1748
2740
|
fabric: this.fabricModule,
|
|
1749
|
-
canvas: this.
|
|
2741
|
+
canvas: this.getLiveCanvasOrThrow('mosaicController'),
|
|
1750
2742
|
options: this.options,
|
|
1751
2743
|
historyManager: this.historyManager,
|
|
1752
2744
|
getMosaicConfig: () => cloneResolvedMosaicConfig(this.currentMosaicConfig),
|
|
@@ -1787,7 +2779,7 @@ export class ImageEditor {
|
|
|
1787
2779
|
},
|
|
1788
2780
|
};
|
|
1789
2781
|
}
|
|
1790
|
-
enterCropMode() {
|
|
2782
|
+
enterCropMode(options = {}) {
|
|
1791
2783
|
if (!this.canvas || !this.originalImage)
|
|
1792
2784
|
return;
|
|
1793
2785
|
if (this.cropSession)
|
|
@@ -1797,12 +2789,23 @@ export class ImageEditor {
|
|
|
1797
2789
|
if (!this.canRunIdleOperation('enterCropMode'))
|
|
1798
2790
|
return;
|
|
1799
2791
|
const cropControllerContext = this.buildCropControllerContext();
|
|
1800
|
-
enterCropModeImpl(cropControllerContext);
|
|
2792
|
+
enterCropModeImpl(cropControllerContext, options);
|
|
1801
2793
|
this.updateUi();
|
|
1802
2794
|
const callbackContext = this.buildCallbackContext('enterCropMode', false);
|
|
1803
2795
|
this.emitBusyChangeIfChanged(callbackContext);
|
|
1804
2796
|
this.emitImageChanged(callbackContext);
|
|
1805
2797
|
}
|
|
2798
|
+
setCropAspectRatio(aspectRatio) {
|
|
2799
|
+
if (!this.canvas || !this.cropSession)
|
|
2800
|
+
return;
|
|
2801
|
+
if (!this.canRunIdleOperation('setCropAspectRatio'))
|
|
2802
|
+
return;
|
|
2803
|
+
const cropControllerContext = this.buildCropControllerContext();
|
|
2804
|
+
setCropAspectRatioImpl(cropControllerContext, aspectRatio);
|
|
2805
|
+
this.updateUi();
|
|
2806
|
+
const callbackContext = this.buildCallbackContext('setCropAspectRatio', false);
|
|
2807
|
+
this.emitImageChanged(callbackContext);
|
|
2808
|
+
}
|
|
1806
2809
|
cancelCrop() {
|
|
1807
2810
|
if (!this.canvas || !this.cropSession)
|
|
1808
2811
|
return;
|
|
@@ -1846,7 +2849,7 @@ export class ImageEditor {
|
|
|
1846
2849
|
buildCropControllerContext(operationToken) {
|
|
1847
2850
|
return {
|
|
1848
2851
|
fabric: this.fabricModule,
|
|
1849
|
-
canvas: this.
|
|
2852
|
+
canvas: this.getLiveCanvasOrThrow('cropController'),
|
|
1850
2853
|
options: this.options,
|
|
1851
2854
|
historyManager: this.historyManager,
|
|
1852
2855
|
isImageLoaded: () => this.isImageLoaded(),
|
|
@@ -1868,50 +2871,127 @@ export class ImageEditor {
|
|
|
1868
2871
|
},
|
|
1869
2872
|
};
|
|
1870
2873
|
}
|
|
2874
|
+
syncInputValue(inputElement, value) {
|
|
2875
|
+
if (!inputElement)
|
|
2876
|
+
return;
|
|
2877
|
+
const ownerDocument = inputElement.ownerDocument;
|
|
2878
|
+
if (ownerDocument.activeElement === inputElement && !inputElement.readOnly)
|
|
2879
|
+
return;
|
|
2880
|
+
if (inputElement.value !== value)
|
|
2881
|
+
inputElement.value = value;
|
|
2882
|
+
}
|
|
1871
2883
|
updateInputs() {
|
|
1872
2884
|
const scaleId = this.elements.scalePercentageInput;
|
|
1873
2885
|
if (scaleId) {
|
|
1874
2886
|
const scaleInputElement = document.getElementById(scaleId);
|
|
1875
|
-
|
|
1876
|
-
scaleInputElement.value = String(Math.round(this.currentScale * 100));
|
|
1877
|
-
}
|
|
2887
|
+
this.syncInputValue(scaleInputElement, String(Math.round(this.currentScale * 100)));
|
|
1878
2888
|
}
|
|
1879
2889
|
const mosaicConfig = this.getMosaicConfig();
|
|
1880
2890
|
const mosaicBrushSizeInputId = this.elements.mosaicBrushSizeInput;
|
|
1881
2891
|
if (mosaicBrushSizeInputId) {
|
|
1882
2892
|
const brushInput = document.getElementById(mosaicBrushSizeInputId);
|
|
1883
|
-
|
|
1884
|
-
brushInput.value = String(mosaicConfig.brushSize);
|
|
2893
|
+
this.syncInputValue(brushInput, String(mosaicConfig.brushSize));
|
|
1885
2894
|
}
|
|
1886
2895
|
const mosaicBlockSizeInputId = this.elements.mosaicBlockSizeInput;
|
|
1887
2896
|
if (mosaicBlockSizeInputId) {
|
|
1888
2897
|
const blockInput = document.getElementById(mosaicBlockSizeInputId);
|
|
1889
|
-
|
|
1890
|
-
|
|
2898
|
+
this.syncInputValue(blockInput, String(mosaicConfig.blockSize));
|
|
2899
|
+
}
|
|
2900
|
+
const textConfig = this.getTextConfig();
|
|
2901
|
+
const textColorInputId = this.elements.textColorInput;
|
|
2902
|
+
if (textColorInputId) {
|
|
2903
|
+
const textColorInput = document.getElementById(textColorInputId);
|
|
2904
|
+
this.syncInputValue(textColorInput, textConfig.fill);
|
|
2905
|
+
}
|
|
2906
|
+
const textFontSizeInputId = this.elements.textFontSizeInput;
|
|
2907
|
+
if (textFontSizeInputId) {
|
|
2908
|
+
const fontInput = document.getElementById(textFontSizeInputId);
|
|
2909
|
+
this.syncInputValue(fontInput, String(textConfig.fontSize));
|
|
2910
|
+
}
|
|
2911
|
+
const drawConfig = this.getDrawConfig();
|
|
2912
|
+
const drawColorInputId = this.elements.drawColorInput;
|
|
2913
|
+
if (drawColorInputId) {
|
|
2914
|
+
const drawColorInput = document.getElementById(drawColorInputId);
|
|
2915
|
+
this.syncInputValue(drawColorInput, drawConfig.color);
|
|
2916
|
+
}
|
|
2917
|
+
const drawBrushSizeInputId = this.elements.drawBrushSizeInput;
|
|
2918
|
+
if (drawBrushSizeInputId) {
|
|
2919
|
+
const brushInput = document.getElementById(drawBrushSizeInputId);
|
|
2920
|
+
this.syncInputValue(brushInput, String(drawConfig.brushSize));
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
async mergeAnnotations() {
|
|
2924
|
+
if (!this.canvas)
|
|
2925
|
+
return;
|
|
2926
|
+
if (!this.canRunIdleOperation('mergeAnnotations'))
|
|
2927
|
+
return;
|
|
2928
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
2929
|
+
const hasAnnotations = this.canvas.getObjects().some(isAnnotationObject);
|
|
2930
|
+
if (!hasAnnotations)
|
|
2931
|
+
return;
|
|
2932
|
+
const callbackContext = this.buildCallbackContext('mergeAnnotations', false);
|
|
2933
|
+
const operationToken = this.operationGuard.beginBusyOperation('mergeAnnotations');
|
|
2934
|
+
this.emitBusyChangeIfChanged(callbackContext);
|
|
2935
|
+
this.updateUi();
|
|
2936
|
+
try {
|
|
2937
|
+
await mergeAnnotationsImpl(this.buildMergeAnnotationsContext(operationToken));
|
|
2938
|
+
this.updateInputs();
|
|
2939
|
+
this.updateMaskList();
|
|
2940
|
+
this.updateAnnotationList();
|
|
2941
|
+
this.emitAnnotationsChanged(callbackContext);
|
|
2942
|
+
if (this.getMasks().length > 0)
|
|
2943
|
+
this.emitMasksChanged(callbackContext);
|
|
2944
|
+
this.emitImageChanged(callbackContext);
|
|
2945
|
+
}
|
|
2946
|
+
finally {
|
|
2947
|
+
this.operationGuard.endBusyOperation(operationToken);
|
|
2948
|
+
this.emitBusyChangeIfChanged(callbackContext);
|
|
2949
|
+
this.updateUi();
|
|
1891
2950
|
}
|
|
1892
2951
|
}
|
|
1893
2952
|
updateUi() {
|
|
1894
|
-
var _a;
|
|
2953
|
+
var _a, _b, _c;
|
|
1895
2954
|
if (!this.canvas)
|
|
1896
2955
|
return;
|
|
1897
2956
|
const hasImage = !!this.originalImage;
|
|
1898
2957
|
const masks = hasImage ? this.canvas.getObjects().filter(isMaskObject) : [];
|
|
2958
|
+
const annotations = hasImage ? this.canvas.getObjects().filter(isAnnotationObject) : [];
|
|
1899
2959
|
const hasMasks = masks.length > 0;
|
|
2960
|
+
const hasAnnotations = annotations.length > 0;
|
|
1900
2961
|
const activeObject = this.canvas.getActiveObject();
|
|
1901
2962
|
const hasSelectedMask = !!(activeObject && isMaskObject(activeObject));
|
|
1902
|
-
const
|
|
2963
|
+
const hasSelectedAnnotation = !!(activeObject && isAnnotationObject(activeObject));
|
|
2964
|
+
const hasSelectedEditableObject = !!activeObject && isEditableOverlayObject(activeObject);
|
|
2965
|
+
const isDefaultTransform = this.currentScale === 1 &&
|
|
2966
|
+
this.currentRotation === 0 &&
|
|
2967
|
+
!((_a = this.originalImage) === null || _a === void 0 ? void 0 : _a.flipX) &&
|
|
2968
|
+
!((_b = this.originalImage) === null || _b === void 0 ? void 0 : _b.flipY);
|
|
1903
2969
|
const canUndo = this.historyManager.canUndo();
|
|
1904
2970
|
const canRedo = this.historyManager.canRedo();
|
|
1905
2971
|
const isInCropMode = this.cropSession !== null;
|
|
1906
2972
|
const isInMosaicMode = this.mosaicSession !== null;
|
|
2973
|
+
const isInTextMode = this.textSession !== null;
|
|
2974
|
+
const isInDrawMode = this.drawSession !== null;
|
|
1907
2975
|
const isBusy = this.operationGuard.isBusy() || this.animQueue.isBusy();
|
|
1908
|
-
const isMosaicApplying = ((
|
|
2976
|
+
const isMosaicApplying = ((_c = this.mosaicSession) === null || _c === void 0 ? void 0 : _c.isApplying) === true;
|
|
1909
2977
|
if (isInCropMode) {
|
|
1910
2978
|
CROP_MODE_CONTROL_KEYS.forEach((key) => {
|
|
1911
2979
|
this.setControlEnabled(key, !isBusy && CROP_MODE_ENABLED_KEYS.includes(key));
|
|
1912
2980
|
});
|
|
1913
2981
|
return;
|
|
1914
2982
|
}
|
|
2983
|
+
if (isInTextMode) {
|
|
2984
|
+
CROP_MODE_CONTROL_KEYS.forEach((key) => {
|
|
2985
|
+
this.setControlEnabled(key, !isBusy && TEXT_MODE_ENABLED_KEYS.includes(key));
|
|
2986
|
+
});
|
|
2987
|
+
return;
|
|
2988
|
+
}
|
|
2989
|
+
if (isInDrawMode) {
|
|
2990
|
+
CROP_MODE_CONTROL_KEYS.forEach((key) => {
|
|
2991
|
+
this.setControlEnabled(key, !isBusy && DRAW_MODE_ENABLED_KEYS.includes(key));
|
|
2992
|
+
});
|
|
2993
|
+
return;
|
|
2994
|
+
}
|
|
1915
2995
|
if (isInMosaicMode) {
|
|
1916
2996
|
MOSAIC_MODE_CONTROL_KEYS.forEach((key) => {
|
|
1917
2997
|
this.setControlEnabled(key, !isBusy && !isMosaicApplying && MOSAIC_MODE_ENABLED_KEYS.includes(key));
|
|
@@ -1926,19 +3006,38 @@ export class ImageEditor {
|
|
|
1926
3006
|
this.setControlEnabled('zoomOutButton', hasImage && !isBusy && this.currentScale > this.options.minScale);
|
|
1927
3007
|
this.setControlEnabled('rotateLeftButton', hasImage && !isBusy);
|
|
1928
3008
|
this.setControlEnabled('rotateRightButton', hasImage && !isBusy);
|
|
3009
|
+
this.setControlEnabled('flipHorizontalButton', hasImage && !isBusy);
|
|
3010
|
+
this.setControlEnabled('flipVerticalButton', hasImage && !isBusy);
|
|
1929
3011
|
this.setControlEnabled('createMaskButton', hasImage && !isBusy);
|
|
1930
3012
|
this.setControlEnabled('removeSelectedMaskButton', hasSelectedMask && !isBusy);
|
|
1931
3013
|
this.setControlEnabled('removeAllMasksButton', hasMasks && !isBusy);
|
|
1932
3014
|
this.setControlEnabled('mergeMasksButton', hasImage && hasMasks && !isBusy);
|
|
3015
|
+
this.setControlEnabled('removeSelectedAnnotationButton', hasSelectedAnnotation && !isBusy);
|
|
3016
|
+
this.setControlEnabled('removeAllAnnotationsButton', hasAnnotations && !isBusy);
|
|
3017
|
+
this.setControlEnabled('deleteSelectedObjectButton', hasSelectedEditableObject && !isBusy);
|
|
3018
|
+
this.setControlEnabled('mergeAnnotationsButton', hasImage && hasAnnotations && !isBusy);
|
|
3019
|
+
this.setControlEnabled('bringSelectedObjectForwardButton', hasSelectedEditableObject && !isBusy);
|
|
3020
|
+
this.setControlEnabled('sendSelectedObjectBackwardButton', hasSelectedEditableObject && !isBusy);
|
|
3021
|
+
this.setControlEnabled('bringSelectedObjectToFrontButton', hasSelectedEditableObject && !isBusy);
|
|
3022
|
+
this.setControlEnabled('sendSelectedObjectToBackButton', hasSelectedEditableObject && !isBusy);
|
|
1933
3023
|
this.setControlEnabled('downloadImageButton', hasImage && !isBusy);
|
|
1934
3024
|
this.setControlEnabled('resetImageTransformButton', hasImage && !isDefaultTransform && !isBusy);
|
|
1935
3025
|
this.setControlEnabled('undoButton', hasImage && !isBusy && canUndo);
|
|
1936
3026
|
this.setControlEnabled('redoButton', hasImage && !isBusy && canRedo);
|
|
1937
3027
|
this.setControlEnabled('enterCropModeButton', hasImage && !isBusy);
|
|
3028
|
+
this.setControlEnabled('cropAspectRatioSelect', hasImage && !isBusy);
|
|
1938
3029
|
this.setControlEnabled('enterMosaicModeButton', hasImage && !isBusy);
|
|
3030
|
+
this.setControlEnabled('enterTextModeButton', hasImage && !isBusy);
|
|
3031
|
+
this.setControlEnabled('enterDrawModeButton', hasImage && !isBusy);
|
|
1939
3032
|
this.setControlEnabled('exitMosaicModeButton', false);
|
|
3033
|
+
this.setControlEnabled('exitTextModeButton', false);
|
|
3034
|
+
this.setControlEnabled('exitDrawModeButton', false);
|
|
1940
3035
|
this.setControlEnabled('mosaicBrushSizeInput', !this.isDisposed);
|
|
1941
3036
|
this.setControlEnabled('mosaicBlockSizeInput', !this.isDisposed);
|
|
3037
|
+
this.setControlEnabled('textColorInput', !this.isDisposed);
|
|
3038
|
+
this.setControlEnabled('textFontSizeInput', !this.isDisposed);
|
|
3039
|
+
this.setControlEnabled('drawColorInput', !this.isDisposed);
|
|
3040
|
+
this.setControlEnabled('drawBrushSizeInput', !this.isDisposed);
|
|
1942
3041
|
this.setControlEnabled('imageInput', !isBusy);
|
|
1943
3042
|
this.setControlEnabled('applyCropButton', false);
|
|
1944
3043
|
this.setControlEnabled('cancelCropButton', false);
|
|
@@ -1953,7 +3052,10 @@ export class ImageEditor {
|
|
|
1953
3052
|
return;
|
|
1954
3053
|
this.recordElementOriginalState(key, controlElement);
|
|
1955
3054
|
if ('disabled' in controlElement) {
|
|
1956
|
-
|
|
3055
|
+
const formControl = controlElement;
|
|
3056
|
+
const nextDisabled = !isEnabled;
|
|
3057
|
+
if (formControl.disabled !== nextDisabled)
|
|
3058
|
+
formControl.disabled = nextDisabled;
|
|
1957
3059
|
return;
|
|
1958
3060
|
}
|
|
1959
3061
|
if (!isEnabled) {
|
|
@@ -2026,6 +3128,15 @@ export class ImageEditor {
|
|
|
2026
3128
|
this.operationGuard.markDisposed();
|
|
2027
3129
|
this.animQueue.clear();
|
|
2028
3130
|
(_a = this.domBindings) === null || _a === void 0 ? void 0 : _a.removeAll();
|
|
3131
|
+
if (this.keyboardHandler && this.keyboardDocument) {
|
|
3132
|
+
try {
|
|
3133
|
+
this.keyboardDocument.removeEventListener('keydown', this.keyboardHandler);
|
|
3134
|
+
}
|
|
3135
|
+
catch {
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
this.keyboardHandler = null;
|
|
3139
|
+
this.keyboardDocument = null;
|
|
2029
3140
|
this.restoreElementOriginalStates();
|
|
2030
3141
|
if (this.cropSession && this.canvas) {
|
|
2031
3142
|
try {
|
|
@@ -2044,6 +3155,22 @@ export class ImageEditor {
|
|
|
2044
3155
|
}
|
|
2045
3156
|
this.mosaicSession = null;
|
|
2046
3157
|
}
|
|
3158
|
+
if (this.textSession && this.canvas) {
|
|
3159
|
+
try {
|
|
3160
|
+
exitTextModeImpl(this.buildTextControllerContext());
|
|
3161
|
+
}
|
|
3162
|
+
catch {
|
|
3163
|
+
}
|
|
3164
|
+
this.textSession = null;
|
|
3165
|
+
}
|
|
3166
|
+
if (this.drawSession && this.canvas) {
|
|
3167
|
+
try {
|
|
3168
|
+
exitDrawModeImpl(this.buildDrawControllerContext());
|
|
3169
|
+
}
|
|
3170
|
+
catch {
|
|
3171
|
+
}
|
|
3172
|
+
this.drawSession = null;
|
|
3173
|
+
}
|
|
2047
3174
|
if (this.canvas) {
|
|
2048
3175
|
try {
|
|
2049
3176
|
void Promise.resolve(this.canvas.dispose()).catch(() => {
|
|
@@ -2059,6 +3186,7 @@ export class ImageEditor {
|
|
|
2059
3186
|
this.currentImageMimeType = null;
|
|
2060
3187
|
this.lastMask = null;
|
|
2061
3188
|
this.maskCounter = 0;
|
|
3189
|
+
this.annotationCounter = 0;
|
|
2062
3190
|
this.currentScale = 1;
|
|
2063
3191
|
this.currentRotation = 0;
|
|
2064
3192
|
this.baseImageScale = 1;
|