<commit_before>import React from 'react';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';

import DOMUtils from '../../../../../src/js/utils/DOMUtils';
import Highlight from './Highlight';
import Loader from '../../../../../src/js/components/Loader';
import Util from '../../../../../src/js/utils/Util';
import {PREPEND} from '../../../../../src/js/constants/SystemLogTypes';

const METHODS_TO_BIND = [
  'handleGoToBottom',
  'handleLogContainerScroll',
  'handleWindowResize'
];

class LogView extends React.Component {
  constructor() {
    super(...arguments);

    this.state = {
      isAtBottom: true,
      fullLog: this.props.fullLog
    };

    METHODS_TO_BIND.forEach((method) => {
      this[method] = this[method].bind(this);
    });

    // Make sure to run this on the leading edge to capture this event as soon
    // as possible. This has impact on both checkIfCloseToTop and
    // checkIfAwayFromBottom. Trailing edge will call them too late, i.e. when
    // the data is out of data and possibly scrolled passed the trigger range.
    // We need them to be called as soon as we hit the range wherein these will
    // trigger handlers.
    this.handleLogContainerScroll = Util.throttle(
      this.handleLogContainerScroll,
      50,
      {leading: true, trailing: false}
    );

    this.handleWindowResize = Util.throttle(
      this.handleWindowResize.bind(this),
      50,
      {leading: true, trailing: false}
    );
  }

  componentDidMount() {
    global.addEventListener('resize', this.handleWindowResize);
  }

  componentWillReceiveProps(nextProps) {
    const {logContainer} = this;
    let previousScrollTop;
    let previousScrollHeight;

    if (logContainer) {
      previousScrollTop = logContainer.scrollTop;
      previousScrollHeight = logContainer.scrollHeight;
    }

    // Prevent updates to fullLog, if it has not changed
    if (this.state.fullLog !== nextProps.fullLog) {
      this.setState({fullLog: nextProps.fullLog}, () => {
        // This allows the user to stay at the place of the log they were at
        // before the prepend.
        if (nextProps.direction === PREPEND && previousScrollHeight
          && logContainer && !this.state.isAtBottom) {
          const currentScrollHeight = logContainer.scrollHeight;
          const heightDifference = currentScrollHeight - previousScrollHeight;
          logContainer.scrollTop = previousScrollTop + heightDifference;
        }
      });
    }
  }

  shouldComponentUpdate(nextProps, nextState) {
    const {hasLoadedTop, highlightText, logName, watching} = this.props;
    const {fullLog, isAtBottom} = this.state;

    return Boolean(
      // Check hasLoadedTop
      (hasLoadedTop !== nextProps.hasLoadedTop) ||
      // Check highlightText
      (highlightText !== nextProps.highlightText) ||
      // Check logName
      (logName !== nextProps.logName) ||
      // Check fullLog
      (fullLog !== nextState.fullLog) ||
      // Check watching
      (watching !== nextProps.watching) ||
      // Check isAtBottom
      (isAtBottom !== nextState.isAtBottom)
    );
  }

  componentDidUpdate(prevProps) {
    const {logContainer} = this;
    if (logContainer == null) {
      return;
    }

    // Make sure to go to bottom if we are tailing
    if (this.state.isAtBottom) {
      logContainer.scrollTop = logContainer.scrollHeight;

      return;
    }

    if (prevProps.watching !== this.props.watching ||
      prevProps.highlightText !== this.props.highlightText) {
      this.goToNewHighlightedSearch();
    }
  }

  componentWillUnmount() {
    global.removeEventListener('resize', this.handleWindowResize);
  }

  handleLogContainerScroll(event) {
    const {target} = event;
    if (!target) {
      return;
    }

    this.checkIfCloseToTop(target);
    this.checkIfAwayFromBottom(target);
  }

  handleGoToBottom() {
    const {logContainer, props: {highlightText}} = this;
    // Do not scroll to bottom if we want to highlight a word in the log,
    // or we are already scrolling
    if (logContainer == null || highlightText) {
      return;
    }

    // Cap animation time between 500 and 3000
    const scrollDistance = logContainer.scrollHeight - logContainer.scrollTop;
    const animationTime = Math.max(500, Math.min(scrollDistance, 3000));

    DOMUtils.scrollTo(
      logContainer,
      animationTime,
      logContainer.scrollHeight - logContainer.clientHeight
    );
  }

  handleWindowResize() {
    this.checkIfCloseToTop(this.logContainer);
    this.checkIfAwayFromBottom(this.logContainer);
  }

  checkIfCloseToTop(container) {
    // This number has been determined by trail and error to be a good
    // measurement for close to the top
    if (container.scrollTop < 2000) {
      const {hasLoadedTop, fetchPreviousLogs} = this.props;
      if (!hasLoadedTop) {
        fetchPreviousLogs();
      }
    }
  }

  checkIfAwayFromBottom(container) {
    const isAtBottom = container.offsetHeight + container.scrollTop
      >= container.scrollHeight;

    if (isAtBottom !== this.state.isAtBottom) {
      this.setState({isAtBottom});
      this.props.onAtBottomChange(isAtBottom);
    }

  }

  goToNewHighlightedSearch() {
    const {logContainer} = this;
    const node = logContainer.querySelector('.highlight.selected');
    if (!node) {
      return;
    }

    const containerHeight = logContainer.clientHeight;
    const containerScrollTop = logContainer.scrollTop;
    const nodeDistanceFromTop = DOMUtils.getDistanceFromTopOfParent(node);<commit_msg>Give isAtBottom check a bit of breathing room to check within<commit_after>import React from 'react';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';

import DOMUtils from '../../../../../src/js/utils/DOMUtils';
import Highlight from './Highlight';
import Loader from '../../../../../src/js/components/Loader';
import Util from '../../../../../src/js/utils/Util';
import {PREPEND} from '../../../../../src/js/constants/SystemLogTypes';

const METHODS_TO_BIND = [
  'handleGoToBottom',
  'handleLogContainerScroll',
  'handleWindowResize'
];

class LogView extends React.Component {
  constructor() {
    super(...arguments);

    this.state = {
      isAtBottom: true,
      fullLog: this.props.fullLog
    };

    METHODS_TO_BIND.forEach((method) => {
      this[method] = this[method].bind(this);
    });

    // Make sure to run this on the leading edge to capture this event as soon
    // as possible. This has impact on both checkIfCloseToTop and
    // checkIfAwayFromBottom. Trailing edge will call them too late, i.e. when
    // the data is out of data and possibly scrolled passed the trigger range.
    // We need them to be called as soon as we hit the range wherein these will
    // trigger handlers.
    this.handleLogContainerScroll = Util.throttle(
      this.handleLogContainerScroll,
      50,
      {leading: true, trailing: false}
    );

    this.handleWindowResize = Util.throttle(
      this.handleWindowResize.bind(this),
      50,
      {leading: true, trailing: false}
    );
  }

  componentDidMount() {
    global.addEventListener('resize', this.handleWindowResize);
  }

  componentWillReceiveProps(nextProps) {
    const {logContainer} = this;
    let previousScrollTop;
    let previousScrollHeight;

    if (logContainer) {
      previousScrollTop = logContainer.scrollTop;
      previousScrollHeight = logContainer.scrollHeight;
    }

    // Prevent updates to fullLog, if it has not changed
    if (this.state.fullLog !== nextProps.fullLog) {
      this.setState({fullLog: nextProps.fullLog}, () => {
        // This allows the user to stay at the place of the log they were at
        // before the prepend.
        if (nextProps.direction === PREPEND && previousScrollHeight
          && logContainer && !this.state.isAtBottom) {
          const currentScrollHeight = logContainer.scrollHeight;
          const heightDifference = currentScrollHeight - previousScrollHeight;
          logContainer.scrollTop = previousScrollTop + heightDifference;
        }
      });
    }
  }

  shouldComponentUpdate(nextProps, nextState) {
    const {hasLoadedTop, highlightText, logName, watching} = this.props;
    const {fullLog, isAtBottom} = this.state;

    return Boolean(
      // Check hasLoadedTop
      (hasLoadedTop !== nextProps.hasLoadedTop) ||
      // Check highlightText
      (highlightText !== nextProps.highlightText) ||
      // Check logName
      (logName !== nextProps.logName) ||
      // Check fullLog
      (fullLog !== nextState.fullLog) ||
      // Check watching
      (watching !== nextProps.watching) ||
      // Check isAtBottom
      (isAtBottom !== nextState.isAtBottom)
    );
  }

  componentDidUpdate(prevProps) {
    const {logContainer} = this;
    if (logContainer == null) {
      return;
    }

    // Make sure to go to bottom if we are tailing
    if (this.state.isAtBottom) {
      logContainer.scrollTop = logContainer.scrollHeight;

      return;
    }

    if (prevProps.watching !== this.props.watching ||
      prevProps.highlightText !== this.props.highlightText) {
      this.goToNewHighlightedSearch();
    }
  }

  componentWillUnmount() {
    global.removeEventListener('resize', this.handleWindowResize);
  }

  handleLogContainerScroll(event) {
    const {target} = event;
    if (!target) {
      return;
    }

    this.checkIfCloseToTop(target);
    this.checkIfAwayFromBottom(target);
  }

  handleGoToBottom() {
    const {logContainer, props: {highlightText}} = this;
    // Do not scroll to bottom if we want to highlight a word in the log,
    // or we are already scrolling
    if (logContainer == null || highlightText) {
      return;
    }

    // Cap animation time between 500 and 3000
    const scrollDistance = logContainer.scrollHeight - logContainer.scrollTop;
    const animationTime = Math.max(500, Math.min(scrollDistance, 3000));

    DOMUtils.scrollTo(
      logContainer,
      animationTime,
      logContainer.scrollHeight - logContainer.clientHeight
    );
  }

  handleWindowResize() {
    this.checkIfCloseToTop(this.logContainer);
    this.checkIfAwayFromBottom(this.logContainer);
  }

  checkIfCloseToTop(container) {
    // This number has been determined by trail and error to be a good
    // measurement for close to the top
    if (container.scrollTop < 2000) {
      const {hasLoadedTop, fetchPreviousLogs} = this.props;
      if (!hasLoadedTop) {
        fetchPreviousLogs();
      }
    }
  }

  checkIfAwayFromBottom(container) {
    // The 200px is to give the checker a little bit of breating room to
    // determine when we are at the bottom
    const isAtBottom = container.offsetHeight + container.scrollTop + 200
      >= container.scrollHeight;

    if (isAtBottom !== this.state.isAtBottom) {
      this.setState({isAtBottom});
      this.props.onAtBottomChange(isAtBottom);
    }

  }

  goToNewHighlightedSearch() {
    const {logContainer} = this;
    const node = logContainer.querySelector('.highlight.selected');
    if (!node) {
      return;
    }

    const containerHeight = logContainer.clientHeight;
    const containerScrollTop = logContainer.scrollTop;
    const nodeDistanceFromTop = DOMUtils.getDistanceFromTopOfParent(node);
{'fraction_non_alphanumeric': 0.11123975774848593, 'fraction_numerical': 0.004453152832205202, 'mean_word_length': 2.882780082987552, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2226387', 'n_tokens_mistral': 3280, 'n_tokens_neox': 3172, 'n_words': 805}