<commit_before>'use strict';

import React from 'react';
import initialState from './initial-state';
import defaultProps from './default-props';
import createReactClass from 'create-react-class';
import classnames from 'classnames';
import { getOnDemandLazySlides, extractObject, initializedState, getHeight,
  canGoNext, slideHandler, changeSlide, keyHandler, swipeStart, swipeMove,
  swipeEnd, getPreClones, getPostClones } from './utils/innerSliderUtils'
import { getTrackLeft, getTrackCSS } from './utils/innerSliderUtils'

import { Track } from './track';
import { Dots } from './dots';
import { PrevArrow, NextArrow } from './arrows';

export class InnerSlider extends React.Component {
  constructor(props) {
    super(props)
    this.list = null
    this.track = null
    this.state = {
      ...initialState,
      currentSlide: this.props.initialSlide
    }
  }
  listRefHandler = ref =>  this.list = ref
  trackRefHandler = ref => this.track = ref
  adaptHeight = () => {
    if (this.props.adaptiveHeight && this.list) {
      const elem = this.list.querySelector(`[data-index="${this.state.currentSlide}"]`)
      this.list.style.height = getHeight(elem) + 'px'
    }
  }
  componentWillMount = () => {
    if (this.props.init) {
      this.props.init();
    }
    if (this.props.lazyLoad) {
      let slidesToLoad = getOnDemandLazySlides({...this.props, ...this.state})
      if (slidesToLoad.length > 0) {
        this.setState((prevState, props) => ({ lazyLoadedList: prevState.lazyLoadedList.concat(slidesToLoad) }))
        if (this.props.onLazyLoad) {
          this.props.onLazyLoad(slidesToLoad)
        }
      }
    }
  }
  componentDidMount = () => {
    let spec = {listRef: this.list, trackRef: this.track, ...this.props}
    this.updateState(spec, true, () => {
      this.adaptHeight()
      this.props.autoplay && this.autoPlay()
    })
    if (this.props.lazyLoad === 'progressive') {
      this.lazyLoadTimer = setInterval(this.progressiveLazyLoad, 1000)
    }
    // To support server-side rendering
    if (!window) {
      return
    }
    if (window.addEventListener) {
      window.addEventListener('resize', this.onWindowResized);
    } else {
      window.attachEvent('onresize', this.onWindowResized);
    }
  }
  componentWillUnmount = () => {
    if (this.animationEndCallback) {
      clearTimeout(this.animationEndCallback);
    }
    if (this.lazyLoadTimer) {
      clearInterval(this.lazyLoadTimer)
    }
    if (window.addEventListener) {
      window.removeEventListener('resize', this.onWindowResized);
    } else {
      window.detachEvent('onresize', this.onWindowResized);
    }
    if (this.autoplayTimer) {
      clearInterval(this.autoplayTimer);
    }
  }
  componentWillReceiveProps = (nextProps) => {
    let spec = {listRef: this.list, trackRef: this.track, ...nextProps, ...this.state}
    this.updateState(spec, false, () => {
      if (this.state.currentSlide >= React.Children.count(nextProps.children)) {
        this.changeSlide({
          message: 'index',
          index: React.Children.count(nextProps.children) - nextProps.slidesToShow,
          currentSlide: this.state.currentSlide
        });
      }
      if (nextProps.autoplay) {
        this.autoPlay()
      } else {
        this.pause()
      }
    })
  }
  componentDidUpdate = () => {
    this.checkImagesLoad()
    if (this.props.reInit) {
      this.props.reInit()
    }
    if (this.props.lazyLoad) {
      let slidesToLoad = getOnDemandLazySlides({...this.props, ...this.state})
      if (slidesToLoad.length > 0) {
        this.setState((prevState, props) => ({ lazyLoadedList: prevState.lazyLoadedList.concat(slidesToLoad) }))
        if (this.props.onLazyLoad) {
          this.props.onLazyLoad(slidesToLoad)
        }
      }
    }
    // if (this.props.onLazyLoad) {
    //   this.props.onLazyLoad([leftMostSlide])
    // }
    this.adaptHeight();
  }
  onWindowResized = () => {
    let spec = {listRef: this.list, trackRef: this.track, ...this.props, ...this.state}
    this.updateState(spec, true, () => {
      if (this.state.autoplaying === 'playing') this.autoPlay()
      else this.pause()
    })
    // animating state should be cleared while resizing, otherwise autoplay stops working
    this.setState({
      animating: false
    });
    clearTimeout(this.animationEndCallback);
    delete this.animationEndCallback;
  }
  updateState = (spec, setTrackStyle, callback, level=0) => {
    let updatedState = initializedState(spec)
    spec = {...spec, ...updatedState, slideIndex: updatedState.currentSlide}
    let targetLeft = getTrackLeft(spec)
    spec = {...spec, left: targetLeft}
    let trackStyle = getTrackCSS(spec)
    if (setTrackStyle || (React.Children.count(this.props.children) !==
      React.Children.count(spec.children))) {
      updatedState['trackStyle'] = trackStyle
    }
    this.setState( updatedState, () => {
      callback()
      if (!this.state.slideWidth && level < 2) {
        this.updateState(spec, true, callback, level+1)
      }
    })
  }
  checkImagesLoad = () => {
    let images = document.querySelectorAll('.slick-slide img')
    let imagesCount = images.length,
      loadedCount = 0
    Array.from(images).forEach(image => {
      const handler = () => ++loadedCount &&
          (loadedCount >= imagesCount) && this.onWindowResized()
      if (!image.onload) {
        if (this.props.lazyLoad) {
          image.onload = () => this.adaptHeight() ||
            setTimeout(this.onWindowResized, this.props.speed)
        } else {
          image.onload = handler
          image.onerror = () => {
            handler()
            this.props.onLazyLoadError && this.props.onLazyLoadError()
          }
        }
      }
    })
  }
  progressiveLazyLoad = () => {
    let slidesToLoad = []
    const spec = {...this.props, ...this.state}
    for(let index = this.state.currentSlide;
      index < this.state.slideCount + getPostClones(spec); index++) {
      if (this.state.lazyLoadedList.indexOf(index) < 0) {
        slidesToLoad.push(index)
        break
      }
    }
    for(let index = this.state.currentSlide - 1;
      index >= -getPreClones(spec); index--) {
      if (this.state.lazyLoadedList.indexOf(index) < 0) {
        slidesToLoad.push(index)
        break
      }
    }
    if (slidesToLoad.length > 0) {
      this.setState( state => (
        {lazyLoadedList: state.lazyLoadedList.concat(slidesToLoad)}
      ))
      if (this.props.onLazyLoad) {
        this.props.onLazyLoad(slidesToLoad)
      }
    } else {
      if (this.lazyLoadTimer) {
        clearInterval(this.lazyLoadTimer)
        delete this.lazyLoadTimer
      }
    }
  }
  slideHandler = (index) => {
    const {
      asNavFor, currentSlide, beforeChange, onLazyLoad, speed, afterChange
    } = this.props
    let {state, nextState} = slideHandler(
      {index, ...this.props, ...this.state, trackRef: this.track})
    if (!state) return
    beforeChange && beforeChange(currentSlide, state.currentSlide)
    let slidesToLoad = state.lazyLoadedList.filter(value =>
      this.state.lazyLoadedList.indexOf(value) < 0)
    onLazyLoad && slidesToLoad.length > 0 && onLazyLoad(slidesToLoad)
    this.setState(state, () => {
      asNavFor && asNavFor.innerSlider.state.currentSlide !== currentSlide
        && asNavFor.innerSlider.slideHandler(index)
      this.animationEndCallback = setTimeout(() => {
        const{animating, ...firstBatch} = nextState
        this.setState(firstBatch, () => {
          setTimeout(() => this.setState({ animating }), 10)
          afterChange && afterChange(state.currentSlide)
          delete this.animationEndCallback
        })
      }, speed)
    })

  }
  changeSlide = (options) => {
    const spec = {...this.props, ...this.state}
    let targetSlide = changeSlide(spec, options)
    if (targetSlide !== 0 && !targetSlide) return
    this.slideHandler(targetSlide)
  }
  keyHandler = (e) => {
    let dir = keyHandler(e, this.props.accessibility, this.props.rtl)
    dir !== '' && this.changeSlide({ message: dir })
  }
  selectHandler = (options) => {
    this.changeSlide(options)
  }
  swipeStart = (e) => {
    let state = swipeStart(e, this.props.swipe, this.props.draggable)
    state !== '' && this.setState(state)
  }
  swipeMove = (e) => {
    let state = swipeMove(e, {
      ...this.props,
      ...this.state,
      trackRef: this.track,
      listRef: this.list,
      slideIndex: this.state.currentSlide
    })
    if (!state) return
    this.setState(state)
  }
  swipeEnd = (e) => {
    let state = swipeEnd(e, {
      ...this.props,
      ...this.state,
      trackRef: this.track,
      listRef: this.list,
      slideIndex: this.state.currentSlide
    })
    if (!state) return
    let triggerSlideHandler = state['triggerSlideHandler']
    delete state['triggerSlideHandler']
    this.setState(state)
    if (triggerSlideHandler === undefined) return
    this.slideHandler(triggerSlideHandler)
  }
  slickPrev = () => {
    // this and fellow methods are wrapped in setTimeout
    // to make sure initialize setState has happened before
    // any of such methods are called
    setTimeout(() => this.changeSlide({ message: 'previous' }), 0)
  }
  slickNext = () => {
    setTimeout(() => this.changeSlide({ message: 'next' }), 0)
  }
  slickGoTo = (slide) => {
    slide = Number(slide)
    !isNaN(slide) && setTimeout( () => this.changeSlide({
      message: 'index',
      index: slide,
      currentSlide: this.state.currentSlide
    }), 0)
  }
  play = ()=> {
    var nextIndex;
    if (this.props.rtl) {
      nextIndex = this.state.currentSlide - this.props.slidesToScroll;
    } else {
      if (canGoNext({...this.props, ...this.state})) {
        nextIndex = this.state.currentSlide + this.props.slidesToScroll;
      } else {
        return false;
      }
    }

    this.slideHandler(nextIndex);
  }
  autoPlay = () => {
    if (this.autoplayTimer) {
      console.warn("autoPlay is triggered more than once")
      clearInterval(this.autoplayTimer)
    }
    this.autoplayTimer = setInterval(this.play, this.props.autoplaySpeed+50)
    this.setState({ autoplaying: 'playing' })
  }
  pause = (hover=false) => {
    if (this.autoplayTimer) {
      clearInterval(this.autoplayTimer)
      this.autoplayTimer = null
    }
    if (this.state.autoplaying === 'paused') return
    if (hover) this.setState({ autoplaying: 'hovered' })
    else this.setState({ autoplaying: 'paused' })
  }
  onInnerSliderEnter = (e) => {
    if (this.props.autoplay && this.props.pauseOnHover) {
      this.pause(true);
    }
  }
  onInnerSliderOver = (e) => {
    if (this.props.autoplay && this.props.pauseOnHover) {
      this.pause(true);
    }
  }
  onInnerSliderLeave = (e) => {
    if (this.props.autoplay && this.props.pauseOnHover &&
      this.state.autoplaying === 'hovered') {
      this.autoPlay();
    }
  }
  render = () => {
    var className = classnames('slick-initialized', 'slick-slider', this.props.className, {
      'slick-vertical': this.props.vertical,
    });
    let spec = {...this.props, ...this.state}
    let trackProps = extractObject(spec, [
      'fade', 'cssEase', 'speed', 'infinite', 'centerMode', 'focusOnSelect',
      'currentSlide', 'lazyLoad', 'lazyLoadedList', 'rtl', 'slideWidth',
      'slideHeight', 'listHeight', 'vertical', 'slidesToShow', 'slidesToScroll',
      'slideCount', 'trackStyle', 'variableWidth', 'unslick', 'centerPadding' ])
    trackProps.focusOnSelect = this.props.focusOnSelect? this.selectHandler: null

    var dots;
    if (this.props.dots === true && this.state.slideCount >= this.props.slidesToShow) {
      let dotProps = extractObject(spec, [
        'dotsClass', 'slideCount', 'slidesToShow', 'currentSlide', 'slidesToScroll',
        'clickHandler', 'children', 'customPaging', 'infinite', 'appendDots' ])
      dotProps.clickHandler = this.changeSlide
      dots = (<Dots {...dotProps} />);
    }

    var prevArrow, nextArrow;
    let arrowProps = extractObject(spec, ['infinite', 'centerMode', 'currentSlide',
      'slideCount', 'slidesToShow', 'prevArrow', 'nextArrow'])
    arrowProps.clickHandler = this.changeSlide

    if (this.props.arrows) {
      prevArrow = (<PrevArrow {...arrowProps} />);
      nextArrow = (<NextArrow {...arrowProps} />);
    }

    var verticalHeightStyle = null;

    if (this.props.vertical) {
      verticalHeightStyle = {
        height: this.state.listHeight,
      };
    }

    var centerPaddingStyle = null;

    if (this.props.vertical === false) {
      if (this.props.centerMode === true) {
        centerPaddingStyle = {
          padding: ('0px ' + this.props.centerPadding)
        };
      }
    } else {
      if (this.props.centerMode === true) {
        centerPaddingStyle = {
          padding: (this.props.centerPadding + ' 0px')
        };
      }
    }

    const listStyle = {...verticalHeightStyle, ...centerPaddingStyle}
    const touchMove = this.props.touchMove
    let listProps = {
      className: 'slick-list',
      style: listStyle,
      onMouseDown: touchMove ? this.swipeStart : null,
      onMouseMove: this.state.dragging && touchMove ? this.swipeMove : null,
      onMouseUp: touchMove ? this.swipeEnd : null,
      onMouseLeave: this.state.dragging && touchMove ? this.swipeEnd : null,
      onTouchStart: touchMove ? this.swipeStart : null,
      onTouchMove: this.state.dragging && touchMove ? this.swipeMove : null,
      onTouchEnd: touchMove ? this.swipeEnd : null,
      onTouchCancel: this.state.dragging && touchMove ? this.swipeEnd : null,
      onKeyDown: this.props.accessibility ? this.keyHandler : null,
    }

    let innerSliderProps = {
      className: className,
      onMouseEnter: this.onInnerSliderEnter,
      onMouseLeave: this.onInnerSliderLeave,
      onMouseOver: this.onInnerSliderOver,
      dir: 'ltr',
    }

    if (this.props.unslick) {
      listProps = { className: 'slick-list' }
      innerSliderProps = { className }
    }

    return (
      <div {...innerSliderProps} >
        { !this.props.unslick ? prevArrow : '' }
        <div ref={this.listRefHandler} {...listProps} >
          <Track ref={this.trackRefHandler} {...trackProps}>
            {this.props.children}
          </Track>
        </div>
        { !this.props.unslick ? nextArrow: '' }
        { !this.props.unslick ? dots : '' }
      </div>
    );
  }
}
<commit_msg>added separate onclick listener for images<commit_after>'use strict';

import React from 'react';
import initialState from './initial-state';
import defaultProps from './default-props';
import createReactClass from 'create-react-class';
import classnames from 'classnames';
import { getOnDemandLazySlides, extractObject, initializedState, getHeight,
  canGoNext, slideHandler, changeSlide, keyHandler, swipeStart, swipeMove,
  swipeEnd, getPreClones, getPostClones } from './utils/innerSliderUtils'
import { getTrackLeft, getTrackCSS } from './utils/innerSliderUtils'

import { Track } from './track';
import { Dots } from './dots';
import { PrevArrow, NextArrow } from './arrows';

export class InnerSlider extends React.Component {
  constructor(props) {
    super(props)
    this.list = null
    this.track = null
    this.state = {
      ...initialState,
      currentSlide: this.props.initialSlide
    }
  }
  listRefHandler = ref =>  this.list = ref
  trackRefHandler = ref => this.track = ref
  adaptHeight = () => {
    if (this.props.adaptiveHeight && this.list) {
      const elem = this.list.querySelector(`[data-index="${this.state.currentSlide}"]`)
      this.list.style.height = getHeight(elem) + 'px'
    }
  }
  componentWillMount = () => {
    if (this.props.init) {
      this.props.init();
    }
    if (this.props.lazyLoad) {
      let slidesToLoad = getOnDemandLazySlides({...this.props, ...this.state})
      if (slidesToLoad.length > 0) {
        this.setState((prevState, props) => ({ lazyLoadedList: prevState.lazyLoadedList.concat(slidesToLoad) }))
        if (this.props.onLazyLoad) {
          this.props.onLazyLoad(slidesToLoad)
        }
      }
    }
  }
  componentDidMount = () => {
    let spec = {listRef: this.list, trackRef: this.track, ...this.props}
    this.updateState(spec, true, () => {
      this.adaptHeight()
      this.props.autoplay && this.autoPlay()
    })
    if (this.props.lazyLoad === 'progressive') {
      this.lazyLoadTimer = setInterval(this.progressiveLazyLoad, 1000)
    }
    // To support server-side rendering
    if (!window) {
      return
    }
    if (window.addEventListener) {
      window.addEventListener('resize', this.onWindowResized);
    } else {
      window.attachEvent('onresize', this.onWindowResized);
    }
  }
  componentWillUnmount = () => {
    if (this.animationEndCallback) {
      clearTimeout(this.animationEndCallback);
    }
    if (this.lazyLoadTimer) {
      clearInterval(this.lazyLoadTimer)
    }
    if (window.addEventListener) {
      window.removeEventListener('resize', this.onWindowResized);
    } else {
      window.detachEvent('onresize', this.onWindowResized);
    }
    if (this.autoplayTimer) {
      clearInterval(this.autoplayTimer);
    }
  }
  componentWillReceiveProps = (nextProps) => {
    let spec = {listRef: this.list, trackRef: this.track, ...nextProps, ...this.state}
    this.updateState(spec, false, () => {
      if (this.state.currentSlide >= React.Children.count(nextProps.children)) {
        this.changeSlide({
          message: 'index',
          index: React.Children.count(nextProps.children) - nextProps.slidesToShow,
          currentSlide: this.state.currentSlide
        });
      }
      if (nextProps.autoplay) {
        this.autoPlay()
      } else {
        this.pause()
      }
    })
  }
  componentDidUpdate = () => {
    this.checkImagesLoad()
    if (this.props.reInit) {
      this.props.reInit()
    }
    if (this.props.lazyLoad) {
      let slidesToLoad = getOnDemandLazySlides({...this.props, ...this.state})
      if (slidesToLoad.length > 0) {
        this.setState((prevState, props) => ({ lazyLoadedList: prevState.lazyLoadedList.concat(slidesToLoad) }))
        if (this.props.onLazyLoad) {
          this.props.onLazyLoad(slidesToLoad)
        }
      }
    }
    // if (this.props.onLazyLoad) {
    //   this.props.onLazyLoad([leftMostSlide])
    // }
    this.adaptHeight();
  }
  onWindowResized = () => {
    let spec = {listRef: this.list, trackRef: this.track, ...this.props, ...this.state}
    this.updateState(spec, true, () => {
      if (this.state.autoplaying === 'playing') this.autoPlay()
      else this.pause()
    })
    // animating state should be cleared while resizing, otherwise autoplay stops working
    this.setState({
      animating: false
    });
    clearTimeout(this.animationEndCallback);
    delete this.animationEndCallback;
  }
  updateState = (spec, setTrackStyle, callback, level=0) => {
    let updatedState = initializedState(spec)
    spec = {...spec, ...updatedState, slideIndex: updatedState.currentSlide}
    let targetLeft = getTrackLeft(spec)
    spec = {...spec, left: targetLeft}
    let trackStyle = getTrackCSS(spec)
    if (setTrackStyle || (React.Children.count(this.props.children) !==
      React.Children.count(spec.children))) {
      updatedState['trackStyle'] = trackStyle
    }
    this.setState( updatedState, () => {
      callback()
      if (!this.state.slideWidth && level < 2) {
        this.updateState(spec, true, callback, level+1)
      }
    })
  }
  checkImagesLoad = () => {
    let images = document.querySelectorAll('.slick-slide img')
    let imagesCount = images.length,
      loadedCount = 0
    Array.from(images).forEach(image => {
      const handler = () => ++loadedCount &&
          (loadedCount >= imagesCount) && this.onWindowResized()
      if (!image.onclick) {
        image.onclick = () => image.parentNode.focus()
      } else {
        const prevClick = image.onclick
        image.onclick = () => {
          prevClick()
          image.parentNode.focus()
        }
      }
      if (!image.onload) {
        if (this.props.lazyLoad) {
          image.onload = () => this.adaptHeight() ||
            setTimeout(this.onWindowResized, this.props.speed)
        } else {
          image.onload = handler
          image.onerror = () => {
            handler()
            this.props.onLazyLoadError && this.props.onLazyLoadError()
          }
        }
      }
    })
  }
  progressiveLazyLoad = () => {
    let slidesToLoad = []
    const spec = {...this.props, ...this.state}
    for(let index = this.state.currentSlide;
      index < this.state.slideCount + getPostClones(spec); index++) {
      if (this.state.lazyLoadedList.indexOf(index) < 0) {
        slidesToLoad.push(index)
        break
      }
    }
    for(let index = this.state.currentSlide - 1;
      index >= -getPreClones(spec); index--) {
      if (this.state.lazyLoadedList.indexOf(index) < 0) {
        slidesToLoad.push(index)
        break
      }
    }
    if (slidesToLoad.length > 0) {
      this.setState( state => (
        {lazyLoadedList: state.lazyLoadedList.concat(slidesToLoad)}
      ))
      if (this.props.onLazyLoad) {
        this.props.onLazyLoad(slidesToLoad)
      }
    } else {
      if (this.lazyLoadTimer) {
        clearInterval(this.lazyLoadTimer)
        delete this.lazyLoadTimer
      }
    }
  }
  slideHandler = (index) => {
    const {
      asNavFor, currentSlide, beforeChange, onLazyLoad, speed, afterChange
    } = this.props
    let {state, nextState} = slideHandler(
      {index, ...this.props, ...this.state, trackRef: this.track})
    if (!state) return
    beforeChange && beforeChange(currentSlide, state.currentSlide)
    let slidesToLoad = state.lazyLoadedList.filter(value =>
      this.state.lazyLoadedList.indexOf(value) < 0)
    onLazyLoad && slidesToLoad.length > 0 && onLazyLoad(slidesToLoad)
    this.setState(state, () => {
      asNavFor && asNavFor.innerSlider.state.currentSlide !== currentSlide
        && asNavFor.innerSlider.slideHandler(index)
      this.animationEndCallback = setTimeout(() => {
        const{animating, ...firstBatch} = nextState
        this.setState(firstBatch, () => {
          setTimeout(() => this.setState({ animating }), 10)
          afterChange && afterChange(state.currentSlide)
          delete this.animationEndCallback
        })
      }, speed)
    })

  }
  changeSlide = (options) => {
    const spec = {...this.props, ...this.state}
    let targetSlide = changeSlide(spec, options)
    if (targetSlide !== 0 && !targetSlide) return
    this.slideHandler(targetSlide)
  }
  keyHandler = (e) => {
    let dir = keyHandler(e, this.props.accessibility, this.props.rtl)
    dir !== '' && this.changeSlide({ message: dir })
  }
  selectHandler = (options) => {
    this.changeSlide(options)
  }
  swipeStart = (e) => {
    let state = swipeStart(e, this.props.swipe, this.props.draggable)
    state !== '' && this.setState(state)
  }
  swipeMove = (e) => {
    let state = swipeMove(e, {
      ...this.props,
      ...this.state,
      trackRef: this.track,
      listRef: this.list,
      slideIndex: this.state.currentSlide
    })
    if (!state) return
    this.setState(state)
  }
  swipeEnd = (e) => {
    let state = swipeEnd(e, {
      ...this.props,
      ...this.state,
      trackRef: this.track,
      listRef: this.list,
      slideIndex: this.state.currentSlide
    })
    if (!state) return
    let triggerSlideHandler = state['triggerSlideHandler']
    delete state['triggerSlideHandler']
    this.setState(state)
    if (triggerSlideHandler === undefined) return
    this.slideHandler(triggerSlideHandler)
  }
  slickPrev = () => {
    // this and fellow methods are wrapped in setTimeout
    // to make sure initialize setState has happened before
    // any of such methods are called
    setTimeout(() => this.changeSlide({ message: 'previous' }), 0)
  }
  slickNext = () => {
    setTimeout(() => this.changeSlide({ message: 'next' }), 0)
  }
  slickGoTo = (slide) => {
    slide = Number(slide)
    !isNaN(slide) && setTimeout( () => this.changeSlide({
      message: 'index',
      index: slide,
      currentSlide: this.state.currentSlide
    }), 0)
  }
  play = ()=> {
    var nextIndex;
    if (this.props.rtl) {
      nextIndex = this.state.currentSlide - this.props.slidesToScroll;
    } else {
      if (canGoNext({...this.props, ...this.state})) {
        nextIndex = this.state.currentSlide + this.props.slidesToScroll;
      } else {
        return false;
      }
    }

    this.slideHandler(nextIndex);
  }
  autoPlay = () => {
    if (this.autoplayTimer) {
      console.warn("autoPlay is triggered more than once")
      clearInterval(this.autoplayTimer)
    }
    this.autoplayTimer = setInterval(this.play, this.props.autoplaySpeed+50)
    this.setState({ autoplaying: 'playing' })
  }
  pause = (hover=false) => {
    if (this.autoplayTimer) {
      clearInterval(this.autoplayTimer)
      this.autoplayTimer = null
    }
    if (this.state.autoplaying === 'paused') return
    if (hover) this.setState({ autoplaying: 'hovered' })
    else this.setState({ autoplaying: 'paused' })
  }
  onInnerSliderEnter = (e) => {
    if (this.props.autoplay && this.props.pauseOnHover) {
      this.pause(true);
    }
  }
  onInnerSliderOver = (e) => {
    if (this.props.autoplay && this.props.pauseOnHover) {
      this.pause(true);
    }
  }
  onInnerSliderLeave = (e) => {
    if (this.props.autoplay && this.props.pauseOnHover &&
      this.state.autoplaying === 'hovered') {
      this.autoPlay();
    }
  }
  render = () => {
    var className = classnames('slick-initialized', 'slick-slider', this.props.className, {
      'slick-vertical': this.props.vertical,
    });
    let spec = {...this.props, ...this.state}
    let trackProps = extractObject(spec, [
      'fade', 'cssEase', 'speed', 'infinite', 'centerMode', 'focusOnSelect',
      'currentSlide', 'lazyLoad', 'lazyLoadedList', 'rtl', 'slideWidth',
      'slideHeight', 'listHeight', 'vertical', 'slidesToShow', 'slidesToScroll',
      'slideCount', 'trackStyle', 'variableWidth', 'unslick', 'centerPadding' ])
    trackProps.focusOnSelect = this.props.focusOnSelect? this.selectHandler: null

    var dots;
    if (this.props.dots === true && this.state.slideCount >= this.props.slidesToShow) {
      let dotProps = extractObject(spec, [
        'dotsClass', 'slideCount', 'slidesToShow', 'currentSlide', 'slidesToScroll',
        'clickHandler', 'children', 'customPaging', 'infinite', 'appendDots' ])
      dotProps.clickHandler = this.changeSlide
      dots = (<Dots {...dotProps} />);
    }

    var prevArrow, nextArrow;
    let arrowProps = extractObject(spec, ['infinite', 'centerMode', 'currentSlide',
      'slideCount', 'slidesToShow', 'prevArrow', 'nextArrow'])
    arrowProps.clickHandler = this.changeSlide

    if (this.props.arrows) {
      prevArrow = (<PrevArrow {...arrowProps} />);
      nextArrow = (<NextArrow {...arrowProps} />);
    }

    var verticalHeightStyle = null;

    if (this.props.vertical) {
      verticalHeightStyle = {
        height: this.state.listHeight,
      };
    }

    var centerPaddingStyle = null;

    if (this.props.vertical === false) {
      if (this.props.centerMode === true) {
        centerPaddingStyle = {
          padding: ('0px ' + this.props.centerPadding)
        };
      }
    } else {
      if (this.props.centerMode === true) {
        centerPaddingStyle = {
          padding: (this.props.centerPadding + ' 0px')
        };
      }
    }

    const listStyle = {...verticalHeightStyle, ...centerPaddingStyle}
    const touchMove = this.props.touchMove
    let listProps = {
      className: 'slick-list',
      style: listStyle,
      onMouseDown: touchMove ? this.swipeStart : null,
      onMouseMove: this.state.dragging && touchMove ? this.swipeMove : null,
      onMouseUp: touchMove ? this.swipeEnd : null,
      onMouseLeave: this.state.dragging && touchMove ? this.swipeEnd : null,
      onTouchStart: touchMove ? this.swipeStart : null,
      onTouchMove: this.state.dragging && touchMove ? this.swipeMove : null,
      onTouchEnd: touchMove ? this.swipeEnd : null,
      onTouchCancel: this.state.dragging && touchMove ? this.swipeEnd : null,
      onKeyDown: this.props.accessibility ? this.keyHandler : null,
    }

    let innerSliderProps = {
      className: className,
      onMouseEnter: this.onInnerSliderEnter,
      onMouseLeave: this.onInnerSliderLeave,
      onMouseOver: this.onInnerSliderOver,
      dir: 'ltr',
    }

    if (this.props.unslick) {
      listProps = { className: 'slick-list' }
      innerSliderProps = { className }
    }

    return (
      <div {...innerSliderProps} >
        { !this.props.unslick ? prevArrow : '' }
        <div ref={this.listRefHandler} {...listProps} >
          <Track ref={this.trackRefHandler} {...trackProps}>
            {this.props.children}
          </Track>
        </div>
        { !this.props.unslick ? nextArrow: '' }
        { !this.props.unslick ? dots : '' }
      </div>
    );
  }
}
<|endoftext|>


<commit_before>if (global.window && !process.env.SERVER) {
  // eslint-disable-next-line global-require
  require('intersection-observer')
}

const INSTANCE_MAP = new Map()
const OBSERVER_MAP = new Map()

/**
 * Monitor element, and trigger callback when element becomes visible
 * @param element {HTMLElement}
 * @param callback {Function} - Called with inView
 * @param threshold {Number} Number between 0 and 1, indicating how much of the element should be visible before triggering
 */
export function observe(element, callback, threshold = 0) {
  if (!element || !callback) return
  let observerInstance = OBSERVER_MAP.get(threshold)
  if (!observerInstance) {
    observerInstance = new IntersectionObserver(onChange, { threshold })
    OBSERVER_MAP.set(threshold, observerInstance)
  }

  INSTANCE_MAP.set(element, {
    callback,
    visible: false,
    threshold,
  })
  observerInstance.observe(element)
}

/**
 * Stop observing an element. If an element is removed from the DOM or otherwise destroyed,
 * make sure to call this method.
 * @param element {HTMLElement}
 */
export function unobserve(element) {
  if (!element) return
  const instance = INSTANCE_MAP.get(element)

  if (instance) {
    INSTANCE_MAP.delete(element)

    const observerInstance = OBSERVER_MAP.get(instance.threshold)
    if (observerInstance) {
      observerInstance.unobserve(element)
    }

    // Check if we are stilling observing any elements with the same threshold.
    let itemsLeft = false
    INSTANCE_MAP.forEach(item => {
      if (item.threshold === instance.threshold) {
        itemsLeft = true
      }
    })

    if (observerInstance && !itemsLeft) {
      // No more elements to observe for threshold, disconnect observer
      observerInstance.disconnect()
      OBSERVER_MAP.delete(instance.threshold)
    }
  }
}

function onChange(changes) {
  changes.forEach(intersection => {
    if (INSTANCE_MAP.has(intersection.target)) {
      const { callback, visible, threshold } = INSTANCE_MAP.get(
        intersection.target,
      )
      // Trigger on 0 ratio only when not visible.
      const inView = visible
        ? intersection.intersectionRatio > threshold
        : intersection.intersectionRatio >= threshold

      INSTANCE_MAP.set(intersection.target, {
        callback,
        visible: inView,
        threshold,
      })

      if (callback) {
        callback(inView)
      }
    }
  })
}

export default {
  observe,
  unobserve,
}
<commit_msg>Add back isIntersecting, but merge usage with ratio. Fixes #8<commit_after>if (global.window && !process.env.SERVER) {
  // eslint-disable-next-line global-require
  require('intersection-observer')
}

const INSTANCE_MAP = new Map()
const OBSERVER_MAP = new Map()

/**
 * Monitor element, and trigger callback when element becomes visible
 * @param element {HTMLElement}
 * @param callback {Function} - Called with inView
 * @param threshold {Number} Number between 0 and 1, indicating how much of the element should be visible before triggering
 */
export function observe(element, callback, threshold = 0) {
  if (!element || !callback) return
  let observerInstance = OBSERVER_MAP.get(threshold)
  if (!observerInstance) {
    observerInstance = new IntersectionObserver(onChange, { threshold })
    OBSERVER_MAP.set(threshold, observerInstance)
  }

  INSTANCE_MAP.set(element, {
    callback,
    visible: false,
    threshold,
  })
  observerInstance.observe(element)
}

/**
 * Stop observing an element. If an element is removed from the DOM or otherwise destroyed,
 * make sure to call this method.
 * @param element {HTMLElement}
 */
export function unobserve(element) {
  if (!element) return
  const instance = INSTANCE_MAP.get(element)

  if (instance) {
    INSTANCE_MAP.delete(element)

    const observerInstance = OBSERVER_MAP.get(instance.threshold)
    if (observerInstance) {
      observerInstance.unobserve(element)
    }

    // Check if we are stilling observing any elements with the same threshold.
    let itemsLeft = false
    INSTANCE_MAP.forEach(item => {
      if (item.threshold === instance.threshold) {
        itemsLeft = true
      }
    })

    if (observerInstance && !itemsLeft) {
      // No more elements to observe for threshold, disconnect observer
      observerInstance.disconnect()
      OBSERVER_MAP.delete(instance.threshold)
    }
  }
}

function onChange(changes) {
  changes.forEach(intersection => {
    if (INSTANCE_MAP.has(intersection.target)) {
      const { isIntersecting, intersectionRatio, target } = intersection
      const { callback, visible, threshold } = INSTANCE_MAP.get(target)

      // Trigger on 0 ratio only when not visible. This is fallback for browsers without isIntersecting support
      let inView = visible
        ? intersectionRatio > threshold
        : intersectionRatio >= threshold

      if (isIntersecting !== undefined) {
        // If isIntersecting is defined, ensure that the element is actually intersecting.
        // Otherwise it reports a threshold of 0
        inView = inView && isIntersecting
      }

      INSTANCE_MAP.set(target, {
        callback,
        visible: inView,
        threshold,
      })

      if (callback) {
        callback(inView)
      }
    }
  })
}

export default {
  observe,
  unobserve,
}
<|endoftext|>


<commit_before>/* eslint
    no-console: 0,
    no-param-reassign: 0,
    no-use-before-define: ["error", { "variables": false }],
    no-return-assign: 0,
    react/no-string-refs: 0,
    react/sort-comp: 0
*/

import PropTypes from 'prop-types';
import React from 'react';

import { FlatList, View, StyleSheet, Keyboard, TouchableOpacity, Text } from 'react-native';

import LoadEarlier from './LoadEarlier';
import Message from './Message';
import Color from './Color';

export default class MessageContainer extends React.Component {

  state = {
    showScrollBottom: false,
  }

  componentDidMount() {
    if (this.props.messages.length === 0) {
      this.attachKeyboardListeners();
    }
  }

  shouldComponentUpdate(nextProps) {
    const next = nextProps.messages;
    const current = this.props.messages;
    return (
      next.length !== current.length || next.extraData !== current.extraData || next.loadEarlier !== current.loadEarlier
    );
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.messages.length === 0 && nextProps.messages.length > 0) {
      this.detachKeyboardListeners();
    } else if (this.props.messages.length > 0 && nextProps.messages.length === 0) {
      this.attachKeyboardListeners(nextProps);
    }
  }

  attachKeyboardListeners = () => {
    const { invertibleScrollViewProps: invertibleProps } = this.props;
    Keyboard.addListener('keyboardWillShow', invertibleProps.onKeyboardWillShow);
    Keyboard.addListener('keyboardDidShow', invertibleProps.onKeyboardDidShow);
    Keyboard.addListener('keyboardWillHide', invertibleProps.onKeyboardWillHide);
    Keyboard.addListener('keyboardDidHide', invertibleProps.onKeyboardDidHide);
  };

  detachKeyboardListeners = () => {
    const { invertibleScrollViewProps: invertibleProps } = this.props;
    Keyboard.removeListener('keyboardWillShow', invertibleProps.onKeyboardWillShow);
    Keyboard.removeListener('keyboardDidShow', invertibleProps.onKeyboardDidShow);
    Keyboard.removeListener('keyboardWillHide', invertibleProps.onKeyboardWillHide);
    Keyboard.removeListener('keyboardDidHide', invertibleProps.onKeyboardDidHide);
  };

  renderFooter = () => {
    if (this.props.renderFooter) {
      const footerProps = {
        ...this.props,
      };
      return this.props.renderFooter(footerProps);
    }
    return null;
  };

  renderLoadEarlier = () => {
    if (this.props.loadEarlier === true) {
      const loadEarlierProps = {
        ...this.props,
      };
      if (this.props.renderLoadEarlier) {
        return this.props.renderLoadEarlier(loadEarlierProps);
      }
      return <LoadEarlier {...loadEarlierProps} />;
    }
    return null;
  };

  scrollTo(options) {
    if (this.flatListRef && options) {
      this.flatListRef.scrollToOffset(options);
    }
  }

  scrollToBottom = () => {
    this.scrollTo({ offset: 0, animated: 'true' });
  }

  handleOnScroll = (event) => {
    if (event.nativeEvent.contentOffset.y > this.props.scrollToBottomOffset) {
      this.setState({ showScrollBottom: true });
    } else {
      this.setState({ showScrollBottom: false });
    }
  }

  renderRow = ({ item, index }) => {
    if (!item._id && item._id !== 0) {
      console.warn('GiftedChat: `_id` is missing for message', JSON.stringify(item));
    }
    if (!item.user) {
      if (!item.system) {
        console.warn('GiftedChat: `user` is missing for message', JSON.stringify(item));
      }
      item.user = {};
    }
    const { messages, ...restProps } = this.props;
    const previousMessage = messages[index + 1] || {};
    const nextMessage = messages[index - 1] || {};

    const messageProps = {
      ...restProps,
      key: item._id,
      currentMessage: item,
      previousMessage,
      nextMessage,
      position: item.user._id === this.props.user._id ? 'right' : 'left',
    };

    if (this.props.renderMessage) {
      return this.props.renderMessage(messageProps);
    }
    return <Message {...messageProps} />;
  };

  renderHeaderWrapper = () => <View style={styles.headerWrapper}>{this.renderLoadEarlier()}</View>;

  renderScrollToBottomWrapper() {
    const scrollToBottomComponent = (
      <View style={styles.scrollToBottomStyle}>
        <TouchableOpacity onPress={this.scrollToBottom} hitSlop={{ top: 5, left: 5, right: 5, bottom: 5 }}>
          <Text>V</Text>
        </TouchableOpacity>
      </View>
    );

    if (this.props.scrollToBottomComponent) {
      return (
        <TouchableOpacity onPress={this.scrollToBottom} hitSlop={{ top: 5, left: 5, right: 5, bottom: 5 }}>
          {this.props.scrollToBottomComponent}
        </TouchableOpacity>);
    }
    return scrollToBottomComponent;
  }

  keyExtractor = (item) => `${item._id}`;

  render() {
    if (this.props.messages.length === 0) {
      return <View style={styles.container} />;
    }
    return (
      <View style={styles.container}>
        {this.state.showScrollBottom && this.props.scrollToBottom ? this.renderScrollToBottomWrapper() : null}
        <FlatList
          ref={(ref) => (this.flatListRef = ref)}
          extraData={this.props.extraData}
          keyExtractor={this.keyExtractor}
          enableEmptySections
          automaticallyAdjustContentInsets={false}
          inverted={this.props.inverted}
          data={this.props.messages}
          style={styles.listStyle}
          contentContainerStyle={styles.contentContainerStyle}
          renderItem={this.renderRow}
          {...this.props.invertibleScrollViewProps}
          ListFooterComponent={this.renderHeaderWrapper}
          ListHeaderComponent={this.renderFooter}
          onScroll={this.handleOnScroll}
          scrollEventThrottle={100}
          {...this.props.listViewProps}
        />
      </View>
    );
  }

}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  contentContainerStyle: {
    justifyContent: 'flex-end',
  },
  headerWrapper: {
    flex: 1,
  },
  listStyle: {
    flex: 1,
  },
  scrollToBottomStyle: {
    opacity: 0.8,
    position: 'absolute',
    paddingHorizontal: 15,
    paddingVertical: 8,
    right: 10,
    bottom: 30,
    zIndex: 999,
    height: 40,
    width: 40,
    borderRadius: 20,
    backgroundColor: Color.white,
    alignItems: 'center',
    justifyContent: 'center',
    shadowColor: Color.black,
    shadowOpacity: 0.5,
    shadowOffset: { width: 0, height: 0 },
    shadowRadius: 1,
  },
});

MessageContainer.defaultProps = {
  messages: [],
  user: {},
  renderFooter: null,
  renderMessage: null,
  onLoadEarlier: () => {},
  inverted: true,
  loadEarlier: false,
  listViewProps: {},
  invertibleScrollViewProps: {},
  extraData: null,
  scrollToBottom: false,
  scrollToBottomOffset: 200,
};

MessageContainer.propTypes = {
  messages: PropTypes.arrayOf(PropTypes.object),
  user: PropTypes.object,
  renderFooter: PropTypes.func,
  renderMessage: PropTypes.func,
  renderLoadEarlier: PropTypes.func,
  onLoadEarlier: PropTypes.func,
  listViewProps: PropTypes.object,
  inverted: PropTypes.bool,
  loadEarlier: PropTypes.bool,
  invertibleScrollViewProps: PropTypes.object,
  extraData: PropTypes.object,
  scrollToBottom: PropTypes.bool,
  scrollToBottomOffset: PropTypes.number,
  scrollToBottomComponent: PropTypes.func,
};
<commit_msg>fix MessageContainer-Pure (#1101)<commit_after>/* eslint
    no-console: 0,
    no-param-reassign: 0,
    no-use-before-define: ["error", { "variables": false }],
    no-return-assign: 0,
    react/no-string-refs: 0,
    react/sort-comp: 0
*/

import PropTypes from 'prop-types';
import React from 'react';

import { FlatList, View, StyleSheet, Keyboard, TouchableOpacity, Text } from 'react-native';

import LoadEarlier from './LoadEarlier';
import Message from './Message';
import Color from './Color';

export default class MessageContainer extends React.PureComponent {

  state = {
    showScrollBottom: false,
  };

  componentDidMount() {
    if (this.props.messages.length === 0) {
      this.attachKeyboardListeners();
    }
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.messages.length === 0 && nextProps.messages.length > 0) {
      this.detachKeyboardListeners();
    } else if (this.props.messages.length > 0 && nextProps.messages.length === 0) {
      this.attachKeyboardListeners(nextProps);
    }
  }

  attachKeyboardListeners = () => {
    const { invertibleScrollViewProps: invertibleProps } = this.props;
    Keyboard.addListener('keyboardWillShow', invertibleProps.onKeyboardWillShow);
    Keyboard.addListener('keyboardDidShow', invertibleProps.onKeyboardDidShow);
    Keyboard.addListener('keyboardWillHide', invertibleProps.onKeyboardWillHide);
    Keyboard.addListener('keyboardDidHide', invertibleProps.onKeyboardDidHide);
  };

  detachKeyboardListeners = () => {
    const { invertibleScrollViewProps: invertibleProps } = this.props;
    Keyboard.removeListener('keyboardWillShow', invertibleProps.onKeyboardWillShow);
    Keyboard.removeListener('keyboardDidShow', invertibleProps.onKeyboardDidShow);
    Keyboard.removeListener('keyboardWillHide', invertibleProps.onKeyboardWillHide);
    Keyboard.removeListener('keyboardDidHide', invertibleProps.onKeyboardDidHide);
  };

  renderFooter = () => {
    if (this.props.renderFooter) {
      const footerProps = {
        ...this.props,
      };
      return this.props.renderFooter(footerProps);
    }
    return null;
  };

  renderLoadEarlier = () => {
    if (this.props.loadEarlier === true) {
      const loadEarlierProps = {
        ...this.props,
      };
      if (this.props.renderLoadEarlier) {
        return this.props.renderLoadEarlier(loadEarlierProps);
      }
      return <LoadEarlier {...loadEarlierProps} />;
    }
    return null;
  };

  scrollTo(options) {
    if (this.flatListRef && options) {
      this.flatListRef.scrollToOffset(options);
    }
  }

  scrollToBottom = () => {
    this.scrollTo({ offset: 0, animated: 'true' });
  };

  handleOnScroll = (event) => {
    if (event.nativeEvent.contentOffset.y > this.props.scrollToBottomOffset) {
      this.setState({ showScrollBottom: true });
    } else {
      this.setState({ showScrollBottom: false });
    }
  };

  renderRow = ({ item, index }) => {
    if (!item._id && item._id !== 0) {
      console.warn('GiftedChat: `_id` is missing for message', JSON.stringify(item));
    }
    if (!item.user) {
      if (!item.system) {
        console.warn('GiftedChat: `user` is missing for message', JSON.stringify(item));
      }
      item.user = {};
    }
    const { messages, ...restProps } = this.props;
    const previousMessage = messages[index + 1] || {};
    const nextMessage = messages[index - 1] || {};

    const messageProps = {
      ...restProps,
      key: item._id,
      currentMessage: item,
      previousMessage,
      nextMessage,
      position: item.user._id === this.props.user._id ? 'right' : 'left',
    };

    if (this.props.renderMessage) {
      return this.props.renderMessage(messageProps);
    }
    return <Message {...messageProps} />;
  };

  renderHeaderWrapper = () => <View style={styles.headerWrapper}>{this.renderLoadEarlier()}</View>;

  renderScrollToBottomWrapper() {
    const scrollToBottomComponent = (
      <View style={styles.scrollToBottomStyle}>
        <TouchableOpacity onPress={this.scrollToBottom} hitSlop={{ top: 5, left: 5, right: 5, bottom: 5 }}>
          <Text>V</Text>
        </TouchableOpacity>
      </View>
    );

    if (this.props.scrollToBottomComponent) {
      return (
        <TouchableOpacity onPress={this.scrollToBottom} hitSlop={{ top: 5, left: 5, right: 5, bottom: 5 }}>
          {this.props.scrollToBottomComponent}
        </TouchableOpacity>
      );
    }
    return scrollToBottomComponent;
  }

  keyExtractor = (item) => `${item._id}`;

  render() {
    if (this.props.messages.length === 0) {
      return <View style={styles.container} />;
    }
    return (
      <View style={styles.container}>
        {this.state.showScrollBottom && this.props.scrollToBottom ? this.renderScrollToBottomWrapper() : null}
        <FlatList
          ref={(ref) => (this.flatListRef = ref)}
          extraData={this.props.extraData}
          keyExtractor={this.keyExtractor}
          enableEmptySections
          automaticallyAdjustContentInsets={false}
          inverted={this.props.inverted}
          data={this.props.messages}
          style={styles.listStyle}
          contentContainerStyle={styles.contentContainerStyle}
          renderItem={this.renderRow}
          {...this.props.invertibleScrollViewProps}
          ListFooterComponent={this.renderHeaderWrapper}
          ListHeaderComponent={this.renderFooter}
          onScroll={this.handleOnScroll}
          scrollEventThrottle={100}
          {...this.props.listViewProps}
        />
      </View>
    );
  }

}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  contentContainerStyle: {
    justifyContent: 'flex-end',
  },
  headerWrapper: {
    flex: 1,
  },
  listStyle: {
    flex: 1,
  },
  scrollToBottomStyle: {
    opacity: 0.8,
    position: 'absolute',
    paddingHorizontal: 15,
    paddingVertical: 8,
    right: 10,
    bottom: 30,
    zIndex: 999,
    height: 40,
    width: 40,
    borderRadius: 20,
    backgroundColor: Color.white,
    alignItems: 'center',
    justifyContent: 'center',
    shadowColor: Color.black,
    shadowOpacity: 0.5,
    shadowOffset: { width: 0, height: 0 },
    shadowRadius: 1,
  },
});

MessageContainer.defaultProps = {
  messages: [],
  user: {},
  renderFooter: null,
  renderMessage: null,
  onLoadEarlier: () => {},
  inverted: true,
  loadEarlier: false,
  listViewProps: {},
  invertibleScrollViewProps: {},
  extraData: null,
  scrollToBottom: false,
  scrollToBottomOffset: 200,
};

MessageContainer.propTypes = {
  messages: PropTypes.arrayOf(PropTypes.object),
  user: PropTypes.object,
  renderFooter: PropTypes.func,
  renderMessage: PropTypes.func,
  renderLoadEarlier: PropTypes.func,
  onLoadEarlier: PropTypes.func,
  listViewProps: PropTypes.object,
  inverted: PropTypes.bool,
  loadEarlier: PropTypes.bool,
  invertibleScrollViewProps: PropTypes.object,
  extraData: PropTypes.object,
  scrollToBottom: PropTypes.bool,
  scrollToBottomOffset: PropTypes.number,
  scrollToBottomComponent: PropTypes.func,
};
<|endoftext|>


<commit_before>/*jslint browser: true */

// Export a global module.
var tangelo = {};

(function () {
    "use strict";

    // Tangelo version number.
    tangelo.version = function () {
        return "0.2.0";
    };

    tangelo.fatalError = function (module, msg) {
        if (msg === undefined) {
            msg = module;
            throw new Error(msg);
        }

        throw new Error("[" + module + "] " + msg);
    };

    // A function that generates an error-generating function, to be used for
    // missing dependencies (Google Maps API, JQuery UI, etc.).
    tangelo.unavailable = function (cfg) {
        var plugin = cfg.plugin,
            required = cfg.required,
            i,
            t;

        if (tangelo.isArray(required)) {
            if (required.length === 1) {
                required = required[0];
            } else if (required.length === 2) {
                required = required[0] + " and " + required[1];
            } else {
                t = "";
                for (i = 0; i < required.length - 1; i += 1) {
                    t += required[i] + ", ";
                }
                t += "and " + required[required.length - 1];

                required = t;
            }
        }

        return function () {
            tangelo.fatalError("JavaScript include error: " + plugin + " requires " + required);
        };
    };

    tangelo.identity = function (d) { return d; };

    tangelo.isNumber = function (value) {
        return typeof value === 'number';
    };

    tangelo.isBoolean = function (value) {
        return typeof value === 'boolean';
    };

    tangelo.isArray = function (value) {
        return Object.prototype.toString.call(value) === '[object Array]';
    };

    tangelo.isObject = function (value) {
        return Object.prototype.toString.call(value) === '[object Object]';
    };

    tangelo.isString = function (value) {
        return Object.prototype.toString.call(value) === '[object String]';
    };

    tangelo.isFunction = function (value) {
        return Object.prototype.toString.call(value) === '[object Function]';
    };

    tangelo.absoluteUrl = function (path) {
        if (path.length > 0) {
            if (path[0] !== "/" && path[0] !== "~") {
                path = window.location.pathname + "/" + path;
            }
        }

        return path;
    };

    tangelo.accessor = function (spec, defaultValue) {
        var parts;
        if (tangelo.isFunction(spec)) {
            return spec;
        }
        if (!spec) {
            return function () { return defaultValue; };
        }
        if (spec.hasOwnProperty("value")) {
            return function () { return spec.value; };
        }
        if (spec.hasOwnProperty("index")) {
            return function (d, i) { return i; };
        }
        if (spec.hasOwnProperty("field")) {
            if (spec.field === ".") {
                return tangelo.identity;
            }
            parts = spec.field.split(".");
            return function (d) {
                var i;
                for (i = 0; i < parts.length; i += 1) {
                    d = d[parts[i]];
                    if (d === undefined) {
                        return defaultValue;
                    }
                }
                return d;
            };
        }
        window.console.log("error: unknown accessor spec");
    };

    tangelo.appendFunction = function (f1, f2) {
        var that = this;
        if (!f1) {
            return f2;
        }
        if (!f2) {
            return f1;
        }
        return function () {
            f1.apply(that, arguments);
            f2.apply(that, arguments);
        };
    };

    // Check for the required version number.
    tangelo.requireCompatibleVersion = function (reqvstr) {
        var i,
            tanv,
            reqv,
            compatible,
            hasNaN = function (values) {
                var i;

                for (i = 0; i < values.length; i += 1) {
                    if (isNaN(values[i])) {
                        return true;
                    }
                }

                return false;
            };

        // Split the argument out into major, minor, and patch version numbers.
        reqv = reqvstr.split(".").map(function (x) { return +x; });

        // Check for: blank argument, too long argument, non-version-number
        // argument.
        if (reqv.length === 0 || reqv.length > 3 || hasNaN(reqv)) {
            tangelo.fatalError("tangelo.requireCompatibleVersion()", "illegal argument '" + reqvstr +  "'");
        }

        // Fill in any missing trailing values (i.e., "1.0" -> "1.0.0").
        for (i = reqv.length; i < 3; i += 1) {
            reqv[i] = 0;
        }

        // Split the Tangelo version number into major, minor, and patch level
        // as well.
        tanv = tangelo.version().split(".").map(function (x) { return +x; });

        // In order to be compatible above major version 0: (1) the major
        // versions MUST MATCH; (2) the required minor version MUST BE AT MOST
        // the Tangelo minor version number; and (3) the required patch level
        // MUST BE AT MOST the Tangelo patch level.
        //
        // For major version 0, in order to be compatible: (1) the major
        // versions MUST BOTH BE 0; (2) the minor versions MUST MATCH; (3) the
        // required patch level MUST BE AT MOST the Tangelo patch level.
        if (reqv[0] === 0) {
            compatible = tanv[0] === 0 && reqv[1] === tanv[1] && reqv[2] <= tanv[2];
        } else {
            compatible = reqv[0] === tanv[0] && reqv[1] <= tanv[1] && reqv[2] <= tanv[2];
        }

        return compatible;
    };

}(window.$));
<commit_msg>Making accessor spec failure into fatal error.<commit_after>/*jslint browser: true */

// Export a global module.
var tangelo = {};

(function () {
    "use strict";

    // Tangelo version number.
    tangelo.version = function () {
        return "0.2.0";
    };

    tangelo.fatalError = function (module, msg) {
        if (msg === undefined) {
            msg = module;
            throw new Error(msg);
        }

        throw new Error("[" + module + "] " + msg);
    };

    // A function that generates an error-generating function, to be used for
    // missing dependencies (Google Maps API, JQuery UI, etc.).
    tangelo.unavailable = function (cfg) {
        var plugin = cfg.plugin,
            required = cfg.required,
            i,
            t;

        if (tangelo.isArray(required)) {
            if (required.length === 1) {
                required = required[0];
            } else if (required.length === 2) {
                required = required[0] + " and " + required[1];
            } else {
                t = "";
                for (i = 0; i < required.length - 1; i += 1) {
                    t += required[i] + ", ";
                }
                t += "and " + required[required.length - 1];

                required = t;
            }
        }

        return function () {
            tangelo.fatalError("JavaScript include error: " + plugin + " requires " + required);
        };
    };

    tangelo.identity = function (d) { return d; };

    tangelo.isNumber = function (value) {
        return typeof value === 'number';
    };

    tangelo.isBoolean = function (value) {
        return typeof value === 'boolean';
    };

    tangelo.isArray = function (value) {
        return Object.prototype.toString.call(value) === '[object Array]';
    };

    tangelo.isObject = function (value) {
        return Object.prototype.toString.call(value) === '[object Object]';
    };

    tangelo.isString = function (value) {
        return Object.prototype.toString.call(value) === '[object String]';
    };

    tangelo.isFunction = function (value) {
        return Object.prototype.toString.call(value) === '[object Function]';
    };

    tangelo.absoluteUrl = function (path) {
        if (path.length > 0) {
            if (path[0] !== "/" && path[0] !== "~") {
                path = window.location.pathname + "/" + path;
            }
        }

        return path;
    };

    tangelo.accessor = function (spec, defaultValue) {
        var parts;
        if (tangelo.isFunction(spec)) {
            return spec;
        }
        if (!spec) {
            return function () { return defaultValue; };
        }
        if (spec.hasOwnProperty("value")) {
            return function () { return spec.value; };
        }
        if (spec.hasOwnProperty("index")) {
            return function (d, i) { return i; };
        }
        if (spec.hasOwnProperty("field")) {
            if (spec.field === ".") {
                return tangelo.identity;
            }
            parts = spec.field.split(".");
            return function (d) {
                var i;
                for (i = 0; i < parts.length; i += 1) {
                    d = d[parts[i]];
                    if (d === undefined) {
                        return defaultValue;
                    }
                }
                return d;
            };
        }
        tangelo.fatalError("tangelo.accessor()", "unknown accessor spec " + spec);
    };

    tangelo.appendFunction = function (f1, f2) {
        var that = this;
        if (!f1) {
            return f2;
        }
        if (!f2) {
            return f1;
        }
        return function () {
            f1.apply(that, arguments);
            f2.apply(that, arguments);
        };
    };

    // Check for the required version number.
    tangelo.requireCompatibleVersion = function (reqvstr) {
        var i,
            tanv,
            reqv,
            compatible,
            hasNaN = function (values) {
                var i;

                for (i = 0; i < values.length; i += 1) {
                    if (isNaN(values[i])) {
                        return true;
                    }
                }

                return false;
            };

        // Split the argument out into major, minor, and patch version numbers.
        reqv = reqvstr.split(".").map(function (x) { return +x; });

        // Check for: blank argument, too long argument, non-version-number
        // argument.
        if (reqv.length === 0 || reqv.length > 3 || hasNaN(reqv)) {
            tangelo.fatalError("tangelo.requireCompatibleVersion()", "illegal argument '" + reqvstr +  "'");
        }

        // Fill in any missing trailing values (i.e., "1.0" -> "1.0.0").
        for (i = reqv.length; i < 3; i += 1) {
            reqv[i] = 0;
        }

        // Split the Tangelo version number into major, minor, and patch level
        // as well.
        tanv = tangelo.version().split(".").map(function (x) { return +x; });

        // In order to be compatible above major version 0: (1) the major
        // versions MUST MATCH; (2) the required minor version MUST BE AT MOST
        // the Tangelo minor version number; and (3) the required patch level
        // MUST BE AT MOST the Tangelo patch level.
        //
        // For major version 0, in order to be compatible: (1) the major
        // versions MUST BOTH BE 0; (2) the minor versions MUST MATCH; (3) the
        // required patch level MUST BE AT MOST the Tangelo patch level.
        if (reqv[0] === 0) {
            compatible = tanv[0] === 0 && reqv[1] === tanv[1] && reqv[2] <= tanv[2];
        } else {
            compatible = reqv[0] === tanv[0] && reqv[1] <= tanv[1] && reqv[2] <= tanv[2];
        }

        return compatible;
    };

}(window.$));
<|endoftext|>


<commit_before>import moment from "moment"
import { pageable } from "relay-cursor-paging"
import { connectionFromArraySlice } from "graphql-relay"
import { isExisty, exclude, existyValue, parseRelayOptions } from "../lib/helpers"
import { find, has } from "lodash"
import gravity from "../lib/loaders/gravity"
import total from "../lib/loaders/total"
import numeral from "./fields/numeral"
import { exhibitionPeriod, exhibitionStatus } from "../lib/date"
import cached from "./fields/cached"
import date from "./fields/date"
import { markdown } from "./fields/markdown"
import Artist from "./artist"
import Partner from "./partner"
import ExternalPartner from "./external_partner"
import Fair from "./fair"
import Artwork, { artworkConnection } from "./artwork"
import Location from "./location"
import Image, { getDefault } from "./image"
import PartnerShowEventType from "./partner_show_event"
import { GravityIDFields, NodeInterface } from "./object_identification"
import {
  GraphQLObjectType,
  GraphQLString,
  GraphQLNonNull,
  GraphQLList,
  GraphQLInt,
  GraphQLBoolean,
  GraphQLUnionType,
} from "graphql"

const kind = ({ artists, fair, artists_without_artworks, group }) => {
  if (isExisty(fair)) return "fair"
  if (group || artists.length > 1 || (artists_without_artworks && artists_without_artworks.length > 1)) {
    return "group"
  }
  if (artists.length === 1 || (artists_without_artworks && artists_without_artworks.length === 1)) {
    return "solo"
  }
}

const artworksArgs = {
  size: {
    type: GraphQLInt,
    description: "Number of artworks to return",
    defaultValue: 25,
  },
  published: {
    type: GraphQLBoolean,
    defaultValue: true,
  },
  page: {
    type: GraphQLInt,
    defaultValue: 1,
  },
  all: {
    type: GraphQLBoolean,
    default: false,
  },
  for_sale: {
    type: GraphQLBoolean,
    default: false,
  },
  exclude: {
    type: new GraphQLList(GraphQLString),
    description: "List of artwork IDs to exclude from the response (irrespective of size)",
  },
}

const ShowType = new GraphQLObjectType({
  name: "Show",
  interfaces: [NodeInterface],
  isTypeOf: obj => has(obj, "is_reference") && has(obj, "display_on_partner_profile"),
  fields: () => ({
    ...GravityIDFields,
    cached,
    artists: {
      type: new GraphQLList(Artist.type),
      resolve: ({ artists }) => artists,
    },
    artworks: {
      type: new GraphQLList(Artwork.type),
      args: artworksArgs,
      resolve: (show, options) => {
        const path = `partner/${show.partner.id}/show/${show.id}/artworks`

        let fetch = null

        if (options.all) {
          fetch = gravity.all(path, options)
        } else {
          fetch = gravity(path, options)
        }

        return fetch.then(exclude(options.exclude, "id"))
      },
    },
    artworks_connection: {
      type: artworkConnection,
      args: pageable(artworksArgs),
      resolve: (show, options) => {
        const path = `partner/${show.partner.id}/show/${show.id}/artworks`
        const gravityOptions = parseRelayOptions(options)
        delete gravityOptions.page

        return total(path, Object.assign({}, gravityOptions, { size: 0 })).then(count => {
          return gravity(path, gravityOptions)
          .then((body) => {
            return connectionFromArraySlice(body, options, {
              arrayLength: count,
              sliceStart: gravityOptions.offset,
            })
          })
        })
      },
    },
    artists_without_artworks: {
      type: new GraphQLList(Artist.type),
      resolve: ({ artists_without_artworks }) => artists_without_artworks,
    },
    city: {
      type: GraphQLString,
      resolve: ({ location, partner_city }) => {
        if (location && isExisty(location.city)) {
          return location.city
        }
        return existyValue(partner_city)
      },
    },
    cover_image: {
      type: Image.type,
      resolve: ({ id, partner, image_versions, image_url }) => {
        if (image_versions && image_versions.length && image_url) {
          return Image.resolve({ image_versions, image_url })
        }

        if (partner) {
          return gravity(`partner/${partner.id}/show/${id}/artworks`, {
            size: 1,
            published: true,
          }).then(artworks => {
            const artwork = artworks[0]
            return artwork && Image.resolve(getDefault(artwork.images))
          })
        }
      },
    },
    counts: {
      type: new GraphQLObjectType({
        name: "ShowCounts",
        fields: {
          artworks: {
            type: GraphQLInt,
            args: {
              artist_id: {
                type: GraphQLString,
                description: "The slug or ID of an artist in the show.",
              },
            },
            resolve: ({ id, partner }, options) => {
              return total(`partner/${partner.id}/show/${id}/artworks`, options)
            },
          },
          eligible_artworks: numeral(({ eligible_artworks_count }) => eligible_artworks_count),
        },
      }),
      resolve: partner_show => partner_show,
    },
    description: {
      type: GraphQLString,
    },
    displayable: {
      type: GraphQLBoolean,
      deprecationReason: "Prefix Boolean returning fields with `is_`",
    },
    end_at: date,
    events: {
      type: new GraphQLList(PartnerShowEventType),
      resolve: ({ partner, id }) =>
        // Gravity redirects from /api/v1/show/:id => /api/v1/partner/:partner_id/show/:show_id
        // this creates issues where events will remain cached. Fetch the non-redirected
        // route to circumvent this
        gravity(`partner/${partner.id}/show/${id}`).then(({ events }) => events),
    },
    exhibition_period: {
      type: GraphQLString,
      description: "A formatted description of the start to end dates",
      resolve: ({ start_at, end_at }) => exhibitionPeriod(start_at, end_at),
    },
    fair: {
      type: Fair.type,
      resolve: ({ fair }) => fair,
    },
    href: {
      type: GraphQLString,
      resolve: ({ id, is_reference, displayable }) => {
        if (is_reference || !displayable) return null
        return `/show/${id}`
      },
    },
    images: {
      type: new GraphQLList(Image.type),
      args: {
        size: {
          type: GraphQLInt,
          description: "Number of images to return",
        },
        default: {
          type: GraphQLBoolean,
          description: "Pass true/false to include cover or not",
        },
        page: {
          type: GraphQLInt,
        },
      },
      resolve: ({ id }, options) => {
        return gravity(`partner_show/${id}/images`, options).then(Image.resolve)
      },
    },
    is_active: {
      type: GraphQLBoolean,
      description: "Gravity doesn’t expose the `active` flag. Temporarily re-state its logic.",
      resolve: ({ start_at, end_at }) => {
        const start = moment.utc(start_at).subtract(7, "days")
        const end = moment.utc(end_at).add(7, "days")
        return moment.utc().isBetween(start, end)
      },
    },
    is_displayable: {
      type: GraphQLBoolean,
      resolve: ({ displayable }) => displayable,
    },
    is_fair_booth: {
      type: GraphQLBoolean,
      resolve: ({ fair }) => isExisty(fair),
    },
    is_reference: {
      type: GraphQLBoolean,
      resolve: ({ is_reference }) => is_reference,
    },
    kind: {
      type: GraphQLString,
      resolve: show => {
        if (show.artists || show.artists_without_artworks) return kind(show)
        return gravity(`partner/${show.partner.id}/show/${show.id}`).then(kind)
      },
    },
    location: {
      type: Location.type,
      resolve: ({ location, fair_location }) => location || fair_location,
    },
    meta_image: {
      type: Image.type,
      resolve: ({ id, partner, image_versions, image_url }) => {
        if (image_versions && image_versions.length && image_url) {
          return Image.resolve({ image_versions, image_url })
        }

        return gravity(`partner/${partner.id}/show/${id}/artworks`, {
          published: true,
        }).then(artworks => {
          Image.resolve(getDefault(find(artworks, { can_share_image: true })))
        })
      },
    },
    name: {
      type: GraphQLString,
      description: "The exhibition title",
      resolve: ({ name }) => (isExisty(name) ? name.trim() : name),
    },
    partner: {
      type: new GraphQLUnionType({
        name: "PartnerTypes",
        types: [Partner.type, ExternalPartner.type],
        resolveType: value => {
          if (value._links) {
            return ExternalPartner.type
          }
          return Partner.type
        },
      }),
      resolve: ({ partner, galaxy_partner_id }) => {
        if (partner) {
          return partner
        }
        if (galaxy_partner_id) {
          return ExternalPartner.resolve(galaxy_partner_id)
        }
      },
    },
    press_release: markdown(),
    start_at: date,
    status: {
      type: GraphQLString,
    },
    status_update: {
      type: GraphQLString,
      description: "A formatted update on upcoming status changes",
      args: {
        max_days: {
          type: GraphQLInt,
          description: "Before this many days no update will be generated",
        },
      },
      resolve: ({ start_at, end_at }, options) => exhibitionStatus(start_at, end_at, options.max_days),
    },
    type: {
      type: GraphQLString,
      resolve: ({ fair }) => (isExisty(fair) ? "Fair Booth" : "Show"),
    },
  }),
})

const Show = {
  type: ShowType,
  description: "A Show",
  args: {
    id: {
      type: new GraphQLNonNull(GraphQLString),
      description: "The slug or ID of the Show",
    },
  },
  resolve: (root, { id }) => {
    return gravity(`show/${id}`)
      .then(show => {
        if (!show.displayable && !show.is_reference) return new Error("Show Not Found")
        return show
      })
      .catch(() => null)
  },
}

export default Show
<commit_msg>Get count and results in parallel<commit_after>import moment from "moment"
import { pageable } from "relay-cursor-paging"
import { connectionFromArraySlice } from "graphql-relay"
import { isExisty, exclude, existyValue, parseRelayOptions } from "../lib/helpers"
import { find, has } from "lodash"
import gravity from "../lib/loaders/gravity"
import total from "../lib/loaders/total"
import numeral from "./fields/numeral"
import { exhibitionPeriod, exhibitionStatus } from "../lib/date"
import cached from "./fields/cached"
import date from "./fields/date"
import { markdown } from "./fields/markdown"
import Artist from "./artist"
import Partner from "./partner"
import ExternalPartner from "./external_partner"
import Fair from "./fair"
import Artwork, { artworkConnection } from "./artwork"
import Location from "./location"
import Image, { getDefault } from "./image"
import PartnerShowEventType from "./partner_show_event"
import { GravityIDFields, NodeInterface } from "./object_identification"
import {
  GraphQLObjectType,
  GraphQLString,
  GraphQLNonNull,
  GraphQLList,
  GraphQLInt,
  GraphQLBoolean,
  GraphQLUnionType,
} from "graphql"

const kind = ({ artists, fair, artists_without_artworks, group }) => {
  if (isExisty(fair)) return "fair"
  if (group || artists.length > 1 || (artists_without_artworks && artists_without_artworks.length > 1)) {
    return "group"
  }
  if (artists.length === 1 || (artists_without_artworks && artists_without_artworks.length === 1)) {
    return "solo"
  }
}

const artworksArgs = {
  size: {
    type: GraphQLInt,
    description: "Number of artworks to return",
    defaultValue: 25,
  },
  published: {
    type: GraphQLBoolean,
    defaultValue: true,
  },
  page: {
    type: GraphQLInt,
    defaultValue: 1,
  },
  all: {
    type: GraphQLBoolean,
    default: false,
  },
  for_sale: {
    type: GraphQLBoolean,
    default: false,
  },
  exclude: {
    type: new GraphQLList(GraphQLString),
    description: "List of artwork IDs to exclude from the response (irrespective of size)",
  },
}

const ShowType = new GraphQLObjectType({
  name: "Show",
  interfaces: [NodeInterface],
  isTypeOf: obj => has(obj, "is_reference") && has(obj, "display_on_partner_profile"),
  fields: () => ({
    ...GravityIDFields,
    cached,
    artists: {
      type: new GraphQLList(Artist.type),
      resolve: ({ artists }) => artists,
    },
    artworks: {
      type: new GraphQLList(Artwork.type),
      args: artworksArgs,
      resolve: (show, options) => {
        const path = `partner/${show.partner.id}/show/${show.id}/artworks`

        let fetch = null

        if (options.all) {
          fetch = gravity.all(path, options)
        } else {
          fetch = gravity(path, options)
        }

        return fetch.then(exclude(options.exclude, "id"))
      },
    },
    artworks_connection: {
      type: artworkConnection,
      args: pageable(artworksArgs),
      resolve: (show, options) => {
        const path = `partner/${show.partner.id}/show/${show.id}/artworks`
        const gravityOptions = parseRelayOptions(options)
        delete gravityOptions.page

        return Promise.all([
          total(path, Object.assign({}, gravityOptions, { size: 0 })),
          gravity(path, gravityOptions),
        ]).then(([count, body]) => {
          return connectionFromArraySlice(body, options, {
            arrayLength: count,
            sliceStart: gravityOptions.offset,
          })
        })
      },
    },
    artists_without_artworks: {
      type: new GraphQLList(Artist.type),
      resolve: ({ artists_without_artworks }) => artists_without_artworks,
    },
    city: {
      type: GraphQLString,
      resolve: ({ location, partner_city }) => {
        if (location && isExisty(location.city)) {
          return location.city
        }
        return existyValue(partner_city)
      },
    },
    cover_image: {
      type: Image.type,
      resolve: ({ id, partner, image_versions, image_url }) => {
        if (image_versions && image_versions.length && image_url) {
          return Image.resolve({ image_versions, image_url })
        }

        if (partner) {
          return gravity(`partner/${partner.id}/show/${id}/artworks`, {
            size: 1,
            published: true,
          }).then(artworks => {
            const artwork = artworks[0]
            return artwork && Image.resolve(getDefault(artwork.images))
          })
        }
      },
    },
    counts: {
      type: new GraphQLObjectType({
        name: "ShowCounts",
        fields: {
          artworks: {
            type: GraphQLInt,
            args: {
              artist_id: {
                type: GraphQLString,
                description: "The slug or ID of an artist in the show.",
              },
            },
            resolve: ({ id, partner }, options) => {
              return total(`partner/${partner.id}/show/${id}/artworks`, options)
            },
          },
          eligible_artworks: numeral(({ eligible_artworks_count }) => eligible_artworks_count),
        },
      }),
      resolve: partner_show => partner_show,
    },
    description: {
      type: GraphQLString,
    },
    displayable: {
      type: GraphQLBoolean,
      deprecationReason: "Prefix Boolean returning fields with `is_`",
    },
    end_at: date,
    events: {
      type: new GraphQLList(PartnerShowEventType),
      resolve: ({ partner, id }) =>
        // Gravity redirects from /api/v1/show/:id => /api/v1/partner/:partner_id/show/:show_id
        // this creates issues where events will remain cached. Fetch the non-redirected
        // route to circumvent this
        gravity(`partner/${partner.id}/show/${id}`).then(({ events }) => events),
    },
    exhibition_period: {
      type: GraphQLString,
      description: "A formatted description of the start to end dates",
      resolve: ({ start_at, end_at }) => exhibitionPeriod(start_at, end_at),
    },
    fair: {
      type: Fair.type,
      resolve: ({ fair }) => fair,
    },
    href: {
      type: GraphQLString,
      resolve: ({ id, is_reference, displayable }) => {
        if (is_reference || !displayable) return null
        return `/show/${id}`
      },
    },
    images: {
      type: new GraphQLList(Image.type),
      args: {
        size: {
          type: GraphQLInt,
          description: "Number of images to return",
        },
        default: {
          type: GraphQLBoolean,
          description: "Pass true/false to include cover or not",
        },
        page: {
          type: GraphQLInt,
        },
      },
      resolve: ({ id }, options) => {
        return gravity(`partner_show/${id}/images`, options).then(Image.resolve)
      },
    },
    is_active: {
      type: GraphQLBoolean,
      description: "Gravity doesn’t expose the `active` flag. Temporarily re-state its logic.",
      resolve: ({ start_at, end_at }) => {
        const start = moment.utc(start_at).subtract(7, "days")
        const end = moment.utc(end_at).add(7, "days")
        return moment.utc().isBetween(start, end)
      },
    },
    is_displayable: {
      type: GraphQLBoolean,
      resolve: ({ displayable }) => displayable,
    },
    is_fair_booth: {
      type: GraphQLBoolean,
      resolve: ({ fair }) => isExisty(fair),
    },
    is_reference: {
      type: GraphQLBoolean,
      resolve: ({ is_reference }) => is_reference,
    },
    kind: {
      type: GraphQLString,
      resolve: show => {
        if (show.artists || show.artists_without_artworks) return kind(show)
        return gravity(`partner/${show.partner.id}/show/${show.id}`).then(kind)
      },
    },
    location: {
      type: Location.type,
      resolve: ({ location, fair_location }) => location || fair_location,
    },
    meta_image: {
      type: Image.type,
      resolve: ({ id, partner, image_versions, image_url }) => {
        if (image_versions && image_versions.length && image_url) {
          return Image.resolve({ image_versions, image_url })
        }

        return gravity(`partner/${partner.id}/show/${id}/artworks`, {
          published: true,
        }).then(artworks => {
          Image.resolve(getDefault(find(artworks, { can_share_image: true })))
        })
      },
    },
    name: {
      type: GraphQLString,
      description: "The exhibition title",
      resolve: ({ name }) => (isExisty(name) ? name.trim() : name),
    },
    partner: {
      type: new GraphQLUnionType({
        name: "PartnerTypes",
        types: [Partner.type, ExternalPartner.type],
        resolveType: value => {
          if (value._links) {
            return ExternalPartner.type
          }
          return Partner.type
        },
      }),
      resolve: ({ partner, galaxy_partner_id }) => {
        if (partner) {
          return partner
        }
        if (galaxy_partner_id) {
          return ExternalPartner.resolve(galaxy_partner_id)
        }
      },
    },
    press_release: markdown(),
    start_at: date,
    status: {
      type: GraphQLString,
    },
    status_update: {
      type: GraphQLString,
      description: "A formatted update on upcoming status changes",
      args: {
        max_days: {
          type: GraphQLInt,
          description: "Before this many days no update will be generated",
        },
      },
      resolve: ({ start_at, end_at }, options) => exhibitionStatus(start_at, end_at, options.max_days),
    },
    type: {
      type: GraphQLString,
      resolve: ({ fair }) => (isExisty(fair) ? "Fair Booth" : "Show"),
    },
  }),
})

const Show = {
  type: ShowType,
  description: "A Show",
  args: {
    id: {
      type: new GraphQLNonNull(GraphQLString),
      description: "The slug or ID of the Show",
    },
  },
  resolve: (root, { id }) => {
    return gravity(`show/${id}`)
      .then(show => {
        if (!show.displayable && !show.is_reference) return new Error("Show Not Found")
        return show
      })
      .catch(() => null)
  },
}

export default Show
<|endoftext|>


<commit_before>/* global history */
var categoryEndpoint = require('./category-endpoint')
var urlParameter = require('./get-url-parameter')
var browser = require('./browser')

var marked = require('marked')

var FindHelp = function () {
  var self = this

  self.getLocation = function () {
    var location = urlParameter.parameter('location')
    var savedLocationCookie = document.cookie.replace(/(?:(?:^|.*;\s*)desired-location\s*\=\s*([^;]*).*$)|^.*$/, '$1')
    if (savedLocationCookie.length && location.length === 0) return savedLocationCookie
    if (location === 'my-location') return ''
    return location
  }

  self.setUrl = function (pageName, subCategoryKey, subCategoryId) {
    history.pushState({}, '', '?category=' + self.theCategory +
      '&' + subCategoryKey + '=' + subCategoryId +
      '&location=' + self.getLocation())
  }

  self.scrollTo = (subCategoryId) => {
    window.setTimeout(() => {
      browser.scrollTo('#' + subCategoryId)
    }, 300)
  }

  self.buildListener = function (pageName, subCategoryKey) {
    return {
      accordionOpened: function (element, context) {
        const subCategoryId = element.getAttribute('id')
        self.setUrl(pageName, subCategoryKey, subCategoryId)
        self.scrollTo(subCategoryId)
      }
    }
  }

  self.handleSubCategoryChange = function (subCategoryKey, accordion) {
    window.onpopstate = function () {
      var subCategory = urlParameter.parameter(subCategoryKey)
      if (subCategory.length) {
        var el = document.getElementById(subCategory)
        var context = document.querySelector('.js-accordion')
        var useAnalytics = true

        accordion.reOpen(el, context, useAnalytics)
      }
    }
  }

  self.formatTags = function (subCategories) {
    subCategories.forEach((subCat) => {
      subCat.serviceProviders.forEach((provider) => {
        if (provider.tags !== null) {
          provider.tags = provider.tags.join(', ')
        }
      })
    })
  }

  self.theCategory = urlParameter.parameter('category')

  self.buildCategories = function (endpoint, buildList) {
    var categoryUrl = endpoint += self.theCategory

    categoryEndpoint.getEndpointUrl(categoryUrl, self.getLocation())
      .then(function (success) {
        buildList(success)
      }, function () {
      })
  }

  self.buildViewModel = function (pagename, data) {
    var hasSetManchesterAsLocation = self.getLocation() === 'manchester'

    // data.synopsis = marked(data.synopsis)

    return {
      organisations: data,
      categoryName: data[0].categoryName,
      categorySynopsis: marked(data[0].categorySynopsis),
      pageAsFromManchester: '?category=' + self.theCategory + '&location=manchester',
      pageFromCurrentLocation: '?category=' + self.theCategory + '&location=my-location',
      useManchesterAsLocation: hasSetManchesterAsLocation,
      useGeoLocation: !hasSetManchesterAsLocation
    }
  }
}

module.exports = FindHelp
<commit_msg>remove comment<commit_after>/* global history */
var categoryEndpoint = require('./category-endpoint')
var urlParameter = require('./get-url-parameter')
var browser = require('./browser')

var marked = require('marked')

var FindHelp = function () {
  var self = this

  self.getLocation = function () {
    var location = urlParameter.parameter('location')
    var savedLocationCookie = document.cookie.replace(/(?:(?:^|.*;\s*)desired-location\s*\=\s*([^;]*).*$)|^.*$/, '$1')
    if (savedLocationCookie.length && location.length === 0) return savedLocationCookie
    if (location === 'my-location') return ''
    return location
  }

  self.setUrl = function (pageName, subCategoryKey, subCategoryId) {
    history.pushState({}, '', '?category=' + self.theCategory +
      '&' + subCategoryKey + '=' + subCategoryId +
      '&location=' + self.getLocation())
  }

  self.scrollTo = (subCategoryId) => {
    window.setTimeout(() => {
      browser.scrollTo('#' + subCategoryId)
    }, 300)
  }

  self.buildListener = function (pageName, subCategoryKey) {
    return {
      accordionOpened: function (element, context) {
        const subCategoryId = element.getAttribute('id')
        self.setUrl(pageName, subCategoryKey, subCategoryId)
        self.scrollTo(subCategoryId)
      }
    }
  }

  self.handleSubCategoryChange = function (subCategoryKey, accordion) {
    window.onpopstate = function () {
      var subCategory = urlParameter.parameter(subCategoryKey)
      if (subCategory.length) {
        var el = document.getElementById(subCategory)
        var context = document.querySelector('.js-accordion')
        var useAnalytics = true

        accordion.reOpen(el, context, useAnalytics)
      }
    }
  }

  self.formatTags = function (subCategories) {
    subCategories.forEach((subCat) => {
      subCat.serviceProviders.forEach((provider) => {
        if (provider.tags !== null) {
          provider.tags = provider.tags.join(', ')
        }
      })
    })
  }

  self.theCategory = urlParameter.parameter('category')

  self.buildCategories = function (endpoint, buildList) {
    var categoryUrl = endpoint += self.theCategory

    categoryEndpoint.getEndpointUrl(categoryUrl, self.getLocation())
      .then(function (success) {
        buildList(success)
      }, function () {
      })
  }

  self.buildViewModel = function (pagename, data) {
    var hasSetManchesterAsLocation = self.getLocation() === 'manchester'

    return {
      organisations: data,
      categoryName: data[0].categoryName,
      categorySynopsis: marked(data[0].categorySynopsis),
      pageAsFromManchester: '?category=' + self.theCategory + '&location=manchester',
      pageFromCurrentLocation: '?category=' + self.theCategory + '&location=my-location',
      useManchesterAsLocation: hasSetManchesterAsLocation,
      useGeoLocation: !hasSetManchesterAsLocation
    }
  }
}

module.exports = FindHelp
<|endoftext|>


<commit_before>/*global define: true */
/*jslint browser:true  vars: true */
define([
    'bluebird',
    './asyncQueue',
    './lang'
],
    function (Promise, asyncQueue, lang) {
        function factory(config) {
            // Very simple message system.
            var channels = {},
                listeners = {},
                subId = 0,
                queue = asyncQueue.make();

            function nextSubId() {
                subId += 1;
                return 'sub_' + subId;
            }
            function fail(msg) {
                throw new Error(msg);
            }
            function receive(subDef) {
                var channelName = subDef.chan || subDef.channel || 'default',
                    messageName = subDef.msg || subDef.message || fail('Message is required for a sub');

                // Get the channel, and create it if it doesn't exist.
                var channel = channels[channelName];
                if (!channel) {
                    channel = {
                        messages: {}
                    };
                    channels[channelName] = channel;
                }

                // Get the message definitions for this message, create if doesn't exist
                var messageListener = channel.messages[messageName];
                if (!messageListener) {
                    messageListener = {
                        listeners: [],
                        byId: {}
                    };
                    channel.messages[messageName] = messageListener;
                }

                // Add our message definition
                var subId = nextSubId();
                subDef.subId = subId;
                messageListener.byId[subId] = subDef;
                messageListener.listeners.push(subDef);
                return {
                    chan: channelName,
                    msg: messageName,
                    id: subId
                };
            }
            function unreceive(sub) {
                var channel = channels[sub.chan];
                if (!channel) {
                    return false;
                }
                var messageListener = channel.messages[sub.msg];
                if (!messageListener) {
                    return false;
                }

                var subDef = messageListener.byId[sub.id];
                if (!subDef) {
                    return false;
                }
                delete messageListener.byId[sub.id];
                messageListener.listeners = messageListener.listeners.filter(function (item) {
                    if (item.subId === sub.id) {
                        return false;
                    }
                    return true;
                });
                return true;
            }
            // syncronous publication
            // TODO: async version.
            function emptyPromiseList() {
                return [Promise.resolve()];
            }
            function send(pubDef) {
                var channelName = pubDef.chan || pubDef.channel,
                    messageName = pubDef.msg || pubDef.message;

                var channel = channels[channelName];
                if (!channel) {
                    return;
                }
                var messageListener = channel.messages[messageName];
                if (!messageListener) {
                    return;
                }

                messageListener.listeners
                    .forEach(function (subDef) {
                        queue.addItem({
                            onRun: function () {
                                try {
                                    subDef.handler(pubDef.data);
                                } catch (ex) {
                                    console.error(ex);
                                    throw new lang.UIError({
                                        type: 'RuntimeError',
                                        reason: 'MessageHandlerError',
                                        message: 'Exception running message ' + messageName + ', sub ' + subId,
                                        data: ex,
                                        suggestion: 'This is an application error, not your fault'
                                    });
                                }
                            }
                        });
                });
            }
            function sendPromise(pubDef) {
                var channelName = pubDef.chan || pubDef.channel,
                    messageName = pubDef.msg || pubDef.message;

                var channel = channels[channelName];
                if (!channel) {
                    if (pubDef.propogate) {
                        return emptyPromiseList();
                    }
                }
                var messageListener = channel.messages[messageName];
                if (!messageListener) {
                    if (pubDef.propogate) {
                        return emptyPromiseList();
                    }
                }

                var listeners = messageListener.listeners;
                return Promise.all(listeners.map(function (subDef) {
                    return new Promise(function (resolve, reject) {
                        queue.addItem({
                            onRun: function () {
                                try {
                                    resolve(subDef.handler(pubDef.data));
                                } catch (ex) {
                                    console.error(ex);
                                    reject(new lang.UIError({
                                        type: 'RuntimeError',
                                        reason: 'MessageHandlerError',
                                        message: 'Exception running message ' + messageName + ', sub ' + subId,
                                        data: ex,
                                        suggestion: 'This is an application error, not your fault'
                                    }));
                                }
                            },
                            onError: function (err) {
                                reject(err);
                            }
                        });
                    });
                }).map(function (promise) {
                    return promise.reflect();
                }));
            }
            return {
                receive: receive,
                unreceive: unreceive,
                send: send,
                sendPromise: sendPromise
            };
        }
        ;

        return {
            make: function (config) {
                return factory(config);
            }
        };

    });<commit_msg>remove comment, code cleanup<commit_after>/*global define: true */
/*jslint browser:true  vars: true */
define([
        'bluebird',
        './asyncQueue',
        './lang'
    ],
    function (Promise, asyncQueue, lang) {
        function factory(config) {
            // Very simple message system.
            var channels = {},
                listeners = {},
                subId = 0,
                queue = asyncQueue.make();

            function nextSubId() {
                subId += 1;
                return 'sub_' + subId;
            }

            function fail(msg) {
                throw new Error(msg);
            }

            function receive(subDef) {
                var channelName = subDef.chan || subDef.channel || 'default',
                    messageName = subDef.msg || subDef.message || fail('Message is required for a sub');

                // Get the channel, and create it if it doesn't exist.
                var channel = channels[channelName];
                if (!channel) {
                    channel = {
                        messages: {}
                    };
                    channels[channelName] = channel;
                }

                // Get the message definitions for this message, create if doesn't exist
                var messageListener = channel.messages[messageName];
                if (!messageListener) {
                    messageListener = {
                        listeners: [],
                        byId: {}
                    };
                    channel.messages[messageName] = messageListener;
                }

                // Add our message definition
                var subId = nextSubId();
                subDef.subId = subId;
                messageListener.byId[subId] = subDef;
                messageListener.listeners.push(subDef);
                return {
                    chan: channelName,
                    msg: messageName,
                    id: subId
                };
            }

            function unreceive(sub) {
                var channel = channels[sub.chan];
                if (!channel) {
                    return false;
                }
                var messageListener = channel.messages[sub.msg];
                if (!messageListener) {
                    return false;
                }

                var subDef = messageListener.byId[sub.id];
                if (!subDef) {
                    return false;
                }
                delete messageListener.byId[sub.id];
                messageListener.listeners = messageListener.listeners.filter(function (item) {
                    if (item.subId === sub.id) {
                        return false;
                    }
                    return true;
                });
                return true;
            }
            // syncronous publication
            // TODO: async version.
            function emptyPromiseList() {
                return [Promise.resolve()];
            }

            function send(pubDef) {
                var channelName = pubDef.chan || pubDef.channel,
                    messageName = pubDef.msg || pubDef.message;

                var channel = channels[channelName];
                if (!channel) {
                    return;
                }
                var messageListener = channel.messages[messageName];
                if (!messageListener) {
                    return;
                }

                messageListener.listeners
                    .forEach(function (subDef) {
                        queue.addItem({
                            onRun: function () {
                                try {
                                    subDef.handler(pubDef.data);
                                } catch (ex) {
                                    console.error(ex);
                                    throw new lang.UIError({
                                        type: 'RuntimeError',
                                        reason: 'MessageHandlerError',
                                        message: 'Exception running message ' + messageName + ', sub ' + subId,
                                        data: ex,
                                        suggestion: 'This is an application error, not your fault'
                                    });
                                }
                            }
                        });
                    });
            }

            function sendPromise(pubDef) {
                var channelName = pubDef.chan || pubDef.channel,
                    messageName = pubDef.msg || pubDef.message;

                var channel = channels[channelName];
                if (!channel) {
                    if (pubDef.propogate) {
                        return emptyPromiseList();
                    }
                }
                var messageListener = channel.messages[messageName];
                if (!messageListener) {
                    if (pubDef.propogate) {
                        return emptyPromiseList();
                    }
                    // console.log('should handle this...', messageName);
                }

                var listeners = messageListener.listeners;
                return Promise.all(listeners.map(function (subDef) {
                    return new Promise(function (resolve, reject) {
                        queue.addItem({
                            onRun: function () {
                                try {
                                    resolve(subDef.handler(pubDef.data));
                                } catch (ex) {
                                    console.error(ex);
                                    reject(new lang.UIError({
                                        type: 'RuntimeError',
                                        reason: 'MessageHandlerError',
                                        message: 'Exception running message ' + messageName + ', sub ' + subId,
                                        data: ex,
                                        suggestion: 'This is an application error, not your fault'
                                    }));
                                }
                            },
                            onError: function (err) {
                                reject(err);
                            }
                        });
                    });
                }).map(function (promise) {
                    return promise.reflect();
                }));
            }
            return {
                receive: receive,
                unreceive: unreceive,
                send: send,
                sendPromise: sendPromise
            };
        }

        return {
            make: function (config) {
                return factory(config);
            }
        };

    });<|endoftext|>