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.
Files changed (57) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/bundle.js +2 -2
  3. package/dist/bundledStyles.js +1 -1
  4. package/dist/cjs/src/Pointer.d.ts +3 -1
  5. package/dist/cjs/src/Pointer.js +27 -2
  6. package/dist/cjs/src/commands/Duplicate.d.ts +24 -0
  7. package/dist/cjs/src/commands/Duplicate.js +26 -0
  8. package/dist/cjs/src/commands/Erase.d.ts +20 -0
  9. package/dist/cjs/src/commands/Erase.js +20 -0
  10. package/dist/cjs/src/commands/invertCommand.js +6 -0
  11. package/dist/cjs/src/commands/uniteCommands.js +9 -13
  12. package/dist/cjs/src/components/BackgroundComponent.js +9 -2
  13. package/dist/cjs/src/components/lib.d.ts +1 -1
  14. package/dist/cjs/src/components/lib.js +2 -2
  15. package/dist/cjs/src/components/localization.d.ts +1 -0
  16. package/dist/cjs/src/components/localization.js +1 -0
  17. package/dist/cjs/src/math/Vec2.d.ts +20 -0
  18. package/dist/cjs/src/math/Vec2.js +20 -0
  19. package/dist/cjs/src/rendering/renderers/AbstractRenderer.d.ts +17 -0
  20. package/dist/cjs/src/rendering/renderers/AbstractRenderer.js +21 -3
  21. package/dist/cjs/src/rendering/renderers/CanvasRenderer.js +12 -7
  22. package/dist/cjs/src/toolbar/widgets/BaseWidget.d.ts +1 -0
  23. package/dist/cjs/src/toolbar/widgets/BaseWidget.js +22 -4
  24. package/dist/cjs/src/tools/Pen.d.ts +4 -0
  25. package/dist/cjs/src/tools/Pen.js +24 -1
  26. package/dist/cjs/src/tools/SelectionTool/SelectionTool.d.ts +1 -0
  27. package/dist/cjs/src/tools/SelectionTool/SelectionTool.js +8 -0
  28. package/dist/cjs/src/util/waitForAll.d.ts +6 -0
  29. package/dist/cjs/src/util/waitForAll.js +17 -0
  30. package/dist/mjs/src/Pointer.d.ts +3 -1
  31. package/dist/mjs/src/Pointer.mjs +27 -2
  32. package/dist/mjs/src/commands/Duplicate.d.ts +24 -0
  33. package/dist/mjs/src/commands/Duplicate.mjs +26 -0
  34. package/dist/mjs/src/commands/Erase.d.ts +20 -0
  35. package/dist/mjs/src/commands/Erase.mjs +20 -0
  36. package/dist/mjs/src/commands/invertCommand.mjs +6 -0
  37. package/dist/mjs/src/commands/uniteCommands.mjs +9 -13
  38. package/dist/mjs/src/components/BackgroundComponent.mjs +9 -2
  39. package/dist/mjs/src/components/lib.d.ts +1 -1
  40. package/dist/mjs/src/components/lib.mjs +3 -1
  41. package/dist/mjs/src/components/localization.d.ts +1 -0
  42. package/dist/mjs/src/components/localization.mjs +1 -0
  43. package/dist/mjs/src/math/Vec2.d.ts +20 -0
  44. package/dist/mjs/src/math/Vec2.mjs +20 -0
  45. package/dist/mjs/src/rendering/renderers/AbstractRenderer.d.ts +17 -0
  46. package/dist/mjs/src/rendering/renderers/AbstractRenderer.mjs +21 -3
  47. package/dist/mjs/src/rendering/renderers/CanvasRenderer.mjs +12 -7
  48. package/dist/mjs/src/toolbar/widgets/BaseWidget.d.ts +1 -0
  49. package/dist/mjs/src/toolbar/widgets/BaseWidget.mjs +22 -4
  50. package/dist/mjs/src/tools/Pen.d.ts +4 -0
  51. package/dist/mjs/src/tools/Pen.mjs +24 -1
  52. package/dist/mjs/src/tools/SelectionTool/SelectionTool.d.ts +1 -0
  53. package/dist/mjs/src/tools/SelectionTool/SelectionTool.mjs +8 -0
  54. package/dist/mjs/src/util/waitForAll.d.ts +6 -0
  55. package/dist/mjs/src/util/waitForAll.mjs +15 -0
  56. package/package.json +9 -9
  57. 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
- this.clipLevels.push(this.objectLevel);
184
- this.ctx.save();
185
- this.ctx.beginPath();
186
- for (const corner of boundingBox.corners) {
187
- const screenCorner = this.canvasToScreen(corner);
188
- this.ctx.lineTo(screenCorner.x, screenCorner.y);
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
- this.dropdownContainer.style.marginLeft = this.button.clientWidth + 'px';
271
- this.dropdownContainer.style.transform = 'translate(-100%, 0)';
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.builder = this.builderFactory(this.toStrokePoint(current), this.editor.viewport);
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
  }
@@ -10,6 +10,7 @@ export default class SelectionTool extends BaseTool {
10
10
  private prevSelectionBox;
11
11
  private selectionBox;
12
12
  private lastEvtTarget;
13
+ private startPoint;
13
14
  private expandingSelectionBox;
14
15
  private shiftKeyPressed;
15
16
  private ctrlKeyPressed;
@@ -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,6 @@
1
+ /**
2
+ * Resolves when all given promises have resolved. If no promises are given,
3
+ * does not return a Promise.
4
+ */
5
+ declare const waitForAll: (results: (Promise<void> | void)[]) => Promise<void> | void;
6
+ export default waitForAll;
@@ -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
  }
@@ -36,8 +36,33 @@ export default class Pointer {
36
36
  // this.
37
37
  snappedToGrid(viewport) {
38
38
  const snappedCanvasPos = viewport.snapToGrid(this.canvasPos);
39
- const snappedScreenPos = viewport.canvasToScreen(snappedCanvasPos);
40
- return new Pointer(snappedScreenPos, snappedCanvasPos, this.pressure, this.isPrimary, this.down, this.device, this.id, this.timeStamp);
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 NonSerializableUnion.waitForAll(results);
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 NonSerializableUnion.waitForAll(results);
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 = true;
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, TextComponent as Text, RestyleableComponent, createRestyleComponentCommand, isRestylableComponent, RestyleableComponentStyle, TextComponent, Stroke as StrokeComponent, BackgroundComponent, ImageComponent, };
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, TextComponent as Text, createRestyleComponentCommand, isRestylableComponent, TextComponent, Stroke as StrokeComponent, BackgroundComponent, ImageComponent, };
12
+ export { Stroke, createRestyleComponentCommand, isRestylableComponent, TextComponent,
13
+ // @deprecated
14
+ TextComponent as Text, Stroke as StrokeComponent, BackgroundComponent, ImageComponent, };
@@ -5,6 +5,7 @@ export interface ImageComponentLocalization {
5
5
  stroke: string;
6
6
  svgObject: string;
7
7
  emptyBackground: string;
8
+ gridBackground: string;
8
9
  filledBackgroundWithColor: (color: string) => string;
9
10
  restyledElement: (elementDescription: string) => string;
10
11
  }
@@ -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 = {}));