<commit_before>/** @flow */
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import reducer from './reducers'
import * as themes from 'redux-devtools-themes'
import { addActionMetadata } from './actions'
import ActionFilter from './components/ActionFilter'
import FilterableState from './components/FilterableState'
import { createRegExpFromFilterText } from './utils'

export default class FilterableLogMonitor extends Component {
  static update = reducer;

  static propTypes = {
    actionsById: PropTypes.object,
    computedStates: PropTypes.array,
    dispatch: PropTypes.func,
    monitorState: PropTypes.shape({
      actionIdToDatumMap: PropTypes.object,
      actionFilterText: PropTypes.string
    }),
    stagedActionIds: PropTypes.array,
    theme: PropTypes.oneOfType([
      PropTypes.object,
      PropTypes.string
    ])
  }

  static defaultProps = {
    theme: 'nicinabox'
  }

  static isMouseOver = false;

  componentWillReceiveProps (nextProps) {
    const { actionsById, dispatch, stagedActionIds } = this.props

    if (nextProps.stagedActionIds !== stagedActionIds) {
      for (var i = stagedActionIds.length; i < nextProps.stagedActionIds.length; i++) {
        let actionId = nextProps.stagedActionIds[i]
        let action = actionsById[actionId]
        let appState = nextProps.computedStates[i].state

        dispatch(addActionMetadata({ action, actionId, appState }))
      }
    }
  }

  componentDidUpdate () {
    if (!this.isMouseOver) {
      this.wrapperNode.scrollTop = this.containerNode.offsetHeight
    }
  }

  render () {
    const {
      actionsById,
      dispatch,
      monitorState: {
        actionFilterText,
        actionIdToDatumMap
      },
      stagedActionIds
    } = this.props

    const theme = this._getTheme()
    const filterableStates = actionIdToDatumMap && stagedActionIds
      .filter(actionId => {
        const actionMetadata = actionsById[actionId]
        const monitorStateAction = actionIdToDatumMap[actionId]

        return (
          monitorStateAction &&
          (
            !actionFilterText ||
            actionMetadata.action.type.match(
              createRegExpFromFilterText(actionFilterText)
            )
          )
        )
      })
      .map(actionId => {
        const actionMetadata = actionsById[actionId]
        const monitorStateAction = actionIdToDatumMap[actionId]

        return (
          <FilterableState
            key={actionId}
            action={actionMetadata.action}
            actionId={actionId}
            dispatch={dispatch}
            monitorStateAction={monitorStateAction}
            theme={theme}
          />
        )
      })

    return (
      <div
        style={{
          position: 'absolute',
          right: 0,
          top: 0,
          bottom: 0,
          width: '100%',
          height: '100%',
          display: 'flex',
          flexDirection: 'column',
          backgroundColor: theme.base00,
          color: theme.base07,
          fontSize: 14,
          minWidth: 200
        }}
        onMouseOver={(e) => this.isMouseOver = true}
        onMouseOut={(e) => this.isMouseOver = false}
      >
        <ActionFilter
          actionFilterText={actionFilterText}
          dispatch={dispatch}
          theme={theme}
        />
        <div
          ref={(node) => { this.wrapperNode = node }}
          style={{
            flex: '1',
            overflowY: 'scroll'
          }}
        >
          <div ref={(node) => { this.containerNode = node }} >{filterableStates}</div><commit_msg>pull request #36, request changes, implement sticky area that the user can attach/detach from<commit_after>/** @flow */
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import reducer from './reducers'
import * as themes from 'redux-devtools-themes'
import { addActionMetadata } from './actions'
import ActionFilter from './components/ActionFilter'
import FilterableState from './components/FilterableState'
import { createRegExpFromFilterText } from './utils'

export default class FilterableLogMonitor extends Component {
  static update = reducer;

  static propTypes = {
    actionsById: PropTypes.object,
    computedStates: PropTypes.array,
    dispatch: PropTypes.func,
    monitorState: PropTypes.shape({
      actionIdToDatumMap: PropTypes.object,
      actionFilterText: PropTypes.string
    }),
    stagedActionIds: PropTypes.array,
    theme: PropTypes.oneOfType([
      PropTypes.object,
      PropTypes.string
    ])
  }

  static defaultProps = {
    theme: 'nicinabox'
  }

  constructor (props) {
    super(props)
    this.isAutoScrollEnabled = true
  }

  componentWillReceiveProps (nextProps) {
    const { actionsById, dispatch, stagedActionIds } = this.props

    if (nextProps.stagedActionIds !== stagedActionIds) {
      for (var i = stagedActionIds.length; i < nextProps.stagedActionIds.length; i++) {
        let actionId = nextProps.stagedActionIds[i]
        let action = actionsById[actionId]
        let appState = nextProps.computedStates[i].state

        dispatch(addActionMetadata({ action, actionId, appState }))
      }
    }
  }

  componentDidUpdate () {
    if (this.isAutoScrollEnabled) {
      this.wrapperNode.scrollTop = this.containerNode.offsetHeight
    }
  }

  render () {
    const {
      actionsById,
      dispatch,
      monitorState: {
        actionFilterText,
        actionIdToDatumMap
      },
      stagedActionIds
    } = this.props

    const theme = this._getTheme()
    const filterableStates = actionIdToDatumMap && stagedActionIds
      .filter(actionId => {
        const actionMetadata = actionsById[actionId]
        const monitorStateAction = actionIdToDatumMap[actionId]

        return (
          monitorStateAction &&
          (
            !actionFilterText ||
            actionMetadata.action.type.match(
              createRegExpFromFilterText(actionFilterText)
            )
          )
        )
      })
      .map(actionId => {
        const actionMetadata = actionsById[actionId]
        const monitorStateAction = actionIdToDatumMap[actionId]

        return (
          <FilterableState
            key={actionId}
            action={actionMetadata.action}
            actionId={actionId}
            dispatch={dispatch}
            monitorStateAction={monitorStateAction}
            theme={theme}
          />
        )
      })

    return (
      <div
        style={{
          position: 'absolute',
          right: 0,
          top: 0,
          bottom: 0,
          width: '100%',
          height: '100%',
          display: 'flex',
          flexDirection: 'column',
          backgroundColor: theme.base00,
          color: theme.base07,
          fontSize: 14,
          minWidth: 200
        }}
        onMouseOver={(e) => this.isMouseOver = true}
        onMouseOut={(e) => this.isMouseOver = false}
      >
        <ActionFilter
          actionFilterText={actionFilterText}
          dispatch={dispatch}
          theme={theme}
        />
        <div
          ref={(node) => { this.wrapperNode = node }}
          style={{
            flex: '1',
            overflowY: 'scroll'
          }}
          onScroll={() => {
            if (this.wrapperNode.scrollTop + this.wrapperNode.offsetHeight >= this.containerNode.offsetHeight) {
              this.isAutoScrollEnabled = true
              return
            }
            this.isAutoScrollEnabled = false
          }}>
          <div ref={(node) => { this.containerNode = node }} >{filterableStates}</div>
{'fraction_non_alphanumeric': 0.10136117351658518, 'fraction_numerical': 0.005286110744020087, 'mean_word_length': 2.171835708298407, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 29, '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': '5262002', 'n_tokens_mistral': 2094, 'n_tokens_neox': 2054, 'n_words': 426}