Browse Source

fix up reducers

Adam Shaw 7 năm trước cách đây
mục cha
commit
f5cf0a7e3a

+ 1 - 1
plugins/gcal/main.ts

@@ -13,7 +13,7 @@ const STANDARD_PROPS = {
 
 registerSourceType('google-calendar', {
 
-  parse(raw) {
+  parseMeta(raw) {
     if (typeof raw === 'string') {
       raw = { url: raw }
     }

+ 3 - 0
src/exports.ts

@@ -114,3 +114,6 @@ export { DateEnv } from './datelib/env'
 export { defineLocale, getLocale, getLocaleCodes } from './datelib/locale'
 export { DateFormatter, createFormatter } from './datelib/formatting'
 export { parse as parseMarker } from './datelib/parsing'
+
+export { registerSourceType } from './reducers/event-sources'
+export { refineProps } from './reducers/utils'

+ 0 - 1
src/main.ts

@@ -1,7 +1,6 @@
 import * as exportHooks from './exports'
 
 // for intentional side-effects
-import './models/event-source/config'
 import './theme/config'
 import './basic/config'
 import './agenda/config'

+ 1 - 1
src/reducers/array-event-source.ts

@@ -3,7 +3,7 @@ import { EventInput } from './event-store'
 
 registerSourceType('array', {
 
-  parse(raw: any): EventInput[] {
+  parseMeta(raw: any): EventInput[] {
     if (Array.isArray(raw)) { // short form
       return raw
     } else if (Array.isArray(raw.events)) {

+ 132 - 0
src/reducers/event-mutation.ts

@@ -0,0 +1,132 @@
+import UnzonedRange from '../models/UnzonedRange'
+import { DateEnv } from '../datelib/env'
+import { diffDayAndTime } from '../datelib/marker'
+import { Duration, createDuration } from '../datelib/duration'
+import { EventStore, EventDef, EventInstance } from './event-store'
+import { assignTo } from '../util/object'
+import { filterHash } from './utils'
+import Calendar from '../Calendar'
+
+export interface EventMutation {
+  startDelta?: Duration
+  endDelta?: Duration
+  standardProps: object // for the def
+  extendedProps: object // for the def
+}
+
+// Creating
+
+export function createMutation(
+  range0: UnzonedRange,
+  isAllDay0: boolean,
+  hasEnd0: boolean,
+  range1: UnzonedRange,
+  isAllDay1: boolean,
+  hasEnd1: boolean,
+  largeUnit: string,
+  dateEnv: DateEnv
+): EventMutation {
+  let startDelta: Duration = null
+  let endDelta: Duration = null
+  let hasEnd: boolean = null // TODO: use this!!!
+  let isAllDay: boolean = null // TODO: use this!!!
+
+  // subtracts the dates in the appropriate way, returning a duration
+  function diffDates(date0, date1) {
+    if (largeUnit === 'year') {
+      return createDuration(dateEnv.diffWholeYears(date0, date1), 'year')
+    } else if (largeUnit === 'month') {
+      return createDuration(dateEnv.diffWholeMonths(date0, date1), 'month')
+    } else {
+      return diffDayAndTime(date0, date1) // returns a duration
+    }
+  }
+
+  startDelta = diffDates(range0.start, range1.start)
+  endDelta = diffDates(range0.end, range1.end)
+
+  if (isAllDay0 !== isAllDay1) {
+    isAllDay = isAllDay1
+  }
+
+  if (hasEnd0 !== hasEnd1) {
+    hasEnd = hasEnd1
+  }
+
+  return {
+    startDelta,
+    endDelta,
+    standardProps: null,
+    extendedProps: null
+  }
+}
+
+// Applying
+
+export function applyMutation(eventStore: EventStore, instanceId: string, mutation: EventMutation, calendar: Calendar): EventStore {
+  const dateEnv = calendar.dateEnv
+  let eventInstance = eventStore.instances[instanceId]
+  let eventDef = eventStore.defs[eventInstance.defId]
+
+  if (eventDef && eventInstance) {
+    let matchGroupId = eventDef.groupId
+
+    return {
+      defs: filterHash(eventStore.defs, function(def) {
+        if (def === eventDef || matchGroupId && matchGroupId === def.groupId) {
+          return applyMutationToDef(def, mutation)
+        }
+      }),
+      instances: filterHash(eventStore.instances, function(instance) {
+        if (
+          instance === eventInstance ||
+          matchGroupId && matchGroupId === eventStore.defs[instance.defId].groupId
+        ) {
+          return applyMutationToInstance(instance, mutation, dateEnv)
+        }
+      })
+    }
+  } else {
+    return eventStore
+  }
+}
+
+function applyMutationToDef(eventDef: EventDef, mutation: EventMutation) {
+
+  if (mutation.standardProps != null || mutation.extendedProps != null) {
+    eventDef = assignTo({}, eventDef)
+
+    if (mutation.standardProps != null) {
+      assignTo(eventDef, mutation.standardProps)
+    }
+
+    if (mutation.extendedProps != null) {
+      eventDef.extendedProps = assignTo({}, eventDef.extendedProps, mutation.extendedProps)
+    }
+  }
+
+  return eventDef
+}
+
+function applyMutationToInstance(eventInstance: EventInstance, mutation: EventMutation, dateEnv: DateEnv) {
+
+  if (mutation.startDelta || mutation.endDelta) {
+    eventInstance = assignTo({}, eventInstance)
+
+    if (mutation.startDelta) {
+      eventInstance.range = new UnzonedRange(
+        dateEnv.add(eventInstance.range.start, mutation.startDelta),
+        eventInstance.range.end
+      )
+    }
+
+    if (mutation.endDelta) {
+      eventInstance.range = new UnzonedRange(
+        eventInstance.range.start,
+        dateEnv.add(eventInstance.range.end, mutation.endDelta),
+      )
+    }
+  }
+
+  return eventInstance
+}

+ 42 - 10
src/reducers/event-sources.ts

@@ -3,10 +3,11 @@ import UnzonedRange from '../models/UnzonedRange'
 import Calendar from '../Calendar'
 import { EventInput } from './event-store'
 import { ClassNameInput, parseClassName, refineProps } from './utils'
+import { warn } from '../util/misc'
 
 // types
 
-export interface AbstractEventSourceInput {
+export interface EventSourceInput {
   id?: string | number
   allDayDefault?: boolean
   eventDataTransform?: any
@@ -21,6 +22,9 @@ export interface AbstractEventSourceInput {
   backgroundColor?: string
   borderColor?: string
   textColor?: string
+  success?: (eventInputs: EventInput[]) => void
+  failure?: (errorObj: any) => void
+  [otherProp: string]: any
 }
 
 export interface EventSource {
@@ -44,12 +48,14 @@ export interface EventSource {
   backgroundColor: string | null
   borderColor: string | null
   textColor: string | null
+  success?: (eventInputs: EventInput[]) => void
+  failure?: (errorObj: any) => void
 }
 
 export type EventSourceHash = { [sourceId: string]: EventSource }
 
 export interface EventSourceTypeSettings {
-  parse: (raw: any) => any
+  parseMeta: (raw: any) => any
   fetch: (
     arg: {
       eventSource: EventSource
@@ -57,7 +63,7 @@ export interface EventSourceTypeSettings {
       range: UnzonedRange
     },
     success: (rawEvents: EventInput) => void,
-    failure: () => void
+    failure: (errorObj: any) => void
   ) => void
 }
 
@@ -76,7 +82,9 @@ const SIMPLE_SOURCE_PROPS = {
   color: String,
   backgroundColor: String,
   borderColor: String,
-  textColor: String
+  textColor: String,
+  success: null,
+  failure: null
 }
 
 let sourceTypes: { [sourceTypeName: string]: EventSourceTypeSettings } = {}
@@ -126,12 +134,21 @@ export function reduceEventSourceHash(sourceHash: EventSourceHash, action: any,
             rawEvents
           })
         },
-        function() {
+        function(errorInput) {
+          let errorObj
+
+          if (typeof errorInput === 'string') {
+            errorObj = { message: errorInput }
+          } else {
+            errorObj = errorInput || {}
+          }
+
           calendar.dispatch({
             type: 'ERROR_EVENT_SOURCE',
             sourceId: eventSource.sourceId,
             fetchId,
-            fetchRange: action.range
+            fetchRange: action.range,
+            error: errorObj
           })
         }
       )
@@ -144,10 +161,23 @@ export function reduceEventSourceHash(sourceHash: EventSourceHash, action: any,
       })
 
     case 'RECEIVE_EVENT_SOURCE':
-    case 'ERROR_EVENT_SOURCE': // TODO: call calendar's/source's error handlers maybe
+    case 'ERROR_EVENT_SOURCE':
       eventSource = sourceHash[action.sourceId]
 
       if (eventSource.latestFetchId === action.fetchId) {
+
+        if (action.type === 'RECEIVE_EVENT_SOURCE') {
+          if (typeof eventSource.success === 'function') {
+            eventSource.success(action.rawEvents)
+          }
+        } else { // failure
+          warn(action.error.message, action.error)
+
+          if (typeof eventSource.failure === 'function') {
+            eventSource.failure(action.error)
+          }
+        }
+
         return assignTo({}, sourceHash, {
           [eventSource.sourceId]: assignTo({}, eventSource, {
             isFetching: false,
@@ -163,6 +193,7 @@ export function reduceEventSourceHash(sourceHash: EventSourceHash, action: any,
         eventSource = sourceHash[sourceId]
 
         if (
+          !calendar.opt('lazyFetching') ||
           !eventSource.fetchRange ||
           eventSource.fetchRange.start < action.range.start ||
           eventSource.fetchRange.end > action.range.end
@@ -188,13 +219,14 @@ export function registerSourceType(type: string, settings: EventSourceTypeSettin
   sourceTypes[type] = settings
 }
 
-function parseSource(raw: AbstractEventSourceInput): EventSource {
+function parseSource(raw: EventSourceInput): EventSource {
   for (let sourceTypeName in sourceTypes) {
+    let leftovers = {}
+    let source: EventSource = refineProps(raw, SIMPLE_SOURCE_PROPS, leftovers)
     let sourceTypeSettings = sourceTypes[sourceTypeName]
-    let sourceTypeMeta = sourceTypeSettings.parse(raw)
+    let sourceTypeMeta = sourceTypeSettings.parseMeta(leftovers)
 
     if (sourceTypeMeta) {
-      let source: EventSource = refineProps(raw, SIMPLE_SOURCE_PROPS)
       source.sourceId = String(guid++)
       source.sourceType = sourceTypeName
       source.sourceTypeMeta = sourceTypeMeta

+ 35 - 20
src/reducers/event-store.ts

@@ -3,11 +3,15 @@ import { DateInput } from '../datelib/env'
 import Calendar from '../Calendar'
 import { filterHash, parseClassName, refineProps, ClassNameInput } from './utils'
 import { expandRecurring } from './recurring-events'
+import { applyMutation } from './event-mutation'
 
 // types
 
+type RenderingChoices = '' | 'background' | 'inverse-background' | 'none'
+
 export interface EventInput {
   id?: string | number
+  groupId?: string | number
   start?: DateInput
   end?: DateInput
   date?: DateInput
@@ -19,7 +23,7 @@ export interface EventInput {
   durationEditable?: boolean
   constraint?: any
   overlap?: any
-  rendering?: '' | 'background' | 'inverse-background' | 'none'
+  rendering?: RenderingChoices
   className?: ClassNameInput
   color?: string
   backgroundColor?: string
@@ -42,7 +46,7 @@ export interface EventDef {
   durationEditable: boolean | null
   constraint: any
   overlap: any
-  rendering: '' | 'background' | 'inverse-background' | 'none'
+  rendering: RenderingChoices
   className: string[]
   color: string | null
   backgroundColor: string | null
@@ -106,16 +110,26 @@ let guid = 0
 // reducing
 
 export function reduceEventStore(eventStore: EventStore, action: any, calendar: Calendar): EventStore {
+  let eventSource
+
   switch(action.type) {
 
     case 'RECEIVE_EVENT_SOURCE':
-      eventStore = excludeSource(eventStore, action.sourceId)
-      addRawEvents(eventStore, action.sourceId, action.fetchRange, action.rawEvents, calendar)
+      eventSource = calendar.state.eventSources[action.sourceId]
+
+      if (eventSource.latestFetchId === action.fetchId) { // this is checked in event-sources too :(
+        eventStore = excludeSource(eventStore, action.sourceId)
+        addRawEvents(eventStore, action.sourceId, action.fetchRange, action.rawEvents, calendar)
+      }
+
       return eventStore
 
     case 'CLEAR_EVENT_SOURCE': // TODO: wire up
       return excludeSource(eventStore, action.sourceId)
 
+    case 'MUTATE_EVENT':
+      return applyMutation(eventStore, action.instanceId, action.mutation, calendar)
+
     default:
       return eventStore
   }
@@ -138,28 +152,33 @@ function addRawEvents(eventStore: EventStore, sourceId: string, fetchRange: Unzo
     let recurringDateInfo = expandRecurring(rawEvent, fetchRange, calendar, leftoverProps)
 
     if (recurringDateInfo) {
-      let def = addDef(eventStore, sourceId, leftoverProps, recurringDateInfo.isAllDay, recurringDateInfo.hasEnd)
+      let def = parseDef(leftoverProps, sourceId, recurringDateInfo.isAllDay, recurringDateInfo.hasEnd)
+      eventStore.defs[def.defId] = def
 
       for (let range of recurringDateInfo.ranges) {
-        addInstance(eventStore, def.defId, range)
+        let instance = createInstance(def.defId, range)
+        eventStore.instances[instance.instanceId] = instance
       }
+
     } else {
       let dateInfo = parseDateInfo(rawEvent, sourceId, calendar, leftoverProps)
 
       if (dateInfo) {
-        let def = addDef(eventStore, sourceId, leftoverProps, dateInfo.isAllDay, dateInfo.hasEnd)
-        addInstance(eventStore, def.defId, dateInfo.range, dateInfo.forcedStartTzo, dateInfo.forcedEndTzo)
+        let def = parseDef(leftoverProps, sourceId, dateInfo.isAllDay, dateInfo.hasEnd)
+        let instance = createInstance(def.defId, dateInfo.range, dateInfo.forcedStartTzo, dateInfo.forcedEndTzo)
+
+        eventStore.defs[def.defId] = def
+        eventStore.instances[instance.instanceId] = instance
       }
     }
   })
 }
 
-// parsing + adding
+// parsing + creating
 
-function addDef(eventStore: EventStore, sourceId: string, raw: EventInput, isAllDay: boolean, hasEnd: boolean): EventDef {
+export function parseDef(raw: EventInput, sourceId: string, isAllDay: boolean, hasEnd: boolean): EventDef {
   let leftovers = {} as any
   let def = refineProps(raw, SIMPLE_DEF_PROPS, leftovers)
-  let defId = String(guid++)
 
   if (leftovers.id != null) {
     def.publicId = String(leftovers.id)
@@ -168,28 +187,23 @@ function addDef(eventStore: EventStore, sourceId: string, raw: EventInput, isAll
     def.publicId = null
   }
 
-  def.defId = defId
+  def.defId = String(guid++)
   def.sourceId = sourceId
   def.isAllDay = isAllDay
   def.hasEnd = hasEnd
   def.extendedProps = leftovers
 
-  eventStore.defs[defId] = def
   return def
 }
 
-function addInstance(
-  eventStore: EventStore,
+export function createInstance(
   defId: string,
   range: UnzonedRange,
   forcedStartTzo: number = null,
   forcedEndTzo: number = null
 ): EventInstance {
   let instanceId = String(guid++)
-  let instance = { instanceId, defId, range, forcedStartTzo, forcedEndTzo }
-
-  eventStore.instances[instanceId] = instance
-  return instance
+  return { instanceId, defId, range, forcedStartTzo, forcedEndTzo }
 }
 
 function parseDateInfo(rawEvent: EventInput, sourceId: string, calendar: Calendar, leftoverProps: any): EventDateInfo {
@@ -238,7 +252,8 @@ function parseDateInfo(rawEvent: EventInput, sourceId: string, calendar: Calenda
   if (endMarker) {
     hasEnd = true
   } else {
-    hasEnd = false
+    hasEnd = calendar.opt('forceEventDuration') || false
+
     endMarker = calendar.dateEnv.add(
       startMeta.marker,
       isAllDay ?

+ 1 - 1
src/reducers/func-event-source.ts

@@ -4,7 +4,7 @@ import { EventInput } from './event-store'
 
 registerSourceType('function', {
 
-  parse(raw: any): EventInput[] {
+  parseMeta(raw: any): EventInput[] {
     if (typeof raw === 'function') { // short form
       return raw
     } else if (typeof raw.events === 'function') {

+ 1 - 1
src/reducers/json-feed-event-source.ts

@@ -15,7 +15,7 @@ interface JsonFeedMeta {
 
 registerSourceType('json-feed', {
 
-  parse(raw: any): JsonFeedMeta {
+  parseMeta(raw: any): JsonFeedMeta {
     if (typeof raw === 'string') { // short form
       raw = { url: raw }
     } else if (!raw || typeof raw !== 'object' || !raw.url) {

+ 2 - 2
src/reducers/recurring-events.ts

@@ -32,10 +32,10 @@ let recurringTypes: { [recurringType: string]: RecurringExpandFunc } = {}
 
 // expanding API
 
-export function expandRecurring(rawEvent: EventInput, range: UnzonedRange, calendar: Calendar, leftoverProps: object): RecurringEventDateInfo {
+export function expandRecurring(rawEvent: EventInput, range: UnzonedRange, calendar: Calendar, leftoverProps?: object): RecurringEventDateInfo {
   for (let recurringType in recurringTypes) {
     let expandFunc = recurringTypes[recurringType]
-    let dateInfo = expandFunc(rawEvent, range, calendar, leftoverProps)
+    let dateInfo = expandFunc(rawEvent, range, calendar, leftoverProps || {})
 
     if (dateInfo) {
       return dateInfo

+ 2 - 2
src/types/input-types.ts

@@ -4,7 +4,7 @@ https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/fullcalenda
 */
 
 import View from '../View'
-import EventSource from '../models/event-source/EventSource'
+import { EventSourceInput } from '../reducers/event-sources'
 import { Duration } from '../datelib/duration'
 import { DateInput } from '../datelib/env'
 import { FormatterInput } from '../datelib/formatting'
@@ -40,7 +40,7 @@ export interface EventObjectInput extends EventOptionsBase, RangeInput {
   title: string
   allDay?: boolean
   url?: string
-  source?: EventSource
+  source?: EventSourceInput
   [customField: string]: any // non-standard fields
 }