@bensitu/image-editor 2.1.0 → 2.2.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 +235 -81
- package/dist/cjs/index.cjs +2969 -747
- 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 +232 -3
- 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 +104 -24
- package/dist/esm/core/state-serializer.js.map +1 -1
- package/dist/esm/crop/crop-controller.js +2 -0
- 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 +123 -135
- 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 +18 -49
- package/dist/esm/image/image-loader.js.map +1 -1
- package/dist/esm/image/transform-controller.js.map +1 -1
- package/dist/esm/image-editor.js +1063 -60
- 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/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 +222 -24
- package/dist/types/core/public-types.d.ts.map +1 -1
- package/dist/types/core/state-serializer.d.ts +30 -5
- package/dist/types/core/state-serializer.d.ts.map +1 -1
- package/dist/types/crop/crop-controller.d.ts +6 -7
- package/dist/types/crop/crop-controller.d.ts.map +1 -1
- package/dist/types/export/export-format.d.ts +5 -33
- package/dist/types/export/export-format.d.ts.map +1 -1
- package/dist/types/export/export-service.d.ts +24 -15
- 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 +22 -17
- 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 +5 -7
- package/dist/types/image/transform-controller.d.ts.map +1 -1
- package/dist/types/image-editor.d.ts +75 -7
- 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/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';
|
|
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';
|
|
9
15
|
import { applyCrop as applyCropImpl, cancelCrop as cancelCropImpl, enterCropMode as enterCropModeImpl, } 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';
|
|
@@ -33,6 +39,22 @@ const CROP_MODE_CONTROL_KEYS = [
|
|
|
33
39
|
'removeSelectedMaskButton',
|
|
34
40
|
'removeAllMasksButton',
|
|
35
41
|
'mergeMasksButton',
|
|
42
|
+
'mergeAnnotationsButton',
|
|
43
|
+
'enterTextModeButton',
|
|
44
|
+
'exitTextModeButton',
|
|
45
|
+
'textColorInput',
|
|
46
|
+
'textFontSizeInput',
|
|
47
|
+
'enterDrawModeButton',
|
|
48
|
+
'exitDrawModeButton',
|
|
49
|
+
'drawColorInput',
|
|
50
|
+
'drawBrushSizeInput',
|
|
51
|
+
'removeSelectedAnnotationButton',
|
|
52
|
+
'removeAllAnnotationsButton',
|
|
53
|
+
'deleteSelectedObjectButton',
|
|
54
|
+
'bringSelectedObjectForwardButton',
|
|
55
|
+
'sendSelectedObjectBackwardButton',
|
|
56
|
+
'bringSelectedObjectToFrontButton',
|
|
57
|
+
'sendSelectedObjectToBackButton',
|
|
36
58
|
'downloadImageButton',
|
|
37
59
|
'zoomInButton',
|
|
38
60
|
'zoomOutButton',
|
|
@@ -50,6 +72,16 @@ const CROP_MODE_CONTROL_KEYS = [
|
|
|
50
72
|
];
|
|
51
73
|
const CROP_MODE_ENABLED_KEYS = ['applyCropButton', 'cancelCropButton'];
|
|
52
74
|
const CROP_SESSION_ALLOWED_OPERATIONS = new Set(['applyCrop', 'cancelCrop']);
|
|
75
|
+
const TEXT_MODE_ENABLED_KEYS = [
|
|
76
|
+
'exitTextModeButton',
|
|
77
|
+
'textColorInput',
|
|
78
|
+
'textFontSizeInput',
|
|
79
|
+
];
|
|
80
|
+
const DRAW_MODE_ENABLED_KEYS = [
|
|
81
|
+
'exitDrawModeButton',
|
|
82
|
+
'drawColorInput',
|
|
83
|
+
'drawBrushSizeInput',
|
|
84
|
+
];
|
|
53
85
|
const MOSAIC_MODE_CONTROL_KEYS = [
|
|
54
86
|
'scalePercentageInput',
|
|
55
87
|
'rotateLeftDegreesInput',
|
|
@@ -60,6 +92,22 @@ const MOSAIC_MODE_CONTROL_KEYS = [
|
|
|
60
92
|
'removeSelectedMaskButton',
|
|
61
93
|
'removeAllMasksButton',
|
|
62
94
|
'mergeMasksButton',
|
|
95
|
+
'mergeAnnotationsButton',
|
|
96
|
+
'enterTextModeButton',
|
|
97
|
+
'exitTextModeButton',
|
|
98
|
+
'textColorInput',
|
|
99
|
+
'textFontSizeInput',
|
|
100
|
+
'enterDrawModeButton',
|
|
101
|
+
'exitDrawModeButton',
|
|
102
|
+
'drawColorInput',
|
|
103
|
+
'drawBrushSizeInput',
|
|
104
|
+
'removeSelectedAnnotationButton',
|
|
105
|
+
'removeAllAnnotationsButton',
|
|
106
|
+
'deleteSelectedObjectButton',
|
|
107
|
+
'bringSelectedObjectForwardButton',
|
|
108
|
+
'sendSelectedObjectBackwardButton',
|
|
109
|
+
'bringSelectedObjectToFrontButton',
|
|
110
|
+
'sendSelectedObjectToBackButton',
|
|
63
111
|
'downloadImageButton',
|
|
64
112
|
'zoomInButton',
|
|
65
113
|
'zoomOutButton',
|
|
@@ -102,6 +150,29 @@ const IMAGE_EDITOR_OPERATIONS = new Set([
|
|
|
102
150
|
'removeSelectedMask',
|
|
103
151
|
'removeAllMasks',
|
|
104
152
|
'mergeMasks',
|
|
153
|
+
'createTextAnnotation',
|
|
154
|
+
'enterTextMode',
|
|
155
|
+
'exitTextMode',
|
|
156
|
+
'setTextConfig',
|
|
157
|
+
'resetTextConfig',
|
|
158
|
+
'setTextColor',
|
|
159
|
+
'setTextFontSize',
|
|
160
|
+
'enterDrawMode',
|
|
161
|
+
'exitDrawMode',
|
|
162
|
+
'setDrawConfig',
|
|
163
|
+
'resetDrawConfig',
|
|
164
|
+
'setDrawColor',
|
|
165
|
+
'setDrawBrushSize',
|
|
166
|
+
'updateSelectedAnnotation',
|
|
167
|
+
'updateAnnotation',
|
|
168
|
+
'removeSelectedAnnotation',
|
|
169
|
+
'removeAllAnnotations',
|
|
170
|
+
'deleteSelectedObject',
|
|
171
|
+
'mergeAnnotations',
|
|
172
|
+
'bringSelectedObjectForward',
|
|
173
|
+
'sendSelectedObjectBackward',
|
|
174
|
+
'bringSelectedObjectToFront',
|
|
175
|
+
'sendSelectedObjectToBack',
|
|
105
176
|
'enterCropMode',
|
|
106
177
|
'applyCrop',
|
|
107
178
|
'cancelCrop',
|
|
@@ -119,6 +190,27 @@ const IMAGE_EDITOR_OPERATIONS = new Set([
|
|
|
119
190
|
'downloadImage',
|
|
120
191
|
'dispose',
|
|
121
192
|
]);
|
|
193
|
+
const TOOL_MODE_ALLOWED_OPERATIONS = {
|
|
194
|
+
crop: CROP_SESSION_ALLOWED_OPERATIONS,
|
|
195
|
+
mosaic: MOSAIC_SESSION_ALLOWED_OPERATIONS,
|
|
196
|
+
text: new Set([
|
|
197
|
+
'exitTextMode',
|
|
198
|
+
'createTextAnnotation',
|
|
199
|
+
'setTextConfig',
|
|
200
|
+
'resetTextConfig',
|
|
201
|
+
'setTextColor',
|
|
202
|
+
'setTextFontSize',
|
|
203
|
+
'saveState',
|
|
204
|
+
]),
|
|
205
|
+
draw: new Set([
|
|
206
|
+
'exitDrawMode',
|
|
207
|
+
'setDrawConfig',
|
|
208
|
+
'resetDrawConfig',
|
|
209
|
+
'setDrawColor',
|
|
210
|
+
'setDrawBrushSize',
|
|
211
|
+
'saveState',
|
|
212
|
+
]),
|
|
213
|
+
};
|
|
122
214
|
function isImageEditorOperation(value) {
|
|
123
215
|
return value !== null && IMAGE_EDITOR_OPERATIONS.has(value);
|
|
124
216
|
}
|
|
@@ -161,6 +253,30 @@ export class ImageEditor {
|
|
|
161
253
|
writable: true,
|
|
162
254
|
value: void 0
|
|
163
255
|
});
|
|
256
|
+
Object.defineProperty(this, "defaultTextConfig", {
|
|
257
|
+
enumerable: true,
|
|
258
|
+
configurable: true,
|
|
259
|
+
writable: true,
|
|
260
|
+
value: void 0
|
|
261
|
+
});
|
|
262
|
+
Object.defineProperty(this, "currentTextConfig", {
|
|
263
|
+
enumerable: true,
|
|
264
|
+
configurable: true,
|
|
265
|
+
writable: true,
|
|
266
|
+
value: void 0
|
|
267
|
+
});
|
|
268
|
+
Object.defineProperty(this, "defaultDrawConfig", {
|
|
269
|
+
enumerable: true,
|
|
270
|
+
configurable: true,
|
|
271
|
+
writable: true,
|
|
272
|
+
value: void 0
|
|
273
|
+
});
|
|
274
|
+
Object.defineProperty(this, "currentDrawConfig", {
|
|
275
|
+
enumerable: true,
|
|
276
|
+
configurable: true,
|
|
277
|
+
writable: true,
|
|
278
|
+
value: void 0
|
|
279
|
+
});
|
|
164
280
|
Object.defineProperty(this, "canvas", {
|
|
165
281
|
enumerable: true,
|
|
166
282
|
configurable: true,
|
|
@@ -257,6 +373,12 @@ export class ImageEditor {
|
|
|
257
373
|
writable: true,
|
|
258
374
|
value: null
|
|
259
375
|
});
|
|
376
|
+
Object.defineProperty(this, "annotationCounter", {
|
|
377
|
+
enumerable: true,
|
|
378
|
+
configurable: true,
|
|
379
|
+
writable: true,
|
|
380
|
+
value: 0
|
|
381
|
+
});
|
|
260
382
|
Object.defineProperty(this, "lastSnapshot", {
|
|
261
383
|
enumerable: true,
|
|
262
384
|
configurable: true,
|
|
@@ -305,12 +427,36 @@ export class ImageEditor {
|
|
|
305
427
|
writable: true,
|
|
306
428
|
value: null
|
|
307
429
|
});
|
|
430
|
+
Object.defineProperty(this, "textSession", {
|
|
431
|
+
enumerable: true,
|
|
432
|
+
configurable: true,
|
|
433
|
+
writable: true,
|
|
434
|
+
value: null
|
|
435
|
+
});
|
|
436
|
+
Object.defineProperty(this, "drawSession", {
|
|
437
|
+
enumerable: true,
|
|
438
|
+
configurable: true,
|
|
439
|
+
writable: true,
|
|
440
|
+
value: null
|
|
441
|
+
});
|
|
308
442
|
Object.defineProperty(this, "domBindings", {
|
|
309
443
|
enumerable: true,
|
|
310
444
|
configurable: true,
|
|
311
445
|
writable: true,
|
|
312
446
|
value: null
|
|
313
447
|
});
|
|
448
|
+
Object.defineProperty(this, "keyboardDocument", {
|
|
449
|
+
enumerable: true,
|
|
450
|
+
configurable: true,
|
|
451
|
+
writable: true,
|
|
452
|
+
value: null
|
|
453
|
+
});
|
|
454
|
+
Object.defineProperty(this, "keyboardHandler", {
|
|
455
|
+
enumerable: true,
|
|
456
|
+
configurable: true,
|
|
457
|
+
writable: true,
|
|
458
|
+
value: null
|
|
459
|
+
});
|
|
314
460
|
Object.defineProperty(this, "isDisposed", {
|
|
315
461
|
enumerable: true,
|
|
316
462
|
configurable: true,
|
|
@@ -348,6 +494,10 @@ export class ImageEditor {
|
|
|
348
494
|
this.currentLayoutMode = this.options.layoutMode;
|
|
349
495
|
this.defaultMosaicConfig = this.options.defaultMosaicConfig;
|
|
350
496
|
this.currentMosaicConfig = cloneResolvedMosaicConfig(this.defaultMosaicConfig);
|
|
497
|
+
this.defaultTextConfig = this.options.defaultTextConfig;
|
|
498
|
+
this.currentTextConfig = cloneResolvedTextAnnotationConfig(this.defaultTextConfig);
|
|
499
|
+
this.defaultDrawConfig = this.options.defaultDrawConfig;
|
|
500
|
+
this.currentDrawConfig = cloneResolvedDrawConfig(this.defaultDrawConfig);
|
|
351
501
|
const rawDefaultLayoutMode = detected.options
|
|
352
502
|
.defaultLayoutMode;
|
|
353
503
|
if (rawDefaultLayoutMode !== undefined && !isLayoutMode(rawDefaultLayoutMode)) {
|
|
@@ -384,6 +534,23 @@ export class ImageEditor {
|
|
|
384
534
|
removeSelectedMaskButton: 'removeSelectedMaskButton',
|
|
385
535
|
removeAllMasksButton: 'removeAllMasksButton',
|
|
386
536
|
mergeMasksButton: 'mergeMasksButton',
|
|
537
|
+
annotationList: 'annotationList',
|
|
538
|
+
enterTextModeButton: 'enterTextModeButton',
|
|
539
|
+
exitTextModeButton: 'exitTextModeButton',
|
|
540
|
+
textColorInput: 'textColorInput',
|
|
541
|
+
textFontSizeInput: 'textFontSizeInput',
|
|
542
|
+
enterDrawModeButton: 'enterDrawModeButton',
|
|
543
|
+
exitDrawModeButton: 'exitDrawModeButton',
|
|
544
|
+
drawColorInput: 'drawColorInput',
|
|
545
|
+
drawBrushSizeInput: 'drawBrushSizeInput',
|
|
546
|
+
removeSelectedAnnotationButton: 'removeSelectedAnnotationButton',
|
|
547
|
+
removeAllAnnotationsButton: 'removeAllAnnotationsButton',
|
|
548
|
+
deleteSelectedObjectButton: 'deleteSelectedObjectButton',
|
|
549
|
+
mergeAnnotationsButton: 'mergeAnnotationsButton',
|
|
550
|
+
bringSelectedObjectForwardButton: 'bringSelectedObjectForwardButton',
|
|
551
|
+
sendSelectedObjectBackwardButton: 'sendSelectedObjectBackwardButton',
|
|
552
|
+
bringSelectedObjectToFrontButton: 'bringSelectedObjectToFrontButton',
|
|
553
|
+
sendSelectedObjectToBackButton: 'sendSelectedObjectToBackButton',
|
|
387
554
|
downloadImageButton: 'downloadImageButton',
|
|
388
555
|
maskList: 'maskList',
|
|
389
556
|
zoomInButton: 'zoomInButton',
|
|
@@ -402,12 +569,13 @@ export class ImageEditor {
|
|
|
402
569
|
uploadArea: 'uploadArea',
|
|
403
570
|
};
|
|
404
571
|
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
572
|
this.initCanvas();
|
|
573
|
+
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
574
|
this.transformController = new TransformController(this.buildTransformContext());
|
|
408
575
|
this.bindDomEvents();
|
|
409
576
|
this.updateInputs();
|
|
410
577
|
this.updateMaskList();
|
|
578
|
+
this.updateAnnotationList();
|
|
411
579
|
this.updateUi();
|
|
412
580
|
if (this.options.initialImageBase64) {
|
|
413
581
|
void this.loadImage(this.options.initialImageBase64).catch(() => {
|
|
@@ -459,20 +627,24 @@ export class ImageEditor {
|
|
|
459
627
|
});
|
|
460
628
|
this.canvas.on('selection:cleared', () => this.handleSelectionChanged([]));
|
|
461
629
|
const onObjectEvent = (e) => {
|
|
462
|
-
if (e.target
|
|
463
|
-
this.
|
|
630
|
+
if (e.target)
|
|
631
|
+
this.handleObjectMovingScalingRotating(e.target);
|
|
464
632
|
};
|
|
465
633
|
const onObjectModified = (e) => {
|
|
466
|
-
if (
|
|
467
|
-
|
|
468
|
-
this.syncMaskLabel(e.target);
|
|
469
|
-
this.saveState();
|
|
634
|
+
if (e.target)
|
|
635
|
+
this.handleObjectModified(e.target);
|
|
470
636
|
};
|
|
471
637
|
this.canvas.on('object:moving', onObjectEvent);
|
|
472
638
|
this.canvas.on('object:scaling', onObjectEvent);
|
|
473
639
|
this.canvas.on('object:rotating', onObjectEvent);
|
|
474
640
|
this.canvas.on('object:modified', onObjectModified);
|
|
475
641
|
}
|
|
642
|
+
getLiveCanvasOrThrow(operationName) {
|
|
643
|
+
if (this.isDisposed || !this.canvas) {
|
|
644
|
+
throw new Error(`[ImageEditor] Cannot run "${operationName}" after dispose.`);
|
|
645
|
+
}
|
|
646
|
+
return this.canvas;
|
|
647
|
+
}
|
|
476
648
|
bindDomEvents() {
|
|
477
649
|
this.bindElementIfExists('uploadArea', 'click', () => {
|
|
478
650
|
var _a;
|
|
@@ -507,6 +679,42 @@ export class ImageEditor {
|
|
|
507
679
|
this.bindElementIfExists('mergeMasksButton', 'click', () => {
|
|
508
680
|
void this.mergeMasks();
|
|
509
681
|
});
|
|
682
|
+
this.bindElementIfExists('mergeAnnotationsButton', 'click', () => {
|
|
683
|
+
void this.mergeAnnotations();
|
|
684
|
+
});
|
|
685
|
+
this.bindElementIfExists('enterTextModeButton', 'click', () => {
|
|
686
|
+
this.enterTextMode();
|
|
687
|
+
});
|
|
688
|
+
this.bindElementIfExists('exitTextModeButton', 'click', () => {
|
|
689
|
+
this.exitTextMode();
|
|
690
|
+
});
|
|
691
|
+
this.bindElementIfExists('enterDrawModeButton', 'click', () => {
|
|
692
|
+
this.enterDrawMode();
|
|
693
|
+
});
|
|
694
|
+
this.bindElementIfExists('exitDrawModeButton', 'click', () => {
|
|
695
|
+
this.exitDrawMode();
|
|
696
|
+
});
|
|
697
|
+
this.bindElementIfExists('removeSelectedAnnotationButton', 'click', () => {
|
|
698
|
+
this.removeSelectedAnnotation();
|
|
699
|
+
});
|
|
700
|
+
this.bindElementIfExists('removeAllAnnotationsButton', 'click', () => {
|
|
701
|
+
this.removeAllAnnotations();
|
|
702
|
+
});
|
|
703
|
+
this.bindElementIfExists('deleteSelectedObjectButton', 'click', () => {
|
|
704
|
+
this.deleteSelectedObject();
|
|
705
|
+
});
|
|
706
|
+
this.bindElementIfExists('bringSelectedObjectForwardButton', 'click', () => {
|
|
707
|
+
this.bringSelectedObjectForward();
|
|
708
|
+
});
|
|
709
|
+
this.bindElementIfExists('sendSelectedObjectBackwardButton', 'click', () => {
|
|
710
|
+
this.sendSelectedObjectBackward();
|
|
711
|
+
});
|
|
712
|
+
this.bindElementIfExists('bringSelectedObjectToFrontButton', 'click', () => {
|
|
713
|
+
this.bringSelectedObjectToFront();
|
|
714
|
+
});
|
|
715
|
+
this.bindElementIfExists('sendSelectedObjectToBackButton', 'click', () => {
|
|
716
|
+
this.sendSelectedObjectToBack();
|
|
717
|
+
});
|
|
510
718
|
this.bindElementIfExists('downloadImageButton', 'click', () => {
|
|
511
719
|
this.downloadImage();
|
|
512
720
|
});
|
|
@@ -573,11 +781,92 @@ export class ImageEditor {
|
|
|
573
781
|
bindMosaicSizeInput('mosaicBlockSizeInput', (value) => {
|
|
574
782
|
this.setMosaicBlockSize(value);
|
|
575
783
|
});
|
|
784
|
+
const bindStringInput = (key, applyValue) => {
|
|
785
|
+
const handler = (event) => {
|
|
786
|
+
applyValue(event.target.value);
|
|
787
|
+
};
|
|
788
|
+
this.bindElementIfExists(key, 'input', handler);
|
|
789
|
+
this.bindElementIfExists(key, 'change', handler);
|
|
790
|
+
};
|
|
791
|
+
const bindNumberInput = (key, applyValue) => {
|
|
792
|
+
const handler = (event) => {
|
|
793
|
+
applyValue(parseFloat(event.target.value));
|
|
794
|
+
};
|
|
795
|
+
this.bindElementIfExists(key, 'input', handler);
|
|
796
|
+
this.bindElementIfExists(key, 'change', handler);
|
|
797
|
+
};
|
|
798
|
+
bindStringInput('textColorInput', (value) => this.applyTextColorInput(value));
|
|
799
|
+
bindNumberInput('textFontSizeInput', (value) => this.applyTextFontSizeInput(value));
|
|
800
|
+
bindStringInput('drawColorInput', (value) => this.applyDrawColorInput(value));
|
|
801
|
+
bindNumberInput('drawBrushSizeInput', (value) => this.applyDrawBrushSizeInput(value));
|
|
802
|
+
this.bindKeyboardEvents();
|
|
576
803
|
}
|
|
577
804
|
bindElementIfExists(key, event, handler) {
|
|
578
805
|
var _a;
|
|
579
806
|
(_a = this.domBindings) === null || _a === void 0 ? void 0 : _a.bindIfExists(key, event, handler);
|
|
580
807
|
}
|
|
808
|
+
bindKeyboardEvents() {
|
|
809
|
+
var _a, _b;
|
|
810
|
+
const ownerDocument = (_b = (_a = this.canvasElement) === null || _a === void 0 ? void 0 : _a.ownerDocument) !== null && _b !== void 0 ? _b : document;
|
|
811
|
+
if (this.keyboardHandler && this.keyboardDocument) {
|
|
812
|
+
this.keyboardDocument.removeEventListener('keydown', this.keyboardHandler);
|
|
813
|
+
}
|
|
814
|
+
this.keyboardDocument = ownerDocument;
|
|
815
|
+
this.keyboardHandler = (event) => this.handleKeyboardEvent(event);
|
|
816
|
+
ownerDocument.addEventListener('keydown', this.keyboardHandler);
|
|
817
|
+
}
|
|
818
|
+
isNativeTextInputActive() {
|
|
819
|
+
var _a;
|
|
820
|
+
const activeElement = (_a = this.keyboardDocument) === null || _a === void 0 ? void 0 : _a.activeElement;
|
|
821
|
+
if (!activeElement)
|
|
822
|
+
return false;
|
|
823
|
+
const tagName = activeElement.tagName.toLowerCase();
|
|
824
|
+
return (tagName === 'input' ||
|
|
825
|
+
tagName === 'textarea' ||
|
|
826
|
+
tagName === 'select' ||
|
|
827
|
+
activeElement.isContentEditable === true);
|
|
828
|
+
}
|
|
829
|
+
isFabricTextEditingActive() {
|
|
830
|
+
var _a;
|
|
831
|
+
const activeObject = (_a = this.canvas) === null || _a === void 0 ? void 0 : _a.getActiveObject();
|
|
832
|
+
return !!(activeObject &&
|
|
833
|
+
isTextAnnotationObject(activeObject) &&
|
|
834
|
+
activeObject.isEditing === true);
|
|
835
|
+
}
|
|
836
|
+
handleKeyboardEvent(event) {
|
|
837
|
+
if (this.isDisposed)
|
|
838
|
+
return;
|
|
839
|
+
if (event.key === 'Delete' || event.key === 'Backspace') {
|
|
840
|
+
if (this.isNativeTextInputActive() || this.isFabricTextEditingActive())
|
|
841
|
+
return;
|
|
842
|
+
this.deleteSelectedObject();
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
if (event.key !== 'Escape')
|
|
846
|
+
return;
|
|
847
|
+
if (this.isFabricTextEditingActive() && this.canvas) {
|
|
848
|
+
finalizeActiveTextEditing(this.buildTextControllerContext(), { commit: false });
|
|
849
|
+
event.preventDefault();
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
if (this.textSession) {
|
|
853
|
+
this.exitTextMode();
|
|
854
|
+
}
|
|
855
|
+
else if (this.drawSession) {
|
|
856
|
+
this.exitDrawMode();
|
|
857
|
+
}
|
|
858
|
+
else if (this.mosaicSession) {
|
|
859
|
+
this.exitMosaicMode();
|
|
860
|
+
}
|
|
861
|
+
else if (this.cropSession) {
|
|
862
|
+
this.cancelCrop();
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
finalizeActiveTextEditingIfNeeded() {
|
|
866
|
+
if (!this.canvas || !this.isFabricTextEditingActive())
|
|
867
|
+
return;
|
|
868
|
+
finalizeActiveTextEditing(this.buildTextControllerContext(), { commit: true });
|
|
869
|
+
}
|
|
581
870
|
async loadImageFile(file) {
|
|
582
871
|
const inputId = this.elements.imageInput;
|
|
583
872
|
const inputEl = inputId
|
|
@@ -619,9 +908,11 @@ export class ImageEditor {
|
|
|
619
908
|
return;
|
|
620
909
|
if (!this.canRunIdleOperation('loadImage', options))
|
|
621
910
|
return;
|
|
911
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
622
912
|
const callbackContext = this.getOperationContext('loadImage', options);
|
|
623
913
|
const previousImage = this.originalImage;
|
|
624
914
|
const hadMasks = this.getMasks().length > 0;
|
|
915
|
+
const hadAnnotations = this.getAnnotations().length > 0;
|
|
625
916
|
this.emitOptionCallback('onImageLoadStart', [callbackContext]);
|
|
626
917
|
this.operationGuard.beginLoading();
|
|
627
918
|
this.emitBusyChangeIfChanged(callbackContext);
|
|
@@ -650,6 +941,10 @@ export class ImageEditor {
|
|
|
650
941
|
setMaskCounter: (v) => {
|
|
651
942
|
this.maskCounter = v;
|
|
652
943
|
},
|
|
944
|
+
getAnnotationCounter: () => this.annotationCounter,
|
|
945
|
+
setAnnotationCounter: (v) => {
|
|
946
|
+
this.annotationCounter = v;
|
|
947
|
+
},
|
|
653
948
|
getCurrentScale: () => this.currentScale,
|
|
654
949
|
setCurrentScale: (v) => {
|
|
655
950
|
this.currentScale = v;
|
|
@@ -682,6 +977,7 @@ export class ImageEditor {
|
|
|
682
977
|
this.lastMask = null;
|
|
683
978
|
this.updateInputs();
|
|
684
979
|
this.updateMaskList();
|
|
980
|
+
this.updateAnnotationList();
|
|
685
981
|
this.updateUi();
|
|
686
982
|
if (previousImage && previousImage !== this.originalImage) {
|
|
687
983
|
this.emitOptionCallback('onImageCleared', [previousImage, callbackContext]);
|
|
@@ -693,6 +989,9 @@ export class ImageEditor {
|
|
|
693
989
|
if (hadMasks) {
|
|
694
990
|
this.emitMasksChanged(callbackContext);
|
|
695
991
|
}
|
|
992
|
+
if (hadAnnotations) {
|
|
993
|
+
this.emitAnnotationsChanged(callbackContext);
|
|
994
|
+
}
|
|
696
995
|
this.emitImageChanged(callbackContext);
|
|
697
996
|
}
|
|
698
997
|
getInternalOperationToken(options) {
|
|
@@ -717,15 +1016,11 @@ export class ImageEditor {
|
|
|
717
1016
|
assertIdleForOperation(operationName, options) {
|
|
718
1017
|
const token = this.getInternalOperationToken(options);
|
|
719
1018
|
this.operationGuard.assertIdleForOperation(operationName, token);
|
|
720
|
-
|
|
1019
|
+
const activeToolMode = this.getActiveToolMode();
|
|
1020
|
+
if (activeToolMode &&
|
|
721
1021
|
!this.operationGuard.isOwnOperation(token) &&
|
|
722
|
-
!
|
|
723
|
-
throw new Error(`[ImageEditor] Cannot run "${operationName}" while
|
|
724
|
-
}
|
|
725
|
-
if (this.mosaicSession &&
|
|
726
|
-
!this.operationGuard.isOwnOperation(token) &&
|
|
727
|
-
!MOSAIC_SESSION_ALLOWED_OPERATIONS.has(operationName)) {
|
|
728
|
-
throw new Error(`[ImageEditor] Cannot run "${operationName}" while mosaic mode is active.`);
|
|
1022
|
+
!TOOL_MODE_ALLOWED_OPERATIONS[activeToolMode].has(operationName)) {
|
|
1023
|
+
throw new Error(`[ImageEditor] Cannot run "${operationName}" while ${activeToolMode} mode is active.`);
|
|
729
1024
|
}
|
|
730
1025
|
if (this.animQueue.isBusy() && !this.canRunDuringAnimationQueue(options)) {
|
|
731
1026
|
throw new Error(`[ImageEditor] Cannot run "${operationName}" while an animation is queued.`);
|
|
@@ -758,10 +1053,7 @@ export class ImageEditor {
|
|
|
758
1053
|
((_b = this.originalImage.height) !== null && _b !== void 0 ? _b : 0) > 0);
|
|
759
1054
|
}
|
|
760
1055
|
isBusy() {
|
|
761
|
-
return
|
|
762
|
-
this.animQueue.isBusy() ||
|
|
763
|
-
this.cropSession !== null ||
|
|
764
|
-
this.mosaicSession !== null);
|
|
1056
|
+
return this.operationGuard.isBusy() || this.animQueue.isBusy() || this.isToolModeActive();
|
|
765
1057
|
}
|
|
766
1058
|
setLayoutMode(mode) {
|
|
767
1059
|
if (!isLayoutMode(mode)) {
|
|
@@ -836,11 +1128,35 @@ export class ImageEditor {
|
|
|
836
1128
|
return [];
|
|
837
1129
|
return this.canvas.getObjects().filter(isMaskObject).slice();
|
|
838
1130
|
}
|
|
1131
|
+
getAnnotations() {
|
|
1132
|
+
if (!this.canvas)
|
|
1133
|
+
return [];
|
|
1134
|
+
return getAnnotationsImpl(this.canvas);
|
|
1135
|
+
}
|
|
839
1136
|
getMaskCollectionSignature() {
|
|
840
1137
|
return this.getMasks()
|
|
841
1138
|
.map((mask) => `${mask.maskId}:${mask.maskName}`)
|
|
842
1139
|
.join('|');
|
|
843
1140
|
}
|
|
1141
|
+
getAnnotationCollectionSignature() {
|
|
1142
|
+
return this.getAnnotations()
|
|
1143
|
+
.map((annotation) => `${annotation.annotationId}:${annotation.annotationName}`)
|
|
1144
|
+
.join('|');
|
|
1145
|
+
}
|
|
1146
|
+
getActiveToolMode() {
|
|
1147
|
+
if (this.cropSession)
|
|
1148
|
+
return 'crop';
|
|
1149
|
+
if (this.mosaicSession)
|
|
1150
|
+
return 'mosaic';
|
|
1151
|
+
if (this.textSession)
|
|
1152
|
+
return 'text';
|
|
1153
|
+
if (this.drawSession)
|
|
1154
|
+
return 'draw';
|
|
1155
|
+
return null;
|
|
1156
|
+
}
|
|
1157
|
+
isToolModeActive() {
|
|
1158
|
+
return this.getActiveToolMode() !== null;
|
|
1159
|
+
}
|
|
844
1160
|
getEditorState() {
|
|
845
1161
|
const canvasWidth = this.canvas ? this.canvas.getWidth() : 0;
|
|
846
1162
|
const canvasHeight = this.canvas ? this.canvas.getHeight() : 0;
|
|
@@ -849,11 +1165,15 @@ export class ImageEditor {
|
|
|
849
1165
|
hasImage: image !== null,
|
|
850
1166
|
image,
|
|
851
1167
|
maskCount: this.getMasks().length,
|
|
1168
|
+
annotationCount: this.getAnnotations().length,
|
|
852
1169
|
currentScale: this.currentScale,
|
|
853
1170
|
currentRotation: this.currentRotation,
|
|
854
1171
|
isBusy: this.isBusy(),
|
|
1172
|
+
activeToolMode: this.getActiveToolMode(),
|
|
855
1173
|
isCropMode: this.cropSession !== null,
|
|
856
1174
|
isMosaicMode: this.mosaicSession !== null,
|
|
1175
|
+
isTextMode: this.textSession !== null,
|
|
1176
|
+
isDrawMode: this.drawSession !== null,
|
|
857
1177
|
canUndo: this.historyManager.canUndo(),
|
|
858
1178
|
canRedo: this.historyManager.canRedo(),
|
|
859
1179
|
canvasWidth,
|
|
@@ -866,6 +1186,9 @@ export class ImageEditor {
|
|
|
866
1186
|
emitMasksChanged(context) {
|
|
867
1187
|
this.emitOptionCallback('onMasksChanged', [this.getMasks(), context]);
|
|
868
1188
|
}
|
|
1189
|
+
emitAnnotationsChanged(context) {
|
|
1190
|
+
this.emitOptionCallback('onAnnotationsChanged', [this.getAnnotations(), context]);
|
|
1191
|
+
}
|
|
869
1192
|
emitBusyChangeIfChanged(context) {
|
|
870
1193
|
const isBusy = this.isBusy();
|
|
871
1194
|
if (this.lastEmittedIsBusy === isBusy)
|
|
@@ -874,11 +1197,20 @@ export class ImageEditor {
|
|
|
874
1197
|
this.emitOptionCallback('onBusyChange', [isBusy, context]);
|
|
875
1198
|
}
|
|
876
1199
|
buildSelection(selected) {
|
|
877
|
-
var _a;
|
|
1200
|
+
var _a, _b;
|
|
878
1201
|
const selectedMasks = selected.filter(isMaskObject);
|
|
1202
|
+
const selectedAnnotations = selected.filter(isAnnotationObject);
|
|
1203
|
+
const selectedObjectKind = selectedMasks.length === 1 && selectedAnnotations.length === 0
|
|
1204
|
+
? 'mask'
|
|
1205
|
+
: selectedAnnotations.length === 1 && selectedMasks.length === 0
|
|
1206
|
+
? 'annotation'
|
|
1207
|
+
: null;
|
|
879
1208
|
return {
|
|
880
1209
|
selectedMask: (_a = selectedMasks[0]) !== null && _a !== void 0 ? _a : null,
|
|
881
1210
|
selectedMasks,
|
|
1211
|
+
selectedAnnotation: (_b = selectedAnnotations[0]) !== null && _b !== void 0 ? _b : null,
|
|
1212
|
+
selectedAnnotations,
|
|
1213
|
+
selectedObjectKind,
|
|
882
1214
|
};
|
|
883
1215
|
}
|
|
884
1216
|
withSelectionChangeContext(context, callback) {
|
|
@@ -917,7 +1249,7 @@ export class ImageEditor {
|
|
|
917
1249
|
applyCanvasDimensions(this.canvas, widthPx, heightPx, this.containerElement);
|
|
918
1250
|
}
|
|
919
1251
|
alignObjectBoundingBoxToCanvasTopLeft(object) {
|
|
920
|
-
var _a, _b;
|
|
1252
|
+
var _a, _b, _c;
|
|
921
1253
|
object.setCoords();
|
|
922
1254
|
const boundingRect = object.getBoundingRect();
|
|
923
1255
|
object.set({
|
|
@@ -925,7 +1257,7 @@ export class ImageEditor {
|
|
|
925
1257
|
top: ((_b = object.top) !== null && _b !== void 0 ? _b : 0) - boundingRect.top,
|
|
926
1258
|
});
|
|
927
1259
|
object.setCoords();
|
|
928
|
-
this.canvas.renderAll();
|
|
1260
|
+
(_c = this.canvas) === null || _c === void 0 ? void 0 : _c.renderAll();
|
|
929
1261
|
}
|
|
930
1262
|
measureLayoutViewport(scrollbarSize) {
|
|
931
1263
|
return this.viewportCache.measure(this.containerElement, {
|
|
@@ -933,7 +1265,13 @@ export class ImageEditor {
|
|
|
933
1265
|
height: this.options.canvasHeight,
|
|
934
1266
|
}, scrollbarSize);
|
|
935
1267
|
}
|
|
936
|
-
|
|
1268
|
+
getScrollbarStableViewportCanvasSize(viewport) {
|
|
1269
|
+
return {
|
|
1270
|
+
width: Math.max(1, viewport.width - 1),
|
|
1271
|
+
height: Math.max(1, viewport.height - 1),
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
updateCanvasSizeToImageBounds(options = {}) {
|
|
937
1275
|
var _a, _b;
|
|
938
1276
|
if (!this.originalImage)
|
|
939
1277
|
return;
|
|
@@ -941,13 +1279,26 @@ export class ImageEditor {
|
|
|
941
1279
|
const boundingRect = this.originalImage.getBoundingRect();
|
|
942
1280
|
const scrollbarSize = measureScrollbarSize((_b = (_a = this.containerElement) === null || _a === void 0 ? void 0 : _a.ownerDocument) !== null && _b !== void 0 ? _b : null);
|
|
943
1281
|
const viewport = this.measureLayoutViewport(scrollbarSize);
|
|
1282
|
+
const shouldStabilizeContainedViewport = options.stabilizeContainedViewport !== false;
|
|
1283
|
+
const imageFitsViewport = boundingRect.width <= viewport.width + LAYOUT_EPSILON &&
|
|
1284
|
+
boundingRect.height <= viewport.height + LAYOUT_EPSILON;
|
|
944
1285
|
if (this.currentLayoutMode === 'fit' || this.currentLayoutMode === 'cover') {
|
|
1286
|
+
if (imageFitsViewport) {
|
|
1287
|
+
const canvasSize = shouldStabilizeContainedViewport
|
|
1288
|
+
? this.getScrollbarStableViewportCanvasSize(viewport)
|
|
1289
|
+
: viewport;
|
|
1290
|
+
this.setCanvasSizePx(canvasSize.width, canvasSize.height);
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
945
1293
|
const canvasSize = computeScrollableCanvasSize(boundingRect.width, boundingRect.height, viewport, scrollbarSize);
|
|
946
1294
|
this.setCanvasSizePx(canvasSize.width, canvasSize.height);
|
|
947
1295
|
return;
|
|
948
1296
|
}
|
|
949
|
-
if (
|
|
950
|
-
|
|
1297
|
+
if (imageFitsViewport) {
|
|
1298
|
+
const canvasSize = shouldStabilizeContainedViewport
|
|
1299
|
+
? this.getScrollbarStableViewportCanvasSize(viewport)
|
|
1300
|
+
: viewport;
|
|
1301
|
+
this.setCanvasSizePx(canvasSize.width, canvasSize.height);
|
|
951
1302
|
return;
|
|
952
1303
|
}
|
|
953
1304
|
this.setCanvasSizePx(Math.max(viewport.width, Math.ceil(boundingRect.width)), Math.max(viewport.height, Math.ceil(boundingRect.height)));
|
|
@@ -1047,7 +1398,7 @@ export class ImageEditor {
|
|
|
1047
1398
|
}
|
|
1048
1399
|
buildTransformContext() {
|
|
1049
1400
|
return {
|
|
1050
|
-
canvas: this.
|
|
1401
|
+
canvas: this.getLiveCanvasOrThrow('buildTransformContext'),
|
|
1051
1402
|
options: this.options,
|
|
1052
1403
|
guard: this.operationGuard,
|
|
1053
1404
|
getOriginalImage: () => this.originalImage,
|
|
@@ -1199,6 +1550,7 @@ export class ImageEditor {
|
|
|
1199
1550
|
const context = this.buildCallbackContext(activeRestoreOperation !== null && activeRestoreOperation !== void 0 ? activeRestoreOperation : 'loadFromState', activeRestoreOperation === 'undo' || activeRestoreOperation === 'redo');
|
|
1200
1551
|
const previousImage = this.originalImage;
|
|
1201
1552
|
const previousMaskSignature = this.getMaskCollectionSignature();
|
|
1553
|
+
const previousAnnotationSignature = this.getAnnotationCollectionSignature();
|
|
1202
1554
|
try {
|
|
1203
1555
|
const restoredState = await loadFromStateImpl({
|
|
1204
1556
|
canvas: this.canvas,
|
|
@@ -1221,6 +1573,7 @@ export class ImageEditor {
|
|
|
1221
1573
|
this.canvas.sendObjectToBack(this.originalImage);
|
|
1222
1574
|
}
|
|
1223
1575
|
this.maskCounter = restoredState.maxMaskId;
|
|
1576
|
+
this.annotationCounter = restoredState.maxAnnotationId;
|
|
1224
1577
|
const editorState = restoredState.editorState;
|
|
1225
1578
|
if (editorState) {
|
|
1226
1579
|
this.currentScale = editorState.currentScale;
|
|
@@ -1238,22 +1591,25 @@ export class ImageEditor {
|
|
|
1238
1591
|
}
|
|
1239
1592
|
this.isImageLoadedToCanvas = !!this.originalImage;
|
|
1240
1593
|
if (this.originalImage && this.shouldNormalizeCanvasSizeAfterStateRestore()) {
|
|
1241
|
-
this.updateCanvasSizeToImageBounds();
|
|
1594
|
+
this.updateCanvasSizeToImageBounds({ stabilizeContainedViewport: false });
|
|
1242
1595
|
this.alignObjectBoundingBoxToCanvasTopLeft(this.originalImage);
|
|
1243
1596
|
}
|
|
1244
1597
|
if (this.originalImage) {
|
|
1245
1598
|
this.settleFitCoverScrollbarsAfterStateRestore();
|
|
1246
1599
|
}
|
|
1247
|
-
const restoredMasks = restoredState.
|
|
1600
|
+
const restoredMasks = restoredState.masks;
|
|
1248
1601
|
this.lastMask = restoredMasks.reduce((lastMask, maskObject) => !lastMask || maskObject.maskId > lastMask.maskId ? maskObject : lastMask, null);
|
|
1249
1602
|
restoredMasks.forEach((maskObject) => {
|
|
1250
1603
|
applyMaskUnselectedStyle(maskObject);
|
|
1251
1604
|
reattachMaskHoverHandlers(maskObject);
|
|
1252
1605
|
});
|
|
1606
|
+
syncAnnotationRuntimeStates(restoredState.annotations);
|
|
1607
|
+
attachTextEditingHandlersToAnnotations(this.buildTextControllerContext(), restoredState.annotations);
|
|
1253
1608
|
this.lastSnapshot = this.captureSnapshotInternal();
|
|
1254
1609
|
this.canvas.renderAll();
|
|
1255
1610
|
this.updateInputs();
|
|
1256
1611
|
this.updateMaskList();
|
|
1612
|
+
this.updateAnnotationList();
|
|
1257
1613
|
this.updateUi();
|
|
1258
1614
|
if (previousImage && previousImage !== this.originalImage) {
|
|
1259
1615
|
this.emitOptionCallback('onImageCleared', [previousImage, context]);
|
|
@@ -1261,17 +1617,32 @@ export class ImageEditor {
|
|
|
1261
1617
|
if (previousMaskSignature !== this.getMaskCollectionSignature()) {
|
|
1262
1618
|
this.emitMasksChanged(context);
|
|
1263
1619
|
}
|
|
1620
|
+
if (previousAnnotationSignature !== this.getAnnotationCollectionSignature()) {
|
|
1621
|
+
this.emitAnnotationsChanged(context);
|
|
1622
|
+
}
|
|
1264
1623
|
this.emitImageChanged(context);
|
|
1624
|
+
const canvas = this.getLiveCanvasOrThrow('loadFromState');
|
|
1265
1625
|
const activeMaskId = editorState === null || editorState === void 0 ? void 0 : editorState.activeMaskId;
|
|
1266
|
-
|
|
1626
|
+
const activeAnnotationId = editorState === null || editorState === void 0 ? void 0 : editorState.activeAnnotationId;
|
|
1627
|
+
if ((editorState === null || editorState === void 0 ? void 0 : editorState.activeObjectKind) === 'mask' && typeof activeMaskId === 'number') {
|
|
1267
1628
|
const activeMask = restoredMasks.find((maskObject) => maskObject.maskId === activeMaskId);
|
|
1268
1629
|
if (activeMask) {
|
|
1269
1630
|
this.withSelectionChangeContext(context, () => {
|
|
1270
|
-
|
|
1631
|
+
canvas.setActiveObject(activeMask);
|
|
1271
1632
|
this.handleSelectionChanged([activeMask]);
|
|
1272
1633
|
});
|
|
1273
1634
|
}
|
|
1274
1635
|
}
|
|
1636
|
+
else if ((editorState === null || editorState === void 0 ? void 0 : editorState.activeObjectKind) === 'annotation' &&
|
|
1637
|
+
typeof activeAnnotationId === 'number') {
|
|
1638
|
+
const activeAnnotation = restoredState.annotations.find((annotation) => annotation.annotationId === activeAnnotationId);
|
|
1639
|
+
if (activeAnnotation) {
|
|
1640
|
+
this.withSelectionChangeContext(context, () => {
|
|
1641
|
+
canvas.setActiveObject(activeAnnotation);
|
|
1642
|
+
this.handleSelectionChanged([activeAnnotation]);
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1275
1646
|
}
|
|
1276
1647
|
catch (error) {
|
|
1277
1648
|
reportError(this.options, error, 'Failed to restore canvas state.');
|
|
@@ -1282,24 +1653,26 @@ export class ImageEditor {
|
|
|
1282
1653
|
this.saveStateInternal();
|
|
1283
1654
|
}
|
|
1284
1655
|
saveStateInternal(options) {
|
|
1285
|
-
var _a, _b;
|
|
1656
|
+
var _a, _b, _c;
|
|
1286
1657
|
if (!this.canvas || this.shouldSuppressSaveState)
|
|
1287
1658
|
return;
|
|
1288
1659
|
if (!this.canRunIdleOperation('saveState', options))
|
|
1289
1660
|
return;
|
|
1290
1661
|
const activeObj = this.canvas.getActiveObject();
|
|
1291
1662
|
const activeMask = this.getActiveMaskForSnapshot();
|
|
1663
|
+
const activeAnnotation = this.getActiveAnnotationForSnapshot();
|
|
1292
1664
|
this.hideAllMaskLabels();
|
|
1293
1665
|
try {
|
|
1294
1666
|
const after = saveStateImpl({
|
|
1295
1667
|
canvas: this.canvas,
|
|
1296
1668
|
activeMaskId: (_a = activeMask === null || activeMask === void 0 ? void 0 : activeMask.maskId) !== null && _a !== void 0 ? _a : null,
|
|
1669
|
+
activeAnnotationId: (_b = activeAnnotation === null || activeAnnotation === void 0 ? void 0 : activeAnnotation.annotationId) !== null && _b !== void 0 ? _b : null,
|
|
1297
1670
|
currentScale: this.currentScale,
|
|
1298
1671
|
currentRotation: this.currentRotation,
|
|
1299
1672
|
baseImageScale: this.baseImageScale,
|
|
1300
1673
|
currentImageMimeType: this.currentImageMimeType,
|
|
1301
1674
|
});
|
|
1302
|
-
const before = (
|
|
1675
|
+
const before = (_c = this.lastSnapshot) !== null && _c !== void 0 ? _c : after;
|
|
1303
1676
|
if (after === before) {
|
|
1304
1677
|
return;
|
|
1305
1678
|
}
|
|
@@ -1315,25 +1688,32 @@ export class ImageEditor {
|
|
|
1315
1688
|
reportWarning(this.options, error, 'Failed to capture canvas snapshot.');
|
|
1316
1689
|
}
|
|
1317
1690
|
finally {
|
|
1318
|
-
this.
|
|
1691
|
+
this.restoreActiveObjectAfterSnapshot(activeObj, activeMask, activeAnnotation);
|
|
1319
1692
|
this.updateUi();
|
|
1320
1693
|
}
|
|
1321
1694
|
}
|
|
1322
|
-
|
|
1695
|
+
restoreActiveObjectAfterSnapshot(activeObj, activeMask, activeAnnotation) {
|
|
1323
1696
|
if (!this.canvas)
|
|
1324
1697
|
return;
|
|
1325
1698
|
const maskToRestore = activeObj && isMaskObject(activeObj) ? activeObj : activeMask;
|
|
1326
|
-
|
|
1699
|
+
const annotationToRestore = activeObj && isAnnotationObject(activeObj) ? activeObj : activeAnnotation;
|
|
1700
|
+
if (maskToRestore && this.canvas.getObjects().includes(maskToRestore)) {
|
|
1701
|
+
this.canvas.setActiveObject(maskToRestore);
|
|
1702
|
+
this.showLabelForMask(maskToRestore);
|
|
1703
|
+
this.updateMaskListSelection(maskToRestore);
|
|
1327
1704
|
return;
|
|
1328
|
-
|
|
1329
|
-
this.
|
|
1330
|
-
|
|
1705
|
+
}
|
|
1706
|
+
if (annotationToRestore && this.canvas.getObjects().includes(annotationToRestore)) {
|
|
1707
|
+
this.canvas.setActiveObject(annotationToRestore);
|
|
1708
|
+
this.updateAnnotationListSelection(annotationToRestore);
|
|
1709
|
+
}
|
|
1331
1710
|
}
|
|
1332
1711
|
undo() {
|
|
1333
1712
|
if (this.isDisposed)
|
|
1334
1713
|
return Promise.resolve();
|
|
1335
1714
|
if (!this.canRunIdleOperation('undo'))
|
|
1336
1715
|
return Promise.resolve();
|
|
1716
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
1337
1717
|
const context = this.buildCallbackContext('undo', true);
|
|
1338
1718
|
const job = this.animQueue.add(async () => {
|
|
1339
1719
|
if (this.isDisposed)
|
|
@@ -1357,6 +1737,7 @@ export class ImageEditor {
|
|
|
1357
1737
|
return Promise.resolve();
|
|
1358
1738
|
if (!this.canRunIdleOperation('redo'))
|
|
1359
1739
|
return Promise.resolve();
|
|
1740
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
1360
1741
|
const context = this.buildCallbackContext('redo', true);
|
|
1361
1742
|
const job = this.animQueue.add(async () => {
|
|
1362
1743
|
if (this.isDisposed)
|
|
@@ -1422,7 +1803,7 @@ export class ImageEditor {
|
|
|
1422
1803
|
buildCreateMaskContext() {
|
|
1423
1804
|
return {
|
|
1424
1805
|
fabric: this.fabricModule,
|
|
1425
|
-
canvas: this.
|
|
1806
|
+
canvas: this.getLiveCanvasOrThrow('createMask'),
|
|
1426
1807
|
options: this.getRuntimeOptions(),
|
|
1427
1808
|
getLastMask: () => this.lastMask,
|
|
1428
1809
|
setLastMask: (maskObject) => {
|
|
@@ -1445,7 +1826,7 @@ export class ImageEditor {
|
|
|
1445
1826
|
}
|
|
1446
1827
|
buildRemoveMaskContext() {
|
|
1447
1828
|
return {
|
|
1448
|
-
canvas: this.
|
|
1829
|
+
canvas: this.getLiveCanvasOrThrow('removeMask'),
|
|
1449
1830
|
removeLabelForMask: (mask) => {
|
|
1450
1831
|
this.removeLabelForMask(mask);
|
|
1451
1832
|
},
|
|
@@ -1495,11 +1876,35 @@ export class ImageEditor {
|
|
|
1495
1876
|
return;
|
|
1496
1877
|
showLabelForMask(context, mask);
|
|
1497
1878
|
}
|
|
1879
|
+
handleObjectMovingScalingRotating(target) {
|
|
1880
|
+
if (isMaskObject(target)) {
|
|
1881
|
+
this.syncMaskLabel(target);
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
handleObjectModified(target) {
|
|
1885
|
+
if (isMaskObject(target)) {
|
|
1886
|
+
this.syncMaskLabel(target);
|
|
1887
|
+
const context = this.buildCallbackContext('saveState', false);
|
|
1888
|
+
this.saveState();
|
|
1889
|
+
this.emitMasksChanged(context);
|
|
1890
|
+
this.emitImageChanged(context);
|
|
1891
|
+
return;
|
|
1892
|
+
}
|
|
1893
|
+
if (isAnnotationObject(target)) {
|
|
1894
|
+
if (isAnnotationLocked(target))
|
|
1895
|
+
return;
|
|
1896
|
+
const context = this.buildCallbackContext('updateAnnotation', false);
|
|
1897
|
+
this.saveState();
|
|
1898
|
+
this.emitAnnotationsChanged(context);
|
|
1899
|
+
this.emitImageChanged(context);
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1498
1902
|
handleSelectionChanged(selected) {
|
|
1499
|
-
var _a, _b, _c;
|
|
1903
|
+
var _a, _b, _c, _d;
|
|
1500
1904
|
if (!this.canvas)
|
|
1501
1905
|
return;
|
|
1502
1906
|
const selectedMask = (_a = selected.find(isMaskObject)) !== null && _a !== void 0 ? _a : null;
|
|
1907
|
+
const selectedAnnotation = (_b = selected.find(isAnnotationObject)) !== null && _b !== void 0 ? _b : null;
|
|
1503
1908
|
const masks = this.canvas.getObjects().filter(isMaskObject);
|
|
1504
1909
|
masks.forEach((maskObject) => {
|
|
1505
1910
|
if (maskObject !== selectedMask) {
|
|
@@ -1515,9 +1920,10 @@ export class ImageEditor {
|
|
|
1515
1920
|
if (selectedMask)
|
|
1516
1921
|
this.showLabelForMask(selectedMask);
|
|
1517
1922
|
this.updateMaskListSelection(selectedMask);
|
|
1923
|
+
this.updateAnnotationListSelection(selectedAnnotation);
|
|
1518
1924
|
this.canvas.requestRenderAll();
|
|
1519
1925
|
this.updateUi();
|
|
1520
|
-
const context = (
|
|
1926
|
+
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
1927
|
this.activeStateRestoreOperation === 'redo');
|
|
1522
1928
|
this.emitOptionCallback('onSelectionChange', [this.buildSelection(selected), context]);
|
|
1523
1929
|
}
|
|
@@ -1534,11 +1940,426 @@ export class ImageEditor {
|
|
|
1534
1940
|
updateMaskListSelection(selectedMask) {
|
|
1535
1941
|
updateMaskListSelection(this.buildMaskListContext(), selectedMask);
|
|
1536
1942
|
}
|
|
1943
|
+
enterTextMode() {
|
|
1944
|
+
if (!this.canvas)
|
|
1945
|
+
return;
|
|
1946
|
+
if (!this.canRunIdleOperation('enterTextMode'))
|
|
1947
|
+
return;
|
|
1948
|
+
if (this.isToolModeActive())
|
|
1949
|
+
return;
|
|
1950
|
+
enterTextModeImpl(this.buildTextControllerContext());
|
|
1951
|
+
const callbackContext = this.buildCallbackContext('enterTextMode', false);
|
|
1952
|
+
this.emitBusyChangeIfChanged(callbackContext);
|
|
1953
|
+
this.emitImageChanged(callbackContext);
|
|
1954
|
+
}
|
|
1955
|
+
exitTextMode() {
|
|
1956
|
+
if (!this.canvas || !this.textSession)
|
|
1957
|
+
return;
|
|
1958
|
+
if (!this.canRunIdleOperation('exitTextMode'))
|
|
1959
|
+
return;
|
|
1960
|
+
exitTextModeImpl(this.buildTextControllerContext());
|
|
1961
|
+
const callbackContext = this.buildCallbackContext('exitTextMode', false);
|
|
1962
|
+
this.emitBusyChangeIfChanged(callbackContext);
|
|
1963
|
+
this.emitImageChanged(callbackContext);
|
|
1964
|
+
}
|
|
1965
|
+
isTextMode() {
|
|
1966
|
+
return this.textSession !== null;
|
|
1967
|
+
}
|
|
1968
|
+
createTextAnnotation(config = {}) {
|
|
1969
|
+
if (!this.canvas)
|
|
1970
|
+
return null;
|
|
1971
|
+
if (!this.canRunIdleOperation('createTextAnnotation'))
|
|
1972
|
+
return null;
|
|
1973
|
+
return createTextAnnotationImpl(this.buildTextControllerContext(), config);
|
|
1974
|
+
}
|
|
1975
|
+
enterDrawMode() {
|
|
1976
|
+
if (!this.canvas)
|
|
1977
|
+
return;
|
|
1978
|
+
if (!this.canRunIdleOperation('enterDrawMode'))
|
|
1979
|
+
return;
|
|
1980
|
+
if (this.isToolModeActive())
|
|
1981
|
+
return;
|
|
1982
|
+
enterDrawModeImpl(this.buildDrawControllerContext());
|
|
1983
|
+
const callbackContext = this.buildCallbackContext('enterDrawMode', false);
|
|
1984
|
+
this.emitBusyChangeIfChanged(callbackContext);
|
|
1985
|
+
this.emitImageChanged(callbackContext);
|
|
1986
|
+
}
|
|
1987
|
+
exitDrawMode() {
|
|
1988
|
+
if (!this.canvas || !this.drawSession)
|
|
1989
|
+
return;
|
|
1990
|
+
if (!this.canRunIdleOperation('exitDrawMode'))
|
|
1991
|
+
return;
|
|
1992
|
+
exitDrawModeImpl(this.buildDrawControllerContext());
|
|
1993
|
+
const callbackContext = this.buildCallbackContext('exitDrawMode', false);
|
|
1994
|
+
this.emitBusyChangeIfChanged(callbackContext);
|
|
1995
|
+
this.emitImageChanged(callbackContext);
|
|
1996
|
+
}
|
|
1997
|
+
isDrawMode() {
|
|
1998
|
+
return this.drawSession !== null;
|
|
1999
|
+
}
|
|
2000
|
+
getTextConfig() {
|
|
2001
|
+
return cloneResolvedTextAnnotationConfig(this.currentTextConfig);
|
|
2002
|
+
}
|
|
2003
|
+
setTextConfig(config) {
|
|
2004
|
+
this.applyTextConfigPatch(config, 'setTextConfig');
|
|
2005
|
+
}
|
|
2006
|
+
resetTextConfig() {
|
|
2007
|
+
this.applyTextConfigPatch(this.defaultTextConfig, 'resetTextConfig');
|
|
2008
|
+
}
|
|
2009
|
+
setTextColor(color) {
|
|
2010
|
+
this.applyTextConfigPatch({ fill: color }, 'setTextColor');
|
|
2011
|
+
}
|
|
2012
|
+
setTextFontSize(size) {
|
|
2013
|
+
this.applyTextConfigPatch({ fontSize: size }, 'setTextFontSize');
|
|
2014
|
+
}
|
|
2015
|
+
getDrawConfig() {
|
|
2016
|
+
return cloneResolvedDrawConfig(this.currentDrawConfig);
|
|
2017
|
+
}
|
|
2018
|
+
setDrawConfig(config) {
|
|
2019
|
+
this.applyDrawConfigPatch(config, 'setDrawConfig');
|
|
2020
|
+
}
|
|
2021
|
+
resetDrawConfig() {
|
|
2022
|
+
this.applyDrawConfigPatch(this.defaultDrawConfig, 'resetDrawConfig');
|
|
2023
|
+
}
|
|
2024
|
+
setDrawColor(color) {
|
|
2025
|
+
this.applyDrawConfigPatch({ color }, 'setDrawColor');
|
|
2026
|
+
}
|
|
2027
|
+
setDrawBrushSize(size) {
|
|
2028
|
+
this.applyDrawConfigPatch({ brushSize: size }, 'setDrawBrushSize');
|
|
2029
|
+
}
|
|
2030
|
+
removeSelectedAnnotation() {
|
|
2031
|
+
if (!this.canvas)
|
|
2032
|
+
return;
|
|
2033
|
+
if (!this.canRunIdleOperation('removeSelectedAnnotation'))
|
|
2034
|
+
return;
|
|
2035
|
+
const before = this.getAnnotations().length;
|
|
2036
|
+
const callbackContext = this.buildCallbackContext('removeSelectedAnnotation', false);
|
|
2037
|
+
this.withSelectionChangeContext(callbackContext, () => {
|
|
2038
|
+
removeSelectedAnnotationImpl(this.buildAnnotationManagerContext());
|
|
2039
|
+
});
|
|
2040
|
+
this.updateAnnotationList();
|
|
2041
|
+
this.updateUi();
|
|
2042
|
+
if (this.getAnnotations().length !== before) {
|
|
2043
|
+
this.emitAnnotationsChanged(callbackContext);
|
|
2044
|
+
this.emitImageChanged(callbackContext);
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
removeAllAnnotations(options = {}) {
|
|
2048
|
+
if (!this.canvas)
|
|
2049
|
+
return;
|
|
2050
|
+
if (!this.canRunIdleOperation('removeAllAnnotations', options))
|
|
2051
|
+
return;
|
|
2052
|
+
const before = this.getAnnotations().length;
|
|
2053
|
+
const callbackContext = this.buildCallbackContext('removeAllAnnotations', false);
|
|
2054
|
+
this.withSelectionChangeContext(callbackContext, () => {
|
|
2055
|
+
removeAllAnnotationsImpl(this.buildAnnotationManagerContext(), options);
|
|
2056
|
+
});
|
|
2057
|
+
this.updateAnnotationList();
|
|
2058
|
+
this.updateUi();
|
|
2059
|
+
if (this.getAnnotations().length !== before) {
|
|
2060
|
+
this.emitAnnotationsChanged(callbackContext);
|
|
2061
|
+
this.emitImageChanged(callbackContext);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
updateAnnotation(annotationId, config) {
|
|
2065
|
+
if (!this.canvas)
|
|
2066
|
+
return;
|
|
2067
|
+
if (!this.canRunIdleOperation('updateAnnotation'))
|
|
2068
|
+
return;
|
|
2069
|
+
const callbackContext = this.buildCallbackContext('updateAnnotation', false);
|
|
2070
|
+
const changed = updateAnnotationImpl(this.buildAnnotationManagerContext(), annotationId, config);
|
|
2071
|
+
if (changed) {
|
|
2072
|
+
this.updateAnnotationList();
|
|
2073
|
+
this.emitAnnotationsChanged(callbackContext);
|
|
2074
|
+
this.emitImageChanged(callbackContext);
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
updateSelectedAnnotation(config) {
|
|
2078
|
+
if (!this.canvas)
|
|
2079
|
+
return;
|
|
2080
|
+
if (!this.canRunIdleOperation('updateSelectedAnnotation'))
|
|
2081
|
+
return;
|
|
2082
|
+
const callbackContext = this.buildCallbackContext('updateSelectedAnnotation', false);
|
|
2083
|
+
const changed = updateSelectedAnnotationImpl(this.buildAnnotationManagerContext(), config);
|
|
2084
|
+
if (changed) {
|
|
2085
|
+
this.updateAnnotationList();
|
|
2086
|
+
this.emitAnnotationsChanged(callbackContext);
|
|
2087
|
+
this.emitImageChanged(callbackContext);
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
deleteSelectedObject() {
|
|
2091
|
+
if (!this.canvas)
|
|
2092
|
+
return;
|
|
2093
|
+
if (!this.canRunIdleOperation('deleteSelectedObject'))
|
|
2094
|
+
return;
|
|
2095
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
2096
|
+
const selectedObjects = this.getSelectedCanvasObjects();
|
|
2097
|
+
const selectedMasks = selectedObjects.filter(isMaskObject);
|
|
2098
|
+
const selectedAnnotations = selectedObjects.filter((object) => isAnnotationObject(object) && isAnnotationUnlocked(object));
|
|
2099
|
+
if (selectedMasks.length === 0 && selectedAnnotations.length === 0)
|
|
2100
|
+
return;
|
|
2101
|
+
const canvas = this.getLiveCanvasOrThrow('deleteSelectedObject');
|
|
2102
|
+
const callbackContext = this.buildCallbackContext('deleteSelectedObject', false);
|
|
2103
|
+
this.withSelectionChangeContext(callbackContext, () => {
|
|
2104
|
+
for (const mask of selectedMasks) {
|
|
2105
|
+
this.removeLabelForMask(mask);
|
|
2106
|
+
canvas.remove(mask);
|
|
2107
|
+
}
|
|
2108
|
+
removeAnnotationObjects(this.buildAnnotationManagerContext(), selectedAnnotations, {
|
|
2109
|
+
saveHistory: false,
|
|
2110
|
+
force: true,
|
|
2111
|
+
});
|
|
2112
|
+
canvas.discardActiveObject();
|
|
2113
|
+
canvas.renderAll();
|
|
2114
|
+
this.saveState();
|
|
2115
|
+
});
|
|
2116
|
+
this.updateMaskList();
|
|
2117
|
+
this.updateAnnotationList();
|
|
2118
|
+
this.updateUi();
|
|
2119
|
+
if (selectedMasks.length > 0)
|
|
2120
|
+
this.emitMasksChanged(callbackContext);
|
|
2121
|
+
if (selectedAnnotations.length > 0)
|
|
2122
|
+
this.emitAnnotationsChanged(callbackContext);
|
|
2123
|
+
this.emitImageChanged(callbackContext);
|
|
2124
|
+
}
|
|
2125
|
+
bringSelectedObjectForward() {
|
|
2126
|
+
this.moveSelectedEditableObject('bringSelectedObjectForward');
|
|
2127
|
+
}
|
|
2128
|
+
sendSelectedObjectBackward() {
|
|
2129
|
+
this.moveSelectedEditableObject('sendSelectedObjectBackward');
|
|
2130
|
+
}
|
|
2131
|
+
bringSelectedObjectToFront() {
|
|
2132
|
+
this.moveSelectedEditableObject('bringSelectedObjectToFront');
|
|
2133
|
+
}
|
|
2134
|
+
sendSelectedObjectToBack() {
|
|
2135
|
+
this.moveSelectedEditableObject('sendSelectedObjectToBack');
|
|
2136
|
+
}
|
|
2137
|
+
buildAnnotationManagerContext() {
|
|
2138
|
+
return {
|
|
2139
|
+
canvas: this.getLiveCanvasOrThrow('annotationManager'),
|
|
2140
|
+
saveCanvasState: () => this.saveState(),
|
|
2141
|
+
updateUi: () => this.updateUi(),
|
|
2142
|
+
};
|
|
2143
|
+
}
|
|
2144
|
+
buildAnnotationListContext() {
|
|
2145
|
+
return {
|
|
2146
|
+
canvas: this.canvas,
|
|
2147
|
+
getListElementId: () => this.elements.annotationList,
|
|
2148
|
+
onAnnotationSelected: (annotation) => this.handleSelectionChanged([annotation]),
|
|
2149
|
+
};
|
|
2150
|
+
}
|
|
2151
|
+
updateAnnotationList() {
|
|
2152
|
+
renderAnnotationList(this.buildAnnotationListContext());
|
|
2153
|
+
}
|
|
2154
|
+
updateAnnotationListSelection(selectedAnnotation) {
|
|
2155
|
+
updateAnnotationListSelection(this.buildAnnotationListContext(), selectedAnnotation);
|
|
2156
|
+
}
|
|
2157
|
+
buildTextControllerContext() {
|
|
2158
|
+
return {
|
|
2159
|
+
fabric: this.fabricModule,
|
|
2160
|
+
canvas: this.getLiveCanvasOrThrow('textController'),
|
|
2161
|
+
options: this.options,
|
|
2162
|
+
getOriginalImage: () => this.originalImage,
|
|
2163
|
+
getTextConfig: () => this.currentTextConfig,
|
|
2164
|
+
isImageLoaded: () => this.isImageLoaded(),
|
|
2165
|
+
getAnnotationCounter: () => this.annotationCounter,
|
|
2166
|
+
setAnnotationCounter: (value) => {
|
|
2167
|
+
this.annotationCounter = value;
|
|
2168
|
+
},
|
|
2169
|
+
getTextSession: () => this.textSession,
|
|
2170
|
+
setTextSession: (session) => {
|
|
2171
|
+
this.textSession = session;
|
|
2172
|
+
},
|
|
2173
|
+
saveCanvasState: () => this.saveState(),
|
|
2174
|
+
updateAnnotationList: () => this.updateAnnotationList(),
|
|
2175
|
+
updateUi: () => this.updateUi(),
|
|
2176
|
+
emitAnnotationsChanged: (context) => this.emitAnnotationsChanged(context),
|
|
2177
|
+
emitImageChanged: (context) => this.emitImageChanged(context),
|
|
2178
|
+
buildCallbackContext: (operation) => this.buildCallbackContext(operation, false),
|
|
2179
|
+
};
|
|
2180
|
+
}
|
|
2181
|
+
buildDrawControllerContext() {
|
|
2182
|
+
return {
|
|
2183
|
+
fabric: this.fabricModule,
|
|
2184
|
+
canvas: this.getLiveCanvasOrThrow('drawController'),
|
|
2185
|
+
options: this.options,
|
|
2186
|
+
getDrawConfig: () => this.currentDrawConfig,
|
|
2187
|
+
isImageLoaded: () => this.isImageLoaded(),
|
|
2188
|
+
getAnnotationCounter: () => this.annotationCounter,
|
|
2189
|
+
setAnnotationCounter: (value) => {
|
|
2190
|
+
this.annotationCounter = value;
|
|
2191
|
+
},
|
|
2192
|
+
getDrawSession: () => this.drawSession,
|
|
2193
|
+
setDrawSession: (session) => {
|
|
2194
|
+
this.drawSession = session;
|
|
2195
|
+
},
|
|
2196
|
+
saveCanvasState: () => this.saveState(),
|
|
2197
|
+
updateAnnotationList: () => this.updateAnnotationList(),
|
|
2198
|
+
updateUi: () => this.updateUi(),
|
|
2199
|
+
emitAnnotationsChanged: (context) => this.emitAnnotationsChanged(context),
|
|
2200
|
+
emitImageChanged: (context) => this.emitImageChanged(context),
|
|
2201
|
+
buildCallbackContext: (operation) => this.buildCallbackContext(operation, false),
|
|
2202
|
+
};
|
|
2203
|
+
}
|
|
2204
|
+
applyTextConfigPatch(config, operation) {
|
|
2205
|
+
if (!this.canRunIdleOperation(operation))
|
|
2206
|
+
return;
|
|
2207
|
+
const invalidFields = getInvalidTextAnnotationConfigFields(config);
|
|
2208
|
+
if (invalidFields.length > 0) {
|
|
2209
|
+
reportWarning(this.options, null, `${operation} ignored invalid Text config fields: ${invalidFields.join(', ')}.`);
|
|
2210
|
+
}
|
|
2211
|
+
const next = mergeTextAnnotationConfigPatch(this.currentTextConfig, config, this.defaultTextConfig);
|
|
2212
|
+
if (areResolvedTextAnnotationConfigsEqual(this.currentTextConfig, next))
|
|
2213
|
+
return;
|
|
2214
|
+
this.currentTextConfig = next;
|
|
2215
|
+
this.updateInputs();
|
|
2216
|
+
this.updateUi();
|
|
2217
|
+
this.emitImageChanged(this.buildCallbackContext(operation, false));
|
|
2218
|
+
}
|
|
2219
|
+
applyDrawConfigPatch(config, operation) {
|
|
2220
|
+
if (!this.canRunIdleOperation(operation))
|
|
2221
|
+
return;
|
|
2222
|
+
const invalidFields = getInvalidDrawConfigFields(config);
|
|
2223
|
+
if (invalidFields.length > 0) {
|
|
2224
|
+
reportWarning(this.options, null, `${operation} ignored invalid Draw config fields: ${invalidFields.join(', ')}.`);
|
|
2225
|
+
}
|
|
2226
|
+
const next = mergeDrawConfigPatch(this.currentDrawConfig, config, this.defaultDrawConfig);
|
|
2227
|
+
if (areResolvedDrawConfigsEqual(this.currentDrawConfig, next))
|
|
2228
|
+
return;
|
|
2229
|
+
this.currentDrawConfig = next;
|
|
2230
|
+
updateDrawBrush(this.buildDrawControllerContext());
|
|
2231
|
+
this.updateInputs();
|
|
2232
|
+
this.updateUi();
|
|
2233
|
+
this.emitImageChanged(this.buildCallbackContext(operation, false));
|
|
2234
|
+
}
|
|
2235
|
+
applyTextColorInput(color) {
|
|
2236
|
+
var _a;
|
|
2237
|
+
if (this.isTextMode()) {
|
|
2238
|
+
this.setTextColor(color);
|
|
2239
|
+
return;
|
|
2240
|
+
}
|
|
2241
|
+
const selected = (_a = this.canvas) === null || _a === void 0 ? void 0 : _a.getActiveObject();
|
|
2242
|
+
if (selected && isTextAnnotationObject(selected)) {
|
|
2243
|
+
this.updateSelectedAnnotation({ fill: color });
|
|
2244
|
+
return;
|
|
2245
|
+
}
|
|
2246
|
+
this.setTextColor(color);
|
|
2247
|
+
}
|
|
2248
|
+
applyTextFontSizeInput(size) {
|
|
2249
|
+
var _a;
|
|
2250
|
+
if (this.isTextMode()) {
|
|
2251
|
+
this.setTextFontSize(size);
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
const selected = (_a = this.canvas) === null || _a === void 0 ? void 0 : _a.getActiveObject();
|
|
2255
|
+
if (selected && isTextAnnotationObject(selected)) {
|
|
2256
|
+
this.updateSelectedAnnotation({ fontSize: size });
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
this.setTextFontSize(size);
|
|
2260
|
+
}
|
|
2261
|
+
applyDrawColorInput(color) {
|
|
2262
|
+
var _a;
|
|
2263
|
+
if (this.isDrawMode()) {
|
|
2264
|
+
this.setDrawColor(color);
|
|
2265
|
+
return;
|
|
2266
|
+
}
|
|
2267
|
+
const selected = (_a = this.canvas) === null || _a === void 0 ? void 0 : _a.getActiveObject();
|
|
2268
|
+
if (selected && isDrawAnnotationObject(selected)) {
|
|
2269
|
+
this.updateSelectedAnnotation({ stroke: color });
|
|
2270
|
+
return;
|
|
2271
|
+
}
|
|
2272
|
+
this.setDrawColor(color);
|
|
2273
|
+
}
|
|
2274
|
+
applyDrawBrushSizeInput(size) {
|
|
2275
|
+
var _a;
|
|
2276
|
+
if (this.isDrawMode()) {
|
|
2277
|
+
this.setDrawBrushSize(size);
|
|
2278
|
+
return;
|
|
2279
|
+
}
|
|
2280
|
+
const selected = (_a = this.canvas) === null || _a === void 0 ? void 0 : _a.getActiveObject();
|
|
2281
|
+
if (selected && isDrawAnnotationObject(selected)) {
|
|
2282
|
+
this.updateSelectedAnnotation({ strokeWidth: size });
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2285
|
+
this.setDrawBrushSize(size);
|
|
2286
|
+
}
|
|
2287
|
+
getSelectedCanvasObjects() {
|
|
2288
|
+
var _a, _b, _c;
|
|
2289
|
+
if (!this.canvas)
|
|
2290
|
+
return [];
|
|
2291
|
+
const activeObject = this.canvas.getActiveObject();
|
|
2292
|
+
if (!activeObject)
|
|
2293
|
+
return [];
|
|
2294
|
+
const type = typeof activeObject.type === 'string' ? activeObject.type.toLowerCase() : '';
|
|
2295
|
+
const isActiveSelection = type === 'activeselection' ||
|
|
2296
|
+
((_c = (_b = (_a = activeObject).isType) === null || _b === void 0 ? void 0 : _b.call(_a, 'ActiveSelection')) !== null && _c !== void 0 ? _c : false);
|
|
2297
|
+
if (!isActiveSelection)
|
|
2298
|
+
return [activeObject];
|
|
2299
|
+
const getObjects = activeObject
|
|
2300
|
+
.getObjects;
|
|
2301
|
+
return typeof getObjects === 'function' ? getObjects.call(activeObject) : [];
|
|
2302
|
+
}
|
|
2303
|
+
moveSelectedEditableObject(operation) {
|
|
2304
|
+
if (!this.canvas)
|
|
2305
|
+
return;
|
|
2306
|
+
if (!this.canRunIdleOperation(operation))
|
|
2307
|
+
return;
|
|
2308
|
+
const selected = this.getSelectedCanvasObjects().filter(isEditableOverlayObject);
|
|
2309
|
+
if (selected.length !== 1) {
|
|
2310
|
+
if (selected.length > 1) {
|
|
2311
|
+
reportWarning(this.options, null, `${operation} skipped: ActiveSelection layer moves are not supported.`);
|
|
2312
|
+
}
|
|
2313
|
+
return;
|
|
2314
|
+
}
|
|
2315
|
+
const object = selected[0];
|
|
2316
|
+
const range = getEditableOverlayRange(this.canvas);
|
|
2317
|
+
const overlays = range.overlays;
|
|
2318
|
+
const currentOverlayIndex = overlays.indexOf(object);
|
|
2319
|
+
if (currentOverlayIndex < 0)
|
|
2320
|
+
return;
|
|
2321
|
+
let nextOverlayIndex = currentOverlayIndex;
|
|
2322
|
+
if (operation === 'bringSelectedObjectForward') {
|
|
2323
|
+
nextOverlayIndex = Math.min(overlays.length - 1, currentOverlayIndex + 1);
|
|
2324
|
+
}
|
|
2325
|
+
else if (operation === 'sendSelectedObjectBackward') {
|
|
2326
|
+
nextOverlayIndex = Math.max(0, currentOverlayIndex - 1);
|
|
2327
|
+
}
|
|
2328
|
+
else if (operation === 'bringSelectedObjectToFront') {
|
|
2329
|
+
nextOverlayIndex = overlays.length - 1;
|
|
2330
|
+
}
|
|
2331
|
+
else if (operation === 'sendSelectedObjectToBack') {
|
|
2332
|
+
nextOverlayIndex = 0;
|
|
2333
|
+
}
|
|
2334
|
+
if (nextOverlayIndex === currentOverlayIndex)
|
|
2335
|
+
return;
|
|
2336
|
+
const reordered = overlays.slice();
|
|
2337
|
+
reordered.splice(currentOverlayIndex, 1);
|
|
2338
|
+
reordered.splice(nextOverlayIndex, 0, object);
|
|
2339
|
+
reordered.forEach((overlay, index) => {
|
|
2340
|
+
var _a, _b;
|
|
2341
|
+
(_b = (_a = this.canvas).moveObjectTo) === null || _b === void 0 ? void 0 : _b.call(_a, overlay, range.start + index);
|
|
2342
|
+
});
|
|
2343
|
+
normalizeLayerOrder(this.canvas);
|
|
2344
|
+
this.canvas.setActiveObject(object);
|
|
2345
|
+
this.canvas.renderAll();
|
|
2346
|
+
this.saveState();
|
|
2347
|
+
this.updateMaskList();
|
|
2348
|
+
this.updateAnnotationList();
|
|
2349
|
+
this.updateUi();
|
|
2350
|
+
const context = this.buildCallbackContext(operation, false);
|
|
2351
|
+
if (isMaskObject(object))
|
|
2352
|
+
this.emitMasksChanged(context);
|
|
2353
|
+
if (isAnnotationObject(object))
|
|
2354
|
+
this.emitAnnotationsChanged(context);
|
|
2355
|
+
this.emitImageChanged(context);
|
|
2356
|
+
}
|
|
1537
2357
|
async mergeMasks() {
|
|
1538
2358
|
if (!this.canvas)
|
|
1539
2359
|
return;
|
|
1540
2360
|
if (!this.canRunIdleOperation('mergeMasks'))
|
|
1541
2361
|
return;
|
|
2362
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
1542
2363
|
const hasMasks = this.canvas.getObjects().some(isMaskObject);
|
|
1543
2364
|
if (!hasMasks)
|
|
1544
2365
|
return;
|
|
@@ -1551,7 +2372,11 @@ export class ImageEditor {
|
|
|
1551
2372
|
await mergeMasksImpl(mergeMasksContext);
|
|
1552
2373
|
this.updateInputs();
|
|
1553
2374
|
this.updateMaskList();
|
|
2375
|
+
this.updateAnnotationList();
|
|
1554
2376
|
this.emitMasksChanged(callbackContext);
|
|
2377
|
+
if (this.getAnnotations().length > 0) {
|
|
2378
|
+
this.emitAnnotationsChanged(callbackContext);
|
|
2379
|
+
}
|
|
1555
2380
|
this.emitImageChanged(callbackContext);
|
|
1556
2381
|
}
|
|
1557
2382
|
finally {
|
|
@@ -1560,17 +2385,18 @@ export class ImageEditor {
|
|
|
1560
2385
|
this.updateUi();
|
|
1561
2386
|
}
|
|
1562
2387
|
}
|
|
1563
|
-
downloadImage(
|
|
2388
|
+
downloadImage(options) {
|
|
1564
2389
|
if (!this.canvas)
|
|
1565
2390
|
return;
|
|
1566
2391
|
if (!this.canRunIdleOperation('downloadImage'))
|
|
1567
2392
|
return;
|
|
2393
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
1568
2394
|
const callbackContext = this.buildCallbackContext('downloadImage', false);
|
|
1569
2395
|
const operationToken = this.operationGuard.beginBusyOperation('downloadImage');
|
|
1570
2396
|
this.emitBusyChangeIfChanged(callbackContext);
|
|
1571
2397
|
const exportContext = this.buildExportServiceContext();
|
|
1572
2398
|
try {
|
|
1573
|
-
downloadImageImpl(exportContext,
|
|
2399
|
+
downloadImageImpl(exportContext, options);
|
|
1574
2400
|
}
|
|
1575
2401
|
finally {
|
|
1576
2402
|
this.operationGuard.endBusyOperation(operationToken);
|
|
@@ -1582,6 +2408,7 @@ export class ImageEditor {
|
|
|
1582
2408
|
return '';
|
|
1583
2409
|
if (!this.canRunIdleOperation('exportImageBase64', options))
|
|
1584
2410
|
return '';
|
|
2411
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
1585
2412
|
const callbackContext = this.buildCallbackContext('exportImageBase64', false);
|
|
1586
2413
|
const operationToken = this.operationGuard.beginBusyOperation('exportImageBase64');
|
|
1587
2414
|
this.emitBusyChangeIfChanged(callbackContext);
|
|
@@ -1596,6 +2423,7 @@ export class ImageEditor {
|
|
|
1596
2423
|
}
|
|
1597
2424
|
async exportImageFile(options) {
|
|
1598
2425
|
this.assertIdleForOperation('exportImageFile', options);
|
|
2426
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
1599
2427
|
const callbackContext = this.buildCallbackContext('exportImageFile', false);
|
|
1600
2428
|
const operationToken = this.operationGuard.beginBusyOperation('exportImageFile');
|
|
1601
2429
|
this.emitBusyChangeIfChanged(callbackContext);
|
|
@@ -1611,7 +2439,7 @@ export class ImageEditor {
|
|
|
1611
2439
|
buildExportServiceContext() {
|
|
1612
2440
|
return {
|
|
1613
2441
|
fabric: this.fabricModule,
|
|
1614
|
-
canvas: this.
|
|
2442
|
+
canvas: this.getLiveCanvasOrThrow('export'),
|
|
1615
2443
|
options: this.options,
|
|
1616
2444
|
isImageLoaded: () => this.isImageLoaded(),
|
|
1617
2445
|
getOriginalImage: () => this.originalImage,
|
|
@@ -1627,24 +2455,74 @@ export class ImageEditor {
|
|
|
1627
2455
|
await this.loadImageInternal(base64, this.withInternalOperationOptions(operationToken, providedOptions !== null && providedOptions !== void 0 ? providedOptions : {}));
|
|
1628
2456
|
this.restoreMergedImageDisplayGeometry(geometry);
|
|
1629
2457
|
},
|
|
1630
|
-
|
|
2458
|
+
captureSnapshot: () => this.captureSnapshotInternal(),
|
|
1631
2459
|
loadFromState: (snapshot) => this.loadFromStateInternal(snapshot, this.withInternalOperationOptions(operationToken, this.withAnimationQueueBypass())),
|
|
2460
|
+
exportImageBase64: (options) => exportImageBase64Impl(this.buildExportServiceContext(), options),
|
|
2461
|
+
updateUi: () => this.updateUi(),
|
|
2462
|
+
updateInputs: () => this.updateInputs(),
|
|
1632
2463
|
removeAllMasksNoHistory: () => {
|
|
1633
2464
|
const context = this.buildRemoveMaskContext();
|
|
1634
2465
|
removeAllMasksImpl(context, { saveHistory: false });
|
|
1635
2466
|
},
|
|
2467
|
+
getAnnotations: () => this.getAnnotations(),
|
|
2468
|
+
restoreAnnotations: (objects) => {
|
|
2469
|
+
const canvas = this.getLiveCanvasOrThrow('restoreAnnotations');
|
|
2470
|
+
objects.forEach((annotation) => {
|
|
2471
|
+
canvas.add(annotation);
|
|
2472
|
+
});
|
|
2473
|
+
syncAnnotationRuntimeStates(objects);
|
|
2474
|
+
attachTextEditingHandlersToAnnotations(this.buildTextControllerContext(), objects);
|
|
2475
|
+
this.annotationCounter = Math.max(this.annotationCounter, ...objects.map((annotation) => annotation.annotationId), 0);
|
|
2476
|
+
this.updateAnnotationList();
|
|
2477
|
+
},
|
|
2478
|
+
};
|
|
2479
|
+
}
|
|
2480
|
+
buildMergeAnnotationsContext(operationToken) {
|
|
2481
|
+
return {
|
|
2482
|
+
...this.buildExportServiceContext(),
|
|
2483
|
+
historyManager: this.historyManager,
|
|
2484
|
+
containerElement: this.containerElement,
|
|
2485
|
+
loadImage: async (base64, providedOptions) => {
|
|
2486
|
+
const geometry = this.captureImageDisplayGeometry();
|
|
2487
|
+
await this.loadImageInternal(base64, this.withInternalOperationOptions(operationToken, providedOptions !== null && providedOptions !== void 0 ? providedOptions : {}));
|
|
2488
|
+
this.restoreMergedImageDisplayGeometry(geometry);
|
|
2489
|
+
},
|
|
2490
|
+
captureSnapshot: () => this.captureSnapshotInternal(),
|
|
2491
|
+
loadFromState: (snapshot) => this.loadFromStateInternal(snapshot, this.withInternalOperationOptions(operationToken, this.withAnimationQueueBypass())),
|
|
2492
|
+
exportImageBase64: (options) => exportImageBase64Impl(this.buildExportServiceContext(), options),
|
|
2493
|
+
updateUi: () => this.updateUi(),
|
|
2494
|
+
updateInputs: () => this.updateInputs(),
|
|
2495
|
+
removeAllAnnotationsNoHistory: () => {
|
|
2496
|
+
removeAllAnnotationsImpl(this.buildAnnotationManagerContext(), {
|
|
2497
|
+
saveHistory: false,
|
|
2498
|
+
force: true,
|
|
2499
|
+
});
|
|
2500
|
+
},
|
|
2501
|
+
getMasks: () => this.getMasks(),
|
|
2502
|
+
restoreMasks: (objects) => {
|
|
2503
|
+
const canvas = this.getLiveCanvasOrThrow('restoreMasks');
|
|
2504
|
+
objects.forEach((mask) => {
|
|
2505
|
+
canvas.add(mask);
|
|
2506
|
+
reattachMaskHoverHandlers(mask);
|
|
2507
|
+
});
|
|
2508
|
+
this.lastMask = objects.reduce((lastMask, mask) => !lastMask || mask.maskId > lastMask.maskId ? mask : lastMask, null);
|
|
2509
|
+
this.maskCounter = Math.max(this.maskCounter, ...objects.map((mask) => mask.maskId), 0);
|
|
2510
|
+
this.updateMaskList();
|
|
2511
|
+
},
|
|
1636
2512
|
};
|
|
1637
2513
|
}
|
|
1638
2514
|
captureSnapshotInternal() {
|
|
1639
|
-
var _a;
|
|
2515
|
+
var _a, _b;
|
|
1640
2516
|
if (!this.canvas) {
|
|
1641
2517
|
throw new Error('[ImageEditor] Cannot capture canvas snapshot before init or after dispose.');
|
|
1642
2518
|
}
|
|
1643
2519
|
const activeMask = this.getActiveMaskForSnapshot();
|
|
2520
|
+
const activeAnnotation = this.getActiveAnnotationForSnapshot();
|
|
1644
2521
|
this.hideAllMaskLabels();
|
|
1645
2522
|
return saveStateImpl({
|
|
1646
2523
|
canvas: this.canvas,
|
|
1647
2524
|
activeMaskId: (_a = activeMask === null || activeMask === void 0 ? void 0 : activeMask.maskId) !== null && _a !== void 0 ? _a : null,
|
|
2525
|
+
activeAnnotationId: (_b = activeAnnotation === null || activeAnnotation === void 0 ? void 0 : activeAnnotation.annotationId) !== null && _b !== void 0 ? _b : null,
|
|
1648
2526
|
currentScale: this.currentScale,
|
|
1649
2527
|
currentRotation: this.currentRotation,
|
|
1650
2528
|
baseImageScale: this.baseImageScale,
|
|
@@ -1663,6 +2541,12 @@ export class ImageEditor {
|
|
|
1663
2541
|
.filter((object) => isMaskObject(object) && !!object.labelObject);
|
|
1664
2542
|
return labeledMasks.length === 1 ? ((_a = labeledMasks[0]) !== null && _a !== void 0 ? _a : null) : null;
|
|
1665
2543
|
}
|
|
2544
|
+
getActiveAnnotationForSnapshot() {
|
|
2545
|
+
if (!this.canvas)
|
|
2546
|
+
return null;
|
|
2547
|
+
const activeObject = this.canvas.getActiveObject();
|
|
2548
|
+
return activeObject && isAnnotationObject(activeObject) ? activeObject : null;
|
|
2549
|
+
}
|
|
1666
2550
|
enterMosaicMode() {
|
|
1667
2551
|
if (!this.canvas || !this.originalImage)
|
|
1668
2552
|
return;
|
|
@@ -1746,7 +2630,7 @@ export class ImageEditor {
|
|
|
1746
2630
|
buildMosaicControllerContext() {
|
|
1747
2631
|
return {
|
|
1748
2632
|
fabric: this.fabricModule,
|
|
1749
|
-
canvas: this.
|
|
2633
|
+
canvas: this.getLiveCanvasOrThrow('mosaicController'),
|
|
1750
2634
|
options: this.options,
|
|
1751
2635
|
historyManager: this.historyManager,
|
|
1752
2636
|
getMosaicConfig: () => cloneResolvedMosaicConfig(this.currentMosaicConfig),
|
|
@@ -1846,7 +2730,7 @@ export class ImageEditor {
|
|
|
1846
2730
|
buildCropControllerContext(operationToken) {
|
|
1847
2731
|
return {
|
|
1848
2732
|
fabric: this.fabricModule,
|
|
1849
|
-
canvas: this.
|
|
2733
|
+
canvas: this.getLiveCanvasOrThrow('cropController'),
|
|
1850
2734
|
options: this.options,
|
|
1851
2735
|
historyManager: this.historyManager,
|
|
1852
2736
|
isImageLoaded: () => this.isImageLoaded(),
|
|
@@ -1868,26 +2752,82 @@ export class ImageEditor {
|
|
|
1868
2752
|
},
|
|
1869
2753
|
};
|
|
1870
2754
|
}
|
|
2755
|
+
syncInputValue(inputElement, value) {
|
|
2756
|
+
if (!inputElement)
|
|
2757
|
+
return;
|
|
2758
|
+
const ownerDocument = inputElement.ownerDocument;
|
|
2759
|
+
if (ownerDocument.activeElement === inputElement && !inputElement.readOnly)
|
|
2760
|
+
return;
|
|
2761
|
+
if (inputElement.value !== value)
|
|
2762
|
+
inputElement.value = value;
|
|
2763
|
+
}
|
|
1871
2764
|
updateInputs() {
|
|
1872
2765
|
const scaleId = this.elements.scalePercentageInput;
|
|
1873
2766
|
if (scaleId) {
|
|
1874
2767
|
const scaleInputElement = document.getElementById(scaleId);
|
|
1875
|
-
|
|
1876
|
-
scaleInputElement.value = String(Math.round(this.currentScale * 100));
|
|
1877
|
-
}
|
|
2768
|
+
this.syncInputValue(scaleInputElement, String(Math.round(this.currentScale * 100)));
|
|
1878
2769
|
}
|
|
1879
2770
|
const mosaicConfig = this.getMosaicConfig();
|
|
1880
2771
|
const mosaicBrushSizeInputId = this.elements.mosaicBrushSizeInput;
|
|
1881
2772
|
if (mosaicBrushSizeInputId) {
|
|
1882
2773
|
const brushInput = document.getElementById(mosaicBrushSizeInputId);
|
|
1883
|
-
|
|
1884
|
-
brushInput.value = String(mosaicConfig.brushSize);
|
|
2774
|
+
this.syncInputValue(brushInput, String(mosaicConfig.brushSize));
|
|
1885
2775
|
}
|
|
1886
2776
|
const mosaicBlockSizeInputId = this.elements.mosaicBlockSizeInput;
|
|
1887
2777
|
if (mosaicBlockSizeInputId) {
|
|
1888
2778
|
const blockInput = document.getElementById(mosaicBlockSizeInputId);
|
|
1889
|
-
|
|
1890
|
-
|
|
2779
|
+
this.syncInputValue(blockInput, String(mosaicConfig.blockSize));
|
|
2780
|
+
}
|
|
2781
|
+
const textConfig = this.getTextConfig();
|
|
2782
|
+
const textColorInputId = this.elements.textColorInput;
|
|
2783
|
+
if (textColorInputId) {
|
|
2784
|
+
const textColorInput = document.getElementById(textColorInputId);
|
|
2785
|
+
this.syncInputValue(textColorInput, textConfig.fill);
|
|
2786
|
+
}
|
|
2787
|
+
const textFontSizeInputId = this.elements.textFontSizeInput;
|
|
2788
|
+
if (textFontSizeInputId) {
|
|
2789
|
+
const fontInput = document.getElementById(textFontSizeInputId);
|
|
2790
|
+
this.syncInputValue(fontInput, String(textConfig.fontSize));
|
|
2791
|
+
}
|
|
2792
|
+
const drawConfig = this.getDrawConfig();
|
|
2793
|
+
const drawColorInputId = this.elements.drawColorInput;
|
|
2794
|
+
if (drawColorInputId) {
|
|
2795
|
+
const drawColorInput = document.getElementById(drawColorInputId);
|
|
2796
|
+
this.syncInputValue(drawColorInput, drawConfig.color);
|
|
2797
|
+
}
|
|
2798
|
+
const drawBrushSizeInputId = this.elements.drawBrushSizeInput;
|
|
2799
|
+
if (drawBrushSizeInputId) {
|
|
2800
|
+
const brushInput = document.getElementById(drawBrushSizeInputId);
|
|
2801
|
+
this.syncInputValue(brushInput, String(drawConfig.brushSize));
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
async mergeAnnotations() {
|
|
2805
|
+
if (!this.canvas)
|
|
2806
|
+
return;
|
|
2807
|
+
if (!this.canRunIdleOperation('mergeAnnotations'))
|
|
2808
|
+
return;
|
|
2809
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
2810
|
+
const hasAnnotations = this.canvas.getObjects().some(isAnnotationObject);
|
|
2811
|
+
if (!hasAnnotations)
|
|
2812
|
+
return;
|
|
2813
|
+
const callbackContext = this.buildCallbackContext('mergeAnnotations', false);
|
|
2814
|
+
const operationToken = this.operationGuard.beginBusyOperation('mergeAnnotations');
|
|
2815
|
+
this.emitBusyChangeIfChanged(callbackContext);
|
|
2816
|
+
this.updateUi();
|
|
2817
|
+
try {
|
|
2818
|
+
await mergeAnnotationsImpl(this.buildMergeAnnotationsContext(operationToken));
|
|
2819
|
+
this.updateInputs();
|
|
2820
|
+
this.updateMaskList();
|
|
2821
|
+
this.updateAnnotationList();
|
|
2822
|
+
this.emitAnnotationsChanged(callbackContext);
|
|
2823
|
+
if (this.getMasks().length > 0)
|
|
2824
|
+
this.emitMasksChanged(callbackContext);
|
|
2825
|
+
this.emitImageChanged(callbackContext);
|
|
2826
|
+
}
|
|
2827
|
+
finally {
|
|
2828
|
+
this.operationGuard.endBusyOperation(operationToken);
|
|
2829
|
+
this.emitBusyChangeIfChanged(callbackContext);
|
|
2830
|
+
this.updateUi();
|
|
1891
2831
|
}
|
|
1892
2832
|
}
|
|
1893
2833
|
updateUi() {
|
|
@@ -1896,14 +2836,20 @@ export class ImageEditor {
|
|
|
1896
2836
|
return;
|
|
1897
2837
|
const hasImage = !!this.originalImage;
|
|
1898
2838
|
const masks = hasImage ? this.canvas.getObjects().filter(isMaskObject) : [];
|
|
2839
|
+
const annotations = hasImage ? this.canvas.getObjects().filter(isAnnotationObject) : [];
|
|
1899
2840
|
const hasMasks = masks.length > 0;
|
|
2841
|
+
const hasAnnotations = annotations.length > 0;
|
|
1900
2842
|
const activeObject = this.canvas.getActiveObject();
|
|
1901
2843
|
const hasSelectedMask = !!(activeObject && isMaskObject(activeObject));
|
|
2844
|
+
const hasSelectedAnnotation = !!(activeObject && isAnnotationObject(activeObject));
|
|
2845
|
+
const hasSelectedEditableObject = !!activeObject && isEditableOverlayObject(activeObject);
|
|
1902
2846
|
const isDefaultTransform = this.currentScale === 1 && this.currentRotation === 0;
|
|
1903
2847
|
const canUndo = this.historyManager.canUndo();
|
|
1904
2848
|
const canRedo = this.historyManager.canRedo();
|
|
1905
2849
|
const isInCropMode = this.cropSession !== null;
|
|
1906
2850
|
const isInMosaicMode = this.mosaicSession !== null;
|
|
2851
|
+
const isInTextMode = this.textSession !== null;
|
|
2852
|
+
const isInDrawMode = this.drawSession !== null;
|
|
1907
2853
|
const isBusy = this.operationGuard.isBusy() || this.animQueue.isBusy();
|
|
1908
2854
|
const isMosaicApplying = ((_a = this.mosaicSession) === null || _a === void 0 ? void 0 : _a.isApplying) === true;
|
|
1909
2855
|
if (isInCropMode) {
|
|
@@ -1912,6 +2858,18 @@ export class ImageEditor {
|
|
|
1912
2858
|
});
|
|
1913
2859
|
return;
|
|
1914
2860
|
}
|
|
2861
|
+
if (isInTextMode) {
|
|
2862
|
+
CROP_MODE_CONTROL_KEYS.forEach((key) => {
|
|
2863
|
+
this.setControlEnabled(key, !isBusy && TEXT_MODE_ENABLED_KEYS.includes(key));
|
|
2864
|
+
});
|
|
2865
|
+
return;
|
|
2866
|
+
}
|
|
2867
|
+
if (isInDrawMode) {
|
|
2868
|
+
CROP_MODE_CONTROL_KEYS.forEach((key) => {
|
|
2869
|
+
this.setControlEnabled(key, !isBusy && DRAW_MODE_ENABLED_KEYS.includes(key));
|
|
2870
|
+
});
|
|
2871
|
+
return;
|
|
2872
|
+
}
|
|
1915
2873
|
if (isInMosaicMode) {
|
|
1916
2874
|
MOSAIC_MODE_CONTROL_KEYS.forEach((key) => {
|
|
1917
2875
|
this.setControlEnabled(key, !isBusy && !isMosaicApplying && MOSAIC_MODE_ENABLED_KEYS.includes(key));
|
|
@@ -1930,15 +2888,31 @@ export class ImageEditor {
|
|
|
1930
2888
|
this.setControlEnabled('removeSelectedMaskButton', hasSelectedMask && !isBusy);
|
|
1931
2889
|
this.setControlEnabled('removeAllMasksButton', hasMasks && !isBusy);
|
|
1932
2890
|
this.setControlEnabled('mergeMasksButton', hasImage && hasMasks && !isBusy);
|
|
2891
|
+
this.setControlEnabled('removeSelectedAnnotationButton', hasSelectedAnnotation && !isBusy);
|
|
2892
|
+
this.setControlEnabled('removeAllAnnotationsButton', hasAnnotations && !isBusy);
|
|
2893
|
+
this.setControlEnabled('deleteSelectedObjectButton', hasSelectedEditableObject && !isBusy);
|
|
2894
|
+
this.setControlEnabled('mergeAnnotationsButton', hasImage && hasAnnotations && !isBusy);
|
|
2895
|
+
this.setControlEnabled('bringSelectedObjectForwardButton', hasSelectedEditableObject && !isBusy);
|
|
2896
|
+
this.setControlEnabled('sendSelectedObjectBackwardButton', hasSelectedEditableObject && !isBusy);
|
|
2897
|
+
this.setControlEnabled('bringSelectedObjectToFrontButton', hasSelectedEditableObject && !isBusy);
|
|
2898
|
+
this.setControlEnabled('sendSelectedObjectToBackButton', hasSelectedEditableObject && !isBusy);
|
|
1933
2899
|
this.setControlEnabled('downloadImageButton', hasImage && !isBusy);
|
|
1934
2900
|
this.setControlEnabled('resetImageTransformButton', hasImage && !isDefaultTransform && !isBusy);
|
|
1935
2901
|
this.setControlEnabled('undoButton', hasImage && !isBusy && canUndo);
|
|
1936
2902
|
this.setControlEnabled('redoButton', hasImage && !isBusy && canRedo);
|
|
1937
2903
|
this.setControlEnabled('enterCropModeButton', hasImage && !isBusy);
|
|
1938
2904
|
this.setControlEnabled('enterMosaicModeButton', hasImage && !isBusy);
|
|
2905
|
+
this.setControlEnabled('enterTextModeButton', hasImage && !isBusy);
|
|
2906
|
+
this.setControlEnabled('enterDrawModeButton', hasImage && !isBusy);
|
|
1939
2907
|
this.setControlEnabled('exitMosaicModeButton', false);
|
|
2908
|
+
this.setControlEnabled('exitTextModeButton', false);
|
|
2909
|
+
this.setControlEnabled('exitDrawModeButton', false);
|
|
1940
2910
|
this.setControlEnabled('mosaicBrushSizeInput', !this.isDisposed);
|
|
1941
2911
|
this.setControlEnabled('mosaicBlockSizeInput', !this.isDisposed);
|
|
2912
|
+
this.setControlEnabled('textColorInput', !this.isDisposed);
|
|
2913
|
+
this.setControlEnabled('textFontSizeInput', !this.isDisposed);
|
|
2914
|
+
this.setControlEnabled('drawColorInput', !this.isDisposed);
|
|
2915
|
+
this.setControlEnabled('drawBrushSizeInput', !this.isDisposed);
|
|
1942
2916
|
this.setControlEnabled('imageInput', !isBusy);
|
|
1943
2917
|
this.setControlEnabled('applyCropButton', false);
|
|
1944
2918
|
this.setControlEnabled('cancelCropButton', false);
|
|
@@ -1953,7 +2927,10 @@ export class ImageEditor {
|
|
|
1953
2927
|
return;
|
|
1954
2928
|
this.recordElementOriginalState(key, controlElement);
|
|
1955
2929
|
if ('disabled' in controlElement) {
|
|
1956
|
-
|
|
2930
|
+
const formControl = controlElement;
|
|
2931
|
+
const nextDisabled = !isEnabled;
|
|
2932
|
+
if (formControl.disabled !== nextDisabled)
|
|
2933
|
+
formControl.disabled = nextDisabled;
|
|
1957
2934
|
return;
|
|
1958
2935
|
}
|
|
1959
2936
|
if (!isEnabled) {
|
|
@@ -2026,6 +3003,15 @@ export class ImageEditor {
|
|
|
2026
3003
|
this.operationGuard.markDisposed();
|
|
2027
3004
|
this.animQueue.clear();
|
|
2028
3005
|
(_a = this.domBindings) === null || _a === void 0 ? void 0 : _a.removeAll();
|
|
3006
|
+
if (this.keyboardHandler && this.keyboardDocument) {
|
|
3007
|
+
try {
|
|
3008
|
+
this.keyboardDocument.removeEventListener('keydown', this.keyboardHandler);
|
|
3009
|
+
}
|
|
3010
|
+
catch {
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
this.keyboardHandler = null;
|
|
3014
|
+
this.keyboardDocument = null;
|
|
2029
3015
|
this.restoreElementOriginalStates();
|
|
2030
3016
|
if (this.cropSession && this.canvas) {
|
|
2031
3017
|
try {
|
|
@@ -2044,6 +3030,22 @@ export class ImageEditor {
|
|
|
2044
3030
|
}
|
|
2045
3031
|
this.mosaicSession = null;
|
|
2046
3032
|
}
|
|
3033
|
+
if (this.textSession && this.canvas) {
|
|
3034
|
+
try {
|
|
3035
|
+
exitTextModeImpl(this.buildTextControllerContext());
|
|
3036
|
+
}
|
|
3037
|
+
catch {
|
|
3038
|
+
}
|
|
3039
|
+
this.textSession = null;
|
|
3040
|
+
}
|
|
3041
|
+
if (this.drawSession && this.canvas) {
|
|
3042
|
+
try {
|
|
3043
|
+
exitDrawModeImpl(this.buildDrawControllerContext());
|
|
3044
|
+
}
|
|
3045
|
+
catch {
|
|
3046
|
+
}
|
|
3047
|
+
this.drawSession = null;
|
|
3048
|
+
}
|
|
2047
3049
|
if (this.canvas) {
|
|
2048
3050
|
try {
|
|
2049
3051
|
void Promise.resolve(this.canvas.dispose()).catch(() => {
|
|
@@ -2059,6 +3061,7 @@ export class ImageEditor {
|
|
|
2059
3061
|
this.currentImageMimeType = null;
|
|
2060
3062
|
this.lastMask = null;
|
|
2061
3063
|
this.maskCounter = 0;
|
|
3064
|
+
this.annotationCounter = 0;
|
|
2062
3065
|
this.currentScale = 1;
|
|
2063
3066
|
this.currentRotation = 0;
|
|
2064
3067
|
this.baseImageScale = 1;
|