js-draw 1.17.0 → 1.18.0

Sign up to get free protection for your applications and to get access to all the features.
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
  }