js-draw 0.0.6 → 0.0.9

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 (38) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/bundle.js +1 -1
  3. package/dist/src/Editor.js +1 -0
  4. package/dist/src/EditorImage.d.ts +2 -2
  5. package/dist/src/SVGLoader.d.ts +2 -0
  6. package/dist/src/SVGLoader.js +12 -0
  7. package/dist/src/UndoRedoHistory.d.ts +2 -0
  8. package/dist/src/UndoRedoHistory.js +6 -0
  9. package/dist/src/Viewport.d.ts +1 -1
  10. package/dist/src/components/SVGGlobalAttributesObject.d.ts +15 -0
  11. package/dist/src/components/SVGGlobalAttributesObject.js +29 -0
  12. package/dist/src/rendering/SVGRenderer.d.ts +2 -0
  13. package/dist/src/rendering/SVGRenderer.js +25 -0
  14. package/dist/src/testing/createEditor.d.ts +3 -0
  15. package/dist/src/testing/createEditor.js +3 -0
  16. package/dist/src/tools/BaseTool.d.ts +4 -4
  17. package/dist/src/tools/BaseTool.js +4 -0
  18. package/dist/src/tools/ToolController.d.ts +2 -1
  19. package/dist/src/tools/ToolController.js +3 -0
  20. package/dist/src/tools/UndoRedoShortcut.d.ts +10 -0
  21. package/dist/src/tools/UndoRedoShortcut.js +23 -0
  22. package/dist/src/tools/localization.d.ts +1 -0
  23. package/dist/src/tools/localization.js +1 -0
  24. package/dist/src/types.d.ts +1 -0
  25. package/package.json +2 -2
  26. package/src/Editor.ts +1 -0
  27. package/src/EditorImage.test.ts +1 -4
  28. package/src/SVGLoader.ts +13 -0
  29. package/src/UndoRedoHistory.ts +8 -0
  30. package/src/components/SVGGlobalAttributesObject.ts +39 -0
  31. package/src/rendering/SVGRenderer.ts +27 -0
  32. package/src/testing/createEditor.ts +4 -0
  33. package/src/tools/BaseTool.ts +5 -4
  34. package/src/tools/ToolController.ts +3 -0
  35. package/src/tools/UndoRedoShortcut.test.ts +53 -0
  36. package/src/tools/UndoRedoShortcut.ts +28 -0
  37. package/src/tools/localization.ts +2 -0
  38. package/src/types.ts +1 -0
@@ -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
  }
@@ -15,8 +15,8 @@ export default class EditorImage {
15
15
  getElementsIntersectingRegion(region: Rect2): AbstractComponent[];
16
16
  static AddElementCommand: {
17
17
  new (element: AbstractComponent, applyByFlattening?: boolean): {
18
- readonly "__#2@#element": AbstractComponent;
19
- "__#2@#applyByFlattening": boolean;
18
+ readonly "__#679@#element": AbstractComponent;
19
+ "__#679@#applyByFlattening": boolean;
20
20
  apply(editor: Editor): void;
21
21
  unapply(editor: Editor): void;
22
22
  description(localization: EditorLocalization): string;
@@ -15,7 +15,9 @@ export default class SVGLoader implements ImageLoader {
15
15
  private addPath;
16
16
  private addUnknownNode;
17
17
  private updateViewBox;
18
+ private updateSVGAttrs;
18
19
  private visit;
20
+ private getSourceAttrs;
19
21
  start(onAddComponent: ComponentAddedListener, onProgress: OnProgressListener): Promise<Rect2>;
20
22
  static fromString(text: string): SVGLoader;
21
23
  }
@@ -9,6 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import Color4 from './Color4';
11
11
  import Stroke from './components/Stroke';
12
+ import SVGGlobalAttributesObject from './components/SVGGlobalAttributesObject';
12
13
  import UnknownSVGObject from './components/UnknownSVGObject';
13
14
  import Path from './geometry/Path';
14
15
  import Rect2 from './geometry/Rect2';
@@ -113,6 +114,10 @@ export default class SVGLoader {
113
114
  }
114
115
  this.rootViewBox = new Rect2(x, y, width, height);
115
116
  }
117
+ updateSVGAttrs(node) {
118
+ var _a;
119
+ (_a = this.onAddComponent) === null || _a === void 0 ? void 0 : _a.call(this, new SVGGlobalAttributesObject(this.getSourceAttrs(node)));
120
+ }
116
121
  visit(node) {
117
122
  var _a;
118
123
  return __awaiter(this, void 0, void 0, function* () {
@@ -126,6 +131,7 @@ export default class SVGLoader {
126
131
  break;
127
132
  case 'svg':
128
133
  this.updateViewBox(node);
134
+ this.updateSVGAttrs(node);
129
135
  break;
130
136
  default:
131
137
  console.warn('Unknown SVG element,', node);
@@ -142,6 +148,12 @@ export default class SVGLoader {
142
148
  yield ((_a = this.onProgress) === null || _a === void 0 ? void 0 : _a.call(this, this.processedCount, this.totalToProcess));
143
149
  });
144
150
  }
151
+ // Get SVG element attributes (e.g. xlink=...)
152
+ getSourceAttrs(node) {
153
+ return node.getAttributeNames().map(attr => {
154
+ return [attr, node.getAttribute(attr)];
155
+ });
156
+ }
145
157
  start(onAddComponent, onProgress) {
146
158
  var _a;
147
159
  return __awaiter(this, void 0, void 0, function* () {
@@ -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;
@@ -10,7 +10,7 @@ export declare class Viewport {
10
10
  private notifier;
11
11
  static ViewportTransform: {
12
12
  new (transform: Mat33): {
13
- readonly "__#1@#inverseTransform": Mat33;
13
+ readonly "__#678@#inverseTransform": Mat33;
14
14
  readonly transform: Mat33;
15
15
  apply(editor: Editor): void;
16
16
  unapply(editor: Editor): void;
@@ -0,0 +1,15 @@
1
+ import LineSegment2 from '../geometry/LineSegment2';
2
+ import Mat33 from '../geometry/Mat33';
3
+ import Rect2 from '../geometry/Rect2';
4
+ import AbstractRenderer from '../rendering/AbstractRenderer';
5
+ import AbstractComponent from './AbstractComponent';
6
+ import { ImageComponentLocalization } from './localization';
7
+ export default class SVGGlobalAttributesObject extends AbstractComponent {
8
+ private readonly attrs;
9
+ protected contentBBox: Rect2;
10
+ constructor(attrs: Array<[string, string | null]>);
11
+ render(canvas: AbstractRenderer, _visibleRect?: Rect2): void;
12
+ intersects(_lineSegment: LineSegment2): boolean;
13
+ protected applyTransformation(_affineTransfm: Mat33): void;
14
+ description(localization: ImageComponentLocalization): string;
15
+ }
@@ -0,0 +1,29 @@
1
+ import Rect2 from '../geometry/Rect2';
2
+ import SVGRenderer from '../rendering/SVGRenderer';
3
+ import AbstractComponent from './AbstractComponent';
4
+ // Stores global SVG attributes (e.g. namespace identifiers.)
5
+ export default class SVGGlobalAttributesObject extends AbstractComponent {
6
+ constructor(attrs) {
7
+ super();
8
+ this.attrs = attrs;
9
+ this.contentBBox = Rect2.empty;
10
+ }
11
+ render(canvas, _visibleRect) {
12
+ if (!(canvas instanceof SVGRenderer)) {
13
+ // Don't draw unrenderable objects if we can't
14
+ return;
15
+ }
16
+ console.log('Rendering to SVG.', this.attrs);
17
+ for (const [attr, value] of this.attrs) {
18
+ canvas.setRootSVGAttribute(attr, value);
19
+ }
20
+ }
21
+ intersects(_lineSegment) {
22
+ return false;
23
+ }
24
+ applyTransformation(_affineTransfm) {
25
+ }
26
+ description(localization) {
27
+ return localization.svgObject;
28
+ }
29
+ }
@@ -10,7 +10,9 @@ export default class SVGRenderer extends AbstractRenderer {
10
10
  private lastPath;
11
11
  private lastPathStart;
12
12
  private mainGroup;
13
+ private overwrittenAttrs;
13
14
  constructor(elem: SVGSVGElement, viewport: Viewport);
15
+ setRootSVGAttribute(name: string, value: string | null): void;
14
16
  displaySize(): Vec2;
15
17
  clear(): void;
16
18
  protected beginPath(startPoint: Point2): void;
@@ -6,13 +6,38 @@ export default class SVGRenderer extends AbstractRenderer {
6
6
  constructor(elem, viewport) {
7
7
  super(viewport);
8
8
  this.elem = elem;
9
+ this.overwrittenAttrs = {};
9
10
  this.clear();
10
11
  }
12
+ // Sets an attribute on the root SVG element.
13
+ setRootSVGAttribute(name, value) {
14
+ // Make the original value of the attribute restorable on clear
15
+ if (!(name in this.overwrittenAttrs)) {
16
+ this.overwrittenAttrs[name] = this.elem.getAttribute(name);
17
+ }
18
+ if (value !== null) {
19
+ this.elem.setAttribute(name, value);
20
+ }
21
+ else {
22
+ this.elem.removeAttribute(name);
23
+ }
24
+ }
11
25
  displaySize() {
12
26
  return Vec2.of(this.elem.clientWidth, this.elem.clientHeight);
13
27
  }
14
28
  clear() {
15
29
  this.mainGroup = document.createElementNS(svgNameSpace, 'g');
30
+ // Restore all alltributes
31
+ for (const attrName in this.overwrittenAttrs) {
32
+ const value = this.overwrittenAttrs[attrName];
33
+ if (value) {
34
+ this.elem.setAttribute(attrName, value);
35
+ }
36
+ else {
37
+ this.elem.removeAttribute(attrName);
38
+ }
39
+ }
40
+ this.overwrittenAttrs = {};
16
41
  // Remove all children
17
42
  this.elem.replaceChildren(this.mainGroup);
18
43
  }
@@ -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 });
@@ -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
  }
@@ -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.6",
3
+ "version": "0.0.9",
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/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
  }
@@ -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([
package/src/SVGLoader.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import Color4 from './Color4';
2
2
  import AbstractComponent from './components/AbstractComponent';
3
3
  import Stroke from './components/Stroke';
4
+ import SVGGlobalAttributesObject from './components/SVGGlobalAttributesObject';
4
5
  import UnknownSVGObject from './components/UnknownSVGObject';
5
6
  import Path from './geometry/Path';
6
7
  import Rect2 from './geometry/Rect2';
@@ -127,6 +128,10 @@ export default class SVGLoader implements ImageLoader {
127
128
  this.rootViewBox = new Rect2(x, y, width, height);
128
129
  }
129
130
 
131
+ private updateSVGAttrs(node: SVGSVGElement) {
132
+ this.onAddComponent?.(new SVGGlobalAttributesObject(this.getSourceAttrs(node)));
133
+ }
134
+
130
135
  private async visit(node: Element) {
131
136
  this.totalToProcess += node.childElementCount;
132
137
 
@@ -139,6 +144,7 @@ export default class SVGLoader implements ImageLoader {
139
144
  break;
140
145
  case 'svg':
141
146
  this.updateViewBox(node as SVGSVGElement);
147
+ this.updateSVGAttrs(node as SVGSVGElement);
142
148
  break;
143
149
  default:
144
150
  console.warn('Unknown SVG element,', node);
@@ -158,6 +164,13 @@ export default class SVGLoader implements ImageLoader {
158
164
  await this.onProgress?.(this.processedCount, this.totalToProcess);
159
165
  }
160
166
 
167
+ // Get SVG element attributes (e.g. xlink=...)
168
+ private getSourceAttrs(node: SVGSVGElement): Array<[string, string|null]> {
169
+ return node.getAttributeNames().map(attr => {
170
+ return [ attr, node.getAttribute(attr) ];
171
+ });
172
+ }
173
+
161
174
  public async start(
162
175
  onAddComponent: ComponentAddedListener, onProgress: OnProgressListener
163
176
  ): Promise<Rect2> {
@@ -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;
@@ -0,0 +1,39 @@
1
+ import LineSegment2 from '../geometry/LineSegment2';
2
+ import Mat33 from '../geometry/Mat33';
3
+ import Rect2 from '../geometry/Rect2';
4
+ import AbstractRenderer from '../rendering/AbstractRenderer';
5
+ import SVGRenderer from '../rendering/SVGRenderer';
6
+ import AbstractComponent from './AbstractComponent';
7
+ import { ImageComponentLocalization } from './localization';
8
+
9
+ // Stores global SVG attributes (e.g. namespace identifiers.)
10
+ export default class SVGGlobalAttributesObject extends AbstractComponent {
11
+ protected contentBBox: Rect2;
12
+ public constructor(private readonly attrs: Array<[string, string|null]>) {
13
+ super();
14
+ this.contentBBox = Rect2.empty;
15
+ }
16
+
17
+ public render(canvas: AbstractRenderer, _visibleRect?: Rect2): void {
18
+ if (!(canvas instanceof SVGRenderer)) {
19
+ // Don't draw unrenderable objects if we can't
20
+ return;
21
+ }
22
+
23
+ console.log('Rendering to SVG.', this.attrs);
24
+ for (const [ attr, value ] of this.attrs) {
25
+ canvas.setRootSVGAttribute(attr, value);
26
+ }
27
+ }
28
+
29
+ public intersects(_lineSegment: LineSegment2): boolean {
30
+ return false;
31
+ }
32
+
33
+ protected applyTransformation(_affineTransfm: Mat33): void {
34
+ }
35
+
36
+ public description(localization: ImageComponentLocalization): string {
37
+ return localization.svgObject;
38
+ }
39
+ }
@@ -15,12 +15,27 @@ export default class SVGRenderer extends AbstractRenderer {
15
15
  private lastPathStart: Point2|null;
16
16
 
17
17
  private mainGroup: SVGGElement;
18
+ private overwrittenAttrs: Record<string, string|null> = {};
18
19
 
19
20
  public constructor(private elem: SVGSVGElement, viewport: Viewport) {
20
21
  super(viewport);
21
22
  this.clear();
22
23
  }
23
24
 
25
+ // Sets an attribute on the root SVG element.
26
+ public setRootSVGAttribute(name: string, value: string|null) {
27
+ // Make the original value of the attribute restorable on clear
28
+ if (!(name in this.overwrittenAttrs)) {
29
+ this.overwrittenAttrs[name] = this.elem.getAttribute(name);
30
+ }
31
+
32
+ if (value !== null) {
33
+ this.elem.setAttribute(name, value);
34
+ } else {
35
+ this.elem.removeAttribute(name);
36
+ }
37
+ }
38
+
24
39
  public displaySize(): Vec2 {
25
40
  return Vec2.of(this.elem.clientWidth, this.elem.clientHeight);
26
41
  }
@@ -28,6 +43,18 @@ export default class SVGRenderer extends AbstractRenderer {
28
43
  public clear() {
29
44
  this.mainGroup = document.createElementNS(svgNameSpace, 'g');
30
45
 
46
+ // Restore all alltributes
47
+ for (const attrName in this.overwrittenAttrs) {
48
+ const value = this.overwrittenAttrs[attrName];
49
+
50
+ if (value) {
51
+ this.elem.setAttribute(attrName, value);
52
+ } else {
53
+ this.elem.removeAttribute(attrName);
54
+ }
55
+ }
56
+ this.overwrittenAttrs = {};
57
+
31
58
  // Remove all children
32
59
  this.elem.replaceChildren(this.mainGroup);
33
60
  }
@@ -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 });
@@ -6,10 +6,11 @@ export default abstract class BaseTool implements PointerEvtListener {
6
6
  private enabled: boolean = true;
7
7
  private group: ToolEnabledGroup|null = null;
8
8
 
9
- public abstract onPointerDown(event: PointerEvt): boolean;
10
- public abstract onPointerMove(event: PointerEvt): void;
11
- public abstract onPointerUp(event: PointerEvt): void;
12
- public abstract onGestureCancel(): void;
9
+ public onPointerDown(_event: PointerEvt): boolean { return false; }
10
+ public onPointerMove(_event: PointerEvt) { }
11
+ public onPointerUp(_event: PointerEvt) { }
12
+ public onGestureCancel() { }
13
+
13
14
  public abstract readonly kind: ToolType;
14
15
 
15
16
  protected constructor(private notifier: EditorNotifier, public readonly description: string) {
@@ -8,6 +8,7 @@ import Eraser from './Eraser';
8
8
  import SelectionTool from './SelectionTool';
9
9
  import Color4 from '../Color4';
10
10
  import { ToolLocalization } from './localization';
11
+ import UndoRedoShortcut from './UndoRedoShortcut';
11
12
 
12
13
  export enum ToolType {
13
14
  TouchPanZoom,
@@ -15,6 +16,7 @@ export enum ToolType {
15
16
  Selection,
16
17
  Eraser,
17
18
  PanZoom,
19
+ UndoRedoShortcut,
18
20
  }
19
21
 
20
22
  export default class ToolController {
@@ -40,6 +42,7 @@ export default class ToolController {
40
42
  touchPanZoom,
41
43
  ...primaryTools,
42
44
  new PanZoom(editor, PanZoomMode.TwoFingerGestures | PanZoomMode.AnyDevice, localization.twoFingerPanZoomTool),
45
+ new UndoRedoShortcut(editor),
43
46
  ];
44
47
  primaryTools.forEach(tool => tool.setToolGroup(primaryToolEnabledGroup));
45
48
  touchPanZoom.setEnabled(false);
@@ -0,0 +1,53 @@
1
+ /* @jest-environment jsdom */
2
+
3
+ import Color4 from '../Color4';
4
+ import Stroke from '../components/Stroke';
5
+ import EditorImage from '../EditorImage';
6
+ import Path from '../geometry/Path';
7
+ import createEditor from '../testing/createEditor';
8
+ import { InputEvtType } from '../types';
9
+
10
+ describe('UndoRedoShortcut', () => {
11
+ const testStroke = new Stroke([Path.fromString('M0,0L10,10').toRenderable({ fill: Color4.red })]);
12
+ const addTestStrokeCommand = new EditorImage.AddElementCommand(testStroke);
13
+
14
+ it('ctrl+z should undo', () => {
15
+ const editor = createEditor();
16
+ editor.dispatch(addTestStrokeCommand);
17
+ expect(editor.history.undoStackSize).toBe(1);
18
+
19
+ editor.toolController.dispatchInputEvent({
20
+ kind: InputEvtType.KeyPressEvent,
21
+ ctrlKey: true,
22
+ key: 'z',
23
+ });
24
+
25
+ expect(editor.history.undoStackSize).toBe(0);
26
+ expect(editor.history.redoStackSize).toBe(1);
27
+ });
28
+
29
+ it('ctrl+shift+Z should re-do', () => {
30
+ const editor = createEditor();
31
+ editor.dispatch(addTestStrokeCommand);
32
+ editor.dispatch(addTestStrokeCommand);
33
+ expect(editor.history.undoStackSize).toBe(2);
34
+
35
+ editor.toolController.dispatchInputEvent({
36
+ kind: InputEvtType.KeyPressEvent,
37
+ ctrlKey: true,
38
+ key: 'z',
39
+ });
40
+
41
+ expect(editor.history.undoStackSize).toBe(1);
42
+ expect(editor.history.redoStackSize).toBe(1);
43
+
44
+ editor.toolController.dispatchInputEvent({
45
+ kind: InputEvtType.KeyPressEvent,
46
+ ctrlKey: true,
47
+ key: 'Z',
48
+ });
49
+
50
+ expect(editor.history.undoStackSize).toBe(2);
51
+ expect(editor.history.redoStackSize).toBe(0);
52
+ });
53
+ });
@@ -0,0 +1,28 @@
1
+ import Editor from '../Editor';
2
+ import { KeyPressEvent } from '../types';
3
+ import BaseTool from './BaseTool';
4
+ import { ToolType } from './ToolController';
5
+
6
+
7
+ export default class UndoRedoShortcut extends BaseTool {
8
+ public kind: ToolType.UndoRedoShortcut = ToolType.UndoRedoShortcut;
9
+
10
+ public constructor(private editor: Editor) {
11
+ super(editor.notifier, editor.localization.undoRedoTool);
12
+ }
13
+
14
+ // Activate undo/redo
15
+ public onKeyPress({ key, ctrlKey }: KeyPressEvent): boolean {
16
+ if (ctrlKey) {
17
+ if (key === 'z') {
18
+ this.editor.history.undo();
19
+ return true;
20
+ } else if (key === 'Z') {
21
+ this.editor.history.redo();
22
+ return true;
23
+ }
24
+ }
25
+
26
+ return false;
27
+ }
28
+ }