js-draw 0.15.2 → 0.16.1
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.
- package/.github/ISSUE_TEMPLATE/translation.yml +56 -0
- package/CHANGELOG.md +12 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +11 -0
- package/dist/src/Editor.js +52 -4
- package/dist/src/EditorImage.d.ts +11 -11
- package/dist/src/EditorImage.js +54 -18
- package/dist/src/SVGLoader.js +6 -0
- package/dist/src/Viewport.d.ts +5 -0
- package/dist/src/Viewport.js +11 -0
- package/dist/src/components/AbstractComponent.d.ts +1 -0
- package/dist/src/components/AbstractComponent.js +5 -0
- package/dist/src/components/ImageBackground.d.ts +2 -1
- package/dist/src/components/ImageBackground.js +8 -1
- package/dist/src/localizations/es.js +1 -1
- package/dist/src/toolbar/HTMLToolbar.d.ts +26 -2
- package/dist/src/toolbar/HTMLToolbar.js +130 -15
- package/dist/src/toolbar/IconProvider.d.ts +2 -0
- package/dist/src/toolbar/IconProvider.js +44 -0
- package/dist/src/toolbar/localization.d.ts +5 -0
- package/dist/src/toolbar/localization.js +5 -0
- package/dist/src/toolbar/widgets/ActionButtonWidget.d.ts +3 -1
- package/dist/src/toolbar/widgets/ActionButtonWidget.js +5 -1
- package/dist/src/toolbar/widgets/BaseToolWidget.d.ts +1 -1
- package/dist/src/toolbar/widgets/BaseToolWidget.js +2 -1
- package/dist/src/toolbar/widgets/BaseWidget.d.ts +7 -2
- package/dist/src/toolbar/widgets/BaseWidget.js +23 -1
- package/dist/src/toolbar/widgets/DocumentPropertiesWidget.d.ts +19 -0
- package/dist/src/toolbar/widgets/DocumentPropertiesWidget.js +135 -0
- package/dist/src/toolbar/widgets/OverflowWidget.d.ts +25 -0
- package/dist/src/toolbar/widgets/OverflowWidget.js +65 -0
- package/dist/src/toolbar/widgets/lib.d.ts +1 -0
- package/dist/src/toolbar/widgets/lib.js +1 -0
- package/dist/src/tools/PasteHandler.js +2 -2
- package/dist/src/tools/SelectionTool/Selection.d.ts +2 -1
- package/dist/src/tools/SelectionTool/Selection.js +2 -1
- package/dist/src/tools/SelectionTool/SelectionTool.js +1 -1
- package/package.json +1 -1
- package/src/Editor.css +4 -0
- package/src/Editor.loadFrom.test.ts +24 -0
- package/src/Editor.ts +59 -4
- package/src/EditorImage.ts +66 -23
- package/src/SVGLoader.test.ts +57 -0
- package/src/SVGLoader.ts +7 -0
- package/src/Viewport.ts +13 -0
- package/src/components/AbstractComponent.ts +6 -0
- package/src/components/ImageBackground.test.ts +35 -0
- package/src/components/ImageBackground.ts +10 -1
- package/src/localizations/es.ts +8 -0
- package/src/math/Mat33.test.ts +30 -5
- package/src/rendering/renderers/CanvasRenderer.ts +1 -1
- package/src/toolbar/HTMLToolbar.ts +169 -16
- package/src/toolbar/IconProvider.ts +46 -0
- package/src/toolbar/localization.ts +10 -0
- package/src/toolbar/toolbar.css +2 -0
- package/src/toolbar/widgets/ActionButtonWidget.ts +5 -0
- package/src/toolbar/widgets/BaseToolWidget.ts +3 -1
- package/src/toolbar/widgets/BaseWidget.ts +34 -2
- package/src/toolbar/widgets/DocumentPropertiesWidget.ts +185 -0
- package/src/toolbar/widgets/OverflowWidget.css +9 -0
- package/src/toolbar/widgets/OverflowWidget.ts +83 -0
- package/src/toolbar/widgets/lib.ts +2 -1
- package/src/tools/PasteHandler.ts +3 -2
- package/src/tools/SelectionTool/Selection.ts +2 -1
- package/src/tools/SelectionTool/SelectionTool.ts +1 -1
@@ -14,14 +14,23 @@ import TextToolWidget from './widgets/TextToolWidget';
|
|
14
14
|
import HandToolWidget from './widgets/HandToolWidget';
|
15
15
|
import ActionButtonWidget from './widgets/ActionButtonWidget';
|
16
16
|
import InsertImageWidget from './widgets/InsertImageWidget';
|
17
|
+
import DocumentPropertiesWidget from './widgets/DocumentPropertiesWidget';
|
18
|
+
import OverflowWidget from './widgets/OverflowWidget';
|
17
19
|
export const toolbarCSSPrefix = 'toolbar-';
|
18
20
|
export default class HTMLToolbar {
|
19
21
|
/** @internal */
|
20
22
|
constructor(editor, parent, localizationTable = defaultToolbarLocalization) {
|
21
23
|
this.editor = editor;
|
22
24
|
this.localizationTable = localizationTable;
|
23
|
-
this.
|
25
|
+
this.listeners = [];
|
26
|
+
// Flex-order of the next widget to be added.
|
27
|
+
this.widgetOrderCounter = 0;
|
28
|
+
this.widgetsById = {};
|
29
|
+
this.widgetList = [];
|
30
|
+
// Widget to toggle overflow menu.
|
31
|
+
this.overflowWidget = null;
|
24
32
|
this.updateColoris = null;
|
33
|
+
this.reLayoutQueued = false;
|
25
34
|
this.container = document.createElement('div');
|
26
35
|
this.container.classList.add(`${toolbarCSSPrefix}root`);
|
27
36
|
this.container.setAttribute('role', 'toolbar');
|
@@ -31,6 +40,15 @@ export default class HTMLToolbar {
|
|
31
40
|
HTMLToolbar.colorisStarted = true;
|
32
41
|
}
|
33
42
|
this.setupColorPickers();
|
43
|
+
if ('ResizeObserver' in window) {
|
44
|
+
this.resizeObserver = new ResizeObserver((_entries) => {
|
45
|
+
this.reLayout();
|
46
|
+
});
|
47
|
+
this.resizeObserver.observe(this.container);
|
48
|
+
}
|
49
|
+
else {
|
50
|
+
console.warn('ResizeObserver not supported. Toolbar will not resize.');
|
51
|
+
}
|
34
52
|
}
|
35
53
|
// @internal
|
36
54
|
setupColorPickers() {
|
@@ -80,20 +98,84 @@ export default class HTMLToolbar {
|
|
80
98
|
initColoris();
|
81
99
|
}
|
82
100
|
};
|
83
|
-
this.editor.notifier.on(EditorEventType.ColorPickerToggled, event => {
|
101
|
+
this.listeners.push(this.editor.notifier.on(EditorEventType.ColorPickerToggled, event => {
|
84
102
|
if (event.kind !== EditorEventType.ColorPickerToggled) {
|
85
103
|
return;
|
86
104
|
}
|
87
105
|
// Show/hide the overlay. Making the overlay visible gives users a surface to click
|
88
106
|
// on that shows/hides the color picker.
|
89
107
|
closePickerOverlay.style.display = event.open ? 'block' : 'none';
|
90
|
-
});
|
108
|
+
}));
|
91
109
|
// Add newly-selected colors to the swatch.
|
92
|
-
this.editor.notifier.on(EditorEventType.ColorPickerColorSelected, event => {
|
110
|
+
this.listeners.push(this.editor.notifier.on(EditorEventType.ColorPickerColorSelected, event => {
|
93
111
|
if (event.kind === EditorEventType.ColorPickerColorSelected) {
|
94
112
|
addColorToSwatch(event.color.toHexString());
|
95
113
|
}
|
96
|
-
});
|
114
|
+
}));
|
115
|
+
}
|
116
|
+
queueReLayout() {
|
117
|
+
if (!this.reLayoutQueued) {
|
118
|
+
this.reLayoutQueued = true;
|
119
|
+
requestAnimationFrame(() => this.reLayout());
|
120
|
+
}
|
121
|
+
}
|
122
|
+
reLayout() {
|
123
|
+
this.reLayoutQueued = false;
|
124
|
+
if (!this.overflowWidget) {
|
125
|
+
return;
|
126
|
+
}
|
127
|
+
const getTotalWidth = (widgetList) => {
|
128
|
+
let totalWidth = 0;
|
129
|
+
for (const widget of widgetList) {
|
130
|
+
if (!widget.isHidden()) {
|
131
|
+
totalWidth += widget.getButtonWidth();
|
132
|
+
}
|
133
|
+
}
|
134
|
+
return totalWidth;
|
135
|
+
};
|
136
|
+
let overflowWidgetsWidth = getTotalWidth(this.overflowWidget.getChildWidgets());
|
137
|
+
let shownWidgetWidth = getTotalWidth(this.widgetList) - overflowWidgetsWidth;
|
138
|
+
let availableWidth = this.container.clientWidth * 0.87;
|
139
|
+
// If on a device that has enough vertical space, allow
|
140
|
+
// showing two rows of buttons.
|
141
|
+
// TODO: Fix magic numbers
|
142
|
+
if (window.innerHeight > availableWidth * 1.75) {
|
143
|
+
availableWidth *= 1.75;
|
144
|
+
}
|
145
|
+
let updatedChildren = false;
|
146
|
+
if (shownWidgetWidth + overflowWidgetsWidth <= availableWidth) {
|
147
|
+
// Move widgets to the main menu.
|
148
|
+
const overflowChildren = this.overflowWidget.clearChildren();
|
149
|
+
for (const child of overflowChildren) {
|
150
|
+
child.addTo(this.container);
|
151
|
+
child.setIsToplevel(true);
|
152
|
+
if (!child.isHidden()) {
|
153
|
+
shownWidgetWidth += child.getButtonWidth();
|
154
|
+
}
|
155
|
+
}
|
156
|
+
this.overflowWidget.setHidden(true);
|
157
|
+
overflowWidgetsWidth = 0;
|
158
|
+
updatedChildren = true;
|
159
|
+
}
|
160
|
+
if (shownWidgetWidth >= availableWidth) {
|
161
|
+
// Move widgets to the overflow menu.
|
162
|
+
this.overflowWidget.setHidden(false);
|
163
|
+
// Start with the rightmost widget, move to the leftmost
|
164
|
+
for (let i = this.widgetList.length - 1; i >= 0 && shownWidgetWidth >= availableWidth; i--) {
|
165
|
+
const child = this.widgetList[i];
|
166
|
+
if (this.overflowWidget.hasAsChild(child)) {
|
167
|
+
continue;
|
168
|
+
}
|
169
|
+
if (child.canBeInOverflowMenu()) {
|
170
|
+
shownWidgetWidth -= child.getButtonWidth();
|
171
|
+
this.overflowWidget.addToOverflow(child);
|
172
|
+
}
|
173
|
+
}
|
174
|
+
updatedChildren = true;
|
175
|
+
}
|
176
|
+
if (updatedChildren) {
|
177
|
+
this.setupColorPickers();
|
178
|
+
}
|
97
179
|
}
|
98
180
|
/**
|
99
181
|
* Adds an `ActionButtonWidget` or `BaseToolWidget`. The widget should not have already have a parent
|
@@ -108,12 +190,17 @@ export default class HTMLToolbar {
|
|
108
190
|
*/
|
109
191
|
addWidget(widget) {
|
110
192
|
// Prevent name collisions
|
111
|
-
const id = widget.getUniqueIdIn(this.
|
193
|
+
const id = widget.getUniqueIdIn(this.widgetsById);
|
112
194
|
// Add the widget
|
113
|
-
this.
|
195
|
+
this.widgetsById[id] = widget;
|
196
|
+
this.widgetList.push(widget);
|
114
197
|
// Add HTML elements.
|
115
|
-
widget.addTo(this.container);
|
198
|
+
const container = widget.addTo(this.container);
|
116
199
|
this.setupColorPickers();
|
200
|
+
// Ensure that the widget gets displayed in the correct
|
201
|
+
// place in the toolbar, even if it's removed and re-added.
|
202
|
+
container.style.order = `${this.widgetOrderCounter++}`;
|
203
|
+
this.queueReLayout();
|
117
204
|
}
|
118
205
|
/**
|
119
206
|
* Adds a spacer.
|
@@ -148,12 +235,13 @@ export default class HTMLToolbar {
|
|
148
235
|
if (options.maxSize) {
|
149
236
|
spacer.style.maxWidth = options.maxSize;
|
150
237
|
}
|
238
|
+
spacer.style.order = `${this.widgetOrderCounter++}`;
|
151
239
|
this.container.appendChild(spacer);
|
152
240
|
}
|
153
241
|
serializeState() {
|
154
242
|
const result = {};
|
155
|
-
for (const widgetId in this.
|
156
|
-
result[widgetId] = this.
|
243
|
+
for (const widgetId in this.widgetsById) {
|
244
|
+
result[widgetId] = this.widgetsById[widgetId].serializeState();
|
157
245
|
}
|
158
246
|
return JSON.stringify(result);
|
159
247
|
}
|
@@ -164,10 +252,10 @@ export default class HTMLToolbar {
|
|
164
252
|
deserializeState(state) {
|
165
253
|
const data = JSON.parse(state);
|
166
254
|
for (const widgetId in data) {
|
167
|
-
if (!(widgetId in this.
|
255
|
+
if (!(widgetId in this.widgetsById)) {
|
168
256
|
console.warn(`Unable to deserialize widget ${widgetId} — no such widget.`);
|
169
257
|
}
|
170
|
-
this.
|
258
|
+
this.widgetsById[widgetId].deserializeFrom(data[widgetId]);
|
171
259
|
}
|
172
260
|
}
|
173
261
|
/**
|
@@ -175,7 +263,7 @@ export default class HTMLToolbar {
|
|
175
263
|
*
|
176
264
|
* @return The added button.
|
177
265
|
*/
|
178
|
-
addActionButton(title, command) {
|
266
|
+
addActionButton(title, command, mustBeToplevel = true) {
|
179
267
|
const titleString = typeof title === 'string' ? title : title.label;
|
180
268
|
const widgetId = 'action-button';
|
181
269
|
const makeIcon = () => {
|
@@ -184,7 +272,7 @@ export default class HTMLToolbar {
|
|
184
272
|
}
|
185
273
|
return title.icon;
|
186
274
|
};
|
187
|
-
const widget = new ActionButtonWidget(this.editor, widgetId, makeIcon, titleString, command, this.editor.localization);
|
275
|
+
const widget = new ActionButtonWidget(this.editor, widgetId, makeIcon, titleString, command, this.editor.localization, mustBeToplevel);
|
188
276
|
this.addWidget(widget);
|
189
277
|
return widget;
|
190
278
|
}
|
@@ -226,25 +314,52 @@ export default class HTMLToolbar {
|
|
226
314
|
for (const tool of toolController.getMatchingTools(TextTool)) {
|
227
315
|
this.addWidget(new TextToolWidget(this.editor, tool, this.localizationTable));
|
228
316
|
}
|
229
|
-
this.addWidget(new InsertImageWidget(this.editor, this.localizationTable));
|
230
317
|
const panZoomTool = toolController.getMatchingTools(PanZoomTool)[0];
|
231
318
|
if (panZoomTool) {
|
232
319
|
this.addWidget(new HandToolWidget(this.editor, panZoomTool, this.localizationTable));
|
233
320
|
}
|
321
|
+
this.addWidget(new InsertImageWidget(this.editor, this.localizationTable));
|
234
322
|
}
|
235
323
|
addDefaultActionButtons() {
|
324
|
+
this.addWidget(new DocumentPropertiesWidget(this.editor, this.localizationTable));
|
236
325
|
this.addUndoRedoButtons();
|
237
326
|
}
|
327
|
+
/**
|
328
|
+
* Adds a widget that toggles the overflow menu. Call `addOverflowWidget` to ensure
|
329
|
+
* that this widget is in the correct space (if shown).
|
330
|
+
*
|
331
|
+
* @example
|
332
|
+
* ```ts
|
333
|
+
* toolbar.addDefaultToolWidgets();
|
334
|
+
* toolbar.addOverflowWidget();
|
335
|
+
* toolbar.addDefaultActionButtons();
|
336
|
+
* ```
|
337
|
+
* shows the overflow widget between the default tool widgets and the default action buttons,
|
338
|
+
* if shown.
|
339
|
+
*/
|
340
|
+
addOverflowWidget() {
|
341
|
+
this.overflowWidget = new OverflowWidget(this.editor, this.localizationTable);
|
342
|
+
this.addWidget(this.overflowWidget);
|
343
|
+
}
|
238
344
|
/**
|
239
345
|
* Adds both the default tool widgets and action buttons. Equivalent to
|
240
346
|
* ```ts
|
241
347
|
* toolbar.addDefaultToolWidgets();
|
348
|
+
* toolbar.addOverflowWidget();
|
242
349
|
* toolbar.addDefaultActionButtons();
|
243
350
|
* ```
|
244
351
|
*/
|
245
352
|
addDefaults() {
|
246
353
|
this.addDefaultToolWidgets();
|
354
|
+
this.addOverflowWidget();
|
247
355
|
this.addDefaultActionButtons();
|
248
356
|
}
|
357
|
+
remove() {
|
358
|
+
this.container.remove();
|
359
|
+
this.resizeObserver.disconnect();
|
360
|
+
for (const listener of this.listeners) {
|
361
|
+
listener.remove();
|
362
|
+
}
|
363
|
+
}
|
249
364
|
}
|
250
365
|
HTMLToolbar.colorisStarted = false;
|
@@ -607,4 +607,48 @@ export default class IconProvider {
|
|
607
607
|
svg.setAttribute('viewBox', '0 0 100 100');
|
608
608
|
return svg;
|
609
609
|
}
|
610
|
+
makeConfigureDocumentIcon() {
|
611
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
612
|
+
svg.innerHTML = `
|
613
|
+
<path
|
614
|
+
d='
|
615
|
+
M 5,5 V 95 H 95 V 5 Z m 5,5 H 90 V 90 H 10 Z
|
616
|
+
m 5,10 V 30 H 50 V 25 H 20 v -5 z
|
617
|
+
m 40,0 V 50 H 85 V 20 Z
|
618
|
+
m 2,2 H 83 V 39 L 77,28 70,42 64,35 57,45 Z
|
619
|
+
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
|
620
|
+
M 15,40 v 5 h 35 v -5 z
|
621
|
+
m 0,15 v 5 h 70 v -5 z
|
622
|
+
m 0,15 v 5 h 70 v -5 z
|
623
|
+
'
|
624
|
+
style='fill: var(--icon-color);'
|
625
|
+
/>
|
626
|
+
`;
|
627
|
+
svg.setAttribute('viewBox', '0 0 100 100');
|
628
|
+
return svg;
|
629
|
+
}
|
630
|
+
makeOverflowIcon() {
|
631
|
+
return this.makeIconFromPath(`
|
632
|
+
M 15 40
|
633
|
+
A 12.5 12.5 0 0 0 2.5 52.5
|
634
|
+
A 12.5 12.5 0 0 0 15 65
|
635
|
+
A 12.5 12.5 0 0 0 27.5 52.5
|
636
|
+
A 12.5 12.5 0 0 0 15 40
|
637
|
+
z
|
638
|
+
|
639
|
+
M 50 40
|
640
|
+
A 12.5 12.5 0 0 0 37.5 52.5
|
641
|
+
A 12.5 12.5 0 0 0 50 65
|
642
|
+
A 12.5 12.5 0 0 0 62.5 52.5
|
643
|
+
A 12.5 12.5 0 0 0 50 40
|
644
|
+
z
|
645
|
+
|
646
|
+
M 85 40
|
647
|
+
A 12.5 12.5 0 0 0 72.5 52.5
|
648
|
+
A 12.5 12.5 0 0 0 85 65
|
649
|
+
A 12.5 12.5 0 0 0 97.5 52.5
|
650
|
+
A 12.5 12.5 0 0 0 85 40
|
651
|
+
z
|
652
|
+
`);
|
653
|
+
}
|
610
654
|
}
|
@@ -33,6 +33,11 @@ export interface ToolbarLocalization {
|
|
33
33
|
resetView: string;
|
34
34
|
selectionToolKeyboardShortcuts: string;
|
35
35
|
paste: string;
|
36
|
+
documentProperties: string;
|
37
|
+
backgroundColor: string;
|
38
|
+
imageWidthOption: string;
|
39
|
+
imageHeightOption: string;
|
40
|
+
toggleOverflow: string;
|
36
41
|
errorImageHasZeroSize: string;
|
37
42
|
dropdownShown: (toolName: string) => string;
|
38
43
|
dropdownHidden: (toolName: string) => string;
|
@@ -24,6 +24,11 @@ export const defaultToolbarLocalization = {
|
|
24
24
|
pickColorFromScreen: 'Pick color from screen',
|
25
25
|
clickToPickColorAnnouncement: 'Click on the screen to pick a color',
|
26
26
|
selectionToolKeyboardShortcuts: 'Selection tool: Use arrow keys to move selected items, lowercase/uppercase ‘i’ and ‘o’ to resize.',
|
27
|
+
documentProperties: 'Document',
|
28
|
+
backgroundColor: 'Background Color: ',
|
29
|
+
imageWidthOption: 'Width: ',
|
30
|
+
imageHeightOption: 'Height: ',
|
31
|
+
toggleOverflow: 'More',
|
27
32
|
touchPanning: 'Touchscreen panning',
|
28
33
|
freehandPen: 'Freehand',
|
29
34
|
pressureSensitiveFreehandPen: 'Freehand (pressure sensitive)',
|
@@ -5,9 +5,11 @@ export default class ActionButtonWidget extends BaseWidget {
|
|
5
5
|
protected makeIcon: () => Element | null;
|
6
6
|
protected title: string;
|
7
7
|
protected clickAction: () => void;
|
8
|
-
|
8
|
+
protected mustBeToplevel: boolean;
|
9
|
+
constructor(editor: Editor, id: string, makeIcon: () => Element | null, title: string, clickAction: () => void, localizationTable?: ToolbarLocalization, mustBeToplevel?: boolean);
|
9
10
|
protected handleClick(): void;
|
10
11
|
protected getTitle(): string;
|
11
12
|
protected createIcon(): Element | null;
|
12
13
|
protected fillDropdown(_dropdown: HTMLElement): boolean;
|
14
|
+
canBeInOverflowMenu(): boolean;
|
13
15
|
}
|
@@ -1,10 +1,11 @@
|
|
1
1
|
import BaseWidget from './BaseWidget';
|
2
2
|
export default class ActionButtonWidget extends BaseWidget {
|
3
|
-
constructor(editor, id, makeIcon, title, clickAction, localizationTable) {
|
3
|
+
constructor(editor, id, makeIcon, title, clickAction, localizationTable, mustBeToplevel = false) {
|
4
4
|
super(editor, id, localizationTable);
|
5
5
|
this.makeIcon = makeIcon;
|
6
6
|
this.title = title;
|
7
7
|
this.clickAction = clickAction;
|
8
|
+
this.mustBeToplevel = mustBeToplevel;
|
8
9
|
}
|
9
10
|
handleClick() {
|
10
11
|
this.clickAction();
|
@@ -18,4 +19,7 @@ export default class ActionButtonWidget extends BaseWidget {
|
|
18
19
|
fillDropdown(_dropdown) {
|
19
20
|
return false;
|
20
21
|
}
|
22
|
+
canBeInOverflowMenu() {
|
23
|
+
return !this.mustBeToplevel;
|
24
|
+
}
|
21
25
|
}
|
@@ -7,5 +7,5 @@ export default abstract class BaseToolWidget extends BaseWidget {
|
|
7
7
|
protected targetTool: BaseTool;
|
8
8
|
constructor(editor: Editor, targetTool: BaseTool, id: string, localizationTable?: ToolbarLocalization);
|
9
9
|
protected handleClick(): void;
|
10
|
-
addTo(parent: HTMLElement):
|
10
|
+
addTo(parent: HTMLElement): HTMLElement;
|
11
11
|
}
|
@@ -36,14 +36,19 @@ export default abstract class BaseWidget {
|
|
36
36
|
protected abstract handleClick(): void;
|
37
37
|
protected get hasDropdown(): boolean;
|
38
38
|
protected addSubWidget(widget: BaseWidget): void;
|
39
|
-
|
39
|
+
private toolbarWidgetToggleListener;
|
40
|
+
addTo(parent: HTMLElement): HTMLElement;
|
40
41
|
protected updateIcon(): void;
|
41
42
|
setDisabled(disabled: boolean): void;
|
42
43
|
setSelected(selected: boolean): void;
|
43
44
|
protected setDropdownVisible(visible: boolean): void;
|
45
|
+
canBeInOverflowMenu(): boolean;
|
46
|
+
getButtonWidth(): number;
|
47
|
+
isHidden(): boolean;
|
48
|
+
setHidden(hidden: boolean): void;
|
44
49
|
protected repositionDropdown(): void;
|
45
50
|
/** Set whether the widget is contained within another. @internal */
|
46
|
-
|
51
|
+
setIsToplevel(toplevel: boolean): void;
|
47
52
|
protected isDropdownVisible(): boolean;
|
48
53
|
protected isSelected(): boolean;
|
49
54
|
private createDropdownIcon;
|
@@ -22,6 +22,7 @@ export default class BaseWidget {
|
|
22
22
|
// Maps subWidget IDs to subWidgets.
|
23
23
|
this.subWidgets = {};
|
24
24
|
this.toplevel = true;
|
25
|
+
this.toolbarWidgetToggleListener = null;
|
25
26
|
this.localizationTable = localizationTable !== null && localizationTable !== void 0 ? localizationTable : editor.localization;
|
26
27
|
this.icon = null;
|
27
28
|
this.container = document.createElement('div');
|
@@ -135,12 +136,14 @@ export default class BaseWidget {
|
|
135
136
|
this.subWidgets[id] = widget;
|
136
137
|
}
|
137
138
|
// Adds this to [parent]. This can only be called once for each ToolbarWidget.
|
139
|
+
// Returns the element that was just added to `parent`.
|
138
140
|
// @internal
|
139
141
|
addTo(parent) {
|
140
142
|
this.label.innerText = this.getTitle();
|
141
143
|
this.setupActionBtnClickListener(this.button);
|
142
144
|
this.icon = null;
|
143
145
|
this.updateIcon();
|
146
|
+
this.container.replaceChildren();
|
144
147
|
this.button.replaceChildren(this.icon, this.label);
|
145
148
|
this.container.appendChild(this.button);
|
146
149
|
__classPrivateFieldSet(this, _BaseWidget_hasDropdown, this.fillDropdown(this.dropdownContainer), "f");
|
@@ -148,7 +151,10 @@ export default class BaseWidget {
|
|
148
151
|
this.dropdownIcon = this.createDropdownIcon();
|
149
152
|
this.button.appendChild(this.dropdownIcon);
|
150
153
|
this.container.appendChild(this.dropdownContainer);
|
151
|
-
this.
|
154
|
+
if (this.toolbarWidgetToggleListener) {
|
155
|
+
this.toolbarWidgetToggleListener.remove();
|
156
|
+
}
|
157
|
+
this.toolbarWidgetToggleListener = this.editor.notifier.on(EditorEventType.ToolbarDropdownShown, (evt) => {
|
152
158
|
if (evt.kind === EditorEventType.ToolbarDropdownShown
|
153
159
|
&& evt.parentWidget !== this
|
154
160
|
// Don't hide if a submenu wash shown (it might be a submenu of
|
@@ -159,7 +165,11 @@ export default class BaseWidget {
|
|
159
165
|
});
|
160
166
|
}
|
161
167
|
this.setDropdownVisible(false);
|
168
|
+
if (this.container.parentElement) {
|
169
|
+
this.container.remove();
|
170
|
+
}
|
162
171
|
parent.appendChild(this.container);
|
172
|
+
return this.container;
|
163
173
|
}
|
164
174
|
updateIcon() {
|
165
175
|
var _a, _b;
|
@@ -219,6 +229,18 @@ export default class BaseWidget {
|
|
219
229
|
}
|
220
230
|
this.repositionDropdown();
|
221
231
|
}
|
232
|
+
canBeInOverflowMenu() {
|
233
|
+
return true;
|
234
|
+
}
|
235
|
+
getButtonWidth() {
|
236
|
+
return this.button.clientWidth;
|
237
|
+
}
|
238
|
+
isHidden() {
|
239
|
+
return this.container.style.display === 'none';
|
240
|
+
}
|
241
|
+
setHidden(hidden) {
|
242
|
+
this.container.style.display = hidden ? 'none' : '';
|
243
|
+
}
|
222
244
|
repositionDropdown() {
|
223
245
|
const dropdownBBox = this.dropdownContainer.getBoundingClientRect();
|
224
246
|
const screenWidth = document.body.clientWidth;
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import Editor from '../../Editor';
|
2
|
+
import { ToolbarLocalization } from '../localization';
|
3
|
+
import BaseWidget from './BaseWidget';
|
4
|
+
export default class DocumentPropertiesWidget extends BaseWidget {
|
5
|
+
private updateDropdownContent;
|
6
|
+
constructor(editor: Editor, localizationTable?: ToolbarLocalization);
|
7
|
+
protected getTitle(): string;
|
8
|
+
protected createIcon(): Element;
|
9
|
+
protected handleClick(): void;
|
10
|
+
private dropdownUpdateQueued;
|
11
|
+
private queueDropdownUpdate;
|
12
|
+
private updateDropdown;
|
13
|
+
private getBackgroundElem;
|
14
|
+
private setBackgroundColor;
|
15
|
+
private getBackgroundColor;
|
16
|
+
private updateImportExportRectSize;
|
17
|
+
private static idCounter;
|
18
|
+
protected fillDropdown(dropdown: HTMLElement): boolean;
|
19
|
+
}
|
@@ -0,0 +1,135 @@
|
|
1
|
+
import Color4 from '../../Color4';
|
2
|
+
import ImageBackground from '../../components/ImageBackground';
|
3
|
+
import { EditorImageEventType } from '../../EditorImage';
|
4
|
+
import Rect2 from '../../math/Rect2';
|
5
|
+
import { EditorEventType } from '../../types';
|
6
|
+
import makeColorInput from '../makeColorInput';
|
7
|
+
import BaseWidget from './BaseWidget';
|
8
|
+
export default class DocumentPropertiesWidget extends BaseWidget {
|
9
|
+
constructor(editor, localizationTable) {
|
10
|
+
super(editor, 'zoom-widget', localizationTable);
|
11
|
+
this.updateDropdownContent = () => { };
|
12
|
+
this.dropdownUpdateQueued = false;
|
13
|
+
// Make it possible to open the dropdown, even if this widget isn't selected.
|
14
|
+
this.container.classList.add('dropdownShowable');
|
15
|
+
this.editor.notifier.on(EditorEventType.UndoRedoStackUpdated, () => {
|
16
|
+
this.queueDropdownUpdate();
|
17
|
+
});
|
18
|
+
this.editor.image.notifier.on(EditorImageEventType.ExportViewportChanged, () => {
|
19
|
+
this.queueDropdownUpdate();
|
20
|
+
});
|
21
|
+
}
|
22
|
+
getTitle() {
|
23
|
+
return this.localizationTable.documentProperties;
|
24
|
+
}
|
25
|
+
createIcon() {
|
26
|
+
return this.editor.icons.makeConfigureDocumentIcon();
|
27
|
+
}
|
28
|
+
handleClick() {
|
29
|
+
this.setDropdownVisible(!this.isDropdownVisible());
|
30
|
+
this.queueDropdownUpdate();
|
31
|
+
}
|
32
|
+
queueDropdownUpdate() {
|
33
|
+
if (!this.dropdownUpdateQueued) {
|
34
|
+
requestAnimationFrame(() => this.updateDropdown());
|
35
|
+
this.dropdownUpdateQueued = true;
|
36
|
+
}
|
37
|
+
}
|
38
|
+
updateDropdown() {
|
39
|
+
this.dropdownUpdateQueued = false;
|
40
|
+
if (this.isDropdownVisible()) {
|
41
|
+
this.updateDropdownContent();
|
42
|
+
}
|
43
|
+
}
|
44
|
+
getBackgroundElem() {
|
45
|
+
const backgroundComponents = [];
|
46
|
+
for (const component of this.editor.image.getBackgroundComponents()) {
|
47
|
+
if (component instanceof ImageBackground) {
|
48
|
+
backgroundComponents.push(component);
|
49
|
+
}
|
50
|
+
}
|
51
|
+
if (backgroundComponents.length === 0) {
|
52
|
+
return null;
|
53
|
+
}
|
54
|
+
// Return the last background component in the list — the component with highest z-index.
|
55
|
+
return backgroundComponents[backgroundComponents.length - 1];
|
56
|
+
}
|
57
|
+
setBackgroundColor(color) {
|
58
|
+
this.editor.dispatch(this.editor.setBackgroundColor(color));
|
59
|
+
}
|
60
|
+
getBackgroundColor() {
|
61
|
+
var _a, _b;
|
62
|
+
const background = this.getBackgroundElem();
|
63
|
+
return (_b = (_a = background === null || background === void 0 ? void 0 : background.getStyle()) === null || _a === void 0 ? void 0 : _a.color) !== null && _b !== void 0 ? _b : Color4.transparent;
|
64
|
+
}
|
65
|
+
updateImportExportRectSize(size) {
|
66
|
+
const filterDimension = (dim) => {
|
67
|
+
if (dim !== undefined && (!isFinite(dim) || dim <= 0)) {
|
68
|
+
dim = 100;
|
69
|
+
}
|
70
|
+
return dim;
|
71
|
+
};
|
72
|
+
const width = filterDimension(size.width);
|
73
|
+
const height = filterDimension(size.height);
|
74
|
+
const currentRect = this.editor.getImportExportRect();
|
75
|
+
const newRect = new Rect2(currentRect.x, currentRect.y, width !== null && width !== void 0 ? width : currentRect.w, height !== null && height !== void 0 ? height : currentRect.h);
|
76
|
+
this.editor.dispatch(this.editor.image.setImportExportRect(newRect));
|
77
|
+
this.editor.queueRerender();
|
78
|
+
}
|
79
|
+
fillDropdown(dropdown) {
|
80
|
+
const container = document.createElement('div');
|
81
|
+
const backgroundColorRow = document.createElement('div');
|
82
|
+
const backgroundColorLabel = document.createElement('label');
|
83
|
+
backgroundColorLabel.innerText = this.localizationTable.backgroundColor;
|
84
|
+
const [colorInput, backgroundColorInputContainer, setBgColorInputValue] = makeColorInput(this.editor, color => {
|
85
|
+
if (!color.eq(this.getBackgroundColor())) {
|
86
|
+
this.setBackgroundColor(color);
|
87
|
+
}
|
88
|
+
});
|
89
|
+
colorInput.id = `document-properties-color-input-${DocumentPropertiesWidget.idCounter++}`;
|
90
|
+
backgroundColorLabel.htmlFor = colorInput.id;
|
91
|
+
backgroundColorRow.replaceChildren(backgroundColorLabel, backgroundColorInputContainer);
|
92
|
+
const addDimensionRow = (labelContent, onChange) => {
|
93
|
+
const row = document.createElement('div');
|
94
|
+
const label = document.createElement('label');
|
95
|
+
const spacer = document.createElement('span');
|
96
|
+
const input = document.createElement('input');
|
97
|
+
label.innerText = labelContent;
|
98
|
+
input.type = 'number';
|
99
|
+
input.min = '0';
|
100
|
+
input.id = `document-properties-dimension-row-${DocumentPropertiesWidget.idCounter++}`;
|
101
|
+
label.htmlFor = input.id;
|
102
|
+
spacer.style.flexGrow = '1';
|
103
|
+
input.style.flexGrow = '2';
|
104
|
+
input.style.width = '25px';
|
105
|
+
row.style.display = 'flex';
|
106
|
+
input.oninput = () => {
|
107
|
+
onChange(parseFloat(input.value));
|
108
|
+
};
|
109
|
+
row.replaceChildren(label, spacer, input);
|
110
|
+
return {
|
111
|
+
setValue: (value) => {
|
112
|
+
input.value = value.toString();
|
113
|
+
},
|
114
|
+
element: row,
|
115
|
+
};
|
116
|
+
};
|
117
|
+
const imageWidthRow = addDimensionRow(this.localizationTable.imageWidthOption, (value) => {
|
118
|
+
this.updateImportExportRectSize({ width: value });
|
119
|
+
});
|
120
|
+
const imageHeightRow = addDimensionRow(this.localizationTable.imageHeightOption, (value) => {
|
121
|
+
this.updateImportExportRectSize({ height: value });
|
122
|
+
});
|
123
|
+
this.updateDropdownContent = () => {
|
124
|
+
setBgColorInputValue(this.getBackgroundColor());
|
125
|
+
const importExportRect = this.editor.getImportExportRect();
|
126
|
+
imageWidthRow.setValue(importExportRect.width);
|
127
|
+
imageHeightRow.setValue(importExportRect.height);
|
128
|
+
};
|
129
|
+
this.updateDropdownContent();
|
130
|
+
container.replaceChildren(backgroundColorRow, imageWidthRow.element, imageHeightRow.element);
|
131
|
+
dropdown.replaceChildren(container);
|
132
|
+
return true;
|
133
|
+
}
|
134
|
+
}
|
135
|
+
DocumentPropertiesWidget.idCounter = 0;
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import Editor from '../../Editor';
|
2
|
+
import { ToolbarLocalization } from '../localization';
|
3
|
+
import BaseWidget from './BaseWidget';
|
4
|
+
export default class OverflowWidget extends BaseWidget {
|
5
|
+
private overflowChildren;
|
6
|
+
private overflowContainer;
|
7
|
+
constructor(editor: Editor, localizationTable?: ToolbarLocalization);
|
8
|
+
protected getTitle(): string;
|
9
|
+
protected createIcon(): Element | null;
|
10
|
+
protected handleClick(): void;
|
11
|
+
protected fillDropdown(dropdown: HTMLElement): boolean;
|
12
|
+
/**
|
13
|
+
* Removes all `BaseWidget`s from this and returns them.
|
14
|
+
*/
|
15
|
+
clearChildren(): BaseWidget[];
|
16
|
+
getChildWidgets(): BaseWidget[];
|
17
|
+
hasAsChild(widget: BaseWidget): boolean;
|
18
|
+
/**
|
19
|
+
* Adds `widget` to this.
|
20
|
+
* `widget`'s previous parent is still responsible
|
21
|
+
* for serializing/deserializing its state.
|
22
|
+
*/
|
23
|
+
addToOverflow(widget: BaseWidget): void;
|
24
|
+
canBeInOverflowMenu(): boolean;
|
25
|
+
}
|