js-draw 0.1.8 → 0.1.11
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/CHANGELOG.md +15 -0
- package/README.md +15 -3
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +3 -0
- package/dist/src/Editor.js +45 -16
- package/dist/src/UndoRedoHistory.js +3 -0
- package/dist/src/Viewport.js +2 -0
- package/dist/src/bundle/bundled.d.ts +2 -1
- package/dist/src/bundle/bundled.js +2 -1
- package/dist/src/geometry/LineSegment2.d.ts +1 -0
- package/dist/src/geometry/LineSegment2.js +16 -0
- package/dist/src/geometry/Rect2.d.ts +1 -0
- package/dist/src/geometry/Rect2.js +16 -0
- package/dist/src/localizations/en.d.ts +3 -0
- package/dist/src/localizations/en.js +4 -0
- package/dist/src/localizations/es.d.ts +3 -0
- package/dist/src/localizations/es.js +18 -0
- package/dist/src/localizations/getLocalizationTable.d.ts +3 -0
- package/dist/src/localizations/getLocalizationTable.js +43 -0
- package/dist/src/toolbar/HTMLToolbar.d.ts +1 -0
- package/dist/src/toolbar/HTMLToolbar.js +10 -8
- package/dist/src/toolbar/icons.js +13 -13
- package/dist/src/toolbar/localization.d.ts +2 -0
- package/dist/src/toolbar/localization.js +2 -0
- package/dist/src/toolbar/makeColorInput.js +22 -8
- package/dist/src/toolbar/widgets/BaseWidget.js +29 -0
- package/dist/src/toolbar/widgets/HandToolWidget.js +8 -1
- package/dist/src/tools/BaseTool.d.ts +2 -1
- package/dist/src/tools/BaseTool.js +3 -0
- package/dist/src/tools/PanZoom.d.ts +2 -1
- package/dist/src/tools/PanZoom.js +10 -4
- package/dist/src/tools/SelectionTool.d.ts +9 -2
- package/dist/src/tools/SelectionTool.js +131 -19
- package/dist/src/tools/ToolController.js +6 -2
- package/dist/src/tools/localization.d.ts +1 -0
- package/dist/src/tools/localization.js +1 -0
- package/dist/src/types.d.ts +9 -2
- package/dist/src/types.js +1 -0
- package/package.json +9 -1
- package/src/Editor.ts +54 -14
- package/src/UndoRedoHistory.ts +4 -0
- package/src/Viewport.ts +2 -0
- package/src/bundle/bundled.ts +2 -1
- package/src/geometry/LineSegment2.test.ts +15 -0
- package/src/geometry/LineSegment2.ts +20 -0
- package/src/geometry/Rect2.test.ts +20 -7
- package/src/geometry/Rect2.ts +19 -1
- package/src/localizations/en.ts +8 -0
- package/src/localizations/es.ts +62 -0
- package/src/localizations/getLocalizationTable.test.ts +27 -0
- package/src/localizations/getLocalizationTable.ts +53 -0
- package/src/toolbar/HTMLToolbar.ts +11 -9
- package/src/toolbar/icons.ts +13 -13
- package/src/toolbar/localization.ts +4 -0
- package/src/toolbar/makeColorInput.ts +25 -10
- package/src/toolbar/toolbar.css +6 -2
- package/src/toolbar/widgets/BaseWidget.ts +34 -0
- package/src/toolbar/widgets/HandToolWidget.ts +12 -1
- package/src/tools/BaseTool.ts +5 -1
- package/src/tools/PanZoom.ts +13 -4
- package/src/tools/SelectionTool.test.ts +24 -1
- package/src/tools/SelectionTool.ts +158 -23
- package/src/tools/ToolController.ts +6 -2
- package/src/tools/localization.ts +2 -0
- package/src/types.ts +10 -1
- package/dist-test/test-dist-bundle.html +0 -42
package/dist/src/Editor.d.ts
CHANGED
@@ -15,6 +15,8 @@ export interface EditorSettings {
|
|
15
15
|
renderingMode: RenderingMode;
|
16
16
|
localization: Partial<EditorLocalization>;
|
17
17
|
wheelEventsEnabled: boolean | 'only-if-focused';
|
18
|
+
minZoom: number;
|
19
|
+
maxZoom: number;
|
18
20
|
}
|
19
21
|
export declare class Editor {
|
20
22
|
private container;
|
@@ -37,6 +39,7 @@ export declare class Editor {
|
|
37
39
|
announceForAccessibility(message: string): void;
|
38
40
|
addToolbar(defaultLayout?: boolean): HTMLToolbar;
|
39
41
|
private registerListeners;
|
42
|
+
handleKeyEventsFrom(elem: HTMLElement): void;
|
40
43
|
dispatch(command: Command, addToHistory?: boolean): void;
|
41
44
|
dispatchNoAnnounce(command: Command, addToHistory?: boolean): void;
|
42
45
|
private asyncApplyOrUnapplyCommands;
|
package/dist/src/Editor.js
CHANGED
@@ -23,11 +23,10 @@ import Color4 from './Color4';
|
|
23
23
|
import SVGLoader from './SVGLoader';
|
24
24
|
import Pointer from './Pointer';
|
25
25
|
import Mat33 from './geometry/Mat33';
|
26
|
-
import
|
26
|
+
import getLocalizationTable from './localizations/getLocalizationTable';
|
27
27
|
export class Editor {
|
28
28
|
constructor(parent, settings = {}) {
|
29
|
-
var _a, _b;
|
30
|
-
this.localization = defaultEditorLocalization;
|
29
|
+
var _a, _b, _c, _d;
|
31
30
|
this.announceUndoCallback = (command) => {
|
32
31
|
this.announceForAccessibility(this.localization.undoAnnouncement(command.description(this.localization)));
|
33
32
|
};
|
@@ -35,12 +34,14 @@ export class Editor {
|
|
35
34
|
this.announceForAccessibility(this.localization.redoAnnouncement(command.description(this.localization)));
|
36
35
|
};
|
37
36
|
this.rerenderQueued = false;
|
38
|
-
this.localization = Object.assign(Object.assign({},
|
37
|
+
this.localization = Object.assign(Object.assign({}, getLocalizationTable()), settings.localization);
|
39
38
|
// Fill default settings.
|
40
39
|
this.settings = {
|
41
40
|
wheelEventsEnabled: (_a = settings.wheelEventsEnabled) !== null && _a !== void 0 ? _a : true,
|
42
41
|
renderingMode: (_b = settings.renderingMode) !== null && _b !== void 0 ? _b : RenderingMode.CanvasRenderer,
|
43
42
|
localization: this.localization,
|
43
|
+
minZoom: (_c = settings.minZoom) !== null && _c !== void 0 ? _c : 2e-10,
|
44
|
+
maxZoom: (_d = settings.maxZoom) !== null && _d !== void 0 ? _d : 1e12,
|
44
45
|
};
|
45
46
|
this.container = document.createElement('div');
|
46
47
|
this.renderingRegion = document.createElement('div');
|
@@ -72,6 +73,20 @@ export class Editor {
|
|
72
73
|
this.registerListeners();
|
73
74
|
this.queueRerender();
|
74
75
|
this.hideLoadingWarning();
|
76
|
+
// Enforce zoom limits.
|
77
|
+
this.notifier.on(EditorEventType.ViewportChanged, evt => {
|
78
|
+
if (evt.kind === EditorEventType.ViewportChanged) {
|
79
|
+
const zoom = evt.newTransform.transformVec3(Vec2.unitX).length();
|
80
|
+
if (zoom > this.settings.maxZoom || zoom < this.settings.minZoom) {
|
81
|
+
const oldZoom = evt.oldTransform.transformVec3(Vec2.unitX).length();
|
82
|
+
let resetTransform = Mat33.identity;
|
83
|
+
if (oldZoom <= this.settings.maxZoom && oldZoom >= this.settings.minZoom) {
|
84
|
+
resetTransform = evt.oldTransform;
|
85
|
+
}
|
86
|
+
this.viewport.resetTransform(resetTransform);
|
87
|
+
}
|
88
|
+
}
|
89
|
+
});
|
75
90
|
}
|
76
91
|
// Returns a reference to this' container.
|
77
92
|
// Example usage:
|
@@ -176,18 +191,7 @@ export class Editor {
|
|
176
191
|
this.renderingRegion.addEventListener('pointercancel', evt => {
|
177
192
|
pointerEnd(evt);
|
178
193
|
});
|
179
|
-
this.renderingRegion
|
180
|
-
if (this.toolController.dispatchInputEvent({
|
181
|
-
kind: InputEvtType.KeyPressEvent,
|
182
|
-
key: evt.key,
|
183
|
-
ctrlKey: evt.ctrlKey,
|
184
|
-
})) {
|
185
|
-
evt.preventDefault();
|
186
|
-
}
|
187
|
-
else if (evt.key === 'Escape') {
|
188
|
-
this.renderingRegion.blur();
|
189
|
-
}
|
190
|
-
});
|
194
|
+
this.handleKeyEventsFrom(this.renderingRegion);
|
191
195
|
this.container.addEventListener('wheel', evt => {
|
192
196
|
let delta = Vec3.of(evt.deltaX, evt.deltaY, evt.deltaZ);
|
193
197
|
// Process wheel events if the ctrl key is down, even if disabled -- we do want to handle
|
@@ -234,6 +238,31 @@ export class Editor {
|
|
234
238
|
this.queueRerender();
|
235
239
|
});
|
236
240
|
}
|
241
|
+
// Adds event listners for keypresses to [elem] and forwards those events to the
|
242
|
+
// editor.
|
243
|
+
handleKeyEventsFrom(elem) {
|
244
|
+
elem.addEventListener('keydown', evt => {
|
245
|
+
if (this.toolController.dispatchInputEvent({
|
246
|
+
kind: InputEvtType.KeyPressEvent,
|
247
|
+
key: evt.key,
|
248
|
+
ctrlKey: evt.ctrlKey,
|
249
|
+
})) {
|
250
|
+
evt.preventDefault();
|
251
|
+
}
|
252
|
+
else if (evt.key === 'Escape') {
|
253
|
+
this.renderingRegion.blur();
|
254
|
+
}
|
255
|
+
});
|
256
|
+
elem.addEventListener('keyup', evt => {
|
257
|
+
if (this.toolController.dispatchInputEvent({
|
258
|
+
kind: InputEvtType.KeyUpEvent,
|
259
|
+
key: evt.key,
|
260
|
+
ctrlKey: evt.ctrlKey,
|
261
|
+
})) {
|
262
|
+
evt.preventDefault();
|
263
|
+
}
|
264
|
+
});
|
265
|
+
}
|
237
266
|
// Adds to history by default
|
238
267
|
dispatch(command, addToHistory = true) {
|
239
268
|
if (addToHistory) {
|
package/dist/src/Viewport.js
CHANGED
@@ -38,11 +38,13 @@ export class Viewport {
|
|
38
38
|
// Updates the transformation directly. Using ViewportTransform is preferred.
|
39
39
|
// [newTransform] should map from canvas coordinates to screen coordinates.
|
40
40
|
resetTransform(newTransform = Mat33.identity) {
|
41
|
+
const oldTransform = this.transform;
|
41
42
|
this.transform = newTransform;
|
42
43
|
this.inverseTransform = newTransform.inverse();
|
43
44
|
this.notifier.dispatch(EditorEventType.ViewportChanged, {
|
44
45
|
kind: EditorEventType.ViewportChanged,
|
45
46
|
newTransform,
|
47
|
+
oldTransform,
|
46
48
|
});
|
47
49
|
}
|
48
50
|
get screenToCanvasTransform() {
|
@@ -1,5 +1,6 @@
|
|
1
1
|
// Main entrypoint for Webpack when building a bundle for release.
|
2
2
|
import '../styles';
|
3
3
|
import Editor from '../Editor';
|
4
|
+
import getLocalizationTable from '../localizations/getLocalizationTable';
|
4
5
|
export default Editor;
|
5
|
-
export { Editor };
|
6
|
+
export { Editor, getLocalizationTable };
|
@@ -97,4 +97,20 @@ export default class LineSegment2 {
|
|
97
97
|
t: resultT,
|
98
98
|
};
|
99
99
|
}
|
100
|
+
// Returns the closest point on this to [target]
|
101
|
+
closestPointTo(target) {
|
102
|
+
// Distance from P1 along this' direction.
|
103
|
+
const projectedDistFromP1 = target.minus(this.p1).dot(this.direction);
|
104
|
+
const projectedDistFromP2 = this.length - projectedDistFromP1;
|
105
|
+
const projection = this.p1.plus(this.direction.times(projectedDistFromP1));
|
106
|
+
if (projectedDistFromP1 > 0 && projectedDistFromP1 < this.length) {
|
107
|
+
return projection;
|
108
|
+
}
|
109
|
+
if (Math.abs(projectedDistFromP2) < Math.abs(projectedDistFromP1)) {
|
110
|
+
return this.p2;
|
111
|
+
}
|
112
|
+
else {
|
113
|
+
return this.p1;
|
114
|
+
}
|
115
|
+
}
|
100
116
|
}
|
@@ -30,6 +30,7 @@ export default class Rect2 {
|
|
30
30
|
divideIntoGrid(columns: number, rows: number): Rect2[];
|
31
31
|
grownToPoint(point: Point2, margin?: number): Rect2;
|
32
32
|
grownBy(margin: number): Rect2;
|
33
|
+
getClosestPointOnBoundaryTo(target: Point2): import("./Vec3").default;
|
33
34
|
get corners(): Point2[];
|
34
35
|
get maxDimension(): number;
|
35
36
|
get topRight(): import("./Vec3").default;
|
@@ -25,6 +25,7 @@ export default class Rect2 {
|
|
25
25
|
translatedBy(vec) {
|
26
26
|
return new Rect2(vec.x + this.x, vec.y + this.y, this.w, this.h);
|
27
27
|
}
|
28
|
+
// Returns a copy of this with the given size (but same top-left).
|
28
29
|
resizedTo(size) {
|
29
30
|
return new Rect2(this.x, this.y, size.x, size.y);
|
30
31
|
}
|
@@ -109,6 +110,21 @@ export default class Rect2 {
|
|
109
110
|
grownBy(margin) {
|
110
111
|
return new Rect2(this.x - margin, this.y - margin, this.w + margin * 2, this.h + margin * 2);
|
111
112
|
}
|
113
|
+
getClosestPointOnBoundaryTo(target) {
|
114
|
+
const closestEdgePoints = this.getEdges().map(edge => {
|
115
|
+
return edge.closestPointTo(target);
|
116
|
+
});
|
117
|
+
let closest = null;
|
118
|
+
let closestDist = null;
|
119
|
+
for (const point of closestEdgePoints) {
|
120
|
+
const dist = point.minus(target).length();
|
121
|
+
if (closestDist === null || dist < closestDist) {
|
122
|
+
closest = point;
|
123
|
+
closestDist = dist;
|
124
|
+
}
|
125
|
+
}
|
126
|
+
return closest;
|
127
|
+
}
|
112
128
|
get corners() {
|
113
129
|
return [
|
114
130
|
this.bottomRight,
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import { defaultEditorLocalization } from '../localization';
|
2
|
+
// A partial Spanish localization.
|
3
|
+
const localization = Object.assign(Object.assign({}, defaultEditorLocalization), {
|
4
|
+
// Strings for the main editor interface
|
5
|
+
// (see src/localization.ts)
|
6
|
+
loading: (percentage) => `Cargando: ${percentage}%...`, imageEditor: 'Editor de dibujos', undoAnnouncement: (commandDescription) => `${commandDescription} fue deshecho`, redoAnnouncement: (commandDescription) => `${commandDescription} fue rehecho`, undo: 'Deshace', redo: 'Rehace',
|
7
|
+
// Strings for the toolbar
|
8
|
+
// (see src/toolbar/localization.ts)
|
9
|
+
pen: 'Lapiz', eraser: 'Borrador', select: 'Selecciona', thicknessLabel: 'Tamaño: ', colorLabel: 'Color: ', doneLoading: 'El cargado terminó', fontLabel: 'Fuente: ', anyDevicePanning: 'Mover la pantalla con todo dispotivo', touchPanning: 'Mover la pantalla con un dedo', touchPanTool: 'Instrumento de mover la pantalla con un dedo', outlinedRectanglePen: 'Rectángulo con nada más que un borde', filledRectanglePen: 'Rectángulo sin borde', linePen: 'Línea', arrowPen: 'Flecha', freehandPen: 'Dibuja sin restricción de forma', selectObjectType: 'Forma de dibuja:', handTool: 'Mover', zoom: 'Zoom', resetView: 'Reiniciar vista', resizeImageToSelection: 'Redimensionar la imagen a lo que está seleccionado', deleteSelection: 'Borra la selección', duplicateSelection: 'Duplica la selección', pickColorFronScreen: 'Selecciona un color de la pantalla', dropdownShown(toolName) {
|
10
|
+
return `Menú por ${toolName} es visible`;
|
11
|
+
}, dropdownHidden: function (toolName) {
|
12
|
+
return `Menú por ${toolName} fue ocultado`;
|
13
|
+
}, colorChangedAnnouncement: function (color) {
|
14
|
+
return `Color fue cambiado a ${color}`;
|
15
|
+
}, keyboardPanZoom: 'Mover la pantalla con el teclado', penTool: function (penId) {
|
16
|
+
return `Lapiz ${penId}`;
|
17
|
+
}, selectionTool: 'Selecciona', eraserTool: 'Borrador', textTool: 'Texto', enterTextToInsert: 'Entra texto', rerenderAsText: 'Redibuja la pantalla al texto' });
|
18
|
+
export default localization;
|
@@ -0,0 +1,43 @@
|
|
1
|
+
import { defaultEditorLocalization } from '../localization';
|
2
|
+
import en from './en';
|
3
|
+
import es from './es';
|
4
|
+
const allLocales = {
|
5
|
+
en,
|
6
|
+
es,
|
7
|
+
};
|
8
|
+
// [locale]: A string in the format languageCode_Region or just languageCode. For example, en_US.
|
9
|
+
const languageFromLocale = (locale) => {
|
10
|
+
const matches = /^(\w+)[_-](\w+)$/.exec(locale);
|
11
|
+
if (!matches) {
|
12
|
+
// If not in languageCode_region format, the locale should be the
|
13
|
+
// languageCode. Return that.
|
14
|
+
return locale;
|
15
|
+
}
|
16
|
+
return matches[1];
|
17
|
+
};
|
18
|
+
const getLocalizationTable = (userLocales) => {
|
19
|
+
userLocales !== null && userLocales !== void 0 ? userLocales : (userLocales = navigator.languages);
|
20
|
+
let prevLanguage;
|
21
|
+
for (const locale of userLocales) {
|
22
|
+
const language = languageFromLocale(locale);
|
23
|
+
// If the specific localization of the language is not available, but
|
24
|
+
// a localization for the language is,
|
25
|
+
if (prevLanguage && language !== prevLanguage) {
|
26
|
+
if (prevLanguage in allLocales) {
|
27
|
+
return allLocales[prevLanguage];
|
28
|
+
}
|
29
|
+
}
|
30
|
+
// If the full locale (e.g. en_US) is available,
|
31
|
+
if (locale in allLocales) {
|
32
|
+
return allLocales[locale];
|
33
|
+
}
|
34
|
+
prevLanguage = language;
|
35
|
+
}
|
36
|
+
if (prevLanguage && prevLanguage in allLocales) {
|
37
|
+
return allLocales[prevLanguage];
|
38
|
+
}
|
39
|
+
else {
|
40
|
+
return defaultEditorLocalization;
|
41
|
+
}
|
42
|
+
};
|
43
|
+
export default getLocalizationTable;
|
@@ -6,6 +6,7 @@ export default class HTMLToolbar {
|
|
6
6
|
private editor;
|
7
7
|
private localizationTable;
|
8
8
|
private container;
|
9
|
+
private static colorisStarted;
|
9
10
|
constructor(editor: Editor, parent: HTMLElement, localizationTable?: ToolbarLocalization);
|
10
11
|
setupColorPickers(): void;
|
11
12
|
addActionButton(title: string | ActionButtonIcon, command: () => void, parent?: Element): HTMLButtonElement;
|
@@ -23,7 +23,10 @@ export default class HTMLToolbar {
|
|
23
23
|
this.container.classList.add(`${toolbarCSSPrefix}root`);
|
24
24
|
this.container.setAttribute('role', 'toolbar');
|
25
25
|
parent.appendChild(this.container);
|
26
|
-
|
26
|
+
if (!HTMLToolbar.colorisStarted) {
|
27
|
+
colorisInit();
|
28
|
+
HTMLToolbar.colorisStarted = true;
|
29
|
+
}
|
27
30
|
this.setupColorPickers();
|
28
31
|
}
|
29
32
|
setupColorPickers() {
|
@@ -105,13 +108,13 @@ export default class HTMLToolbar {
|
|
105
108
|
const undoRedoGroup = document.createElement('div');
|
106
109
|
undoRedoGroup.classList.add(`${toolbarCSSPrefix}buttonGroup`);
|
107
110
|
const undoButton = this.addActionButton({
|
108
|
-
label:
|
111
|
+
label: this.localizationTable.undo,
|
109
112
|
icon: makeUndoIcon()
|
110
113
|
}, () => {
|
111
114
|
this.editor.history.undo();
|
112
115
|
}, undoRedoGroup);
|
113
116
|
const redoButton = this.addActionButton({
|
114
|
-
label:
|
117
|
+
label: this.localizationTable.redo,
|
115
118
|
icon: makeRedoIcon(),
|
116
119
|
}, () => {
|
117
120
|
this.editor.history.redo();
|
@@ -154,11 +157,9 @@ export default class HTMLToolbar {
|
|
154
157
|
}
|
155
158
|
(new TextToolWidget(this.editor, tool, this.localizationTable)).addTo(this.container);
|
156
159
|
}
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
}
|
161
|
-
(new HandToolWidget(this.editor, tool, this.localizationTable)).addTo(this.container);
|
160
|
+
const panZoomTool = toolController.getMatchingTools(ToolType.PanZoom)[0];
|
161
|
+
if (panZoomTool && panZoomTool instanceof PanZoom) {
|
162
|
+
(new HandToolWidget(this.editor, panZoomTool, this.localizationTable)).addTo(this.container);
|
162
163
|
}
|
163
164
|
this.setupColorPickers();
|
164
165
|
}
|
@@ -166,3 +167,4 @@ export default class HTMLToolbar {
|
|
166
167
|
this.addUndoRedoButtons();
|
167
168
|
}
|
168
169
|
}
|
170
|
+
HTMLToolbar.colorisStarted = false;
|
@@ -3,11 +3,11 @@ import { Vec2 } from '../geometry/Vec2';
|
|
3
3
|
import SVGRenderer from '../rendering/renderers/SVGRenderer';
|
4
4
|
import Viewport from '../Viewport';
|
5
5
|
const svgNamespace = 'http://www.w3.org/2000/svg';
|
6
|
-
const
|
7
|
-
style='fill: var(--
|
6
|
+
const iconColorFill = `
|
7
|
+
style='fill: var(--icon-color);'
|
8
8
|
`;
|
9
|
-
const
|
10
|
-
style='fill: var(--
|
9
|
+
const iconColorStrokeFill = `
|
10
|
+
style='fill: var(--icon-color); stroke: var(--icon-color);'
|
11
11
|
`;
|
12
12
|
const checkerboardPatternDef = `
|
13
13
|
<pattern
|
@@ -31,7 +31,7 @@ export const makeRedoIcon = (mirror = false) => {
|
|
31
31
|
icon.innerHTML = `
|
32
32
|
<style>
|
33
33
|
.toolbar-svg-undo-redo-icon {
|
34
|
-
stroke: var(--
|
34
|
+
stroke: var(--icon-color);
|
35
35
|
stroke-width: 12;
|
36
36
|
stroke-linejoin: round;
|
37
37
|
stroke-linecap: round;
|
@@ -54,7 +54,7 @@ export const makeDropdownIcon = () => {
|
|
54
54
|
<g>
|
55
55
|
<path
|
56
56
|
d='M5,10 L50,90 L95,10 Z'
|
57
|
-
${
|
57
|
+
${iconColorFill}
|
58
58
|
/>
|
59
59
|
</g>
|
60
60
|
`;
|
@@ -69,7 +69,7 @@ export const makeEraserIcon = () => {
|
|
69
69
|
<rect x=10 y=50 width=80 height=30 rx=10 fill='pink' />
|
70
70
|
<rect
|
71
71
|
x=10 y=10 width=80 height=50
|
72
|
-
${
|
72
|
+
${iconColorFill}
|
73
73
|
/>
|
74
74
|
</g>
|
75
75
|
`;
|
@@ -116,7 +116,7 @@ export const makeHandToolIcon = () => {
|
|
116
116
|
|
117
117
|
fill='none'
|
118
118
|
style='
|
119
|
-
stroke: var(--
|
119
|
+
stroke: var(--icon-color);
|
120
120
|
stroke-width: 2;
|
121
121
|
'
|
122
122
|
/>
|
@@ -158,7 +158,7 @@ export const makeTouchPanningIcon = () => {
|
|
158
158
|
'
|
159
159
|
fill='none'
|
160
160
|
style='
|
161
|
-
stroke: var(--
|
161
|
+
stroke: var(--icon-color);
|
162
162
|
stroke-width: 2;
|
163
163
|
'
|
164
164
|
/>
|
@@ -222,7 +222,7 @@ export const makeAllDevicePanningIcon = () => {
|
|
222
222
|
'
|
223
223
|
fill='none'
|
224
224
|
style='
|
225
|
-
stroke: var(--
|
225
|
+
stroke: var(--icon-color);
|
226
226
|
stroke-width: 2;
|
227
227
|
'
|
228
228
|
/>
|
@@ -241,7 +241,7 @@ export const makeZoomIcon = () => {
|
|
241
241
|
textNode.style.textAlign = 'center';
|
242
242
|
textNode.style.textAnchor = 'middle';
|
243
243
|
textNode.style.fontSize = '55px';
|
244
|
-
textNode.style.fill = 'var(--
|
244
|
+
textNode.style.fill = 'var(--icon-color)';
|
245
245
|
textNode.style.fontFamily = 'monospace';
|
246
246
|
icon.appendChild(textNode);
|
247
247
|
};
|
@@ -282,7 +282,7 @@ export const makePenIcon = (tipThickness, color) => {
|
|
282
282
|
<!-- Pen grip -->
|
283
283
|
<path
|
284
284
|
d='M10,10 L90,10 L90,60 L${50 + halfThickness},80 L${50 - halfThickness},80 L10,60 Z'
|
285
|
-
${
|
285
|
+
${iconColorStrokeFill}
|
286
286
|
/>
|
287
287
|
</g>
|
288
288
|
<g>
|
@@ -348,7 +348,7 @@ export const makePipetteIcon = (color) => {
|
|
348
348
|
65,15 65,5 47,6
|
349
349
|
Z
|
350
350
|
`);
|
351
|
-
pipette.style.fill = 'var(--
|
351
|
+
pipette.style.fill = 'var(--icon-color)';
|
352
352
|
if (color) {
|
353
353
|
const defs = document.createElementNS(svgNamespace, 'defs');
|
354
354
|
defs.innerHTML = checkerboardPatternDef;
|
@@ -21,6 +21,8 @@ export interface ToolbarLocalization {
|
|
21
21
|
undo: string;
|
22
22
|
redo: string;
|
23
23
|
zoom: string;
|
24
|
+
resetView: string;
|
25
|
+
selectionToolKeyboardShortcuts: string;
|
24
26
|
dropdownShown: (toolName: string) => string;
|
25
27
|
dropdownHidden: (toolName: string) => string;
|
26
28
|
zoomLevel: (zoomPercentage: number) => string;
|
@@ -4,6 +4,7 @@ export const defaultToolbarLocalization = {
|
|
4
4
|
select: 'Select',
|
5
5
|
handTool: 'Pan',
|
6
6
|
zoom: 'Zoom',
|
7
|
+
resetView: 'Reset view',
|
7
8
|
thicknessLabel: 'Thickness: ',
|
8
9
|
colorLabel: 'Color: ',
|
9
10
|
fontLabel: 'Font: ',
|
@@ -14,6 +15,7 @@ export const defaultToolbarLocalization = {
|
|
14
15
|
redo: 'Redo',
|
15
16
|
selectObjectType: 'Object type: ',
|
16
17
|
pickColorFronScreen: 'Pick color from screen',
|
18
|
+
selectionToolKeyboardShortcuts: 'Selection tool: Use arrow keys to move selected items, lowercase/uppercase ‘i’ and ‘o’ to resize.',
|
17
19
|
touchPanning: 'Touchscreen panning',
|
18
20
|
anyDevicePanning: 'Any device panning',
|
19
21
|
freehandPen: 'Freehand',
|
@@ -12,7 +12,7 @@ export const makeColorInput = (editor, onColorChange) => {
|
|
12
12
|
colorInputContainer.appendChild(colorInput);
|
13
13
|
addPipetteTool(editor, colorInputContainer, (color) => {
|
14
14
|
colorInput.value = color.toHexString();
|
15
|
-
|
15
|
+
onInputEnd();
|
16
16
|
// Update the color preview, if it exists (may be managed by Coloris).
|
17
17
|
const parentElem = colorInput.parentElement;
|
18
18
|
if (parentElem && parentElem.classList.contains('clr-field')) {
|
@@ -22,12 +22,17 @@ export const makeColorInput = (editor, onColorChange) => {
|
|
22
22
|
let currentColor;
|
23
23
|
const handleColorInput = () => {
|
24
24
|
currentColor = Color4.fromHex(colorInput.value);
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
25
|
+
};
|
26
|
+
const onInputEnd = () => {
|
27
|
+
handleColorInput();
|
28
|
+
if (currentColor) {
|
29
|
+
editor.announceForAccessibility(editor.localization.colorChangedAnnouncement(currentColor.toHexString()));
|
30
|
+
onColorChange(currentColor);
|
31
|
+
editor.notifier.dispatch(EditorEventType.ColorPickerColorSelected, {
|
32
|
+
kind: EditorEventType.ColorPickerColorSelected,
|
33
|
+
color: currentColor,
|
34
|
+
});
|
35
|
+
}
|
31
36
|
};
|
32
37
|
colorInput.oninput = handleColorInput;
|
33
38
|
colorInput.addEventListener('open', () => {
|
@@ -41,6 +46,7 @@ export const makeColorInput = (editor, onColorChange) => {
|
|
41
46
|
kind: EditorEventType.ColorPickerToggled,
|
42
47
|
open: false,
|
43
48
|
});
|
49
|
+
onInputEnd();
|
44
50
|
});
|
45
51
|
return [colorInput, colorInputContainer];
|
46
52
|
};
|
@@ -54,10 +60,13 @@ const addPipetteTool = (editor, container, onColorChange) => {
|
|
54
60
|
};
|
55
61
|
updatePipetteIcon();
|
56
62
|
const pipetteTool = editor.toolController.getMatchingTools(ToolType.Pipette)[0];
|
57
|
-
const
|
63
|
+
const endColorSelectMode = () => {
|
58
64
|
pipetteTool === null || pipetteTool === void 0 ? void 0 : pipetteTool.clearColorListener();
|
59
65
|
updatePipetteIcon();
|
60
66
|
pipetteButton.classList.remove('active');
|
67
|
+
};
|
68
|
+
const pipetteColorSelect = (color) => {
|
69
|
+
endColorSelectMode();
|
61
70
|
if (color) {
|
62
71
|
onColorChange(color);
|
63
72
|
}
|
@@ -71,6 +80,11 @@ const addPipetteTool = (editor, container, onColorChange) => {
|
|
71
80
|
}
|
72
81
|
};
|
73
82
|
pipetteButton.onclick = () => {
|
83
|
+
// If already picking, cancel it.
|
84
|
+
if (pipetteButton.classList.contains('active')) {
|
85
|
+
endColorSelectMode();
|
86
|
+
return;
|
87
|
+
}
|
74
88
|
pipetteTool === null || pipetteTool === void 0 ? void 0 : pipetteTool.setColorListener(pipetteColorPreview, pipetteColorSelect);
|
75
89
|
if (pipetteTool) {
|
76
90
|
pipetteButton.classList.add('active');
|
@@ -10,6 +10,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
10
10
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
11
11
|
};
|
12
12
|
var _BaseWidget_hasDropdown;
|
13
|
+
import { InputEvtType } from '../../types';
|
13
14
|
import { toolbarCSSPrefix } from '../HTMLToolbar';
|
14
15
|
import { makeDropdownIcon } from '../icons';
|
15
16
|
export default class BaseWidget {
|
@@ -44,6 +45,34 @@ export default class BaseWidget {
|
|
44
45
|
return true;
|
45
46
|
}
|
46
47
|
setupActionBtnClickListener(button) {
|
48
|
+
const clickTriggers = { Enter: true, ' ': true, };
|
49
|
+
button.onkeydown = (evt) => {
|
50
|
+
let handled = false;
|
51
|
+
if (evt.key in clickTriggers) {
|
52
|
+
if (!this.disabled) {
|
53
|
+
this.handleClick();
|
54
|
+
handled = true;
|
55
|
+
}
|
56
|
+
}
|
57
|
+
// If we didn't do anything with the event, send it to the editor.
|
58
|
+
if (!handled) {
|
59
|
+
this.editor.toolController.dispatchInputEvent({
|
60
|
+
kind: InputEvtType.KeyPressEvent,
|
61
|
+
key: evt.key,
|
62
|
+
ctrlKey: evt.ctrlKey,
|
63
|
+
});
|
64
|
+
}
|
65
|
+
};
|
66
|
+
button.onkeyup = evt => {
|
67
|
+
if (evt.key in clickTriggers) {
|
68
|
+
return;
|
69
|
+
}
|
70
|
+
this.editor.toolController.dispatchInputEvent({
|
71
|
+
kind: InputEvtType.KeyUpEvent,
|
72
|
+
key: evt.key,
|
73
|
+
ctrlKey: evt.ctrlKey,
|
74
|
+
});
|
75
|
+
};
|
47
76
|
button.onclick = () => {
|
48
77
|
if (!this.disabled) {
|
49
78
|
this.handleClick();
|
@@ -10,10 +10,12 @@ const makeZoomControl = (localizationTable, editor) => {
|
|
10
10
|
const zoomLevelRow = document.createElement('div');
|
11
11
|
const increaseButton = document.createElement('button');
|
12
12
|
const decreaseButton = document.createElement('button');
|
13
|
+
const resetViewButton = document.createElement('button');
|
13
14
|
const zoomLevelDisplay = document.createElement('span');
|
14
15
|
increaseButton.innerText = '+';
|
15
16
|
decreaseButton.innerText = '-';
|
16
|
-
|
17
|
+
resetViewButton.innerText = localizationTable.resetView;
|
18
|
+
zoomLevelRow.replaceChildren(zoomLevelDisplay, increaseButton, decreaseButton, resetViewButton);
|
17
19
|
zoomLevelRow.classList.add(`${toolbarCSSPrefix}zoomLevelEditor`);
|
18
20
|
zoomLevelDisplay.classList.add('zoomDisplay');
|
19
21
|
let lastZoom;
|
@@ -34,6 +36,8 @@ const makeZoomControl = (localizationTable, editor) => {
|
|
34
36
|
editor.notifier.on(EditorEventType.ViewportChanged, (event) => {
|
35
37
|
if (event.kind === EditorEventType.ViewportChanged) {
|
36
38
|
updateZoomDisplay();
|
39
|
+
// Can't reset if already reset.
|
40
|
+
resetViewButton.disabled = event.newTransform.eq(Mat33.identity);
|
37
41
|
}
|
38
42
|
});
|
39
43
|
const zoomBy = (factor) => {
|
@@ -47,6 +51,9 @@ const makeZoomControl = (localizationTable, editor) => {
|
|
47
51
|
decreaseButton.onclick = () => {
|
48
52
|
zoomBy(4.0 / 5);
|
49
53
|
};
|
54
|
+
resetViewButton.onclick = () => {
|
55
|
+
editor.dispatch(new Viewport.ViewportTransform(editor.viewport.canvasToScreenTransform.inverse()), true);
|
56
|
+
};
|
50
57
|
return zoomLevelRow;
|
51
58
|
};
|
52
59
|
class ZoomWidget extends BaseWidget {
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { PointerEvtListener, WheelEvt, PointerEvt, EditorNotifier, KeyPressEvent } from '../types';
|
1
|
+
import { PointerEvtListener, WheelEvt, PointerEvt, EditorNotifier, KeyPressEvent, KeyUpEvent } from '../types';
|
2
2
|
import { ToolType } from './ToolController';
|
3
3
|
import ToolEnabledGroup from './ToolEnabledGroup';
|
4
4
|
export default abstract class BaseTool implements PointerEvtListener {
|
@@ -14,6 +14,7 @@ export default abstract class BaseTool implements PointerEvtListener {
|
|
14
14
|
protected constructor(notifier: EditorNotifier, description: string);
|
15
15
|
onWheel(_event: WheelEvt): boolean;
|
16
16
|
onKeyPress(_event: KeyPressEvent): boolean;
|
17
|
+
onKeyUp(_event: KeyUpEvent): boolean;
|
17
18
|
setEnabled(enabled: boolean): void;
|
18
19
|
isEnabled(): boolean;
|
19
20
|
setToolGroup(group: ToolEnabledGroup): void;
|