<commit_before>import React, { PureComponent } from 'react'
import warning from 'warning'
import PropTypes from 'prop-types'
import { discrete, linear } from './scales'
import Rail from '../Rail'
import Ticks from '../Ticks'
import Tracks from '../Tracks'
import Handles from '../Handles'
import { mode1, mode2 } from './modes'
import * as utils from './utils'

const prfx = 'react-compound-slider:'

const noop = () => {}

const compare = b => (m, d, i) => m && b[i] === d

const areValuesEqual = (a, b) => {
  return a === b || (a.length === b.length && a.reduce(compare(b), true))
}

class Slider extends PureComponent {
  constructor(props) {
    super(props)

    this.slider = null

    this.isControlled = false

    this.valueToPerc = linear()
    this.valueToStep = discrete()
    this.pixelToStep = discrete()

    this.onMouseMove = this.onMouseMove.bind(this)
    this.onTouchMove = this.onTouchMove.bind(this)
    this.onMove = this.onMove.bind(this)

    this.onMouseDown = this.onMouseDown.bind(this)
    this.onTouchStart = this.onTouchStart.bind(this)
    this.onStart = this.onStart.bind(this)

    this.onMouseUp = this.onMouseUp.bind(this)
    this.onTouchEnd = this.onTouchEnd.bind(this)
  }

  componentWillMount() {
    const { defaultValues, values, domain, step, reversed } = this.props

    if (values) {
      this.isControlled = true
    }

    warning(
      defaultValues === undefined || values === undefined,
      `${prfx} Cannot set both 'defaultValues' and 'values' props.`,
    )

    this.updateRange(domain, step, reversed)
    this.updateValues(values || defaultValues, reversed)
  }

  componentWillReceiveProps(next) {
    const { domain, step, reversed, values } = next
    const { props } = this

    if (
      domain[0] !== props.domain[0] ||
      domain[1] !== props.domain[1] ||
      step !== props.step ||
      reversed !== props.reversed
    ) {
      this.updateRange(domain, step, reversed)
      const remapped = this.reMapValues(reversed)
      next.onChange(remapped)
      next.onUpdate(remapped)
    }

    if (
      !this.active &&
      this.isControlled === true &&
      !areValuesEqual(values, props.values)
    ) {
      this.updateValues(values, reversed)
    }
  }

  reMapValues(reversed) {
    const { values } = this.state
    return this.updateValues(values.map(d => d.val), reversed)
  }

  updateValues(arr = [], reversed) {
    const values = arr
      .map(x => {
        const val = this.valueToStep(x)

        warning(
          x === val,
          `${prfx} Invalid value encountered. Changing ${x} to ${val}.`,
        )

        return val
      })
      .map((val, i) => ({ key: `$$-${i}`, val }))
      .sort(utils.getSortByVal(reversed))

    this.setState(() => ({ values }))

    return values.map(d => d.val)
  }

  updateRange([min, max], step, reversed) {
    const range = utils.getStepRange(min, max, step)

    this.valueToStep
      .range(range.slice())
      .domain([min - step / 2, max + step / 2])

    if (reversed === true) {
      this.valueToPerc.domain([min, max]).range([100, 0])
      range.reverse()
    } else {
      this.valueToPerc.domain([min, max]).range([0, 100])
    }

    this.pixelToStep.range(range)

    warning(
      max > min,
      `${prfx} Max must be greater than min (even if reversed). Max is ${max}. Min is ${min}.`,
    )

    warning(
      range.length <= 10001,
      `${prfx} Increase step value. Found ${range.length.toLocaleString()} values in range.`,
    )

    const last = range.length - 1

    warning(
      range[reversed ? last : 0] === min && range[reversed ? 0 : last] === max,
      `${prfx} The range is incorrectly calculated. Check domain (min, max) and step values.`,
    )
  }

  onMouseDown(e, key) {
    this.onStart(e, key, false)
  }

  onTouchStart(e, key) {
    if (utils.isNotValidTouch(e)) {
      return
    }
    this.onStart(e, key, true)
  }

  onStart(e, key, isTouch) {
    const { values } = this.state

    e.stopPropagation && e.stopPropagation()
    e.preventDefault && e.preventDefault()

    const active = values.find(value => {
      return value.key === key
    })

    if (active) {
      this.active = key
      isTouch ? this.addTouchEvents() : this.addMouseEvents()
    } else {
      this.active = null
      this.requestMove(e, isTouch)
    }
  }

  requestMove(e, isTouch) {
    const { state: { values: prev }, props: { vertical, reversed } } = this
    const { slider } = this

    this.pixelToStep.domain(utils.getSliderDomain(slider, vertical))

    let step

    if (isTouch) {
      step = this.pixelToStep(utils.getTouchPosition(vertical, e))
    } else {
      step = this.pixelToStep(vertical ? e.clientY : e.pageX)
    }

    let active = null
    let lowest = Infinity

    for (let i = 0; i < prev.length; i++) {
      const diff = Math.abs(this.valueToStep(prev[i].val) - step)

      if (diff < lowest) {
        active = prev[i].key
        lowest = diff
      }
    }

    if (active) {
      const next = utils.updateValues(prev, active, step, reversed)
      this.onMove(prev, next, true)
    }
  }

  addMouseEvents() {
    document.addEventListener('mousemove', this.onMouseMove)
    document.addEventListener('mouseup', this.onMouseUp)
  }

  addTouchEvents() {
    document.addEventListener('touchmove', this.onTouchMove)
    document.addEventListener('touchend', this.onTouchEnd)
  }

  onMouseMove(e) {
    const { state: { values: prev }, props: { vertical, reversed } } = this
    const { active, slider } = this

    this.pixelToStep.domain(utils.getSliderDomain(slider, vertical))

    const step = this.pixelToStep(vertical ? e.clientY : e.pageX)
    const next = utils.updateValues(prev, active, step, reversed)

    this.onMove(prev, next)
  }

  onTouchMove(e) {
    const { state: { values: prev }, props: { vertical, reversed } } = this
    const { active, slider } = this

    if (utils.isNotValidTouch(e)) {
      return
    }

    this.pixelToStep.domain(utils.getSliderDomain(slider, vertical))

    const step = this.pixelToStep(utils.getTouchPosition(vertical, e))
    const next = utils.updateValues(prev, active, step, reversed)

    this.onMove(prev, next)
  }

  onMove(prev, next, submit) {
    const { mode, onUpdate, onChange } = this.props

    if (next !== prev) {
      let values

      switch (mode) {
        case 1:
          values = mode1(prev, next)
          break
        case 2:
          values = mode2(prev, next)
          break
        default:
          values = next
          warning(false, `${prfx} Invalid mode value.`)
      }

      this.setState({ values })

      onUpdate(values.map(d => d.val))

      if (submit) {
        onChange(values.map(d => d.val))
      }
    }
  }

  onMouseUp() {
    const { state: { values }, props: { onChange } } = this
    this.active = null
    onChange(values.map(d => d.val))

    document.removeEventListener('mousemove', this.onMouseMove)
    document.removeEventListener('mouseup', this.onMouseUp)
  }

  onTouchEnd() {
    const { state: { values }, props: { onChange } } = this
    this.active = null
    onChange(values.map(d => d.val))

    document.removeEventListener('touchmove', this.onTouchMove)
    document.removeEventListener('touchend', this.onTouchEnd)
  }

  render() {
    const { state: { values }, props: { className, rootStyle } } = this

    const handles = values.map(({ key, val }) => {
      return { id: key, value: val, percent: this.valueToPerc(val) }
    })

    const children = React.Children.map(this.props.children, child => {
      if (
        child.type === Rail ||
        child.type === Ticks ||
        child.type === Tracks ||
        child.type === Handles
      ) {
        return React.cloneElement(child, {
          scale: this.valueToPerc,
          handles,
          emitMouse: this.onMouseDown,
          emitTouch: this.onTouchStart,
        })
      }

      return child
    })

    return (

 (this.slider = d)}
      >
        {children}

    )
  }
}

Slider.propTypes = {
  /**
   * CSS class name applied to the root div of the slider.
   */
  className: PropTypes.string,
  /**
   * An object with any inline styles you want applied to the root div.
   */
  rootStyle: PropTypes.object,
  /**
   * Two element array of numbers providing the min and max values for the slider [min, max] e.g. [0, 100].
   * It does not matter if the slider is reversed on the screen, domain is always [min, max] with min < max.
   */
  domain: PropTypes.array,
  /**
   * Use the values prop for a 'controlled' slider i.e. you want to be able to set tha values manually after mounting. Note: you CAN'T set BOTH values and defaultValues.

An array of numbers. You can supply one for a value slider, two for a range slider or more to create n-handled sliders.
   * The values should correspond to valid step values in the domain.
   * The numbers will be forced into the domain if they are two small or large.
   */
  values: PropTypes.array,
  /**
   * Use defaultValues if you want to set the values when the component mounts and just receive updates as the values change. Note: you CAN'T set BOTH values and defaultValues.

An array of numbers. You can supply one for a value slider, two for a range slider or more to create n-handled sliders.
   * The values should correspond to valid step values in the domain.
   * The numbers will be forced into the domain if they are two small or large.
   */
  defaultValues: PropTypes.array,
  /**
   * The step value for the slider.
   */
  step: PropTypes.number,
  /**
   * The interaction mode. Value of 1 will allow handles to cross.
   * Value of 2 will keep the sliders from crossing and separated by a step.
   */
  mode: PropTypes.oneOf([1, 2]),
  /**
   * Set to true if the slider is displayed vertically to tell the slider to use the height to calculate positions.
   */
  vertical: PropTypes.bool,
  /**
   * Reverse the display of slider values.
   */
  reversed: PropTypes.bool,
  /**
   * Function called with the values at each update (caution: high-volume updates when dragging).
   */
  onUpdate: PropTypes.func,
  /**
   * Function called with the values when interaction stops.
   */
  onChange: PropTypes.func,
  /**
   * Component children to render
   */
  children: PropTypes.any,
}

Slider.defaultProps = {
  mode: 1,
  step: 0.1,
  domain: [0, 100],
  vertical: false,
  reversed: false,
  onUpdate: noop,
  onChange: noop,
}

export default Slider
<commit_msg>no defaultValues<commit_after>import React, { PureComponent } from 'react'
import warning from 'warning'
import PropTypes from 'prop-types'
import { discrete, linear } from './scales'
import Rail from '../Rail'
import Ticks from '../Ticks'
import Tracks from '../Tracks'
import Handles from '../Handles'
import { mode1, mode2 } from './modes'
import * as utils from './utils'

const prfx = 'react-compound-slider:'

const noop = () => {}

const compare = b => (m, d, i) => m && b[i] === d

const equal = (a, b) => {
  return a === b || (a.length === b.length && a.reduce(compare(b), true))
}

class Slider extends PureComponent {
  constructor(props) {
    super(props)

    this.slider = null

    this.valueToPerc = linear()
    this.valueToStep = discrete()
    this.pixelToStep = discrete()

    this.onMouseMove = this.onMouseMove.bind(this)
    this.onTouchMove = this.onTouchMove.bind(this)
    this.onMove = this.onMove.bind(this)

    this.onMouseDown = this.onMouseDown.bind(this)
    this.onTouchStart = this.onTouchStart.bind(this)
    this.onStart = this.onStart.bind(this)

    this.onMouseUp = this.onMouseUp.bind(this)
    this.onTouchEnd = this.onTouchEnd.bind(this)
  }

  componentWillMount() {
    const { defaultValues, values, domain, step, reversed } = this.props

    warning(
      defaultValues === undefined,
      `${prfx} defaultValues is deprecated. Use 'values' prop.`,
    )

    this.updateRange(domain, step, reversed)
    this.updateValues(values || defaultValues, reversed)
  }

  componentWillReceiveProps(next) {
    const { domain, step, reversed, values } = next
    const { props } = this

    if (
      domain[0] !== props.domain[0] ||
      domain[1] !== props.domain[1] ||
      step !== props.step ||
      reversed !== props.reversed
    ) {
      this.updateRange(domain, step, reversed)
      const remapped = this.reMapValues(reversed)
      next.onChange(remapped)
      next.onUpdate(remapped)
    }

    if (!this.active && !equal(values, props.values)) {
      this.updateValues(values, reversed)
    }
  }

  reMapValues(reversed) {
    const { values } = this.state
    return this.updateValues(values.map(d => d.val), reversed)
  }

  updateValues(arr = [], reversed) {
    const values = arr
      .map(x => {
        const val = this.valueToStep(x)

        warning(
          x === val,
          `${prfx} Invalid value encountered. Changing ${x} to ${val}.`,
        )

        return val
      })
      .map((val, i) => ({ key: `$$-${i}`, val }))
      .sort(utils.getSortByVal(reversed))

    this.setState(() => ({ values }))

    return values.map(d => d.val)
  }

  updateRange([min, max], step, reversed) {
    const range = utils.getStepRange(min, max, step)

    this.valueToStep
      .range(range.slice())
      .domain([min - step / 2, max + step / 2])

    if (reversed === true) {
      this.valueToPerc.domain([min, max]).range([100, 0])
      range.reverse()
    } else {
      this.valueToPerc.domain([min, max]).range([0, 100])
    }

    this.pixelToStep.range(range)

    warning(
      max > min,
      `${prfx} Max must be greater than min (even if reversed). Max is ${max}. Min is ${min}.`,
    )

    warning(
      range.length <= 10001,
      `${prfx} Increase step value. Found ${range.length.toLocaleString()} values in range.`,
    )

    const last = range.length - 1

    warning(
      range[reversed ? last : 0] === min && range[reversed ? 0 : last] === max,
      `${prfx} The range is incorrectly calculated. Check domain (min, max) and step values.`,
    )
  }

  onMouseDown(e, key) {
    this.onStart(e, key, false)
  }

  onTouchStart(e, key) {
    if (utils.isNotValidTouch(e)) {
      return
    }
    this.onStart(e, key, true)
  }

  onStart(e, key, isTouch) {
    const { values } = this.state

    e.stopPropagation && e.stopPropagation()
    e.preventDefault && e.preventDefault()

    const active = values.find(value => {
      return value.key === key
    })

    if (active) {
      this.active = key
      isTouch ? this.addTouchEvents() : this.addMouseEvents()
    } else {
      this.active = null
      this.requestMove(e, isTouch)
    }
  }

  requestMove(e, isTouch) {
    const { state: { values: prev }, props: { vertical, reversed } } = this
    const { slider } = this

    this.pixelToStep.domain(utils.getSliderDomain(slider, vertical))

    let step

    if (isTouch) {
      step = this.pixelToStep(utils.getTouchPosition(vertical, e))
    } else {
      step = this.pixelToStep(vertical ? e.clientY : e.pageX)
    }

    let active = null
    let lowest = Infinity

    for (let i = 0; i < prev.length; i++) {
      const diff = Math.abs(this.valueToStep(prev[i].val) - step)

      if (diff < lowest) {
        active = prev[i].key
        lowest = diff
      }
    }

    if (active) {
      const next = utils.updateValues(prev, active, step, reversed)
      this.onMove(prev, next, true)
    }
  }

  addMouseEvents() {
    document.addEventListener('mousemove', this.onMouseMove)
    document.addEventListener('mouseup', this.onMouseUp)
  }

  addTouchEvents() {
    document.addEventListener('touchmove', this.onTouchMove)
    document.addEventListener('touchend', this.onTouchEnd)
  }

  onMouseMove(e) {
    const { state: { values: prev }, props: { vertical, reversed } } = this
    const { active, slider } = this

    this.pixelToStep.domain(utils.getSliderDomain(slider, vertical))

    const step = this.pixelToStep(vertical ? e.clientY : e.pageX)
    const next = utils.updateValues(prev, active, step, reversed)

    this.onMove(prev, next)
  }

  onTouchMove(e) {
    const { state: { values: prev }, props: { vertical, reversed } } = this
    const { active, slider } = this

    if (utils.isNotValidTouch(e)) {
      return
    }

    this.pixelToStep.domain(utils.getSliderDomain(slider, vertical))

    const step = this.pixelToStep(utils.getTouchPosition(vertical, e))
    const next = utils.updateValues(prev, active, step, reversed)

    this.onMove(prev, next)
  }

  onMove(prev, next, submit) {
    const { mode, onUpdate, onChange } = this.props

    if (next !== prev) {
      let values

      switch (mode) {
        case 1:
          values = mode1(prev, next)
          break
        case 2:
          values = mode2(prev, next)
          break
        default:
          values = next
          warning(false, `${prfx} Invalid mode value.`)
      }

      this.setState({ values })

      onUpdate(values.map(d => d.val))

      if (submit) {
        onChange(values.map(d => d.val))
      }
    }
  }

  onMouseUp() {
    const { state: { values }, props: { onChange } } = this
    this.active = null
    onChange(values.map(d => d.val))

    document.removeEventListener('mousemove', this.onMouseMove)
    document.removeEventListener('mouseup', this.onMouseUp)
  }

  onTouchEnd() {
    const { state: { values }, props: { onChange } } = this
    this.active = null
    onChange(values.map(d => d.val))

    document.removeEventListener('touchmove', this.onTouchMove)
    document.removeEventListener('touchend', this.onTouchEnd)
  }

  render() {
    const { state: { values }, props: { className, rootStyle } } = this

    const handles = values.map(({ key, val }) => {
      return { id: key, value: val, percent: this.valueToPerc(val) }
    })

    const children = React.Children.map(this.props.children, child => {
      if (
        child.type === Rail ||
        child.type === Ticks ||
        child.type === Tracks ||
        child.type === Handles
      ) {
        return React.cloneElement(child, {
          scale: this.valueToPerc,
          handles,
          emitMouse: this.onMouseDown,
          emitTouch: this.onTouchStart,
        })
      }

      return child
    })

    return (

 (this.slider = d)}
      >
        {children}

    )
  }
}

Slider.propTypes = {
  /**
   * CSS class name applied to the root div of the slider.
   */
  className: PropTypes.string,
  /**
   * An object with any inline styles you want applied to the root div.
   */
  rootStyle: PropTypes.object,
  /**
   * Two element array of numbers providing the min and max values for the slider [min, max] e.g. [0, 100].
   * It does not matter if the slider is reversed on the screen, domain is always [min, max] with min < max.
   */
  domain: PropTypes.array,
  /**
   * Use the values prop for a 'controlled' slider i.e. you want to be able to set tha values manually after mounting. Note: you CAN'T set BOTH values and defaultValues.

An array of numbers. You can supply one for a value slider, two for a range slider or more to create n-handled sliders.
   * The values should correspond to valid step values in the domain.
   * The numbers will be forced into the domain if they are two small or large.
   */
  values: PropTypes.array,
  /**
   * DEPRECATED - Use 'values' prop
   */
  defaultValues: PropTypes.array,
  /**
   * The step value for the slider.
   */
  step: PropTypes.number,
  /**
   * The interaction mode. Value of 1 will allow handles to cross.
   * Value of 2 will keep the sliders from crossing and separated by a step.
   */
  mode: PropTypes.oneOf([1, 2]),
  /**
   * Set to true if the slider is displayed vertically to tell the slider to use the height to calculate positions.
   */
  vertical: PropTypes.bool,
  /**
   * Reverse the display of slider values.
   */
  reversed: PropTypes.bool,
  /**
   * Function called with the values at each update (caution: high-volume updates when dragging).
   */
  onUpdate: PropTypes.func,
  /**
   * Function called with the values when interaction stops.
   */
  onChange: PropTypes.func,
  /**
   * Component children to render
   */
  children: PropTypes.any,
}

Slider.defaultProps = {
  mode: 1,
  step: 0.1,
  domain: [0, 100],
  vertical: false,
  reversed: false,
  onUpdate: noop,
  onChange: noop,
}

export default Slider
<|endoftext|>


<commit_before>import React, { PureComponent } from 'react'
import warning from 'warning'
import PropTypes from 'prop-types'
import Rail from '../Rail'
import Ticks from '../Ticks'
import Tracks from '../Tracks'
import Handles from '../Handles'
import { mode1, mode2, mode3 } from './modes'
import {
  isNotValidTouch,
  getTouchPosition,
  getUpdatedValues,
  getSliderDomain,
  getStepRange,
  getSortByVal,
} from './utils'
import LinearScale from './LinearScale'
import DiscreteScale from './DiscreteScale'

const prfx = 'react-compound-slider:'

const isBrowser =
  typeof window !== 'undefined' && typeof document !== 'undefined'

const noop = () => {}

const compare = b => (m, d, i) => m && b[i] === d

const equal = (a, b) => {
  return a === b || (a.length === b.length && a.reduce(compare(b), true))
}

class Slider extends PureComponent {
  constructor(props) {
    super(props)

    this.state = { values: [] }

    this.slider = null

    this.valueToPerc = new LinearScale()
    this.valueToStep = new DiscreteScale()
    this.pixelToStep = new DiscreteScale()

    this.onMouseMove = this.onMouseMove.bind(this)
    this.onTouchMove = this.onTouchMove.bind(this)
    this.submitUpdate = this.submitUpdate.bind(this)

    this.onMouseDown = this.onMouseDown.bind(this)
    this.onTouchStart = this.onTouchStart.bind(this)
    this.onStart = this.onStart.bind(this)

    this.onMouseUp = this.onMouseUp.bind(this)
    this.onTouchEnd = this.onTouchEnd.bind(this)
  }

  componentWillMount() {
    const { values, domain, step, reversed } = this.props

    this.updateRange(domain, step, reversed)
    this.setValues(values, reversed)
  }

  componentWillReceiveProps(next) {
    const { domain, step, reversed, values } = next
    const { props } = this

    if (
      domain[0] !== props.domain[0] ||
      domain[1] !== props.domain[1] ||
      step !== props.step ||
      reversed !== props.reversed
    ) {
      this.updateRange(domain, step, reversed)
      const remapped = this.reMapValues(reversed)
      if (values === undefined || values === props.values) {
        next.onChange(remapped)
        next.onUpdate(remapped)
      }
    }

    if (!equal(values, props.values)) {
      this.setValues(values, reversed)
    }
  }

  componentWillUnmount() {
    this.removeListeners()
  }

  removeListeners() {
    if (isBrowser) {
      document.removeEventListener('mousemove', this.onMouseMove)
      document.removeEventListener('mouseup', this.onMouseUp)
      document.removeEventListener('touchmove', this.onTouchMove)
      document.removeEventListener('touchend', this.onTouchEnd)
    }
  }

  reMapValues(reversed) {
    const { values } = this.state
    return this.setValues(values.map(d => d.val), reversed)
  }

  setValues(arr = [], reversed) {
    const values = arr
      .map(x => {
        const val = this.valueToStep.getValue(x)

        warning(
          x === val,
          `${prfx} Invalid value encountered. Changing ${x} to ${val}.`,
        )

        return val
      })
      .map((val, i) => ({ key: `$$-${i}`, val }))
      .sort(getSortByVal(reversed))

    this.setState(() => ({ values }))

    return values.map(d => d.val)
  }

  updateRange([min, max], step, reversed) {
    const range = getStepRange(min, max, step)

    this.valueToStep.setRange(range).setDomain([min - step / 2, max + step / 2])

    if (reversed === true) {
      this.valueToPerc.setDomain([min, max]).setRange([100, 0])
      range.reverse()
    } else {
      this.valueToPerc.setDomain([min, max]).setRange([0, 100])
    }

    this.pixelToStep.setRange(range)

    warning(
      max > min,
      `${prfx} Max must be greater than min (even if reversed). Max is ${max}. Min is ${min}.`,
    )

    warning(
      range.length <= 10001,
      `${prfx} Increase step value. Found ${range.length.toLocaleString()} values in range.`,
    )

    const last = range.length - 1

    warning(
      range[reversed ? last : 0] === min && range[reversed ? 0 : last] === max,
      `${prfx} The range is incorrectly calculated. Check domain (min, max) and step values.`,
    )
  }

  onMouseDown(e, handleID) {
    this.onStart(e, handleID, false)
  }

  onTouchStart(e, handleID) {
    if (isNotValidTouch(e)) {
      return
    }

    this.onStart(e, handleID, true)
  }

  onStart(e, handleID, isTouch) {
    const { state: { values }, props: { onSlideStart } } = this

    e.stopPropagation && e.stopPropagation()
    e.preventDefault && e.preventDefault()

    if (handleID) {
      const found = values.find(value => {
        return value.key === handleID
      })

      if (found) {
        this.active = handleID
        onSlideStart(values.map(d => d.val))
        isTouch ? this.addTouchEvents() : this.addMouseEvents()
      } else {
        this.active = null
      }
    } else {
      this.active = null
      this.handleRailAndTrackClicks(e, isTouch)
    }
  }

  handleRailAndTrackClicks(e, isTouch) {
    const { state: { values: curr }, props: { vertical, reversed } } = this
    const { slider } = this

    // double check the dimensions of the slider
    this.pixelToStep.setDomain(getSliderDomain(slider, vertical))

    // find the closest value (aka step) to the event location
    let updateValue

    if (isTouch) {
      updateValue = this.pixelToStep.getValue(getTouchPosition(vertical, e))
    } else {
      updateValue = this.pixelToStep.getValue(vertical ? e.clientY : e.pageX)
    }

    // find the closest handle key
    let updateKey = null
    let minDiff = Infinity

    for (let i = 0; i < curr.length; i++) {
      const { key, val } = curr[i]
      const diff = Math.abs(val - updateValue)

      if (diff < minDiff) {
        updateKey = key
        minDiff = diff
      }
    }

    // generate a "candidate" set of values - a suggestion of what to do
    const nextValues = getUpdatedValues(curr, updateKey, updateValue, reversed)

    // submit the candidate values
    this.submitUpdate(nextValues, true)
  }

  addMouseEvents() {
    if (isBrowser) {
      document.addEventListener('mousemove', this.onMouseMove)
      document.addEventListener('mouseup', this.onMouseUp)
    }
  }

  addTouchEvents() {
    if (isBrowser) {
      document.addEventListener('touchmove', this.onTouchMove)
      document.addEventListener('touchend', this.onTouchEnd)
    }
  }

  onMouseMove(e) {
    const { state: { values: curr }, props: { vertical, reversed } } = this
    const { active: updateKey, slider } = this

    // double check the dimensions of the slider
    this.pixelToStep.setDomain(getSliderDomain(slider, vertical))

    // find the closest value (aka step) to the event location
    const updateValue = this.pixelToStep.getValue(
      vertical ? e.clientY : e.pageX,
    )

    // generate a "candidate" set of values - a suggestion of what to do
    const nextValues = getUpdatedValues(curr, updateKey, updateValue, reversed)

    // submit the candidate values
    this.submitUpdate(nextValues)
  }

  onTouchMove(e) {
    const { state: { values: curr }, props: { vertical, reversed } } = this
    const { active: updateKey, slider } = this

    if (isNotValidTouch(e)) {
      return
    }

    // double check the dimensions of the slider
    this.pixelToStep.setDomain(getSliderDomain(slider, vertical))

    // find the closest value (aka step) to the event location
    const updateValue = this.pixelToStep.getValue(getTouchPosition(vertical, e))

    // generate a "candidate" set of values - a suggestion of what to do
    const nextValues = getUpdatedValues(curr, updateKey, updateValue, reversed)

    // submit the candidate values
    this.submitUpdate(nextValues)
  }

  submitUpdate(next, callOnChange) {
    const { mode, step, onUpdate, onChange, reversed } = this.props
    const { getValue } = this.valueToStep

    this.setState(({ values: curr }) => {
      let values

      // given the current values and a candidate set, decide what to do
      if (typeof mode === 'function') {
        values = mode(curr, next, step, reversed, getValue)
        warning(
          Array.isArray(values),
          `Custom mode function did not return an array.`,
        )
      } else {
        switch (mode) {
          case 1:
            values = mode1(curr, next)
            break
          case 2:
            values = mode2(curr, next)
            break
          case 3:
            values = mode3(curr, next, step, reversed, getValue)
            break
          default:
            values = next
            warning(false, `${prfx} Invalid mode value.`)
        }
      }

      onUpdate(values.map(d => d.val))

      if (callOnChange) {
        onChange(values.map(d => d.val))
      }

      return { values }
    })
  }

  onMouseUp() {
    const { state: { values }, props: { onChange, onSlideEnd } } = this
    this.active = null

    onChange(values.map(d => d.val))
    onSlideEnd(values.map(d => d.val))

    if (isBrowser) {
      document.removeEventListener('mousemove', this.onMouseMove)
      document.removeEventListener('mouseup', this.onMouseUp)
    }
  }

  onTouchEnd() {
    const { state: { values }, props: { onChange, onSlideEnd } } = this
    this.active = null

    onChange(values.map(d => d.val))
    onSlideEnd(values.map(d => d.val))

    if (isBrowser) {
      document.removeEventListener('touchmove', this.onTouchMove)
      document.removeEventListener('touchend', this.onTouchEnd)
    }
  }

  render() {
    const { state: { values }, props: { className, rootStyle } } = this

    const handles = values.map(({ key, val }) => {
      return { id: key, value: val, percent: this.valueToPerc.getValue(val) }
    })

    const children = React.Children.map(this.props.children, child => {
      if (
        child.type === Rail ||
        child.type === Ticks ||
        child.type === Tracks ||
        child.type === Handles
      ) {
        return React.cloneElement(child, {
          scale: this.valueToPerc,
          handles,
          emitMouse: this.onMouseDown,
          emitTouch: this.onTouchStart,
        })
      }

      return child
    })

    return (
      <div
        style={rootStyle || {}}
        className={className}
        ref={d => (this.slider = d)}
      >
        {children}
      </div>
    )
  }
}

Slider.propTypes = {
  /**
   * CSS class name applied to the root div of the slider.
   */
  className: PropTypes.string,
  /**
   * An object with any inline styles you want applied to the root div.
   */
  rootStyle: PropTypes.object,
  /**
   * Two element array of numbers providing the min and max values for the slider [min, max] e.g. [0, 100].
   * It does not matter if the slider is reversed on the screen, domain is always [min, max] with min < max.
   */
  domain: PropTypes.array,
  /**
   * An array of numbers. You can supply one for a value slider, two for a range slider or more to create n-handled sliders.
   * The values should correspond to valid step values in the domain.
   * The numbers will be forced into the domain if they are two small or large.
   */
  values: PropTypes.array,
  /**
   * The step value for the slider.
   */
  step: PropTypes.number,
  /**
   * The interaction mode. Value of 1 will allow handles to push each other.
   * Value of 2 will keep the sliders from crossing and separated by a step.
   * ADVANCED: You can also supply a function that will be passed the current values and the incoming update.
   * Your function should return what the state should be set as.
   */
  mode: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),
  /**
   * Set to true if the slider is displayed vertically to tell the slider to use the height to calculate positions.
   */
  vertical: PropTypes.bool,
  /**
   * Reverse the display of slider values.
   */
  reversed: PropTypes.bool,
  /**
   * Function triggered when the value of the slider has changed. This will recieve changes at the end of a slide as well as changes from clicks on rails and tracks. Receives values.
   */
  onChange: PropTypes.func,
  /**
   * Function called with the values at each update (caution: high-volume updates when dragging).
   */
  onUpdate: PropTypes.func,
  /**
   * Function triggered with ontouchstart or onmousedown on a handle. Receives values.
   */
  onSlideStart: PropTypes.func,
  /**
   * Function triggered on ontouchend or onmouseup on a handle. Receives values.
   */
  onSlideEnd: PropTypes.func,
  /**
   * Component children to render
   */
  children: PropTypes.any,
}

Slider.defaultProps = {
  mode: 1,
  step: 0.1,
  domain: [0, 100],
  vertical: false,
  reversed: false,
  onChange: noop,
  onUpdate: noop,
  onSlideStart: noop,
  onSlideEnd: noop,
}

export default Slider
<commit_msg>update props descriptions<commit_after>import React, { PureComponent } from 'react'
import warning from 'warning'
import PropTypes from 'prop-types'
import Rail from '../Rail'
import Ticks from '../Ticks'
import Tracks from '../Tracks'
import Handles from '../Handles'
import { mode1, mode2, mode3 } from './modes'
import {
  isNotValidTouch,
  getTouchPosition,
  getUpdatedValues,
  getSliderDomain,
  getStepRange,
  getSortByVal,
} from './utils'
import LinearScale from './LinearScale'
import DiscreteScale from './DiscreteScale'

const prfx = 'react-compound-slider:'

const isBrowser =
  typeof window !== 'undefined' && typeof document !== 'undefined'

const noop = () => {}

const compare = b => (m, d, i) => m && b[i] === d

const equal = (a, b) => {
  return a === b || (a.length === b.length && a.reduce(compare(b), true))
}

class Slider extends PureComponent {
  constructor(props) {
    super(props)

    this.state = { values: [] }

    this.slider = null

    this.valueToPerc = new LinearScale()
    this.valueToStep = new DiscreteScale()
    this.pixelToStep = new DiscreteScale()

    this.onMouseMove = this.onMouseMove.bind(this)
    this.onTouchMove = this.onTouchMove.bind(this)
    this.submitUpdate = this.submitUpdate.bind(this)

    this.onMouseDown = this.onMouseDown.bind(this)
    this.onTouchStart = this.onTouchStart.bind(this)
    this.onStart = this.onStart.bind(this)

    this.onMouseUp = this.onMouseUp.bind(this)
    this.onTouchEnd = this.onTouchEnd.bind(this)
  }

  componentWillMount() {
    const { values, domain, step, reversed } = this.props

    this.updateRange(domain, step, reversed)
    this.setValues(values, reversed)
  }

  componentWillReceiveProps(next) {
    const { domain, step, reversed, values } = next
    const { props } = this

    if (
      domain[0] !== props.domain[0] ||
      domain[1] !== props.domain[1] ||
      step !== props.step ||
      reversed !== props.reversed
    ) {
      this.updateRange(domain, step, reversed)
      const remapped = this.reMapValues(reversed)
      if (values === undefined || values === props.values) {
        next.onChange(remapped)
        next.onUpdate(remapped)
      }
    }

    if (!equal(values, props.values)) {
      this.setValues(values, reversed)
    }
  }

  componentWillUnmount() {
    this.removeListeners()
  }

  removeListeners() {
    if (isBrowser) {
      document.removeEventListener('mousemove', this.onMouseMove)
      document.removeEventListener('mouseup', this.onMouseUp)
      document.removeEventListener('touchmove', this.onTouchMove)
      document.removeEventListener('touchend', this.onTouchEnd)
    }
  }

  reMapValues(reversed) {
    const { values } = this.state
    return this.setValues(values.map(d => d.val), reversed)
  }

  setValues(arr = [], reversed) {
    const values = arr
      .map(x => {
        const val = this.valueToStep.getValue(x)

        warning(
          x === val,
          `${prfx} Invalid value encountered. Changing ${x} to ${val}.`,
        )

        return val
      })
      .map((val, i) => ({ key: `$$-${i}`, val }))
      .sort(getSortByVal(reversed))

    this.setState(() => ({ values }))

    return values.map(d => d.val)
  }

  updateRange([min, max], step, reversed) {
    const range = getStepRange(min, max, step)

    this.valueToStep.setRange(range).setDomain([min - step / 2, max + step / 2])

    if (reversed === true) {
      this.valueToPerc.setDomain([min, max]).setRange([100, 0])
      range.reverse()
    } else {
      this.valueToPerc.setDomain([min, max]).setRange([0, 100])
    }

    this.pixelToStep.setRange(range)

    warning(
      max > min,
      `${prfx} Max must be greater than min (even if reversed). Max is ${max}. Min is ${min}.`,
    )

    warning(
      range.length <= 10001,
      `${prfx} Increase step value. Found ${range.length.toLocaleString()} values in range.`,
    )

    const last = range.length - 1

    warning(
      range[reversed ? last : 0] === min && range[reversed ? 0 : last] === max,
      `${prfx} The range is incorrectly calculated. Check domain (min, max) and step values.`,
    )
  }

  onMouseDown(e, handleID) {
    this.onStart(e, handleID, false)
  }

  onTouchStart(e, handleID) {
    if (isNotValidTouch(e)) {
      return
    }

    this.onStart(e, handleID, true)
  }

  onStart(e, handleID, isTouch) {
    const { state: { values }, props: { onSlideStart } } = this

    e.stopPropagation && e.stopPropagation()
    e.preventDefault && e.preventDefault()

    if (handleID) {
      const found = values.find(value => {
        return value.key === handleID
      })

      if (found) {
        this.active = handleID
        onSlideStart(values.map(d => d.val))
        isTouch ? this.addTouchEvents() : this.addMouseEvents()
      } else {
        this.active = null
      }
    } else {
      this.active = null
      this.handleRailAndTrackClicks(e, isTouch)
    }
  }

  handleRailAndTrackClicks(e, isTouch) {
    const { state: { values: curr }, props: { vertical, reversed } } = this
    const { slider } = this

    // double check the dimensions of the slider
    this.pixelToStep.setDomain(getSliderDomain(slider, vertical))

    // find the closest value (aka step) to the event location
    let updateValue

    if (isTouch) {
      updateValue = this.pixelToStep.getValue(getTouchPosition(vertical, e))
    } else {
      updateValue = this.pixelToStep.getValue(vertical ? e.clientY : e.pageX)
    }

    // find the closest handle key
    let updateKey = null
    let minDiff = Infinity

    for (let i = 0; i < curr.length; i++) {
      const { key, val } = curr[i]
      const diff = Math.abs(val - updateValue)

      if (diff < minDiff) {
        updateKey = key
        minDiff = diff
      }
    }

    // generate a "candidate" set of values - a suggestion of what to do
    const nextValues = getUpdatedValues(curr, updateKey, updateValue, reversed)

    // submit the candidate values
    this.submitUpdate(nextValues, true)
  }

  addMouseEvents() {
    if (isBrowser) {
      document.addEventListener('mousemove', this.onMouseMove)
      document.addEventListener('mouseup', this.onMouseUp)
    }
  }

  addTouchEvents() {
    if (isBrowser) {
      document.addEventListener('touchmove', this.onTouchMove)
      document.addEventListener('touchend', this.onTouchEnd)
    }
  }

  onMouseMove(e) {
    const { state: { values: curr }, props: { vertical, reversed } } = this
    const { active: updateKey, slider } = this

    // double check the dimensions of the slider
    this.pixelToStep.setDomain(getSliderDomain(slider, vertical))

    // find the closest value (aka step) to the event location
    const updateValue = this.pixelToStep.getValue(
      vertical ? e.clientY : e.pageX,
    )

    // generate a "candidate" set of values - a suggestion of what to do
    const nextValues = getUpdatedValues(curr, updateKey, updateValue, reversed)

    // submit the candidate values
    this.submitUpdate(nextValues)
  }

  onTouchMove(e) {
    const { state: { values: curr }, props: { vertical, reversed } } = this
    const { active: updateKey, slider } = this

    if (isNotValidTouch(e)) {
      return
    }

    // double check the dimensions of the slider
    this.pixelToStep.setDomain(getSliderDomain(slider, vertical))

    // find the closest value (aka step) to the event location
    const updateValue = this.pixelToStep.getValue(getTouchPosition(vertical, e))

    // generate a "candidate" set of values - a suggestion of what to do
    const nextValues = getUpdatedValues(curr, updateKey, updateValue, reversed)

    // submit the candidate values
    this.submitUpdate(nextValues)
  }

  submitUpdate(next, callOnChange) {
    const { mode, step, onUpdate, onChange, reversed } = this.props
    const { getValue } = this.valueToStep

    this.setState(({ values: curr }) => {
      let values

      // given the current values and a candidate set, decide what to do
      if (typeof mode === 'function') {
        values = mode(curr, next, step, reversed, getValue)
        warning(
          Array.isArray(values),
          `Custom mode function did not return an array.`,
        )
      } else {
        switch (mode) {
          case 1:
            values = mode1(curr, next)
            break
          case 2:
            values = mode2(curr, next)
            break
          case 3:
            values = mode3(curr, next, step, reversed, getValue)
            break
          default:
            values = next
            warning(false, `${prfx} Invalid mode value.`)
        }
      }

      onUpdate(values.map(d => d.val))

      if (callOnChange) {
        onChange(values.map(d => d.val))
      }

      return { values }
    })
  }

  onMouseUp() {
    const { state: { values }, props: { onChange, onSlideEnd } } = this
    this.active = null

    onChange(values.map(d => d.val))
    onSlideEnd(values.map(d => d.val))

    if (isBrowser) {
      document.removeEventListener('mousemove', this.onMouseMove)
      document.removeEventListener('mouseup', this.onMouseUp)
    }
  }

  onTouchEnd() {
    const { state: { values }, props: { onChange, onSlideEnd } } = this
    this.active = null

    onChange(values.map(d => d.val))
    onSlideEnd(values.map(d => d.val))

    if (isBrowser) {
      document.removeEventListener('touchmove', this.onTouchMove)
      document.removeEventListener('touchend', this.onTouchEnd)
    }
  }

  render() {
    const { state: { values }, props: { className, rootStyle } } = this

    const handles = values.map(({ key, val }) => {
      return { id: key, value: val, percent: this.valueToPerc.getValue(val) }
    })

    const children = React.Children.map(this.props.children, child => {
      if (
        child.type === Rail ||
        child.type === Ticks ||
        child.type === Tracks ||
        child.type === Handles
      ) {
        return React.cloneElement(child, {
          scale: this.valueToPerc,
          handles,
          emitMouse: this.onMouseDown,
          emitTouch: this.onTouchStart,
        })
      }

      return child
    })

    return (
      <div
        style={rootStyle || {}}
        className={className}
        ref={d => (this.slider = d)}
      >
        {children}
      </div>
    )
  }
}

Slider.propTypes = {
  /**
   * CSS class name applied to the root div of the slider.
   */
  className: PropTypes.string,
  /**
   * An object with any inline styles you want applied to the root div.
   */
  rootStyle: PropTypes.object,
  /**
   * Two element array of numbers providing the min and max values for the slider [min, max] e.g. [0, 100].
   * It does not matter if the slider is reversed on the screen, domain is always [min, max] with min < max.
   */
  domain: PropTypes.array,
  /**
   * An array of numbers. You can supply one for a value slider, two for a range slider or more to create n-handled sliders.
   * The values should correspond to valid step values in the domain.
   * The numbers will be forced into the domain if they are two small or large.
   */
  values: PropTypes.array,
  /**
   * The step value for the slider.
   */
  step: PropTypes.number,
  /**
   * The interaction mode. Value of 1 will allow handles to cross each other.
   * Value of 2 will keep the sliders from crossing and separated by a step.
   * Value of 3 will make the handles pushable and keep them a step apart.
   * ADVANCED: You can also supply a function that will be passed the current values and the incoming update.
   * Your function should return what the state should be set as.
   */
  mode: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),
  /**
   * Set to true if the slider is displayed vertically to tell the slider to use the height to calculate positions.
   */
  vertical: PropTypes.bool,
  /**
   * Reverse the display of slider values.
   */
  reversed: PropTypes.bool,
  /**
   * Function triggered when the value of the slider has changed. This will recieve changes at the end of a slide as well as changes from clicks on rails and tracks. Receives values.
   */
  onChange: PropTypes.func,
  /**
   * Function called with the values at each update (caution: high-volume updates when dragging). Receives values.
   */
  onUpdate: PropTypes.func,
  /**
   * Function triggered with ontouchstart or onmousedown on a handle. Receives values.
   */
  onSlideStart: PropTypes.func,
  /**
   * Function triggered on ontouchend or onmouseup on a handle. Receives values.
   */
  onSlideEnd: PropTypes.func,
  /**
   * Component children to render
   */
  children: PropTypes.any,
}

Slider.defaultProps = {
  mode: 1,
  step: 0.1,
  domain: [0, 100],
  vertical: false,
  reversed: false,
  onChange: noop,
  onUpdate: noop,
  onSlideStart: noop,
  onSlideEnd: noop,
}

export default Slider
<|endoftext|>


<commit_before>import React, { PropTypes, Component } from 'react';
import * as themes from 'redux-devtools-themes';
import { ActionCreators } from 'redux-devtools';

import reducer from './reducers';
import Slider from './Slider';
import SliderButton from './SliderButton';

const { reset, jumpToState } = ActionCreators;

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

  static propTypes = {
    dispatch: PropTypes.func,
    computedStates: PropTypes.array,
    stagedActionIds: PropTypes.array,
    actionsById: PropTypes.object,
    currentStateIndex: PropTypes.number,
    monitorState: PropTypes.shape({
      initialScrollTop: PropTypes.number
    }),
    preserveScrollTop: PropTypes.bool,
    stagedActions: PropTypes.array,
    select: PropTypes.func.isRequired,
    theme: PropTypes.oneOfType([
      PropTypes.object,
      PropTypes.string
    ])
  };

  static defaultProps = {
    select: (state) => state,
    theme: 'nicinabox',
    preserveScrollTop: true
  };

  constructor(props) {
    super(props);

    if (typeof window !== 'undefined') {
      window.addEventListener('keydown', this.handleKeyPress);
    }

    this.state = {
      timer: undefined,
      replaySpeed: '1x'
    };
  }

  setUpTheme = () => {
    let theme;
    if (typeof this.props.theme === 'string') {
      if (typeof themes[this.props.theme] !== 'undefined') {
        theme = themes[this.props.theme];
      } else {
        theme = themes.nicinabox;
      }
    } else {
      theme = this.props.theme;
    }

    return theme;
  }

  handleReset = () => {
    this.props.dispatch(reset());
  }

  handleKeyPress = (event) => {
    if (event.ctrlKey && event.keyCode === 74) { // ctrl+j
      event.preventDefault();

      if (this.state.timer) {
        return this.pauseReplay();
      }

      if (this.state.replaySpeed === 'Live') {
        this.startRealtimeReplay();
      } else {
        this.startReplay();
      }
    } else if (event.ctrlKey && event.keyCode === 219) { // ctrl+[
      event.preventDefault();
      this.stepLeft();
    } else if (event.ctrlKey && event.keyCode === 221) { // ctrl+]
      event.preventDefault();
      this.stepRight();
    }
  }

  handleSliderChange = (value) => {
    if (this.state.timer) {
      this.pauseReplay();
    }

    this.props.dispatch(jumpToState(value));
  }

  startReplay = () => {
    const { computedStates, currentStateIndex, dispatch } = this.props;

    if (computedStates.length < 2) {
      return;
    }
    const speed = this.state.replaySpeed === '1x' ? 500 : 200;

    let stateIndex;
    if (currentStateIndex === computedStates.length - 1) {
      dispatch(jumpToState(0));
      stateIndex = 0;
    } else if (currentStateIndex === computedStates.length - 2) {
      dispatch(jumpToState(currentStateIndex + 1));
      return;
    } else {
      stateIndex = currentStateIndex + 1;
      dispatch(jumpToState(currentStateIndex + 1));
    }

    let counter = stateIndex;
    const timer = setInterval(() => {
      if (counter + 1 <= computedStates.length - 1) {
        dispatch(jumpToState(counter + 1));
      }
      counter++;

      if (counter >= computedStates.length - 1) {
        clearInterval(this.state.timer);
        return this.setState({
          timer: undefined
        });
      }
    }, speed);

    this.setState({ timer });
  }

  startRealtimeReplay = () => {
    if (this.props.computedStates.length < 2) {
      return;
    }

    if (this.props.currentStateIndex === this.props.computedStates.length - 1) {
      this.props.dispatch(jumpToState(0));

      this.loop(0);
    } else {
      this.loop(this.props.currentStateIndex);
    }
  }

  loop = (index) => {
    let currentTimestamp = Date.now();
    let timestampDiff = this.getLatestTimestampDiff(index);

    const aLoop = () => {
      const replayDiff = Date.now() - currentTimestamp;
      if (replayDiff >= timestampDiff) {
        this.props.dispatch(jumpToState(this.props.currentStateIndex + 1));

        if (this.props.currentStateIndex >= this.props.computedStates.length - 1) {
          return this.pauseReplay();
        }

        timestampDiff = this.getLatestTimestampDiff(this.props.currentStateIndex);
        currentTimestamp = Date.now();

        this.setState({
          timer: requestAnimationFrame(aLoop)
        });
      } else {
        this.setState({
          timer: requestAnimationFrame(aLoop)
        });
      }
    };

    if (index !== this.props.computedStates.length - 1) {
      this.setState({
        timer: requestAnimationFrame(aLoop)
      });
    }
  }

  getLatestTimestampDiff = (index) => {
    return this.getTimestampOfStateIndex(index + 1) - this.getTimestampOfStateIndex(index);
  }

  getTimestampOfStateIndex = (stateIndex) => {
    const id = this.props.stagedActionIds[stateIndex];
    return this.props.actionsById[id].timestamp;
  }

  pauseReplay = (cb) => {
    if (this.state.timer) {
      cancelAnimationFrame(this.state.timer);
      clearInterval(this.state.timer);
      this.setState({
        timer: undefined
      }, () => {
        if (typeof cb === 'function') {
          cb();
        }
      });
    }
  }

  stepLeft = () => {
    this.pauseReplay();

    if (this.props.currentStateIndex !== 0) {
      this.props.dispatch(jumpToState(this.props.currentStateIndex - 1));
    }
  }

  stepRight = () => {
    this.pauseReplay();

    if (this.props.currentStateIndex !== this.props.computedStates.length - 1) {
      this.props.dispatch(jumpToState(this.props.currentStateIndex + 1));
    }
  }

  changeReplaySpeed = () => {
    let replaySpeed;
    switch (this.state.replaySpeed) {
      case '1x':
        replaySpeed = '2x';
        break;
      case '2x':
        replaySpeed = 'Live';
        break;
      default:
        replaySpeed = '1x';
    }

    this.setState({ replaySpeed });

    if (this.state.timer) {
      this.pauseReplay(() => {
        if (replaySpeed === 'Live') {
          this.startRealtimeReplay();
        } else {
          this.startReplay();
        }
      });
    }
  }

  containerStyle = (theme) => {
    return {
      height: '85%',
      fontFamily: 'monospace',
      position: 'relative',
      padding: '0.7rem',
      display: 'flex',
      justifyContent: 'space-between',
      alignItems: 'center',
      background: theme.base00,
      WebkitUserSelect: 'none', MozUserSelect: 'none', MsUserSelect: 'none'
    };
  }

  render() {
    const { currentStateIndex, computedStates } = this.props;
    const { replaySpeed } = this.state;
    const theme = this.setUpTheme();

    const onPlayClick = replaySpeed === 'Live' ? this.startRealtimeReplay : this.startReplay;
    const playPause = this.state.timer ?
      <SliderButton theme={theme} type='pause' onClick={this.pauseReplay} /> :
      <SliderButton theme={theme} type='play' onClick={onPlayClick} />;

    return (

        {playPause}

          <Slider
            min={0}
            max={computedStates.length - 1}
            value={currentStateIndex}
            onChange={this.handleSliderChange}
            theme={theme}
          />

        <SliderButton theme={theme} type='stepLeft' onClick={this.stepLeft} />
        <SliderButton theme={theme} type='stepRight' onClick={this.stepRight} />
        <SliderButton theme={theme} type='playBackSpeed' replaySpeed={replaySpeed} onClick={this.changeReplaySpeed} />

          Reset

    );
  }
}
<commit_msg>Adjust slider dock height (white bottom stripe)<commit_after>import React, { PropTypes, Component } from 'react';
import * as themes from 'redux-devtools-themes';
import { ActionCreators } from 'redux-devtools';

import reducer from './reducers';
import Slider from './Slider';
import SliderButton from './SliderButton';

const { reset, jumpToState } = ActionCreators;

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

  static propTypes = {
    dispatch: PropTypes.func,
    computedStates: PropTypes.array,
    stagedActionIds: PropTypes.array,
    actionsById: PropTypes.object,
    currentStateIndex: PropTypes.number,
    monitorState: PropTypes.shape({
      initialScrollTop: PropTypes.number
    }),
    preserveScrollTop: PropTypes.bool,
    stagedActions: PropTypes.array,
    select: PropTypes.func.isRequired,
    theme: PropTypes.oneOfType([
      PropTypes.object,
      PropTypes.string
    ])
  };

  static defaultProps = {
    select: (state) => state,
    theme: 'nicinabox',
    preserveScrollTop: true
  };

  constructor(props) {
    super(props);

    if (typeof window !== 'undefined') {
      window.addEventListener('keydown', this.handleKeyPress);
    }

    this.state = {
      timer: undefined,
      replaySpeed: '1x'
    };
  }

  setUpTheme = () => {
    let theme;
    if (typeof this.props.theme === 'string') {
      if (typeof themes[this.props.theme] !== 'undefined') {
        theme = themes[this.props.theme];
      } else {
        theme = themes.nicinabox;
      }
    } else {
      theme = this.props.theme;
    }

    return theme;
  }

  handleReset = () => {
    this.props.dispatch(reset());
  }

  handleKeyPress = (event) => {
    if (event.ctrlKey && event.keyCode === 74) { // ctrl+j
      event.preventDefault();

      if (this.state.timer) {
        return this.pauseReplay();
      }

      if (this.state.replaySpeed === 'Live') {
        this.startRealtimeReplay();
      } else {
        this.startReplay();
      }
    } else if (event.ctrlKey && event.keyCode === 219) { // ctrl+[
      event.preventDefault();
      this.stepLeft();
    } else if (event.ctrlKey && event.keyCode === 221) { // ctrl+]
      event.preventDefault();
      this.stepRight();
    }
  }

  handleSliderChange = (value) => {
    if (this.state.timer) {
      this.pauseReplay();
    }

    this.props.dispatch(jumpToState(value));
  }

  startReplay = () => {
    const { computedStates, currentStateIndex, dispatch } = this.props;

    if (computedStates.length < 2) {
      return;
    }
    const speed = this.state.replaySpeed === '1x' ? 500 : 200;

    let stateIndex;
    if (currentStateIndex === computedStates.length - 1) {
      dispatch(jumpToState(0));
      stateIndex = 0;
    } else if (currentStateIndex === computedStates.length - 2) {
      dispatch(jumpToState(currentStateIndex + 1));
      return;
    } else {
      stateIndex = currentStateIndex + 1;
      dispatch(jumpToState(currentStateIndex + 1));
    }

    let counter = stateIndex;
    const timer = setInterval(() => {
      if (counter + 1 <= computedStates.length - 1) {
        dispatch(jumpToState(counter + 1));
      }
      counter++;

      if (counter >= computedStates.length - 1) {
        clearInterval(this.state.timer);
        return this.setState({
          timer: undefined
        });
      }
    }, speed);

    this.setState({ timer });
  }

  startRealtimeReplay = () => {
    if (this.props.computedStates.length < 2) {
      return;
    }

    if (this.props.currentStateIndex === this.props.computedStates.length - 1) {
      this.props.dispatch(jumpToState(0));

      this.loop(0);
    } else {
      this.loop(this.props.currentStateIndex);
    }
  }

  loop = (index) => {
    let currentTimestamp = Date.now();
    let timestampDiff = this.getLatestTimestampDiff(index);

    const aLoop = () => {
      const replayDiff = Date.now() - currentTimestamp;
      if (replayDiff >= timestampDiff) {
        this.props.dispatch(jumpToState(this.props.currentStateIndex + 1));

        if (this.props.currentStateIndex >= this.props.computedStates.length - 1) {
          return this.pauseReplay();
        }

        timestampDiff = this.getLatestTimestampDiff(this.props.currentStateIndex);
        currentTimestamp = Date.now();

        this.setState({
          timer: requestAnimationFrame(aLoop)
        });
      } else {
        this.setState({
          timer: requestAnimationFrame(aLoop)
        });
      }
    };

    if (index !== this.props.computedStates.length - 1) {
      this.setState({
        timer: requestAnimationFrame(aLoop)
      });
    }
  }

  getLatestTimestampDiff = (index) => {
    return this.getTimestampOfStateIndex(index + 1) - this.getTimestampOfStateIndex(index);
  }

  getTimestampOfStateIndex = (stateIndex) => {
    const id = this.props.stagedActionIds[stateIndex];
    return this.props.actionsById[id].timestamp;
  }

  pauseReplay = (cb) => {
    if (this.state.timer) {
      cancelAnimationFrame(this.state.timer);
      clearInterval(this.state.timer);
      this.setState({
        timer: undefined
      }, () => {
        if (typeof cb === 'function') {
          cb();
        }
      });
    }
  }

  stepLeft = () => {
    this.pauseReplay();

    if (this.props.currentStateIndex !== 0) {
      this.props.dispatch(jumpToState(this.props.currentStateIndex - 1));
    }
  }

  stepRight = () => {
    this.pauseReplay();

    if (this.props.currentStateIndex !== this.props.computedStates.length - 1) {
      this.props.dispatch(jumpToState(this.props.currentStateIndex + 1));
    }
  }

  changeReplaySpeed = () => {
    let replaySpeed;
    switch (this.state.replaySpeed) {
      case '1x':
        replaySpeed = '2x';
        break;
      case '2x':
        replaySpeed = 'Live';
        break;
      default:
        replaySpeed = '1x';
    }

    this.setState({ replaySpeed });

    if (this.state.timer) {
      this.pauseReplay(() => {
        if (replaySpeed === 'Live') {
          this.startRealtimeReplay();
        } else {
          this.startReplay();
        }
      });
    }
  }

  containerStyle = (theme) => {
    return {
      height: '100%',
      fontFamily: 'monospace',
      position: 'relative',
      padding: '0 0.7rem 0.7rem 0.7rem',
      display: 'flex',
      justifyContent: 'space-between',
      alignItems: 'center',
      background: theme.base00,
      WebkitUserSelect: 'none', MozUserSelect: 'none', MsUserSelect: 'none'
    };
  }

  render() {
    const { currentStateIndex, computedStates } = this.props;
    const { replaySpeed } = this.state;
    const theme = this.setUpTheme();

    const onPlayClick = replaySpeed === 'Live' ? this.startRealtimeReplay : this.startReplay;
    const playPause = this.state.timer ?
      <SliderButton theme={theme} type='pause' onClick={this.pauseReplay} /> :
      <SliderButton theme={theme} type='play' onClick={onPlayClick} />;

    return (

        {playPause}

          <Slider
            min={0}
            max={computedStates.length - 1}
            value={currentStateIndex}
            onChange={this.handleSliderChange}
            theme={theme}
          />

        <SliderButton theme={theme} type='stepLeft' onClick={this.stepLeft} />
        <SliderButton theme={theme} type='stepRight' onClick={this.stepRight} />
        <SliderButton theme={theme} type='playBackSpeed' replaySpeed={replaySpeed} onClick={this.changeReplaySpeed} />

          Reset

    );
  }
}
<|endoftext|>


<commit_before>// Will return the first turthy value.
// Handy for setting default values.
module.exports = function firstof () {
    // Use everything, but the options object
    var valuesLength = arguments.length - 1;

    for (i = 0; valuesLength; i++) {
        var value = arguments[i];
        if (value) return value;
    }

    return '';
};
<commit_msg>Fix issue w/ firstof helper where hbs options object would be included in check<commit_after>// Will return the first turthy value.
// Handy for setting default values.
module.exports = function firstof () {
    // Use everything, but the options object
    var valuesLength = arguments.length - 1;

    for (var i = 0; i < valuesLength; i++) {
        var value = arguments[i];
        if (value) return value;
    }

    return '';
};
<|endoftext|>


<commit_before>console.log("%c p5.geolocation Loaded ", "color:pink; background:black; ");

/**
* Check if location services are available
*
* Returns true if geolocation is available
*
* @method locationCheck
* @return {boolean} true if geolocation is available
*/
p5.prototype.geoCheck = function(){
  if (navigator.geolocation) {
    return true;
  }else{
    return false;
  }

}

/**
* Get User's Current Position
*
* Gets the users current position. Can be used in preload(), or as a callback.
*
* @method getCurrentPosition
* @param  {function} a callback to handle the current position data
* @param  {function} a callback to handle an error
* @return {object} an object containing the users position data
*/
p5.prototype.getCurrentPosition = function(callback, errorCallback) {

  var ret = {};

  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(success, geoError);
  }else{
    geoError("geolocation not available");
  };

    function geoError(message){
      console.log(message.message);
      ret.error = message.message;
       if(typeof errorCallback == 'function'){ errorCallback(message.message) };
    }

    function success(position){
      // console.log(position);

      //get the entire position object....
      // //see the p5.js github libraries wiki page for more info on what is going on here.
      // for(var k in position){
      //   if (typeof position[k] == 'object'){
      //     ret[k] = {};
      //     for(var x in position[k]){
      //       ret[k][x] = position[k][x];
      //     }
      //   } else {
      //     ret[k] = position[k];
      //   }
      // }

      //get only the coords part of the position object
          for(var x in position.coords){
            ret[x] = position.coords[x];
          }

      if(typeof callback == 'function'){ callback(position.coords) };
    }

    return ret;
};

//add the get Current position to the preload stack.
p5.prototype.registerPreloadMethod('getCurrentPosition');

/**
* Get User's Current Position on an interval
*
* Gets the users current position on an interval. Can be useful if watchPosition is not responsive enough. This can be a resource hog (read:battery) as it is calling the getPosition at the rate of your interval. Set it long for less intense usage.
*
* @method getCurrentPosition
* @param  {function} a callback to handle the current position data
* @param  {function} an interval in MS
* @param  {function} a callback to handle an error
*/
p5.prototype._intervalPosition = null;
p5.prototype.intervalCurrentPosition = function(callback, interval,  errorCallback){

  var gogogadget = 5000;
  gogogadget = interval;

  if (navigator.geolocation) {

    _intervalPosition = setInterval(function(){

      console.log("pos");
      navigator.geolocation.getCurrentPosition(success, geoError);

    }, gogogadget)

  }else{
    geoError("geolocation not available");
  };

    function geoError(message){
      console.log(message.message);
       if(typeof errorCallback == 'function'){ errorCallback(message.message) };
    }

    function success(position){
      if(typeof callback == 'function'){ callback(position.coords) };
    }
}

/**
* Clear interval Position
*
* clears the current intervalCurrentPosition()
*
* @method clearIntervalPos()
*/
p5.prototype.clearIntervalPos = function(){
  window.clearInterval(_intervalPosition);
}

/**
* Watch User's Current Position
*
* Watches the users current position
*
* @method watchPosition
* @param  {function} a callback to handle the current position data
* @param  {function} a callback to handle an error
* @param  {object} an positionOptions object: enableHighAccuracy, maximumAge, timeout
*/
p5.prototype._posWatch = null;
p5.prototype.watchPosition = function(callback, errorCallback, options){

  if (navigator.geolocation) {
    _posWatch = navigator.geolocation.watchPosition(success, geoError, options);
  }else{
    geoError("geolocation not available");
  };

  function geoError(message){
      console.log("watch Postition Error" + message);
       if(typeof errorCallback == 'function'){ errorCallback(message.message) };
    }

  function success(position){
        if(typeof callback == 'function'){ callback(position.coords) };
        // console.log(_posWatch);
  }

}

/**
* Clear the watchPosition
*
* clears the current watchPosition
*
* @method clearWatch
*/
p5.prototype.clearWatch = function(){
  navigator.geolocation.clearWatch( _posWatch );
}

/**
* Calculate the Distance between two points
*
*
* @method watchPosition
* @param  {float} latitude of the first point
* @param  {float} longitude of the first point
* @param  {float} latitude of the second point
* @param  {float} longitude of the second point
* @param  {string} units to use: 'km' or 'mi', 'mi' is default if left blank
* @return {float} the distance between the two points in the specified units, miles is default
*/

// http://www.movable-type.co.uk/scripts/latlong.html
// Used Under MIT License
p5.prototype.calcGeoDistance = function(lat1, lon1, lat2, lon2, units) {
  if(units == 'km'){
     var R = 6371; //earth radius in KM
  }else{
    var R = 3959; // earth radius in Miles (default)
  }
    var dLat = (lat2-lat1) * (Math.PI / 180);
    var dLon = (lon2-lon1) * (Math.PI / 180);
    var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
            Math.cos(lat1 * (Math.PI / 180)) * Math.cos(lat2 * (Math.PI / 180)) *
            Math.sin(dLon/2) * Math.sin(dLon/2);
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
    var d = R * c;
    return d;
  }

/**
* Calculate if a Location is inside Polygon
*
*
* @method watchPosition
* @param  {float} Array of Objects with lat: and lon:
* @param  {float} Object with lat and long of my location
* @return {boolean} true if geolocation is within polygon
*/

// http://jsfromhell.com/math/is-point-in-poly
// Adapted from: [http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html]
// Used Under MIT License
p5.prototype.isLocationInPolygon = function(poly, pt){
    for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
        ((poly[i].lon <= pt.lon && pt.lon < poly[j].lon) || (poly[j].lon <= pt.lon && pt.lon < poly[i].lon))
        && (pt.lat < (poly[j].lat - poly[i].lat) * (pt.lon - poly[i].lon) / (poly[j].lon - poly[i].lon) + poly[i].lat)
        && (c = !c);
    return c;
}

/**
* Create a new geoFenceCircle
*
* Watches the users current position and checks to see if they are witihn a set radius of a specified point.
*
* @method watchPosition
* @param  {float} latitude of the first point
* @param  {float} longitude of the first point
* @param  {float} distance from the point to trigger the insideCallback
* @param  {function} a callback to fire when the user is inside the geoFenceCircle
* @param  {function} a callback to fire when the user is outside the geoFenceCircle
* @param  {string} units to use: 'km' or 'mi', 'mi' is default if left blank
* @param  {object} an positionOptions object: enableHighAccuracy, maximumAge, timeout
*/
p5.prototype.geoFenceCircle = function(lat, lon, fence, insideCallback, outsideCallback, units, options){

  this.lat = lat;
  this.lon = lon;
  this.fence = fence;
  this.units = units; //this should work since calcGeoDistance defaults to miles.
  this.distance = 0.0;
  this.insideCallback = insideCallback;
  this.outsideCallback = outsideCallback;
  this.insideFence = false;
  this.options = options;
  this.id = '';

    this.geoError = function(message){
      console.log("geoFenceCircle Error :" + message);
    }

    this.success = function(position){
      this.distance = calcGeoDistance(this.lat,this.lon, position.coords.latitude, position.coords.longitude, this.units);

      if(this.distance <= this.fence){
          if(typeof this.insideCallback == 'function'){ this.insideCallback(position.coords) };
          this.insideFence = true;
      }else{
        if(typeof this.outsideCallback == 'function'){ this.outsideCallback(position.coords) };
        this.insideFence = false;
      }
    }

    this.clear = function() {
      if (this.id) {
        navigator.geolocation.clearWatch(this.id);
        this.id = '';
      }
    }

    if (navigator.geolocation) {
      // bind the callbacks to the geoFenceCircle 'this' so we can access, this.lat, this.lon, etc..
      this.id = navigator.geolocation.watchPosition(this.success.bind(this), this.geoError.bind(this), this.options);
    }else{
      geoError("geolocation not available");
    };
}

/**
* Create a new geoFencePolygon
*
* Watches the users current position and checks to see if they are witihn a set radius of a specified point.
*
* @method watchPosition
* @param  {float} latitude of the first point
* @param  {float} longitude of the first point
* @param  {float} distance from the point to trigger the insideCallback
* @param  {function} a callback to fire when the user is inside the geoFenceCircle
* @param  {function} a callback to fire when the user is outside the geoFenceCircle
* @param  {string} units to use: 'km' or 'mi', 'mi' is default if left blank
* @param  {object} an positionOptions object: enableHighAccuracy, maximumAge, timeout
*/

/*var points = [
    {x: 34.076089, y: -118.440915},
    {x: 34.076095, y: -118.440605},
    {x: 34.075906, y: -118.440597},
    {x: 34.075891, y: -118.440932},
];*/
p5.prototype.geoFencePolygon = function( ArrayOfObjectsWithLatLong, insideCallback, outsideCallback, units, options){

  this.ArrayOfObjectsWithLatLong = ArrayOfObjectsWithLatLong;
  this.units = units; //this should work since calcGeoDistance defaults to miles.
  this.insideCallback = insideCallback;
  this.outsideCallback = outsideCallback;
  this.insideFence = false;
  this.options = options;
  this.id = '';

    this.geoError = function(message){
      console.log("geoFencePolygon Error :" + message);
    }

    this.success = function(position){
      this.insideFence = isLocationInPolygon(this.ArrayOfObjectsWithLatLong, { lat:position.coords.latitude, lon: position.coords.longitude });

      if(this.insideFence == true){
          if(typeof this.insideCallback == 'function'){ this.insideCallback(position.coords) };
      }else{
        if(typeof this.outsideCallback == 'function'){ this.outsideCallback(position.coords) };
      }
    }

    this.clear = function() {
      if (this.id) {
        navigator.geolocation.clearWatch(this.id);
        this.id = '';
      }
    }

    if (navigator.geolocation) {
      // bind the callbacks to the geoFenceCircle 'this' so we can access, this.lat, this.lon, etc..
      this.id = navigator.geolocation.watchPosition(this.success.bind(this), this.geoError.bind(this), this.options);
    }else{
      geoError("geolocation not available");
    };
}
<commit_msg>Updated preload register according to P5 0.7.2<commit_after>console.log("%c p5.geolocation Loaded ", "color:pink; background:black; ");

/**
* Check if location services are available
*
* Returns true if geolocation is available
*
* @method locationCheck
* @return {boolean} true if geolocation is available
*/
p5.prototype.geoCheck = function(){
  if (navigator.geolocation) {
    return true;
  }else{
    return false;
  }

}

/**
* Get User's Current Position
*
* Gets the users current position. Can be used in preload(), or as a callback.
*
* @method getCurrentPosition
* @param  {function} a callback to handle the current position data
* @param  {function} a callback to handle an error
* @return {object} an object containing the users position data
*/
p5.prototype.getCurrentPosition = function(callback, errorCallback) {

  var ret = {};

  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(success, geoError);
  }else{
    geoError("geolocation not available");
  };

    function geoError(message){
      console.log(message.message);
      ret.error = message.message;
       if(typeof errorCallback == 'function'){ errorCallback(message.message) };
    }

    function success(position){
      // console.log(position);

      //get the entire position object....
      // //see the p5.js github libraries wiki page for more info on what is going on here.
      // for(var k in position){
      //   if (typeof position[k] == 'object'){
      //     ret[k] = {};
      //     for(var x in position[k]){
      //       ret[k][x] = position[k][x];
      //     }
      //   } else {
      //     ret[k] = position[k];
      //   }
      // }

      //get only the coords part of the position object
          for(var x in position.coords){
            ret[x] = position.coords[x];
          }

      if(typeof callback == 'function'){ callback(position.coords) };
    }

    return ret;
};

//add the get Current position to the preload stack.
p5.prototype.registerPreloadMethod('getCurrentPosition', p5.prototype);

/**
* Get User's Current Position on an interval
*
* Gets the users current position on an interval. Can be useful if watchPosition is not responsive enough. This can be a resource hog (read:battery) as it is calling the getPosition at the rate of your interval. Set it long for less intense usage.
*
* @method getCurrentPosition
* @param  {function} a callback to handle the current position data
* @param  {function} an interval in MS
* @param  {function} a callback to handle an error
*/
p5.prototype._intervalPosition = null;
p5.prototype.intervalCurrentPosition = function(callback, interval,  errorCallback){

  var gogogadget = 5000;
  gogogadget = interval;

  if (navigator.geolocation) {

    _intervalPosition = setInterval(function(){

      console.log("pos");
      navigator.geolocation.getCurrentPosition(success, geoError);

    }, gogogadget)

  }else{
    geoError("geolocation not available");
  };

    function geoError(message){
      console.log(message.message);
       if(typeof errorCallback == 'function'){ errorCallback(message.message) };
    }

    function success(position){
      if(typeof callback == 'function'){ callback(position.coords) };
    }
}

/**
* Clear interval Position
*
* clears the current intervalCurrentPosition()
*
* @method clearIntervalPos()
*/
p5.prototype.clearIntervalPos = function(){
  window.clearInterval(_intervalPosition);
}

/**
* Watch User's Current Position
*
* Watches the users current position
*
* @method watchPosition
* @param  {function} a callback to handle the current position data
* @param  {function} a callback to handle an error
* @param  {object} an positionOptions object: enableHighAccuracy, maximumAge, timeout
*/
p5.prototype._posWatch = null;
p5.prototype.watchPosition = function(callback, errorCallback, options){

  if (navigator.geolocation) {
    _posWatch = navigator.geolocation.watchPosition(success, geoError, options);
  }else{
    geoError("geolocation not available");
  };

  function geoError(message){
      console.log("watch Postition Error" + message);
       if(typeof errorCallback == 'function'){ errorCallback(message.message) };
    }

  function success(position){
        if(typeof callback == 'function'){ callback(position.coords) };
        // console.log(_posWatch);
  }

}

/**
* Clear the watchPosition
*
* clears the current watchPosition
*
* @method clearWatch
*/
p5.prototype.clearWatch = function(){
  navigator.geolocation.clearWatch( _posWatch );
}

/**
* Calculate the Distance between two points
*
*
* @method watchPosition
* @param  {float} latitude of the first point
* @param  {float} longitude of the first point
* @param  {float} latitude of the second point
* @param  {float} longitude of the second point
* @param  {string} units to use: 'km' or 'mi', 'mi' is default if left blank
* @return {float} the distance between the two points in the specified units, miles is default
*/

// http://www.movable-type.co.uk/scripts/latlong.html
// Used Under MIT License
p5.prototype.calcGeoDistance = function(lat1, lon1, lat2, lon2, units) {
  if(units == 'km'){
     var R = 6371; //earth radius in KM
  }else{
    var R = 3959; // earth radius in Miles (default)
  }
    var dLat = (lat2-lat1) * (Math.PI / 180);
    var dLon = (lon2-lon1) * (Math.PI / 180);
    var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
            Math.cos(lat1 * (Math.PI / 180)) * Math.cos(lat2 * (Math.PI / 180)) *
            Math.sin(dLon/2) * Math.sin(dLon/2);
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
    var d = R * c;
    return d;
  }

/**
* Calculate if a Location is inside Polygon
*
*
* @method watchPosition
* @param  {float} Array of Objects with lat: and lon:
* @param  {float} Object with lat and long of my location
* @return {boolean} true if geolocation is within polygon
*/

// http://jsfromhell.com/math/is-point-in-poly
// Adapted from: [http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html]
// Used Under MIT License
p5.prototype.isLocationInPolygon = function(poly, pt){
    for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
        ((poly[i].lon <= pt.lon && pt.lon < poly[j].lon) || (poly[j].lon <= pt.lon && pt.lon < poly[i].lon))
        && (pt.lat < (poly[j].lat - poly[i].lat) * (pt.lon - poly[i].lon) / (poly[j].lon - poly[i].lon) + poly[i].lat)
        && (c = !c);
    return c;
}

/**
* Create a new geoFenceCircle
*
* Watches the users current position and checks to see if they are witihn a set radius of a specified point.
*
* @method watchPosition
* @param  {float} latitude of the first point
* @param  {float} longitude of the first point
* @param  {float} distance from the point to trigger the insideCallback
* @param  {function} a callback to fire when the user is inside the geoFenceCircle
* @param  {function} a callback to fire when the user is outside the geoFenceCircle
* @param  {string} units to use: 'km' or 'mi', 'mi' is default if left blank
* @param  {object} an positionOptions object: enableHighAccuracy, maximumAge, timeout
*/
p5.prototype.geoFenceCircle = function(lat, lon, fence, insideCallback, outsideCallback, units, options){

  this.lat = lat;
  this.lon = lon;
  this.fence = fence;
  this.units = units; //this should work since calcGeoDistance defaults to miles.
  this.distance = 0.0;
  this.insideCallback = insideCallback;
  this.outsideCallback = outsideCallback;
  this.insideFence = false;
  this.options = options;
  this.id = '';

    this.geoError = function(message){
      console.log("geoFenceCircle Error :" + message);
    }

    this.success = function(position){
      this.distance = calcGeoDistance(this.lat,this.lon, position.coords.latitude, position.coords.longitude, this.units);

      if(this.distance <= this.fence){
          if(typeof this.insideCallback == 'function'){ this.insideCallback(position.coords) };
          this.insideFence = true;
      }else{
        if(typeof this.outsideCallback == 'function'){ this.outsideCallback(position.coords) };
        this.insideFence = false;
      }
    }

    this.clear = function() {
      if (this.id) {
        navigator.geolocation.clearWatch(this.id);
        this.id = '';
      }
    }

    if (navigator.geolocation) {
      // bind the callbacks to the geoFenceCircle 'this' so we can access, this.lat, this.lon, etc..
      this.id = navigator.geolocation.watchPosition(this.success.bind(this), this.geoError.bind(this), this.options);
    }else{
      geoError("geolocation not available");
    };
}

/**
* Create a new geoFencePolygon
*
* Watches the users current position and checks to see if they are witihn a set radius of a specified point.
*
* @method watchPosition
* @param  {float} latitude of the first point
* @param  {float} longitude of the first point
* @param  {float} distance from the point to trigger the insideCallback
* @param  {function} a callback to fire when the user is inside the geoFenceCircle
* @param  {function} a callback to fire when the user is outside the geoFenceCircle
* @param  {string} units to use: 'km' or 'mi', 'mi' is default if left blank
* @param  {object} an positionOptions object: enableHighAccuracy, maximumAge, timeout
*/

/*var points = [
    {x: 34.076089, y: -118.440915},
    {x: 34.076095, y: -118.440605},
    {x: 34.075906, y: -118.440597},
    {x: 34.075891, y: -118.440932},
];*/
p5.prototype.geoFencePolygon = function( ArrayOfObjectsWithLatLong, insideCallback, outsideCallback, units, options){

  this.ArrayOfObjectsWithLatLong = ArrayOfObjectsWithLatLong;
  this.units = units; //this should work since calcGeoDistance defaults to miles.
  this.insideCallback = insideCallback;
  this.outsideCallback = outsideCallback;
  this.insideFence = false;
  this.options = options;
  this.id = '';

    this.geoError = function(message){
      console.log("geoFencePolygon Error :" + message);
    }

    this.success = function(position){
      this.insideFence = isLocationInPolygon(this.ArrayOfObjectsWithLatLong, { lat:position.coords.latitude, lon: position.coords.longitude });

      if(this.insideFence == true){
          if(typeof this.insideCallback == 'function'){ this.insideCallback(position.coords) };
      }else{
        if(typeof this.outsideCallback == 'function'){ this.outsideCallback(position.coords) };
      }
    }

    this.clear = function() {
      if (this.id) {
        navigator.geolocation.clearWatch(this.id);
        this.id = '';
      }
    }

    if (navigator.geolocation) {
      // bind the callbacks to the geoFenceCircle 'this' so we can access, this.lat, this.lon, etc..
      this.id = navigator.geolocation.watchPosition(this.success.bind(this), this.geoError.bind(this), this.options);
    }else{
      geoError("geolocation not available");
    };
}
<|endoftext|>


<commit_before>/**
 * Component that flattens an object into a reference list instead of a nested hierarchy
 * It can also expand these flattened structures again.
 *
 * As a notice, this is supposed to be used on plain objects and arrays.
 * e.g. as a middle step between toJSON() and the actual string formatting
 *
 * Be warned that, as of now, these methods are pretty destructive, altering their given objects
 */
"use strict";

var
    _ = require('lodash')
;

//Pull all arrays, buffers and objects, find or put them into the pancake
function rollObjectIntoPancake(obj, pancake)
{
    _.forIn(obj, function(mValue, mKey) {
        if(_.isPlainObject(mValue) || _.isArray(mValue) || Buffer.isBuffer(mValue)) {

            var referenceIdx = _.findIndex(pancake, function(stored){
                return stored == mValue;
            });

            if(referenceIdx < 0)
            {
                //Put the object into the pancake an retrieve new reference index
                pancake.push(mValue);
                referenceIdx = pancake.length - 1;
            }

            //Create an array object that acts as reference (instead of any complex object to save size
            obj[mKey] = [referenceIdx];
        }
    });
}

//Pull all arrays and objects, find or put them into the pancake
function rollArrayIntoPancake(array, pancake)
{
    _.forEach(array, function(mValue, mKey) {
        if(_.isPlainObject(mValue) || _.isArray(mValue) || Buffer.isBuffer(mValue)) {

            var referenceIdx = _.findIndex(pancake, function(stored){
                return stored == mValue;
            });

            if(referenceIdx < 0)
            {
                //Put the object into the pancake an retrieve new reference index
                pancake.push(mValue);
                referenceIdx = pancake.length - 1;
            }

            //Create an array object that acts as reference (instead of any complex object to save size
            array[mKey] = [referenceIdx];
        }
    });
}

function flattenObjectOrArray(obj, pancake)
{
    pancake.push(obj);

    for(var i = 0; i < pancake.length; i++)
    {
        var unrolledElement = pancake[i];
        if(_.isArray(unrolledElement))
        {
            rollArrayIntoPancake(pancake[i], pancake);
        } else {
            rollObjectIntoPancake(pancake[i], pancake);
        }
    }
}

function reconstructObjectOrArray(pancake, indexOfStartObj, onConstruct, onRelink)
{
    //[pancakeIndex]
    var indicesToConstruct = [];
    indicesToConstruct.push(indexOfStartObj);

    //{pancakeIndex:constructedObj}
    var constructedObjects = {};
    //{pancakeIndex:{membername:pancakeIndex||-1}}
    var objectsLinks = {};

    if(!_.isFunction(onConstruct))
    {
        onConstruct = _.identity;
    }

    if(!_.isFunction(onRelink))
    {
        onRelink = function(object, links){
            _.extend(object, links);
        }
    }

    for(var i = 0; i < indicesToConstruct.length; i++) {
        var pancakeIndexToConstruct = indicesToConstruct[i];

        if(constructedObjects.hasOwnProperty(pancakeIndexToConstruct))
        {
            //Already constructed
            continue;
        }

        var flatObject = pancake[pancakeIndexToConstruct];

        var reconstructionObject = _.isArray(flatObject) ? [] : {};
        var links = {};
        if(Buffer.isBuffer(flatObject))
        {
            //Take buffer as-is
            reconstructionObject = flatObject;
        } else {
            //Create reconstruction object and store links
            _.forEach(flatObject, function (mValue, mKey) {
                //Check if it is a reference/link to another object type in the pancake
                if(_.isArray(mValue)) {
                    if(mValue.length == 1 && mValue[0] < pancake.length)
                    {
                        indicesToConstruct.push(mValue[0]);
                        links[mKey] = [mValue[0]];
                    } else {
                        links[mKey] = -1;
                    }
                } else {
                    reconstructionObject[mKey] = mValue;
                }
            });
        }

        if(_.size(links) > 0)
        {
            objectsLinks[pancakeIndexToConstruct] = links;
        }
        constructedObjects[pancakeIndexToConstruct] = onConstruct(reconstructionObject);
    }

    //Now that all objects have been constructed, possibly with specialized constructors, we can restore references
    _.forEach(objectsLinks, function (links, pancakeIndex) {
        var resolvedLinks = {};
        _.forEach(links, function(referencedPancakeIndex, fieldname){
            if(referencedPancakeIndex >= 0)
            {
                resolvedLinks[fieldname] = constructedObjects[referencedPancakeIndex];
            }
        });
        onRelink(constructedObjects[pancakeIndex], resolvedLinks);
    });

    return constructedObjects[indexOfStartObj];
}

exports.flatten = function(obj)
{
    //We wil flatten this into a electronic pancake
    var pancake = [];

    if(_.isUndefined(obj))
    {
        //Nothing to do, undefined things result in an empty pancake
    } else if(_.isPlainObject(obj) || _.isArray(obj)) {
        //It is an object or array, time to roll it flat!
        flattenObjectOrArray(obj, pancake);
    } else {
        //Primitive that can be flattened as-is
        pancake.push(obj);
    }

    return pancake;
};

exports.unflatten = function(pancake, onConstruct, onRelink)
{
    if(!_.isArray(pancake))
    {
        throw new TypeError("Cannot unflatten non-array");
    }

    if(pancake.length > 0)
    {
        //Let the pancake expand to become fluffy and thick
        var expanded = pancake[0];
        //if the root element is an object, we have to expand it
        if(_.isPlainObject(expanded) || _.isArray(expanded)) {
            expanded = reconstructObjectOrArray(pancake, 0, onConstruct, onRelink);
        }

        return expanded;
    } else {
        return null;
    }
};
<commit_msg>Pass default methods to hooks, so custom hooks can easily revert to default behaviour<commit_after>/**
 * Component that flattens an object into a reference list instead of a nested hierarchy
 * It can also expand these flattened structures again.
 *
 * As a notice, this is supposed to be used on plain objects and arrays.
 * e.g. as a middle step between toJSON() and the actual string formatting
 *
 * Be warned that, as of now, these methods are pretty destructive, altering their given objects
 */
"use strict";

var
    _ = require('lodash')
;

//Pull all arrays, buffers and objects, find or put them into the pancake
function rollObjectIntoPancake(obj, pancake)
{
    _.forIn(obj, function(mValue, mKey) {
        if(_.isPlainObject(mValue) || _.isArray(mValue) || Buffer.isBuffer(mValue)) {

            var referenceIdx = _.findIndex(pancake, function(stored){
                return stored == mValue;
            });

            if(referenceIdx < 0)
            {
                //Put the object into the pancake an retrieve new reference index
                pancake.push(mValue);
                referenceIdx = pancake.length - 1;
            }

            //Create an array object that acts as reference (instead of any complex object to save size
            obj[mKey] = [referenceIdx];
        }
    });
}

//Pull all arrays and objects, find or put them into the pancake
function rollArrayIntoPancake(array, pancake)
{
    _.forEach(array, function(mValue, mKey) {
        if(_.isPlainObject(mValue) || _.isArray(mValue) || Buffer.isBuffer(mValue)) {

            var referenceIdx = _.findIndex(pancake, function(stored){
                return stored == mValue;
            });

            if(referenceIdx < 0)
            {
                //Put the object into the pancake an retrieve new reference index
                pancake.push(mValue);
                referenceIdx = pancake.length - 1;
            }

            //Create an array object that acts as reference (instead of any complex object to save size
            array[mKey] = [referenceIdx];
        }
    });
}

function flattenObjectOrArray(obj, pancake)
{
    pancake.push(obj);

    for(var i = 0; i < pancake.length; i++)
    {
        var unrolledElement = pancake[i];
        if(_.isArray(unrolledElement))
        {
            rollArrayIntoPancake(pancake[i], pancake);
        } else {
            rollObjectIntoPancake(pancake[i], pancake);
        }
    }
}

function defaultOnConstruct(objectData){
    return _.identity(objectData);
}
function defaultOnRelink(object, links){
    _.extend(object, links);
}

function reconstructObjectOrArray(pancake, indexOfStartObj, onConstruct, onRelink)
{
    //[pancakeIndex]
    var indicesToConstruct = [];
    indicesToConstruct.push(indexOfStartObj);

    //{pancakeIndex:constructedObj}
    var constructedObjects = {};
    //{pancakeIndex:{membername:pancakeIndex||-1}}
    var objectsLinks = {};

    if(!_.isFunction(onConstruct))
    {
        onConstruct = defaultOnConstruct;
    }

    if(!_.isFunction(onRelink))
    {
        onRelink = defaultOnRelink;
    }

    for(var i = 0; i < indicesToConstruct.length; i++) {
        var pancakeIndexToConstruct = indicesToConstruct[i];

        if(constructedObjects.hasOwnProperty(pancakeIndexToConstruct))
        {
            //Already constructed
            continue;
        }

        var flatObject = pancake[pancakeIndexToConstruct];

        var reconstructionObject = _.isArray(flatObject) ? [] : {};
        var links = {};
        if(Buffer.isBuffer(flatObject))
        {
            //Take buffer as-is
            reconstructionObject = flatObject;
        } else {
            //Create reconstruction object and store links
            _.forEach(flatObject, function (mValue, mKey) {
                //Check if it is a reference/link to another object type in the pancake
                if(_.isArray(mValue)) {
                    if(mValue.length == 1 && mValue[0] < pancake.length)
                    {
                        indicesToConstruct.push(mValue[0]);
                        links[mKey] = [mValue[0]];
                    } else {
                        links[mKey] = -1;
                    }
                } else {
                    reconstructionObject[mKey] = mValue;
                }
            });
        }

        if(_.size(links) > 0)
        {
            objectsLinks[pancakeIndexToConstruct] = links;
        }
        constructedObjects[pancakeIndexToConstruct] = onConstruct(reconstructionObject, defaultOnConstruct);
    }

    //Now that all objects have been constructed, possibly with specialized constructors, we can restore references
    _.forEach(objectsLinks, function (links, pancakeIndex) {
        var resolvedLinks = {};
        _.forEach(links, function(referencedPancakeIndex, fieldname){
            if(referencedPancakeIndex >= 0)
            {
                resolvedLinks[fieldname] = constructedObjects[referencedPancakeIndex];
            }
        });
        onRelink(constructedObjects[pancakeIndex], resolvedLinks, defaultOnRelink);
    });

    return constructedObjects[indexOfStartObj];
}

exports.flatten = function(obj)
{
    //We wil flatten this into a electronic pancake
    var pancake = [];

    if(_.isUndefined(obj))
    {
        //Nothing to do, undefined things result in an empty pancake
    } else if(_.isPlainObject(obj) || _.isArray(obj)) {
        //It is an object or array, time to roll it flat!
        flattenObjectOrArray(obj, pancake);
    } else {
        //Primitive that can be flattened as-is
        pancake.push(obj);
    }

    return pancake;
};

exports.unflatten = function(pancake, onConstruct, onRelink)
{
    if(!_.isArray(pancake))
    {
        throw new TypeError("Cannot unflatten non-array");
    }

    if(pancake.length > 0)
    {
        //Let the pancake expand to become fluffy and thick
        var expanded = pancake[0];
        //if the root element is an object, we have to expand it
        if(_.isPlainObject(expanded) || _.isArray(expanded)) {
            expanded = reconstructObjectOrArray(pancake, 0, onConstruct, onRelink);
        }

        return expanded;
    } else {
        return null;
    }
};
<|endoftext|>


<commit_before>function schedule(_args) {
	var self = Ti.UI.createWindow({
		title:_args.title,
		backgroundColor:'white'
	});

	// var url = "https://x8-avian-bricolage-r.appspot.com/schedule/ScheduleService.schedule";
	var url = "http://localhost:8080/schedule/ScheduleService.schedule";
	var data, json, schedule, fighters, games;

	var xhr = Ti.Network.createHTTPClient({
    onload: function() {
		json = JSON.parse(this.responseText);
		if (json.schedule != "") {
			json = JSON.parse(json.schedule);
			//replace this with a for loop to load all the games rather than just one.
			game = json.games[0];

			data = [
				{title: game.game_date + ' vs. ' + game.opponent + ' at ' + game.location, hasChild:false, test:''}
			];
		} else {
			data = [
				{title: "No Games Found", hasChild:false, test:''}
			];
		}

		// create table view
		for (var i = 0; i < data.length; i++ ) { data[i].color = '#000'; data[i].font = {fontWeight:'bold'} };
		var tableview = Titanium.UI.createTableView({
			data:data
		});

		// add table view to the window
		self.add(tableview);
    },
	onerror: function(e) {
		Ti.API.error("STATUS: " + this.status);
		Ti.API.error("TEXT:   " + this.responseText);
		Ti.API.error("ERROR:  " + e.error);
		alert('There was an error retrieving the remote data. Try again.');
	    },
	    timeout:5000
	});
	var params = '{"team_id": "12345"}';
	xhr.open("POST", url);
	xhr.setRequestHeader('Content-Type','application/json')
	xhr.send(params);
	return self;
}

module.exports = schedule;
<commit_msg>Now able to display many games on the schedule.<commit_after>function schedule(_args) {
	var self = Ti.UI.createWindow({
		title:_args.title,
		backgroundColor:'white'
	});

	// var url = "https://x8-avian-bricolage-r.appspot.com/schedule/ScheduleService.schedule";
	var url = "http://localhost:8080/schedule/ScheduleService.schedule";
	var data = []
	var json, schedule, fighters, games;

	var xhr = Ti.Network.createHTTPClient({
    onload: function() {
		json = JSON.parse(this.responseText);
		if (json.schedule != "") {
			json = JSON.parse(json.schedule);
			//replace this with a for loop to load all the games rather than just one.
			for (i = 0; i < json.games.length; i++) {
				game = json.games[i];
				data.push({title: game.game_date + ' vs. ' + game.opponent + ' at ' + game.location, hasChild:false, test:''});
			}
		} else {
			data.push({title: "No Games Found", hasChild:false, test:''});
		}

		// create table view
		for (var i = 0; i < data.length; i++ ) { data[i].color = '#000'; data[i].font = {fontWeight:'bold'} };
		var tableview = Titanium.UI.createTableView({
			data:data
		});

		// add table view to the window
		self.add(tableview);
    },
	onerror: function(e) {
		Ti.API.error("STATUS: " + this.status);
		Ti.API.error("TEXT:   " + this.responseText);
		Ti.API.error("ERROR:  " + e.error);
		alert('There was an error retrieving the remote data. Try again.');
	    },
	    timeout:5000
	});
	var params = '{"team_id": "1234"}';
	xhr.open("POST", url);
	xhr.setRequestHeader('Content-Type','application/json')
	xhr.send(params);
	return self;
}

module.exports = schedule;
<|endoftext|>