All files positionStoreMixin.js

25% Statements 14/56
15.56% Branches 7/45
22.22% Functions 2/9
25% Lines 14/56
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158                                                                                                          5x                                                 5x 5x             5x 5x               5x       5x       5x               5x 4x                         4x 4x                                               1x 1x            
/**
* Copyright 2018, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
 
import PropTypes from 'prop-types';
 
/**
 * Mixes in position store functionality in the requiring components.
 * Can't use HoC components as React Tree calculations need to be prevented at the utmost cost.
 *
 * @param {Object} Component - class to inject the position store into
 * @param {Object} Configuration - which parts of the position store to inject
 *
 * Select from:
 * - `withY` (`yPosOffset`, `currentViewSequence`)
 * - `withX` (`xPosOffset`, `currentViewSequencePosition`)
 * - `withPosition` (`xPos`, yPos`)
 *
 * Multiple selections are allowed.
 *
 * It will inject the following functionality:
 *
 * (1) `updateFromPositionStore`
 *   - a method which updates this.position from the PositionStore)
 *   - `setState({position: positionState})` is called when `shouldRerender` returns true
 * (2) `shouldRerender`
 *  - only if not defined
 *  - called to determine if the current viewpoint still has enough nodes)
 *  - checks the respective viewports when `withX` or `withY` have been set
 *  - calls `updateScrollPosition`
 *
 * (3) `updateScrollPosition`
 *  - only if not defined
 *  - the default implementation sets `this.el.current.scroll{Left,Top}`
 *
 * (4) `dispatch` (to access the PositionStore for event dispatching)
 *
 * Additionally, it will:
 * (5) enhance `componentWillMount` to inject a subscription to the positionStore
 * (6) enhance `componentWillUnmount` to unsubscribe from the positionStore
 *
 * However, if a `componentWillMount` or `componentWillUnmount` method did exist,
 * this will be called first.
 */
export function positionStoreMixin(Component, {
  withX = false,
  withY = false,
  withPosition = false,
}) {
  Component.prototype.updateFromPositionStore = function() {
    const state = this.context.positionMSAStore.getState();
    this.position = this.position || {};
    if (withPosition) {
      this.position.xPos = state.position.xPos;
      this.position.yPos = state.position.yPos;
    }
    if (withX) {
      this.position.xPosOffset = state.xPosOffset;
      this.position.currentViewSequencePosition = state.currentViewSequencePosition;
    }
    if (withY) {
      this.position.yPosOffset = state.yPosOffset;
      this.position.currentViewSequence = state.currentViewSequence;
    }
    if (this.shouldRerender()) {
      // this will always force a rerender as position is a new object
      this.setState({
        position: {
          ...this.position,
        }
      });
    }
  }
 
  const oldConstructor = Component.prototype.constructor;
  Component.prototype.constructor = function(props) {
    oldConstructor.call(this, props);
    this.updateFromPositionStore = this.updateFromPositionStore.bind(this);
    this.updateFromPositionStore();
    this.context.positionMSAStore.subscribe(this.updateFromPositionStore);
  }
 
  const oldComponentDidUpdate = Component.prototype.componentDidUpdate;
  Component.prototype.componentDidUpdate = function() {
    if (oldComponentDidUpdate) {
      oldComponentDidUpdate.call(this);
    }
    this.updateScrollPosition();
  }
 
  // inject the store via contexts
  Component.contextTypes = {
    positionMSAStore: PropTypes.object,
  }
 
  Component.prototype.dispatch = function(payload) {
    this.context.positionMSAStore.dispatch(payload);
  };
 
  defaultRerender(Component, {withX, withY});
}
 
/**
 * Injects a default shouldRerender and updateScrollPosition implementations.
 * updateScrollPosition is called when the shouldRerender yields false
 */
function defaultRerender(Component, {withX = false, withY = false}) {
  if (Component.prototype.shouldRerender === undefined) {
    Component.prototype.shouldRerender = function() {
        if (withY) {
          if (Math.abs(this.position.currentViewSequence - this.position.lastCurrentViewSequence) >= this.props.cacheElements) {
            return true;
          }
        }
        if (withX) {
          if (Math.abs(this.position.currentViewSequencePosition - this.position.lastCurrentViewSequencePosition) >= this.props.cacheElements) {
            return true;
          }
        }
        return this.updateScrollPosition() || false;
    }
    Eif (Component.prototype.updateScrollPosition === undefined) {
      Component.prototype.updateScrollPosition = function(){
        if (this.el && this.el.current) {
          if (withX) {
            let offsetX = -this.position.xPosOffset;
            offsetX += (this.position.lastCurrentViewSequencePosition - this.position.lastStartXTile) * this.props.tileWidth;
            if (this.position.currentViewSequencePosition !== this.position.lastCurrentViewSequencePosition) {
              offsetX += (this.position.currentViewSequencePosition - this.position.lastCurrentViewSequencePosition) * this.props.tileWidth;
            }
            this.el.current.scrollLeft = offsetX;
          }
          if (withY) {
            let offsetY = -this.position.yPosOffset;
            offsetY += (this.position.lastCurrentViewSequence - this.position.lastStartYTile) * this.props.tileHeight;
            if (this.position.currentViewSequence !== this.position.lastCurrentViewSequence) {
              offsetY += (this.position.currentViewSequence - this.position.lastCurrentViewSequence) * this.props.tileHeight;
            }
            this.el.current.scrollTop = offsetY;
          }
        }
        return false;
      }
    }
  } else {
    // provide dummy updateScrollPosition
    Eif (Component.prototype.updateScrollPosition === undefined) {
      Component.prototype.updateScrollPosition = function(){
      };
    }
  }
}
export default positionStoreMixin;