All files / store positionStoreMixin.js

93.33% Statements 56/60
86.27% Branches 44/51
77.78% Functions 7/9
93.33% Lines 56/60
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 159 160 161 162 163 164 165 166 167                                                                                                            22x 46x     46x     46x 46x 46x 16x 16x   46x 26x 26x   46x 20x 20x   46x   2x               22x 22x 34x 6x   34x 34x 34x     22x 22x 8x 1x   8x       22x       22x       22x               22x 18x 46x 20x 1x     45x 26x 1x     44x   18x 18x 34x 12x 7x 7x 7x     7x   12x 5x 5x 5x     5x     34x         4x 4x            
/**
* 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';
import assert from '../assert';
 
/**
 * 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() {
    Iif (!this.context.positionMSAStore) {
      console.log(this.context);
    }
    assert(this.context && this.context.positionMSAStore,
      "MSA PositionStore needs to be injected"
    );
    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 oldComponentWillMount = Component.prototype.componentWillMount;
  Component.prototype.componentWillMount = function() {
    if (oldComponentWillMount) {
      oldComponentWillMount.call(this);
    }
    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;
            Iif (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;
            Iif (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;