Selaa lähdekoodia

nowIndicator refactor

Adam Shaw 6 vuotta sitten
vanhempi
sitoutus
865abbcd8f

+ 1 - 1
packages-premium

@@ -1 +1 @@
-Subproject commit 5017202274e60fe8e6d66c6588488465c4e20886
+Subproject commit acf121770b64b6b1f164131c0287ec4a9f7a1f4d

+ 9 - 0
packages/core/src/Calendar.tsx

@@ -35,6 +35,8 @@ import ComponentContext, { ComponentContextType, buildContext } from './componen
 import { render, h, createRef, flushToDom } from './vdom'
 import { TaskRunner, DelayedRunner } from './util/runner'
 import ViewApi from './ViewApi'
+import NowTimer, { NowTimerCallback } from './NowTimer'
+
 
 export interface DateClickApi extends DatePointApi {
   dayEl: HTMLElement
@@ -973,6 +975,13 @@ export default class Calendar {
   }
 
 
+  createNowIndicatorTimer(unit: string, callback: NowTimerCallback) {
+    if (this.opt('nowIndicator')) {
+      return new NowTimer(this.getNow(), unit, this.dateEnv, callback)
+    }
+  }
+
+
   // Event-Date Utilities
   // -----------------------------------------------------------------------------------------------------------------
 

+ 56 - 0
packages/core/src/NowTimer.ts

@@ -0,0 +1,56 @@
+import { DateMarker, addMs } from './datelib/marker'
+import { createDuration } from './datelib/duration'
+import { DateEnv } from './datelib/env'
+
+
+export type NowTimerCallback = (now: DateMarker) => void
+
+
+export default class NowTimer {
+
+  private timeoutId: any
+  private intervalId: any
+
+
+  constructor(initialNowDate: DateMarker, unit: string, dateEnv: DateEnv, callback: NowTimerCallback) {
+    let initialNowQueriedMs = new Date().valueOf()
+
+    function update() {
+      callback(addMs(initialNowDate, new Date().valueOf() - initialNowQueriedMs))
+    }
+
+    // wait until the beginning of the next interval
+    let delay = dateEnv.add(
+      dateEnv.startOf(initialNowDate, unit),
+      createDuration(1, unit)
+    ).valueOf() - initialNowDate.valueOf()
+
+    // TODO: maybe always use setTimeout, waiting until start of next unit
+    this.timeoutId = setTimeout(() => {
+      this.timeoutId = null
+      update()
+
+      if (unit === 'second') {
+        delay = 1000 // every second
+      } else {
+        delay = 1000 * 60 // otherwise, every minute
+      }
+
+      this.intervalId = setInterval(update, delay) // update every interval
+    }, delay)
+  }
+
+
+  destroy() {
+    if (this.timeoutId) {
+      clearTimeout(this.timeoutId)
+      this.timeoutId = null
+    }
+
+    if (this.intervalId) {
+      clearInterval(this.intervalId)
+      this.intervalId = null
+    }
+  }
+
+}

+ 0 - 111
packages/core/src/View.ts

@@ -1,5 +1,4 @@
 import DateProfileGenerator, { DateProfile } from './DateProfileGenerator'
-import { DateMarker, addMs } from './datelib/marker'
 import { createDuration, Duration } from './datelib/duration'
 import { default as EmitterMixin, EmitterInterface } from './common/EmitterMixin'
 import { ViewSpec } from './structs/view-spec'
@@ -39,13 +38,6 @@ export default abstract class View<State={}> extends DateComponent<ViewProps, St
   triggerWith: EmitterInterface['triggerWith']
   hasHandlers: EmitterInterface['hasHandlers']
 
-  // now indicator
-  isNowIndicatorRendered: boolean
-  initialNowDate: DateMarker // result first getNow call
-  initialNowQueriedMs: number // ms time the getNow was called
-  nowIndicatorTimeoutID: any // for refresh timing of now indicator
-  nowIndicatorIntervalID: any // "
-
 
   // Event Rendering
   // -----------------------------------------------------------------------------------------------------------------
@@ -64,109 +56,6 @@ export default abstract class View<State={}> extends DateComponent<ViewProps, St
   }
 
 
-  // Now Indicator
-  // -----------------------------------------------------------------------------------------------------------------
-
-
-  // Immediately render the current time indicator and begins re-rendering it at an interval,
-  // which is defined by this.getNowIndicatorUnit().
-  // TODO: somehow do this for the current whole day's background too
-  // USAGE: must be called manually from subclasses' render methods! don't need to call stopNowIndicator tho
-  startNowIndicator() {
-    let { calendar, dateEnv, options } = this.context
-    let unit
-    let update
-    let delay // ms wait value
-
-    if (options.nowIndicator && !this.initialNowDate) {
-      unit = this.getNowIndicatorUnit()
-
-      if (unit) {
-        update = this.updateNowIndicator.bind(this)
-
-        this.initialNowDate = calendar.getNow()
-        this.initialNowQueriedMs = new Date().valueOf()
-
-        // wait until the beginning of the next interval
-        delay = dateEnv.add(
-          dateEnv.startOf(this.initialNowDate, unit),
-          createDuration(1, unit)
-        ).valueOf() - this.initialNowDate.valueOf()
-
-        // TODO: maybe always use setTimeout, waiting until start of next unit
-        this.nowIndicatorTimeoutID = setTimeout(() => {
-          this.nowIndicatorTimeoutID = null
-          update()
-
-          if (unit === 'second') {
-            delay = 1000 // every second
-          } else {
-            delay = 1000 * 60 // otherwise, every minute
-          }
-
-          this.nowIndicatorIntervalID = setInterval(update, delay) // update every interval
-        }, delay)
-      }
-
-      // rendering will be initiated in updateSize
-    }
-  }
-
-
-  // rerenders the now indicator, computing the new current time from the amount of time that has passed
-  // since the initial getNow call.
-  updateNowIndicator() {
-    if (
-      this.props.dateProfile && // a way to determine if dates were rendered yet
-      this.initialNowDate // activated before?
-    ) {
-      this.unrenderNowIndicator() // won't unrender if unnecessary
-      this.renderNowIndicator(
-        addMs(this.initialNowDate, new Date().valueOf() - this.initialNowQueriedMs)
-      )
-      this.isNowIndicatorRendered = true
-    }
-  }
-
-
-  // Immediately unrenders the view's current time indicator and stops any re-rendering timers.
-  // Won't cause side effects if indicator isn't rendered.
-  stopNowIndicator() {
-
-    if (this.nowIndicatorTimeoutID) {
-      clearTimeout(this.nowIndicatorTimeoutID)
-      this.nowIndicatorTimeoutID = null
-    }
-
-    if (this.nowIndicatorIntervalID) {
-      clearInterval(this.nowIndicatorIntervalID)
-      this.nowIndicatorIntervalID = null
-    }
-
-    if (this.isNowIndicatorRendered) {
-      this.unrenderNowIndicator()
-      this.isNowIndicatorRendered = false
-    }
-  }
-
-
-  getNowIndicatorUnit() {
-    // subclasses should implement
-  }
-
-
-  // Renders a current time indicator at the given datetime
-  renderNowIndicator(date) {
-    // SUBCLASSES MUST PASS TO CHILDREN!
-  }
-
-
-  // Undoes the rendering actions from renderNowIndicator
-  unrenderNowIndicator() {
-    // SUBCLASSES MUST PASS TO CHILDREN!
-  }
-
-
   // Scroller
   // -----------------------------------------------------------------------------------------------------------------
 

+ 4 - 1
packages/core/src/component/ComponentContext.ts

@@ -8,6 +8,7 @@ import { createDuration, Duration } from '../datelib/duration'
 import { PluginHooks } from '../plugin-system'
 import { createContext } from '../vdom'
 import { parseToolbars, ToolbarModel } from '../toolbar-parse'
+import NowTimer, { NowTimerCallback } from '../NowTimer'
 
 
 export const ComponentContextType = createContext({}) // for Components
@@ -28,6 +29,7 @@ export default interface ComponentContext {
   viewsWithButtons: string[]
   addResizeHandler: (handler: ResizeHandler) => void
   removeResizeHandler: (handler: ResizeHandler) => void
+  createNowIndicatorTimer: (unit: string, callback: NowTimerCallback) => NowTimer | null
 }
 
 
@@ -48,7 +50,8 @@ export function buildContext(
     options,
     ...computeContextProps(options, theme, calendar),
     addResizeHandler: calendar.component.addResizeHandler,
-    removeResizeHandler: calendar.component.removeResizeHandler
+    removeResizeHandler: calendar.component.removeResizeHandler,
+    createNowIndicatorTimer: calendar.createNowIndicatorTimer
   }
 }
 

+ 2 - 0
packages/core/src/main.ts

@@ -182,3 +182,5 @@ export { default as Scroller, ScrollerProps, OverflowValue } from './scrollgrid/
 export { getScrollbarWidths } from './util/scrollbar-width'
 export { default as RefMap } from './util/RefMap'
 export { getIsRtlScrollbarOnLeft } from './util/scrollbar-side'
+
+export { default as NowTimer, NowTimerCallback } from './NowTimer'

+ 26 - 19
packages/timegrid/src/DayTimeCols.tsx

@@ -13,9 +13,10 @@ import {
   DateMarker,
   Slicer,
   Hit,
-  ComponentContext
+  ComponentContext,
+  NowTimer
 } from '@fullcalendar/core'
-import TimeCols, { TimeColsSeg } from './TimeCols'
+import TimeCols, { TimeColsSeg, TIME_COLS_NOW_INDICATOR_UNIT } from './TimeCols'
 
 
 export interface DayTimeColsProps {
@@ -33,18 +34,24 @@ export interface DayTimeColsProps {
   renderIntro: () => VNode[]
 }
 
+interface DayTimeColsState {
+  nowIndicatorDate: DateMarker
+  nowIndicatorSegs: TimeColsSeg[]
+}
+
 
-export default class DayTimeCols extends DateComponent<DayTimeColsProps> {
+export default class DayTimeCols extends DateComponent<DayTimeColsProps, DayTimeColsState> {
 
   private buildDayRanges = memoize(buildDayRanges)
   private dayRanges: DateRange[] // for now indicator
   private slicer = new DayTimeColsSlicer()
   private timeColsRef = createRef<TimeCols>()
+  private nowTimer: NowTimer
 
-  get timeCols() { return this.timeColsRef.current }
+  get timeCols() { return this.timeColsRef.current } // used for view's computeDateScroll :(
 
 
-  render(props: DayTimeColsProps, state: {}, context: ComponentContext) {
+  render(props: DayTimeColsProps, state: DayTimeColsState, context: ComponentContext) {
     let { dateEnv } = context
     let { dateProfile, dayTableModel } = props
     let dayRanges = this.dayRanges = this.buildDayRanges(dayTableModel, dateProfile, dateEnv)
@@ -60,6 +67,8 @@ export default class DayTimeCols extends DateComponent<DayTimeColsProps> {
         colGroupNode={props.colGroupNode}
         renderBgIntro={props.renderBgIntro}
         renderIntro={props.renderIntro}
+        nowIndicatorDate={state.nowIndicatorDate}
+        nowIndicatorSegs={state.nowIndicatorSegs}
       />
     )
   }
@@ -76,21 +85,20 @@ export default class DayTimeCols extends DateComponent<DayTimeColsProps> {
   }
 
 
-  getNowIndicatorUnit() {
-    return this.timeCols.getNowIndicatorUnit()
-  }
-
-
-  renderNowIndicator(date: DateMarker) {
-    this.timeCols.renderNowIndicator(
-      this.slicer.sliceNowDate(date, this.context.calendar, this.dayRanges),
-      date
-    )
+  componentDidMount() {
+    this.nowTimer = this.context.createNowIndicatorTimer(TIME_COLS_NOW_INDICATOR_UNIT, (dateMarker: DateMarker) => {
+      this.setState({
+        nowIndicatorDate: dateMarker,
+        nowIndicatorSegs: this.slicer.sliceNowDate(dateMarker, this.context.calendar, this.dayRanges)
+      })
+    })
   }
 
 
-  unrenderNowIndicator() {
-    this.timeCols.unrenderNowIndicator()
+  componentWillUnmount() {
+    if (this.nowTimer) {
+      this.nowTimer.destroy()
+    }
   }
 
 
@@ -100,8 +108,7 @@ export default class DayTimeCols extends DateComponent<DayTimeColsProps> {
 
 
   queryHit(positionLeft: number, positionTop: number): Hit {
-    let timeCols = this.timeColsRef.current
-    let rawHit = timeCols.positionToHit(positionLeft, positionTop)
+    let rawHit = this.timeCols.positionToHit(positionLeft, positionTop)
 
     if (rawHit) {
       return {

+ 15 - 20
packages/timegrid/src/TimeCols.tsx

@@ -49,8 +49,12 @@ export interface TimeColsProps {
   colGroupNode: VNode
   renderBgIntro: () => VNode[]
   renderIntro: () => VNode[]
+  nowIndicatorDate: DateMarker
+  nowIndicatorSegs: TimeColsSeg[]
 }
 
+export const TIME_COLS_NOW_INDICATOR_UNIT = 'minute'
+
 
 /* A component that renders one or more columns of vertical time slots
 ----------------------------------------------------------------------------------------------------------------------*/
@@ -63,6 +67,7 @@ export default class TimeCols extends BaseComponent<TimeColsProps> {
   private renderBgEvents = subrenderer(TimeColsFills)
   private renderBusinessHours = subrenderer(TimeColsFills)
   private renderDateSelection = subrenderer(TimeColsFills)
+  private renderNowIndicator = subrenderer(this._renderNowIndicator, this._unrenderNowIndicator)
 
   // computed options
   private slotDuration: Duration // duration of a "slot", a distinct time segment on given day, visualized by lines
@@ -79,7 +84,6 @@ export default class TimeCols extends BaseComponent<TimeColsProps> {
   public colEls: HTMLElement[] // cells elements in the day-row background
   private rootSlatEl: HTMLElement // div that wraps all the slat rows
   private slatEls: HTMLElement[] // elements running horizontally across all columns
-  private nowIndicatorEls: HTMLElement[]
 
   private colPositions: PositionCache
   private slatPositions: PositionCache
@@ -234,6 +238,11 @@ export default class TimeCols extends BaseComponent<TimeColsProps> {
       }),
       this.subrenderMirror(props, this.mirrorContainerEls, options)
     ]
+
+    this.renderNowIndicator({
+      date: props.nowIndicatorDate,
+      segs: props.nowIndicatorSegs
+    })
   }
 
 
@@ -304,20 +313,9 @@ export default class TimeCols extends BaseComponent<TimeColsProps> {
   ------------------------------------------------------------------------------------------------------------------*/
 
 
-  getNowIndicatorUnit() {
-    return 'minute' // will refresh on the minute
-  }
-
-
-  renderNowIndicator(segs: TimeColsSeg[], date) {
-
-    // HACK: if date columns not ready for some reason (scheduler)
-    if (!this.colContainerEls) {
-      return
-    }
-
+  _renderNowIndicator({ date, segs }: { date: DateMarker, segs: TimeColsSeg[] }) {
     let top = this.computeDateTop(date)
-    let nodes = []
+    let nodes: HTMLElement[] = []
     let i
 
     // render lines within the columns
@@ -338,15 +336,12 @@ export default class TimeCols extends BaseComponent<TimeColsProps> {
       nodes.push(arrowEl)
     }
 
-    this.nowIndicatorEls = nodes
+    return nodes
   }
 
 
-  unrenderNowIndicator() {
-    if (this.nowIndicatorEls) {
-      this.nowIndicatorEls.forEach(removeElement)
-      this.nowIndicatorEls = null
-    }
+  _unrenderNowIndicator(nodes: HTMLElement[]) {
+    nodes.forEach(removeElement)
   }
 
 

+ 1 - 33
packages/timegrid/src/TimeColsView.tsx

@@ -3,7 +3,6 @@ import {
   View,
   createFormatter, diffDays,
   Duration,
-  DateMarker,
   getViewClassNames,
   GotoAnchor,
   ViewProps,
@@ -39,13 +38,7 @@ export default abstract class TimeColsView extends View {
   // ----------------------------------------------------------------------------------------------------
 
   abstract getAllDayTableObj(): { table: Table } | null
-
-  abstract getTimeColsObj(): {
-    timeCols: TimeCols,
-    getNowIndicatorUnit: () => string,
-    renderNowIndicator: (d: DateMarker) => void,
-    unrenderNowIndicator: () => void
-  }
+  abstract getTimeColsObj(): { timeCols: TimeCols }
 
 
   // rendering
@@ -113,7 +106,6 @@ export default abstract class TimeColsView extends View {
       allDayTable.table.bottomCoordPadding = dividerEl.getBoundingClientRect().height
     }
 
-    this.startNowIndicator()
     this.scrollToInitialTime()
   }
 
@@ -125,30 +117,6 @@ export default abstract class TimeColsView extends View {
   }
 
 
-  componentWillUnmount() {
-    this.stopNowIndicator()
-  }
-
-
-  /* Now Indicator
-  ------------------------------------------------------------------------------------------------------------------*/
-
-
-  getNowIndicatorUnit() {
-    return this.getTimeColsObj().getNowIndicatorUnit()
-  }
-
-
-  renderNowIndicator(date) {
-    this.getTimeColsObj().renderNowIndicator(date)
-  }
-
-
-  unrenderNowIndicator() {
-    this.getTimeColsObj().unrenderNowIndicator()
-  }
-
-
   /* Dimensions
   ------------------------------------------------------------------------------------------------------------------*/
 

+ 1 - 1
packages/timegrid/src/main.ts

@@ -5,7 +5,7 @@ import { TimeColsSeg } from './TimeCols'
 import { default as DayTimeCols, DayTimeColsSlicer, buildDayRanges } from './DayTimeCols'
 
 export { DayTimeCols, DayTimeColsView, TimeColsView, buildDayTableModel, buildDayRanges, DayTimeColsSlicer, TimeColsSeg }
-export { default as TimeCols } from './TimeCols'
+export { default as TimeCols, TIME_COLS_NOW_INDICATOR_UNIT } from './TimeCols'
 
 export default createPlugin({
   defaultView: 'timeGridWeek',