js-draw 1.0.1 → 1.0.2

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 (182) hide show
  1. package/LICENSE +21 -0
  2. package/dist/bundle.js +1 -1
  3. package/dist/cjs/version.js +1 -1
  4. package/dist/mjs/version.mjs +1 -1
  5. package/docs/img/readme-images/js-draw.jpg +0 -0
  6. package/docs/img/readme-images/unsupported-elements--in-editor.png +0 -0
  7. package/package.json +5 -4
  8. package/dist-test/test_imports/package-lock.json +0 -13
  9. package/dist-test/test_imports/package.json +0 -12
  10. package/dist-test/test_imports/test-imports.js +0 -11
  11. package/dist-test/test_imports/test-require.cjs +0 -14
  12. package/src/Editor.loadFrom.test.ts +0 -24
  13. package/src/Editor.test.ts +0 -107
  14. package/src/Editor.toSVG.test.ts +0 -294
  15. package/src/Editor.ts +0 -1443
  16. package/src/EditorImage.test.ts +0 -117
  17. package/src/EditorImage.ts +0 -609
  18. package/src/EventDispatcher.test.ts +0 -123
  19. package/src/EventDispatcher.ts +0 -72
  20. package/src/Pointer.ts +0 -183
  21. package/src/SVGLoader.test.ts +0 -114
  22. package/src/SVGLoader.ts +0 -672
  23. package/src/UndoRedoHistory.test.ts +0 -34
  24. package/src/UndoRedoHistory.ts +0 -102
  25. package/src/Viewport.ts +0 -322
  26. package/src/bundle/bundled.ts +0 -7
  27. package/src/commands/Command.ts +0 -45
  28. package/src/commands/Duplicate.ts +0 -75
  29. package/src/commands/Erase.ts +0 -95
  30. package/src/commands/SerializableCommand.ts +0 -49
  31. package/src/commands/UnresolvedCommand.ts +0 -37
  32. package/src/commands/invertCommand.ts +0 -58
  33. package/src/commands/lib.ts +0 -16
  34. package/src/commands/localization.ts +0 -47
  35. package/src/commands/uniteCommands.test.ts +0 -23
  36. package/src/commands/uniteCommands.ts +0 -140
  37. package/src/components/AbstractComponent.transformBy.test.ts +0 -23
  38. package/src/components/AbstractComponent.ts +0 -383
  39. package/src/components/BackgroundComponent.test.ts +0 -44
  40. package/src/components/BackgroundComponent.ts +0 -348
  41. package/src/components/ImageComponent.ts +0 -176
  42. package/src/components/RestylableComponent.ts +0 -161
  43. package/src/components/SVGGlobalAttributesObject.ts +0 -79
  44. package/src/components/Stroke.test.ts +0 -137
  45. package/src/components/Stroke.ts +0 -294
  46. package/src/components/TextComponent.test.ts +0 -202
  47. package/src/components/TextComponent.ts +0 -429
  48. package/src/components/UnknownSVGObject.test.ts +0 -10
  49. package/src/components/UnknownSVGObject.ts +0 -60
  50. package/src/components/builders/ArrowBuilder.ts +0 -106
  51. package/src/components/builders/CircleBuilder.ts +0 -100
  52. package/src/components/builders/FreehandLineBuilder.test.ts +0 -24
  53. package/src/components/builders/FreehandLineBuilder.ts +0 -210
  54. package/src/components/builders/LineBuilder.ts +0 -77
  55. package/src/components/builders/PressureSensitiveFreehandLineBuilder.ts +0 -453
  56. package/src/components/builders/RectangleBuilder.ts +0 -73
  57. package/src/components/builders/types.ts +0 -15
  58. package/src/components/lib.ts +0 -31
  59. package/src/components/localization.ts +0 -24
  60. package/src/components/util/StrokeSmoother.ts +0 -302
  61. package/src/components/util/describeComponentList.ts +0 -18
  62. package/src/dialogs/makeAboutDialog.ts +0 -82
  63. package/src/inputEvents.ts +0 -143
  64. package/src/lib.ts +0 -91
  65. package/src/localization.ts +0 -34
  66. package/src/localizations/de.ts +0 -146
  67. package/src/localizations/en.ts +0 -8
  68. package/src/localizations/es.ts +0 -74
  69. package/src/localizations/getLocalizationTable.test.ts +0 -27
  70. package/src/localizations/getLocalizationTable.ts +0 -74
  71. package/src/rendering/Display.ts +0 -247
  72. package/src/rendering/RenderablePathSpec.ts +0 -88
  73. package/src/rendering/RenderingStyle.test.ts +0 -68
  74. package/src/rendering/RenderingStyle.ts +0 -55
  75. package/src/rendering/TextRenderingStyle.ts +0 -55
  76. package/src/rendering/caching/CacheRecord.test.ts +0 -48
  77. package/src/rendering/caching/CacheRecord.ts +0 -76
  78. package/src/rendering/caching/CacheRecordManager.ts +0 -71
  79. package/src/rendering/caching/RenderingCache.test.ts +0 -43
  80. package/src/rendering/caching/RenderingCache.ts +0 -66
  81. package/src/rendering/caching/RenderingCacheNode.ts +0 -404
  82. package/src/rendering/caching/testUtils.ts +0 -35
  83. package/src/rendering/caching/types.ts +0 -34
  84. package/src/rendering/lib.ts +0 -8
  85. package/src/rendering/localization.ts +0 -20
  86. package/src/rendering/renderers/AbstractRenderer.ts +0 -232
  87. package/src/rendering/renderers/CanvasRenderer.ts +0 -312
  88. package/src/rendering/renderers/DummyRenderer.test.ts +0 -41
  89. package/src/rendering/renderers/DummyRenderer.ts +0 -142
  90. package/src/rendering/renderers/SVGRenderer.ts +0 -434
  91. package/src/rendering/renderers/TextOnlyRenderer.test.ts +0 -34
  92. package/src/rendering/renderers/TextOnlyRenderer.ts +0 -68
  93. package/src/shortcuts/KeyBinding.test.ts +0 -61
  94. package/src/shortcuts/KeyBinding.ts +0 -257
  95. package/src/shortcuts/KeyboardShortcutManager.test.ts +0 -95
  96. package/src/shortcuts/KeyboardShortcutManager.ts +0 -163
  97. package/src/shortcuts/lib.ts +0 -3
  98. package/src/testing/createEditor.ts +0 -11
  99. package/src/testing/getUniquePointerId.ts +0 -18
  100. package/src/testing/lib.ts +0 -3
  101. package/src/testing/sendPenEvent.ts +0 -36
  102. package/src/testing/sendTouchEvent.ts +0 -71
  103. package/src/toolbar/AbstractToolbar.ts +0 -542
  104. package/src/toolbar/DropdownToolbar.ts +0 -220
  105. package/src/toolbar/EdgeToolbar.test.ts +0 -54
  106. package/src/toolbar/EdgeToolbar.ts +0 -543
  107. package/src/toolbar/IconProvider.ts +0 -861
  108. package/src/toolbar/constants.ts +0 -1
  109. package/src/toolbar/lib.ts +0 -6
  110. package/src/toolbar/localization.ts +0 -136
  111. package/src/toolbar/types.ts +0 -13
  112. package/src/toolbar/widgets/ActionButtonWidget.ts +0 -39
  113. package/src/toolbar/widgets/BaseToolWidget.ts +0 -81
  114. package/src/toolbar/widgets/BaseWidget.ts +0 -495
  115. package/src/toolbar/widgets/DocumentPropertiesWidget.ts +0 -250
  116. package/src/toolbar/widgets/EraserToolWidget.ts +0 -84
  117. package/src/toolbar/widgets/HandToolWidget.ts +0 -239
  118. package/src/toolbar/widgets/InsertImageWidget.ts +0 -248
  119. package/src/toolbar/widgets/OverflowWidget.ts +0 -92
  120. package/src/toolbar/widgets/PenToolWidget.ts +0 -369
  121. package/src/toolbar/widgets/SelectionToolWidget.ts +0 -195
  122. package/src/toolbar/widgets/TextToolWidget.ts +0 -149
  123. package/src/toolbar/widgets/components/makeColorInput.ts +0 -184
  124. package/src/toolbar/widgets/components/makeFileInput.ts +0 -128
  125. package/src/toolbar/widgets/components/makeGridSelector.ts +0 -179
  126. package/src/toolbar/widgets/components/makeSeparator.ts +0 -17
  127. package/src/toolbar/widgets/components/makeThicknessSlider.ts +0 -62
  128. package/src/toolbar/widgets/keybindings.ts +0 -19
  129. package/src/toolbar/widgets/layout/DropdownLayoutManager.ts +0 -262
  130. package/src/toolbar/widgets/layout/EdgeToolbarLayoutManager.ts +0 -71
  131. package/src/toolbar/widgets/layout/types.ts +0 -74
  132. package/src/toolbar/widgets/lib.ts +0 -13
  133. package/src/tools/BaseTool.ts +0 -169
  134. package/src/tools/Eraser.test.ts +0 -103
  135. package/src/tools/Eraser.ts +0 -173
  136. package/src/tools/FindTool.test.ts +0 -67
  137. package/src/tools/FindTool.ts +0 -153
  138. package/src/tools/InputFilter/FunctionMapper.ts +0 -17
  139. package/src/tools/InputFilter/InputMapper.ts +0 -41
  140. package/src/tools/InputFilter/InputPipeline.test.ts +0 -41
  141. package/src/tools/InputFilter/InputPipeline.ts +0 -34
  142. package/src/tools/InputFilter/InputStabilizer.ts +0 -254
  143. package/src/tools/InputFilter/StrokeKeyboardControl.ts +0 -104
  144. package/src/tools/PanZoom.test.ts +0 -339
  145. package/src/tools/PanZoom.ts +0 -525
  146. package/src/tools/PasteHandler.ts +0 -94
  147. package/src/tools/Pen.test.ts +0 -260
  148. package/src/tools/Pen.ts +0 -284
  149. package/src/tools/PipetteTool.ts +0 -84
  150. package/src/tools/SelectionTool/SelectAllShortcutHandler.ts +0 -29
  151. package/src/tools/SelectionTool/Selection.ts +0 -647
  152. package/src/tools/SelectionTool/SelectionHandle.ts +0 -142
  153. package/src/tools/SelectionTool/SelectionTool.test.ts +0 -370
  154. package/src/tools/SelectionTool/SelectionTool.ts +0 -510
  155. package/src/tools/SelectionTool/TransformMode.ts +0 -112
  156. package/src/tools/SelectionTool/types.ts +0 -11
  157. package/src/tools/SoundUITool.ts +0 -221
  158. package/src/tools/TextTool.ts +0 -339
  159. package/src/tools/ToolController.ts +0 -224
  160. package/src/tools/ToolEnabledGroup.ts +0 -14
  161. package/src/tools/ToolSwitcherShortcut.ts +0 -39
  162. package/src/tools/ToolbarShortcutHandler.ts +0 -39
  163. package/src/tools/UndoRedoShortcut.test.ts +0 -62
  164. package/src/tools/UndoRedoShortcut.ts +0 -24
  165. package/src/tools/keybindings.ts +0 -85
  166. package/src/tools/lib.ts +0 -22
  167. package/src/tools/localization.ts +0 -76
  168. package/src/types.ts +0 -151
  169. package/src/util/ReactiveValue.test.ts +0 -168
  170. package/src/util/ReactiveValue.ts +0 -241
  171. package/src/util/assertions.ts +0 -55
  172. package/src/util/fileToBase64.ts +0 -18
  173. package/src/util/guessKeyCodeFromKey.ts +0 -36
  174. package/src/util/listPrefixMatch.ts +0 -19
  175. package/src/util/stopPropagationOfScrollingWheelEvents.ts +0 -20
  176. package/src/util/untilNextAnimationFrame.ts +0 -9
  177. package/src/util/waitForAll.ts +0 -18
  178. package/src/util/waitForTimeout.ts +0 -9
  179. package/src/version.test.ts +0 -12
  180. package/src/version.ts +0 -3
  181. package/tools/allLocales.js +0 -4
  182. package/tools/copyREADME.ts +0 -62
@@ -1,248 +0,0 @@
1
- import ImageComponent from '../../components/ImageComponent';
2
- import Editor from '../../Editor';
3
- import Erase from '../../commands/Erase';
4
- import EditorImage from '../../EditorImage';
5
- import uniteCommands from '../../commands/uniteCommands';
6
- import SelectionTool from '../../tools/SelectionTool/SelectionTool';
7
- import { Mat33 } from '@js-draw/math';
8
- import fileToBase64 from '../../util/fileToBase64';
9
- import { ToolbarLocalization } from '../localization';
10
- import BaseWidget from './BaseWidget';
11
- import { EditorEventType } from '../../types';
12
- import { toolbarCSSPrefix } from '../constants';
13
- import makeFileInput from './components/makeFileInput';
14
- import { MutableReactiveValue } from '../../util/ReactiveValue';
15
-
16
- export default class InsertImageWidget extends BaseWidget {
17
- private imagePreview: HTMLImageElement;
18
- private selectedFiles: MutableReactiveValue<File[]>|null;
19
- private imageAltTextInput: HTMLInputElement;
20
- private statusView: HTMLElement;
21
- private imageBase64URL: string|null;
22
- private submitButton: HTMLButtonElement;
23
-
24
- public constructor(editor: Editor, localization?: ToolbarLocalization) {
25
- localization ??= editor.localization;
26
-
27
- super(editor, 'insert-image-widget', localization);
28
-
29
- // Make the dropdown showable
30
- this.container.classList.add('dropdownShowable');
31
-
32
- editor.notifier.on(EditorEventType.SelectionUpdated, event => {
33
- if (event.kind === EditorEventType.SelectionUpdated && this.isDropdownVisible()) {
34
- this.updateInputs();
35
- }
36
- });
37
- }
38
-
39
- protected override getTitle(): string {
40
- return this.localizationTable.image;
41
- }
42
-
43
- protected override createIcon(): Element | null {
44
- return this.editor.icons.makeInsertImageIcon();
45
- }
46
-
47
- protected override setDropdownVisible(visible: boolean): void {
48
- super.setDropdownVisible(visible);
49
-
50
- // Update the dropdown just before showing.
51
- if (this.isDropdownVisible()) {
52
- this.updateInputs();
53
- }
54
- }
55
-
56
- protected override handleClick() {
57
- this.setDropdownVisible(!this.isDropdownVisible());
58
- }
59
-
60
- private static nextInputId = 0;
61
-
62
- protected override fillDropdown(dropdown: HTMLElement): boolean {
63
- const container = document.createElement('div');
64
- container.classList.add(
65
- 'insert-image-widget-dropdown-content',
66
- `${toolbarCSSPrefix}spacedList`,
67
- `${toolbarCSSPrefix}nonbutton-controls-main-list`,
68
- );
69
-
70
- const {
71
- container: chooseImageRow,
72
- selectedFiles,
73
- } = makeFileInput(
74
- this.localizationTable.chooseFile,
75
- this.editor,
76
- 'image/*',
77
- );
78
- const altTextRow = document.createElement('div');
79
- this.imagePreview = document.createElement('img');
80
- this.statusView = document.createElement('div');
81
- const actionButtonRow = document.createElement('div');
82
-
83
- actionButtonRow.classList.add('action-button-row');
84
-
85
- this.submitButton = document.createElement('button');
86
- this.selectedFiles = selectedFiles;
87
- this.imageAltTextInput = document.createElement('input');
88
-
89
- // Label the alt text input
90
- const imageAltTextLabel = document.createElement('label');
91
-
92
- const altTextInputId = `insert-image-alt-text-input-${InsertImageWidget.nextInputId++}`;
93
- this.imageAltTextInput.setAttribute('id', altTextInputId);
94
- imageAltTextLabel.htmlFor = altTextInputId;
95
-
96
- imageAltTextLabel.innerText = this.localizationTable.inputAltText;
97
- this.imageAltTextInput.type = 'text';
98
-
99
- this.statusView.setAttribute('aria-live', 'polite');
100
-
101
- this.submitButton.innerText = this.localizationTable.submit;
102
-
103
- this.selectedFiles.onUpdateAndNow(async files => {
104
- if (files.length === 0) {
105
- this.imagePreview.style.display = 'none';
106
- this.submitButton.disabled = true;
107
- this.submitButton.style.display = 'none';
108
- return;
109
- }
110
-
111
- this.imagePreview.style.display = 'block';
112
-
113
- const image = files[0];
114
-
115
- let data: string|null = null;
116
-
117
- try {
118
- data = await fileToBase64(image);
119
- } catch(e) {
120
- this.statusView.innerText = this.localizationTable.imageLoadError(e);
121
- }
122
-
123
- this.imageBase64URL = data;
124
-
125
- if (data) {
126
- this.imagePreview.src = data;
127
- this.submitButton.disabled = false;
128
- this.submitButton.style.display = '';
129
- this.updateImageSizeDisplay();
130
- } else {
131
- this.submitButton.disabled = true;
132
- this.submitButton.style.display = 'none';
133
- this.statusView.innerText = '';
134
- }
135
- });
136
-
137
- altTextRow.replaceChildren(imageAltTextLabel, this.imageAltTextInput);
138
- actionButtonRow.replaceChildren(this.submitButton);
139
-
140
- container.replaceChildren(
141
- chooseImageRow, altTextRow, this.imagePreview, this.statusView, actionButtonRow
142
- );
143
-
144
- dropdown.replaceChildren(container);
145
- return true;
146
- }
147
-
148
- private hideDialog() {
149
- this.setDropdownVisible(false);
150
- }
151
-
152
- private updateImageSizeDisplay() {
153
- const imageData = this.imageBase64URL ?? '';
154
-
155
- const sizeInKiB = imageData.length / 1024;
156
- const sizeInMiB = sizeInKiB / 1024;
157
-
158
- let units = 'KiB';
159
- let size = sizeInKiB;
160
-
161
- if (sizeInMiB >= 1) {
162
- size = sizeInMiB;
163
- units = 'MiB';
164
- }
165
-
166
- this.statusView.innerText = this.localizationTable.imageSize(
167
- Math.round(size), units
168
- );
169
- }
170
-
171
- private updateInputs() {
172
- const resetInputs = () => {
173
- this.selectedFiles?.set([]);
174
- this.imageAltTextInput.value = '';
175
- this.imagePreview.style.display = 'none';
176
- this.submitButton.disabled = true;
177
- this.statusView.innerText = '';
178
-
179
- this.submitButton.style.display = '';
180
-
181
- this.imageAltTextInput.oninput = null;
182
- };
183
- resetInputs();
184
-
185
- const selectionTools = this.editor.toolController.getMatchingTools(SelectionTool);
186
- const selectedObjects = selectionTools.map(tool => tool.getSelectedObjects()).flat();
187
-
188
- // Check: Is there a selected image that can be edited?
189
- let editingImage: ImageComponent|null = null;
190
- if (selectedObjects.length === 1 && selectedObjects[0] instanceof ImageComponent) {
191
- editingImage = selectedObjects[0];
192
-
193
- this.imageAltTextInput.value = editingImage.getAltText() ?? '';
194
- this.imagePreview.style.display = 'block';
195
- this.submitButton.disabled = false;
196
-
197
- this.imageBase64URL = editingImage.getURL();
198
- this.imagePreview.src = this.imageBase64URL;
199
-
200
- this.updateImageSizeDisplay();
201
- } else if (selectedObjects.length > 0) {
202
- // If not, clear the selection.
203
- selectionTools.forEach(tool => tool.clearSelection());
204
- }
205
-
206
- // Show the submit button only when there is data to submit.
207
- this.submitButton.style.display = 'none';
208
- this.imageAltTextInput.oninput = () => {
209
- if (this.imagePreview.src?.length > 0) {
210
- this.submitButton.style.display = '';
211
- }
212
- };
213
-
214
- this.submitButton.onclick = async () => {
215
- if (!this.imageBase64URL) {
216
- return;
217
- }
218
-
219
- const image = new Image();
220
- image.src = this.imageBase64URL;
221
- image.setAttribute('alt', this.imageAltTextInput.value);
222
-
223
- const component = await ImageComponent.fromImage(image, Mat33.identity);
224
-
225
- if (component.getBBox().area === 0) {
226
- this.statusView.innerText = this.localizationTable.errorImageHasZeroSize;
227
- return;
228
- }
229
-
230
- this.hideDialog();
231
-
232
- if (editingImage) {
233
- const eraseCommand = new Erase([ editingImage ]);
234
-
235
- await this.editor.dispatch(uniteCommands([
236
- EditorImage.addElement(component),
237
- component.transformBy(editingImage.getTransformation()),
238
- component.setZIndex(editingImage.getZIndex()),
239
- eraseCommand,
240
- ]));
241
-
242
- selectionTools[0]?.setSelection([ component ]);
243
- } else {
244
- await this.editor.addAndCenterComponents([ component ]);
245
- }
246
- };
247
- }
248
- }
@@ -1,92 +0,0 @@
1
- import Editor from '../../Editor';
2
- import { ToolbarLocalization } from '../localization';
3
- import BaseWidget from './BaseWidget';
4
-
5
-
6
- export default class OverflowWidget extends BaseWidget {
7
- private overflowChildren: BaseWidget[] = [];
8
- private overflowContainer: HTMLElement;
9
-
10
- public constructor(editor: Editor, localizationTable?: ToolbarLocalization) {
11
- super(editor, 'overflow-widget', localizationTable);
12
-
13
-
14
- this.container.classList.add('toolbar-overflow-widget');
15
-
16
- // Make the dropdown openable
17
- this.container.classList.add('dropdownShowable');
18
- this.overflowContainer ??= document.createElement('div');
19
- }
20
-
21
- protected getTitle(): string {
22
- return this.localizationTable.toggleOverflow;
23
- }
24
-
25
- protected createIcon(): Element | null {
26
- return this.editor.icons.makeOverflowIcon();
27
- }
28
-
29
- protected handleClick(): void {
30
- this.setDropdownVisible(!this.isDropdownVisible());
31
- }
32
-
33
- protected override fillDropdown(dropdown: HTMLElement) {
34
- this.overflowContainer ??= document.createElement('div');
35
- if (this.overflowContainer.parentElement) {
36
- this.overflowContainer.remove();
37
- }
38
-
39
- this.overflowContainer.classList.add('toolbar-overflow-widget-overflow-list');
40
- dropdown.appendChild(this.overflowContainer);
41
-
42
- return true;
43
- }
44
-
45
- /**
46
- * Removes all `BaseWidget`s from this and returns them.
47
- */
48
- public clearChildren(): BaseWidget[] {
49
- this.overflowContainer.replaceChildren();
50
- this.container.classList.remove('horizontal');
51
-
52
- const overflowChildren = this.overflowChildren;
53
- this.overflowChildren = [];
54
- return overflowChildren;
55
- }
56
-
57
- public getChildWidgets(): BaseWidget[] {
58
- return [ ...this.overflowChildren ];
59
- }
60
-
61
- public hasAsChild(widget: BaseWidget) {
62
- for (const otherWidget of this.overflowChildren) {
63
- if (widget === otherWidget) {
64
- return true;
65
- }
66
- }
67
-
68
- return false;
69
- }
70
-
71
- /**
72
- * Adds `widget` to this.
73
- * `widget`'s previous parent is still responsible
74
- * for serializing/deserializing its state.
75
- */
76
- public addToOverflow(widget: BaseWidget) {
77
- this.overflowChildren.push(widget);
78
- widget.addTo(this.overflowContainer);
79
- widget.setIsToplevel(false);
80
-
81
- // Switch to a horizontal layout if enough children
82
- if (this.overflowChildren.length > 2) {
83
- this.container.classList.add('horizontal');
84
- }
85
- }
86
-
87
- // This always returns false.
88
- // Don't try to move the overflow menu to itself.
89
- public override canBeInOverflowMenu(): boolean {
90
- return false;
91
- }
92
- }
@@ -1,369 +0,0 @@
1
- import { makeArrowBuilder } from '../../components/builders/ArrowBuilder';
2
- import { makeFreehandLineBuilder } from '../../components/builders/FreehandLineBuilder';
3
- import { makePressureSensitiveFreehandLineBuilder } from '../../components/builders/PressureSensitiveFreehandLineBuilder';
4
- import { makeLineBuilder } from '../../components/builders/LineBuilder';
5
- import { makeFilledRectangleBuilder, makeOutlinedRectangleBuilder } from '../../components/builders/RectangleBuilder';
6
- import { makeOutlinedCircleBuilder } from '../../components/builders/CircleBuilder';
7
- import { ComponentBuilderFactory } from '../../components/builders/types';
8
- import Editor from '../../Editor';
9
- import Pen from '../../tools/Pen';
10
- import { EditorEventType } from '../../types';
11
- import { KeyPressEvent } from '../../inputEvents';
12
- import { ToolbarLocalization } from '../localization';
13
- import makeColorInput from './components/makeColorInput';
14
- import BaseToolWidget from './BaseToolWidget';
15
- import { Color4 } from '@js-draw/math';
16
- import { SavedToolbuttonState } from './BaseWidget';
17
- import { selectStrokeTypeKeyboardShortcutIds } from './keybindings';
18
- import { toolbarCSSPrefix } from '../constants';
19
- import makeThicknessSlider from './components/makeThicknessSlider';
20
- import makeGridSelector from './components/makeGridSelector';
21
-
22
- export interface PenTypeRecord {
23
- // Description of the factory (e.g. 'Freehand line')
24
- name: string;
25
-
26
- // A unique ID for the facotory (e.g. 'chisel-tip-pen')
27
- id: string;
28
-
29
- // True if the pen type generates shapes (and should thus be shown in the GUI
30
- // as a shape generator). Defaults to false.
31
- isShapeBuilder?: boolean;
32
-
33
- // Creates an `AbstractComponent` from pen input.
34
- factory: ComponentBuilderFactory;
35
- }
36
-
37
- export default class PenToolWidget extends BaseToolWidget {
38
- private updateInputs: ()=> void = () => {};
39
- protected penTypes: PenTypeRecord[];
40
- protected shapelikeIDs: string[];
41
-
42
- // A counter variable that ensures different HTML elements are given unique names/ids.
43
- private static idCounter: number = 0;
44
-
45
- public constructor(
46
- editor: Editor, private tool: Pen, localization?: ToolbarLocalization
47
- ) {
48
- super(editor, tool, 'pen', localization);
49
-
50
- // Pen types that correspond to
51
- this.shapelikeIDs = [ 'pressure-sensitive-pen', 'freehand-pen' ];
52
-
53
- // Default pen types
54
- this.penTypes = [
55
- {
56
- name: this.localizationTable.flatTipPen,
57
- id: 'pressure-sensitive-pen',
58
-
59
- factory: makePressureSensitiveFreehandLineBuilder,
60
- },
61
- {
62
- name: this.localizationTable.roundedTipPen,
63
- id: 'freehand-pen',
64
-
65
- factory: makeFreehandLineBuilder,
66
- },
67
- {
68
- name: this.localizationTable.arrowPen,
69
- id: 'arrow',
70
-
71
- isShapeBuilder: true,
72
- factory: makeArrowBuilder,
73
- },
74
- {
75
- name: this.localizationTable.linePen,
76
- id: 'line',
77
-
78
- isShapeBuilder: true,
79
- factory: makeLineBuilder,
80
- },
81
- {
82
- name: this.localizationTable.filledRectanglePen,
83
- id: 'filled-rectangle',
84
-
85
- isShapeBuilder: true,
86
- factory: makeFilledRectangleBuilder,
87
- },
88
- {
89
- name: this.localizationTable.outlinedRectanglePen,
90
- id: 'outlined-rectangle',
91
-
92
- isShapeBuilder: true,
93
- factory: makeOutlinedRectangleBuilder,
94
- },
95
- {
96
- name: this.localizationTable.outlinedCirclePen,
97
- id: 'outlined-circle',
98
-
99
- isShapeBuilder: true,
100
- factory: makeOutlinedCircleBuilder,
101
- }
102
- ];
103
-
104
- this.editor.notifier.on(EditorEventType.ToolUpdated, toolEvt => {
105
- if (toolEvt.kind !== EditorEventType.ToolUpdated) {
106
- throw new Error('Invalid event type!');
107
- }
108
-
109
- // The button icon may depend on tool properties.
110
- if (toolEvt.tool === this.tool) {
111
- this.updateIcon();
112
- this.updateInputs();
113
- }
114
- });
115
- }
116
-
117
- protected getTitle(): string {
118
- return this.targetTool.description;
119
- }
120
-
121
- // Return the index of this tool's stroke factory in the list of
122
- // all stroke factories.
123
- //
124
- // Returns -1 if the stroke factory is not in the list of all stroke factories.
125
- private getCurrentPenTypeIdx(): number {
126
- const currentFactory = this.tool.getStrokeFactory();
127
-
128
- for (let i = 0; i < this.penTypes.length; i ++) {
129
- if (this.penTypes[i].factory === currentFactory) {
130
- return i;
131
- }
132
- }
133
- return -1;
134
- }
135
-
136
- private getCurrentPenType(): PenTypeRecord|null {
137
- for (const penType of this.penTypes) {
138
- if (penType.factory === this.tool.getStrokeFactory()) {
139
- return penType;
140
- }
141
- }
142
- return null;
143
- }
144
-
145
- private createIconForRecord(record: PenTypeRecord|null) {
146
- const style = {
147
- ...this.tool.getStyleValue().get(),
148
- };
149
-
150
- if (record?.factory) {
151
- style.factory = record.factory;
152
- }
153
-
154
- const strokeFactory = record?.factory;
155
- if (!strokeFactory || strokeFactory === makeFreehandLineBuilder || strokeFactory === makePressureSensitiveFreehandLineBuilder) {
156
- return this.editor.icons.makePenIcon(style);
157
- } else {
158
- return this.editor.icons.makeIconFromFactory(style);
159
- }
160
- }
161
-
162
- protected createIcon(): Element {
163
- return this.createIconForRecord(this.getCurrentPenType());
164
- }
165
-
166
-
167
- // Creates a widget that allows selecting different pen types
168
- private createPenTypeSelector() {
169
- const allChoices = this.penTypes.map((penType, index) => {
170
- return {
171
- id: index,
172
- makeIcon: () => this.createIconForRecord(penType),
173
- title: penType.name,
174
- isShapeBuilder: penType.isShapeBuilder ?? false,
175
- };
176
- });
177
-
178
- const penSelector = makeGridSelector(
179
- this.localizationTable.selectPenTip,
180
- this.getCurrentPenTypeIdx(),
181
- allChoices.filter(choice => !choice.isShapeBuilder),
182
- );
183
-
184
- const shapeSelector = makeGridSelector(
185
- this.localizationTable.selectShape,
186
- this.getCurrentPenTypeIdx(),
187
- allChoices.filter(choice => choice.isShapeBuilder),
188
- );
189
-
190
- const onSelectorUpdate = (newPenTypeIndex: number) => {
191
- this.tool.setStrokeFactory(this.penTypes[newPenTypeIndex].factory);
192
- };
193
-
194
- penSelector.value.onUpdate(onSelectorUpdate);
195
- shapeSelector.value.onUpdate(onSelectorUpdate);
196
-
197
- return {
198
- setValue: (penTypeIndex: number) => {
199
- penSelector.value.set(penTypeIndex);
200
- shapeSelector.value.set(penTypeIndex);
201
- },
202
-
203
- updateIcons: () => {
204
- penSelector.updateIcons();
205
- shapeSelector.updateIcons();
206
- },
207
-
208
- addTo: (parent: HTMLElement) => {
209
- penSelector.addTo(parent);
210
- shapeSelector.addTo(parent);
211
- },
212
- };
213
- }
214
-
215
- private setInputStabilizationEnabled(enabled: boolean) {
216
- this.tool.setHasStabilization(enabled);
217
- }
218
-
219
- protected createStabilizationOption() {
220
- const stabilizationOption = document.createElement('div');
221
- const stabilizationCheckbox = document.createElement('input');
222
- const stabilizationLabel = document.createElement('label');
223
- stabilizationLabel.innerText = this.localizationTable.inputStabilization;
224
-
225
- stabilizationCheckbox.type = 'checkbox';
226
- stabilizationCheckbox.id = `${toolbarCSSPrefix}-penInputStabilizationCheckbox-${PenToolWidget.idCounter++}`;
227
- stabilizationLabel.htmlFor = stabilizationCheckbox.id;
228
-
229
- stabilizationOption.replaceChildren(stabilizationLabel, stabilizationCheckbox);
230
-
231
- stabilizationCheckbox.oninput = () => {
232
- this.setInputStabilizationEnabled(stabilizationCheckbox.checked);
233
- };
234
-
235
- return {
236
- update: () => {
237
- stabilizationCheckbox.checked = !!this.tool.getInputMapper();
238
- },
239
-
240
- addTo: (parent: HTMLElement) => {
241
- parent.appendChild(stabilizationOption);
242
- }
243
- };
244
- }
245
-
246
- protected override fillDropdown(dropdown: HTMLElement): boolean {
247
- const container = document.createElement('div');
248
- container.classList.add(
249
- `${toolbarCSSPrefix}spacedList`, `${toolbarCSSPrefix}nonbutton-controls-main-list`
250
- );
251
-
252
- // Thickness: Value of the input is squared to allow for finer control/larger values.
253
- const { container: thicknessRow, setValue: setThickness } = makeThicknessSlider(this.editor, thickness => {
254
- this.tool.setThickness(thickness);
255
- });
256
-
257
- const penTypeSelect = this.createPenTypeSelector();
258
-
259
- const colorRow = document.createElement('div');
260
- const colorLabel = document.createElement('label');
261
- const {
262
- input: colorInput, container: colorInputContainer, setValue: setColorInputValue
263
- } = makeColorInput(this.editor, color => {
264
- this.tool.setColor(color);
265
- });
266
-
267
- colorInput.id = `${toolbarCSSPrefix}colorInput${PenToolWidget.idCounter++}`;
268
- colorLabel.innerText = this.localizationTable.colorLabel;
269
- colorLabel.setAttribute('for', colorInput.id);
270
-
271
- colorRow.appendChild(colorLabel);
272
- colorRow.appendChild(colorInputContainer);
273
-
274
- const stabilizationOption = this.createStabilizationOption();
275
-
276
- this.updateInputs = () => {
277
- setColorInputValue(this.tool.getColor());
278
- setThickness(this.tool.getThickness());
279
-
280
- penTypeSelect.updateIcons();
281
-
282
- // Update the selected stroke factory.
283
- penTypeSelect.setValue(this.getCurrentPenTypeIdx());
284
- stabilizationOption.update();
285
- };
286
- this.updateInputs();
287
-
288
- container.replaceChildren(colorRow, thicknessRow);
289
- penTypeSelect.addTo(container);
290
- stabilizationOption.addTo(container);
291
-
292
- dropdown.replaceChildren(container);
293
- return true;
294
- }
295
-
296
- protected override onKeyPress(event: KeyPressEvent): boolean {
297
- if (!this.isSelected()) {
298
- return false;
299
- }
300
-
301
- for (let i = 0; i < selectStrokeTypeKeyboardShortcutIds.length; i++) {
302
- const shortcut = selectStrokeTypeKeyboardShortcutIds[i];
303
- if (this.editor.shortcuts.matchesShortcut(shortcut, event)) {
304
- const penTypeIdx = i;
305
- if (penTypeIdx < this.penTypes.length) {
306
- this.tool.setStrokeFactory(this.penTypes[penTypeIdx].factory);
307
- return true;
308
- }
309
- }
310
- }
311
-
312
- // Run any default actions registered by the parent class.
313
- if (super.onKeyPress(event)) {
314
- return true;
315
- }
316
- return false;
317
- }
318
-
319
- public override serializeState(): SavedToolbuttonState {
320
- return {
321
- ...super.serializeState(),
322
-
323
- color: this.tool.getColor().toHexString(),
324
- thickness: this.tool.getThickness(),
325
- strokeFactoryId: this.getCurrentPenType()?.id,
326
- inputStabilization: !!this.tool.getInputMapper(),
327
- };
328
- }
329
-
330
- public override deserializeFrom(state: SavedToolbuttonState) {
331
- super.deserializeFrom(state);
332
-
333
- const verifyPropertyType = (propertyName: string, expectedType: 'string'|'number'|'object') => {
334
- const actualType = typeof(state[propertyName]);
335
- if (actualType !== expectedType) {
336
- throw new Error(
337
- `Deserializing property ${propertyName}: Invalid type. Expected ${expectedType},` +
338
- ` was ${actualType}.`
339
- );
340
- }
341
- };
342
-
343
- if (state.color) {
344
- verifyPropertyType('color', 'string');
345
- this.tool.setColor(Color4.fromHex(state.color));
346
- }
347
-
348
- if (state.thickness) {
349
- verifyPropertyType('thickness', 'number');
350
- this.tool.setThickness(state.thickness);
351
- }
352
-
353
- if (state.strokeFactoryId) {
354
- verifyPropertyType('strokeFactoryId', 'string');
355
-
356
- const factoryId: string = state.strokeFactoryId;
357
- for (const penType of this.penTypes) {
358
- if (factoryId === penType.id) {
359
- this.tool.setStrokeFactory(penType.factory);
360
- break;
361
- }
362
- }
363
- }
364
-
365
- if (state.inputStabilization !== undefined) {
366
- this.setInputStabilizationEnabled(!!state.inputStabilization);
367
- }
368
- }
369
- }