text: import PropTypes from 'prop-types'
  import clsx from 'clsx'
  import * as animationFrame from 'dom-helpers/animationFrame'
  import React, { Component } from 'react'
  import { findDOMNode } from 'react-dom'
  import memoize from 'memoize-one'
  
  import * as dates from './utils/dates'
  import DayColumn from './DayColumn'
  import TimeGutter from './TimeGutter'
  
  import getWidth from 'dom-helpers/width'
  import TimeGridHeader from './TimeGridHeader'
  import { notify } from './utils/helpers'
  import { inRange, sortEvents } from './utils/eventLevels'
  import Resources from './utils/Resources'
  import { DayLayoutAlgorithmPropType } from './utils/propTypes'
  
  export default class TimeGrid extends Component {
    constructor(props) {
      super(props)
  
      this.state = { gutterWidth: undefined, isOverflowing: null }
  
      this.scrollRef = React.createRef()
      this.contentRef = React.createRef()
      this._scrollRatio = null
    }
  
    UNSAFE_componentWillMount = () => {
      this.calculateScroll()
    }
  
    componentDidMount = () => {
      this.checkOverflow()
  
      if (this.props.width == null) {
        this.measureGutter()
      }
  
      this.applyScroll()
  
      window.addEventListener('resize', this.handleResize)
    }
  
    handleScroll = (e) => {
      if (this.scrollRef.current) {
        this.scrollRef.current.scrollLeft = e.target.scrollLeft
      }
    }
  
    handleResize = () => {
      animationFrame.cancel(this.rafHandle)
      this.rafHandle = animationFrame.request(this.checkOverflow)
    }
  
    componentWillUnmount = () => {
      window.removeEventListener('resize', this.handleResize)
  
      animationFrame.cancel(this.rafHandle)
  
      if (this.measureGutterAnimationFrameRequest) {
        window.cancelAnimationFrame(this.measureGutterAnimationFrameRequest)
      }
    }
  
    componentDidUpdate = () => {
      if (this.props.width == null) {
        this.measureGutter()
      }
  
      this.applyScroll()
      //this.checkOverflow()
    }
  
    UNSAFE_componentWillReceiveProps = (nextProps) => {
      const { range, scrollToTime } = this.props
      // When paginating, reset scroll
      if (
        !dates.eq(nextProps.range[0], range[0], 'minute') ||
        !dates.eq(nextProps.scrollToTime, scrollToTime, 'minute')
      ) {
        this.calculateScroll(nextProps)
      }
    }
  
    gutterRef = (ref) => {
      this.gutter = ref && findDOMNode(ref)
    }
  
    handleSelectAlldayEvent = (...args) => {
      //cancel any pending selections so only the event click goes through.
      this.clearSelection()
      notify(this.props.onSelectEvent, args)
    }
  
    handleSelectAllDaySlot = (slots, slotInfo) => {
      const { onSelectSlot } = this.props
      notify(onSelectSlot, {
        slots,
        start: slots[0],
        end: slots[slots.length - 1],
        action: slotInfo.action,
        resourceId: slotInfo.resourceId,
      })
    }
  
    renderEvents(range, events, backgroundEvents, now) {
      let { min, max, components, accessors, localizer, dayLayoutAlgorithm } =
        this.props
  
      const resources = this.memoizedResources(this.props.resources, accessors)
      const groupedEvents = resources.groupEvents(events)
      const groupedBackgroundEvents = resources.groupEvents(backgroundEvents)
  
      return resources.map(([id, resource], i) =>
        range.map((date, jj) => {
          let daysEvents = (groupedEvents.get(id) || []).filter((event) =>
            dates.inRange(
              date,
              accessors.start(event),
              accessors.end(event),
              'day'
            )
          )
  
          let daysBackgroundEvents = (
            groupedBackgroundEvents.get(id) || []
          ).filter((event) =>
            dates.inRange(
              date,
              accessors.start(event),
              accessors.end(event),
              'day'
            )
          )
  
          return (
            <DayColumn
              {...this.props}
              localizer={localizer}
              min={dates.merge(date, min)}
              max={dates.merge(date, max)}
              resource={resource && id}
              components={components}
              isNow={dates.eq(date, now, 'day')}
              key={i + '-' + jj}
              date={date}
              events={daysEvents}
              backgroundEvents={daysBackgroundEvents}
              dayLayoutAlgorithm={dayLayoutAlgorithm}
            />
          )
        })
      )
    }
  
    render() {
      let {
        events,
        backgroundEvents,
        range,
        width,
        rtl,
        selected,
        getNow,
        resources,
        components,
        accessors,
        getters,
        localizer,
        min,
        max,
        showMultiDayTimes,
        longPressThreshold,
        resizable,
      } = this.props
  
      width = width || this.state.gutterWidth
  
      let start = range[0],
        end = range[range.length - 1]
  
      this.slots = range.length
  
      let allDayEvents = [],
        rangeEvents = [],
        rangeBackgroundEvents = []
  
      events.forEach((event) => {
        if (inRange(event, start, end, accessors)) {
          let eStart = accessors.start(event),
            eEnd = accessors.end(event)
  
          if (
            accessors.allDay(event) ||
            (dates.isJustDate(eStart) && dates.isJustDate(eEnd)) ||
            (!showMultiDayTimes && !dates.eq(eStart, eEnd, 'day'))
          ) {
            allDayEvents.push(event)
          } else {
            rangeEvents.push(event)
          }
        }
      })
  
      backgroundEvents.forEach((event) => {
        if (inRange(event, start, end, accessors)) {
          rangeBackgroundEvents.push(event)
        }
      })
  
      allDayEvents.sort((a, b) => sortEvents(a, b, accessors))
  
      return (
        <div
          className={clsx(
            'rbc-time-view',
            resources && 'rbc-time-view-resources'
          )}
        >
          <TimeGridHeader
            range={range}
            events={allDayEvents}
            width={width}
            rtl={rtl}
            getNow={getNow}
            localizer={localizer}
            selected={selected}
            resources={this.memoizedResources(resources, accessors)}
            selectable={this.props.selectable}
            accessors={accessors}
            getters={getters}
            components={components}
            scrollRef={this.scrollRef}
            isOverflowing={this.state.isOverflowing}
            longPressThreshold={longPressThreshold}
            onSelectSlot={this.handleSelectAllDaySlot}
            onSelectEvent={this.handleSelectAlldayEvent}
            onDoubleClickEvent={this.props.onDoubleClickEvent}
            onKeyPressEvent={this.props.onKeyPressEvent}
            onDrillDown={this.props.onDrillDown}
            getDrilldownView={this.props.getDrilldownView}
            resizable={resizable}
          />
          <div
            ref={this.contentRef}
            className="rbc-time-content"
            onScroll={this.handleScroll}
          >
            <TimeGutter
              date={start}
              ref={this.gutterRef}
              localizer={localizer}
              min={dates.merge(start, min)}
              max={dates.merge(start, max)}
              step={this.props.step}
              getNow={this.props.getNow}
              timeslots={this.props.timeslots}
              components={components}
              className="rbc-time-gutter"
              getters={getters}
            />
            {this.renderEvents(
              range,
              rangeEvents,
              rangeBackgroundEvents,
              getNow()
            )}
          </div>
        </div>
      )
    }
  
    clearSelection() {
      clearTimeout(this._selectTimer)
      this._pendingSelection = []
    }
  
    measureGutter() {
      if (this.measureGutterAnimationFrameRequest) {
        window.cancelAnimationFrame(this.measureGutterAnimationFrameRequest)
      }
      this.measureGutterAnimationFrameRequest = window.requestAnimationFrame(
        () => {
          const width = getWidth(this.gutter)
  
          if (width && this.state.gutterWidth !== width) {
            this.setState({ gutterWidth: width })
          }
        }
      )
    }
  
    applyScroll() {
      if (this._scrollRatio != null) {
        const content = this.contentRef.current
        content.scrollTop = content.scrollHeight * this._scrollRatio
        // Only do this once
        this._scrollRatio = null
      }
    }
  
    calculateScroll(props = this.props) {
      const { min, max, scrollToTime } = props
  
      const diffMillis = scrollToTime - dates.startOf(scrollToTime, 'day')
      const totalMillis = dates.diff(max, min)
  
      this._scrollRatio = diffMillis / totalMillis
    }
  
    checkOverflow = () => {
      if (this._updatingOverflow) return
  
      const content = this.contentRef.current
      let isOverflowing = content.scrollHeight > content.clientHeight
  
      if (this.state.isOverflowing !== isOverflowing) {
        this._updatingOverflow = true
        this.setState({ isOverflowing }, () => {
          this._updatingOverflow = false
        })
      }
    }
  
    memoizedResources = memoize((resources, accessors) =>
      Resources(resources, accessors)
    )
  }
  
  TimeGrid.propTypes = {
    events: PropTypes.array.isRequired,
    backgroundEvents: PropTypes.array.isRequired,
    resources: PropTypes.array,
  
    step: PropTypes.number,
    timeslots: PropTypes.number,
    range: PropTypes.arrayOf(PropTypes.instanceOf(Date)),
    min: PropTypes.instanceOf(Date),
    max: PropTypes.instanceOf(Date),
    getNow: PropTypes.func.isRequired,
  
    scrollToTime: PropTypes.instanceOf(Date),
    showMultiDayTimes: PropTypes.bool,
  
    rtl: PropTypes.bool,
    resizable: PropTypes.bool,
    width: PropTypes.number,
  
    accessors: PropTypes.object.isRequired,
    components: PropTypes.object.isRequired,
    getters: PropTypes.object.isRequired,
    localizer: PropTypes.object.isRequired,
  
    selected: PropTypes.object,
    selectable: PropTypes.oneOf([true, false, 'ignoreEvents']),
    longPressThreshold: PropTypes.number,
  
    onNavigate: PropTypes.func,
    onSelectSlot: PropTypes.func,
    onSelectEnd: PropTypes.func,
    onSelectStart: PropTypes.func,
    onSelectEvent: PropTypes.func,
    onDoubleClickEvent: PropTypes.func,
    onKeyPressEvent: PropTypes.func,
    onDrillDown: PropTypes.func,
    getDrilldownView: PropTypes.func.isRequired,
  
    dayLayoutAlgorithm: DayLayoutAlgorithmPropType,
  }
  
  TimeGrid.defaultProps = {
    step: 30,
    timeslots: 2,
    min: dates.startOf(new Date(), 'day'),
    max: dates.endOf(new Date(), 'day'),
    scrollToTime: dates.startOf(new Date(), 'day'),
  }
  