js-draw 0.11.0 → 0.11.1
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/CHANGELOG.md +5 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +9 -2
- package/dist/src/Editor.js +24 -14
- package/dist/src/EditorImage.js +1 -1
- package/dist/src/tools/Eraser.d.ts +1 -1
- package/dist/src/tools/Eraser.js +16 -18
- package/dist/src/tools/SelectionTool/Selection.d.ts +2 -3
- package/dist/src/tools/SelectionTool/Selection.js +63 -26
- package/dist/src/tools/SelectionTool/SelectionTool.js +9 -0
- package/package.json +1 -1
- package/src/Editor.ts +28 -15
- package/src/EditorImage.ts +1 -1
- package/src/tools/Eraser.ts +19 -15
- package/src/tools/SelectionTool/Selection.ts +62 -22
- package/src/tools/SelectionTool/SelectionTool.ts +12 -0
package/dist/src/Editor.d.ts
CHANGED
@@ -174,7 +174,7 @@ export declare class Editor {
|
|
174
174
|
* editor.dispatchNoAnnounce(editor.viewport.zoomTo(someRectangle), addToHistory);
|
175
175
|
* ```
|
176
176
|
*/
|
177
|
-
dispatchNoAnnounce(command: Command, addToHistory?: boolean): void
|
177
|
+
dispatchNoAnnounce(command: Command, addToHistory?: boolean): void | Promise<void>;
|
178
178
|
/**
|
179
179
|
* Apply a large transformation in chunks.
|
180
180
|
* If `apply` is `false`, the commands are unapplied.
|
@@ -186,8 +186,15 @@ export declare class Editor {
|
|
186
186
|
asyncUnapplyCommands(commands: Command[], chunkSize: number): Promise<void>;
|
187
187
|
private announceUndoCallback;
|
188
188
|
private announceRedoCallback;
|
189
|
+
private nextRerenderListeners;
|
189
190
|
private rerenderQueued;
|
190
|
-
|
191
|
+
/**
|
192
|
+
* Schedule a re-render for some time in the near future. Does not schedule an additional
|
193
|
+
* re-render if a re-render is already queued.
|
194
|
+
*
|
195
|
+
* @returns a promise that resolves when
|
196
|
+
*/
|
197
|
+
queueRerender(): Promise<void>;
|
191
198
|
rerender(showImageBounds?: boolean): void;
|
192
199
|
drawWetInk(...path: RenderablePathSpec[]): void;
|
193
200
|
clearWetInk(): void;
|
package/dist/src/Editor.js
CHANGED
@@ -84,6 +84,8 @@ export class Editor {
|
|
84
84
|
this.announceRedoCallback = (command) => {
|
85
85
|
this.announceForAccessibility(this.localization.redoAnnouncement(command.description(this, this.localization)));
|
86
86
|
};
|
87
|
+
// Listeners to be called once at the end of the next re-render.
|
88
|
+
this.nextRerenderListeners = [];
|
87
89
|
this.rerenderQueued = false;
|
88
90
|
this.localization = Object.assign(Object.assign({}, getLocalizationTable()), settings.localization);
|
89
91
|
// Fill default settings.
|
@@ -474,13 +476,9 @@ export class Editor {
|
|
474
476
|
}
|
475
477
|
/** `apply` a command. `command` will be announced for accessibility. */
|
476
478
|
dispatch(command, addToHistory = true) {
|
477
|
-
|
478
|
-
const apply = false; // Don't double-apply
|
479
|
-
this.history.push(command, apply);
|
480
|
-
}
|
481
|
-
const applyResult = command.apply(this);
|
479
|
+
const dispatchResult = this.dispatchNoAnnounce(command, addToHistory);
|
482
480
|
this.announceForAccessibility(command.description(this, this.localization));
|
483
|
-
return
|
481
|
+
return dispatchResult;
|
484
482
|
}
|
485
483
|
/**
|
486
484
|
* Dispatches a command without announcing it. By default, does not add to history.
|
@@ -499,11 +497,10 @@ export class Editor {
|
|
499
497
|
*/
|
500
498
|
dispatchNoAnnounce(command, addToHistory = false) {
|
501
499
|
if (addToHistory) {
|
502
|
-
|
503
|
-
|
504
|
-
else {
|
505
|
-
command.apply(this);
|
500
|
+
const apply = false; // Don't double-apply
|
501
|
+
this.history.push(command, apply);
|
506
502
|
}
|
503
|
+
return command.apply(this);
|
507
504
|
}
|
508
505
|
/**
|
509
506
|
* Apply a large transformation in chunks.
|
@@ -546,16 +543,27 @@ export class Editor {
|
|
546
543
|
asyncUnapplyCommands(commands, chunkSize) {
|
547
544
|
return this.asyncApplyOrUnapplyCommands(commands, false, chunkSize);
|
548
545
|
}
|
549
|
-
|
550
|
-
|
546
|
+
/**
|
547
|
+
* Schedule a re-render for some time in the near future. Does not schedule an additional
|
548
|
+
* re-render if a re-render is already queued.
|
549
|
+
*
|
550
|
+
* @returns a promise that resolves when
|
551
|
+
*/
|
551
552
|
queueRerender() {
|
552
553
|
if (!this.rerenderQueued) {
|
553
554
|
this.rerenderQueued = true;
|
554
555
|
requestAnimationFrame(() => {
|
555
|
-
|
556
|
-
|
556
|
+
// If .rerender was called manually, we might not need to
|
557
|
+
// re-render.
|
558
|
+
if (this.rerenderQueued) {
|
559
|
+
this.rerender();
|
560
|
+
this.rerenderQueued = false;
|
561
|
+
}
|
557
562
|
});
|
558
563
|
}
|
564
|
+
return new Promise(resolve => {
|
565
|
+
this.nextRerenderListeners.push(() => resolve());
|
566
|
+
});
|
559
567
|
}
|
560
568
|
rerender(showImageBounds = true) {
|
561
569
|
this.display.startRerender();
|
@@ -572,6 +580,8 @@ export class Editor {
|
|
572
580
|
renderer.drawRect(this.importExportViewport.visibleRect, exportRectStrokeWidth, exportRectFill);
|
573
581
|
}
|
574
582
|
this.rerenderQueued = false;
|
583
|
+
this.nextRerenderListeners.forEach(listener => listener());
|
584
|
+
this.nextRerenderListeners = [];
|
575
585
|
}
|
576
586
|
drawWetInk(...path) {
|
577
587
|
for (const part of path) {
|
package/dist/src/EditorImage.js
CHANGED
@@ -2,7 +2,7 @@ var _a;
|
|
2
2
|
import AbstractComponent from './components/AbstractComponent';
|
3
3
|
import Rect2 from './math/Rect2';
|
4
4
|
import SerializableCommand from './commands/SerializableCommand';
|
5
|
-
// @internal
|
5
|
+
// @internal Sort by z-index, low to high
|
6
6
|
export const sortLeavesByZIndex = (leaves) => {
|
7
7
|
leaves.sort((a, b) => a.getContent().getZIndex() - b.getContent().getZIndex());
|
8
8
|
};
|
@@ -4,8 +4,8 @@ import Editor from '../Editor';
|
|
4
4
|
export default class Eraser extends BaseTool {
|
5
5
|
private editor;
|
6
6
|
private lastPoint;
|
7
|
-
private command;
|
8
7
|
private toRemove;
|
8
|
+
private partialCommands;
|
9
9
|
constructor(editor: Editor, description: string);
|
10
10
|
onPointerDown(event: PointerEvt): boolean;
|
11
11
|
onPointerMove(event: PointerEvt): void;
|
package/dist/src/tools/Eraser.js
CHANGED
@@ -6,7 +6,8 @@ export default class Eraser extends BaseTool {
|
|
6
6
|
constructor(editor, description) {
|
7
7
|
super(editor.notifier, description);
|
8
8
|
this.editor = editor;
|
9
|
-
|
9
|
+
// Commands that each remove one element
|
10
|
+
this.partialCommands = [];
|
10
11
|
}
|
11
12
|
onPointerDown(event) {
|
12
13
|
if (event.allPointers.length === 1 || event.current.device === PointerDevice.Eraser) {
|
@@ -17,35 +18,32 @@ export default class Eraser extends BaseTool {
|
|
17
18
|
return false;
|
18
19
|
}
|
19
20
|
onPointerMove(event) {
|
20
|
-
var _a;
|
21
21
|
const currentPoint = event.current.canvasPos;
|
22
22
|
if (currentPoint.minus(this.lastPoint).magnitude() === 0) {
|
23
23
|
return;
|
24
24
|
}
|
25
25
|
const line = new LineSegment2(this.lastPoint, currentPoint);
|
26
26
|
const region = line.bbox;
|
27
|
-
|
28
|
-
this.toRemove.push(...this.editor.image
|
29
|
-
.getElementsIntersectingRegion(region).filter(component => {
|
27
|
+
const intersectingElems = this.editor.image.getElementsIntersectingRegion(region).filter(component => {
|
30
28
|
return component.intersects(line);
|
31
|
-
})
|
32
|
-
|
33
|
-
this.
|
34
|
-
|
29
|
+
});
|
30
|
+
// Remove any intersecting elements.
|
31
|
+
this.toRemove.push(...intersectingElems);
|
32
|
+
// Create new Erase commands for the now-to-be-erased elements and apply them.
|
33
|
+
const newPartialCommands = intersectingElems.map(elem => new Erase([elem]));
|
34
|
+
newPartialCommands.forEach(cmd => cmd.apply(this.editor));
|
35
|
+
this.partialCommands.push(...newPartialCommands);
|
35
36
|
this.lastPoint = currentPoint;
|
36
37
|
}
|
37
38
|
onPointerUp(_event) {
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
this.editor.dispatch(
|
39
|
+
if (this.toRemove.length > 0) {
|
40
|
+
// Undo commands for each individual component and unite into a single command.
|
41
|
+
this.partialCommands.forEach(cmd => cmd.unapply(this.editor));
|
42
|
+
const command = new Erase(this.toRemove);
|
43
|
+
this.editor.dispatch(command); // dispatch: Makes undo-able.
|
43
44
|
}
|
44
|
-
this.command = null;
|
45
45
|
}
|
46
46
|
onGestureCancel() {
|
47
|
-
|
48
|
-
(_a = this.command) === null || _a === void 0 ? void 0 : _a.unapply(this.editor);
|
49
|
-
this.command = null;
|
47
|
+
this.partialCommands.forEach(cmd => cmd.unapply(this.editor));
|
50
48
|
}
|
51
49
|
}
|
@@ -14,7 +14,6 @@ export default class Selection {
|
|
14
14
|
private originalRegion;
|
15
15
|
private transformers;
|
16
16
|
private transform;
|
17
|
-
private transformCommands;
|
18
17
|
private selectedElems;
|
19
18
|
private container;
|
20
19
|
private backgroundElem;
|
@@ -28,7 +27,6 @@ export default class Selection {
|
|
28
27
|
get preTransformedScreenRegionRotation(): number;
|
29
28
|
get screenRegion(): Rect2;
|
30
29
|
get screenRegionRotation(): number;
|
31
|
-
private computeTransformCommands;
|
32
30
|
setTransform(transform: Mat33, preview?: boolean): void;
|
33
31
|
finalizeTransform(): void;
|
34
32
|
private static ApplyTransformationCommand;
|
@@ -38,13 +36,14 @@ export default class Selection {
|
|
38
36
|
getMinCanvasSize(): number;
|
39
37
|
getSelectedItemCount(): number;
|
40
38
|
updateUI(): void;
|
39
|
+
private addRemoveSelectionFromImage;
|
41
40
|
private targetHandle;
|
42
41
|
private backgroundDragging;
|
43
42
|
onDragStart(pointer: Pointer, target: EventTarget): boolean;
|
44
43
|
onDragUpdate(pointer: Pointer): void;
|
45
44
|
onDragEnd(): void;
|
46
45
|
onDragCancel(): void;
|
47
|
-
scrollTo(): void
|
46
|
+
scrollTo(): Promise<void>;
|
48
47
|
deleteSelectedObjects(): Command;
|
49
48
|
duplicateSelectedObjects(): Command;
|
50
49
|
addTo(elem: HTMLElement): void;
|
@@ -22,13 +22,14 @@ import Erase from '../../commands/Erase';
|
|
22
22
|
import Duplicate from '../../commands/Duplicate';
|
23
23
|
import { DragTransformer, ResizeTransformer, RotateTransformer } from './TransformMode';
|
24
24
|
import { ResizeMode } from './types';
|
25
|
+
import EditorImage from '../../EditorImage';
|
25
26
|
const updateChunkSize = 100;
|
27
|
+
const maxPreviewElemCount = 500;
|
26
28
|
// @internal
|
27
29
|
export default class Selection {
|
28
30
|
constructor(startPoint, editor) {
|
29
31
|
this.editor = editor;
|
30
32
|
this.transform = Mat33.identity;
|
31
|
-
this.transformCommands = [];
|
32
33
|
this.selectedElems = [];
|
33
34
|
this.hasParent = true;
|
34
35
|
this.targetHandle = null;
|
@@ -89,28 +90,19 @@ export default class Selection {
|
|
89
90
|
get screenRegionRotation() {
|
90
91
|
return this.regionRotation + this.editor.viewport.getRotationAngle();
|
91
92
|
}
|
92
|
-
computeTransformCommands() {
|
93
|
-
return this.selectedElems.map(elem => {
|
94
|
-
return elem.transformBy(this.transform);
|
95
|
-
});
|
96
|
-
}
|
97
93
|
// Applies, previews, but doesn't finalize the given transformation.
|
98
94
|
setTransform(transform, preview = true) {
|
99
95
|
this.transform = transform;
|
100
96
|
if (preview && this.hasParent) {
|
101
|
-
this.previewTransformCmds();
|
102
97
|
this.scrollTo();
|
98
|
+
this.previewTransformCmds();
|
103
99
|
}
|
104
100
|
}
|
105
101
|
// Applies the current transformation to the selection
|
106
102
|
finalizeTransform() {
|
107
|
-
this.transformCommands.forEach(cmd => {
|
108
|
-
cmd.unapply(this.editor);
|
109
|
-
});
|
110
103
|
const fullTransform = this.transform;
|
111
104
|
const selectedElems = this.selectedElems;
|
112
105
|
// Reset for the next drag
|
113
|
-
this.transformCommands = [];
|
114
106
|
this.originalRegion = this.originalRegion.transformedBoundingBox(this.transform);
|
115
107
|
this.transform = Mat33.identity;
|
116
108
|
// Make the commands undo-able
|
@@ -119,13 +111,19 @@ export default class Selection {
|
|
119
111
|
// Preview the effects of the current transformation on the selection
|
120
112
|
previewTransformCmds() {
|
121
113
|
// Don't render what we're moving if it's likely to be slow.
|
122
|
-
if (this.selectedElems.length >
|
114
|
+
if (this.selectedElems.length > maxPreviewElemCount) {
|
123
115
|
this.updateUI();
|
124
116
|
return;
|
125
117
|
}
|
126
|
-
this.
|
127
|
-
|
128
|
-
|
118
|
+
const wetInkRenderer = this.editor.display.getWetInkRenderer();
|
119
|
+
wetInkRenderer.clear();
|
120
|
+
wetInkRenderer.pushTransform(this.transform);
|
121
|
+
const viewportVisibleRect = this.editor.viewport.visibleRect;
|
122
|
+
const visibleRect = viewportVisibleRect.transformedBoundingBox(this.transform.inverse());
|
123
|
+
for (const elem of this.selectedElems) {
|
124
|
+
elem.render(wetInkRenderer, visibleRect);
|
125
|
+
}
|
126
|
+
wetInkRenderer.popTransform();
|
129
127
|
this.updateUI();
|
130
128
|
}
|
131
129
|
// Find the objects corresponding to this in the document,
|
@@ -208,7 +206,39 @@ export default class Selection {
|
|
208
206
|
handle.updatePosition();
|
209
207
|
}
|
210
208
|
}
|
209
|
+
// Add/remove the contents of this' seleciton from the editor.
|
210
|
+
// Used to prevent previewed content from looking like duplicate content
|
211
|
+
// while dragging.
|
212
|
+
//
|
213
|
+
// Does nothing if a large number of elements are selected (and so modifying
|
214
|
+
// the editor image is likely to be slow.)
|
215
|
+
//
|
216
|
+
// If removed from the image, selected elements are drawn as wet ink.
|
217
|
+
addRemoveSelectionFromImage(inImage) {
|
218
|
+
return __awaiter(this, void 0, void 0, function* () {
|
219
|
+
// Don't hide elements if doing so will be slow.
|
220
|
+
if (!inImage && this.selectedElems.length > maxPreviewElemCount) {
|
221
|
+
return;
|
222
|
+
}
|
223
|
+
for (const elem of this.selectedElems) {
|
224
|
+
const parent = this.editor.image.findParent(elem);
|
225
|
+
if (!inImage) {
|
226
|
+
parent === null || parent === void 0 ? void 0 : parent.remove();
|
227
|
+
}
|
228
|
+
// If we're making things visible and the selected object wasn't previously
|
229
|
+
// visible,
|
230
|
+
else if (!parent) {
|
231
|
+
EditorImage.addElement(elem).apply(this.editor);
|
232
|
+
}
|
233
|
+
}
|
234
|
+
yield this.editor.queueRerender();
|
235
|
+
if (!inImage) {
|
236
|
+
this.previewTransformCmds();
|
237
|
+
}
|
238
|
+
});
|
239
|
+
}
|
211
240
|
onDragStart(pointer, target) {
|
241
|
+
void this.addRemoveSelectionFromImage(false);
|
212
242
|
for (const handle of this.handles) {
|
213
243
|
if (handle.isTarget(target)) {
|
214
244
|
handle.handleDragStart(pointer);
|
@@ -230,7 +260,6 @@ export default class Selection {
|
|
230
260
|
if (this.targetHandle) {
|
231
261
|
this.targetHandle.handleDragUpdate(pointer);
|
232
262
|
}
|
233
|
-
this.updateUI();
|
234
263
|
}
|
235
264
|
onDragEnd() {
|
236
265
|
if (this.backgroundDragging) {
|
@@ -239,6 +268,7 @@ export default class Selection {
|
|
239
268
|
else if (this.targetHandle) {
|
240
269
|
this.targetHandle.handleDragEnd();
|
241
270
|
}
|
271
|
+
this.addRemoveSelectionFromImage(true);
|
242
272
|
this.backgroundDragging = false;
|
243
273
|
this.targetHandle = null;
|
244
274
|
this.updateUI();
|
@@ -247,19 +277,26 @@ export default class Selection {
|
|
247
277
|
this.backgroundDragging = false;
|
248
278
|
this.targetHandle = null;
|
249
279
|
this.setTransform(Mat33.identity);
|
280
|
+
this.addRemoveSelectionFromImage(true);
|
250
281
|
}
|
251
282
|
// Scroll the viewport to this. Does not zoom
|
252
283
|
scrollTo() {
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
284
|
+
return __awaiter(this, void 0, void 0, function* () {
|
285
|
+
if (this.selectedElems.length === 0) {
|
286
|
+
return;
|
287
|
+
}
|
288
|
+
const screenRect = new Rect2(0, 0, this.editor.display.width, this.editor.display.height);
|
289
|
+
if (!screenRect.containsPoint(this.screenRegion.center)) {
|
290
|
+
const closestPoint = screenRect.getClosestPointOnBoundaryTo(this.screenRegion.center);
|
291
|
+
const screenDelta = this.screenRegion.center.minus(closestPoint);
|
292
|
+
const delta = this.editor.viewport.screenToCanvasTransform.transformVec3(screenDelta);
|
293
|
+
yield this.editor.dispatchNoAnnounce(Viewport.transformBy(Mat33.translation(delta.times(-1))), false);
|
294
|
+
// Re-renders clear wet ink, so we need to re-draw the preview
|
295
|
+
// after the full re-render.
|
296
|
+
yield this.editor.queueRerender();
|
297
|
+
this.previewTransformCmds();
|
298
|
+
}
|
299
|
+
});
|
263
300
|
}
|
264
301
|
deleteSelectedObjects() {
|
265
302
|
return new Erase(this.selectedElems);
|
@@ -311,6 +311,15 @@ export default class SelectionTool extends BaseTool {
|
|
311
311
|
setSelection(objects) {
|
312
312
|
// Only select selectable objects.
|
313
313
|
objects = objects.filter(obj => obj.isSelectable());
|
314
|
+
// Sort by z-index
|
315
|
+
objects.sort((a, b) => a.getZIndex() - b.getZIndex());
|
316
|
+
// Remove duplicates
|
317
|
+
objects = objects.filter((current, idx) => {
|
318
|
+
if (idx > 0) {
|
319
|
+
return current !== objects[idx - 1];
|
320
|
+
}
|
321
|
+
return true;
|
322
|
+
});
|
314
323
|
let bbox = null;
|
315
324
|
for (const object of objects) {
|
316
325
|
if (bbox) {
|
package/package.json
CHANGED
package/src/Editor.ts
CHANGED
@@ -626,15 +626,10 @@ export class Editor {
|
|
626
626
|
|
627
627
|
/** `apply` a command. `command` will be announced for accessibility. */
|
628
628
|
public dispatch(command: Command, addToHistory: boolean = true) {
|
629
|
-
|
630
|
-
const apply = false; // Don't double-apply
|
631
|
-
this.history.push(command, apply);
|
632
|
-
}
|
633
|
-
|
634
|
-
const applyResult = command.apply(this);
|
629
|
+
const dispatchResult = this.dispatchNoAnnounce(command, addToHistory);
|
635
630
|
this.announceForAccessibility(command.description(this, this.localization));
|
636
631
|
|
637
|
-
return
|
632
|
+
return dispatchResult;
|
638
633
|
}
|
639
634
|
|
640
635
|
/**
|
@@ -654,10 +649,11 @@ export class Editor {
|
|
654
649
|
*/
|
655
650
|
public dispatchNoAnnounce(command: Command, addToHistory: boolean = false) {
|
656
651
|
if (addToHistory) {
|
657
|
-
|
658
|
-
|
659
|
-
command.apply(this);
|
652
|
+
const apply = false; // Don't double-apply
|
653
|
+
this.history.push(command, apply);
|
660
654
|
}
|
655
|
+
|
656
|
+
return command.apply(this);
|
661
657
|
}
|
662
658
|
|
663
659
|
/**
|
@@ -714,17 +710,32 @@ export class Editor {
|
|
714
710
|
this.announceForAccessibility(this.localization.redoAnnouncement(command.description(this, this.localization)));
|
715
711
|
};
|
716
712
|
|
713
|
+
// Listeners to be called once at the end of the next re-render.
|
714
|
+
private nextRerenderListeners: Array<()=> void> = [];
|
717
715
|
private rerenderQueued: boolean = false;
|
718
|
-
|
719
|
-
|
720
|
-
|
716
|
+
|
717
|
+
/**
|
718
|
+
* Schedule a re-render for some time in the near future. Does not schedule an additional
|
719
|
+
* re-render if a re-render is already queued.
|
720
|
+
*
|
721
|
+
* @returns a promise that resolves when
|
722
|
+
*/
|
723
|
+
public queueRerender(): Promise<void> {
|
721
724
|
if (!this.rerenderQueued) {
|
722
725
|
this.rerenderQueued = true;
|
723
726
|
requestAnimationFrame(() => {
|
724
|
-
|
725
|
-
|
727
|
+
// If .rerender was called manually, we might not need to
|
728
|
+
// re-render.
|
729
|
+
if (this.rerenderQueued) {
|
730
|
+
this.rerender();
|
731
|
+
this.rerenderQueued = false;
|
732
|
+
}
|
726
733
|
});
|
727
734
|
}
|
735
|
+
|
736
|
+
return new Promise(resolve => {
|
737
|
+
this.nextRerenderListeners.push(() => resolve());
|
738
|
+
});
|
728
739
|
}
|
729
740
|
|
730
741
|
public rerender(showImageBounds: boolean = true) {
|
@@ -751,6 +762,8 @@ export class Editor {
|
|
751
762
|
}
|
752
763
|
|
753
764
|
this.rerenderQueued = false;
|
765
|
+
this.nextRerenderListeners.forEach(listener => listener());
|
766
|
+
this.nextRerenderListeners = [];
|
754
767
|
}
|
755
768
|
|
756
769
|
public drawWetInk(...path: RenderablePathSpec[]) {
|
package/src/EditorImage.ts
CHANGED
@@ -7,7 +7,7 @@ import { EditorLocalization } from './localization';
|
|
7
7
|
import RenderingCache from './rendering/caching/RenderingCache';
|
8
8
|
import SerializableCommand from './commands/SerializableCommand';
|
9
9
|
|
10
|
-
// @internal
|
10
|
+
// @internal Sort by z-index, low to high
|
11
11
|
export const sortLeavesByZIndex = (leaves: Array<ImageNode>) => {
|
12
12
|
leaves.sort((a, b) => a.getContent()!.getZIndex() - b.getContent()!.getZIndex());
|
13
13
|
};
|
package/src/tools/Eraser.ts
CHANGED
@@ -9,9 +9,11 @@ import { PointerDevice } from '../Pointer';
|
|
9
9
|
|
10
10
|
export default class Eraser extends BaseTool {
|
11
11
|
private lastPoint: Point2;
|
12
|
-
private command: Erase|null = null;
|
13
12
|
private toRemove: AbstractComponent[];
|
14
13
|
|
14
|
+
// Commands that each remove one element
|
15
|
+
private partialCommands: Erase[] = [];
|
16
|
+
|
15
17
|
public constructor(private editor: Editor, description: string) {
|
16
18
|
super(editor.notifier, description);
|
17
19
|
}
|
@@ -35,31 +37,33 @@ export default class Eraser extends BaseTool {
|
|
35
37
|
const line = new LineSegment2(this.lastPoint, currentPoint);
|
36
38
|
const region = line.bbox;
|
37
39
|
|
40
|
+
const intersectingElems = this.editor.image.getElementsIntersectingRegion(region).filter(component => {
|
41
|
+
return component.intersects(line);
|
42
|
+
});
|
43
|
+
|
38
44
|
// Remove any intersecting elements.
|
39
|
-
this.toRemove.push(...
|
40
|
-
|
41
|
-
|
42
|
-
|
45
|
+
this.toRemove.push(...intersectingElems);
|
46
|
+
|
47
|
+
// Create new Erase commands for the now-to-be-erased elements and apply them.
|
48
|
+
const newPartialCommands = intersectingElems.map(elem => new Erase([ elem ]));
|
49
|
+
newPartialCommands.forEach(cmd => cmd.apply(this.editor));
|
43
50
|
|
44
|
-
this.
|
45
|
-
this.command = new Erase(this.toRemove);
|
46
|
-
this.command.apply(this.editor);
|
51
|
+
this.partialCommands.push(...newPartialCommands);
|
47
52
|
|
48
53
|
this.lastPoint = currentPoint;
|
49
54
|
}
|
50
55
|
|
51
56
|
public onPointerUp(_event: PointerEvt): void {
|
52
|
-
if (this.
|
53
|
-
|
57
|
+
if (this.toRemove.length > 0) {
|
58
|
+
// Undo commands for each individual component and unite into a single command.
|
59
|
+
this.partialCommands.forEach(cmd => cmd.unapply(this.editor));
|
54
60
|
|
55
|
-
|
56
|
-
this.editor.dispatch(
|
61
|
+
const command = new Erase(this.toRemove);
|
62
|
+
this.editor.dispatch(command); // dispatch: Makes undo-able.
|
57
63
|
}
|
58
|
-
this.command = null;
|
59
64
|
}
|
60
65
|
|
61
66
|
public onGestureCancel(): void {
|
62
|
-
this.
|
63
|
-
this.command = null;
|
67
|
+
this.partialCommands.forEach(cmd => cmd.unapply(this.editor));
|
64
68
|
}
|
65
69
|
}
|