Просмотр исходного кода

change lots of things about reducing

Adam Shaw 5 лет назад
Родитель
Сommit
accf2dd8fe

+ 1 - 1
packages-premium

@@ -1 +1 @@
-Subproject commit 77fae276578dadf5505d950ac416b12221c2f1b3
+Subproject commit 7ca7da6f52a6e19c8e93ab110f0da28faebf52f3

+ 17 - 14
packages/core/src/Calendar.tsx

@@ -70,7 +70,6 @@ export class Calendar {
   interactionsStore: { [componentUid: string]: Interaction[] } = {}
 
   get view() { return this.state.viewApi } // for public API
-  get component() { return this.componentRef.current } // used to get view-specific business hours :(
 
 
   constructor(el: HTMLElement, optionOverrides: OptionsInput = {}) {
@@ -94,7 +93,6 @@ export class Calendar {
       optionOverrides
     })
 
-    // must go after INIT
     this.calendarInteractions = this.state.pluginHooks.calendarInteractions
       .map((calendarInteractionClass) => {
         return new calendarInteractionClass(this.state)
@@ -146,7 +144,12 @@ export class Calendar {
 
 
   runAction(action: Action) {
-    this.state = this.reducer.reduce(this.state, action, this.emitter, this)
+    this.state = this.reducer.reduce(this.state, action, this.dispatch, this.emitter, this.getCurrentState, this)
+  }
+
+
+  getCurrentState = () => {
+    return this.state
   }
 
 
@@ -333,10 +336,10 @@ export class Calendar {
 
         if ((dateOrRange as DateRangeInput).start && (dateOrRange as DateRangeInput).end) { // a range
           this.dispatch({
-            type: 'SET_VIEW_TYPE',
+            type: 'CHANGE_VIEW_TYPE',
             viewType,
           })
-          this.dispatch({
+          this.dispatch({ // not very efficient to do two dispatches
             type: 'SET_OPTION',
             optionName: 'visibleRange',
             optionValue: dateOrRange
@@ -344,7 +347,7 @@ export class Calendar {
 
         } else {
           this.dispatch({
-            type: 'SET_VIEW_TYPE',
+            type: 'CHANGE_VIEW_TYPE',
             viewType,
             dateMarker: this.state.dateEnv.createMarker(dateOrRange as DateInput)
           })
@@ -352,7 +355,7 @@ export class Calendar {
 
       } else {
         this.dispatch({
-          type: 'SET_VIEW_TYPE',
+          type: 'CHANGE_VIEW_TYPE',
           viewType
         })
       }
@@ -373,14 +376,14 @@ export class Calendar {
 
     if (spec) {
       this.dispatch({
-        type: 'SET_VIEW_TYPE',
+        type: 'CHANGE_VIEW_TYPE',
         viewType: spec.type,
         dateMarker
       })
 
     } else {
       this.dispatch({
-        type: 'SET_DATE',
+        type: 'CHANGE_DATE',
         dateMarker
       })
     }
@@ -429,7 +432,7 @@ export class Calendar {
   prevYear() {
     this.unselect()
     this.dispatch({
-      type: 'SET_DATE',
+      type: 'CHANGE_DATE',
       dateMarker: this.state.dateEnv.addYears(this.state.currentDate, -1)
     })
   }
@@ -438,7 +441,7 @@ export class Calendar {
   nextYear() {
     this.unselect()
     this.dispatch({
-      type: 'SET_DATE',
+      type: 'CHANGE_DATE',
       dateMarker: this.state.dateEnv.addYears(this.state.currentDate, 1)
     })
   }
@@ -447,7 +450,7 @@ export class Calendar {
   today() {
     this.unselect()
     this.dispatch({
-      type: 'SET_DATE',
+      type: 'CHANGE_DATE',
       dateMarker: this.getNow()
     })
   }
@@ -456,7 +459,7 @@ export class Calendar {
   gotoDate(zonedDateInput) {
     this.unselect()
     this.dispatch({
-      type: 'SET_DATE',
+      type: 'CHANGE_DATE',
       dateMarker: this.state.dateEnv.createMarker(zonedDateInput)
     })
   }
@@ -468,7 +471,7 @@ export class Calendar {
     if (delta) { // else, warn about invalid input?
       this.unselect()
       this.dispatch({
-        type: 'SET_DATE',
+        type: 'CHANGE_DATE',
         dateMarker: this.state.dateEnv.add(this.state.currentDate, delta)
       })
     }

+ 1 - 5
packages/core/src/CalendarComponent.tsx

@@ -5,7 +5,6 @@ import { Toolbar } from './Toolbar'
 import { DateProfileGenerator, DateProfile } from './DateProfileGenerator'
 import { rangeContainsMarker } from './datelib/date-range'
 import { EventUiHash } from './component/event-ui'
-import { parseBusinessHours } from './structs/business-hours'
 import { memoize } from './util/memoize'
 import { DateMarker } from './datelib/marker'
 import { CalendarState } from './reducers/types'
@@ -44,7 +43,6 @@ export class CalendarComponent extends Component<CalendarComponentProps, Calenda
   context: never
 
   private buildViewContext = memoize(buildViewContext)
-  private parseBusinessHours = memoize((input) => parseBusinessHours(input, this.props))
   private buildViewPropTransformers = memoize(buildViewPropTransformers)
   private buildToolbarProps = memoize(buildToolbarProps)
   private reportClassNames = memoize(reportClassNames)
@@ -54,8 +52,6 @@ export class CalendarComponent extends Component<CalendarComponentProps, Calenda
   private footerRef = createRef<Toolbar>()
   private viewRef = createRef<ViewComponent>()
 
-  get view() { return this.viewRef.current }
-
   state = {
     forPrint: false
   }
@@ -105,12 +101,12 @@ export class CalendarComponent extends Component<CalendarComponentProps, Calenda
       props.viewApi,
       props.options,
       props.computedOptions,
-      props.dateProfile,
       props.dateProfileGenerator,
       props.dateEnv,
       props.theme,
       props.pluginHooks,
       props.dispatch,
+      props.getCurrentState,
       props.emitter,
       props.calendar
     )

+ 1 - 1
packages/core/src/DateProfileGenerator.ts

@@ -12,7 +12,7 @@ export interface DateProfile {
   currentRangeUnit: string
   isRangeAllDay: boolean
   validRange: OpenDateRange
-  activeRange: DateRange
+  activeRange: DateRange | null
   renderRange: DateRange
   slotMinTime: Duration
   slotMaxTime: Duration

+ 33 - 21
packages/core/src/ViewApi.ts

@@ -1,32 +1,44 @@
-import { DateEnv } from './datelib/env';
-import { DateProfile } from './DateProfileGenerator';
-
-export interface ViewApi {
-  type: string
-  title: string
-  activeStart: Date
-  activeEnd: Date
-  currentStart: Date
-  currentEnd: Date
-}
+import { DateEnv } from './datelib/env'
+import { CalendarState } from 'fullcalendar'
+
 
-export class ViewApi {
+export class ViewApi { // always represents the current view
 
   constructor(
     public type: string,
-    dateProfile: DateProfile,
-    public title: string,
-    private options: any,
-    dateEnv: DateEnv
+    private getCurrentState: () => CalendarState,
+    private dateEnv: DateEnv
   ) {
-    this.activeStart = dateEnv.toDate(dateProfile.activeRange.start)
-    this.activeEnd = dateEnv.toDate(dateProfile.activeRange.end)
-    this.currentStart = dateEnv.toDate(dateProfile.currentRange.start)
-    this.currentEnd = dateEnv.toDate(dateProfile.currentRange.end)
   }
 
+
+  get title() {
+    return this.getCurrentState().viewTitle
+  }
+
+
+  get activeStart() {
+    return this.dateEnv.toDate(this.getCurrentState().dateProfile.activeRange.start)
+  }
+
+
+  get activeEnd() {
+    return this.dateEnv.toDate(this.getCurrentState().dateProfile.activeRange.end)
+  }
+
+
+  get currentStart() {
+    return this.dateEnv.toDate(this.getCurrentState().dateProfile.currentRange.start)
+  }
+
+
+  get currentEnd() {
+    return this.dateEnv.toDate(this.getCurrentState().dateProfile.currentRange.end)
+  }
+
+
   getOption(name: string) {
-    return this.options[name]
+    return this.getCurrentState().options[name] // are the view-specific options
   }
 
 }

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

@@ -34,12 +34,12 @@ export function buildViewContext(
   viewApi: ViewApi,
   options: any,
   computedOptions: any,
-  dateProfile: DateProfile,
   dateProfileGenerator: DateProfileGenerator,
   dateEnv: DateEnv,
   theme: Theme,
   pluginHooks: PluginHooks,
   dispatch: (action: Action) => void,
+  getCurrentState: () => CalendarState,
   emitter: Emitter,
   calendar: Calendar
 ): ComponentContext {
@@ -50,6 +50,7 @@ export function buildViewContext(
     pluginHooks,
     emitter,
     dispatch,
+    getCurrentState,
     calendar
   }
 

+ 13 - 7
packages/core/src/reducers/CalendarStateReducer.ts

@@ -29,6 +29,7 @@ import { diffWholeDays } from '../datelib/marker'
 import { createFormatter } from '../datelib/formatting'
 import { DateRange } from '../datelib/date-range'
 import { ViewApi } from '../ViewApi'
+import { parseBusinessHours } from '../structs/business-hours'
 
 
 export class CalendarStateReducer {
@@ -49,9 +50,10 @@ export class CalendarStateReducer {
   private computeTitle = memoize(computeTitle)
   private buildViewApi = memoize(buildViewApi)
   private buildLocale = memoize(buildLocale)
+  private parseContextBusinessHours = memoizeObjArg(parseContextBusinessHours)
 
 
-  reduce(state: CalendarState, action: Action, emitter: Emitter, calendar: Calendar): CalendarState {
+  reduce(state: CalendarState, action: Action, dispatch: (action: Action) => void, emitter: Emitter, getCurrentState: () => CalendarState, calendar: Calendar): CalendarState {
     let optionOverrides = state.optionOverrides || {}
     let dynamicOptionOverrides = state.dynamicOptionOverrides || {}
 
@@ -135,7 +137,6 @@ export class CalendarStateReducer {
       emitter.trigger('_init') // for tests. needs to happen after emitter.setOptions
     }
 
-    let dispatch = state.dispatch || calendar.dispatch.bind(calendar) // will reuse past functions! TODO: memoize? TODO: calendar should bind?
     let reducerContext: ReducerContext = {
       dateEnv,
       options: viewOptions,
@@ -143,6 +144,7 @@ export class CalendarStateReducer {
       pluginHooks,
       emitter,
       dispatch,
+      getCurrentState,
       calendar
     }
 
@@ -174,11 +176,12 @@ export class CalendarStateReducer {
     }
 
     let viewTitle = this.computeTitle(dateProfile, viewOptions, dateEnv)
-    let viewApi = this.buildViewApi(viewSpec.type, dateProfile, viewTitle, viewOptions, dateEnv)
+    let viewApi = this.buildViewApi(viewSpec.type, getCurrentState, dateEnv)
 
     let nextState: CalendarState = {
       ...(state as object), // preserve previous state from plugin reducers. tho remove type to make sure all data is provided right now
       ...reducerContext,
+      businessHours: this.parseContextBusinessHours(reducerContext),
       calendarOptions,
       optionOverrides,
       dynamicOptionOverrides,
@@ -303,12 +306,15 @@ function buildEventUiBases(eventDefs: EventDefHash, eventUiSingleBase: EventUi,
 
 function buildViewApi(
   type: string,
-  dateProfile: DateProfile,
-  title: string,
-  options: any,
+  getCurrentState: () => CalendarState,
   dateEnv: DateEnv
 ) {
-  return new ViewApi(type, dateProfile, title, options, dateEnv)
+  return new ViewApi(type, getCurrentState, dateEnv)
+}
+
+
+function parseContextBusinessHours(context: ReducerContext) {
+  return parseBusinessHours(context.options.businessHours, context)
 }
 
 

+ 2 - 0
packages/core/src/reducers/ReducerContext.ts

@@ -5,6 +5,7 @@ import { Calendar } from '../Calendar'
 import { Emitter } from '../common/Emitter'
 import { parseFieldSpecs } from '../util/misc'
 import { createDuration, Duration } from '../datelib/duration'
+import { CalendarState } from '../reducers/types'
 
 
 export interface ReducerContext {
@@ -14,6 +15,7 @@ export interface ReducerContext {
   pluginHooks: PluginHooks
   emitter: Emitter
   dispatch(action: Action): void
+  getCurrentState(): CalendarState
   calendar: Calendar
 }
 

+ 13 - 8
packages/core/src/reducers/current-date.ts

@@ -8,21 +8,26 @@ export function reduceCurrentDate(currentDate: DateMarker, action: Action, dateP
   // on INIT, currentDate will already be set
 
   switch (action.type) {
-    case 'PREV':
-    case 'NEXT':
-      if (!rangeContainsMarker(dateProfile.currentRange, currentDate)) {
+
+    case 'CHANGE_DATE':
+    case 'CHANGE_VIEW_TYPE':
+      if (action.dateMarker) {
+        currentDate = action.dateMarker
+      }
+      // fall through...
+    case 'INIT':
+      if (dateProfile.activeRange && !rangeContainsMarker(dateProfile.activeRange, currentDate)) {
         return dateProfile.currentRange.start
       } else {
         return currentDate
       }
 
-    case 'SET_DATE':
-    case 'SET_VIEW_TYPE':
-      let newDate = action.dateMarker || currentDate
-      if (dateProfile.activeRange && !rangeContainsMarker(dateProfile.activeRange, newDate)) {
+    case 'PREV':
+    case 'NEXT':
+      if (!rangeContainsMarker(dateProfile.currentRange, currentDate)) {
         return dateProfile.currentRange.start
       } else {
-        return newDate
+        return currentDate
       }
 
     default:

+ 10 - 17
packages/core/src/reducers/date-profile.ts

@@ -16,33 +16,26 @@ export function reduceDateProfile(currentDateProfile: DateProfile | null, action
       )
       break
 
-    case 'PREV':
-      newDateProfile = dateProfileGenerator.buildPrev(currentDateProfile, currentDate)
-      break
-
-    case 'NEXT':
-      newDateProfile = dateProfileGenerator.buildNext(currentDateProfile, currentDate)
-      break
-
-    case 'SET_DATE':
+    case 'CHANGE_DATE':
+    case 'CHANGE_VIEW_TYPE':
       if (
         !currentDateProfile.activeRange ||
-        !rangeContainsMarker(currentDateProfile.currentRange, action.dateMarker)
+        !rangeContainsMarker(currentDateProfile.currentRange, (action as any).dateMarker)
       ) {
         newDateProfile = dateProfileGenerator.build(
-          action.dateMarker,
+          action.dateMarker || currentDate,
           undefined,
           true // forceToValid
         )
       }
       break
 
-    case 'SET_VIEW_TYPE':
-      newDateProfile = dateProfileGenerator.build(
-        action.dateMarker || currentDate,
-        undefined,
-        true // forceToValid
-      )
+    case 'PREV':
+      newDateProfile = dateProfileGenerator.buildPrev(currentDateProfile, currentDate)
+      break
+
+    case 'NEXT':
+      newDateProfile = dateProfileGenerator.buildNext(currentDateProfile, currentDate)
       break
   }
 

+ 2 - 2
packages/core/src/reducers/eventSources.ts

@@ -27,8 +27,8 @@ export function reduceEventSources(eventSources: EventSourceHash, action: Action
 
     case 'PREV': // TODO: how do we track all actions that affect dateProfile :(
     case 'NEXT':
-    case 'SET_DATE':
-    case 'SET_VIEW_TYPE':
+    case 'CHANGE_DATE':
+    case 'CHANGE_VIEW_TYPE':
       if (dateProfile) {
         return fetchDirtySources(eventSources, activeRange, context)
       } else {

+ 2 - 2
packages/core/src/reducers/eventStore.ts

@@ -48,8 +48,8 @@ export function reduceEventStore(eventStore: EventStore, action: Action, eventSo
 
     case 'PREV': // TODO: how do we track all actions that affect dateProfile :(
     case 'NEXT':
-    case 'SET_DATE':
-    case 'SET_VIEW_TYPE':
+    case 'CHANGE_DATE':
+    case 'CHANGE_VIEW_TYPE':
       if (dateProfile) {
         return expandRecurring(eventStore, dateProfile.activeRange, context)
       } else {

+ 3 - 2
packages/core/src/reducers/types.ts

@@ -15,6 +15,7 @@ import { EventUiHash, EventUi } from '../component/event-ui'
 import { ViewApi } from '../ViewApi'
 
 export interface CalendarState extends ReducerContext {
+  businessHours: EventStore
   calendarOptions: any // NOTE: use `options` instead. view-specific
   eventSources: EventSourceHash
   eventSourceLoadingLevel: number
@@ -53,8 +54,8 @@ export type Action =
 
   { type: 'PREV' } |
   { type: 'NEXT' } |
-  { type: 'SET_DATE', dateMarker: DateMarker } |
-  { type: 'SET_VIEW_TYPE', viewType: string, dateMarker?: DateMarker } |
+  { type: 'CHANGE_DATE', dateMarker: DateMarker } |
+  { type: 'CHANGE_VIEW_TYPE', viewType: string, dateMarker?: DateMarker } |
 
   { type: 'SELECT_DATES', selection: DateSpan } |
   { type: 'UNSELECT_DATES' } |

+ 1 - 1
packages/core/src/reducers/view-type.ts

@@ -5,7 +5,7 @@ export function reduceViewType(viewType: string, action: Action, availableViewHa
   // for INIT, viewType will have already been set
 
   switch (action.type) {
-    case 'SET_VIEW_TYPE':
+    case 'CHANGE_VIEW_TYPE':
       return viewType = action.viewType
   }
 

+ 1 - 0
packages/core/src/structs/event-store.ts

@@ -59,6 +59,7 @@ export function expandRecurring(eventStore: EventStore, framingRange: DateRange,
   let { defs, instances } = eventStore
 
   // remove existing recurring instances
+  // TODO: bad. always expand events as a second step
   instances = filterHash(instances, function(instance: EventInstance) {
     return !defs[instance.defId].recurringDef
   })

+ 6 - 7
packages/core/src/validation.ts

@@ -1,4 +1,4 @@
-import { EventStore, expandRecurring, filterEventStoreDefs, parseEvents, createEmptyEventStore } from './structs/event-store'
+import { EventStore, expandRecurring, filterEventStoreDefs, parseEvents } from './structs/event-store'
 import { DateSpan, DateSpanApi } from './structs/date-span'
 import { rangeContainsRange, rangesIntersect, DateRange, OpenDateRange } from './datelib/date-range'
 import { EventApi } from './api/EventApi'
@@ -33,21 +33,20 @@ export function isDateSelectionValid(dateSelection: DateSpan, context: ReducerCo
 
 
 function isNewPropsValid(newProps, context: ReducerContext) {
-  let { calendar } = context
-  let viewComponent = calendar.component.view
+  let calendarState = context.calendar.state
 
   let props = {
-    businessHours: viewComponent ? viewComponent.props.businessHours : createEmptyEventStore(), // why? yuck
+    businessHours: calendarState.businessHours,
     dateSelection: '',
-    eventStore: calendar.state.eventStore,
-    eventUiBases: calendar.state.eventUiBases,
+    eventStore: calendarState.eventStore,
+    eventUiBases: calendarState.eventUiBases,
     eventSelection: '',
     eventDrag: null,
     eventResize: null,
     ...newProps
   }
 
-  return (calendar.state.pluginHooks.isPropsValid || isPropsValid)(props, context)
+  return (context.pluginHooks.isPropsValid || isPropsValid)(props, context)
 }
 
 

+ 2 - 2
packages/interaction/src/interactions/HitDragging.ts

@@ -182,13 +182,13 @@ export class HitDragging {
           positionTop >= 0 && positionTop < height
         ) {
           let hit = component.queryHit(positionLeft, positionTop, width, height)
+          let dateProfile = component.context.getCurrentState().dateProfile
 
           if (
             hit &&
             (
               // make sure the hit is within activeRange, meaning it's not a deal cell
-              !component.context.dateProfile || // hack for MorePopover
-              rangeContainsRange(component.context.dateProfile.activeRange, hit.dateSpan.range)
+              rangeContainsRange(dateProfile.activeRange, hit.dateSpan.range)
             ) &&
             (!bestHit || hit.layer > bestHit.layer)
           ) {