js-draw 0.1.10 → 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.
@@ -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;
@@ -26,7 +26,7 @@ import Mat33 from './geometry/Mat33';
26
26
  import getLocalizationTable from './localizations/getLocalizationTable';
27
27
  export class Editor {
28
28
  constructor(parent, settings = {}) {
29
- var _a, _b;
29
+ var _a, _b, _c, _d;
30
30
  this.announceUndoCallback = (command) => {
31
31
  this.announceForAccessibility(this.localization.undoAnnouncement(command.description(this.localization)));
32
32
  };
@@ -40,6 +40,8 @@ export class Editor {
40
40
  wheelEventsEnabled: (_a = settings.wheelEventsEnabled) !== null && _a !== void 0 ? _a : true,
41
41
  renderingMode: (_b = settings.renderingMode) !== null && _b !== void 0 ? _b : RenderingMode.CanvasRenderer,
42
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,
43
45
  };
44
46
  this.container = document.createElement('div');
45
47
  this.renderingRegion = document.createElement('div');
@@ -71,6 +73,20 @@ export class Editor {
71
73
  this.registerListeners();
72
74
  this.queueRerender();
73
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
+ });
74
90
  }
75
91
  // Returns a reference to this' container.
76
92
  // Example usage:
@@ -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() {
@@ -6,7 +6,7 @@ const localization = Object.assign(Object.assign({}, defaultEditorLocalization),
6
6
  loading: (percentage) => `Cargando: ${percentage}%...`, imageEditor: 'Editor de dibujos', undoAnnouncement: (commandDescription) => `${commandDescription} fue deshecho`, redoAnnouncement: (commandDescription) => `${commandDescription} fue rehecho`, undo: 'Deshace', redo: 'Rehace',
7
7
  // Strings for the toolbar
8
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) {
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
10
  return `Menú por ${toolName} es visible`;
11
11
  }, dropdownHidden: function (toolName) {
12
12
  return `Menú por ${toolName} fue ocultado`;
@@ -21,6 +21,7 @@ export interface ToolbarLocalization {
21
21
  undo: string;
22
22
  redo: string;
23
23
  zoom: string;
24
+ resetView: string;
24
25
  selectionToolKeyboardShortcuts: string;
25
26
  dropdownShown: (toolName: string) => string;
26
27
  dropdownHidden: (toolName: string) => 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: ',
@@ -45,7 +45,7 @@ export default class BaseWidget {
45
45
  return true;
46
46
  }
47
47
  setupActionBtnClickListener(button) {
48
- const clickTriggers = { enter: true, ' ': true, };
48
+ const clickTriggers = { Enter: true, ' ': true, };
49
49
  button.onkeydown = (evt) => {
50
50
  let handled = false;
51
51
  if (evt.key in clickTriggers) {
@@ -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
- zoomLevelRow.replaceChildren(zoomLevelDisplay, increaseButton, decreaseButton);
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 {
@@ -121,15 +121,15 @@ export default class PanZoom extends BaseTool {
121
121
  this.transform.apply(this.editor);
122
122
  }
123
123
  onWheel({ delta, screenPos }) {
124
- if (this.transform === null) {
125
- this.transform = new Viewport.ViewportTransform(Mat33.identity);
126
- }
124
+ // Reset the transformation -- wheel events are individual events, so we don't
125
+ // need to unapply/reapply.
126
+ this.transform = new Viewport.ViewportTransform(Mat33.identity);
127
127
  const canvasPos = this.editor.viewport.screenToCanvas(screenPos);
128
128
  const toCanvas = this.editor.viewport.screenToCanvasTransform;
129
129
  // Transform without including translation
130
130
  const translation = toCanvas.transformVec3(Vec3.of(-delta.x, -delta.y, 0));
131
131
  const pinchZoomScaleFactor = 1.04;
132
- const transformUpdate = Mat33.scaling2D(Math.pow(pinchZoomScaleFactor, -delta.z), canvasPos).rightMul(Mat33.translation(translation));
132
+ const transformUpdate = Mat33.scaling2D(Math.max(0.25, Math.min(Math.pow(pinchZoomScaleFactor, -delta.z), 4)), canvasPos).rightMul(Mat33.translation(translation));
133
133
  this.updateTransform(transformUpdate);
134
134
  return true;
135
135
  }
@@ -137,6 +137,8 @@ export default class PanZoom extends BaseTool {
137
137
  if (!(this.mode & PanZoomMode.Keyboard)) {
138
138
  return false;
139
139
  }
140
+ // No need to keep the same the transform for keyboard events.
141
+ this.transform = new Viewport.ViewportTransform(Mat33.identity);
140
142
  let translation = Vec2.zero;
141
143
  let scale = 1;
142
144
  let rotation = 0;
@@ -79,6 +79,7 @@ export interface EditorObjectEvent {
79
79
  export interface EditorViewportChangedEvent {
80
80
  readonly kind: EditorEventType.ViewportChanged;
81
81
  readonly newTransform: Mat33;
82
+ readonly oldTransform: Mat33;
82
83
  }
83
84
  export interface DisplayResizedEvent {
84
85
  readonly kind: EditorEventType.DisplayResized;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
5
5
  "main": "dist/src/Editor.js",
6
6
  "types": "dist/src/Editor.d.ts",
package/src/Editor.ts CHANGED
@@ -31,6 +31,9 @@ export interface EditorSettings {
31
31
  // This does not include pinch-zoom events.
32
32
  // Defaults to true.
33
33
  wheelEventsEnabled: boolean|'only-if-focused';
34
+
35
+ minZoom: number,
36
+ maxZoom: number,
34
37
  }
35
38
 
36
39
  export class Editor {
@@ -69,6 +72,8 @@ export class Editor {
69
72
  wheelEventsEnabled: settings.wheelEventsEnabled ?? true,
70
73
  renderingMode: settings.renderingMode ?? RenderingMode.CanvasRenderer,
71
74
  localization: this.localization,
75
+ minZoom: settings.minZoom ?? 2e-10,
76
+ maxZoom: settings.maxZoom ?? 1e12,
72
77
  };
73
78
 
74
79
  this.container = document.createElement('div');
@@ -111,6 +116,24 @@ export class Editor {
111
116
  this.registerListeners();
112
117
  this.queueRerender();
113
118
  this.hideLoadingWarning();
119
+
120
+
121
+ // Enforce zoom limits.
122
+ this.notifier.on(EditorEventType.ViewportChanged, evt => {
123
+ if (evt.kind === EditorEventType.ViewportChanged) {
124
+ const zoom = evt.newTransform.transformVec3(Vec2.unitX).length();
125
+ if (zoom > this.settings.maxZoom || zoom < this.settings.minZoom) {
126
+ const oldZoom = evt.oldTransform.transformVec3(Vec2.unitX).length();
127
+ let resetTransform = Mat33.identity;
128
+
129
+ if (oldZoom <= this.settings.maxZoom && oldZoom >= this.settings.minZoom) {
130
+ resetTransform = evt.oldTransform;
131
+ }
132
+
133
+ this.viewport.resetTransform(resetTransform);
134
+ }
135
+ }
136
+ });
114
137
  }
115
138
 
116
139
  // Returns a reference to this' container.
package/src/Viewport.ts CHANGED
@@ -103,11 +103,13 @@ export class Viewport {
103
103
  // Updates the transformation directly. Using ViewportTransform is preferred.
104
104
  // [newTransform] should map from canvas coordinates to screen coordinates.
105
105
  public resetTransform(newTransform: Mat33 = Mat33.identity) {
106
+ const oldTransform = this.transform;
106
107
  this.transform = newTransform;
107
108
  this.inverseTransform = newTransform.inverse();
108
109
  this.notifier.dispatch(EditorEventType.ViewportChanged, {
109
110
  kind: EditorEventType.ViewportChanged,
110
111
  newTransform,
112
+ oldTransform,
111
113
  });
112
114
  }
113
115
 
@@ -33,6 +33,8 @@ const localization: EditorLocalization = {
33
33
  freehandPen: 'Dibuja sin restricción de forma',
34
34
  selectObjectType: 'Forma de dibuja:',
35
35
  handTool: 'Mover',
36
+ zoom: 'Zoom',
37
+ resetView: 'Reiniciar vista',
36
38
  resizeImageToSelection: 'Redimensionar la imagen a lo que está seleccionado',
37
39
  deleteSelection: 'Borra la selección',
38
40
  duplicateSelection: 'Duplica la selección',
@@ -23,6 +23,7 @@ export interface ToolbarLocalization {
23
23
  undo: string;
24
24
  redo: string;
25
25
  zoom: string;
26
+ resetView: string;
26
27
  selectionToolKeyboardShortcuts: string;
27
28
 
28
29
  dropdownShown: (toolName: string)=> string;
@@ -37,6 +38,7 @@ export const defaultToolbarLocalization: ToolbarLocalization = {
37
38
  select: 'Select',
38
39
  handTool: 'Pan',
39
40
  zoom: 'Zoom',
41
+ resetView: 'Reset view',
40
42
  thicknessLabel: 'Thickness: ',
41
43
  colorLabel: 'Color: ',
42
44
  fontLabel: 'Font: ',
@@ -155,8 +155,7 @@
155
155
  }
156
156
 
157
157
  .toolbar-root .toolbar-zoomLevelEditor button {
158
- width: min-content;
159
- height: min-content;
158
+ min-width: 48px;
160
159
  }
161
160
 
162
161
  .color-input-container {
@@ -51,7 +51,7 @@ export default abstract class BaseWidget {
51
51
  }
52
52
 
53
53
  protected setupActionBtnClickListener(button: HTMLElement) {
54
- const clickTriggers = { enter: true, ' ': true, };
54
+ const clickTriggers = { Enter: true, ' ': true, };
55
55
  button.onkeydown = (evt) => {
56
56
  let handled = false;
57
57
 
@@ -14,10 +14,12 @@ const makeZoomControl = (localizationTable: ToolbarLocalization, editor: Editor)
14
14
 
15
15
  const increaseButton = document.createElement('button');
16
16
  const decreaseButton = document.createElement('button');
17
+ const resetViewButton = document.createElement('button');
17
18
  const zoomLevelDisplay = document.createElement('span');
18
19
  increaseButton.innerText = '+';
19
20
  decreaseButton.innerText = '-';
20
- zoomLevelRow.replaceChildren(zoomLevelDisplay, increaseButton, decreaseButton);
21
+ resetViewButton.innerText = localizationTable.resetView;
22
+ zoomLevelRow.replaceChildren(zoomLevelDisplay, increaseButton, decreaseButton, resetViewButton);
21
23
 
22
24
  zoomLevelRow.classList.add(`${toolbarCSSPrefix}zoomLevelEditor`);
23
25
  zoomLevelDisplay.classList.add('zoomDisplay');
@@ -42,6 +44,9 @@ const makeZoomControl = (localizationTable: ToolbarLocalization, editor: Editor)
42
44
  editor.notifier.on(EditorEventType.ViewportChanged, (event) => {
43
45
  if (event.kind === EditorEventType.ViewportChanged) {
44
46
  updateZoomDisplay();
47
+
48
+ // Can't reset if already reset.
49
+ resetViewButton.disabled = event.newTransform.eq(Mat33.identity);
45
50
  }
46
51
  });
47
52
 
@@ -59,6 +64,12 @@ const makeZoomControl = (localizationTable: ToolbarLocalization, editor: Editor)
59
64
  zoomBy(4.0/5);
60
65
  };
61
66
 
67
+ resetViewButton.onclick = () => {
68
+ editor.dispatch(new Viewport.ViewportTransform(
69
+ editor.viewport.canvasToScreenTransform.inverse()
70
+ ), true);
71
+ };
72
+
62
73
  return zoomLevelRow;
63
74
  };
64
75
 
@@ -158,9 +158,9 @@ export default class PanZoom extends BaseTool {
158
158
  }
159
159
 
160
160
  public onWheel({ delta, screenPos }: WheelEvt): boolean {
161
- if (this.transform === null) {
162
- this.transform = new Viewport.ViewportTransform(Mat33.identity);
163
- }
161
+ // Reset the transformation -- wheel events are individual events, so we don't
162
+ // need to unapply/reapply.
163
+ this.transform = new Viewport.ViewportTransform(Mat33.identity);
164
164
 
165
165
  const canvasPos = this.editor.viewport.screenToCanvas(screenPos);
166
166
  const toCanvas = this.editor.viewport.screenToCanvasTransform;
@@ -172,7 +172,7 @@ export default class PanZoom extends BaseTool {
172
172
  );
173
173
  const pinchZoomScaleFactor = 1.04;
174
174
  const transformUpdate = Mat33.scaling2D(
175
- Math.pow(pinchZoomScaleFactor, -delta.z), canvasPos
175
+ Math.max(0.25, Math.min(Math.pow(pinchZoomScaleFactor, -delta.z), 4)), canvasPos
176
176
  ).rightMul(
177
177
  Mat33.translation(translation)
178
178
  );
@@ -186,6 +186,9 @@ export default class PanZoom extends BaseTool {
186
186
  return false;
187
187
  }
188
188
 
189
+ // No need to keep the same the transform for keyboard events.
190
+ this.transform = new Viewport.ViewportTransform(Mat33.identity);
191
+
189
192
  let translation = Vec2.zero;
190
193
  let scale = 1;
191
194
  let rotation = 0;
package/src/types.ts CHANGED
@@ -117,6 +117,7 @@ export interface EditorViewportChangedEvent {
117
117
 
118
118
  // Canvas -> screen transform
119
119
  readonly newTransform: Mat33;
120
+ readonly oldTransform: Mat33;
120
121
  }
121
122
 
122
123
  export interface DisplayResizedEvent {