js-draw 0.1.7 → 0.1.10

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 (68) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +15 -3
  3. package/dist/bundle.js +1 -1
  4. package/dist/src/Editor.d.ts +1 -0
  5. package/dist/src/Editor.js +32 -15
  6. package/dist/src/UndoRedoHistory.js +3 -0
  7. package/dist/src/bundle/bundled.d.ts +2 -1
  8. package/dist/src/bundle/bundled.js +2 -1
  9. package/dist/src/components/builders/RectangleBuilder.d.ts +3 -1
  10. package/dist/src/components/builders/RectangleBuilder.js +17 -8
  11. package/dist/src/geometry/LineSegment2.d.ts +1 -0
  12. package/dist/src/geometry/LineSegment2.js +16 -0
  13. package/dist/src/geometry/Rect2.d.ts +1 -0
  14. package/dist/src/geometry/Rect2.js +16 -0
  15. package/dist/src/localizations/en.d.ts +3 -0
  16. package/dist/src/localizations/en.js +4 -0
  17. package/dist/src/localizations/es.d.ts +3 -0
  18. package/dist/src/localizations/es.js +18 -0
  19. package/dist/src/localizations/getLocalizationTable.d.ts +3 -0
  20. package/dist/src/localizations/getLocalizationTable.js +43 -0
  21. package/dist/src/rendering/caching/RenderingCacheNode.js +5 -1
  22. package/dist/src/toolbar/HTMLToolbar.d.ts +1 -0
  23. package/dist/src/toolbar/HTMLToolbar.js +10 -8
  24. package/dist/src/toolbar/icons.js +13 -13
  25. package/dist/src/toolbar/localization.d.ts +1 -0
  26. package/dist/src/toolbar/localization.js +1 -0
  27. package/dist/src/toolbar/makeColorInput.js +22 -8
  28. package/dist/src/toolbar/widgets/BaseWidget.js +29 -0
  29. package/dist/src/tools/BaseTool.d.ts +2 -1
  30. package/dist/src/tools/BaseTool.js +3 -0
  31. package/dist/src/tools/PanZoom.d.ts +2 -1
  32. package/dist/src/tools/PanZoom.js +4 -0
  33. package/dist/src/tools/SelectionTool.d.ts +9 -2
  34. package/dist/src/tools/SelectionTool.js +131 -19
  35. package/dist/src/tools/ToolController.js +6 -2
  36. package/dist/src/tools/localization.d.ts +1 -0
  37. package/dist/src/tools/localization.js +1 -0
  38. package/dist/src/types.d.ts +8 -2
  39. package/dist/src/types.js +1 -0
  40. package/package.json +9 -1
  41. package/src/Editor.ts +36 -14
  42. package/src/EditorImage.test.ts +1 -1
  43. package/src/UndoRedoHistory.ts +4 -0
  44. package/src/bundle/bundled.ts +2 -1
  45. package/src/components/builders/RectangleBuilder.ts +23 -8
  46. package/src/geometry/LineSegment2.test.ts +15 -0
  47. package/src/geometry/LineSegment2.ts +20 -0
  48. package/src/geometry/Rect2.test.ts +20 -7
  49. package/src/geometry/Rect2.ts +19 -1
  50. package/src/localizations/en.ts +8 -0
  51. package/src/localizations/es.ts +60 -0
  52. package/src/localizations/getLocalizationTable.test.ts +27 -0
  53. package/src/localizations/getLocalizationTable.ts +53 -0
  54. package/src/rendering/caching/RenderingCacheNode.ts +6 -1
  55. package/src/toolbar/HTMLToolbar.ts +11 -9
  56. package/src/toolbar/icons.ts +13 -13
  57. package/src/toolbar/localization.ts +2 -0
  58. package/src/toolbar/makeColorInput.ts +25 -10
  59. package/src/toolbar/toolbar.css +5 -0
  60. package/src/toolbar/widgets/BaseWidget.ts +34 -0
  61. package/src/tools/BaseTool.ts +5 -1
  62. package/src/tools/PanZoom.ts +6 -0
  63. package/src/tools/SelectionTool.test.ts +24 -1
  64. package/src/tools/SelectionTool.ts +158 -23
  65. package/src/tools/ToolController.ts +6 -2
  66. package/src/tools/localization.ts +2 -0
  67. package/src/types.ts +9 -1
  68. package/dist-test/test-dist-bundle.html +0 -42
@@ -37,6 +37,7 @@ export declare class Editor {
37
37
  announceForAccessibility(message: string): void;
38
38
  addToolbar(defaultLayout?: boolean): HTMLToolbar;
39
39
  private registerListeners;
40
+ handleKeyEventsFrom(elem: HTMLElement): void;
40
41
  dispatch(command: Command, addToHistory?: boolean): void;
41
42
  dispatchNoAnnounce(command: Command, addToHistory?: boolean): void;
42
43
  private asyncApplyOrUnapplyCommands;
@@ -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 { defaultEditorLocalization } from './localization';
26
+ import getLocalizationTable from './localizations/getLocalizationTable';
27
27
  export class Editor {
28
28
  constructor(parent, settings = {}) {
29
29
  var _a, _b;
30
- this.localization = defaultEditorLocalization;
31
30
  this.announceUndoCallback = (command) => {
32
31
  this.announceForAccessibility(this.localization.undoAnnouncement(command.description(this.localization)));
33
32
  };
@@ -35,7 +34,7 @@ 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({}, this.localization), settings.localization);
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,
@@ -176,18 +175,7 @@ export class Editor {
176
175
  this.renderingRegion.addEventListener('pointercancel', evt => {
177
176
  pointerEnd(evt);
178
177
  });
179
- this.renderingRegion.addEventListener('keydown', evt => {
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
- });
178
+ this.handleKeyEventsFrom(this.renderingRegion);
191
179
  this.container.addEventListener('wheel', evt => {
192
180
  let delta = Vec3.of(evt.deltaX, evt.deltaY, evt.deltaZ);
193
181
  // Process wheel events if the ctrl key is down, even if disabled -- we do want to handle
@@ -234,6 +222,31 @@ export class Editor {
234
222
  this.queueRerender();
235
223
  });
236
224
  }
225
+ // Adds event listners for keypresses to [elem] and forwards those events to the
226
+ // editor.
227
+ handleKeyEventsFrom(elem) {
228
+ elem.addEventListener('keydown', evt => {
229
+ if (this.toolController.dispatchInputEvent({
230
+ kind: InputEvtType.KeyPressEvent,
231
+ key: evt.key,
232
+ ctrlKey: evt.ctrlKey,
233
+ })) {
234
+ evt.preventDefault();
235
+ }
236
+ else if (evt.key === 'Escape') {
237
+ this.renderingRegion.blur();
238
+ }
239
+ });
240
+ elem.addEventListener('keyup', evt => {
241
+ if (this.toolController.dispatchInputEvent({
242
+ kind: InputEvtType.KeyUpEvent,
243
+ key: evt.key,
244
+ ctrlKey: evt.ctrlKey,
245
+ })) {
246
+ evt.preventDefault();
247
+ }
248
+ });
249
+ }
237
250
  // Adds to history by default
238
251
  dispatch(command, addToHistory = true) {
239
252
  if (addToHistory) {
@@ -301,6 +314,10 @@ export class Editor {
301
314
  }
302
315
  rerender(showImageBounds = true) {
303
316
  this.display.startRerender();
317
+ // Don't render if the display has zero size.
318
+ if (this.display.width === 0 || this.display.height === 0) {
319
+ return;
320
+ }
304
321
  // Draw a rectangle around the region that will be visible on save
305
322
  const renderer = this.display.getDryInkRenderer();
306
323
  this.image.renderWithCache(renderer, this.display.getCache(), this.viewport);
@@ -20,6 +20,9 @@ class UndoRedoHistory {
20
20
  command.apply(this.editor);
21
21
  }
22
22
  this.undoStack.push(command);
23
+ for (const elem of this.redoStack) {
24
+ elem.onDrop(this.editor);
25
+ }
23
26
  this.redoStack = [];
24
27
  this.fireUpdateEvent();
25
28
  }
@@ -1,4 +1,5 @@
1
1
  import '../styles';
2
2
  import Editor from '../Editor';
3
+ import getLocalizationTable from '../localizations/getLocalizationTable';
3
4
  export default Editor;
4
- export { Editor };
5
+ export { Editor, getLocalizationTable };
@@ -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 };
@@ -1,6 +1,7 @@
1
1
  import Rect2 from '../../geometry/Rect2';
2
2
  import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
3
3
  import { StrokeDataPoint } from '../../types';
4
+ import Viewport from '../../Viewport';
4
5
  import AbstractComponent from '../AbstractComponent';
5
6
  import { ComponentBuilder, ComponentBuilderFactory } from './types';
6
7
  export declare const makeFilledRectangleBuilder: ComponentBuilderFactory;
@@ -8,8 +9,9 @@ export declare const makeOutlinedRectangleBuilder: ComponentBuilderFactory;
8
9
  export default class RectangleBuilder implements ComponentBuilder {
9
10
  private readonly startPoint;
10
11
  private filled;
12
+ private viewport;
11
13
  private endPoint;
12
- constructor(startPoint: StrokeDataPoint, filled: boolean);
14
+ constructor(startPoint: StrokeDataPoint, filled: boolean, viewport: Viewport);
13
15
  getBBox(): Rect2;
14
16
  private buildPreview;
15
17
  build(): AbstractComponent;
@@ -1,16 +1,18 @@
1
+ import Mat33 from '../../geometry/Mat33';
1
2
  import Path from '../../geometry/Path';
2
3
  import Rect2 from '../../geometry/Rect2';
3
4
  import Stroke from '../Stroke';
4
- export const makeFilledRectangleBuilder = (initialPoint, _viewport) => {
5
- return new RectangleBuilder(initialPoint, true);
5
+ export const makeFilledRectangleBuilder = (initialPoint, viewport) => {
6
+ return new RectangleBuilder(initialPoint, true, viewport);
6
7
  };
7
- export const makeOutlinedRectangleBuilder = (initialPoint, _viewport) => {
8
- return new RectangleBuilder(initialPoint, false);
8
+ export const makeOutlinedRectangleBuilder = (initialPoint, viewport) => {
9
+ return new RectangleBuilder(initialPoint, false, viewport);
9
10
  };
10
11
  export default class RectangleBuilder {
11
- constructor(startPoint, filled) {
12
+ constructor(startPoint, filled, viewport) {
12
13
  this.startPoint = startPoint;
13
14
  this.filled = filled;
15
+ this.viewport = viewport;
14
16
  // Initially, the start and end points are the same.
15
17
  this.endPoint = startPoint;
16
18
  }
@@ -19,9 +21,16 @@ export default class RectangleBuilder {
19
21
  return preview.getBBox();
20
22
  }
21
23
  buildPreview() {
22
- const startPoint = this.startPoint.pos;
23
- const endPoint = this.endPoint.pos;
24
- const path = Path.fromRect(Rect2.fromCorners(startPoint, endPoint), this.filled ? null : this.endPoint.width);
24
+ const canvasAngle = this.viewport.getRotationAngle();
25
+ const rotationMat = Mat33.zRotation(-canvasAngle);
26
+ // Adjust startPoint and endPoint such that applying [rotationMat] to them
27
+ // brings them to this.startPoint and this.endPoint.
28
+ const startPoint = rotationMat.inverse().transformVec2(this.startPoint.pos);
29
+ const endPoint = rotationMat.inverse().transformVec2(this.endPoint.pos);
30
+ const rect = Rect2.fromCorners(startPoint, endPoint);
31
+ const path = Path.fromRect(rect, this.filled ? null : this.endPoint.width).transformedBy(
32
+ // Rotate the canvas rectangle so that its rotation matches the screen
33
+ rotationMat);
25
34
  const preview = new Stroke([
26
35
  path.toRenderable({
27
36
  fill: this.endPoint.color
@@ -15,5 +15,6 @@ export default class LineSegment2 {
15
15
  get p2(): Point2;
16
16
  get(t: number): Point2;
17
17
  intersection(other: LineSegment2): IntersectionResult | null;
18
+ closestPointTo(target: Point2): import("./Vec3").default;
18
19
  }
19
20
  export {};
@@ -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,3 @@
1
+ import { EditorLocalization } from '../localization';
2
+ declare const localization: EditorLocalization;
3
+ export default localization;
@@ -0,0 +1,4 @@
1
+ import { defaultEditorLocalization } from '../localization';
2
+ // Default localizations are already in English.
3
+ const localization = Object.assign({}, defaultEditorLocalization);
4
+ export default localization;
@@ -0,0 +1,3 @@
1
+ import { EditorLocalization } from '../localization';
2
+ declare const localization: EditorLocalization;
3
+ export default localization;
@@ -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', 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,3 @@
1
+ import { EditorLocalization } from '../localization';
2
+ declare const getLocalizationTable: (userLocales?: readonly string[]) => EditorLocalization;
3
+ export default getLocalizationTable;
@@ -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;
@@ -44,6 +44,10 @@ export default class RenderingCacheNode {
44
44
  generateChildren() {
45
45
  if (this.instantiatedChildren.length === 0) {
46
46
  const childRects = this.region.divideIntoGrid(cacheDivisionSize, cacheDivisionSize);
47
+ if (this.region.size.x === 0 || this.region.size.y === 0) {
48
+ console.warn('Cache element has zero size! Not generating children.');
49
+ return;
50
+ }
47
51
  for (const rect of childRects) {
48
52
  const child = new RenderingCacheNode(rect, this.cacheState);
49
53
  child.parent = this;
@@ -283,7 +287,7 @@ export default class RenderingCacheNode {
283
287
  }
284
288
  checkRep() {
285
289
  if (this.instantiatedChildren.length !== cacheDivisionSize * cacheDivisionSize && this.instantiatedChildren.length !== 0) {
286
- throw new Error('Repcheck: Wrong number of children');
290
+ throw new Error(`Repcheck: Wrong number of children. Got ${this.instantiatedChildren.length}`);
287
291
  }
288
292
  if (this.renderedIds[1] !== undefined && this.renderedIds[0] >= this.renderedIds[1]) {
289
293
  console.error(this.renderedIds);
@@ -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
- colorisInit();
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: 'Undo',
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: 'Redo',
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
- for (const tool of toolController.getMatchingTools(ToolType.PanZoom)) {
158
- if (!(tool instanceof PanZoom)) {
159
- throw new Error('All SelectionTools must have kind === ToolType.PanZoom');
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 primaryForegroundFill = `
7
- style='fill: var(--primary-foreground-color);'
6
+ const iconColorFill = `
7
+ style='fill: var(--icon-color);'
8
8
  `;
9
- const primaryForegroundStrokeFill = `
10
- style='fill: var(--primary-foreground-color); stroke: var(--primary-foreground-color);'
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(--primary-foreground-color);
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
- ${primaryForegroundFill}
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
- ${primaryForegroundFill}
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(--primary-foreground-color);
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(--primary-foreground-color);
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(--primary-foreground-color);
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(--primary-foreground-color)';
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
- ${primaryForegroundStrokeFill}
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(--primary-foreground-color)';
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,7 @@ export interface ToolbarLocalization {
21
21
  undo: string;
22
22
  redo: string;
23
23
  zoom: string;
24
+ selectionToolKeyboardShortcuts: string;
24
25
  dropdownShown: (toolName: string) => string;
25
26
  dropdownHidden: (toolName: string) => string;
26
27
  zoomLevel: (zoomPercentage: number) => string;
@@ -14,6 +14,7 @@ export const defaultToolbarLocalization = {
14
14
  redo: 'Redo',
15
15
  selectObjectType: 'Object type: ',
16
16
  pickColorFronScreen: 'Pick color from screen',
17
+ selectionToolKeyboardShortcuts: 'Selection tool: Use arrow keys to move selected items, lowercase/uppercase ‘i’ and ‘o’ to resize.',
17
18
  touchPanning: 'Touchscreen panning',
18
19
  anyDevicePanning: 'Any device panning',
19
20
  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
- handleColorInput();
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
- editor.announceForAccessibility(editor.localization.colorChangedAnnouncement(currentColor.toHexString()));
26
- onColorChange(currentColor);
27
- editor.notifier.dispatch(EditorEventType.ColorPickerColorSelected, {
28
- kind: EditorEventType.ColorPickerColorSelected,
29
- color: currentColor,
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 pipetteColorSelect = (color) => {
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();
@@ -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;