js-draw 0.22.1 → 0.23.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/CHANGELOG.md +8 -0
- package/dist/bundle.js +2 -2
- package/dist/bundledStyles.js +1 -1
- package/dist/cjs/src/Pointer.d.ts +3 -1
- package/dist/cjs/src/Pointer.js +27 -2
- package/dist/cjs/src/commands/Duplicate.d.ts +24 -0
- package/dist/cjs/src/commands/Duplicate.js +26 -0
- package/dist/cjs/src/commands/Erase.d.ts +20 -0
- package/dist/cjs/src/commands/Erase.js +20 -0
- package/dist/cjs/src/commands/invertCommand.js +6 -0
- package/dist/cjs/src/commands/uniteCommands.js +9 -13
- package/dist/cjs/src/components/BackgroundComponent.js +9 -2
- package/dist/cjs/src/components/lib.d.ts +1 -1
- package/dist/cjs/src/components/lib.js +2 -2
- package/dist/cjs/src/components/localization.d.ts +1 -0
- package/dist/cjs/src/components/localization.js +1 -0
- package/dist/cjs/src/math/Vec2.d.ts +20 -0
- package/dist/cjs/src/math/Vec2.js +20 -0
- package/dist/cjs/src/rendering/renderers/AbstractRenderer.d.ts +17 -0
- package/dist/cjs/src/rendering/renderers/AbstractRenderer.js +21 -3
- package/dist/cjs/src/rendering/renderers/CanvasRenderer.js +12 -7
- package/dist/cjs/src/toolbar/widgets/BaseWidget.d.ts +1 -0
- package/dist/cjs/src/toolbar/widgets/BaseWidget.js +22 -4
- package/dist/cjs/src/tools/Pen.d.ts +4 -0
- package/dist/cjs/src/tools/Pen.js +24 -1
- package/dist/cjs/src/tools/SelectionTool/SelectionTool.d.ts +1 -0
- package/dist/cjs/src/tools/SelectionTool/SelectionTool.js +8 -0
- package/dist/cjs/src/util/waitForAll.d.ts +6 -0
- package/dist/cjs/src/util/waitForAll.js +17 -0
- package/dist/mjs/src/Pointer.d.ts +3 -1
- package/dist/mjs/src/Pointer.mjs +27 -2
- package/dist/mjs/src/commands/Duplicate.d.ts +24 -0
- package/dist/mjs/src/commands/Duplicate.mjs +26 -0
- package/dist/mjs/src/commands/Erase.d.ts +20 -0
- package/dist/mjs/src/commands/Erase.mjs +20 -0
- package/dist/mjs/src/commands/invertCommand.mjs +6 -0
- package/dist/mjs/src/commands/uniteCommands.mjs +9 -13
- package/dist/mjs/src/components/BackgroundComponent.mjs +9 -2
- package/dist/mjs/src/components/lib.d.ts +1 -1
- package/dist/mjs/src/components/lib.mjs +3 -1
- package/dist/mjs/src/components/localization.d.ts +1 -0
- package/dist/mjs/src/components/localization.mjs +1 -0
- package/dist/mjs/src/math/Vec2.d.ts +20 -0
- package/dist/mjs/src/math/Vec2.mjs +20 -0
- package/dist/mjs/src/rendering/renderers/AbstractRenderer.d.ts +17 -0
- package/dist/mjs/src/rendering/renderers/AbstractRenderer.mjs +21 -3
- package/dist/mjs/src/rendering/renderers/CanvasRenderer.mjs +12 -7
- package/dist/mjs/src/toolbar/widgets/BaseWidget.d.ts +1 -0
- package/dist/mjs/src/toolbar/widgets/BaseWidget.mjs +22 -4
- package/dist/mjs/src/tools/Pen.d.ts +4 -0
- package/dist/mjs/src/tools/Pen.mjs +24 -1
- package/dist/mjs/src/tools/SelectionTool/SelectionTool.d.ts +1 -0
- package/dist/mjs/src/tools/SelectionTool/SelectionTool.mjs +8 -0
- package/dist/mjs/src/util/waitForAll.d.ts +6 -0
- package/dist/mjs/src/util/waitForAll.mjs +15 -0
- package/package.json +9 -9
- package/src/toolbar/toolbar.css +35 -1
@@ -180,14 +180,19 @@ class CanvasRenderer extends AbstractRenderer_1.default {
|
|
180
180
|
super.startObject(boundingBox);
|
181
181
|
this.currentObjectBBox = boundingBox;
|
182
182
|
if (!this.ignoringObject && clip) {
|
183
|
-
|
184
|
-
|
185
|
-
this.
|
186
|
-
|
187
|
-
|
188
|
-
this.ctx.
|
183
|
+
// Don't clip if it would only remove content already trimmed by
|
184
|
+
// the edge of the screen.
|
185
|
+
const clippedIsOutsideScreen = boundingBox.containsRect(this.getViewport().visibleRect);
|
186
|
+
if (!clippedIsOutsideScreen) {
|
187
|
+
this.clipLevels.push(this.objectLevel);
|
188
|
+
this.ctx.save();
|
189
|
+
this.ctx.beginPath();
|
190
|
+
for (const corner of boundingBox.corners) {
|
191
|
+
const screenCorner = this.canvasToScreen(corner);
|
192
|
+
this.ctx.lineTo(screenCorner.x, screenCorner.y);
|
193
|
+
}
|
194
|
+
this.ctx.clip();
|
189
195
|
}
|
190
|
-
this.ctx.clip();
|
191
196
|
}
|
192
197
|
}
|
193
198
|
endObject() {
|
@@ -46,6 +46,7 @@ export default abstract class BaseWidget {
|
|
46
46
|
protected updateIcon(): void;
|
47
47
|
setDisabled(disabled: boolean): void;
|
48
48
|
setSelected(selected: boolean): void;
|
49
|
+
private hideDropdownTimeout;
|
49
50
|
protected setDropdownVisible(visible: boolean): void;
|
50
51
|
canBeInOverflowMenu(): boolean;
|
51
52
|
getButtonWidth(): number;
|
@@ -28,6 +28,7 @@ class BaseWidget {
|
|
28
28
|
this.subWidgets = {};
|
29
29
|
this.toplevel = true;
|
30
30
|
this.toolbarWidgetToggleListener = null;
|
31
|
+
this.hideDropdownTimeout = null;
|
31
32
|
this.localizationTable = localizationTable !== null && localizationTable !== void 0 ? localizationTable : editor.localization;
|
32
33
|
this.icon = null;
|
33
34
|
this.container = document.createElement('div');
|
@@ -235,6 +236,13 @@ class BaseWidget {
|
|
235
236
|
if (currentlyVisible === visible) {
|
236
237
|
return;
|
237
238
|
}
|
239
|
+
// If waiting to hide the dropdown, cancel it.
|
240
|
+
if (this.hideDropdownTimeout) {
|
241
|
+
clearTimeout(this.hideDropdownTimeout);
|
242
|
+
this.hideDropdownTimeout = null;
|
243
|
+
this.dropdownContainer.classList.remove('hiding');
|
244
|
+
}
|
245
|
+
const animationDuration = 150; // ms
|
238
246
|
if (visible) {
|
239
247
|
this.dropdownContainer.classList.remove('hidden');
|
240
248
|
this.container.classList.add('dropdownVisible');
|
@@ -245,10 +253,20 @@ class BaseWidget {
|
|
245
253
|
});
|
246
254
|
}
|
247
255
|
else {
|
248
|
-
this.dropdownContainer.classList.add('hidden');
|
249
256
|
this.container.classList.remove('dropdownVisible');
|
250
257
|
this.editor.announceForAccessibility(this.localizationTable.dropdownHidden(this.getTitle()));
|
258
|
+
this.dropdownContainer.classList.add('hiding');
|
259
|
+
// Hide the dropdown *slightly* before the animation finishes. This
|
260
|
+
// prevents flickering in some browsers.
|
261
|
+
const hideDelay = animationDuration * 0.95;
|
262
|
+
this.hideDropdownTimeout = setTimeout(() => {
|
263
|
+
this.dropdownContainer.classList.add('hidden');
|
264
|
+
this.dropdownContainer.classList.remove('hiding');
|
265
|
+
}, hideDelay);
|
251
266
|
}
|
267
|
+
// Animate
|
268
|
+
const animationName = `var(--dropdown-${visible ? 'show' : 'hide'}-animation)`;
|
269
|
+
this.dropdownContainer.style.animation = `${animationDuration}ms ease ${animationName}`;
|
252
270
|
this.repositionDropdown();
|
253
271
|
}
|
254
272
|
canBeInOverflowMenu() {
|
@@ -267,11 +285,11 @@ class BaseWidget {
|
|
267
285
|
const dropdownBBox = this.dropdownContainer.getBoundingClientRect();
|
268
286
|
const screenWidth = document.body.clientWidth;
|
269
287
|
if (dropdownBBox.left > screenWidth / 2) {
|
270
|
-
|
271
|
-
|
288
|
+
// Use .translate so as not to conflict with CSS animating the
|
289
|
+
// transform property.
|
290
|
+
this.dropdownContainer.style.translate = `calc(${this.button.clientWidth + 'px'} - 100%) 0`;
|
272
291
|
}
|
273
292
|
else {
|
274
|
-
this.dropdownContainer.style.marginLeft = '';
|
275
293
|
this.dropdownContainer.style.transform = '';
|
276
294
|
}
|
277
295
|
}
|
@@ -14,9 +14,12 @@ export default class Pen extends BaseTool {
|
|
14
14
|
private builderFactory;
|
15
15
|
protected builder: ComponentBuilder | null;
|
16
16
|
private lastPoint;
|
17
|
+
private startPoint;
|
17
18
|
private ctrlKeyPressed;
|
19
|
+
private shiftKeyPressed;
|
18
20
|
constructor(editor: Editor, description: string, style: PenStyle, builderFactory?: ComponentBuilderFactory);
|
19
21
|
private getPressureMultiplier;
|
22
|
+
private xyAxesSnap;
|
20
23
|
protected toStrokePoint(pointer: Pointer): StrokeDataPoint;
|
21
24
|
protected previewStroke(): void;
|
22
25
|
protected addPointToStroke(point: StrokeDataPoint): void;
|
@@ -34,6 +37,7 @@ export default class Pen extends BaseTool {
|
|
34
37
|
getStrokeFactory(): ComponentBuilderFactory;
|
35
38
|
setEnabled(enabled: boolean): void;
|
36
39
|
private isSnappingToGrid;
|
40
|
+
private isAngleLocked;
|
37
41
|
onKeyPress({ key, ctrlKey }: KeyPressEvent): boolean;
|
38
42
|
onKeyUp({ key }: KeyUpEvent): boolean;
|
39
43
|
}
|
@@ -16,14 +16,26 @@ class Pen extends BaseTool_1.default {
|
|
16
16
|
this.builderFactory = builderFactory;
|
17
17
|
this.builder = null;
|
18
18
|
this.lastPoint = null;
|
19
|
+
this.startPoint = null;
|
19
20
|
this.ctrlKeyPressed = false;
|
21
|
+
this.shiftKeyPressed = false;
|
20
22
|
}
|
21
23
|
getPressureMultiplier() {
|
22
24
|
return 1 / this.editor.viewport.getScaleFactor() * this.style.thickness;
|
23
25
|
}
|
26
|
+
// Snap the given pointer to the nearer of the x/y axes.
|
27
|
+
xyAxesSnap(pointer) {
|
28
|
+
if (!this.startPoint) {
|
29
|
+
return pointer;
|
30
|
+
}
|
31
|
+
return pointer.lockedToXYAxes(this.startPoint.pos, this.editor.viewport);
|
32
|
+
}
|
24
33
|
// Converts a `pointer` to a `StrokeDataPoint`.
|
25
34
|
toStrokePoint(pointer) {
|
26
35
|
var _a;
|
36
|
+
if (this.isAngleLocked() && this.lastPoint) {
|
37
|
+
pointer = this.xyAxesSnap(pointer);
|
38
|
+
}
|
27
39
|
if (this.isSnappingToGrid()) {
|
28
40
|
pointer = pointer.snappedToGrid(this.editor.viewport);
|
29
41
|
}
|
@@ -69,7 +81,8 @@ class Pen extends BaseTool_1.default {
|
|
69
81
|
}
|
70
82
|
}
|
71
83
|
if ((allPointers.length === 1 && !isEraser) || anyDeviceIsStylus) {
|
72
|
-
this.
|
84
|
+
this.startPoint = this.toStrokePoint(current);
|
85
|
+
this.builder = this.builderFactory(this.startPoint, this.editor.viewport);
|
73
86
|
return true;
|
74
87
|
}
|
75
88
|
return false;
|
@@ -109,6 +122,7 @@ class Pen extends BaseTool_1.default {
|
|
109
122
|
}
|
110
123
|
}
|
111
124
|
this.builder = null;
|
125
|
+
this.lastPoint = null;
|
112
126
|
this.editor.clearWetInk();
|
113
127
|
}
|
114
128
|
noteUpdated() {
|
@@ -143,6 +157,7 @@ class Pen extends BaseTool_1.default {
|
|
143
157
|
this.ctrlKeyPressed = false;
|
144
158
|
}
|
145
159
|
isSnappingToGrid() { return this.ctrlKeyPressed; }
|
160
|
+
isAngleLocked() { return this.shiftKeyPressed; }
|
146
161
|
onKeyPress({ key, ctrlKey }) {
|
147
162
|
key = key.toLowerCase();
|
148
163
|
let newThickness;
|
@@ -161,6 +176,10 @@ class Pen extends BaseTool_1.default {
|
|
161
176
|
this.ctrlKeyPressed = true;
|
162
177
|
return true;
|
163
178
|
}
|
179
|
+
if (key === 'shift') {
|
180
|
+
this.shiftKeyPressed = true;
|
181
|
+
return true;
|
182
|
+
}
|
164
183
|
// Ctrl+Z: End the stroke so that it can be undone/redone.
|
165
184
|
if (key === 'z' && ctrlKey && this.builder) {
|
166
185
|
this.finalizeStroke();
|
@@ -173,6 +192,10 @@ class Pen extends BaseTool_1.default {
|
|
173
192
|
this.ctrlKeyPressed = false;
|
174
193
|
return true;
|
175
194
|
}
|
195
|
+
if (key === 'shift') {
|
196
|
+
this.shiftKeyPressed = false;
|
197
|
+
return true;
|
198
|
+
}
|
176
199
|
return false;
|
177
200
|
}
|
178
201
|
}
|
@@ -20,6 +20,7 @@ class SelectionTool extends BaseTool_1.default {
|
|
20
20
|
super(editor.notifier, description);
|
21
21
|
this.editor = editor;
|
22
22
|
this.lastEvtTarget = null;
|
23
|
+
this.startPoint = null; // canvas position
|
23
24
|
this.expandingSelectionBox = false;
|
24
25
|
this.shiftKeyPressed = false;
|
25
26
|
this.ctrlKeyPressed = false;
|
@@ -30,6 +31,9 @@ class SelectionTool extends BaseTool_1.default {
|
|
30
31
|
this.handleOverlay.classList.add('handleOverlay');
|
31
32
|
editor.notifier.on(types_1.EditorEventType.ViewportChanged, _data => {
|
32
33
|
var _a;
|
34
|
+
// The selection box could be using the wet ink display if its transformation
|
35
|
+
// hasn't been finalized yet. Clear before updating the UI.
|
36
|
+
this.editor.clearWetInk();
|
33
37
|
(_a = this.selectionBox) === null || _a === void 0 ? void 0 : _a.updateUI();
|
34
38
|
});
|
35
39
|
this.editor.handleKeyEventsFrom(this.handleOverlay);
|
@@ -67,6 +71,7 @@ class SelectionTool extends BaseTool_1.default {
|
|
67
71
|
current = current.snappedToGrid(this.editor.viewport);
|
68
72
|
}
|
69
73
|
if (allPointers.length === 1 && current.isPrimary) {
|
74
|
+
this.startPoint = current.canvasPos;
|
70
75
|
let transforming = false;
|
71
76
|
if (this.lastEvtTarget && this.selectionBox) {
|
72
77
|
if (snapToGrid) {
|
@@ -92,6 +97,9 @@ class SelectionTool extends BaseTool_1.default {
|
|
92
97
|
if (!this.selectionBox)
|
93
98
|
return;
|
94
99
|
let currentPointer = event.current;
|
100
|
+
if (!this.expandingSelectionBox && this.shiftKeyPressed && this.startPoint) {
|
101
|
+
currentPointer = currentPointer.lockedToXYAxes(this.startPoint, this.editor.viewport);
|
102
|
+
}
|
95
103
|
if (this.ctrlKeyPressed) {
|
96
104
|
currentPointer = currentPointer.snappedToGrid(this.editor.viewport);
|
97
105
|
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
/**
|
4
|
+
* Resolves when all given promises have resolved. If no promises are given,
|
5
|
+
* does not return a Promise.
|
6
|
+
*/
|
7
|
+
const waitForAll = (results) => {
|
8
|
+
// If any are Promises...
|
9
|
+
if (results.some(command => command && command['then'])) {
|
10
|
+
// Wait for all commands to finish.
|
11
|
+
return Promise.all(results)
|
12
|
+
// Ensure we return a Promise<void> and not a Promise<void[]>
|
13
|
+
.then(() => { });
|
14
|
+
}
|
15
|
+
return;
|
16
|
+
};
|
17
|
+
exports.default = waitForAll;
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Point2 } from './math/Vec2';
|
1
|
+
import { Point2, Vec2 } from './math/Vec2';
|
2
2
|
import Viewport from './Viewport';
|
3
3
|
export declare enum PointerDevice {
|
4
4
|
Pen = 0,
|
@@ -19,6 +19,8 @@ export default class Pointer {
|
|
19
19
|
readonly timeStamp: number;
|
20
20
|
private constructor();
|
21
21
|
snappedToGrid(viewport: Viewport): Pointer;
|
22
|
+
lockedToXYAxes(originPoint: Vec2, viewport: Viewport): Pointer;
|
23
|
+
withCanvasPosition(canvasPos: Point2, viewport: Viewport): Pointer;
|
22
24
|
static ofEvent(evt: PointerEvent, isDown: boolean, viewport: Viewport, relativeTo?: HTMLElement): Pointer;
|
23
25
|
static ofCanvasPoint(canvasPos: Point2, isDown: boolean, viewport: Viewport, id?: number, device?: PointerDevice, isPrimary?: boolean, pressure?: number | null): Pointer;
|
24
26
|
}
|
package/dist/mjs/src/Pointer.mjs
CHANGED
@@ -36,8 +36,33 @@ export default class Pointer {
|
|
36
36
|
// this.
|
37
37
|
snappedToGrid(viewport) {
|
38
38
|
const snappedCanvasPos = viewport.snapToGrid(this.canvasPos);
|
39
|
-
|
40
|
-
|
39
|
+
return this.withCanvasPosition(snappedCanvasPos, viewport);
|
40
|
+
}
|
41
|
+
// Snap this pointer to the X or Y axis (whichever is closer), where (0,0)
|
42
|
+
// is considered to be at `originPoint`.
|
43
|
+
// @internal
|
44
|
+
lockedToXYAxes(originPoint, viewport) {
|
45
|
+
const current = this.canvasPos;
|
46
|
+
const currentFromStart = current.minus(originPoint);
|
47
|
+
// Determine whether the last point was closer to being on the
|
48
|
+
// x- or y- axis.
|
49
|
+
const projOntoXAxis = Vec2.unitX.times(currentFromStart.x);
|
50
|
+
const projOntoYAxis = Vec2.unitY.times(currentFromStart.y);
|
51
|
+
let pos;
|
52
|
+
if (currentFromStart.dot(projOntoXAxis) > currentFromStart.dot(projOntoYAxis)) {
|
53
|
+
pos = projOntoXAxis;
|
54
|
+
}
|
55
|
+
else {
|
56
|
+
pos = projOntoYAxis;
|
57
|
+
}
|
58
|
+
pos = pos.plus(originPoint);
|
59
|
+
return this.withCanvasPosition(pos, viewport);
|
60
|
+
}
|
61
|
+
// Returns a copy of this pointer with a new position. The screen position is determined
|
62
|
+
// by the given `canvasPos`.
|
63
|
+
withCanvasPosition(canvasPos, viewport) {
|
64
|
+
const screenPos = viewport.canvasToScreen(canvasPos);
|
65
|
+
return new Pointer(screenPos, canvasPos, this.pressure, this.isPrimary, this.down, this.device, this.id, this.timeStamp);
|
41
66
|
}
|
42
67
|
// Creates a Pointer from a DOM event. If `relativeTo` is given, (0, 0) in screen coordinates is
|
43
68
|
// considered the top left of `relativeTo`.
|
@@ -2,6 +2,29 @@ import AbstractComponent from '../components/AbstractComponent';
|
|
2
2
|
import Editor from '../Editor';
|
3
3
|
import { EditorLocalization } from '../localization';
|
4
4
|
import SerializableCommand from './SerializableCommand';
|
5
|
+
/**
|
6
|
+
* A command that duplicates the {@link AbstractComponent}s it's given. This command
|
7
|
+
* is the reverse of an {@link Erase} command.
|
8
|
+
*
|
9
|
+
* @example
|
10
|
+
* ```ts
|
11
|
+
* // Given some editor...
|
12
|
+
*
|
13
|
+
* // Find all elements intersecting the rectangle with top left (0,0) and
|
14
|
+
* // (width,height)=(100,100).
|
15
|
+
* const elems = editor.image.getElementsIntersectingRegion(
|
16
|
+
* new Rect2(0, 0, 100, 100)
|
17
|
+
* );
|
18
|
+
*
|
19
|
+
* // Create a command that, when applied, will duplicate the elements.
|
20
|
+
* const duplicateElems = new Duplicate(elems);
|
21
|
+
*
|
22
|
+
* // Apply the command (and make it undoable)
|
23
|
+
* editor.dispatch(duplicateElems);
|
24
|
+
* ```
|
25
|
+
*
|
26
|
+
* @see {@link Editor.dispatch} {@link EditorImage.getElementsIntersectingRegion}
|
27
|
+
*/
|
5
28
|
export default class Duplicate extends SerializableCommand {
|
6
29
|
private toDuplicate;
|
7
30
|
private duplicates;
|
@@ -9,6 +32,7 @@ export default class Duplicate extends SerializableCommand {
|
|
9
32
|
constructor(toDuplicate: AbstractComponent[]);
|
10
33
|
apply(editor: Editor): void;
|
11
34
|
unapply(editor: Editor): void;
|
35
|
+
onDrop(editor: Editor): void;
|
12
36
|
description(_editor: Editor, localizationTable: EditorLocalization): string;
|
13
37
|
protected serializeToJSON(): string[];
|
14
38
|
}
|
@@ -1,6 +1,29 @@
|
|
1
1
|
import describeComponentList from '../components/util/describeComponentList.mjs';
|
2
2
|
import Erase from './Erase.mjs';
|
3
3
|
import SerializableCommand from './SerializableCommand.mjs';
|
4
|
+
/**
|
5
|
+
* A command that duplicates the {@link AbstractComponent}s it's given. This command
|
6
|
+
* is the reverse of an {@link Erase} command.
|
7
|
+
*
|
8
|
+
* @example
|
9
|
+
* ```ts
|
10
|
+
* // Given some editor...
|
11
|
+
*
|
12
|
+
* // Find all elements intersecting the rectangle with top left (0,0) and
|
13
|
+
* // (width,height)=(100,100).
|
14
|
+
* const elems = editor.image.getElementsIntersectingRegion(
|
15
|
+
* new Rect2(0, 0, 100, 100)
|
16
|
+
* );
|
17
|
+
*
|
18
|
+
* // Create a command that, when applied, will duplicate the elements.
|
19
|
+
* const duplicateElems = new Duplicate(elems);
|
20
|
+
*
|
21
|
+
* // Apply the command (and make it undoable)
|
22
|
+
* editor.dispatch(duplicateElems);
|
23
|
+
* ```
|
24
|
+
*
|
25
|
+
* @see {@link Editor.dispatch} {@link EditorImage.getElementsIntersectingRegion}
|
26
|
+
*/
|
4
27
|
export default class Duplicate extends SerializableCommand {
|
5
28
|
constructor(toDuplicate) {
|
6
29
|
super('duplicate');
|
@@ -14,6 +37,9 @@ export default class Duplicate extends SerializableCommand {
|
|
14
37
|
unapply(editor) {
|
15
38
|
this.reverse.apply(editor);
|
16
39
|
}
|
40
|
+
onDrop(editor) {
|
41
|
+
this.reverse.onDrop(editor);
|
42
|
+
}
|
17
43
|
description(_editor, localizationTable) {
|
18
44
|
var _a;
|
19
45
|
if (this.duplicates.length === 0) {
|
@@ -2,6 +2,26 @@ import AbstractComponent from '../components/AbstractComponent';
|
|
2
2
|
import Editor from '../Editor';
|
3
3
|
import { EditorLocalization } from '../localization';
|
4
4
|
import SerializableCommand from './SerializableCommand';
|
5
|
+
/**
|
6
|
+
* Removes the given {@link AbstractComponent}s from the image.
|
7
|
+
*
|
8
|
+
* @example
|
9
|
+
* ```ts
|
10
|
+
* // Given some editor...
|
11
|
+
*
|
12
|
+
* // Find all elements intersecting the rectangle with top left (-10,-30) and
|
13
|
+
* // (width,height)=(50,100).
|
14
|
+
* const elems = editor.image.getElementsIntersectingRegion(
|
15
|
+
* new Rect2(-10, -30, 50, 100)
|
16
|
+
* );
|
17
|
+
*
|
18
|
+
* // Create a command that erases [elems] when applied
|
19
|
+
* const eraseElemsCmd = new Erase(elems);
|
20
|
+
*
|
21
|
+
* // Apply the command (and make it undoable)
|
22
|
+
* editor.dispatch(eraseElemsCmd);
|
23
|
+
* ```
|
24
|
+
*/
|
5
25
|
export default class Erase extends SerializableCommand {
|
6
26
|
private toRemove;
|
7
27
|
private applied;
|
@@ -1,6 +1,26 @@
|
|
1
1
|
import describeComponentList from '../components/util/describeComponentList.mjs';
|
2
2
|
import EditorImage from '../EditorImage.mjs';
|
3
3
|
import SerializableCommand from './SerializableCommand.mjs';
|
4
|
+
/**
|
5
|
+
* Removes the given {@link AbstractComponent}s from the image.
|
6
|
+
*
|
7
|
+
* @example
|
8
|
+
* ```ts
|
9
|
+
* // Given some editor...
|
10
|
+
*
|
11
|
+
* // Find all elements intersecting the rectangle with top left (-10,-30) and
|
12
|
+
* // (width,height)=(50,100).
|
13
|
+
* const elems = editor.image.getElementsIntersectingRegion(
|
14
|
+
* new Rect2(-10, -30, 50, 100)
|
15
|
+
* );
|
16
|
+
*
|
17
|
+
* // Create a command that erases [elems] when applied
|
18
|
+
* const eraseElemsCmd = new Erase(elems);
|
19
|
+
*
|
20
|
+
* // Apply the command (and make it undoable)
|
21
|
+
* editor.dispatch(eraseElemsCmd);
|
22
|
+
* ```
|
23
|
+
*/
|
4
24
|
export default class Erase extends SerializableCommand {
|
5
25
|
constructor(toRemove) {
|
6
26
|
super('erase');
|
@@ -15,6 +15,9 @@ const invertCommand = (command) => {
|
|
15
15
|
unapply(editor) {
|
16
16
|
command.unapply(editor);
|
17
17
|
}
|
18
|
+
onDrop(editor) {
|
19
|
+
command.onDrop(editor);
|
20
|
+
}
|
18
21
|
description(editor, localizationTable) {
|
19
22
|
return localizationTable.inverseOf(command.description(editor, localizationTable));
|
20
23
|
}
|
@@ -29,6 +32,9 @@ const invertCommand = (command) => {
|
|
29
32
|
unapply(editor) {
|
30
33
|
command.apply(editor);
|
31
34
|
}
|
35
|
+
onDrop(editor) {
|
36
|
+
command.onDrop(editor);
|
37
|
+
}
|
32
38
|
description(editor, localizationTable) {
|
33
39
|
return localizationTable.inverseOf(command.description(editor, localizationTable));
|
34
40
|
}
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import waitForAll from '../util/waitForAll.mjs';
|
1
2
|
import Command from './Command.mjs';
|
2
3
|
import SerializableCommand from './SerializableCommand.mjs';
|
3
4
|
class NonSerializableUnion extends Command {
|
@@ -6,21 +7,10 @@ class NonSerializableUnion extends Command {
|
|
6
7
|
this.commands = commands;
|
7
8
|
this.applyChunkSize = applyChunkSize;
|
8
9
|
}
|
9
|
-
static waitForAll(commands) {
|
10
|
-
// If any are Promises...
|
11
|
-
if (commands.some(command => command && command['then'])) {
|
12
|
-
console.log('waiting...');
|
13
|
-
// Wait for all commands to finish.
|
14
|
-
return Promise.all(commands)
|
15
|
-
// Ensure we return a Promise<void> and not a Promise<void[]>
|
16
|
-
.then(() => { });
|
17
|
-
}
|
18
|
-
return;
|
19
|
-
}
|
20
10
|
apply(editor) {
|
21
11
|
if (this.applyChunkSize === undefined) {
|
22
12
|
const results = this.commands.map(cmd => cmd.apply(editor));
|
23
|
-
return
|
13
|
+
return waitForAll(results);
|
24
14
|
}
|
25
15
|
else {
|
26
16
|
return editor.asyncApplyCommands(this.commands, this.applyChunkSize);
|
@@ -31,12 +21,15 @@ class NonSerializableUnion extends Command {
|
|
31
21
|
commands.reverse();
|
32
22
|
if (this.applyChunkSize === undefined) {
|
33
23
|
const results = commands.map(cmd => cmd.unapply(editor));
|
34
|
-
return
|
24
|
+
return waitForAll(results);
|
35
25
|
}
|
36
26
|
else {
|
37
27
|
return editor.asyncUnapplyCommands(commands, this.applyChunkSize, false);
|
38
28
|
}
|
39
29
|
}
|
30
|
+
onDrop(editor) {
|
31
|
+
this.commands.forEach(command => command.onDrop(editor));
|
32
|
+
}
|
40
33
|
description(editor, localizationTable) {
|
41
34
|
const descriptions = [];
|
42
35
|
let lastDescription = null;
|
@@ -84,6 +77,9 @@ class SerializableUnion extends SerializableCommand {
|
|
84
77
|
unapply(editor) {
|
85
78
|
return this.nonserializableCommand.unapply(editor);
|
86
79
|
}
|
80
|
+
onDrop(editor) {
|
81
|
+
this.nonserializableCommand.onDrop(editor);
|
82
|
+
}
|
87
83
|
description(editor, localizationTable) {
|
88
84
|
return this.nonserializableCommand.description(editor, localizationTable);
|
89
85
|
}
|
@@ -167,7 +167,7 @@ export default class BackgroundComponent extends AbstractComponent {
|
|
167
167
|
if (this.backgroundType === BackgroundType.None) {
|
168
168
|
return;
|
169
169
|
}
|
170
|
-
const clip =
|
170
|
+
const clip = this.backgroundType === BackgroundType.Grid;
|
171
171
|
canvas.startObject(this.contentBBox, clip);
|
172
172
|
if (this.backgroundType === BackgroundType.SolidColor || this.backgroundType === BackgroundType.Grid) {
|
173
173
|
// If the rectangle for this region contains the visible rect,
|
@@ -235,9 +235,16 @@ export default class BackgroundComponent extends AbstractComponent {
|
|
235
235
|
if (this.backgroundType === BackgroundType.SolidColor) {
|
236
236
|
return localizationTable.filledBackgroundWithColor(this.mainColor.toString());
|
237
237
|
}
|
238
|
-
else {
|
238
|
+
else if (this.backgroundType === BackgroundType.None) {
|
239
239
|
return localizationTable.emptyBackground;
|
240
240
|
}
|
241
|
+
else if (this.backgroundType === BackgroundType.Grid) {
|
242
|
+
return localizationTable.gridBackground;
|
243
|
+
}
|
244
|
+
else {
|
245
|
+
const exhaustivenessCheck = this.backgroundType;
|
246
|
+
return exhaustivenessCheck;
|
247
|
+
}
|
241
248
|
}
|
242
249
|
createClone() {
|
243
250
|
return new BackgroundComponent(this.backgroundType, this.mainColor);
|
@@ -10,4 +10,4 @@ import ImageComponent from './ImageComponent';
|
|
10
10
|
import RestyleableComponent from './RestylableComponent';
|
11
11
|
import { createRestyleComponentCommand, isRestylableComponent, ComponentStyle as RestyleableComponentStyle } from './RestylableComponent';
|
12
12
|
import BackgroundComponent from './BackgroundComponent';
|
13
|
-
export { Stroke,
|
13
|
+
export { Stroke, RestyleableComponent, createRestyleComponentCommand, isRestylableComponent, RestyleableComponentStyle, TextComponent, TextComponent as Text, Stroke as StrokeComponent, BackgroundComponent, ImageComponent, };
|
@@ -9,4 +9,6 @@ import TextComponent from './TextComponent.mjs';
|
|
9
9
|
import ImageComponent from './ImageComponent.mjs';
|
10
10
|
import { createRestyleComponentCommand, isRestylableComponent } from './RestylableComponent.mjs';
|
11
11
|
import BackgroundComponent from './BackgroundComponent.mjs';
|
12
|
-
export { Stroke,
|
12
|
+
export { Stroke, createRestyleComponentCommand, isRestylableComponent, TextComponent,
|
13
|
+
// @deprecated
|
14
|
+
TextComponent as Text, Stroke as StrokeComponent, BackgroundComponent, ImageComponent, };
|
@@ -3,6 +3,7 @@ export const defaultComponentLocalization = {
|
|
3
3
|
stroke: 'Stroke',
|
4
4
|
svgObject: 'SVG Object',
|
5
5
|
emptyBackground: 'Empty background',
|
6
|
+
gridBackground: 'Grid background',
|
6
7
|
filledBackgroundWithColor: (color) => `Filled background (${color})`,
|
7
8
|
text: (text) => `Text object: ${text}`,
|
8
9
|
imageNode: (description) => `Image: ${description}`,
|
@@ -1,12 +1,32 @@
|
|
1
1
|
import Vec3 from './Vec3';
|
2
2
|
export declare namespace Vec2 {
|
3
|
+
/**
|
4
|
+
* Creates a `Vec2` from an x and y coordinate.
|
5
|
+
*
|
6
|
+
* For example,
|
7
|
+
* ```ts
|
8
|
+
* const v = Vec2.of(3, 4); // x=3, y=4.
|
9
|
+
* ```
|
10
|
+
*/
|
3
11
|
const of: (x: number, y: number) => Vec2;
|
12
|
+
/**
|
13
|
+
* Creates a `Vec2` from an object containing x and y coordinates.
|
14
|
+
*
|
15
|
+
* For example,
|
16
|
+
* ```ts
|
17
|
+
* const v1 = Vec2.ofXY({ x: 3, y: 4.5 });
|
18
|
+
* const v2 = Vec2.ofXY({ x: -123.4, y: 1 });
|
19
|
+
* ```
|
20
|
+
*/
|
4
21
|
const ofXY: ({ x, y }: {
|
5
22
|
x: number;
|
6
23
|
y: number;
|
7
24
|
}) => Vec2;
|
25
|
+
/** A vector of length 1 in the X direction (→). */
|
8
26
|
const unitX: Vec3;
|
27
|
+
/** A vector of length 1 in the Y direction (↑). */
|
9
28
|
const unitY: Vec3;
|
29
|
+
/** The zero vector: A vector with x=0, y=0. */
|
10
30
|
const zero: Vec3;
|
11
31
|
}
|
12
32
|
export type Point2 = Vec3;
|
@@ -1,13 +1,33 @@
|
|
1
1
|
import Vec3 from './Vec3.mjs';
|
2
2
|
export var Vec2;
|
3
3
|
(function (Vec2) {
|
4
|
+
/**
|
5
|
+
* Creates a `Vec2` from an x and y coordinate.
|
6
|
+
*
|
7
|
+
* For example,
|
8
|
+
* ```ts
|
9
|
+
* const v = Vec2.of(3, 4); // x=3, y=4.
|
10
|
+
* ```
|
11
|
+
*/
|
4
12
|
Vec2.of = (x, y) => {
|
5
13
|
return Vec3.of(x, y, 0);
|
6
14
|
};
|
15
|
+
/**
|
16
|
+
* Creates a `Vec2` from an object containing x and y coordinates.
|
17
|
+
*
|
18
|
+
* For example,
|
19
|
+
* ```ts
|
20
|
+
* const v1 = Vec2.ofXY({ x: 3, y: 4.5 });
|
21
|
+
* const v2 = Vec2.ofXY({ x: -123.4, y: 1 });
|
22
|
+
* ```
|
23
|
+
*/
|
7
24
|
Vec2.ofXY = ({ x, y }) => {
|
8
25
|
return Vec3.of(x, y, 0);
|
9
26
|
};
|
27
|
+
/** A vector of length 1 in the X direction (→). */
|
10
28
|
Vec2.unitX = Vec2.of(1, 0);
|
29
|
+
/** A vector of length 1 in the Y direction (↑). */
|
11
30
|
Vec2.unitY = Vec2.of(0, 1);
|
31
|
+
/** The zero vector: A vector with x=0, y=0. */
|
12
32
|
Vec2.zero = Vec2.of(0, 0);
|
13
33
|
})(Vec2 || (Vec2 = {}));
|