js-draw 0.0.7 → 0.0.10

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 (46) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/bundle.js +1 -1
  3. package/dist/src/Display.d.ts +1 -0
  4. package/dist/src/Display.js +3 -0
  5. package/dist/src/Editor.js +3 -1
  6. package/dist/src/UndoRedoHistory.d.ts +2 -0
  7. package/dist/src/UndoRedoHistory.js +6 -0
  8. package/dist/src/rendering/AbstractRenderer.d.ts +1 -0
  9. package/dist/src/rendering/AbstractRenderer.js +1 -0
  10. package/dist/src/rendering/CanvasRenderer.d.ts +4 -0
  11. package/dist/src/rendering/CanvasRenderer.js +22 -6
  12. package/dist/src/testing/createEditor.d.ts +3 -0
  13. package/dist/src/testing/createEditor.js +3 -0
  14. package/dist/src/toolbar/HTMLToolbar.js +10 -1
  15. package/dist/src/toolbar/localization.d.ts +1 -0
  16. package/dist/src/toolbar/localization.js +1 -0
  17. package/dist/src/tools/BaseTool.d.ts +4 -4
  18. package/dist/src/tools/BaseTool.js +4 -0
  19. package/dist/src/tools/PanZoom.js +3 -0
  20. package/dist/src/tools/SelectionTool.d.ts +3 -0
  21. package/dist/src/tools/SelectionTool.js +13 -0
  22. package/dist/src/tools/ToolController.d.ts +2 -1
  23. package/dist/src/tools/ToolController.js +3 -0
  24. package/dist/src/tools/UndoRedoShortcut.d.ts +10 -0
  25. package/dist/src/tools/UndoRedoShortcut.js +23 -0
  26. package/dist/src/tools/localization.d.ts +1 -0
  27. package/dist/src/tools/localization.js +1 -0
  28. package/dist/src/types.d.ts +1 -0
  29. package/package.json +2 -2
  30. package/src/Display.ts +4 -0
  31. package/src/Editor.ts +3 -1
  32. package/src/EditorImage.test.ts +1 -4
  33. package/src/UndoRedoHistory.ts +8 -0
  34. package/src/rendering/AbstractRenderer.ts +2 -0
  35. package/src/rendering/CanvasRenderer.ts +35 -6
  36. package/src/testing/createEditor.ts +4 -0
  37. package/src/toolbar/HTMLToolbar.ts +12 -1
  38. package/src/toolbar/localization.ts +2 -0
  39. package/src/tools/BaseTool.ts +5 -4
  40. package/src/tools/PanZoom.ts +3 -0
  41. package/src/tools/SelectionTool.ts +16 -0
  42. package/src/tools/ToolController.ts +3 -0
  43. package/src/tools/UndoRedoShortcut.test.ts +53 -0
  44. package/src/tools/UndoRedoShortcut.ts +28 -0
  45. package/src/tools/localization.ts +2 -0
  46. package/src/types.ts +1 -0
@@ -16,6 +16,7 @@ export default class Display {
16
16
  get height(): number;
17
17
  private initializeCanvasRendering;
18
18
  startRerender(): AbstractRenderer;
19
+ setDraftMode(draftMode: boolean): void;
19
20
  getDryInkRenderer(): AbstractRenderer;
20
21
  getWetInkRenderer(): AbstractRenderer;
21
22
  flatten(): void;
@@ -82,6 +82,9 @@ export default class Display {
82
82
  this.dryInkRenderer.clear();
83
83
  return this.dryInkRenderer;
84
84
  }
85
+ setDraftMode(draftMode) {
86
+ this.dryInkRenderer.setDraftMode(draftMode);
87
+ }
85
88
  getDryInkRenderer() {
86
89
  return this.dryInkRenderer;
87
90
  }
@@ -175,6 +175,7 @@ export class Editor {
175
175
  if (this.toolController.dispatchInputEvent({
176
176
  kind: InputEvtType.KeyPressEvent,
177
177
  key: evt.key,
178
+ ctrlKey: evt.ctrlKey,
178
179
  })) {
179
180
  evt.preventDefault();
180
181
  }
@@ -233,6 +234,7 @@ export class Editor {
233
234
  // has been applied.
234
235
  asyncApplyOrUnapplyCommands(commands, apply, updateChunkSize) {
235
236
  return __awaiter(this, void 0, void 0, function* () {
237
+ this.display.setDraftMode(true);
236
238
  for (let i = 0; i < commands.length; i += updateChunkSize) {
237
239
  this.showLoadingWarning(i / commands.length);
238
240
  for (let j = i; j < commands.length && j < i + updateChunkSize; j++) {
@@ -252,6 +254,7 @@ export class Editor {
252
254
  });
253
255
  }
254
256
  }
257
+ this.display.setDraftMode(false);
255
258
  this.hideLoadingWarning();
256
259
  });
257
260
  }
@@ -331,7 +334,6 @@ export class Editor {
331
334
  result.setAttribute('viewBox', `${rect.x} ${rect.y} ${rect.w} ${rect.h}`);
332
335
  result.setAttribute('width', `${rect.w}`);
333
336
  result.setAttribute('height', `${rect.h}`);
334
- console.log('res', result);
335
337
  // Ensure the image can be identified as an SVG if downloaded.
336
338
  // See https://jwatt.org/svg/authoring/
337
339
  result.setAttribute('version', '1.1');
@@ -13,5 +13,7 @@ declare class UndoRedoHistory {
13
13
  push(command: Command, apply?: boolean): void;
14
14
  undo(): void;
15
15
  redo(): void;
16
+ get undoStackSize(): number;
17
+ get redoStackSize(): number;
16
18
  }
17
19
  export default UndoRedoHistory;
@@ -42,5 +42,11 @@ class UndoRedoHistory {
42
42
  }
43
43
  this.fireUpdateEvent();
44
44
  }
45
+ get undoStackSize() {
46
+ return this.undoStack.length;
47
+ }
48
+ get redoStackSize() {
49
+ return this.redoStack.length;
50
+ }
45
51
  }
46
52
  export default UndoRedoHistory;
@@ -26,6 +26,7 @@ export default abstract class AbstractRenderer {
26
26
  protected abstract moveTo(point: Point2): void;
27
27
  protected abstract traceCubicBezierCurve(p1: Point2, p2: Point2, p3: Point2): void;
28
28
  protected abstract traceQuadraticBezierCurve(controlPoint: Point2, endPoint: Point2): void;
29
+ setDraftMode(_draftMode: boolean): void;
29
30
  private objectLevel;
30
31
  private currentPaths;
31
32
  private flushPath;
@@ -11,6 +11,7 @@ export default class AbstractRenderer {
11
11
  this.objectLevel = 0;
12
12
  this.currentPaths = null;
13
13
  }
14
+ setDraftMode(_draftMode) { }
14
15
  flushPath() {
15
16
  if (!this.currentPaths) {
16
17
  return;
@@ -7,7 +7,11 @@ export default class CanvasRenderer extends AbstractRenderer {
7
7
  private ctx;
8
8
  private ignoreObjectsAboveLevel;
9
9
  private ignoringObject;
10
+ private minSquareCurveApproxDist;
11
+ private minRenderSizeAnyDimen;
12
+ private minRenderSizeBothDimens;
10
13
  constructor(ctx: CanvasRenderingContext2D, viewport: Viewport);
14
+ setDraftMode(draftMode: boolean): void;
11
15
  displaySize(): Vec2;
12
16
  clear(): void;
13
17
  protected beginPath(startPoint: Point2): void;
@@ -1,13 +1,26 @@
1
1
  import Color4 from '../Color4';
2
2
  import { Vec2 } from '../geometry/Vec2';
3
3
  import AbstractRenderer from './AbstractRenderer';
4
- const minSquareCurveApproxDist = 25;
5
4
  export default class CanvasRenderer extends AbstractRenderer {
6
5
  constructor(ctx, viewport) {
7
6
  super(viewport);
8
7
  this.ctx = ctx;
9
8
  this.ignoreObjectsAboveLevel = null;
10
9
  this.ignoringObject = false;
10
+ this.setDraftMode(false);
11
+ }
12
+ // Set parameters for lower/higher quality rendering
13
+ setDraftMode(draftMode) {
14
+ if (draftMode) {
15
+ this.minSquareCurveApproxDist = 64;
16
+ this.minRenderSizeBothDimens = 8;
17
+ this.minRenderSizeAnyDimen = 2;
18
+ }
19
+ else {
20
+ this.minSquareCurveApproxDist = 1;
21
+ this.minRenderSizeBothDimens = 1;
22
+ this.minRenderSizeAnyDimen = 0;
23
+ }
11
24
  }
12
25
  displaySize() {
13
26
  return Vec2.of(this.ctx.canvas.clientWidth, this.ctx.canvas.clientHeight);
@@ -45,8 +58,8 @@ export default class CanvasRenderer extends AbstractRenderer {
45
58
  // Approximate the curve if small enough.
46
59
  const delta1 = p2.minus(p1);
47
60
  const delta2 = p3.minus(p2);
48
- if (delta1.magnitudeSquared() < minSquareCurveApproxDist
49
- && delta2.magnitudeSquared() < minSquareCurveApproxDist) {
61
+ if (delta1.magnitudeSquared() < this.minSquareCurveApproxDist
62
+ && delta2.magnitudeSquared() < this.minSquareCurveApproxDist) {
50
63
  this.ctx.lineTo(p3.x, p3.y);
51
64
  }
52
65
  else {
@@ -58,7 +71,7 @@ export default class CanvasRenderer extends AbstractRenderer {
58
71
  endPoint = this.viewport.canvasToScreen(endPoint);
59
72
  // Approximate the curve with a line if small enough
60
73
  const delta = controlPoint.minus(endPoint);
61
- if (delta.magnitudeSquared() < minSquareCurveApproxDist) {
74
+ if (delta.magnitudeSquared() < this.minSquareCurveApproxDist) {
62
75
  this.ctx.lineTo(endPoint.x, endPoint.y);
63
76
  }
64
77
  else {
@@ -74,8 +87,11 @@ export default class CanvasRenderer extends AbstractRenderer {
74
87
  startObject(boundingBox) {
75
88
  // Should we ignore all objects within this object's bbox?
76
89
  const diagonal = this.viewport.canvasToScreenTransform.transformVec3(boundingBox.size);
77
- const minRenderSize = 4;
78
- if (Math.abs(diagonal.x) < minRenderSize && Math.abs(diagonal.y) < minRenderSize) {
90
+ const bothDimenMinSize = this.minRenderSizeBothDimens;
91
+ const bothTooSmall = Math.abs(diagonal.x) < bothDimenMinSize && Math.abs(diagonal.y) < bothDimenMinSize;
92
+ const anyDimenMinSize = this.minRenderSizeAnyDimen;
93
+ const anyTooSmall = Math.abs(diagonal.x) < anyDimenMinSize || Math.abs(diagonal.y) < anyDimenMinSize;
94
+ if (bothTooSmall || anyTooSmall) {
79
95
  this.ignoreObjectsAboveLevel = this.getNestingLevel();
80
96
  this.ignoringObject = true;
81
97
  }
@@ -0,0 +1,3 @@
1
+ import Editor from '../Editor';
2
+ declare const _default: () => Editor;
3
+ export default _default;
@@ -0,0 +1,3 @@
1
+ import { RenderingMode } from '../Display';
2
+ import Editor from '../Editor';
3
+ export default () => new Editor(document.body, { renderingMode: RenderingMode.DummyRenderer });
@@ -192,12 +192,20 @@ class SelectionWidget extends ToolbarWidget {
192
192
  fillDropdown(dropdown) {
193
193
  const container = document.createElement('div');
194
194
  const resizeButton = document.createElement('button');
195
+ const deleteButton = document.createElement('button');
195
196
  resizeButton.innerText = this.localizationTable.resizeImageToSelection;
196
197
  resizeButton.disabled = true;
198
+ deleteButton.innerText = this.localizationTable.deleteSelection;
199
+ deleteButton.disabled = true;
197
200
  resizeButton.onclick = () => {
198
201
  const selection = this.tool.getSelection();
199
202
  this.editor.dispatch(this.editor.setImportExportRect(selection.region));
200
203
  };
204
+ deleteButton.onclick = () => {
205
+ const selection = this.tool.getSelection();
206
+ this.editor.dispatch(selection.deleteSelectedObjects());
207
+ this.tool.clearSelection();
208
+ };
201
209
  // Enable/disable actions based on whether items are selected
202
210
  this.editor.notifier.on(EditorEventType.ToolUpdated, toolEvt => {
203
211
  if (toolEvt.kind !== EditorEventType.ToolUpdated) {
@@ -207,9 +215,10 @@ class SelectionWidget extends ToolbarWidget {
207
215
  const selection = this.tool.getSelection();
208
216
  const hasSelection = selection && selection.region.area > 0;
209
217
  resizeButton.disabled = !hasSelection;
218
+ deleteButton.disabled = resizeButton.disabled;
210
219
  }
211
220
  });
212
- container.replaceChildren(resizeButton);
221
+ container.replaceChildren(resizeButton, deleteButton);
213
222
  dropdown.appendChild(container);
214
223
  return true;
215
224
  }
@@ -12,6 +12,7 @@ export interface ToolbarLocalization {
12
12
  touchDrawing: string;
13
13
  thicknessLabel: string;
14
14
  resizeImageToSelection: string;
15
+ deleteSelection: string;
15
16
  undo: string;
16
17
  redo: string;
17
18
  dropdownShown: (toolName: string) => string;
@@ -6,6 +6,7 @@ export const defaultToolbarLocalization = {
6
6
  thicknessLabel: 'Thickness: ',
7
7
  colorLabel: 'Color: ',
8
8
  resizeImageToSelection: 'Resize image to selection',
9
+ deleteSelection: 'Delete selection',
9
10
  undo: 'Undo',
10
11
  redo: 'Redo',
11
12
  selectObjectType: 'Object type: ',
@@ -6,10 +6,10 @@ export default abstract class BaseTool implements PointerEvtListener {
6
6
  readonly description: string;
7
7
  private enabled;
8
8
  private group;
9
- abstract onPointerDown(event: PointerEvt): boolean;
10
- abstract onPointerMove(event: PointerEvt): void;
11
- abstract onPointerUp(event: PointerEvt): void;
12
- abstract onGestureCancel(): void;
9
+ onPointerDown(_event: PointerEvt): boolean;
10
+ onPointerMove(_event: PointerEvt): void;
11
+ onPointerUp(_event: PointerEvt): void;
12
+ onGestureCancel(): void;
13
13
  abstract readonly kind: ToolType;
14
14
  protected constructor(notifier: EditorNotifier, description: string);
15
15
  onWheel(_event: WheelEvt): boolean;
@@ -6,6 +6,10 @@ export default class BaseTool {
6
6
  this.enabled = true;
7
7
  this.group = null;
8
8
  }
9
+ onPointerDown(_event) { return false; }
10
+ onPointerMove(_event) { }
11
+ onPointerUp(_event) { }
12
+ onGestureCancel() { }
9
13
  onWheel(_event) {
10
14
  return false;
11
15
  }
@@ -56,6 +56,7 @@ export default class PanZoom extends BaseTool {
56
56
  }
57
57
  if (handlingGesture) {
58
58
  (_a = this.transform) !== null && _a !== void 0 ? _a : (this.transform = new Viewport.ViewportTransform(Mat33.identity));
59
+ this.editor.display.setDraftMode(true);
59
60
  }
60
61
  return handlingGesture;
61
62
  }
@@ -100,11 +101,13 @@ export default class PanZoom extends BaseTool {
100
101
  this.transform.unapply(this.editor);
101
102
  this.editor.dispatch(this.transform, false);
102
103
  }
104
+ this.editor.display.setDraftMode(false);
103
105
  this.transform = null;
104
106
  }
105
107
  onGestureCancel() {
106
108
  var _a;
107
109
  (_a = this.transform) === null || _a === void 0 ? void 0 : _a.unapply(this.editor);
110
+ this.editor.display.setDraftMode(false);
108
111
  this.transform = null;
109
112
  }
110
113
  // Applies [transformUpdate] to the editor. This stacks on top of the
@@ -1,3 +1,4 @@
1
+ import Command from '../commands/Command';
1
2
  import Editor from '../Editor';
2
3
  import Rect2 from '../geometry/Rect2';
3
4
  import { Point2, Vec2 } from '../geometry/Vec2';
@@ -30,6 +31,7 @@ declare class Selection {
30
31
  private recomputeBoxRotation;
31
32
  getSelectedItemCount(): number;
32
33
  updateUI(): void;
34
+ deleteSelectedObjects(): Command;
33
35
  }
34
36
  export default class SelectionTool extends BaseTool {
35
37
  private editor;
@@ -45,5 +47,6 @@ export default class SelectionTool extends BaseTool {
45
47
  onGestureCancel(): void;
46
48
  setEnabled(enabled: boolean): void;
47
49
  getSelection(): Selection | null;
50
+ clearSelection(): void;
48
51
  }
49
52
  export {};
@@ -7,6 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
+ import Erase from '../commands/Erase';
10
11
  import Mat33 from '../geometry/Mat33';
11
12
  // import Mat33 from "../geometry/Mat33";
12
13
  import Rect2 from '../geometry/Rect2';
@@ -344,6 +345,9 @@ class Selection {
344
345
  this.backgroundBox.style.transform = `rotate(${rotationDeg}deg)`;
345
346
  this.rotateCircle.style.transform = `rotate(${-rotationDeg}deg)`;
346
347
  }
348
+ deleteSelectedObjects() {
349
+ return new Erase(this.selectedElems);
350
+ }
347
351
  }
348
352
  export default class SelectionTool extends BaseTool {
349
353
  constructor(editor, description) {
@@ -434,4 +438,13 @@ export default class SelectionTool extends BaseTool {
434
438
  getSelection() {
435
439
  return this.selectionBox;
436
440
  }
441
+ clearSelection() {
442
+ this.handleOverlay.replaceChildren();
443
+ this.prevSelectionBox = this.selectionBox;
444
+ this.selectionBox = null;
445
+ this.editor.notifier.dispatch(EditorEventType.ToolUpdated, {
446
+ kind: EditorEventType.ToolUpdated,
447
+ tool: this,
448
+ });
449
+ }
437
450
  }
@@ -7,7 +7,8 @@ export declare enum ToolType {
7
7
  Pen = 1,
8
8
  Selection = 2,
9
9
  Eraser = 3,
10
- PanZoom = 4
10
+ PanZoom = 4,
11
+ UndoRedoShortcut = 5
11
12
  }
12
13
  export default class ToolController {
13
14
  private tools;
@@ -5,6 +5,7 @@ import ToolEnabledGroup from './ToolEnabledGroup';
5
5
  import Eraser from './Eraser';
6
6
  import SelectionTool from './SelectionTool';
7
7
  import Color4 from '../Color4';
8
+ import UndoRedoShortcut from './UndoRedoShortcut';
8
9
  export var ToolType;
9
10
  (function (ToolType) {
10
11
  ToolType[ToolType["TouchPanZoom"] = 0] = "TouchPanZoom";
@@ -12,6 +13,7 @@ export var ToolType;
12
13
  ToolType[ToolType["Selection"] = 2] = "Selection";
13
14
  ToolType[ToolType["Eraser"] = 3] = "Eraser";
14
15
  ToolType[ToolType["PanZoom"] = 4] = "PanZoom";
16
+ ToolType[ToolType["UndoRedoShortcut"] = 5] = "UndoRedoShortcut";
15
17
  })(ToolType || (ToolType = {}));
16
18
  export default class ToolController {
17
19
  constructor(editor, localization) {
@@ -31,6 +33,7 @@ export default class ToolController {
31
33
  touchPanZoom,
32
34
  ...primaryTools,
33
35
  new PanZoom(editor, PanZoomMode.TwoFingerGestures | PanZoomMode.AnyDevice, localization.twoFingerPanZoomTool),
36
+ new UndoRedoShortcut(editor),
34
37
  ];
35
38
  primaryTools.forEach(tool => tool.setToolGroup(primaryToolEnabledGroup));
36
39
  touchPanZoom.setEnabled(false);
@@ -0,0 +1,10 @@
1
+ import Editor from '../Editor';
2
+ import { KeyPressEvent } from '../types';
3
+ import BaseTool from './BaseTool';
4
+ import { ToolType } from './ToolController';
5
+ export default class UndoRedoShortcut extends BaseTool {
6
+ private editor;
7
+ kind: ToolType.UndoRedoShortcut;
8
+ constructor(editor: Editor);
9
+ onKeyPress({ key, ctrlKey }: KeyPressEvent): boolean;
10
+ }
@@ -0,0 +1,23 @@
1
+ import BaseTool from './BaseTool';
2
+ import { ToolType } from './ToolController';
3
+ export default class UndoRedoShortcut extends BaseTool {
4
+ constructor(editor) {
5
+ super(editor.notifier, editor.localization.undoRedoTool);
6
+ this.editor = editor;
7
+ this.kind = ToolType.UndoRedoShortcut;
8
+ }
9
+ // Activate undo/redo
10
+ onKeyPress({ key, ctrlKey }) {
11
+ if (ctrlKey) {
12
+ if (key === 'z') {
13
+ this.editor.history.undo();
14
+ return true;
15
+ }
16
+ else if (key === 'Z') {
17
+ this.editor.history.redo();
18
+ return true;
19
+ }
20
+ }
21
+ return false;
22
+ }
23
+ }
@@ -4,6 +4,7 @@ export interface ToolLocalization {
4
4
  eraserTool: string;
5
5
  touchPanTool: string;
6
6
  twoFingerPanZoomTool: string;
7
+ undoRedoTool: string;
7
8
  toolEnabledAnnouncement: (toolName: string) => string;
8
9
  toolDisabledAnnouncement: (toolName: string) => string;
9
10
  }
@@ -4,6 +4,7 @@ export const defaultToolLocalization = {
4
4
  eraserTool: 'Eraser',
5
5
  touchPanTool: 'Touch Panning',
6
6
  twoFingerPanZoomTool: 'Panning and Zooming',
7
+ undoRedoTool: 'Undo/Redo',
7
8
  toolEnabledAnnouncement: (toolName) => `${toolName} enabled`,
8
9
  toolDisabledAnnouncement: (toolName) => `${toolName} disabled`,
9
10
  };
@@ -29,6 +29,7 @@ export interface WheelEvt {
29
29
  export interface KeyPressEvent {
30
30
  readonly kind: InputEvtType.KeyPressEvent;
31
31
  readonly key: string;
32
+ readonly ctrlKey: boolean;
32
33
  }
33
34
  export interface GestureCancelEvt {
34
35
  readonly kind: InputEvtType.GestureCancelEvt;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "0.0.7",
3
+ "version": "0.0.10",
4
4
  "description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
5
5
  "main": "dist/src/Editor.js",
6
6
  "types": "dist/src/Editor.d.ts",
@@ -48,7 +48,7 @@
48
48
  "linter-precommit": "eslint --fix --ext .js --ext .ts",
49
49
  "lint-staged": "lint-staged",
50
50
  "_postinstall": "husky install",
51
- "prepack": "pinst --disable",
51
+ "prepack": "yarn build && pinst --disable",
52
52
  "postpack": "pinst --enable"
53
53
  },
54
54
  "dependencies": {
package/src/Display.ts CHANGED
@@ -105,6 +105,10 @@ export default class Display {
105
105
  return this.dryInkRenderer;
106
106
  }
107
107
 
108
+ public setDraftMode(draftMode: boolean) {
109
+ this.dryInkRenderer.setDraftMode(draftMode);
110
+ }
111
+
108
112
  public getDryInkRenderer(): AbstractRenderer {
109
113
  return this.dryInkRenderer;
110
114
  }
package/src/Editor.ts CHANGED
@@ -238,6 +238,7 @@ export class Editor {
238
238
  if (this.toolController.dispatchInputEvent({
239
239
  kind: InputEvtType.KeyPressEvent,
240
240
  key: evt.key,
241
+ ctrlKey: evt.ctrlKey,
241
242
  })) {
242
243
  evt.preventDefault();
243
244
  }
@@ -310,6 +311,7 @@ export class Editor {
310
311
  private async asyncApplyOrUnapplyCommands(
311
312
  commands: Command[], apply: boolean, updateChunkSize: number
312
313
  ) {
314
+ this.display.setDraftMode(true);
313
315
  for (let i = 0; i < commands.length; i += updateChunkSize) {
314
316
  this.showLoadingWarning(i / commands.length);
315
317
 
@@ -331,6 +333,7 @@ export class Editor {
331
333
  });
332
334
  }
333
335
  }
336
+ this.display.setDraftMode(false);
334
337
  this.hideLoadingWarning();
335
338
  }
336
339
 
@@ -447,7 +450,6 @@ export class Editor {
447
450
  result.setAttribute('viewBox', `${rect.x} ${rect.y} ${rect.w} ${rect.h}`);
448
451
  result.setAttribute('width', `${rect.w}`);
449
452
  result.setAttribute('height', `${rect.h}`);
450
- console.log('res', result);
451
453
 
452
454
  // Ensure the image can be identified as an SVG if downloaded.
453
455
  // See https://jwatt.org/svg/authoring/
@@ -5,12 +5,9 @@ import Stroke from './components/Stroke';
5
5
  import { Vec2 } from './geometry/Vec2';
6
6
  import Path, { PathCommandType } from './geometry/Path';
7
7
  import Color4 from './Color4';
8
- import Editor from './Editor';
9
- import { RenderingMode } from './Display';
10
8
  import DummyRenderer from './rendering/DummyRenderer';
11
9
  import { RenderingStyle } from './rendering/AbstractRenderer';
12
-
13
- const createEditor = () => new Editor(document.body, { renderingMode: RenderingMode.DummyRenderer });
10
+ import createEditor from './testing/createEditor';
14
11
 
15
12
  describe('EditorImage', () => {
16
13
  const testStroke = new Stroke([
@@ -56,6 +56,14 @@ class UndoRedoHistory {
56
56
  }
57
57
  this.fireUpdateEvent();
58
58
  }
59
+
60
+ public get undoStackSize(): number {
61
+ return this.undoStack.length;
62
+ }
63
+
64
+ public get redoStackSize(): number {
65
+ return this.redoStack.length;
66
+ }
59
67
  }
60
68
 
61
69
  export default UndoRedoHistory;
@@ -43,6 +43,8 @@ export default abstract class AbstractRenderer {
43
43
  controlPoint: Point2, endPoint: Point2,
44
44
  ): void;
45
45
 
46
+ public setDraftMode(_draftMode: boolean) { }
47
+
46
48
  private objectLevel: number = 0;
47
49
  private currentPaths: RenderablePathSpec[]|null = null;
48
50
  private flushPath() {
@@ -5,13 +5,37 @@ import Vec3 from '../geometry/Vec3';
5
5
  import Viewport from '../Viewport';
6
6
  import AbstractRenderer, { RenderablePathSpec, RenderingStyle } from './AbstractRenderer';
7
7
 
8
- const minSquareCurveApproxDist = 25;
9
8
  export default class CanvasRenderer extends AbstractRenderer {
10
9
  private ignoreObjectsAboveLevel: number|null = null;
11
10
  private ignoringObject: boolean = false;
12
11
 
12
+ // Minimum square distance of a control point from the line between the end points
13
+ // for the curve not to be drawn as a line.
14
+ // For example, if [minSquareCurveApproxDist] = 25 = 5², then a control point on a quadratic
15
+ // bezier curve needs to be at least 5 units away from the line between the curve's end points
16
+ // for the curve to be drawn as a Bezier curve (and not a line).
17
+ private minSquareCurveApproxDist: number;
18
+
19
+ // Minimum size of an object (in pixels) for it to be rendered.
20
+ private minRenderSizeAnyDimen: number;
21
+ private minRenderSizeBothDimens: number;
22
+
13
23
  public constructor(private ctx: CanvasRenderingContext2D, viewport: Viewport) {
14
24
  super(viewport);
25
+ this.setDraftMode(false);
26
+ }
27
+
28
+ // Set parameters for lower/higher quality rendering
29
+ public setDraftMode(draftMode: boolean) {
30
+ if (draftMode) {
31
+ this.minSquareCurveApproxDist = 64;
32
+ this.minRenderSizeBothDimens = 8;
33
+ this.minRenderSizeAnyDimen = 2;
34
+ } else {
35
+ this.minSquareCurveApproxDist = 1;
36
+ this.minRenderSizeBothDimens = 1;
37
+ this.minRenderSizeAnyDimen = 0;
38
+ }
15
39
  }
16
40
 
17
41
  public displaySize(): Vec2 {
@@ -63,8 +87,8 @@ export default class CanvasRenderer extends AbstractRenderer {
63
87
  // Approximate the curve if small enough.
64
88
  const delta1 = p2.minus(p1);
65
89
  const delta2 = p3.minus(p2);
66
- if (delta1.magnitudeSquared() < minSquareCurveApproxDist
67
- && delta2.magnitudeSquared() < minSquareCurveApproxDist) {
90
+ if (delta1.magnitudeSquared() < this.minSquareCurveApproxDist
91
+ && delta2.magnitudeSquared() < this.minSquareCurveApproxDist) {
68
92
  this.ctx.lineTo(p3.x, p3.y);
69
93
  } else {
70
94
  this.ctx.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
@@ -77,7 +101,7 @@ export default class CanvasRenderer extends AbstractRenderer {
77
101
 
78
102
  // Approximate the curve with a line if small enough
79
103
  const delta = controlPoint.minus(endPoint);
80
- if (delta.magnitudeSquared() < minSquareCurveApproxDist) {
104
+ if (delta.magnitudeSquared() < this.minSquareCurveApproxDist) {
81
105
  this.ctx.lineTo(endPoint.x, endPoint.y);
82
106
  } else {
83
107
  this.ctx.quadraticCurveTo(
@@ -97,8 +121,13 @@ export default class CanvasRenderer extends AbstractRenderer {
97
121
  public startObject(boundingBox: Rect2) {
98
122
  // Should we ignore all objects within this object's bbox?
99
123
  const diagonal = this.viewport.canvasToScreenTransform.transformVec3(boundingBox.size);
100
- const minRenderSize = 4;
101
- if (Math.abs(diagonal.x) < minRenderSize && Math.abs(diagonal.y) < minRenderSize) {
124
+
125
+ const bothDimenMinSize = this.minRenderSizeBothDimens;
126
+ const bothTooSmall = Math.abs(diagonal.x) < bothDimenMinSize && Math.abs(diagonal.y) < bothDimenMinSize;
127
+ const anyDimenMinSize = this.minRenderSizeAnyDimen;
128
+ const anyTooSmall = Math.abs(diagonal.x) < anyDimenMinSize || Math.abs(diagonal.y) < anyDimenMinSize;
129
+
130
+ if (bothTooSmall || anyTooSmall) {
102
131
  this.ignoreObjectsAboveLevel = this.getNestingLevel();
103
132
  this.ignoringObject = true;
104
133
  }
@@ -0,0 +1,4 @@
1
+ import { RenderingMode } from '../Display';
2
+ import Editor from '../Editor';
3
+
4
+ export default () => new Editor(document.body, { renderingMode: RenderingMode.DummyRenderer });
@@ -245,15 +245,24 @@ class SelectionWidget extends ToolbarWidget {
245
245
  protected fillDropdown(dropdown: HTMLElement): boolean {
246
246
  const container = document.createElement('div');
247
247
  const resizeButton = document.createElement('button');
248
+ const deleteButton = document.createElement('button');
248
249
 
249
250
  resizeButton.innerText = this.localizationTable.resizeImageToSelection;
250
251
  resizeButton.disabled = true;
252
+ deleteButton.innerText = this.localizationTable.deleteSelection;
253
+ deleteButton.disabled = true;
251
254
 
252
255
  resizeButton.onclick = () => {
253
256
  const selection = this.tool.getSelection();
254
257
  this.editor.dispatch(this.editor.setImportExportRect(selection!.region));
255
258
  };
256
259
 
260
+ deleteButton.onclick = () => {
261
+ const selection = this.tool.getSelection();
262
+ this.editor.dispatch(selection!.deleteSelectedObjects());
263
+ this.tool.clearSelection();
264
+ };
265
+
257
266
  // Enable/disable actions based on whether items are selected
258
267
  this.editor.notifier.on(EditorEventType.ToolUpdated, toolEvt => {
259
268
  if (toolEvt.kind !== EditorEventType.ToolUpdated) {
@@ -263,11 +272,13 @@ class SelectionWidget extends ToolbarWidget {
263
272
  if (toolEvt.tool === this.tool) {
264
273
  const selection = this.tool.getSelection();
265
274
  const hasSelection = selection && selection.region.area > 0;
275
+
266
276
  resizeButton.disabled = !hasSelection;
277
+ deleteButton.disabled = resizeButton.disabled;
267
278
  }
268
279
  });
269
280
 
270
- container.replaceChildren(resizeButton);
281
+ container.replaceChildren(resizeButton, deleteButton);
271
282
  dropdown.appendChild(container);
272
283
  return true;
273
284
  }