js-draw 1.15.0 → 1.16.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -71,9 +71,11 @@ export default class PanZoom extends BaseTool {
71
71
  this.initialRotationSnapAngle = 0.22; // radians
72
72
  this.afterRotationStartSnapAngle = 0.07; // radians
73
73
  this.pinchZoomStartThreshold = 1.08; // scale factor
74
+ // Last timestamp at which a pointerdown event was received
74
75
  this.lastPointerDownTimestamp = 0;
75
76
  this.initialTouchAngle = 0;
76
77
  this.initialViewportRotation = 0;
78
+ this.initialViewportScale = 0;
77
79
  // Set to `true` only when scaling has started (if two fingers are down and have moved
78
80
  // far enough).
79
81
  this.isScaling = false;
@@ -113,11 +115,12 @@ export default class PanZoom extends BaseTool {
113
115
  const isRightClick = this.allPointersAreOfType(pointers, PointerDevice.RightButtonMouse);
114
116
  if (allAreTouch && pointers.length === 2 && this.mode & PanZoomMode.TwoFingerTouchGestures) {
115
117
  const { screenCenter, angle, dist } = this.computePinchData(pointers[0], pointers[1]);
116
- this.lastDist = dist;
117
- this.startDist = dist;
118
+ this.lastTouchDist = dist;
119
+ this.startTouchDist = dist;
118
120
  this.lastScreenCenter = screenCenter;
119
121
  this.initialTouchAngle = angle;
120
122
  this.initialViewportRotation = this.editor.viewport.getRotationAngle();
123
+ this.initialViewportScale = this.editor.viewport.getScaleFactor();
121
124
  this.isScaling = false;
122
125
  // We're initially rotated if `initialViewportRotation` isn't near a multiple of pi/2.
123
126
  // In other words, if sin(2 initialViewportRotation) is near zero.
@@ -193,6 +196,22 @@ export default class PanZoom extends BaseTool {
193
196
  }
194
197
  return fullRotation - this.editor.viewport.getRotationAngle();
195
198
  }
199
+ /**
200
+ * Given a scale update, `scaleFactor`, returns a new scale factor snapped
201
+ * to a power of two (if within some tolerance of that scale).
202
+ */
203
+ toSnappedScaleFactor(touchDist) {
204
+ // scaleFactor is applied to the current transformation of the viewport.
205
+ const newScale = this.initialViewportScale * touchDist / this.startTouchDist;
206
+ const currentScale = this.editor.viewport.getScaleFactor();
207
+ const logNewScale = Math.log(newScale) / Math.log(10);
208
+ const roundedLogNewScale = Math.round(logNewScale);
209
+ const logTolerance = 0.04;
210
+ if (Math.abs(roundedLogNewScale - logNewScale) < logTolerance) {
211
+ return Math.pow(10, roundedLogNewScale) / currentScale;
212
+ }
213
+ return touchDist / this.lastTouchDist;
214
+ }
196
215
  handleTwoFingerMove(allPointers) {
197
216
  const { screenCenter, canvasCenter, angle, dist } = this.computePinchData(allPointers[0], allPointers[1]);
198
217
  const delta = this.getCenterDelta(screenCenter);
@@ -209,25 +228,25 @@ export default class PanZoom extends BaseTool {
209
228
  this.isRotating = true;
210
229
  }
211
230
  this.updateVelocity(screenCenter);
212
- let scaleFactor = 1;
213
- if (this.isScaling) {
214
- scaleFactor = dist / this.lastDist;
215
- }
216
- else {
217
- const initialScaleFactor = dist / this.startDist;
231
+ if (!this.isScaling) {
232
+ const initialScaleFactor = dist / this.startTouchDist;
218
233
  // Only start scaling if scaling done so far exceeds some threshold.
219
234
  const upperBound = this.pinchZoomStartThreshold;
220
235
  const lowerBound = 1 / this.pinchZoomStartThreshold;
221
236
  if (initialScaleFactor > upperBound || initialScaleFactor < lowerBound) {
222
- scaleFactor = initialScaleFactor;
223
237
  this.isScaling = true;
224
238
  }
225
239
  }
240
+ let scaleFactor = 1;
241
+ if (this.isScaling) {
242
+ scaleFactor = this.toSnappedScaleFactor(dist);
243
+ // Don't set lastDist until we start scaling --
244
+ this.lastTouchDist = dist;
245
+ }
226
246
  const transformUpdate = Mat33.translation(delta)
227
247
  .rightMul(Mat33.scaling2D(scaleFactor, canvasCenter))
228
248
  .rightMul(Mat33.zRotation(deltaRotation, canvasCenter));
229
249
  this.lastScreenCenter = screenCenter;
230
- this.lastDist = dist;
231
250
  this.transform = Viewport.transformBy(this.transform.transform.rightMul(transformUpdate));
232
251
  return transformUpdate;
233
252
  }
@@ -62,6 +62,10 @@ export default class PasteHandler extends BaseTool {
62
62
  });
63
63
  const defaultTextStyle = { size: 12, fontFamily: 'sans', renderingStyle: { fill: Color4.red } };
64
64
  const pastedTextStyle = textTools[0]?.getTextStyle() ?? defaultTextStyle;
65
+ // Don't paste text that would be invisible.
66
+ if (text.trim() === '') {
67
+ return;
68
+ }
65
69
  const lines = text.split('\n');
66
70
  await this.addComponentsFromPaste([TextComponent.fromLines(lines, Mat33.identity, pastedTextStyle)]);
67
71
  }
@@ -80,6 +80,7 @@ class Selection {
80
80
  for (const handle of this.handles) {
81
81
  handle.addTo(this.backgroundElem);
82
82
  }
83
+ this.updateUI();
83
84
  }
84
85
  // @internal Intended for unit tests
85
86
  getBackgroundElem() {
@@ -285,6 +286,13 @@ class Selection {
285
286
  else {
286
287
  this.innerContainer.classList.remove(perpendicularClassName);
287
288
  }
289
+ // Hide handles when empty
290
+ if (screenRegion.width === 0 && screenRegion.height === 0) {
291
+ this.innerContainer.classList.add('-empty');
292
+ }
293
+ else {
294
+ this.innerContainer.classList.remove('-empty');
295
+ }
288
296
  for (const handle of this.handles) {
289
297
  handle.updatePosition();
290
298
  }
@@ -533,7 +541,11 @@ Selection.ApplyTransformationCommand = class extends SerializableCommand {
533
541
  this.transformCommands = this.selectedElemIds.map(id => {
534
542
  const elem = editor.image.lookupElement(id);
535
543
  if (!elem) {
536
- throw new Error(`Unable to find element with ID, ${id}.`);
544
+ // There may be valid reasons for an element lookup to fail:
545
+ // For example, if the element was deleted remotely and the remote deletion
546
+ // hasn't been undone.
547
+ console.warn(`Unable to find element with ID, ${id}.`);
548
+ return null;
537
549
  }
538
550
  let originalZIndex = elem.getZIndex();
539
551
  let targetZIndex = elem.getZIndex() + this.deltaZIndex;
@@ -544,7 +556,9 @@ Selection.ApplyTransformationCommand = class extends SerializableCommand {
544
556
  originalZIndex = elem.getZIndex() - this.deltaZIndex;
545
557
  }
546
558
  return elem.setZIndexAndTransformBy(this.fullTransform, targetZIndex, originalZIndex);
547
- });
559
+ }).filter(// Remove all null commands
560
+ // Remove all null commands
561
+ command => command !== null);
548
562
  }
549
563
  async apply(editor) {
550
564
  this.resolveToElems(editor, false);
@@ -1,3 +1,3 @@
1
1
  export default {
2
- number: '1.15.0',
2
+ number: '1.16.1',
3
3
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "1.15.0",
3
+ "version": "1.16.1",
4
4
  "description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
5
5
  "types": "./dist/mjs/lib.d.ts",
6
6
  "main": "./dist/cjs/lib.js",
@@ -64,7 +64,7 @@
64
64
  "postpack": "ts-node tools/copyREADME.ts revert"
65
65
  },
66
66
  "dependencies": {
67
- "@js-draw/math": "^1.11.1",
67
+ "@js-draw/math": "^1.16.0",
68
68
  "@melloware/coloris": "0.22.0"
69
69
  },
70
70
  "devDependencies": {
@@ -86,5 +86,5 @@
86
86
  "freehand",
87
87
  "svg"
88
88
  ],
89
- "gitHead": "d723c028faa43c7661df54606064f6237a8a767b"
89
+ "gitHead": "7a1d4ea8bae0a2549494fce672b9d43ce97a68a8"
90
90
  }
package/src/Editor.scss CHANGED
@@ -118,6 +118,10 @@
118
118
  max-width: inherit;
119
119
  min-height: 0px;
120
120
  max-height: inherit;
121
+
122
+ user-select: none;
123
+ -webkit-user-select: none;
124
+ -webkit-user-drag: none;
121
125
  }
122
126
 
123
127
  .imageEditorContainer .loadingMessage {
@@ -137,7 +141,10 @@
137
141
  width: 0;
138
142
  height: 0;
139
143
  overflow: hidden;
144
+
140
145
  pointer-events: none;
146
+ user-select: none;
147
+ -webkit-user-select: none;
141
148
  }
142
149
 
143
150
  .imageEditorContainer .textRendererOutputContainer {
@@ -146,6 +153,9 @@
146
153
  width: 0.001px;
147
154
  height: 0.001px;
148
155
  overflow: hidden;
156
+
157
+ -webkit-user-select: none;
158
+ user-select: none;
149
159
  }
150
160
 
151
161
  .imageEditorContainer .textRendererOutputContainer:focus-within {
@@ -335,6 +335,7 @@
335
335
 
336
336
  touch-action: none;
337
337
  user-select: none;
338
+ -webkit-user-select: none;
338
339
 
339
340
  background-color: var(--background-color-2);
340
341
  color: var(--foreground-color-2);
@@ -356,6 +357,7 @@
356
357
 
357
358
  input, textarea {
358
359
  user-select: auto;
360
+ -webkit-user-select: auto;
359
361
  }
360
362
 
361
363
  .toolbar-toolContainer {
@@ -117,6 +117,10 @@
117
117
  // handles.
118
118
  pointer-events: all;
119
119
  }
120
+
121
+ &.-empty {
122
+ opacity: 0;
123
+ }
120
124
  }
121
125
  }
122
126
 
@@ -2,7 +2,10 @@
2
2
  .js-draw-sound-ui-toggle {
3
3
  width: 0px;
4
4
  height: 0px;
5
- overflow: hidden;
5
+ overflow: hidden;
6
+
7
+ user-select: none;
8
+ -webkit-user-select: none;
6
9
  }
7
10
 
8
11
  .js-draw-sound-ui-toggle button {