js-draw 0.0.5 → 0.0.8

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 (40) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +13 -0
  3. package/dist/build_tools/bundle.js +2 -2
  4. package/dist/bundle.js +1 -1
  5. package/dist/src/Editor.js +9 -1
  6. package/dist/src/EditorImage.d.ts +2 -2
  7. package/dist/src/SVGLoader.d.ts +2 -0
  8. package/dist/src/SVGLoader.js +15 -1
  9. package/dist/src/UndoRedoHistory.d.ts +2 -0
  10. package/dist/src/Viewport.d.ts +1 -1
  11. package/dist/src/components/SVGGlobalAttributesObject.d.ts +15 -0
  12. package/dist/src/components/SVGGlobalAttributesObject.js +29 -0
  13. package/dist/src/components/builders/FreehandLineBuilder.js +15 -3
  14. package/dist/src/geometry/Path.js +11 -0
  15. package/dist/src/htmlUtil.d.ts +1 -0
  16. package/dist/src/rendering/SVGRenderer.d.ts +2 -0
  17. package/dist/src/rendering/SVGRenderer.js +25 -0
  18. package/dist/src/tools/BaseTool.d.ts +4 -4
  19. package/dist/src/tools/SelectionTool.js +0 -1
  20. package/dist/src/tools/ToolController.d.ts +2 -1
  21. package/dist/src/tools/UndoRedoShortcut.d.ts +10 -0
  22. package/dist/src/tools/localization.d.ts +1 -0
  23. package/dist/src/types.d.ts +1 -0
  24. package/package.json +2 -2
  25. package/src/Editor.ts +12 -1
  26. package/src/EditorImage.test.ts +1 -4
  27. package/src/SVGLoader.ts +18 -1
  28. package/src/UndoRedoHistory.ts +8 -0
  29. package/src/components/SVGGlobalAttributesObject.ts +39 -0
  30. package/src/components/builders/FreehandLineBuilder.ts +23 -4
  31. package/src/geometry/Path.fromString.test.ts +11 -24
  32. package/src/geometry/Path.ts +13 -0
  33. package/src/rendering/SVGRenderer.ts +27 -0
  34. package/src/testing/createEditor.ts +4 -0
  35. package/src/tools/BaseTool.ts +5 -4
  36. package/src/tools/ToolController.ts +3 -0
  37. package/src/tools/UndoRedoShortcut.test.ts +53 -0
  38. package/src/tools/UndoRedoShortcut.ts +28 -0
  39. package/src/tools/localization.ts +2 -0
  40. package/src/types.ts +1 -0
@@ -367,16 +367,28 @@ export default class Path {
367
367
 
368
368
  let lastPos: Point2 = Vec2.zero;
369
369
  let firstPos: Point2|null = null;
370
+ let isFirstCommand: boolean = true;
370
371
  const commands: PathCommand[] = [];
371
372
 
372
373
 
373
374
  const moveTo = (point: Point2) => {
375
+ // The first moveTo/lineTo is already handled by the [startPoint] parameter of the Path constructor.
376
+ if (isFirstCommand) {
377
+ isFirstCommand = false;
378
+ return;
379
+ }
380
+
374
381
  commands.push({
375
382
  kind: PathCommandType.MoveTo,
376
383
  point,
377
384
  });
378
385
  };
379
386
  const lineTo = (point: Point2) => {
387
+ if (isFirstCommand) {
388
+ isFirstCommand = false;
389
+ return;
390
+ }
391
+
380
392
  commands.push({
381
393
  kind: PathCommandType.LineTo,
382
394
  point,
@@ -492,6 +504,7 @@ export default class Path {
492
504
  if (args.length > 0) {
493
505
  firstPos ??= args[0];
494
506
  }
507
+ isFirstCommand = false;
495
508
  }
496
509
 
497
510
  return new Path(firstPos ?? Vec2.zero, commands);
@@ -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
+ }
@@ -5,6 +5,7 @@ export interface ToolLocalization {
5
5
  eraserTool: string;
6
6
  touchPanTool: string;
7
7
  twoFingerPanZoomTool: string;
8
+ undoRedoTool: string;
8
9
 
9
10
  toolEnabledAnnouncement: (toolName: string) => string;
10
11
  toolDisabledAnnouncement: (toolName: string) => string;
@@ -16,6 +17,7 @@ export const defaultToolLocalization: ToolLocalization = {
16
17
  eraserTool: 'Eraser',
17
18
  touchPanTool: 'Touch Panning',
18
19
  twoFingerPanZoomTool: 'Panning and Zooming',
20
+ undoRedoTool: 'Undo/Redo',
19
21
 
20
22
  toolEnabledAnnouncement: (toolName) => `${toolName} enabled`,
21
23
  toolDisabledAnnouncement: (toolName) => `${toolName} disabled`,
package/src/types.ts CHANGED
@@ -46,6 +46,7 @@ export interface WheelEvt {
46
46
  export interface KeyPressEvent {
47
47
  readonly kind: InputEvtType.KeyPressEvent;
48
48
  readonly key: string;
49
+ readonly ctrlKey: boolean;
49
50
  }
50
51
 
51
52
  // Event triggered when pointer capture is taken by a different [PointerEvtListener].