<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// @flow
import * as React from 'react';
import classNames from 'classnames';
import { connect } from 'react-redux';
import { getHasZoomedViaMousewheel } from '../../../reducers/app';
import { setHasZoomedViaMousewheel } from '../../../actions/stack-chart';

import type {
  CssPixels,
  UnitIntervalOfProfileRange,
  StartEndRange,
} from '../../../types/units';
import typeof { updateProfileSelection as UpdateProfileSelection } from '../../../actions/profile-view';
import type { ProfileSelection } from '../../../types/actions';

const { DOM_DELTA_PAGE, DOM_DELTA_LINE } =
  typeof window === 'object' && window.WheelEvent
    ? new WheelEvent('mouse')
    : { DOM_DELTA_LINE: 1, DOM_DELTA_PAGE: 2 };

// These are the props consumed by this Higher-Order Component (HOC)
type ViewportProps = {
  viewportNeedsUpdate: (ViewportProps, ViewportProps) => boolean,
  timeRange: StartEndRange,
  maxViewportHeight: number,
  maximumZoom: UnitIntervalOfProfileRange,
  updateProfileSelection: UpdateProfileSelection,
  selection: ProfileSelection,
  setHasZoomedViaMousewheel: () => void,
  hasZoomedViaMousewheel: boolean,
};

// These are the props injected by the HOC to WrappedComponent
type InjectedProps = {
  containerWidth: CssPixels,
  containerHeight: CssPixels,
  viewportLeft: UnitIntervalOfProfileRange,
  viewportRight: UnitIntervalOfProfileRange,
  viewportTop: CssPixels,
  viewportBottom: CssPixels,
  isDragging: boolean,
};

type State = {
  containerWidth: CssPixels,
  containerHeight: CssPixels,
  containerLeft: CssPixels,
  viewportTop: CssPixels,
  viewportBottom: CssPixels,
  viewportLeft: UnitIntervalOfProfileRange,
  viewportRight: UnitIntervalOfProfileRange,
  dragX: CssPixels,
  dragY: CssPixels,
  isDragging: boolean,
  isShiftScrollHintVisible: boolean,
};

require('./Viewport.css');

/**
 * Viewport terminology:
 *                                                  (this time is relative to current
 *                                                   profile range, not the total profile)
 *                 <------ e.g. 1000px ------>         0.7 - Sample's unit time
 *                 ___________________________          |
 *         _______|___________________________|_________|______________________
 *        |       |                           |         v                      |
 * |<-------------|---------------------------|---------*------- Total profile samples ------>|
 *        |       |                           |                                |
 *        |       |      Screen Viewport      |                                |
 *        |       |                           |         Current profile range  |
 *        |_______|___________________________|________________________________|
 *                |___________________________|
 *        ^       ^                           ^                                ^
 *        0.0    0.1                          0.6                              1.0
 *                 ^ viewportLeft               ^ viewportRight
 *
 * viewportLeft = 0.1
 * viewportRight = 0.6
 * viewportLength = viewportRight - viewportLeft
 * viewportTop = 30 (in pixels)
 * screenWidth = 1000
 * unitPixel = viewportLength / screenWidth
 * viewportRight += mouseMoveDelta * unitPixel
 * viewportLeft += mouseMoveDelta * unitPixel
 **/
/**
 * About the Flow typing:
 *
 * - `Props` are the props for the returned component. It means that they are the
 *   props that the user for this component will need to specify. From the
 *   generic definition `<Props: ViewportProps>`, they must include the
 *   properties consumed par this HOC.
 *
 * - The argument, which is the augmented component (the `WrappedComponent`),
 *   needs to accept both the `InjectedProps` and some supertype of `Props`.
 *   A supertype of `Props` is an object will some properties of
 *   `Props` but not all of them.
 *   To understand what this means to Flow, we need, like sometimes, to think
 *   backwards: from the props of `WrappedComponent`, take out `InjectedProps`,
 *   and make it part of `Props`.
 *
 * So `Props` will need to hold both `ViewportProps` as said earlier and the
 * props from `WrappedComponent` that aren't `Injectedprops`.
 * This is exactly what we want Flow to check: that the user of this HOC
 * properly passes all these props!
 */
export default function withChartViewport<Props: ViewportProps>(
  WrappedComponent: React.ComponentType<InjectedProps & $Supertype<Props>>
): React.ComponentType<Props> {
  class ChartViewport extends React.PureComponent<Props, State> {
    shiftScrollId: number;
    zoomRangeSelectionScheduled: boolean;
    zoomRangeSelectionScrollDelta: number;
    _container: ?HTMLElement;

    constructor(props: Props) {
      super(props);
      (this: any)._mouseWheelListener = this._mouseWheelListener.bind(this);
      (this: any)._mouseDownListener = this._mouseDownListener.bind(this);
      (this: any)._mouseMoveListener = this._mouseMoveListener.bind(this);
      (this: any)._mouseUpListener = this._mouseUpListener.bind(this);

      (this: any)._setSize = this._setSize.bind(this);
      (this: any)._setSizeNextFrame = this._setSizeNextFrame.bind(this);

      this.shiftScrollId = 0;
      this.zoomRangeSelectionScheduled = false;
      this.zoomRangeSelectionScrollDelta = 0;

      this.state = this.getDefaultState(props);
    }

    getHorizontalViewport({ selection, timeRange }: ViewportProps) {
      if (selection.hasSelection) {
        const { selectionStart, selectionEnd } = selection;
        const timeRangeLength = timeRange.end - timeRange.start;
        return {
          viewportLeft: (selectionStart - timeRange.start) / timeRangeLength,
          viewportRight: (selectionEnd - timeRange.start) / timeRangeLength,
        };
      }
      return {
        viewportLeft: 0,
        viewportRight: 1,
      };
    }

    getDefaultState(props: ViewportProps) {
      const { viewportLeft, viewportRight } = this.getHorizontalViewport(props);
      return {
        containerWidth: 0,
        containerHeight: 0,
        containerLeft: 0,
        viewportTop: 0,
        viewportBottom: 0,
        viewportLeft,
        viewportRight,
        dragX: 0,
        dragY: 0,
        isDragging: false,
        isShiftScrollHintVisible: false,
      };
    }

    /**
     * Let the viewport know when we are actively scrolling.
     */
    showShiftScrollingHint() {
      // Only show this message if we haven't shift zoomed yet.
      if (this.props.hasZoomedViaMousewheel) {
        return;
      }

      const scollId = ++this.shiftScrollId;
      if (!this.state.isShiftScrollHintVisible) {
        this.setState({ isShiftScrollHintVisible: true });
      }
      setTimeout(() => {
        if (scollId === this.shiftScrollId) {
          this.setState({ isShiftScrollHintVisible: false });
        }
      }, 1000);
    }

    componentDidUpdate(prevProps: Props) {
      if (this.props.viewportNeedsUpdate(prevProps, this.props)) {
        this.setState(this.getDefaultState(this.props));
        this._setSizeNextFrame();
      }
    }

    componentWillReceiveProps(newProps: Props) {
      if (
        this.props.selection !== newProps.selection ||
        this.props.timeRange !== newProps.timeRange
      ) {
        this.setState(this.getHorizontalViewport(newProps));
      }
    }

    _setSize() {
      if (this._container) {
        const rect = this._container.getBoundingClientRect();
        if (
          this.state.containerWidth !== rect.width ||
          this.state.containerHeight !== rect.height<commit_msg>Viewport: disable rule react/no-did-update-set-state<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// @flow
import * as React from 'react';
import classNames from 'classnames';
import { connect } from 'react-redux';
import { getHasZoomedViaMousewheel } from '../../../reducers/app';
import { setHasZoomedViaMousewheel } from '../../../actions/stack-chart';

import type {
  CssPixels,
  UnitIntervalOfProfileRange,
  StartEndRange,
} from '../../../types/units';
import typeof { updateProfileSelection as UpdateProfileSelection } from '../../../actions/profile-view';
import type { ProfileSelection } from '../../../types/actions';

const { DOM_DELTA_PAGE, DOM_DELTA_LINE } =
  typeof window === 'object' && window.WheelEvent
    ? new WheelEvent('mouse')
    : { DOM_DELTA_LINE: 1, DOM_DELTA_PAGE: 2 };

// These are the props consumed by this Higher-Order Component (HOC)
type ViewportProps = {
  viewportNeedsUpdate: (ViewportProps, ViewportProps) => boolean,
  timeRange: StartEndRange,
  maxViewportHeight: number,
  maximumZoom: UnitIntervalOfProfileRange,
  updateProfileSelection: UpdateProfileSelection,
  selection: ProfileSelection,
  setHasZoomedViaMousewheel: () => void,
  hasZoomedViaMousewheel: boolean,
};

// These are the props injected by the HOC to WrappedComponent
type InjectedProps = {
  containerWidth: CssPixels,
  containerHeight: CssPixels,
  viewportLeft: UnitIntervalOfProfileRange,
  viewportRight: UnitIntervalOfProfileRange,
  viewportTop: CssPixels,
  viewportBottom: CssPixels,
  isDragging: boolean,
};

type State = {
  containerWidth: CssPixels,
  containerHeight: CssPixels,
  containerLeft: CssPixels,
  viewportTop: CssPixels,
  viewportBottom: CssPixels,
  viewportLeft: UnitIntervalOfProfileRange,
  viewportRight: UnitIntervalOfProfileRange,
  dragX: CssPixels,
  dragY: CssPixels,
  isDragging: boolean,
  isShiftScrollHintVisible: boolean,
};

require('./Viewport.css');

/**
 * Viewport terminology:
 *                                                  (this time is relative to current
 *                                                   profile range, not the total profile)
 *                 <------ e.g. 1000px ------>         0.7 - Sample's unit time
 *                 ___________________________          |
 *         _______|___________________________|_________|______________________
 *        |       |                           |         v                      |
 * |<-------------|---------------------------|---------*------- Total profile samples ------>|
 *        |       |                           |                                |
 *        |       |      Screen Viewport      |                                |
 *        |       |                           |         Current profile range  |
 *        |_______|___________________________|________________________________|
 *                |___________________________|
 *        ^       ^                           ^                                ^
 *        0.0    0.1                          0.6                              1.0
 *                 ^ viewportLeft               ^ viewportRight
 *
 * viewportLeft = 0.1
 * viewportRight = 0.6
 * viewportLength = viewportRight - viewportLeft
 * viewportTop = 30 (in pixels)
 * screenWidth = 1000
 * unitPixel = viewportLength / screenWidth
 * viewportRight += mouseMoveDelta * unitPixel
 * viewportLeft += mouseMoveDelta * unitPixel
 **/
/**
 * About the Flow typing:
 *
 * - `Props` are the props for the returned component. It means that they are the
 *   props that the user for this component will need to specify. From the
 *   generic definition `<Props: ViewportProps>`, they must include the
 *   properties consumed par this HOC.
 *
 * - The argument, which is the augmented component (the `WrappedComponent`),
 *   needs to accept both the `InjectedProps` and some supertype of `Props`.
 *   A supertype of `Props` is an object will some properties of
 *   `Props` but not all of them.
 *   To understand what this means to Flow, we need, like sometimes, to think
 *   backwards: from the props of `WrappedComponent`, take out `InjectedProps`,
 *   and make it part of `Props`.
 *
 * So `Props` will need to hold both `ViewportProps` as said earlier and the
 * props from `WrappedComponent` that aren't `Injectedprops`.
 * This is exactly what we want Flow to check: that the user of this HOC
 * properly passes all these props!
 */
export default function withChartViewport<Props: ViewportProps>(
  WrappedComponent: React.ComponentType<InjectedProps & $Supertype<Props>>
): React.ComponentType<Props> {
  class ChartViewport extends React.PureComponent<Props, State> {
    shiftScrollId: number;
    zoomRangeSelectionScheduled: boolean;
    zoomRangeSelectionScrollDelta: number;
    _container: ?HTMLElement;

    constructor(props: Props) {
      super(props);
      (this: any)._mouseWheelListener = this._mouseWheelListener.bind(this);
      (this: any)._mouseDownListener = this._mouseDownListener.bind(this);
      (this: any)._mouseMoveListener = this._mouseMoveListener.bind(this);
      (this: any)._mouseUpListener = this._mouseUpListener.bind(this);

      (this: any)._setSize = this._setSize.bind(this);
      (this: any)._setSizeNextFrame = this._setSizeNextFrame.bind(this);

      this.shiftScrollId = 0;
      this.zoomRangeSelectionScheduled = false;
      this.zoomRangeSelectionScrollDelta = 0;

      this.state = this.getDefaultState(props);
    }

    getHorizontalViewport({ selection, timeRange }: ViewportProps) {
      if (selection.hasSelection) {
        const { selectionStart, selectionEnd } = selection;
        const timeRangeLength = timeRange.end - timeRange.start;
        return {
          viewportLeft: (selectionStart - timeRange.start) / timeRangeLength,
          viewportRight: (selectionEnd - timeRange.start) / timeRangeLength,
        };
      }
      return {
        viewportLeft: 0,
        viewportRight: 1,
      };
    }

    getDefaultState(props: ViewportProps) {
      const { viewportLeft, viewportRight } = this.getHorizontalViewport(props);
      return {
        containerWidth: 0,
        containerHeight: 0,
        containerLeft: 0,
        viewportTop: 0,
        viewportBottom: 0,
        viewportLeft,
        viewportRight,
        dragX: 0,
        dragY: 0,
        isDragging: false,
        isShiftScrollHintVisible: false,
      };
    }

    /**
     * Let the viewport know when we are actively scrolling.
     */
    showShiftScrollingHint() {
      // Only show this message if we haven't shift zoomed yet.
      if (this.props.hasZoomedViaMousewheel) {
        return;
      }

      const scollId = ++this.shiftScrollId;
      if (!this.state.isShiftScrollHintVisible) {
        this.setState({ isShiftScrollHintVisible: true });
      }
      setTimeout(() => {
        if (scollId === this.shiftScrollId) {
          this.setState({ isShiftScrollHintVisible: false });
        }
      }, 1000);
    }

    componentDidUpdate(prevProps: Props) {
      // TODO Consider merging this in componentWillReceiveProps
      if (this.props.viewportNeedsUpdate(prevProps, this.props)) {
        // eslint-disable-next-line react/no-did-update-set-state
        this.setState(this.getDefaultState(this.props));
        this._setSizeNextFrame();
      }
    }

    componentWillReceiveProps(newProps: Props) {
      if (
        this.props.selection !== newProps.selection ||
        this.props.timeRange !== newProps.timeRange
      ) {
        this.setState(this.getHorizontalViewport(newProps));
      }
    }

    _setSize() {
      if (this._container) {
        const rect = this._container.getBoundingClientRect();
        if (
          this.state.containerWidth !== rect.width ||
          this.state.containerHeight !== rect.height
{'fraction_non_alphanumeric': 0.1071790969051243, 'fraction_numerical': 0.005707762557077625, 'mean_word_length': 2.399223970683337, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 6, 'word_list_counts': {'cursed_substrings.json': 36, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5615131', 'n_tokens_mistral': 4390, 'n_tokens_neox': 4265, 'n_words': 1102}