js-draw 0.15.1 → 0.16.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 (117) hide show
  1. package/.github/ISSUE_TEMPLATE/translation.yml +56 -0
  2. package/CHANGELOG.md +13 -0
  3. package/dist/bundle.js +1 -1
  4. package/dist/src/Color4.d.ts +1 -1
  5. package/dist/src/Color4.js +5 -1
  6. package/dist/src/Editor.d.ts +11 -2
  7. package/dist/src/Editor.js +66 -33
  8. package/dist/src/EditorImage.d.ts +28 -3
  9. package/dist/src/EditorImage.js +109 -18
  10. package/dist/src/EventDispatcher.d.ts +4 -3
  11. package/dist/src/SVGLoader.d.ts +1 -0
  12. package/dist/src/SVGLoader.js +15 -1
  13. package/dist/src/Viewport.d.ts +8 -3
  14. package/dist/src/Viewport.js +15 -8
  15. package/dist/src/components/AbstractComponent.d.ts +6 -1
  16. package/dist/src/components/AbstractComponent.js +15 -2
  17. package/dist/src/components/ImageBackground.d.ts +42 -0
  18. package/dist/src/components/ImageBackground.js +139 -0
  19. package/dist/src/components/ImageComponent.js +2 -0
  20. package/dist/src/components/builders/ArrowBuilder.d.ts +3 -1
  21. package/dist/src/components/builders/ArrowBuilder.js +43 -40
  22. package/dist/src/components/builders/LineBuilder.d.ts +3 -1
  23. package/dist/src/components/builders/LineBuilder.js +25 -28
  24. package/dist/src/components/builders/RectangleBuilder.js +1 -1
  25. package/dist/src/components/lib.d.ts +2 -1
  26. package/dist/src/components/lib.js +2 -1
  27. package/dist/src/components/localization.d.ts +2 -0
  28. package/dist/src/components/localization.js +2 -0
  29. package/dist/src/localizations/es.js +1 -1
  30. package/dist/src/math/Mat33.js +43 -5
  31. package/dist/src/math/Path.d.ts +5 -0
  32. package/dist/src/math/Path.js +80 -28
  33. package/dist/src/math/Vec3.js +1 -1
  34. package/dist/src/rendering/Display.js +1 -1
  35. package/dist/src/rendering/renderers/AbstractRenderer.d.ts +13 -1
  36. package/dist/src/rendering/renderers/AbstractRenderer.js +18 -3
  37. package/dist/src/rendering/renderers/CanvasRenderer.d.ts +2 -1
  38. package/dist/src/rendering/renderers/CanvasRenderer.js +12 -2
  39. package/dist/src/rendering/renderers/SVGRenderer.d.ts +1 -1
  40. package/dist/src/rendering/renderers/SVGRenderer.js +8 -2
  41. package/dist/src/testing/sendTouchEvent.d.ts +6 -0
  42. package/dist/src/testing/sendTouchEvent.js +26 -0
  43. package/dist/src/toolbar/HTMLToolbar.d.ts +25 -2
  44. package/dist/src/toolbar/HTMLToolbar.js +127 -15
  45. package/dist/src/toolbar/IconProvider.d.ts +2 -0
  46. package/dist/src/toolbar/IconProvider.js +45 -2
  47. package/dist/src/toolbar/localization.d.ts +5 -0
  48. package/dist/src/toolbar/localization.js +5 -0
  49. package/dist/src/toolbar/widgets/ActionButtonWidget.d.ts +3 -1
  50. package/dist/src/toolbar/widgets/ActionButtonWidget.js +5 -1
  51. package/dist/src/toolbar/widgets/BaseToolWidget.d.ts +1 -1
  52. package/dist/src/toolbar/widgets/BaseToolWidget.js +2 -1
  53. package/dist/src/toolbar/widgets/BaseWidget.d.ts +7 -2
  54. package/dist/src/toolbar/widgets/BaseWidget.js +23 -1
  55. package/dist/src/toolbar/widgets/DocumentPropertiesWidget.d.ts +19 -0
  56. package/dist/src/toolbar/widgets/DocumentPropertiesWidget.js +135 -0
  57. package/dist/src/toolbar/widgets/HandToolWidget.js +1 -1
  58. package/dist/src/toolbar/widgets/OverflowWidget.d.ts +25 -0
  59. package/dist/src/toolbar/widgets/OverflowWidget.js +65 -0
  60. package/dist/src/toolbar/widgets/lib.d.ts +1 -0
  61. package/dist/src/toolbar/widgets/lib.js +1 -0
  62. package/dist/src/tools/Eraser.js +5 -2
  63. package/dist/src/tools/PanZoom.js +12 -0
  64. package/dist/src/tools/PasteHandler.js +2 -2
  65. package/dist/src/tools/SelectionTool/Selection.d.ts +2 -1
  66. package/dist/src/tools/SelectionTool/Selection.js +3 -2
  67. package/dist/src/tools/SelectionTool/SelectionTool.js +5 -1
  68. package/package.json +1 -1
  69. package/src/Color4.test.ts +6 -0
  70. package/src/Color4.ts +6 -1
  71. package/src/Editor.loadFrom.test.ts +24 -0
  72. package/src/Editor.ts +73 -39
  73. package/src/EditorImage.ts +136 -21
  74. package/src/EventDispatcher.ts +4 -1
  75. package/src/SVGLoader.ts +12 -1
  76. package/src/Viewport.ts +17 -7
  77. package/src/components/AbstractComponent.ts +17 -1
  78. package/src/components/ImageBackground.test.ts +35 -0
  79. package/src/components/ImageBackground.ts +176 -0
  80. package/src/components/ImageComponent.ts +2 -0
  81. package/src/components/builders/ArrowBuilder.ts +44 -41
  82. package/src/components/builders/LineBuilder.ts +26 -28
  83. package/src/components/builders/RectangleBuilder.ts +1 -1
  84. package/src/components/lib.ts +2 -0
  85. package/src/components/localization.ts +4 -0
  86. package/src/localizations/es.ts +8 -0
  87. package/src/math/Mat33.test.ts +47 -3
  88. package/src/math/Mat33.ts +47 -5
  89. package/src/math/Path.ts +87 -28
  90. package/src/math/Vec3.test.ts +4 -0
  91. package/src/math/Vec3.ts +1 -1
  92. package/src/rendering/Display.ts +1 -1
  93. package/src/rendering/renderers/AbstractRenderer.ts +20 -3
  94. package/src/rendering/renderers/CanvasRenderer.ts +17 -4
  95. package/src/rendering/renderers/DummyRenderer.test.ts +1 -2
  96. package/src/rendering/renderers/SVGRenderer.ts +8 -1
  97. package/src/testing/sendTouchEvent.ts +43 -0
  98. package/src/toolbar/HTMLToolbar.ts +164 -16
  99. package/src/toolbar/IconProvider.ts +47 -2
  100. package/src/toolbar/localization.ts +10 -0
  101. package/src/toolbar/toolbar.css +2 -0
  102. package/src/toolbar/widgets/ActionButtonWidget.ts +5 -0
  103. package/src/toolbar/widgets/BaseToolWidget.ts +3 -1
  104. package/src/toolbar/widgets/BaseWidget.ts +34 -2
  105. package/src/toolbar/widgets/DocumentPropertiesWidget.ts +185 -0
  106. package/src/toolbar/widgets/HandToolWidget.ts +1 -1
  107. package/src/toolbar/widgets/OverflowWidget.css +9 -0
  108. package/src/toolbar/widgets/OverflowWidget.ts +83 -0
  109. package/src/toolbar/widgets/lib.ts +2 -1
  110. package/src/tools/Eraser.test.ts +24 -1
  111. package/src/tools/Eraser.ts +6 -2
  112. package/src/tools/PanZoom.test.ts +267 -23
  113. package/src/tools/PanZoom.ts +15 -1
  114. package/src/tools/PasteHandler.ts +3 -2
  115. package/src/tools/SelectionTool/Selection.ts +3 -2
  116. package/src/tools/SelectionTool/SelectionTool.ts +6 -1
  117. package/src/types.ts +1 -0
@@ -1,6 +1,5 @@
1
1
  import Color4 from '../Color4';
2
2
  import { ComponentBuilderFactory } from '../components/builders/types';
3
- import EventDispatcher from '../EventDispatcher';
4
3
  import { Vec2 } from '../math/Vec2';
5
4
  import SVGRenderer from '../rendering/renderers/SVGRenderer';
6
5
  import TextStyle from '../rendering/TextRenderingStyle';
@@ -542,7 +541,7 @@ export default class IconProvider {
542
541
  time: nowTime,
543
542
  };
544
543
 
545
- const viewport = new Viewport(new EventDispatcher());
544
+ const viewport = new Viewport(() => {});
546
545
  const builder = factory(startPoint, viewport);
547
546
  builder.addPoint(endPoint);
548
547
 
@@ -687,5 +686,51 @@ export default class IconProvider {
687
686
  svg.setAttribute('viewBox', '0 0 100 100');
688
687
  return svg;
689
688
  }
689
+
690
+ public makeConfigureDocumentIcon(): IconType {
691
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
692
+ svg.innerHTML = `
693
+ <path
694
+ d='
695
+ M 5,5 V 95 H 95 V 5 Z m 5,5 H 90 V 90 H 10 Z
696
+ m 5,10 V 30 H 50 V 25 H 20 v -5 z
697
+ m 40,0 V 50 H 85 V 20 Z
698
+ m 2,2 H 83 V 39 L 77,28 70,42 64,35 57,45 Z
699
+ m 8.5,5 C 64.67,27 64,27.67 64,28.5 64,29.33 64.67,30 65.5,30 66.33,30 67,29.33 67,28.5 67,27.67 66.33,27 65.5,27 Z
700
+ M 15,40 v 5 h 35 v -5 z
701
+ m 0,15 v 5 h 70 v -5 z
702
+ m 0,15 v 5 h 70 v -5 z
703
+ '
704
+ style='fill: var(--icon-color);'
705
+ />
706
+ `;
707
+ svg.setAttribute('viewBox', '0 0 100 100');
708
+ return svg;
709
+ }
710
+
711
+ public makeOverflowIcon(): IconType {
712
+ return this.makeIconFromPath(`
713
+ M 15 40
714
+ A 12.5 12.5 0 0 0 2.5 52.5
715
+ A 12.5 12.5 0 0 0 15 65
716
+ A 12.5 12.5 0 0 0 27.5 52.5
717
+ A 12.5 12.5 0 0 0 15 40
718
+ z
719
+
720
+ M 50 40
721
+ A 12.5 12.5 0 0 0 37.5 52.5
722
+ A 12.5 12.5 0 0 0 50 65
723
+ A 12.5 12.5 0 0 0 62.5 52.5
724
+ A 12.5 12.5 0 0 0 50 40
725
+ z
726
+
727
+ M 85 40
728
+ A 12.5 12.5 0 0 0 72.5 52.5
729
+ A 12.5 12.5 0 0 0 85 65
730
+ A 12.5 12.5 0 0 0 97.5 52.5
731
+ A 12.5 12.5 0 0 0 85 40
732
+ z
733
+ `);
734
+ }
690
735
 
691
736
  }
@@ -35,6 +35,11 @@ export interface ToolbarLocalization {
35
35
  resetView: string;
36
36
  selectionToolKeyboardShortcuts: string;
37
37
  paste: string;
38
+ documentProperties: string;
39
+ backgroundColor: string;
40
+ imageWidthOption: string;
41
+ imageHeightOption: string;
42
+ toggleOverflow: string,
38
43
 
39
44
  errorImageHasZeroSize: string;
40
45
 
@@ -72,6 +77,11 @@ export const defaultToolbarLocalization: ToolbarLocalization = {
72
77
  pickColorFromScreen: 'Pick color from screen',
73
78
  clickToPickColorAnnouncement: 'Click on the screen to pick a color',
74
79
  selectionToolKeyboardShortcuts: 'Selection tool: Use arrow keys to move selected items, lowercase/uppercase ‘i’ and ‘o’ to resize.',
80
+ documentProperties: 'Document',
81
+ backgroundColor: 'Background Color: ',
82
+ imageWidthOption: 'Width: ',
83
+ imageHeightOption: 'Height: ',
84
+ toggleOverflow: 'More',
75
85
 
76
86
  touchPanning: 'Touchscreen panning',
77
87
 
@@ -1,4 +1,6 @@
1
1
  @import url(./widgets/InsertImageWidget.css);
2
+ @import url(./widgets/OverflowWidget.css);
3
+
2
4
 
3
5
  .toolbar-root {
4
6
  background-color: var(--primary-background-color);
@@ -12,6 +12,7 @@ export default class ActionButtonWidget extends BaseWidget {
12
12
  protected clickAction: ()=>void,
13
13
 
14
14
  localizationTable?: ToolbarLocalization,
15
+ protected mustBeToplevel: boolean = false,
15
16
  ) {
16
17
  super(editor, id, localizationTable);
17
18
  }
@@ -31,4 +32,8 @@ export default class ActionButtonWidget extends BaseWidget {
31
32
  protected fillDropdown(_dropdown: HTMLElement): boolean {
32
33
  return false;
33
34
  }
35
+
36
+ public canBeInOverflowMenu(): boolean {
37
+ return !this.mustBeToplevel;
38
+ }
34
39
  }
@@ -48,7 +48,9 @@ export default abstract class BaseToolWidget extends BaseWidget {
48
48
  }
49
49
 
50
50
  public addTo(parent: HTMLElement) {
51
- super.addTo(parent);
51
+ const result = super.addTo(parent);
52
52
  this.setSelected(this.targetTool.isEnabled());
53
+
54
+ return result;
53
55
  }
54
56
  }
@@ -1,4 +1,5 @@
1
1
  import Editor from '../../Editor';
2
+ import { DispatcherEventListener } from '../../EventDispatcher';
2
3
  import ToolbarShortcutHandler from '../../tools/ToolbarShortcutHandler';
3
4
  import { EditorEventType, InputEvtType, KeyPressEvent } from '../../types';
4
5
  import { toolbarCSSPrefix } from '../HTMLToolbar';
@@ -168,7 +169,10 @@ export default abstract class BaseWidget {
168
169
  this.subWidgets[id] = widget;
169
170
  }
170
171
 
172
+ private toolbarWidgetToggleListener: DispatcherEventListener|null = null;
173
+
171
174
  // Adds this to [parent]. This can only be called once for each ToolbarWidget.
175
+ // Returns the element that was just added to `parent`.
172
176
  // @internal
173
177
  public addTo(parent: HTMLElement) {
174
178
  this.label.innerText = this.getTitle();
@@ -178,6 +182,8 @@ export default abstract class BaseWidget {
178
182
  this.icon = null;
179
183
  this.updateIcon();
180
184
 
185
+ this.container.replaceChildren();
186
+
181
187
  this.button.replaceChildren(this.icon!, this.label);
182
188
  this.container.appendChild(this.button);
183
189
 
@@ -187,7 +193,11 @@ export default abstract class BaseWidget {
187
193
  this.button.appendChild(this.dropdownIcon);
188
194
  this.container.appendChild(this.dropdownContainer);
189
195
 
190
- this.editor.notifier.on(EditorEventType.ToolbarDropdownShown, (evt) => {
196
+ if (this.toolbarWidgetToggleListener) {
197
+ this.toolbarWidgetToggleListener.remove();
198
+ }
199
+
200
+ this.toolbarWidgetToggleListener = this.editor.notifier.on(EditorEventType.ToolbarDropdownShown, (evt) => {
191
201
  if (
192
202
  evt.kind === EditorEventType.ToolbarDropdownShown
193
203
  && evt.parentWidget !== this
@@ -202,7 +212,13 @@ export default abstract class BaseWidget {
202
212
  }
203
213
 
204
214
  this.setDropdownVisible(false);
215
+
216
+ if (this.container.parentElement) {
217
+ this.container.remove();
218
+ }
219
+
205
220
  parent.appendChild(this.container);
221
+ return this.container;
206
222
  }
207
223
 
208
224
 
@@ -272,6 +288,22 @@ export default abstract class BaseWidget {
272
288
  this.repositionDropdown();
273
289
  }
274
290
 
291
+ public canBeInOverflowMenu(): boolean {
292
+ return true;
293
+ }
294
+
295
+ public getButtonWidth(): number {
296
+ return this.button.clientWidth;
297
+ }
298
+
299
+ public isHidden(): boolean {
300
+ return this.container.style.display === 'none';
301
+ }
302
+
303
+ public setHidden(hidden: boolean) {
304
+ this.container.style.display = hidden ? 'none' : '';
305
+ }
306
+
275
307
  protected repositionDropdown() {
276
308
  const dropdownBBox = this.dropdownContainer.getBoundingClientRect();
277
309
  const screenWidth = document.body.clientWidth;
@@ -286,7 +318,7 @@ export default abstract class BaseWidget {
286
318
  }
287
319
 
288
320
  /** Set whether the widget is contained within another. @internal */
289
- protected setIsToplevel(toplevel: boolean) {
321
+ public setIsToplevel(toplevel: boolean) {
290
322
  this.toplevel = toplevel;
291
323
  }
292
324
 
@@ -0,0 +1,185 @@
1
+ import Color4 from '../../Color4';
2
+ import ImageBackground from '../../components/ImageBackground';
3
+ import Editor from '../../Editor';
4
+ import { EditorImageEventType } from '../../EditorImage';
5
+ import Rect2 from '../../math/Rect2';
6
+ import { EditorEventType } from '../../types';
7
+ import { ToolbarLocalization } from '../localization';
8
+ import makeColorInput from '../makeColorInput';
9
+ import BaseWidget from './BaseWidget';
10
+
11
+ export default class DocumentPropertiesWidget extends BaseWidget {
12
+ private updateDropdownContent: ()=>void = () => {};
13
+
14
+ public constructor(editor: Editor, localizationTable?: ToolbarLocalization) {
15
+ super(editor, 'zoom-widget', localizationTable);
16
+
17
+ // Make it possible to open the dropdown, even if this widget isn't selected.
18
+ this.container.classList.add('dropdownShowable');
19
+
20
+ this.editor.notifier.on(EditorEventType.UndoRedoStackUpdated, () => {
21
+ this.queueDropdownUpdate();
22
+ });
23
+
24
+
25
+ this.editor.image.notifier.on(EditorImageEventType.ExportViewportChanged, () => {
26
+ this.queueDropdownUpdate();
27
+ });
28
+ }
29
+
30
+ protected getTitle(): string {
31
+ return this.localizationTable.documentProperties;
32
+ }
33
+
34
+ protected createIcon(): Element {
35
+ return this.editor.icons.makeConfigureDocumentIcon();
36
+ }
37
+
38
+ protected handleClick() {
39
+ this.setDropdownVisible(!this.isDropdownVisible());
40
+ this.queueDropdownUpdate();
41
+ }
42
+
43
+ private dropdownUpdateQueued: boolean = false;
44
+ private queueDropdownUpdate() {
45
+ if (!this.dropdownUpdateQueued) {
46
+ requestAnimationFrame(() => this.updateDropdown());
47
+ this.dropdownUpdateQueued = true;
48
+ }
49
+ }
50
+
51
+ private updateDropdown() {
52
+ this.dropdownUpdateQueued = false;
53
+
54
+ if (this.isDropdownVisible()) {
55
+ this.updateDropdownContent();
56
+ }
57
+ }
58
+
59
+ private getBackgroundElem() {
60
+ const backgroundComponents = [];
61
+
62
+ for (const component of this.editor.image.getBackgroundComponents()) {
63
+ if (component instanceof ImageBackground) {
64
+ backgroundComponents.push(component);
65
+ }
66
+ }
67
+
68
+ if (backgroundComponents.length === 0) {
69
+ return null;
70
+ }
71
+
72
+ // Return the last background component in the list — the component with highest z-index.
73
+ return backgroundComponents[backgroundComponents.length - 1];
74
+ }
75
+
76
+ private setBackgroundColor(color: Color4) {
77
+ this.editor.dispatch(this.editor.setBackgroundColor(color));
78
+ }
79
+
80
+ private getBackgroundColor() {
81
+ const background = this.getBackgroundElem();
82
+
83
+ return background?.getStyle()?.color ?? Color4.transparent;
84
+ }
85
+
86
+ private updateImportExportRectSize(size: { width?: number, height?: number }) {
87
+ const filterDimension = (dim: number|undefined) => {
88
+ if (dim !== undefined && (!isFinite(dim) || dim <= 0)) {
89
+ dim = 100;
90
+ }
91
+
92
+ return dim;
93
+ };
94
+
95
+ const width = filterDimension(size.width);
96
+ const height = filterDimension(size.height);
97
+
98
+ const currentRect = this.editor.getImportExportRect();
99
+ const newRect = new Rect2(
100
+ currentRect.x, currentRect.y,
101
+ width ?? currentRect.w, height ?? currentRect.h
102
+ );
103
+
104
+ this.editor.dispatch(this.editor.image.setImportExportRect(newRect));
105
+ this.editor.queueRerender();
106
+ }
107
+
108
+ private static idCounter = 0;
109
+
110
+ protected fillDropdown(dropdown: HTMLElement): boolean {
111
+ const container = document.createElement('div');
112
+
113
+ const backgroundColorRow = document.createElement('div');
114
+ const backgroundColorLabel = document.createElement('label');
115
+
116
+ backgroundColorLabel.innerText = this.localizationTable.backgroundColor;
117
+
118
+ const [ colorInput, backgroundColorInputContainer, setBgColorInputValue ] = makeColorInput(this.editor, color => {
119
+ if (!color.eq(this.getBackgroundColor())) {
120
+ this.setBackgroundColor(color);
121
+ }
122
+ });
123
+
124
+ colorInput.id = `document-properties-color-input-${DocumentPropertiesWidget.idCounter++}`;
125
+ backgroundColorLabel.htmlFor = colorInput.id;
126
+
127
+ backgroundColorRow.replaceChildren(backgroundColorLabel, backgroundColorInputContainer);
128
+
129
+ const addDimensionRow = (labelContent: string, onChange: (value: number)=>void) => {
130
+ const row = document.createElement('div');
131
+ const label = document.createElement('label');
132
+ const spacer = document.createElement('span');
133
+ const input = document.createElement('input');
134
+
135
+ label.innerText = labelContent;
136
+ input.type = 'number';
137
+ input.min = '0';
138
+ input.id = `document-properties-dimension-row-${DocumentPropertiesWidget.idCounter++}`;
139
+ label.htmlFor = input.id;
140
+
141
+ spacer.style.flexGrow = '1';
142
+ input.style.flexGrow = '2';
143
+ input.style.width = '25px';
144
+
145
+ row.style.display = 'flex';
146
+
147
+ input.oninput = () => {
148
+ onChange(parseFloat(input.value));
149
+ };
150
+
151
+ row.replaceChildren(label, spacer, input);
152
+
153
+ return {
154
+ setValue: (value: number) => {
155
+ input.value = value.toString();
156
+ },
157
+ element: row,
158
+ };
159
+ };
160
+
161
+ const imageWidthRow = addDimensionRow(this.localizationTable.imageWidthOption, (value: number) => {
162
+ this.updateImportExportRectSize({ width: value });
163
+ });
164
+ const imageHeightRow = addDimensionRow(this.localizationTable.imageHeightOption, (value: number) => {
165
+ this.updateImportExportRectSize({ height: value });
166
+ });
167
+
168
+ this.updateDropdownContent = () => {
169
+ setBgColorInputValue(this.getBackgroundColor());
170
+
171
+ const importExportRect = this.editor.getImportExportRect();
172
+ imageWidthRow.setValue(importExportRect.width);
173
+ imageHeightRow.setValue(importExportRect.height);
174
+ };
175
+ this.updateDropdownContent();
176
+
177
+
178
+ container.replaceChildren(
179
+ backgroundColorRow, imageWidthRow.element, imageHeightRow.element
180
+ );
181
+ dropdown.replaceChildren(container);
182
+
183
+ return true;
184
+ }
185
+ }
@@ -94,7 +94,7 @@ class ZoomWidget extends BaseWidget {
94
94
  }
95
95
 
96
96
  protected fillDropdown(dropdown: HTMLElement): boolean {
97
- dropdown.appendChild(makeZoomControl(this.localizationTable, this.editor));
97
+ dropdown.replaceChildren(makeZoomControl(this.localizationTable, this.editor));
98
98
  return true;
99
99
  }
100
100
  }
@@ -0,0 +1,9 @@
1
+
2
+ .toolbar-overflow-widget-overflow-list {
3
+ display: flex;
4
+ flex-direction: column;
5
+ flex-wrap: wrap;
6
+
7
+ overflow-x: auto;
8
+ max-height: 100%;
9
+ }
@@ -0,0 +1,83 @@
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
+ // Make the dropdown openable
14
+ this.container.classList.add('dropdownShowable');
15
+ this.overflowContainer ??= document.createElement('div');
16
+ }
17
+
18
+ protected getTitle(): string {
19
+ return this.localizationTable.toggleOverflow;
20
+ }
21
+
22
+ protected createIcon(): Element | null {
23
+ return this.editor.icons.makeOverflowIcon();
24
+ }
25
+
26
+ protected handleClick(): void {
27
+ this.setDropdownVisible(!this.isDropdownVisible());
28
+ }
29
+
30
+ protected fillDropdown(dropdown: HTMLElement) {
31
+ this.overflowContainer ??= document.createElement('div');
32
+ if (this.overflowContainer.parentElement) {
33
+ this.overflowContainer.remove();
34
+ }
35
+
36
+ this.overflowContainer.classList.add('toolbar-overflow-widget-overflow-list');
37
+ dropdown.appendChild(this.overflowContainer);
38
+
39
+ return true;
40
+ }
41
+
42
+ /**
43
+ * Removes all `BaseWidget`s from this and returns them.
44
+ */
45
+ public clearChildren(): BaseWidget[] {
46
+ this.overflowContainer.replaceChildren();
47
+
48
+ const overflowChildren = this.overflowChildren;
49
+ this.overflowChildren = [];
50
+ return overflowChildren;
51
+ }
52
+
53
+ public getChildWidgets(): BaseWidget[] {
54
+ return [ ...this.overflowChildren ];
55
+ }
56
+
57
+ public hasAsChild(widget: BaseWidget) {
58
+ for (const otherWidget of this.overflowChildren) {
59
+ if (widget === otherWidget) {
60
+ return true;
61
+ }
62
+ }
63
+
64
+ return false;
65
+ }
66
+
67
+ /**
68
+ * Adds `widget` to this.
69
+ * `widget`'s previous parent is still responsible
70
+ * for serializing/deserializing its state.
71
+ */
72
+ public addToOverflow(widget: BaseWidget) {
73
+ this.overflowChildren.push(widget);
74
+ widget.addTo(this.overflowContainer);
75
+ widget.setIsToplevel(false);
76
+ }
77
+
78
+ // This always returns false.
79
+ // Don't try to move the overflow menu to itself.
80
+ public canBeInOverflowMenu(): boolean {
81
+ return false;
82
+ }
83
+ }
@@ -9,4 +9,5 @@ export { default as HandToolWidget } from './HandToolWidget';
9
9
  export { default as SelectionToolWidget } from './SelectionToolWidget';
10
10
  export { default as EraserToolWidget } from './EraserToolWidget';
11
11
 
12
- export { default as InsertImageWidget } from './InsertImageWidget';
12
+ export { default as InsertImageWidget } from './InsertImageWidget';
13
+ export { default as DocumentPropertiesWidget } from './DocumentPropertiesWidget';
@@ -1,5 +1,6 @@
1
+ import UnknownSVGObject from '../components/UnknownSVGObject';
1
2
  import Editor from '../Editor';
2
- import { Rect2, StrokeComponent } from '../lib';
3
+ import { EditorImage, Rect2, StrokeComponent } from '../lib';
3
4
  import { Vec2 } from '../math/Vec2';
4
5
  import createEditor from '../testing/createEditor';
5
6
  import { InputEvtType } from '../types';
@@ -76,4 +77,26 @@ describe('Eraser', () => {
76
77
 
77
78
  expect(getAllStrokes(editor)).toHaveLength(0);
78
79
  });
80
+
81
+ it('should not erase unselectable objects', () => {
82
+ const editor = createEditor();
83
+ const unerasableObj = new UnknownSVGObject(document.createElementNS('http://www.w3.org/2000/svg', 'arc'));
84
+
85
+ // Add to the image
86
+ expect(editor.image.getAllElements()).toHaveLength(0);
87
+ editor.dispatch(EditorImage.addElement(unerasableObj));
88
+ expect(editor.image.getAllElements()).toHaveLength(1);
89
+
90
+
91
+ const eraser = selectEraser(editor);
92
+ eraser.setThickness(100);
93
+
94
+ // Try to erase it.
95
+ editor.sendPenEvent(InputEvtType.PointerDownEvt, Vec2.of(0, 0));
96
+ jest.advanceTimersByTime(100);
97
+ editor.sendPenEvent(InputEvtType.PointerUpEvt, Vec2.of(3, 0));
98
+
99
+ // Should not have been erased
100
+ expect(editor.image.getAllElements()).toHaveLength(1);
101
+ });
79
102
  });
@@ -67,11 +67,15 @@ export default class Eraser extends BaseTool {
67
67
  return component.intersects(line) || component.intersectsRect(eraserRect);
68
68
  });
69
69
 
70
+ // Only erase components that could be selected (and thus interacted with)
71
+ // by the user.
72
+ const toErase = intersectingElems.filter(elem => elem.isSelectable());
73
+
70
74
  // Remove any intersecting elements.
71
- this.toRemove.push(...intersectingElems);
75
+ this.toRemove.push(...toErase);
72
76
 
73
77
  // Create new Erase commands for the now-to-be-erased elements and apply them.
74
- const newPartialCommands = intersectingElems.map(elem => new Erase([ elem ]));
78
+ const newPartialCommands = toErase.map(elem => new Erase([ elem ]));
75
79
  newPartialCommands.forEach(cmd => cmd.apply(this.editor));
76
80
 
77
81
  this.partialCommands.push(...newPartialCommands);