js-draw 1.18.0 → 1.19.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. package/dist/Editor.css +35 -3
  2. package/dist/bundle.js +2 -2
  3. package/dist/bundledStyles.js +1 -1
  4. package/dist/cjs/Editor.d.ts +20 -1
  5. package/dist/cjs/Editor.js +6 -0
  6. package/dist/cjs/{SVGLoader.d.ts → SVGLoader/index.d.ts} +1 -1
  7. package/dist/cjs/{SVGLoader.js → SVGLoader/index.js} +12 -29
  8. package/dist/cjs/SVGLoader/utils/determineFontSize.d.ts +3 -0
  9. package/dist/cjs/SVGLoader/utils/determineFontSize.js +27 -0
  10. package/dist/cjs/Viewport.d.ts +33 -1
  11. package/dist/cjs/components/TextComponent.js +3 -1
  12. package/dist/cjs/rendering/caching/RenderingCacheNode.js +20 -15
  13. package/dist/cjs/testing/findNodeWithText.d.ts +3 -0
  14. package/dist/cjs/testing/findNodeWithText.js +16 -0
  15. package/dist/cjs/testing/firstElementAncestorOfNode.d.ts +3 -0
  16. package/dist/cjs/testing/firstElementAncestorOfNode.js +13 -0
  17. package/dist/cjs/testing/sendKeyPressRelease.d.ts +3 -0
  18. package/dist/cjs/testing/sendKeyPressRelease.js +8 -0
  19. package/dist/cjs/testing/sendPenEvent.d.ts +2 -2
  20. package/dist/cjs/testing/sendPenEvent.js +26 -3
  21. package/dist/cjs/toolbar/localization.d.ts +2 -0
  22. package/dist/cjs/toolbar/localization.js +2 -0
  23. package/dist/cjs/toolbar/widgets/BaseWidget.d.ts +1 -0
  24. package/dist/cjs/toolbar/widgets/BaseWidget.js +1 -0
  25. package/dist/cjs/toolbar/widgets/InsertImageWidget/ImageWrapper.d.ts +22 -0
  26. package/dist/cjs/toolbar/widgets/InsertImageWidget/ImageWrapper.js +58 -0
  27. package/dist/cjs/toolbar/widgets/InsertImageWidget/fileToImages.d.ts +3 -0
  28. package/dist/cjs/toolbar/widgets/InsertImageWidget/fileToImages.js +21 -0
  29. package/dist/cjs/toolbar/widgets/InsertImageWidget/index.d.ts +37 -0
  30. package/dist/cjs/toolbar/widgets/InsertImageWidget/index.js +281 -0
  31. package/dist/cjs/toolbar/widgets/TextToolWidget.js +5 -3
  32. package/dist/cjs/toolbar/widgets/TextToolWidget.test.d.ts +1 -0
  33. package/dist/cjs/toolbar/widgets/components/makeFileInput.d.ts +12 -2
  34. package/dist/cjs/toolbar/widgets/components/makeFileInput.js +102 -45
  35. package/dist/cjs/toolbar/widgets/components/makeFileInput.test.d.ts +1 -0
  36. package/dist/cjs/toolbar/widgets/components/makeSnappedList.d.ts +15 -0
  37. package/dist/cjs/toolbar/widgets/components/makeSnappedList.js +103 -0
  38. package/dist/cjs/tools/Eraser.d.ts +7 -2
  39. package/dist/cjs/tools/Eraser.js +54 -1
  40. package/dist/cjs/tools/SelectionTool/Selection.d.ts +2 -2
  41. package/dist/cjs/tools/SelectionTool/Selection.js +20 -20
  42. package/dist/cjs/tools/SelectionTool/SelectionHandle.d.ts +8 -2
  43. package/dist/cjs/tools/SelectionTool/SelectionHandle.js +6 -0
  44. package/dist/cjs/tools/SelectionTool/SelectionTool.js +1 -1
  45. package/dist/cjs/tools/SelectionTool/types.d.ts +19 -0
  46. package/dist/cjs/tools/TextTool.js +2 -1
  47. package/dist/cjs/tools/TextTool.test.d.ts +1 -0
  48. package/dist/cjs/tools/ToolController.d.ts +2 -0
  49. package/dist/cjs/tools/ToolController.js +10 -1
  50. package/dist/cjs/util/ReactiveValue.d.ts +2 -0
  51. package/dist/cjs/util/ReactiveValue.js +11 -0
  52. package/dist/cjs/util/bytesToSizeString.d.ts +8 -0
  53. package/dist/cjs/util/bytesToSizeString.js +26 -0
  54. package/dist/cjs/util/bytesToSizeString.test.d.ts +1 -0
  55. package/dist/cjs/util/stopPropagationOfScrollingWheelEvents.js +10 -6
  56. package/dist/cjs/util/waitForAll.d.ts +2 -0
  57. package/dist/cjs/util/waitForAll.js +2 -0
  58. package/dist/cjs/util/waitForImageLoaded.js +3 -0
  59. package/dist/cjs/util/waitForTimeout.d.ts +1 -0
  60. package/dist/cjs/util/waitForTimeout.js +1 -1
  61. package/dist/cjs/version.js +1 -1
  62. package/dist/mjs/Editor.d.ts +20 -1
  63. package/dist/mjs/Editor.mjs +6 -0
  64. package/dist/mjs/{SVGLoader.d.ts → SVGLoader/index.d.ts} +1 -1
  65. package/dist/mjs/{SVGLoader.mjs → SVGLoader/index.mjs} +12 -29
  66. package/dist/mjs/SVGLoader/index.test.d.ts +1 -0
  67. package/dist/mjs/SVGLoader/utils/determineFontSize.d.ts +3 -0
  68. package/dist/mjs/SVGLoader/utils/determineFontSize.mjs +25 -0
  69. package/dist/mjs/Viewport.d.ts +33 -1
  70. package/dist/mjs/components/TextComponent.mjs +3 -1
  71. package/dist/mjs/rendering/caching/RenderingCacheNode.mjs +20 -15
  72. package/dist/mjs/testing/findNodeWithText.d.ts +3 -0
  73. package/dist/mjs/testing/findNodeWithText.mjs +14 -0
  74. package/dist/mjs/testing/firstElementAncestorOfNode.d.ts +3 -0
  75. package/dist/mjs/testing/firstElementAncestorOfNode.mjs +11 -0
  76. package/dist/mjs/testing/sendKeyPressRelease.d.ts +3 -0
  77. package/dist/mjs/testing/sendKeyPressRelease.mjs +6 -0
  78. package/dist/mjs/testing/sendPenEvent.d.ts +2 -2
  79. package/dist/mjs/testing/sendPenEvent.mjs +3 -3
  80. package/dist/mjs/toolbar/localization.d.ts +2 -0
  81. package/dist/mjs/toolbar/localization.mjs +2 -0
  82. package/dist/mjs/toolbar/widgets/BaseWidget.d.ts +1 -0
  83. package/dist/mjs/toolbar/widgets/BaseWidget.mjs +1 -0
  84. package/dist/mjs/toolbar/widgets/InsertImageWidget/ImageWrapper.d.ts +22 -0
  85. package/dist/mjs/toolbar/widgets/InsertImageWidget/ImageWrapper.mjs +54 -0
  86. package/dist/mjs/toolbar/widgets/InsertImageWidget/fileToImages.d.ts +3 -0
  87. package/dist/mjs/toolbar/widgets/InsertImageWidget/fileToImages.mjs +16 -0
  88. package/dist/mjs/toolbar/widgets/InsertImageWidget/index.d.ts +37 -0
  89. package/dist/mjs/toolbar/widgets/InsertImageWidget/index.mjs +276 -0
  90. package/dist/mjs/toolbar/widgets/InsertImageWidget/index.test.d.ts +1 -0
  91. package/dist/mjs/toolbar/widgets/TextToolWidget.mjs +5 -3
  92. package/dist/mjs/toolbar/widgets/TextToolWidget.test.d.ts +1 -0
  93. package/dist/mjs/toolbar/widgets/components/makeFileInput.d.ts +12 -2
  94. package/dist/mjs/toolbar/widgets/components/makeFileInput.mjs +102 -45
  95. package/dist/mjs/toolbar/widgets/components/makeFileInput.test.d.ts +1 -0
  96. package/dist/mjs/toolbar/widgets/components/makeSnappedList.d.ts +15 -0
  97. package/dist/mjs/toolbar/widgets/components/makeSnappedList.mjs +98 -0
  98. package/dist/mjs/tools/Eraser.d.ts +7 -2
  99. package/dist/mjs/tools/Eraser.mjs +54 -1
  100. package/dist/mjs/tools/SelectionTool/Selection.d.ts +2 -2
  101. package/dist/mjs/tools/SelectionTool/Selection.mjs +20 -20
  102. package/dist/mjs/tools/SelectionTool/SelectionHandle.d.ts +8 -2
  103. package/dist/mjs/tools/SelectionTool/SelectionHandle.mjs +6 -0
  104. package/dist/mjs/tools/SelectionTool/SelectionTool.mjs +1 -1
  105. package/dist/mjs/tools/SelectionTool/types.d.ts +19 -0
  106. package/dist/mjs/tools/TextTool.mjs +2 -1
  107. package/dist/mjs/tools/TextTool.test.d.ts +1 -0
  108. package/dist/mjs/tools/ToolController.d.ts +2 -0
  109. package/dist/mjs/tools/ToolController.mjs +10 -1
  110. package/dist/mjs/util/ReactiveValue.d.ts +2 -0
  111. package/dist/mjs/util/ReactiveValue.mjs +11 -0
  112. package/dist/mjs/util/bytesToSizeString.d.ts +8 -0
  113. package/dist/mjs/util/bytesToSizeString.mjs +24 -0
  114. package/dist/mjs/util/bytesToSizeString.test.d.ts +1 -0
  115. package/dist/mjs/util/stopPropagationOfScrollingWheelEvents.mjs +10 -6
  116. package/dist/mjs/util/waitForAll.d.ts +2 -0
  117. package/dist/mjs/util/waitForAll.mjs +2 -0
  118. package/dist/mjs/util/waitForImageLoaded.mjs +3 -0
  119. package/dist/mjs/util/waitForTimeout.d.ts +1 -0
  120. package/dist/mjs/util/waitForTimeout.mjs +1 -1
  121. package/dist/mjs/version.mjs +1 -1
  122. package/package.json +4 -4
  123. package/src/toolbar/toolbar.scss +1 -7
  124. package/src/toolbar/widgets/{InsertImageWidget.scss → InsertImageWidget/index.scss} +3 -2
  125. package/src/toolbar/widgets/components/components.scss +2 -1
  126. package/src/toolbar/widgets/components/makeFileInput.scss +14 -1
  127. package/src/toolbar/widgets/components/makeSnappedList.scss +28 -0
  128. package/src/toolbar/widgets/widgets.scss +7 -0
  129. package/dist/cjs/toolbar/widgets/InsertImageWidget.d.ts +0 -22
  130. package/dist/cjs/toolbar/widgets/InsertImageWidget.js +0 -269
  131. package/dist/mjs/toolbar/widgets/InsertImageWidget.d.ts +0 -22
  132. package/dist/mjs/toolbar/widgets/InsertImageWidget.mjs +0 -264
  133. /package/dist/cjs/{SVGLoader.test.d.ts → SVGLoader/index.test.d.ts} +0 -0
  134. /package/dist/{mjs/SVGLoader.test.d.ts → cjs/toolbar/widgets/InsertImageWidget/index.test.d.ts} +0 -0
@@ -1,264 +0,0 @@
1
- import ImageComponent from '../../components/ImageComponent.mjs';
2
- import Erase from '../../commands/Erase.mjs';
3
- import EditorImage from '../../image/EditorImage.mjs';
4
- import uniteCommands from '../../commands/uniteCommands.mjs';
5
- import SelectionTool from '../../tools/SelectionTool/SelectionTool.mjs';
6
- import { Mat33 } from '@js-draw/math';
7
- import fileToBase64Url from '../../util/fileToBase64Url.mjs';
8
- import BaseWidget from './BaseWidget.mjs';
9
- import { EditorEventType } from '../../types.mjs';
10
- import { toolbarCSSPrefix } from '../constants.mjs';
11
- import makeFileInput from './components/makeFileInput.mjs';
12
- class ImageWrapper {
13
- constructor(imageBase64Url, preview, onUrlUpdate) {
14
- this.imageBase64Url = imageBase64Url;
15
- this.preview = preview;
16
- this.onUrlUpdate = onUrlUpdate;
17
- this.originalSrc = imageBase64Url;
18
- preview.src = imageBase64Url;
19
- }
20
- updateImageData(base64DataUrl) {
21
- this.preview.src = base64DataUrl;
22
- this.imageBase64Url = base64DataUrl;
23
- this.onUrlUpdate();
24
- }
25
- decreaseSize(resizeFactor = 3 / 4) {
26
- const canvas = document.createElement('canvas');
27
- canvas.width = this.preview.naturalWidth * resizeFactor;
28
- canvas.height = this.preview.naturalHeight * resizeFactor;
29
- const ctx = canvas.getContext('2d');
30
- ctx?.drawImage(this.preview, 0, 0, canvas.width, canvas.height);
31
- // JPEG can be much smaller than PNG for the same image size. Prefer it if
32
- // the image is already a JPEG.
33
- const format = this.originalSrc?.startsWith('data:image/jpeg;') ? 'image/jpeg' : 'image/png';
34
- this.updateImageData(canvas.toDataURL(format));
35
- }
36
- reset() {
37
- this.updateImageData(this.originalSrc);
38
- }
39
- isChanged() {
40
- return this.imageBase64Url !== this.originalSrc;
41
- }
42
- getBase64Url() {
43
- return this.imageBase64Url;
44
- }
45
- static fromSrcAndPreview(initialBase64Src, preview, onUrlUpdate) {
46
- return new ImageWrapper(initialBase64Src, preview, onUrlUpdate);
47
- }
48
- }
49
- class InsertImageWidget extends BaseWidget {
50
- constructor(editor, localization) {
51
- localization ??= editor.localization;
52
- super(editor, 'insert-image-widget', localization);
53
- this.image = null;
54
- // Make the dropdown showable
55
- this.container.classList.add('dropdownShowable');
56
- editor.notifier.on(EditorEventType.SelectionUpdated, event => {
57
- if (event.kind === EditorEventType.SelectionUpdated && this.isDropdownVisible()) {
58
- this.updateInputs();
59
- }
60
- });
61
- }
62
- getTitle() {
63
- return this.localizationTable.image;
64
- }
65
- createIcon() {
66
- return this.editor.icons.makeInsertImageIcon();
67
- }
68
- setDropdownVisible(visible) {
69
- super.setDropdownVisible(visible);
70
- // Update the dropdown just before showing.
71
- if (this.isDropdownVisible()) {
72
- this.updateInputs();
73
- }
74
- }
75
- handleClick() {
76
- this.setDropdownVisible(!this.isDropdownVisible());
77
- }
78
- fillDropdown(dropdown) {
79
- const container = document.createElement('div');
80
- container.classList.add('insert-image-widget-dropdown-content', `${toolbarCSSPrefix}spacedList`, `${toolbarCSSPrefix}nonbutton-controls-main-list`);
81
- const { container: chooseImageRow, selectedFiles, } = makeFileInput(this.localizationTable.chooseFile, this.editor, 'image/*');
82
- const altTextRow = document.createElement('div');
83
- this.imagePreview = document.createElement('img');
84
- this.statusView = document.createElement('div');
85
- const actionButtonRow = document.createElement('div');
86
- actionButtonRow.classList.add('action-button-row');
87
- this.statusView.classList.add('insert-image-image-status-view');
88
- this.submitButton = document.createElement('button');
89
- this.selectedFiles = selectedFiles;
90
- this.imageAltTextInput = document.createElement('input');
91
- // Label the alt text input
92
- const imageAltTextLabel = document.createElement('label');
93
- const altTextInputId = `insert-image-alt-text-input-${InsertImageWidget.nextInputId++}`;
94
- this.imageAltTextInput.setAttribute('id', altTextInputId);
95
- imageAltTextLabel.htmlFor = altTextInputId;
96
- imageAltTextLabel.innerText = this.localizationTable.inputAltText;
97
- this.imageAltTextInput.type = 'text';
98
- this.imageAltTextInput.placeholder = this.localizationTable.describeTheImage;
99
- this.statusView.setAttribute('aria-live', 'polite');
100
- this.submitButton.innerText = this.localizationTable.submit;
101
- this.selectedFiles.onUpdateAndNow(async (files) => {
102
- if (files.length === 0) {
103
- this.image = null;
104
- this.onImageDataUpdate();
105
- return;
106
- }
107
- this.imagePreview.style.display = 'block';
108
- const image = files[0];
109
- let data = null;
110
- let errorMessage = null;
111
- try {
112
- data = await fileToBase64Url(image);
113
- }
114
- catch (error) {
115
- console.error('Image load error', error);
116
- errorMessage = this.localizationTable.imageLoadError(error);
117
- }
118
- if (data) {
119
- this.image = ImageWrapper.fromSrcAndPreview(data, this.imagePreview, () => this.onImageDataUpdate());
120
- }
121
- else {
122
- this.image = null;
123
- }
124
- this.onImageDataUpdate();
125
- // Show the error after image update callbacks to ensure it is
126
- // actually shown.
127
- if (errorMessage) {
128
- this.statusView.innerText = errorMessage;
129
- }
130
- });
131
- altTextRow.replaceChildren(imageAltTextLabel, this.imageAltTextInput);
132
- actionButtonRow.replaceChildren(this.submitButton);
133
- container.replaceChildren(chooseImageRow, altTextRow, this.imagePreview, this.statusView, actionButtonRow);
134
- dropdown.replaceChildren(container);
135
- return true;
136
- }
137
- onImageDataUpdate() {
138
- const base64Data = this.image?.getBase64Url();
139
- if (base64Data) {
140
- this.submitButton.disabled = false;
141
- this.submitButton.style.display = '';
142
- this.imagePreview.style.display = '';
143
- this.updateImageSizeDisplay();
144
- }
145
- else {
146
- this.submitButton.disabled = true;
147
- this.submitButton.style.display = 'none';
148
- this.statusView.innerText = '';
149
- this.imagePreview.style.display = 'none';
150
- this.submitButton.disabled = true;
151
- }
152
- }
153
- hideDialog() {
154
- this.setDropdownVisible(false);
155
- }
156
- updateImageSizeDisplay() {
157
- const imageData = this.image?.getBase64Url() ?? '';
158
- const sizeInKiB = imageData.length / 1024;
159
- const sizeInMiB = sizeInKiB / 1024;
160
- let units = 'KiB';
161
- let size = sizeInKiB;
162
- if (sizeInMiB >= 1) {
163
- size = sizeInMiB;
164
- units = 'MiB';
165
- }
166
- const sizeText = document.createElement('span');
167
- sizeText.innerText = this.localizationTable.imageSize(Math.round(size), units);
168
- // Add a button to allow decreasing the size of large images.
169
- const decreaseSizeButton = document.createElement('button');
170
- decreaseSizeButton.innerText = this.localizationTable.decreaseImageSize;
171
- decreaseSizeButton.onclick = () => {
172
- this.image?.decreaseSize();
173
- };
174
- const resetSizeButton = document.createElement('button');
175
- resetSizeButton.innerText = this.localizationTable.resetImage;
176
- resetSizeButton.onclick = () => {
177
- this.image?.reset();
178
- };
179
- this.statusView.replaceChildren(sizeText);
180
- const largeImageThreshold = 0.12; // MiB
181
- if (sizeInMiB > largeImageThreshold) {
182
- this.statusView.appendChild(decreaseSizeButton);
183
- }
184
- else if (this.image?.isChanged()) {
185
- this.statusView.appendChild(resetSizeButton);
186
- }
187
- }
188
- updateInputs() {
189
- const resetInputs = () => {
190
- this.selectedFiles?.set([]);
191
- this.imageAltTextInput.value = '';
192
- this.imagePreview.style.display = 'none';
193
- this.submitButton.disabled = true;
194
- this.statusView.innerText = '';
195
- this.submitButton.style.display = '';
196
- this.imageAltTextInput.oninput = null;
197
- };
198
- resetInputs();
199
- const selectionTools = this.editor.toolController.getMatchingTools(SelectionTool);
200
- const selectedObjects = selectionTools.map(tool => tool.getSelectedObjects()).flat();
201
- // Check: Is there a selected image that can be edited?
202
- let editingImage = null;
203
- if (selectedObjects.length === 1 && selectedObjects[0] instanceof ImageComponent) {
204
- editingImage = selectedObjects[0];
205
- this.imageAltTextInput.value = editingImage.getAltText() ?? '';
206
- this.image = ImageWrapper.fromSrcAndPreview(editingImage.getURL(), this.imagePreview, () => this.onImageDataUpdate());
207
- this.onImageDataUpdate();
208
- }
209
- else if (selectedObjects.length > 0) {
210
- // If not, clear the selection.
211
- selectionTools.forEach(tool => tool.clearSelection());
212
- }
213
- // Show the submit button only when there is data to submit.
214
- this.submitButton.style.display = 'none';
215
- this.imageAltTextInput.oninput = () => {
216
- if (this.imagePreview.src?.length > 0) {
217
- this.submitButton.style.display = '';
218
- }
219
- };
220
- this.submitButton.onclick = async () => {
221
- if (!this.image) {
222
- return;
223
- }
224
- const image = new Image();
225
- image.src = this.image.getBase64Url();
226
- image.setAttribute('alt', this.imageAltTextInput.value);
227
- let component;
228
- try {
229
- component = await ImageComponent.fromImage(image, Mat33.identity);
230
- }
231
- catch (error) {
232
- console.error('Error loading image', error);
233
- this.statusView.innerText = this.localizationTable.imageLoadError(error);
234
- return;
235
- }
236
- if (component.getBBox().area === 0) {
237
- this.statusView.innerText = this.localizationTable.errorImageHasZeroSize;
238
- return;
239
- }
240
- this.hideDialog();
241
- if (editingImage) {
242
- const eraseCommand = new Erase([editingImage]);
243
- // Try to preserve the original width
244
- const originalTransform = editingImage.getTransformation();
245
- // || 1: Prevent division by zero
246
- const originalWidth = editingImage.getBBox().width || 1;
247
- const newWidth = component.getBBox().transformedBoundingBox(originalTransform).width || 1;
248
- const widthAdjustTransform = Mat33.scaling2D(originalWidth / newWidth);
249
- await this.editor.dispatch(uniteCommands([
250
- EditorImage.addElement(component),
251
- component.transformBy(originalTransform.rightMul(widthAdjustTransform)),
252
- component.setZIndex(editingImage.getZIndex()),
253
- eraseCommand,
254
- ]));
255
- selectionTools[0]?.setSelection([component]);
256
- }
257
- else {
258
- await this.editor.addAndCenterComponents([component]);
259
- }
260
- };
261
- }
262
- }
263
- InsertImageWidget.nextInputId = 0;
264
- export default InsertImageWidget;