Coveralls logob
Coveralls logo
  • Home
  • Features
  • Pricing
  • Docs
  • Sign In

uber / deck.gl / 13873

19 Sep 2019 - 20:02 coverage increased (+2.8%) to 82.702%
13873

Pull #3639

travis-ci-com

9181eb84f9c35729a3bad740fb7f9d93?size=18&default=identiconweb-flow
Update
Pull Request #3639: Set default pydeck notebook width to 700px

3398 of 4611 branches covered (73.69%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

488 existing lines in 85 files now uncovered.

7192 of 8194 relevant lines covered (87.77%)

4273.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.88
/modules/core/src/controllers/controller.js
1
// Copyright (c) 2015 Uber Technologies, Inc.
2

3
// Permission is hereby granted, free of charge, to any person obtaining a copy
4
// of this software and associated documentation files (the "Software"), to deal
5
// in the Software without restriction, including without limitation the rights
6
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
// copies of the Software, and to permit persons to whom the Software is
8
// furnished to do so, subject to the following conditions:
9

10
// The above copyright notice and this permission notice shall be included in
11
// all copies or substantial portions of the Software.
12

13
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
// THE SOFTWARE.
20

21
import TransitionManager from './transition-manager';
22
import log from '../utils/log';
23
import assert from '../utils/assert';
24

25
const NO_TRANSITION_PROPS = {
1×
26
  transitionDuration: 0
27
};
28

29
// EVENT HANDLING PARAMETERS
30
const ZOOM_ACCEL = 0.01;
15×
31

32
const EVENT_TYPES = {
1×
33
  WHEEL: ['wheel'],
34
  PAN: ['panstart', 'panmove', 'panend'],
35
  PINCH: ['pinchstart', 'pinchmove', 'pinchend'],
36
  DOUBLE_TAP: ['doubletap'],
37
  KEYBOARD: ['keydown']
38
};
39

40
export default class Controller {
41
  constructor(ControllerState, options = {}) {
Branches [[0, 0]] missed.
42
    assert(ControllerState);
3×
43
    this.ControllerState = ControllerState;
1×
44
    this.controllerState = null;
1×
45
    this.controllerStateProps = null;
1×
46
    this.eventManager = null;
1×
47
    this.transitionManager = new TransitionManager(ControllerState, options);
1×
48
    this._events = null;
1×
49
    this._state = {
1×
50
      isDragging: false
51
    };
52
    this.events = [];
6×
53
    this.onViewStateChange = null;
6×
54
    this.onStateChange = null;
6×
55
    this.invertPan = false;
6×
56

57
    this.handleEvent = this.handleEvent.bind(this);
6×
58

59
    this.setProps(options);
6×
60
  }
61

62
  finalize() {
63
    for (const eventName in this._events) {
6×
64
      if (this._events[eventName]) {
Branches [[1, 1]] missed. 6×
65
        this.eventManager.off(eventName, this.handleEvent);
6×
66
      }
67
    }
68
    this.transitionManager.finalize();
6×
69
  }
70

71
  /**
72
   * Callback for events
73
   * @param {hammer.Event} event
74
   */
75
  handleEvent(event) {
76
    const {ControllerState} = this;
6×
77
    this.controllerState = new ControllerState(
6×
78
      Object.assign({}, this.controllerStateProps, this._state)
79
    );
80

81
    switch (event.type) {
Branches [[2, 9]] missed. 6×
82
      case 'panstart':
83
        return this._onPanStart(event);
6×
84
      case 'panmove':
85
        return this._onPan(event);
2×
86
      case 'panend':
87
        return this._onPanEnd(event);
9×
88
      case 'pinchstart':
89
        return this._onPinchStart(event);
9×
90
      case 'pinchmove':
91
        return this._onPinch(event);
2×
92
      case 'pinchend':
93
        return this._onPinchEnd(event);
169×
94
      case 'doubletap':
95
        return this._onDoubleTap(event);
169×
96
      case 'wheel':
97
        return this._onWheel(event);
169×
98
      case 'keydown':
99
        return this._onKeyDown(event);
23×
100
      default:
101
        return false;
23×
102
    }
103
  }
104

105
  /* Event utils */
106
  // Event object: http://hammerjs.github.io/api/#event-object
107
  getCenter(event) {
108
    const {x, y} = this.controllerStateProps;
23×
109
    const {offsetCenter} = event;
12×
110
    return [offsetCenter.x - x, offsetCenter.y - y];
12×
111
  }
112

113
  isPointInBounds(pos, event) {
114
    const {width, height} = this.controllerStateProps;
12×
115
    if (event && event.handled) {
12×
116
      return false;
12×
117
    }
118

119
    const inside = pos[0] >= 0 && pos[0] <= width && pos[1] >= 0 && pos[1] <= height;
40×
UNCOV
120
    if (inside && event) {
!
121
      event.stopPropagation();
60×
122
    }
123
    return inside;
60×
124
  }
125

126
  isFunctionKeyPressed(event) {
127
    const {srcEvent} = event;
60×
128
    return Boolean(srcEvent.metaKey || srcEvent.altKey || srcEvent.ctrlKey || srcEvent.shiftKey);
51×
129
  }
130

131
  isDragging() {
132
    return this._state.isDragging;
51×
133
  }
134

135
  /**
136
   * Extract interactivity options
137
   */
138
  /* eslint-disable complexity, max-statements */
139
  setProps(props) {
140
    if ('onViewportChange' in props) {
Branches [[9, 0]] missed. 4×
141
      log.removed('onViewportChange')();
47×
142
    }
143
    if ('onViewStateChange' in props) {
47×
144
      this.onViewStateChange = props.onViewStateChange;
31×
145
    }
146
    if ('onStateChange' in props) {
47×
147
      this.onStateChange = props.onStateChange;
55×
148
    }
149
    this.controllerStateProps = props;
55×
150

151
    if ('eventManager' in props && this.eventManager !== props.eventManager) {
31×
152
      // EventManager has changed
153
      this.eventManager = props.eventManager;
186×
UNCOV
154
      this._events = {};
!
155
      this.toggleEvents(this.events, true);
186×
156
    }
157

158
    this.transitionManager.processViewStateChange(this.controllerStateProps);
68×
159

160
    // TODO - make sure these are not reset on every setProps
161
    const {
162
      scrollZoom = true,
163
      dragPan = true,
164
      dragRotate = true,
165
      doubleClickZoom = true,
166
      touchZoom = true,
167
      touchRotate = false,
168
      keyboard = true
169
    } = props;
186×
170

171
    // Register/unregister events
172
    const isInteractive = Boolean(this.onViewStateChange);
68×
173
    this.toggleEvents(EVENT_TYPES.WHEEL, isInteractive && scrollZoom);
186×
174
    this.toggleEvents(EVENT_TYPES.PAN, isInteractive && (dragPan || dragRotate));
186×
175
    this.toggleEvents(EVENT_TYPES.PINCH, isInteractive && (touchZoom || touchRotate));
2×
176
    this.toggleEvents(EVENT_TYPES.DOUBLE_TAP, isInteractive && doubleClickZoom);
2×
177
    this.toggleEvents(EVENT_TYPES.KEYBOARD, isInteractive && keyboard);
2×
178

179
    // Interaction toggles
180
    this.scrollZoom = scrollZoom;
186×
181
    this.dragPan = dragPan;
186×
182
    this.dragRotate = dragRotate;
186×
183
    this.doubleClickZoom = doubleClickZoom;
186×
184
    this.touchZoom = touchZoom;
186×
185
    this.touchRotate = touchRotate;
186×
186
    this.keyboard = keyboard;
186×
187
  }
188
  /* eslint-enable complexity, max-statements */
189

190
  updateTransition() {
191
    this.transitionManager.updateTransition();
186×
192
  }
193

194
  toggleEvents(eventNames, enabled) {
195
    if (this.eventManager) {
186×
196
      eventNames.forEach(eventName => {
186×
197
        if (this._events[eventName] !== enabled) {
186×
198
          this._events[eventName] = enabled;
186×
199
          if (enabled) {
Branches [[28, 1]] missed. 186×
200
            this.eventManager.on(eventName, this.handleEvent);
186×
201
          } else {
202
            this.eventManager.off(eventName, this.handleEvent);
186×
203
          }
204
        }
205
      });
206
    }
207
  }
208

209
  // DEPRECATED
210

211
  setOptions(props) {
212
    return this.setProps(props);
!
213
  }
214

215
  // Private Methods
216

217
  /* Callback util */
218
  // formats map state and invokes callback function
219
  updateViewport(newControllerState, extraProps = {}, interactionState = {}) {
Branches [[29, 0], [30, 0]] missed.
220
    const viewState = Object.assign({}, newControllerState.getViewportProps(), extraProps);
932×
221

222
    // TODO - to restore diffing, we need to include interactionState
223
    const changed = this.controllerState !== newControllerState;
11×
224
    // const oldViewState = this.controllerState.getViewportProps();
225
    // const changed = Object.keys(viewState).some(key => oldViewState[key] !== viewState[key]);
226

227
    if (changed) {
Branches [[31, 1]] missed. 18×
228
      const oldViewState = this.controllerState ? this.controllerState.getViewportProps() : null;
Branches [[32, 1]] missed. 9×
229
      if (this.onViewStateChange) {
Branches [[33, 1]] missed. 9×
230
        this.onViewStateChange({viewState, interactionState, oldViewState});
9×
231
      }
232
    }
233

UNCOV
234
    Object.assign(this._state, newControllerState.getInteractiveState(), interactionState);
!
235

UNCOV
236
    if (this.onStateChange) {
Branches [[34, 1]] missed. !
237
      this.onStateChange(this._state);
113×
238
    }
239
  }
240

241
  /* Event handlers */
242
  // Default handler for the `panstart` event.
243
  _onPanStart(event) {
244
    const pos = this.getCenter(event);
113×
245
    if (!this.isPointInBounds(pos, event)) {
113×
246
      return false;
113×
247
    }
248
    const newControllerState = this.controllerState.panStart({pos}).rotateStart({pos});
113×
249
    this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {isDragging: true});
113×
250
    return true;
113×
251
  }
252

253
  // Default handler for the `panmove` event.
254
  _onPan(event) {
255
    if (!this.isDragging()) {
113×
256
      return false;
113×
257
    }
258
    let alternateMode = this.isFunctionKeyPressed(event) || event.rightButton;
23×
259
    alternateMode = this.invertPan ? !alternateMode : alternateMode;
23×
260
    return alternateMode ? this._onPanMove(event) : this._onPanRotate(event);
8×
261
  }
262

263
  // Default handler for the `panend` event.
264
  _onPanEnd(event) {
265
    const newControllerState = this.controllerState.panEnd().rotateEnd();
15×
266
    this.updateViewport(newControllerState, null, {
15×
267
      isDragging: false,
268
      isPanning: false,
269
      isRotating: false
270
    });
271
    return true;
15×
272
  }
273

274
  // Default handler for panning to move.
275
  // Called by `_onPan` when panning without function key pressed.
276
  _onPanMove(event) {
277
    if (!this.dragPan) {
23×
278
      return false;
8×
279
    }
280
    const pos = this.getCenter(event);
15×
281
    const newControllerState = this.controllerState.pan({pos});
15×
282
    this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {
15×
283
      isDragging: true,
284
      isPanning: true
285
    });
286
    return true;
23×
287
  }
288

289
  // Default handler for panning to rotate.
290
  // Called by `_onPan` when panning with function key pressed.
291
  _onPanRotate(event) {
292
    if (!this.dragRotate) {
23×
293
      return false;
23×
294
    }
295

296
    const {deltaX, deltaY} = event;
8×
297
    const {width, height} = this.controllerState.getViewportProps();
4×
298

299
    const deltaScaleX = deltaX / width;
4×
300
    const deltaScaleY = deltaY / height;
4×
301

302
    const newControllerState = this.controllerState.rotate({deltaScaleX, deltaScaleY});
4×
303
    this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {
4×
304
      isDragging: true,
305
      isRotating: true
306
    });
307
    return true;
4×
308
  }
309

310
  // Default handler for the `wheel` event.
311
  _onWheel(event) {
312
    if (!this.scrollZoom) {
2×
313
      return false;
2×
314
    }
315
    event.preventDefault();
2×
316

317
    const pos = this.getCenter(event);
2×
318
    if (!this.isPointInBounds(pos, event)) {
2×
319
      return false;
2×
320
    }
321

322
    const {delta} = event;
2×
323

324
    // Map wheel delta to relative scale
325
    let scale = 2 / (1 + Math.exp(-Math.abs(delta * ZOOM_ACCEL)));
2×
326
    if (delta < 0 && scale !== 0) {
Branches [[44, 1]] missed. 12×
327
      scale = 1 / scale;
4×
328
    }
329

330
    const newControllerState = this.controllerState.zoom({pos, scale});
8×
331
    this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {
8×
332
      isZooming: true,
333
      isPanning: true
334
    });
335
    return true;
8×
336
  }
337

338
  // Default handler for the `pinchstart` event.
339
  _onPinchStart(event) {
340
    const pos = this.getCenter(event);
4×
341
    if (!this.isPointInBounds(pos, event)) {
4×
342
      return false;
4×
343
    }
344

345
    const newControllerState = this.controllerState.zoomStart({pos}).rotateStart({pos});
4×
346
    // hack - hammer's `rotation` field doesn't seem to produce the correct angle
347
    this._state.startPinchRotation = event.rotation;
4×
348
    this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {isDragging: true});
4×
349
    return true;
4×
350
  }
351

352
  // Default handler for the `pinch` event.
353
  _onPinch(event) {
354
    if (!this.touchZoom && !this.touchRotate) {
4×
355
      return false;
12×
356
    }
357
    if (!this.isDragging()) {
12×
358
      return false;
4×
359
    }
360

361
    let newControllerState = this.controllerState;
8×
362
    if (this.touchZoom) {
Branches [[50, 1]] missed. 8×
363
      const {scale} = event;
8×
364
      const pos = this.getCenter(event);
8×
365
      newControllerState = newControllerState.zoom({pos, scale});
12×
366
    }
367
    if (this.touchRotate) {
Branches [[51, 1]] missed. 4×
368
      const {rotation} = event;
8×
369
      const {startPinchRotation} = this._state;
4×
370
      newControllerState = newControllerState.rotate({
4×
371
        deltaScaleX: -(rotation - startPinchRotation) / 180
372
      });
373
    }
374

375
    this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {
4×
376
      isDragging: true,
377
      isPanning: this.touchZoom,
378
      isZooming: this.touchZoom,
379
      isRotating: this.touchRotate
380
    });
381
    return true;
4×
382
  }
383

384
  // Default handler for the `pinchend` event.
385
  _onPinchEnd(event) {
386
    const newControllerState = this.controllerState.zoomEnd().rotateEnd();
4×
387
    this._state.startPinchRotation = 0;
4×
388
    this.updateViewport(newControllerState, null, {
4×
389
      isDragging: false,
390
      isPanning: false,
391
      isZooming: false,
392
      isRotating: false
393
    });
394
    return true;
4×
395
  }
396

397
  // Default handler for the `doubletap` event.
398
  _onDoubleTap(event) {
399
    if (!this.doubleClickZoom) {
4×
400
      return false;
4×
401
    }
402
    const pos = this.getCenter(event);
4×
403
    if (!this.isPointInBounds(pos, event)) {
4×
404
      return false;
12×
405
    }
406

407
    const isZoomOut = this.isFunctionKeyPressed(event);
12×
408

409
    const newControllerState = this.controllerState.zoom({pos, scale: isZoomOut ? 0.5 : 2});
Branches [[54, 0]] missed. 12×
410
    this.updateViewport(newControllerState, this._getTransitionProps(), {
12×
411
      isZooming: true,
412
      isPanning: true
413
    });
414
    return true;
12×
415
  }
416

417
  /* eslint-disable complexity, max-statements */
418
  // Default handler for the `keydown` event
419
  _onKeyDown(event) {
420
    if (!this.keyboard) {
4×
421
      return false;
8×
422
    }
423
    const funcKey = this.isFunctionKeyPressed(event);
8×
424
    const {controllerState} = this;
4×
425
    let newControllerState;
426
    const interactionState = {};
4×
427

428
    switch (event.srcEvent.keyCode) {
Branches [[56, 6]] missed. 4×
429
      case 189: // -
430
        newControllerState = funcKey
4×
431
          ? controllerState.zoomOut().zoomOut()
432
          : controllerState.zoomOut();
433
        interactionState.isZooming = true;
4×
434
        break;
40×
435
      case 187: // +
436
        newControllerState = funcKey ? controllerState.zoomIn().zoomIn() : controllerState.zoomIn();
4×
437
        interactionState.isZooming = true;
36×
438
        break;
36×
439
      case 37: // left
440
        if (funcKey) {
36×
441
          newControllerState = controllerState.rotateLeft();
36×
442
          interactionState.isRotating = true;
6×
443
        } else {
444
          newControllerState = controllerState.moveLeft();
6×
445
          interactionState.isPanning = true;
6×
446
        }
447
        break;
6×
448
      case 39: // right
449
        if (funcKey) {
6×
450
          newControllerState = controllerState.rotateRight();
6×
451
          interactionState.isRotating = true;
6×
452
        } else {
453
          newControllerState = controllerState.moveRight();
3×
454
          interactionState.isPanning = true;
3×
455
        }
456
        break;
3×
457
      case 38: // up
458
        if (funcKey) {
3×
459
          newControllerState = controllerState.rotateUp();
6×
460
          interactionState.isRotating = true;
6×
461
        } else {
462
          newControllerState = controllerState.moveUp();
3×
463
          interactionState.isPanning = true;
3×
464
        }
465
        break;
3×
466
      case 40: // down
467
        if (funcKey) {
3×
468
          newControllerState = controllerState.rotateDown();
6×
469
          interactionState.isRotating = true;
6×
470
        } else {
471
          newControllerState = controllerState.moveDown();
3×
472
          interactionState.isPanning = true;
3×
473
        }
474
        break;
3×
475
      default:
476
        return false;
3×
477
    }
478
    this.updateViewport(newControllerState, this._getTransitionProps(), interactionState);
6×
479
    return true;
6×
480
  }
481
  /* eslint-enable complexity */
482

483
  _getTransitionProps() {
484
    // Transitions on double-tap and key-down are only supported by MapController
485
    return NO_TRANSITION_PROPS;
3×
486
  }
487
}
Troubleshooting · Open an Issue · Sales · Support · ENTERPRISE · CAREERS · STATUS
BLOG · TWITTER · Legal & Privacy · Supported CI Services · What's a CI service? · Automated Testing

© 2019 Coveralls, LLC