js-draw 1.17.0 → 1.18.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 (63) hide show
  1. package/README.md +70 -10
  2. package/dist/bundle.js +2 -2
  3. package/dist/cjs/Editor.d.ts +18 -20
  4. package/dist/cjs/Editor.js +5 -2
  5. package/dist/cjs/components/AbstractComponent.d.ts +17 -5
  6. package/dist/cjs/components/AbstractComponent.js +15 -15
  7. package/dist/cjs/components/Stroke.d.ts +4 -1
  8. package/dist/cjs/components/Stroke.js +158 -2
  9. package/dist/cjs/components/builders/PolylineBuilder.d.ts +1 -1
  10. package/dist/cjs/components/builders/PolylineBuilder.js +9 -2
  11. package/dist/cjs/components/builders/PressureSensitiveFreehandLineBuilder.d.ts +1 -1
  12. package/dist/cjs/components/builders/PressureSensitiveFreehandLineBuilder.js +44 -51
  13. package/dist/cjs/image/EditorImage.js +1 -1
  14. package/dist/cjs/localizations/de.js +1 -1
  15. package/dist/cjs/localizations/es.js +1 -1
  16. package/dist/cjs/testing/createEditor.d.ts +2 -2
  17. package/dist/cjs/testing/createEditor.js +2 -2
  18. package/dist/cjs/toolbar/IconProvider.d.ts +3 -1
  19. package/dist/cjs/toolbar/IconProvider.js +15 -3
  20. package/dist/cjs/toolbar/localization.d.ts +6 -1
  21. package/dist/cjs/toolbar/localization.js +7 -2
  22. package/dist/cjs/toolbar/widgets/EraserToolWidget.d.ts +6 -1
  23. package/dist/cjs/toolbar/widgets/EraserToolWidget.js +45 -5
  24. package/dist/cjs/toolbar/widgets/PenToolWidget.js +10 -3
  25. package/dist/cjs/toolbar/widgets/PenToolWidget.test.d.ts +1 -0
  26. package/dist/cjs/toolbar/widgets/keybindings.js +1 -1
  27. package/dist/cjs/tools/Eraser.d.ts +24 -4
  28. package/dist/cjs/tools/Eraser.js +107 -20
  29. package/dist/cjs/tools/PasteHandler.js +0 -1
  30. package/dist/cjs/tools/lib.d.ts +1 -4
  31. package/dist/cjs/tools/lib.js +2 -4
  32. package/dist/cjs/version.js +1 -1
  33. package/dist/mjs/Editor.d.ts +18 -20
  34. package/dist/mjs/Editor.mjs +5 -2
  35. package/dist/mjs/components/AbstractComponent.d.ts +17 -5
  36. package/dist/mjs/components/AbstractComponent.mjs +15 -15
  37. package/dist/mjs/components/Stroke.d.ts +4 -1
  38. package/dist/mjs/components/Stroke.mjs +159 -3
  39. package/dist/mjs/components/builders/PolylineBuilder.d.ts +1 -1
  40. package/dist/mjs/components/builders/PolylineBuilder.mjs +10 -3
  41. package/dist/mjs/components/builders/PressureSensitiveFreehandLineBuilder.d.ts +1 -1
  42. package/dist/mjs/components/builders/PressureSensitiveFreehandLineBuilder.mjs +45 -52
  43. package/dist/mjs/image/EditorImage.mjs +1 -1
  44. package/dist/mjs/localizations/de.mjs +1 -1
  45. package/dist/mjs/localizations/es.mjs +1 -1
  46. package/dist/mjs/testing/createEditor.d.ts +2 -2
  47. package/dist/mjs/testing/createEditor.mjs +2 -2
  48. package/dist/mjs/toolbar/IconProvider.d.ts +3 -1
  49. package/dist/mjs/toolbar/IconProvider.mjs +15 -3
  50. package/dist/mjs/toolbar/localization.d.ts +6 -1
  51. package/dist/mjs/toolbar/localization.mjs +7 -2
  52. package/dist/mjs/toolbar/widgets/EraserToolWidget.d.ts +6 -1
  53. package/dist/mjs/toolbar/widgets/EraserToolWidget.mjs +47 -6
  54. package/dist/mjs/toolbar/widgets/PenToolWidget.mjs +10 -3
  55. package/dist/mjs/toolbar/widgets/PenToolWidget.test.d.ts +1 -0
  56. package/dist/mjs/toolbar/widgets/keybindings.mjs +1 -1
  57. package/dist/mjs/tools/Eraser.d.ts +24 -4
  58. package/dist/mjs/tools/Eraser.mjs +107 -21
  59. package/dist/mjs/tools/PasteHandler.mjs +0 -1
  60. package/dist/mjs/tools/lib.d.ts +1 -4
  61. package/dist/mjs/tools/lib.mjs +1 -4
  62. package/dist/mjs/version.mjs +1 -1
  63. package/package.json +3 -3
@@ -1,19 +1,28 @@
1
1
  import { EditorEventType } from '../types.mjs';
2
2
  import BaseTool from './BaseTool.mjs';
3
- import { Vec2, LineSegment2, Color4, Rect2 } from '@js-draw/math';
3
+ import { Vec2, LineSegment2, Color4, Rect2, Path } from '@js-draw/math';
4
4
  import Erase from '../commands/Erase.mjs';
5
5
  import { PointerDevice } from '../Pointer.mjs';
6
6
  import { decreaseSizeKeyboardShortcutId, increaseSizeKeyboardShortcutId } from './keybindings.mjs';
7
7
  import { ReactiveValue } from '../util/ReactiveValue.mjs';
8
+ import EditorImage from '../image/EditorImage.mjs';
9
+ import uniteCommands from '../commands/uniteCommands.mjs';
10
+ import { pathToRenderable } from '../rendering/RenderablePathSpec.mjs';
11
+ export var EraserMode;
12
+ (function (EraserMode) {
13
+ EraserMode["PartialStroke"] = "partial-stroke";
14
+ EraserMode["FullStroke"] = "full-stroke";
15
+ })(EraserMode || (EraserMode = {}));
8
16
  export default class Eraser extends BaseTool {
9
- constructor(editor, description) {
17
+ constructor(editor, description, options) {
10
18
  super(editor.notifier, description);
11
19
  this.editor = editor;
12
20
  this.lastPoint = null;
13
21
  this.isFirstEraseEvt = true;
14
- this.thickness = 10;
15
22
  // Commands that each remove one element
16
- this.partialCommands = [];
23
+ this.eraseCommands = [];
24
+ this.addCommands = [];
25
+ this.thickness = options?.thickness ?? 10;
17
26
  this.thicknessValue = ReactiveValue.fromInitialValue(this.thickness);
18
27
  this.thicknessValue.onUpdate(value => {
19
28
  this.thickness = value;
@@ -22,6 +31,13 @@ export default class Eraser extends BaseTool {
22
31
  tool: this,
23
32
  });
24
33
  });
34
+ this.modeValue = ReactiveValue.fromInitialValue(options?.mode ?? EraserMode.FullStroke);
35
+ this.modeValue.onUpdate(_value => {
36
+ this.editor.notifier.dispatch(EditorEventType.ToolUpdated, {
37
+ kind: EditorEventType.ToolUpdated,
38
+ tool: this,
39
+ });
40
+ });
25
41
  }
26
42
  clearPreview() {
27
43
  this.editor.clearWetInk();
@@ -34,16 +50,24 @@ export default class Eraser extends BaseTool {
34
50
  const size = this.getSizeOnCanvas();
35
51
  const renderer = this.editor.display.getWetInkRenderer();
36
52
  const rect = this.getEraserRect(point);
53
+ const rect2 = this.getEraserRect(this.lastPoint ?? point);
37
54
  const fill = {
38
- fill: Color4.gray,
55
+ fill: Color4.transparent,
56
+ stroke: { width: size / 10, color: Color4.gray },
39
57
  };
40
- renderer.drawRect(rect, size / 4, fill);
58
+ renderer.drawPath(pathToRenderable(Path.fromConvexHullOf([...rect.corners, ...rect2.corners]), fill));
41
59
  }
60
+ /**
61
+ * @returns the eraser rectangle in canvas coordinates.
62
+ *
63
+ * For now, all erasers are rectangles or points.
64
+ */
42
65
  getEraserRect(centerPoint) {
43
66
  const size = this.getSizeOnCanvas();
44
67
  const halfSize = Vec2.of(size / 2, size / 2);
45
68
  return Rect2.fromCorners(centerPoint.minus(halfSize), centerPoint.plus(halfSize));
46
69
  }
70
+ /** Erases in a line from the last point to the current. */
47
71
  eraseTo(currentPoint) {
48
72
  if (!this.isFirstEraseEvt && currentPoint.distanceTo(this.lastPoint) === 0) {
49
73
  return;
@@ -60,13 +84,55 @@ export default class Eraser extends BaseTool {
60
84
  });
61
85
  // Only erase components that could be selected (and thus interacted with)
62
86
  // by the user.
63
- const toErase = intersectingElems.filter(elem => elem.isSelectable());
64
- // Remove any intersecting elements.
65
- this.toRemove.push(...toErase);
66
- // Create new Erase commands for the now-to-be-erased elements and apply them.
67
- const newPartialCommands = toErase.map(elem => new Erase([elem]));
68
- newPartialCommands.forEach(cmd => cmd.apply(this.editor));
69
- this.partialCommands.push(...newPartialCommands);
87
+ const eraseableElems = intersectingElems.filter(elem => elem.isSelectable());
88
+ if (this.modeValue.get() === EraserMode.FullStroke) {
89
+ // Remove any intersecting elements.
90
+ this.toRemove.push(...eraseableElems);
91
+ // Create new Erase commands for the now-to-be-erased elements and apply them.
92
+ const newPartialCommands = eraseableElems.map(elem => new Erase([elem]));
93
+ newPartialCommands.forEach(cmd => cmd.apply(this.editor));
94
+ this.eraseCommands.push(...newPartialCommands);
95
+ }
96
+ else {
97
+ const toErase = [];
98
+ const toAdd = [];
99
+ for (const targetElem of eraseableElems) {
100
+ toErase.push(targetElem);
101
+ // Completely delete items that can't be divided.
102
+ if (!targetElem.withRegionErased) {
103
+ continue;
104
+ }
105
+ // Completely delete items that are completely or almost completely
106
+ // contained within the eraser.
107
+ const grownRect = eraserRect.grownBy(eraserRect.maxDimension / 3);
108
+ if (grownRect.containsRect(targetElem.getExactBBox())) {
109
+ continue;
110
+ }
111
+ // Join the current and previous rectangles so that points between events are also
112
+ // erased.
113
+ const erasePath = Path.fromConvexHullOf([
114
+ ...eraserRect.corners, ...this.getEraserRect(this.lastPoint ?? currentPoint).corners
115
+ ].map(p => this.editor.viewport.roundPoint(p)));
116
+ toAdd.push(...targetElem.withRegionErased(erasePath, this.editor.viewport));
117
+ }
118
+ const eraseCommand = new Erase(toErase);
119
+ const newAddCommands = toAdd.map(elem => EditorImage.addElement(elem));
120
+ eraseCommand.apply(this.editor);
121
+ newAddCommands.forEach(command => command.apply(this.editor));
122
+ const finalToErase = [];
123
+ for (const item of toErase) {
124
+ if (this.toAdd.includes(item)) {
125
+ this.toAdd = this.toAdd.filter(i => i !== item);
126
+ }
127
+ else {
128
+ finalToErase.push(item);
129
+ }
130
+ }
131
+ this.toRemove.push(...finalToErase);
132
+ this.toAdd.push(...toAdd);
133
+ this.eraseCommands.push(new Erase(finalToErase));
134
+ this.addCommands.push(...newAddCommands);
135
+ }
70
136
  this.drawPreviewAt(currentPoint);
71
137
  this.lastPoint = currentPoint;
72
138
  }
@@ -74,6 +140,7 @@ export default class Eraser extends BaseTool {
74
140
  if (event.allPointers.length === 1 || event.current.device === PointerDevice.Eraser) {
75
141
  this.lastPoint = event.current.canvasPos;
76
142
  this.toRemove = [];
143
+ this.toAdd = [];
77
144
  this.isFirstEraseEvt = true;
78
145
  this.drawPreviewAt(event.current.canvasPos);
79
146
  return true;
@@ -86,18 +153,32 @@ export default class Eraser extends BaseTool {
86
153
  }
87
154
  onPointerUp(event) {
88
155
  this.eraseTo(event.current.canvasPos);
89
- if (this.toRemove.length > 0) {
156
+ const commands = [];
157
+ if (this.addCommands.length > 0) {
158
+ this.addCommands.forEach(cmd => cmd.unapply(this.editor));
159
+ commands.push(...this.toAdd.map(a => EditorImage.addElement(a)));
160
+ this.addCommands = [];
161
+ }
162
+ if (this.eraseCommands.length > 0) {
90
163
  // Undo commands for each individual component and unite into a single command.
91
- this.partialCommands.forEach(cmd => cmd.unapply(this.editor));
92
- this.partialCommands = [];
164
+ this.eraseCommands.forEach(cmd => cmd.unapply(this.editor));
165
+ this.eraseCommands = [];
93
166
  const command = new Erase(this.toRemove);
94
- this.editor.dispatch(command); // dispatch: Makes undo-able.
167
+ commands.push(command);
168
+ }
169
+ if (commands.length === 1) {
170
+ this.editor.dispatch(commands[0]); // dispatch: Makes undo-able.
171
+ }
172
+ else {
173
+ this.editor.dispatch(uniteCommands(commands));
95
174
  }
96
175
  this.clearPreview();
97
176
  }
98
177
  onGestureCancel() {
99
- this.partialCommands.forEach(cmd => cmd.unapply(this.editor));
100
- this.partialCommands = [];
178
+ this.addCommands.forEach(cmd => cmd.unapply(this.editor));
179
+ this.eraseCommands.forEach(cmd => cmd.unapply(this.editor));
180
+ this.eraseCommands = [];
181
+ this.addCommands = [];
101
182
  this.clearPreview();
102
183
  }
103
184
  onKeyPress(event) {
@@ -116,9 +197,14 @@ export default class Eraser extends BaseTool {
116
197
  }
117
198
  return false;
118
199
  }
200
+ /** Returns the side-length of the tip of this eraser. */
119
201
  getThickness() {
120
202
  return this.thickness;
121
203
  }
204
+ /** Sets the side-length of this' tip. */
205
+ setThickness(thickness) {
206
+ this.thicknessValue.set(thickness);
207
+ }
122
208
  /**
123
209
  * Returns a {@link MutableReactiveValue} that can be used to watch
124
210
  * this tool's thickness.
@@ -126,7 +212,7 @@ export default class Eraser extends BaseTool {
126
212
  getThicknessValue() {
127
213
  return this.thicknessValue;
128
214
  }
129
- setThickness(thickness) {
130
- this.thicknessValue.set(thickness);
215
+ getModeValue() {
216
+ return this.modeValue;
131
217
  }
132
218
  }
@@ -44,7 +44,6 @@ export default class PasteHandler extends BaseTool {
44
44
  return event.data.substring(event.data.search(/<svg/i), svgEnd);
45
45
  })();
46
46
  if (svgData) {
47
- console.log('svgpaste', svgData);
48
47
  void this.doSVGPaste(svgData);
49
48
  return true;
50
49
  }
@@ -1,6 +1,3 @@
1
- /**
2
- * @packageDocumentation
3
- */
4
1
  export { default as BaseTool } from './BaseTool';
5
2
  export { default as ToolController } from './ToolController';
6
3
  export { default as ToolEnabledGroup } from './ToolEnabledGroup';
@@ -11,7 +8,7 @@ export { default as PenTool, PenStyle } from './Pen';
11
8
  export { default as TextTool } from './TextTool';
12
9
  export { default as SelectionTool } from './SelectionTool/SelectionTool';
13
10
  export { default as SelectAllShortcutHandler } from './SelectionTool/SelectAllShortcutHandler';
14
- export { default as EraserTool } from './Eraser';
11
+ export { default as EraserTool, EraserMode } from './Eraser';
15
12
  export { default as PasteHandler } from './PasteHandler';
16
13
  export { default as SoundUITool } from './SoundUITool';
17
14
  export { default as ToolbarShortcutHandler } from './ToolbarShortcutHandler';
@@ -1,6 +1,3 @@
1
- /**
2
- * @packageDocumentation
3
- */
4
1
  export { default as BaseTool } from './BaseTool.mjs';
5
2
  export { default as ToolController } from './ToolController.mjs';
6
3
  export { default as ToolEnabledGroup } from './ToolEnabledGroup.mjs';
@@ -11,7 +8,7 @@ export { default as PenTool } from './Pen.mjs';
11
8
  export { default as TextTool } from './TextTool.mjs';
12
9
  export { default as SelectionTool } from './SelectionTool/SelectionTool.mjs';
13
10
  export { default as SelectAllShortcutHandler } from './SelectionTool/SelectAllShortcutHandler.mjs';
14
- export { default as EraserTool } from './Eraser.mjs';
11
+ export { default as EraserTool, EraserMode } from './Eraser.mjs';
15
12
  export { default as PasteHandler } from './PasteHandler.mjs';
16
13
  export { default as SoundUITool } from './SoundUITool.mjs';
17
14
  export { default as ToolbarShortcutHandler } from './ToolbarShortcutHandler.mjs';
@@ -4,5 +4,5 @@
4
4
  * @internal
5
5
  */
6
6
  export default {
7
- number: '1.17.0',
7
+ number: '1.18.0',
8
8
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "1.17.0",
3
+ "version": "1.18.0",
4
4
  "description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
5
5
  "types": "./dist/mjs/lib.d.ts",
6
6
  "main": "./dist/cjs/lib.js",
@@ -64,7 +64,7 @@
64
64
  "postpack": "ts-node tools/copyREADME.ts revert"
65
65
  },
66
66
  "dependencies": {
67
- "@js-draw/math": "^1.17.0",
67
+ "@js-draw/math": "^1.18.0",
68
68
  "@melloware/coloris": "0.22.0"
69
69
  },
70
70
  "devDependencies": {
@@ -86,5 +86,5 @@
86
86
  "freehand",
87
87
  "svg"
88
88
  ],
89
- "gitHead": "d0eff585750ab5670af3acda8ddff090e8825bd3"
89
+ "gitHead": "73c0d802a8439b5d408ba1e60f91be029db7e402"
90
90
  }