<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import Triangle from './Triangle'
import RemoveButton from './RemoveButton'
import Variants from './Variants'
import WidthControl from './WidthControl'
import BuildingHeightControl from './BuildingHeightControl'
import Warnings from './Warnings'
import Description from './Description.jsx'
import { infoBubble } from './info_bubble'
import { getDescriptionData } from './description'
import { resumeFadeoutControls } from '../segments/resizing'
// import { trackEvent } from '../app/event_tracking'
import { BUILDINGS } from '../segments/buildings'
import { getSegmentInfo, getSegmentVariantInfo } from '../segments/info'
import { loseAnyFocus } from '../util/focus'
import { getElAbsolutePos } from '../util/helpers'
import { setInfoBubbleMouseInside } from '../store/actions/infoBubble'
import { t } from '../app/locale'

const INFO_BUBBLE_TYPE_SEGMENT = 1
const INFO_BUBBLE_TYPE_LEFT_BUILDING = 2
const INFO_BUBBLE_TYPE_RIGHT_BUILDING = 3

class InfoBubble extends React.Component {
  static propTypes = {
    visible: PropTypes.bool.isRequired,
    dataNo: PropTypes.string,
    setInfoBubbleMouseInside: PropTypes.func,
    street: PropTypes.object,
    system: PropTypes.object
  }

  static defaultProps = {
    visible: false
  }

  constructor (props) {
    super(props)

    // Stores a ref to the element
    this.el = null

    this.state = {
      type: null,
      street: null,
      segment: null,
      description: null,
      highlightTriangle: false
    }
  }

  componentDidMount () {
    // This listener hides the info bubble when the mouse leaves the
    // document area. Do not normalize it to a pointerleave event
    // because it doesn't make sense for other pointer types
    document.addEventListener('mouseleave', this.hide)

    // Listen for external triggers to update contents here
    window.addEventListener('stmx:force_infobubble_update', (e) => {
      this.updateInfoBubbleState()
    })
  }

  // TODO: Will be deprecated after Version 17
  // Use getDerivedStateFromProps throughout application after updating ReactJS
  componentWillReceiveProps (nextProps) {
    const { dataNo, street } = nextProps
    const isBuilding = (dataNo === 'left' || dataNo === 'right')
    if (isBuilding) {
      this.updateInfoBubbleForBuildings(dataNo, street)
    }
  }

  componentDidUpdate () {
    this.updateBubbleDimensions()
  }

  componentWillUnmount () {
    // Clean up event listener
    document.removeEventListener('mouseleave', this.hide)
  }

  componentDidCatch (error) {
    console.error(error)
  }

  hide = () => {
    infoBubble.hide()
  }

  onTouchStart (event) {
    resumeFadeoutControls()
  }

  // TODO: verify this continues to work with pointer / touch taps
  onMouseEnter = (event) => {
    if (infoBubble.segmentEl) {
      infoBubble.segmentEl.classList.add('hide-drag-handles-when-inside-info-bubble')
    }

    this.props.setInfoBubbleMouseInside(true)

    infoBubble.updateHoverPolygon()
  }

  onMouseLeave = (event) => {
    // TODO: Prevent pointer taps from flashing the drag handles
    if (infoBubble.segmentEl) {
      infoBubble.segmentEl.classList.remove('hide-drag-handles-when-inside-info-bubble')
    }

    this.props.setInfoBubbleMouseInside(false)

    // Returns focus to body when pointer leaves the info bubble area
    // so that keyboard commands respond to pointer position rather than
    // any focused buttons/inputs
    loseAnyFocus()
  }

  toggleHighlightTriangle = () => {
    this.setState({ highlightTriangle: !this.state.highlightTriangle })
  }

  updateInfoBubbleForBuildings = (position, street) => {
    const type = (position === 'left') ? INFO_BUBBLE_TYPE_LEFT_BUILDING : INFO_BUBBLE_TYPE_RIGHT_BUILDING

    if (this.state.type !== type) {
      this.setState({
        type,
        street,
        segment: null,
        description: null
      })
    }
  }

  updateInfoBubbleState = () => {
    const { street } = this.props
    const segment = street.segments[this.props.dataNo]
    this.setState({
      type: INFO_BUBBLE_TYPE_SEGMENT,
      street,
      segment,
      description: getDescriptionData(segment)
    })
  }

  updateBubbleDimensions = () => {
    let bubbleHeight
    if (infoBubble.descriptionVisible) {
      const el = this.el.querySelector('.description-canvas')
      const pos = getElAbsolutePos(el)
      bubbleHeight = pos[1] + el.offsetHeight - 38
    } else {
      bubbleHeight = this.el.offsetHeight
    }

    const height = bubbleHeight + 30
    infoBubble.bubbleHeight = bubbleHeight

    this.el.style.webkitTransformOrigin = '50% ' + height + 'px'
    this.el.style.MozTransformOrigin = '50% ' + height + 'px'
    this.el.style.transformOrigin = '50% ' + height + 'px'

    if (this.state.type === INFO_BUBBLE_TYPE_RIGHT_BUILDING) {
      const bubbleX = this.props.system.viewportWidth - this.el.offsetWidth - 50
      infoBubble.bubbleX = bubbleX
      this.el.style.left = bubbleX + 'px'
    }
  }

  /**
   * Retrieve name from segment data. It should also find the equivalent strings from the
   * translation files if provided.
   */
  getName = () => {
    let name<commit_msg>added documentation on why we are manually calculating the left style for the infobubble of the right building<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import Triangle from './Triangle'
import RemoveButton from './RemoveButton'
import Variants from './Variants'
import WidthControl from './WidthControl'
import BuildingHeightControl from './BuildingHeightControl'
import Warnings from './Warnings'
import Description from './Description.jsx'
import { infoBubble } from './info_bubble'
import { getDescriptionData } from './description'
import { resumeFadeoutControls } from '../segments/resizing'
// import { trackEvent } from '../app/event_tracking'
import { BUILDINGS } from '../segments/buildings'
import { getSegmentInfo, getSegmentVariantInfo } from '../segments/info'
import { loseAnyFocus } from '../util/focus'
import { getElAbsolutePos } from '../util/helpers'
import { setInfoBubbleMouseInside } from '../store/actions/infoBubble'
import { t } from '../app/locale'

const INFO_BUBBLE_TYPE_SEGMENT = 1
const INFO_BUBBLE_TYPE_LEFT_BUILDING = 2
const INFO_BUBBLE_TYPE_RIGHT_BUILDING = 3

class InfoBubble extends React.Component {
  static propTypes = {
    visible: PropTypes.bool.isRequired,
    dataNo: PropTypes.string,
    setInfoBubbleMouseInside: PropTypes.func,
    street: PropTypes.object,
    system: PropTypes.object
  }

  static defaultProps = {
    visible: false
  }

  constructor (props) {
    super(props)

    // Stores a ref to the element
    this.el = null

    this.state = {
      type: null,
      street: null,
      segment: null,
      description: null,
      highlightTriangle: false
    }
  }

  componentDidMount () {
    // This listener hides the info bubble when the mouse leaves the
    // document area. Do not normalize it to a pointerleave event
    // because it doesn't make sense for other pointer types
    document.addEventListener('mouseleave', this.hide)

    // Listen for external triggers to update contents here
    window.addEventListener('stmx:force_infobubble_update', (e) => {
      this.updateInfoBubbleState()
    })
  }

  // TODO: Will be deprecated after Version 17
  // Use getDerivedStateFromProps throughout application after updating ReactJS
  componentWillReceiveProps (nextProps) {
    const { dataNo, street } = nextProps
    const isBuilding = (dataNo === 'left' || dataNo === 'right')
    if (isBuilding) {
      this.updateInfoBubbleForBuildings(dataNo, street)
    }
  }

  componentDidUpdate () {
    this.updateBubbleDimensions()
  }

  componentWillUnmount () {
    // Clean up event listener
    document.removeEventListener('mouseleave', this.hide)
  }

  componentDidCatch (error) {
    console.error(error)
  }

  hide = () => {
    infoBubble.hide()
  }

  onTouchStart (event) {
    resumeFadeoutControls()
  }

  // TODO: verify this continues to work with pointer / touch taps
  onMouseEnter = (event) => {
    if (infoBubble.segmentEl) {
      infoBubble.segmentEl.classList.add('hide-drag-handles-when-inside-info-bubble')
    }

    this.props.setInfoBubbleMouseInside(true)

    infoBubble.updateHoverPolygon()
  }

  onMouseLeave = (event) => {
    // TODO: Prevent pointer taps from flashing the drag handles
    if (infoBubble.segmentEl) {
      infoBubble.segmentEl.classList.remove('hide-drag-handles-when-inside-info-bubble')
    }

    this.props.setInfoBubbleMouseInside(false)

    // Returns focus to body when pointer leaves the info bubble area
    // so that keyboard commands respond to pointer position rather than
    // any focused buttons/inputs
    loseAnyFocus()
  }

  toggleHighlightTriangle = () => {
    this.setState({ highlightTriangle: !this.state.highlightTriangle })
  }

  updateInfoBubbleForBuildings = (position, street) => {
    const type = (position === 'left') ? INFO_BUBBLE_TYPE_LEFT_BUILDING : INFO_BUBBLE_TYPE_RIGHT_BUILDING

    if (this.state.type !== type) {
      this.setState({
        type,
        street,
        segment: null,
        description: null
      })
    }
  }

  updateInfoBubbleState = () => {
    const { street } = this.props
    const segment = street.segments[this.props.dataNo]
    this.setState({
      type: INFO_BUBBLE_TYPE_SEGMENT,
      street,
      segment,
      description: getDescriptionData(segment)
    })
  }

  updateBubbleDimensions = () => {
    let bubbleHeight
    if (infoBubble.descriptionVisible) {
      const el = this.el.querySelector('.description-canvas')
      const pos = getElAbsolutePos(el)
      bubbleHeight = pos[1] + el.offsetHeight - 38
    } else {
      bubbleHeight = this.el.offsetHeight
    }

    const height = bubbleHeight + 30
    infoBubble.bubbleHeight = bubbleHeight

    this.el.style.webkitTransformOrigin = '50% ' + height + 'px'
    this.el.style.MozTransformOrigin = '50% ' + height + 'px'
    this.el.style.transformOrigin = '50% ' + height + 'px'

    // When the infoBubble needed to be shown for the right building,the offsetWidth
    // used to calculate the left style was from the previous rendering of this component.
    // This meant that if the last time the infoBubble was shown was for a segment, then the
    // offsetWidth used to calculate the new left style would be smaller than it should be.
    // The current solution is to manually recalculate the left style and set the style
    // when hovering over the right building.

    if (this.state.type === INFO_BUBBLE_TYPE_RIGHT_BUILDING) {
      const bubbleX = this.props.system.viewportWidth - this.el.offsetWidth - 50
      infoBubble.bubbleX = bubbleX
      this.el.style.left = bubbleX + 'px'
    }
  }

  /**
   * Retrieve name from segment data. It should also find the equivalent strings from the
   * translation files if provided.
   */
  getName = () => {
    let name
{'fraction_non_alphanumeric': 0.10270074970644025, 'fraction_numerical': 0.0032517387769849158, 'mean_word_length': 3.190764572293717, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 21, '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': '1873963', 'n_tokens_mistral': 3317, 'n_tokens_neox': 3210, 'n_words': 868}