<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { t } from '../app/locale'
import {
  MAX_BUILDING_HEIGHT,
  isFlooredBuilding,
  changeBuildingHeight,
  buildingHeightUpdated
} from '../segments/buildings'
import { _prettifyHeight } from './info_bubble'

import { KEYS } from '../app/keyboard_commands'
import { hideAllMenus } from '../menus/menu_controller'
import { loseAnyFocus } from '../util/focus'
import { getStreet } from '../streets/data_model'

const WIDTH_EDIT_INPUT_DELAY = 200

class BuildingHeightControl extends React.Component {
  static propTypes = {
    touch: PropTypes.bool,
    position: PropTypes.oneOf(['left', 'right']),
    variant: PropTypes.string,
    value: PropTypes.number
  }

  constructor (props) {
    super(props)

    this.oldValue = null
    this.held = false
    this.timerId = -1
    this.inputEl = null

    this.state = {
      value: props.value,
      displayValue: ''
    }
  }

  addFloor = () => {
    changeBuildingHeight(this.props.position === 'left', true)
    const value = this.state.value + 1
    this.setState({
      value,
      displayValue: _prettifyHeight(value)
    })
  }

  removeFloor = () => {
    changeBuildingHeight(this.props.position === 'left', false)
    const value = this.state.value - 1
    this.setState({
      value,
      displayValue: _prettifyHeight(value)
    })
  }

  onInput = (event) => {
    this.setState({
      displayValue: event.target.value
    })

    this._heightEditInputChanged(event.target.value, false)
  }

  onClickInput = (event) => {
    const el = event.target
    this.held = true

    if (document.activeElement !== el) {
      el.select()
    }
  }

  onFocusInput = (event) => {
    const street = getStreet()
    const value = (this.props.position === 'left') ? street.leftBuildingHeight : street.rightBuildingHeight
    this.oldValue = value
    this.setState({
      value,
      displayValue: value
    })
  }

  /**
   * On blur, input shows prettified value.
   */
  onBlurInput = (event) => {
    const street = getStreet()
    const position = this.props.position

    this._heightEditInputChanged(event.target.value, true)

    const value = (position === 'left') ? street.leftBuildingHeight : street.rightBuildingHeight

    this.setState({
      value,
      displayValue: _prettifyHeight(value)
    })

    this.held = false
  }

  /* same as WidthControl */
  onMouseOverInput = (event) => {
    if (!this.held) {
      // Automatically select the value on hover so that it's easy to start typing new values.
      // In React, this only works if the .select() is called at the end of the execution
      // stack. Since this is in a setTimeout() we need to either persist the React synthetic
      // event or store the reference to the event target.
      const target = event.target
      window.setTimeout(() => {
        target.focus()
        target.select()
      }, 0)
    }
  }

  /* same as WidthControl */
  onMouseOutInput = (event) => {
    if (!this.held) {
      event.target.blur()
    }
  }

  onKeyDownInput = (event) => {
    const street = getStreet()

    switch (event.keyCode) {
      case KEYS.ENTER:
        this._heightEditInputChanged(event.target.value, true)

        // TODO: don't read height off the data again
        const value = this.props.position === 'left' ? street.leftBuildingHeight : street.rightBuildingHeight
        this.setState({
          value,
          displayValue: _prettifyHeight(value) // doesn't work?
        })

        // Apparently we need to lose focus first or we can't re-focus and select
        loseAnyFocus()
        this.inputEl.focus()
        this.inputEl.select()
        break
      case KEYS.ESC:
        this.setState({
          value: this.oldValue
        })
        this._heightEditInputChanged(event.target.value, true)
        hideAllMenus()
        loseAnyFocus()
        break
    }
  }

  /**
   * Given an input value, convert it to an integer, then make sure it is within
   * bounds for a building.
   *
   * @param {String|Number} value - input value to test
   * @returns {Number} - number of floors
   */
  ensureHeightInBounds (value) {
    let height = window.parseInt(value)

    if (!height || (height < 1)) {
      height = 1
    } else if (height > MAX_BUILDING_HEIGHT) {
      height = MAX_BUILDING_HEIGHT
    }

    return height
  }

  _heightEditInputChanged = (value, immediate) => {
    window.clearTimeout(this.timerId)
    const street = getStreet()
    const height = this.ensureHeightInBounds(value)

    if (immediate) {
      if (this.props.position === 'left') {
        street.leftBuildingHeight = height
      } else {
        street.rightBuildingHeight = height
      }
      buildingHeightUpdated()
    } else {
      this.timerId = window.setTimeout(() => {
        if (this.props.position === 'left') {
          street.leftBuildingHeight = height
        } else {
          street.rightBuildingHeight = height
        }
        buildingHeightUpdated()
      }, WIDTH_EDIT_INPUT_DELAY)
    }
  }<commit_msg>BuildingHeightControl: refactor dupe code in _heightEditInputChanged<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { t } from '../app/locale'
import {
  MAX_BUILDING_HEIGHT,
  isFlooredBuilding,
  changeBuildingHeight,
  buildingHeightUpdated
} from '../segments/buildings'
import { _prettifyHeight } from './info_bubble'

import { KEYS } from '../app/keyboard_commands'
import { hideAllMenus } from '../menus/menu_controller'
import { loseAnyFocus } from '../util/focus'
import { getStreet } from '../streets/data_model'

const WIDTH_EDIT_INPUT_DELAY = 200

class BuildingHeightControl extends React.Component {
  static propTypes = {
    touch: PropTypes.bool,
    position: PropTypes.oneOf(['left', 'right']),
    variant: PropTypes.string,
    value: PropTypes.number
  }

  constructor (props) {
    super(props)

    this.oldValue = null
    this.held = false
    this.timerId = -1
    this.inputEl = null

    this.state = {
      value: props.value,
      displayValue: ''
    }
  }

  addFloor = () => {
    changeBuildingHeight(this.props.position === 'left', true)
    const value = this.state.value + 1
    this.setState({
      value,
      displayValue: _prettifyHeight(value)
    })
  }

  removeFloor = () => {
    changeBuildingHeight(this.props.position === 'left', false)
    const value = this.state.value - 1
    this.setState({
      value,
      displayValue: _prettifyHeight(value)
    })
  }

  onInput = (event) => {
    this.setState({
      displayValue: event.target.value
    })

    this._heightEditInputChanged(event.target.value, false)
  }

  onClickInput = (event) => {
    const el = event.target
    this.held = true

    if (document.activeElement !== el) {
      el.select()
    }
  }

  onFocusInput = (event) => {
    const street = getStreet()
    const value = (this.props.position === 'left') ? street.leftBuildingHeight : street.rightBuildingHeight
    this.oldValue = value
    this.setState({
      value,
      displayValue: value
    })
  }

  /**
   * On blur, input shows prettified value.
   */
  onBlurInput = (event) => {
    const street = getStreet()
    const position = this.props.position

    this._heightEditInputChanged(event.target.value, true)

    const value = (position === 'left') ? street.leftBuildingHeight : street.rightBuildingHeight

    this.setState({
      value,
      displayValue: _prettifyHeight(value)
    })

    this.held = false
  }

  /* same as WidthControl */
  onMouseOverInput = (event) => {
    if (!this.held) {
      // Automatically select the value on hover so that it's easy to start typing new values.
      // In React, this only works if the .select() is called at the end of the execution
      // stack. Since this is in a setTimeout() we need to either persist the React synthetic
      // event or store the reference to the event target.
      const target = event.target
      window.setTimeout(() => {
        target.focus()
        target.select()
      }, 0)
    }
  }

  /* same as WidthControl */
  onMouseOutInput = (event) => {
    if (!this.held) {
      event.target.blur()
    }
  }

  onKeyDownInput = (event) => {
    const street = getStreet()

    switch (event.keyCode) {
      case KEYS.ENTER:
        this._heightEditInputChanged(event.target.value, true)

        // TODO: don't read height off the data again
        const value = this.props.position === 'left' ? street.leftBuildingHeight : street.rightBuildingHeight
        this.setState({
          value,
          displayValue: _prettifyHeight(value) // doesn't work?
        })

        // Apparently we need to lose focus first or we can't re-focus and select
        loseAnyFocus()
        this.inputEl.focus()
        this.inputEl.select()
        break
      case KEYS.ESC:
        this.setState({
          value: this.oldValue
        })
        this._heightEditInputChanged(event.target.value, true)
        hideAllMenus()
        loseAnyFocus()
        break
    }
  }

  /**
   * Given an input value, convert it to an integer, then make sure it is within
   * bounds for a building.
   *
   * @param {String|Number} value - input value to test
   * @returns {Number} - number of floors
   */
  ensureHeightInBounds (value) {
    let height = window.parseInt(value)

    if (!height || (height < 1)) {
      height = 1
    } else if (height > MAX_BUILDING_HEIGHT) {
      height = MAX_BUILDING_HEIGHT
    }

    return height
  }

  _heightEditInputChanged = (value, immediate) => {
    window.clearTimeout(this.timerId)
    const street = getStreet()
    const height = this.ensureHeightInBounds(value)

    const update = () => {
      if (this.props.position === 'left') {
        street.leftBuildingHeight = height
      } else {
        street.rightBuildingHeight = height
      }
      buildingHeightUpdated()
    }

    if (immediate) {
      update()
    } else {
      this.timerId = window.setTimeout(update, WIDTH_EDIT_INPUT_DELAY)
    }
  }
{'fraction_non_alphanumeric': 0.11462138414453549, 'fraction_numerical': 0.0017770757231710928, 'mean_word_length': 2.495514147688061, 'pattern_counts': {'":': 0, '<': 5, '<?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': '2008290', 'n_tokens_mistral': 3107, 'n_tokens_neox': 3003, 'n_words': 763}