js-draw 1.0.1 → 1.1.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 (197) hide show
  1. package/LICENSE +21 -0
  2. package/dist/Editor.css +1 -0
  3. package/dist/bundle.js +1 -1
  4. package/dist/bundledStyles.js +1 -1
  5. package/dist/cjs/toolbar/AbstractToolbar.d.ts +9 -13
  6. package/dist/cjs/toolbar/AbstractToolbar.js +14 -19
  7. package/dist/cjs/toolbar/widgets/SaveActionWidget.d.ts +10 -0
  8. package/dist/cjs/toolbar/widgets/SaveActionWidget.js +26 -0
  9. package/dist/cjs/toolbar/widgets/keybindings.d.ts +1 -0
  10. package/dist/cjs/toolbar/widgets/keybindings.js +4 -1
  11. package/dist/cjs/version.js +1 -1
  12. package/dist/mjs/toolbar/AbstractToolbar.d.ts +9 -13
  13. package/dist/mjs/toolbar/AbstractToolbar.mjs +14 -19
  14. package/dist/mjs/toolbar/widgets/SaveActionWidget.d.ts +10 -0
  15. package/dist/mjs/toolbar/widgets/SaveActionWidget.mjs +21 -0
  16. package/dist/mjs/toolbar/widgets/keybindings.d.ts +1 -0
  17. package/dist/mjs/toolbar/widgets/keybindings.mjs +3 -0
  18. package/dist/mjs/version.mjs +1 -1
  19. package/docs/img/readme-images/js-draw.jpg +0 -0
  20. package/docs/img/readme-images/unsupported-elements--in-editor.png +0 -0
  21. package/package.json +5 -4
  22. package/src/toolbar/EdgeToolbar.scss +1 -0
  23. package/dist-test/test_imports/package-lock.json +0 -13
  24. package/dist-test/test_imports/package.json +0 -12
  25. package/dist-test/test_imports/test-imports.js +0 -11
  26. package/dist-test/test_imports/test-require.cjs +0 -14
  27. package/src/Editor.loadFrom.test.ts +0 -24
  28. package/src/Editor.test.ts +0 -107
  29. package/src/Editor.toSVG.test.ts +0 -294
  30. package/src/Editor.ts +0 -1443
  31. package/src/EditorImage.test.ts +0 -117
  32. package/src/EditorImage.ts +0 -609
  33. package/src/EventDispatcher.test.ts +0 -123
  34. package/src/EventDispatcher.ts +0 -72
  35. package/src/Pointer.ts +0 -183
  36. package/src/SVGLoader.test.ts +0 -114
  37. package/src/SVGLoader.ts +0 -672
  38. package/src/UndoRedoHistory.test.ts +0 -34
  39. package/src/UndoRedoHistory.ts +0 -102
  40. package/src/Viewport.ts +0 -322
  41. package/src/bundle/bundled.ts +0 -7
  42. package/src/commands/Command.ts +0 -45
  43. package/src/commands/Duplicate.ts +0 -75
  44. package/src/commands/Erase.ts +0 -95
  45. package/src/commands/SerializableCommand.ts +0 -49
  46. package/src/commands/UnresolvedCommand.ts +0 -37
  47. package/src/commands/invertCommand.ts +0 -58
  48. package/src/commands/lib.ts +0 -16
  49. package/src/commands/localization.ts +0 -47
  50. package/src/commands/uniteCommands.test.ts +0 -23
  51. package/src/commands/uniteCommands.ts +0 -140
  52. package/src/components/AbstractComponent.transformBy.test.ts +0 -23
  53. package/src/components/AbstractComponent.ts +0 -383
  54. package/src/components/BackgroundComponent.test.ts +0 -44
  55. package/src/components/BackgroundComponent.ts +0 -348
  56. package/src/components/ImageComponent.ts +0 -176
  57. package/src/components/RestylableComponent.ts +0 -161
  58. package/src/components/SVGGlobalAttributesObject.ts +0 -79
  59. package/src/components/Stroke.test.ts +0 -137
  60. package/src/components/Stroke.ts +0 -294
  61. package/src/components/TextComponent.test.ts +0 -202
  62. package/src/components/TextComponent.ts +0 -429
  63. package/src/components/UnknownSVGObject.test.ts +0 -10
  64. package/src/components/UnknownSVGObject.ts +0 -60
  65. package/src/components/builders/ArrowBuilder.ts +0 -106
  66. package/src/components/builders/CircleBuilder.ts +0 -100
  67. package/src/components/builders/FreehandLineBuilder.test.ts +0 -24
  68. package/src/components/builders/FreehandLineBuilder.ts +0 -210
  69. package/src/components/builders/LineBuilder.ts +0 -77
  70. package/src/components/builders/PressureSensitiveFreehandLineBuilder.ts +0 -453
  71. package/src/components/builders/RectangleBuilder.ts +0 -73
  72. package/src/components/builders/types.ts +0 -15
  73. package/src/components/lib.ts +0 -31
  74. package/src/components/localization.ts +0 -24
  75. package/src/components/util/StrokeSmoother.ts +0 -302
  76. package/src/components/util/describeComponentList.ts +0 -18
  77. package/src/dialogs/makeAboutDialog.ts +0 -82
  78. package/src/inputEvents.ts +0 -143
  79. package/src/lib.ts +0 -91
  80. package/src/localization.ts +0 -34
  81. package/src/localizations/de.ts +0 -146
  82. package/src/localizations/en.ts +0 -8
  83. package/src/localizations/es.ts +0 -74
  84. package/src/localizations/getLocalizationTable.test.ts +0 -27
  85. package/src/localizations/getLocalizationTable.ts +0 -74
  86. package/src/rendering/Display.ts +0 -247
  87. package/src/rendering/RenderablePathSpec.ts +0 -88
  88. package/src/rendering/RenderingStyle.test.ts +0 -68
  89. package/src/rendering/RenderingStyle.ts +0 -55
  90. package/src/rendering/TextRenderingStyle.ts +0 -55
  91. package/src/rendering/caching/CacheRecord.test.ts +0 -48
  92. package/src/rendering/caching/CacheRecord.ts +0 -76
  93. package/src/rendering/caching/CacheRecordManager.ts +0 -71
  94. package/src/rendering/caching/RenderingCache.test.ts +0 -43
  95. package/src/rendering/caching/RenderingCache.ts +0 -66
  96. package/src/rendering/caching/RenderingCacheNode.ts +0 -404
  97. package/src/rendering/caching/testUtils.ts +0 -35
  98. package/src/rendering/caching/types.ts +0 -34
  99. package/src/rendering/lib.ts +0 -8
  100. package/src/rendering/localization.ts +0 -20
  101. package/src/rendering/renderers/AbstractRenderer.ts +0 -232
  102. package/src/rendering/renderers/CanvasRenderer.ts +0 -312
  103. package/src/rendering/renderers/DummyRenderer.test.ts +0 -41
  104. package/src/rendering/renderers/DummyRenderer.ts +0 -142
  105. package/src/rendering/renderers/SVGRenderer.ts +0 -434
  106. package/src/rendering/renderers/TextOnlyRenderer.test.ts +0 -34
  107. package/src/rendering/renderers/TextOnlyRenderer.ts +0 -68
  108. package/src/shortcuts/KeyBinding.test.ts +0 -61
  109. package/src/shortcuts/KeyBinding.ts +0 -257
  110. package/src/shortcuts/KeyboardShortcutManager.test.ts +0 -95
  111. package/src/shortcuts/KeyboardShortcutManager.ts +0 -163
  112. package/src/shortcuts/lib.ts +0 -3
  113. package/src/testing/createEditor.ts +0 -11
  114. package/src/testing/getUniquePointerId.ts +0 -18
  115. package/src/testing/lib.ts +0 -3
  116. package/src/testing/sendPenEvent.ts +0 -36
  117. package/src/testing/sendTouchEvent.ts +0 -71
  118. package/src/toolbar/AbstractToolbar.ts +0 -542
  119. package/src/toolbar/DropdownToolbar.ts +0 -220
  120. package/src/toolbar/EdgeToolbar.test.ts +0 -54
  121. package/src/toolbar/EdgeToolbar.ts +0 -543
  122. package/src/toolbar/IconProvider.ts +0 -861
  123. package/src/toolbar/constants.ts +0 -1
  124. package/src/toolbar/lib.ts +0 -6
  125. package/src/toolbar/localization.ts +0 -136
  126. package/src/toolbar/types.ts +0 -13
  127. package/src/toolbar/widgets/ActionButtonWidget.ts +0 -39
  128. package/src/toolbar/widgets/BaseToolWidget.ts +0 -81
  129. package/src/toolbar/widgets/BaseWidget.ts +0 -495
  130. package/src/toolbar/widgets/DocumentPropertiesWidget.ts +0 -250
  131. package/src/toolbar/widgets/EraserToolWidget.ts +0 -84
  132. package/src/toolbar/widgets/HandToolWidget.ts +0 -239
  133. package/src/toolbar/widgets/InsertImageWidget.ts +0 -248
  134. package/src/toolbar/widgets/OverflowWidget.ts +0 -92
  135. package/src/toolbar/widgets/PenToolWidget.ts +0 -369
  136. package/src/toolbar/widgets/SelectionToolWidget.ts +0 -195
  137. package/src/toolbar/widgets/TextToolWidget.ts +0 -149
  138. package/src/toolbar/widgets/components/makeColorInput.ts +0 -184
  139. package/src/toolbar/widgets/components/makeFileInput.ts +0 -128
  140. package/src/toolbar/widgets/components/makeGridSelector.ts +0 -179
  141. package/src/toolbar/widgets/components/makeSeparator.ts +0 -17
  142. package/src/toolbar/widgets/components/makeThicknessSlider.ts +0 -62
  143. package/src/toolbar/widgets/keybindings.ts +0 -19
  144. package/src/toolbar/widgets/layout/DropdownLayoutManager.ts +0 -262
  145. package/src/toolbar/widgets/layout/EdgeToolbarLayoutManager.ts +0 -71
  146. package/src/toolbar/widgets/layout/types.ts +0 -74
  147. package/src/toolbar/widgets/lib.ts +0 -13
  148. package/src/tools/BaseTool.ts +0 -169
  149. package/src/tools/Eraser.test.ts +0 -103
  150. package/src/tools/Eraser.ts +0 -173
  151. package/src/tools/FindTool.test.ts +0 -67
  152. package/src/tools/FindTool.ts +0 -153
  153. package/src/tools/InputFilter/FunctionMapper.ts +0 -17
  154. package/src/tools/InputFilter/InputMapper.ts +0 -41
  155. package/src/tools/InputFilter/InputPipeline.test.ts +0 -41
  156. package/src/tools/InputFilter/InputPipeline.ts +0 -34
  157. package/src/tools/InputFilter/InputStabilizer.ts +0 -254
  158. package/src/tools/InputFilter/StrokeKeyboardControl.ts +0 -104
  159. package/src/tools/PanZoom.test.ts +0 -339
  160. package/src/tools/PanZoom.ts +0 -525
  161. package/src/tools/PasteHandler.ts +0 -94
  162. package/src/tools/Pen.test.ts +0 -260
  163. package/src/tools/Pen.ts +0 -284
  164. package/src/tools/PipetteTool.ts +0 -84
  165. package/src/tools/SelectionTool/SelectAllShortcutHandler.ts +0 -29
  166. package/src/tools/SelectionTool/Selection.ts +0 -647
  167. package/src/tools/SelectionTool/SelectionHandle.ts +0 -142
  168. package/src/tools/SelectionTool/SelectionTool.test.ts +0 -370
  169. package/src/tools/SelectionTool/SelectionTool.ts +0 -510
  170. package/src/tools/SelectionTool/TransformMode.ts +0 -112
  171. package/src/tools/SelectionTool/types.ts +0 -11
  172. package/src/tools/SoundUITool.ts +0 -221
  173. package/src/tools/TextTool.ts +0 -339
  174. package/src/tools/ToolController.ts +0 -224
  175. package/src/tools/ToolEnabledGroup.ts +0 -14
  176. package/src/tools/ToolSwitcherShortcut.ts +0 -39
  177. package/src/tools/ToolbarShortcutHandler.ts +0 -39
  178. package/src/tools/UndoRedoShortcut.test.ts +0 -62
  179. package/src/tools/UndoRedoShortcut.ts +0 -24
  180. package/src/tools/keybindings.ts +0 -85
  181. package/src/tools/lib.ts +0 -22
  182. package/src/tools/localization.ts +0 -76
  183. package/src/types.ts +0 -151
  184. package/src/util/ReactiveValue.test.ts +0 -168
  185. package/src/util/ReactiveValue.ts +0 -241
  186. package/src/util/assertions.ts +0 -55
  187. package/src/util/fileToBase64.ts +0 -18
  188. package/src/util/guessKeyCodeFromKey.ts +0 -36
  189. package/src/util/listPrefixMatch.ts +0 -19
  190. package/src/util/stopPropagationOfScrollingWheelEvents.ts +0 -20
  191. package/src/util/untilNextAnimationFrame.ts +0 -9
  192. package/src/util/waitForAll.ts +0 -18
  193. package/src/util/waitForTimeout.ts +0 -9
  194. package/src/version.test.ts +0 -12
  195. package/src/version.ts +0 -3
  196. package/tools/allLocales.js +0 -4
  197. package/tools/copyREADME.ts +0 -62
@@ -1,169 +0,0 @@
1
- import { EditorNotifier, EditorEventType } from '../types';
2
- import { WheelEvt, PointerEvt, KeyPressEvent, KeyUpEvent, PasteEvent, CopyEvent, InputEvt, InputEvtType, GestureCancelEvt, PointerDownEvt, PointerMoveEvt, PointerUpEvt } from '../inputEvents';
3
- import ToolEnabledGroup from './ToolEnabledGroup';
4
- import InputMapper, { InputEventListener } from './InputFilter/InputMapper';
5
- import { MutableReactiveValue, ReactiveValue } from '../util/ReactiveValue';
6
-
7
- export default abstract class BaseTool implements InputEventListener {
8
- #enabled: MutableReactiveValue<boolean>;
9
- #group: ToolEnabledGroup|null = null;
10
-
11
- #inputMapper: InputMapper|null = null;
12
-
13
- protected constructor(private notifier: EditorNotifier, public readonly description: string) {
14
- this.#enabled = ReactiveValue.fromInitialValue(true);
15
- this.#enabled.onUpdate(enabled => {
16
- // Ensure that at most one tool in the group is enabled.
17
- if (enabled) {
18
- this.#group?.notifyEnabled(this);
19
- this.notifier.dispatch(EditorEventType.ToolEnabled, {
20
- kind: EditorEventType.ToolEnabled,
21
- tool: this,
22
- });
23
- } else {
24
- this.notifier.dispatch(EditorEventType.ToolDisabled, {
25
- kind: EditorEventType.ToolDisabled,
26
- tool: this,
27
- });
28
- }
29
- });
30
- }
31
-
32
- public setInputMapper(mapper: InputMapper|null) {
33
- this.#inputMapper = mapper;
34
- if (mapper) {
35
- mapper.setEmitListener(event => this.dispatchEventToCallback(event));
36
- }
37
- }
38
-
39
- public getInputMapper() {
40
- return this.#inputMapper;
41
- }
42
-
43
- private dispatchEventToCallback(event: InputEvt) {
44
- let exhaustivenessCheck: never;
45
- switch (event.kind) {
46
- case InputEvtType.PointerDownEvt:
47
- return this.onPointerDown(event);
48
- case InputEvtType.PointerMoveEvt:
49
- this.onPointerMove(event);
50
- break;
51
- case InputEvtType.PointerUpEvt:
52
- return this.onPointerUp(event) ?? false;
53
- case InputEvtType.GestureCancelEvt:
54
- this.onGestureCancel(event);
55
- break;
56
- case InputEvtType.WheelEvt:
57
- return this.onWheel(event);
58
- case InputEvtType.KeyPressEvent:
59
- return this.onKeyPress(event);
60
- case InputEvtType.KeyUpEvent:
61
- return this.onKeyUp(event);
62
- case InputEvtType.CopyEvent:
63
- return this.onCopy(event);
64
- case InputEvtType.PasteEvent:
65
- return this.onPaste(event);
66
- default:
67
- exhaustivenessCheck = event;
68
- return exhaustivenessCheck;
69
- }
70
- return true;
71
- }
72
-
73
- // @internal
74
- public onEvent(event: InputEvt): boolean {
75
- if (this.#inputMapper) {
76
- return this.#inputMapper.onEvent(event);
77
- }
78
- return this.dispatchEventToCallback(event);
79
- }
80
-
81
- /**
82
- * Returns true iff the tool handled the event and thus should receive additional
83
- * events.
84
- */
85
- public onPointerDown(_event: PointerDownEvt): boolean { return false; }
86
- public onPointerMove(_event: PointerMoveEvt) { }
87
-
88
- /**
89
- * Returns true iff there are additional pointers down and the tool should
90
- * remain active to handle the additional events.
91
- *
92
- * For most purposes, this should return `false` or nothing.
93
- */
94
- public onPointerUp(_event: PointerUpEvt): boolean|void { }
95
-
96
- public onGestureCancel(_event: GestureCancelEvt) { }
97
-
98
- public onWheel(_event: WheelEvt): boolean {
99
- return false;
100
- }
101
-
102
- public onCopy(_event: CopyEvent): boolean {
103
- return false;
104
- }
105
-
106
- public onPaste(_event: PasteEvent): boolean {
107
- return false;
108
- }
109
-
110
- public onKeyPress(_event: KeyPressEvent): boolean {
111
- return false;
112
- }
113
-
114
- public onKeyUp(_event: KeyUpEvent): boolean {
115
- return false;
116
- }
117
-
118
- /**
119
- * Return true if, while this tool is active, `_event` can be delivered to
120
- * another tool that is higher priority than this.
121
- * @internal May be renamed
122
- */
123
- public eventCanBeDeliveredToNonActiveTool(_event: PointerEvt) {
124
- return true;
125
- }
126
-
127
- public setEnabled(enabled: boolean) {
128
- this.#enabled.set(enabled);
129
- }
130
-
131
- public isEnabled(): boolean {
132
- return this.#enabled.get();
133
- }
134
-
135
- /**
136
- * Returns a {@link ReactiveValue} that updates based on whether this tool is
137
- * enabled.
138
- *
139
- * @example
140
- * ```ts
141
- * const tool = new SomeTool();
142
- *
143
- * // Watch for changes in enabled status
144
- * tool.enabledValue().onUpdate(enabled => doSomething(enabled));
145
- * ```
146
- */
147
- public enabledValue(): ReactiveValue<boolean> {
148
- return this.#enabled;
149
- }
150
-
151
- // Connect this tool to a set of other tools, ensuring that at most one
152
- // of the tools in the group is enabled.
153
- public setToolGroup(group: ToolEnabledGroup) {
154
- if (this.isEnabled()) {
155
- group.notifyEnabled(this);
156
- }
157
-
158
- this.#group = group;
159
- }
160
-
161
- public getToolGroup(): ToolEnabledGroup|null {
162
- if (this.#group) {
163
- return this.#group;
164
- }
165
-
166
- return null;
167
- }
168
- }
169
-
@@ -1,103 +0,0 @@
1
- import UnknownSVGObject from '../components/UnknownSVGObject';
2
- import Editor from '../Editor';
3
- import { EditorImage, Rect2, StrokeComponent } from '../lib';
4
- import { Vec2 } from '@js-draw/math';
5
- import createEditor from '../testing/createEditor';
6
- import sendPenEvent from '../testing/sendPenEvent';
7
- import { InputEvtType } from '../inputEvents';
8
- import Eraser from './Eraser';
9
-
10
- const selectEraser = (editor: Editor) => {
11
- const tools = editor.toolController;
12
- const eraser = tools.getMatchingTools(Eraser)[0];
13
- eraser.setEnabled(true);
14
-
15
- return eraser;
16
- };
17
-
18
- const getAllStrokes = (editor: Editor) => {
19
- return editor.image.getAllElements().filter(elem => elem instanceof StrokeComponent);
20
- };
21
-
22
- describe('Eraser', () => {
23
- it('should erase object between locations of events', () => {
24
- const editor = createEditor();
25
-
26
- // Draw a line
27
- sendPenEvent(editor, InputEvtType.PointerDownEvt, Vec2.of(0, 0));
28
- jest.advanceTimersByTime(100);
29
- sendPenEvent(editor, InputEvtType.PointerUpEvt, Vec2.of(200, 200));
30
-
31
- // Should have drawn a line
32
- const strokes = getAllStrokes(editor);
33
- expect(strokes).toHaveLength(1);
34
- expect(strokes[0].getBBox().area).toBeGreaterThanOrEqual(200 * 200);
35
-
36
- selectEraser(editor);
37
-
38
- // Erase the line.
39
- sendPenEvent(editor, InputEvtType.PointerDownEvt, Vec2.of(200, 0));
40
- jest.advanceTimersByTime(400);
41
- sendPenEvent(editor, InputEvtType.PointerUpEvt, Vec2.of(0, 200));
42
-
43
- // Should have erased the line
44
- expect(getAllStrokes(editor)).toHaveLength(0);
45
- });
46
-
47
- it('should erase objects within eraser.thickness of an event when not zoomed', async () => {
48
- const editor = createEditor();
49
-
50
- await editor.loadFromSVG(`
51
- <svg>
52
- <path d='m0,0 l2,0 l0,2 l-2,0 z' fill="#ff0000"/>
53
- <path d='m50,50 l2,0 l0,2 l-2,0 z' fill="#ff0000"/>
54
- </svg>
55
- `, true);
56
-
57
- editor.viewport.resetTransform();
58
-
59
- const allStrokes = getAllStrokes(editor);
60
- expect(allStrokes).toHaveLength(2);
61
- expect(allStrokes[0].getBBox()).objEq(new Rect2(0, 0, 2, 2));
62
- expect(allStrokes[1].getBBox()).objEq(new Rect2(50, 50, 2, 2));
63
-
64
- const eraser = selectEraser(editor);
65
- eraser.setThickness(10);
66
-
67
- // Erase the first stroke
68
- sendPenEvent(editor, InputEvtType.PointerDownEvt, Vec2.of(3, 0));
69
- jest.advanceTimersByTime(100);
70
- sendPenEvent(editor, InputEvtType.PointerUpEvt, Vec2.of(3, 0));
71
-
72
- expect(getAllStrokes(editor)).toHaveLength(1);
73
-
74
- // Erase the remaining stroke
75
- sendPenEvent(editor, InputEvtType.PointerDownEvt, Vec2.of(47, 47));
76
- jest.advanceTimersByTime(100);
77
- sendPenEvent(editor, InputEvtType.PointerUpEvt, Vec2.of(47, 47));
78
-
79
- expect(getAllStrokes(editor)).toHaveLength(0);
80
- });
81
-
82
- it('should not erase unselectable objects', () => {
83
- const editor = createEditor();
84
- const unerasableObj = new UnknownSVGObject(document.createElementNS('http://www.w3.org/2000/svg', 'arc'));
85
-
86
- // Add to the image
87
- expect(editor.image.getAllElements()).toHaveLength(0);
88
- editor.dispatch(EditorImage.addElement(unerasableObj));
89
- expect(editor.image.getAllElements()).toHaveLength(1);
90
-
91
-
92
- const eraser = selectEraser(editor);
93
- eraser.setThickness(100);
94
-
95
- // Try to erase it.
96
- sendPenEvent(editor, InputEvtType.PointerDownEvt, Vec2.of(0, 0));
97
- jest.advanceTimersByTime(100);
98
- sendPenEvent(editor, InputEvtType.PointerUpEvt, Vec2.of(3, 0));
99
-
100
- // Should not have been erased
101
- expect(editor.image.getAllElements()).toHaveLength(1);
102
- });
103
- });
@@ -1,173 +0,0 @@
1
- import { EditorEventType } from '../types';
2
- import { KeyPressEvent, PointerEvt } from '../inputEvents';
3
- import BaseTool from './BaseTool';
4
- import Editor from '../Editor';
5
- import { Point2, Vec2, LineSegment2, Color4, Rect2 } from '@js-draw/math';
6
- import Erase from '../commands/Erase';
7
- import AbstractComponent from '../components/AbstractComponent';
8
- import { PointerDevice } from '../Pointer';
9
- import RenderingStyle from '../rendering/RenderingStyle';
10
- import { decreaseSizeKeyboardShortcutId, increaseSizeKeyboardShortcutId } from './keybindings';
11
- import { MutableReactiveValue, ReactiveValue } from '../util/ReactiveValue';
12
-
13
- export default class Eraser extends BaseTool {
14
- private lastPoint: Point2|null = null;
15
- private isFirstEraseEvt: boolean = true;
16
- private toRemove: AbstractComponent[];
17
- private thickness: number = 10;
18
- private thicknessValue: MutableReactiveValue<number>;
19
-
20
- // Commands that each remove one element
21
- private partialCommands: Erase[] = [];
22
-
23
- public constructor(private editor: Editor, description: string) {
24
- super(editor.notifier, description);
25
-
26
- this.thicknessValue = ReactiveValue.fromInitialValue(this.thickness);
27
- this.thicknessValue.onUpdate(value => {
28
- this.thickness = value;
29
-
30
- this.editor.notifier.dispatch(EditorEventType.ToolUpdated, {
31
- kind: EditorEventType.ToolUpdated,
32
- tool: this,
33
- });
34
- });
35
- }
36
-
37
- private clearPreview() {
38
- this.editor.clearWetInk();
39
- }
40
-
41
- private getSizeOnCanvas() {
42
- return this.thickness / this.editor.viewport.getScaleFactor();
43
- }
44
-
45
- private drawPreviewAt(point: Point2) {
46
- this.clearPreview();
47
-
48
- const size = this.getSizeOnCanvas();
49
-
50
- const renderer = this.editor.display.getWetInkRenderer();
51
- const rect = this.getEraserRect(point);
52
- const fill: RenderingStyle = {
53
- fill: Color4.gray,
54
- };
55
- renderer.drawRect(rect, size / 4, fill);
56
- }
57
-
58
- private getEraserRect(centerPoint: Point2) {
59
- const size = this.getSizeOnCanvas();
60
- const halfSize = Vec2.of(size / 2, size / 2);
61
- return Rect2.fromCorners(centerPoint.minus(halfSize), centerPoint.plus(halfSize));
62
- }
63
-
64
- private eraseTo(currentPoint: Point2) {
65
- if (!this.isFirstEraseEvt && currentPoint.minus(this.lastPoint!).magnitude() === 0) {
66
- return;
67
- }
68
- this.isFirstEraseEvt = false;
69
-
70
- // Currently only objects within eraserRect or that intersect a straight line
71
- // from the center of the current rect to the previous are erased. TODO: Erase
72
- // all objects as if there were pointerMove events between the two points.
73
- const eraserRect = this.getEraserRect(currentPoint);
74
- const line = new LineSegment2(this.lastPoint!, currentPoint);
75
- const region = Rect2.union(line.bbox, eraserRect);
76
-
77
- const intersectingElems = this.editor.image.getElementsIntersectingRegion(region).filter(component => {
78
- return component.intersects(line) || component.intersectsRect(eraserRect);
79
- });
80
-
81
- // Only erase components that could be selected (and thus interacted with)
82
- // by the user.
83
- const toErase = intersectingElems.filter(elem => elem.isSelectable());
84
-
85
- // Remove any intersecting elements.
86
- this.toRemove.push(...toErase);
87
-
88
- // Create new Erase commands for the now-to-be-erased elements and apply them.
89
- const newPartialCommands = toErase.map(elem => new Erase([ elem ]));
90
- newPartialCommands.forEach(cmd => cmd.apply(this.editor));
91
-
92
- this.partialCommands.push(...newPartialCommands);
93
-
94
- this.drawPreviewAt(currentPoint);
95
- this.lastPoint = currentPoint;
96
- }
97
-
98
- public override onPointerDown(event: PointerEvt): boolean {
99
- if (event.allPointers.length === 1 || event.current.device === PointerDevice.Eraser) {
100
- this.lastPoint = event.current.canvasPos;
101
- this.toRemove = [];
102
- this.isFirstEraseEvt = true;
103
-
104
- this.drawPreviewAt(event.current.canvasPos);
105
- return true;
106
- }
107
-
108
- return false;
109
- }
110
-
111
- public override onPointerMove(event: PointerEvt): void {
112
- const currentPoint = event.current.canvasPos;
113
-
114
- this.eraseTo(currentPoint);
115
- }
116
-
117
- public override onPointerUp(event: PointerEvt): void {
118
- this.eraseTo(event.current.canvasPos);
119
-
120
- if (this.toRemove.length > 0) {
121
- // Undo commands for each individual component and unite into a single command.
122
- this.partialCommands.forEach(cmd => cmd.unapply(this.editor));
123
- this.partialCommands = [];
124
-
125
- const command = new Erase(this.toRemove);
126
- this.editor.dispatch(command); // dispatch: Makes undo-able.
127
- }
128
-
129
- this.clearPreview();
130
- }
131
-
132
- public override onGestureCancel(): void {
133
- this.partialCommands.forEach(cmd => cmd.unapply(this.editor));
134
- this.partialCommands = [];
135
- this.clearPreview();
136
- }
137
-
138
-
139
- public override onKeyPress(event: KeyPressEvent): boolean {
140
- const shortcuts = this.editor.shortcuts;
141
-
142
- let newThickness: number|undefined;
143
- if (shortcuts.matchesShortcut(decreaseSizeKeyboardShortcutId, event)) {
144
- newThickness = this.getThickness() * 2/3;
145
- } else if (shortcuts.matchesShortcut(increaseSizeKeyboardShortcutId, event)) {
146
- newThickness = this.getThickness() * 3/2;
147
- }
148
-
149
- if (newThickness !== undefined) {
150
- newThickness = Math.min(Math.max(1, newThickness), 200);
151
- this.setThickness(newThickness);
152
- return true;
153
- }
154
-
155
- return false;
156
- }
157
-
158
- public getThickness() {
159
- return this.thickness;
160
- }
161
-
162
- /**
163
- * Returns a {@link MutableReactiveValue} that can be used to watch
164
- * this tool's thickness.
165
- */
166
- public getThicknessValue() {
167
- return this.thicknessValue;
168
- }
169
-
170
- public setThickness(thickness: number) {
171
- this.thicknessValue.set(thickness);
172
- }
173
- }
@@ -1,67 +0,0 @@
1
- import { InputEvtType } from '../inputEvents';
2
- import TextComponent from '../components/TextComponent';
3
- import { Mat33, Color4 } from '@js-draw/math';
4
- import createEditor from '../testing/createEditor';
5
- import FindTool from './FindTool';
6
-
7
- describe('FindTool', () => {
8
- it('should open/close on ctrl+f', () => {
9
- const editor = createEditor();
10
- const findTool = editor.toolController.getMatchingTools(FindTool)[0];
11
- expect(findTool).not.toBeFalsy();
12
-
13
- const overlay = editor.getRootElement().querySelector('.find-tool-overlay')!;
14
-
15
- expect(getComputedStyle(overlay).display).toBe('none');
16
- editor.sendKeyboardEvent(InputEvtType.KeyPressEvent, 'f', true);
17
-
18
- expect(getComputedStyle(overlay).display).not.toBe('none');
19
-
20
- editor.sendKeyboardEvent(InputEvtType.KeyPressEvent, 'f', true);
21
- expect(getComputedStyle(overlay).display).toBe('none');
22
- });
23
-
24
- it('should navigate to the next match on pressing enter', () => {
25
- const editor = createEditor();
26
- const findTool = editor.toolController.getMatchingTools(FindTool)[0];
27
- expect(findTool).not.toBeFalsy();
28
-
29
- // Reset the viewport
30
- editor.viewport.resetTransform();
31
- expect(editor.viewport.getScaleFactor()).toBeCloseTo(1);
32
-
33
- // Show the find tool
34
- const overlay = editor.getRootElement().querySelector('.find-tool-overlay')!;
35
- editor.sendKeyboardEvent(InputEvtType.KeyPressEvent, 'f', true);
36
- expect(getComputedStyle(overlay).display).not.toBe('none');
37
-
38
- // Add some text to the image
39
- const style = { size: 12, fontFamily: 'serif', renderingStyle: { fill: Color4.red }};
40
- const text = TextComponent.fromLines([ 'test' ], Mat33.scaling2D(0.01), style);
41
- editor.image.addElement(text).apply(editor);
42
-
43
- // Should focus the search input
44
- const searchInput = document.querySelector(':focus')!;
45
- expect(searchInput).not.toBeFalsy();
46
- expect(searchInput.tagName).toBe('INPUT');
47
-
48
- // Should not change the view when searching for something that doesn't exist.
49
- searchInput.setAttribute('value', 'testing');
50
- searchInput.dispatchEvent(new KeyboardEvent('keydown', {
51
- key: 'Enter',
52
- code: 'Enter',
53
- }));
54
- expect(editor.viewport.getScaleFactor()).toBeCloseTo(1);
55
-
56
- // Search input should still have focus
57
- expect(document.querySelector(':focus')).toBe(searchInput);
58
-
59
- // When searching for a substring that does exist, should zoom.
60
- searchInput.setAttribute('value', 'test');
61
- searchInput.dispatchEvent(new KeyboardEvent('keydown', {
62
- key: 'Enter',
63
- code: 'Enter',
64
- }));
65
- expect(editor.viewport.getScaleFactor()).not.toBeCloseTo(1);
66
- });
67
- });
@@ -1,153 +0,0 @@
1
- // Displays a find dialog that allows the user to search for and focus text.
2
- //
3
- // @packageDocumentation
4
-
5
- import Editor from '../Editor';
6
- import TextComponent from '../components/TextComponent';
7
- import { Rect2 } from '@js-draw/math';
8
- import { KeyPressEvent } from '../inputEvents';
9
- import BaseTool from './BaseTool';
10
- import { toggleFindVisibleShortcutId } from './keybindings';
11
-
12
- const cssPrefix = 'find-tool';
13
-
14
- export default class FindTool extends BaseTool {
15
- private overlay: HTMLElement;
16
- private searchInput: HTMLInputElement;
17
- private currentMatchIdx: number = 0;
18
-
19
- public constructor(private editor: Editor) {
20
- super(editor.notifier, editor.localization.findLabel);
21
-
22
- this.overlay = document.createElement('div');
23
- this.fillOverlay();
24
- editor.createHTMLOverlay(this.overlay);
25
-
26
- this.overlay.style.display = 'none';
27
- this.overlay.classList.add(`${cssPrefix}-overlay`);
28
- }
29
-
30
- private getMatches(searchFor: string): Rect2[] {
31
- searchFor = searchFor.toLocaleLowerCase();
32
- const allTextComponents = this.editor.image.getAllElements()
33
- .filter(
34
- elem => elem instanceof TextComponent
35
- ) as TextComponent[];
36
-
37
- const matches = allTextComponents.filter(
38
- text => text.getText().toLocaleLowerCase().indexOf(searchFor) !== -1
39
- );
40
-
41
- return matches.map(match => match.getBBox());
42
- }
43
-
44
- private focusCurrentMatch() {
45
- const matches = this.getMatches(this.searchInput.value);
46
- let matchIdx = this.currentMatchIdx % matches.length;
47
-
48
- if (matchIdx < 0) {
49
- matchIdx = matches.length + matchIdx;
50
- }
51
-
52
- if (matchIdx < matches.length) {
53
- const undoable = false;
54
- this.editor.dispatch(this.editor.viewport.zoomTo(matches[matchIdx], true, true), undoable);
55
- this.editor.announceForAccessibility(
56
- this.editor.localization.focusedFoundText(matchIdx + 1, matches.length)
57
- );
58
- }
59
- }
60
-
61
- private toNextMatch() {
62
- this.currentMatchIdx ++;
63
- this.focusCurrentMatch();
64
- }
65
-
66
- private toPrevMatch() {
67
- this.currentMatchIdx --;
68
- this.focusCurrentMatch();
69
- }
70
-
71
- private fillOverlay() {
72
- const label = document.createElement('label');
73
- this.searchInput = document.createElement('input');
74
- const nextBtn = document.createElement('button');
75
- const closeBtn = document.createElement('button');
76
-
77
- // Math.random() ensures that the ID is unique (to allow us to refer to it
78
- // with an htmlFor).
79
- this.searchInput.setAttribute('id', `${cssPrefix}-searchInput-${Math.random()}`);
80
- label.htmlFor = this.searchInput.getAttribute('id')!;
81
-
82
- label.innerText = this.editor.localization.findLabel;
83
- nextBtn.innerText = this.editor.localization.toNextMatch;
84
- closeBtn.innerText = this.editor.localization.closeDialog;
85
-
86
- this.searchInput.onkeydown = (ev: KeyboardEvent) => {
87
- if (ev.key === 'Enter') {
88
- if (ev.shiftKey) {
89
- this.toPrevMatch();
90
- } else {
91
- this.toNextMatch();
92
- }
93
- }
94
- else if (ev.key === 'Escape') {
95
- this.setVisible(false);
96
- }
97
- else if (this.editor.shortcuts.matchesShortcut(toggleFindVisibleShortcutId, ev)) {
98
- ev.preventDefault();
99
- this.toggleVisible();
100
- }
101
- };
102
-
103
- nextBtn.onclick = () => {
104
- this.toNextMatch();
105
- };
106
-
107
- closeBtn.onclick = () => {
108
- this.setVisible(false);
109
- };
110
-
111
- this.overlay.replaceChildren(label, this.searchInput, nextBtn, closeBtn);
112
- }
113
-
114
- private isVisible() {
115
- return this.overlay.style.display !== 'none';
116
- }
117
-
118
- private setVisible(visible: boolean) {
119
- if (visible !== this.isVisible()) {
120
- this.overlay.style.display = visible ? 'block' : 'none';
121
-
122
- if (visible) {
123
- this.searchInput.focus();
124
- this.editor.announceForAccessibility(this.editor.localization.findDialogShown);
125
- } else {
126
- this.editor.focus();
127
- this.editor.announceForAccessibility(this.editor.localization.findDialogHidden);
128
- }
129
- }
130
- }
131
-
132
- private toggleVisible() {
133
- this.setVisible(!this.isVisible());
134
- }
135
-
136
- public override onKeyPress(event: KeyPressEvent): boolean {
137
- if (this.editor.shortcuts.matchesShortcut(toggleFindVisibleShortcutId, event)) {
138
- this.toggleVisible();
139
-
140
- return true;
141
- }
142
-
143
- return false;
144
- }
145
-
146
- public override setEnabled(enabled: boolean) {
147
- super.setEnabled(enabled);
148
-
149
- if (enabled) {
150
- this.setVisible(false);
151
- }
152
- }
153
- }
@@ -1,17 +0,0 @@
1
- import { InputEvt } from '../../inputEvents';
2
- import InputMapper from './InputMapper';
3
-
4
- /**
5
- * An `InputMapper` that applies a function to all events it receives.
6
- *
7
- * Useful for automated testing.
8
- */
9
- export default class FunctionMapper extends InputMapper {
10
- public constructor(private fn: (event: InputEvt)=>InputEvt) {
11
- super();
12
- }
13
-
14
- public override onEvent(event: InputEvt): boolean {
15
- return this.emit(this.fn(event));
16
- }
17
- }