Explorar o código

change how we represent businesshours

Adam Shaw %!s(int64=7) %!d(string=hai) anos
pai
achega
1ebbba5c9f

+ 9 - 7
src/Calendar.ts

@@ -31,6 +31,7 @@ import EventSourceApi from './api/EventSourceApi'
 import EventApi from './api/EventApi'
 import { parseEventStore, createEmptyEventStore, EventStore } from './structs/event-store'
 import { computeEventDefUis, EventUiHash } from './component/event-rendering'
+import { BusinessHoursInput, parseBusinessHours } from './structs/business-hours'
 
 
 export default class Calendar {
@@ -53,6 +54,8 @@ export default class Calendar {
 
   buildDateEnv: any
   buildTheme: any
+  computeEventDefUis: (eventDefs: EventDefHash, eventSources: EventSourceHash, options: any) => EventUiHash
+  parseBusinessHours: (input: BusinessHoursInput) => EventStore
 
   optionsManager: OptionsManager
   viewSpecManager: ViewSpecManager
@@ -84,7 +87,6 @@ export default class Calendar {
   isDisplaying: boolean = false // installed in DOM? accepting renders?
   isRendering: boolean = false // currently in the _render function?
   isSkeletonRendered: boolean = false // fyi: set within the debounce delay
-  computeEventDefUis: (eventDefs: EventDefHash, eventSources: EventSourceHash, options: any) => EventUiHash
   renderingPauseDepth: number = 0
   rerenderFlags: RenderForceFlags
   renderableEventStore: EventStore
@@ -104,6 +106,9 @@ export default class Calendar {
     this.buildTheme = reselector(buildTheme)
     this.buildDelayedRerender = reselector(buildDelayedRerender)
     this.computeEventDefUis = reselector(computeEventDefUis)
+    this.parseBusinessHours = reselector((input) => {
+      return parseBusinessHours(input, this)
+    })
 
     this.handleOptions(this.optionsManager.computed)
     this.hydrate()
@@ -348,12 +353,9 @@ export default class Calendar {
       eventSourceLoadingLevel: 0,
       dateProfile: null,
       eventSources: {},
-      eventStore: {
-        defs: {},
-        instances: {}
-      },
+      eventStore: createEmptyEventStore(),
       eventUis: {},
-      businessHoursDef: false, // gets populated when we delegate rendering to View
+      businessHours: createEmptyEventStore(), // gets populated when we delegate rendering to View
       dateSelection: null,
       eventSelection: '',
       eventDrag: null,
@@ -583,7 +585,7 @@ export default class Calendar {
       dateProfile: state.dateProfile,
       eventStore: renderableEventStore,
       eventUis: eventUis,
-      businessHoursDef: renderedView.opt('businessHours'),
+      businessHours: this.parseBusinessHours(renderedView.opt('businessHours')),
       dateSelection: state.dateSelection,
       eventSelection: state.eventSelection,
       eventDrag: state.eventDrag,

+ 45 - 59
src/agenda/AgendaView.ts

@@ -13,11 +13,12 @@ import TimeGrid from './TimeGrid'
 import DayGrid from '../basic/DayGrid'
 import { createDuration } from '../datelib/duration'
 import { createFormatter } from '../datelib/formatting'
-import { EventStore } from '../structs/event-store'
+import { EventStore, filterEventStoreDefs } from '../structs/event-store'
 import { RenderForceFlags } from '../component/Component'
 import { DateComponentRenderState } from '../component/DateComponent'
 import { EventInteractionState } from '../interactions/event-interaction-state'
 import reselector from '../util/reselector'
+import { EventUiHash, hasBgRendering } from '../component/event-rendering'
 
 const AGENDA_ALL_DAY_EVENT_LIMIT = 5
 const WEEK_HEADER_FORMAT = createFormatter({ week: 'short' })
@@ -44,8 +45,13 @@ export default class AgendaView extends View {
   axisWidth: any // the width of the time axis running down the side
   usesMinMaxTime: boolean = true // indicates that minTime/maxTime affects rendering
 
-  splitEventStore: any
-  splitInteractionState: any
+  // reselectors
+  filterEventsForTimeGrid: any
+  filterEventsForDayGrid: any
+  buildEventDragForTimeGrid: any
+  buildEventDragForDayGrid: any
+  buildEventResizeForTimeGrid: any
+  buildEventResizeForDayGrid: any
 
 
   constructor(calendar, viewSpec) {
@@ -64,8 +70,12 @@ export default class AgendaView extends View {
       'auto' // overflow y
     )
 
-    this.splitEventStore = reselector(splitEventStore)
-    this.splitInteractionState = reselector(splitInteractionState)
+    this.filterEventsForTimeGrid = reselector(filterEventsForTimeGrid)
+    this.filterEventsForDayGrid = reselector(filterEventsForDayGrid)
+    this.buildEventDragForTimeGrid = reselector(buildInteractionForTimeGrid)
+    this.buildEventDragForDayGrid = reselector(buildInteractionForDayGrid)
+    this.buildEventResizeForTimeGrid = reselector(buildInteractionForTimeGrid)
+    this.buildEventResizeForDayGrid = reselector(buildInteractionForDayGrid)
   }
 
 
@@ -174,10 +184,6 @@ export default class AgendaView extends View {
   renderChildren(renderState: DateComponentRenderState, forceFlags: RenderForceFlags) {
     let allDaySeletion = null
     let timedSelection = null
-    let eventStoreGroups = this.splitEventStore(renderState.eventStore)
-    let dragGroups = this.splitInteractionState(renderState.eventDrag)
-    let resizeGroups = this.splitInteractionState(renderState.eventResize)
-
     if (renderState.dateSelection) {
       if (renderState.dateSelection.isAllDay) {
         allDaySeletion = renderState.dateSelection
@@ -188,25 +194,25 @@ export default class AgendaView extends View {
 
     this.timeGrid.render({
       dateProfile: renderState.dateProfile,
-      eventStore: eventStoreGroups.timed,
+      eventStore: this.filterEventsForTimeGrid(renderState.eventStore, renderState.eventUis),
       eventUis: renderState.eventUis,
       dateSelection: timedSelection,
       eventSelection: renderState.eventSelection,
-      eventDrag: dragGroups.timed,
-      eventResize: resizeGroups.timed,
-      businessHoursDef: renderState.businessHoursDef
+      eventDrag: this.buildEventDragForTimeGrid(renderState.eventDrag, renderState.eventUis),
+      eventResize: this.buildEventResizeForTimeGrid(renderState.eventResize, renderState.eventUis),
+      businessHours: renderState.businessHours
     }, forceFlags)
 
     if (this.dayGrid) {
       this.dayGrid.render({
         dateProfile: renderState.dateProfile,
-        eventStore: eventStoreGroups.allDay,
+        eventStore: this.filterEventsForDayGrid(renderState.eventStore, renderState.eventUis),
         eventUis: renderState.eventUis,
         dateSelection: allDaySeletion,
         eventSelection: renderState.eventSelection,
-        eventDrag: dragGroups.allDay,
-        eventResize: resizeGroups.allDay,
-        businessHoursDef: renderState.businessHoursDef
+        eventDrag: this.buildEventDragForDayGrid(renderState.eventDrag, renderState.eventUis),
+        eventResize: this.buildEventResizeForDayGrid(renderState.eventResize, renderState.eventUis),
+        businessHours: renderState.businessHours
       }, forceFlags)
     }
   }
@@ -415,58 +421,38 @@ agendaDayGridMethods = {
 
 }
 
-
-function splitEventStore(eventStore: EventStore) {
-  let allDay: EventStore = { defs: {}, instances: {} }
-  let timed: EventStore = { defs: {}, instances: {} }
-
-  for (let defId in eventStore.defs) {
-    let def = eventStore.defs[defId]
-
-    if (def.isAllDay) {
-      allDay.defs[defId] = def
-    } else {
-      timed.defs[defId] = def
-    }
-  }
-
-  for (let instanceId in eventStore.instances) {
-    let instance = eventStore.instances[instanceId]
-    let def = eventStore.defs[instance.defId]
-
-    if (def.isAllDay) {
-      allDay.instances[instanceId] = instance
-    } else {
-      timed.instances[instanceId] = instance
-    }
-  }
-
-  return { allDay, timed }
+function filterEventsForTimeGrid(eventStore: EventStore, eventUis: EventUiHash): EventStore {
+  return filterEventStoreDefs(eventStore, function(eventDef) {
+    return !eventDef.isAllDay || hasBgRendering(eventUis[eventDef.defId])
+  })
 }
 
+function filterEventsForDayGrid(eventStore: EventStore, eventUis: EventUiHash): EventStore {
+  return filterEventStoreDefs(eventStore, function(eventDef) {
+    return eventDef.isAllDay || hasBgRendering(eventUis[eventDef.defId])
+  })
+}
 
-function splitInteractionState(state: EventInteractionState) {
-  let allDay = null
-  let timed = null
-
+function buildInteractionForTimeGrid(state: EventInteractionState, eventUis: EventUiHash): EventInteractionState {
   if (state) {
-    let affectedEventGroups = splitEventStore(state.affectedEvents)
-    let mutatedEventGroups = splitEventStore(state.mutatedEvents)
-
-    allDay = {
-      affectedEvents: affectedEventGroups.allDay,
-      mutatedEvents: mutatedEventGroups.allDay,
+    return {
+      affectedEvents: filterEventsForTimeGrid(state.affectedEvents, eventUis),
+      mutatedEvents: filterEventsForTimeGrid(state.mutatedEvents, eventUis),
       isEvent: state.isEvent,
       origSeg: state.origSeg
     }
+  }
+  return null
+}
 
-    timed = {
-      affectedEvents: affectedEventGroups.timed,
-      mutatedEvents: mutatedEventGroups.timed,
+function buildInteractionForDayGrid(state: EventInteractionState, eventUis: EventUiHash): EventInteractionState {
+  if (state) {
+    return {
+      affectedEvents: filterEventsForDayGrid(state.affectedEvents, eventUis),
+      mutatedEvents: filterEventsForDayGrid(state.mutatedEvents, eventUis),
       isEvent: state.isEvent,
       origSeg: state.origSeg
     }
   }
-
-  return { allDay, timed }
+  return null
 }

+ 1 - 0
src/agenda/TimeGrid.ts

@@ -43,6 +43,7 @@ export default class TimeGrid extends DateComponent {
   isInteractable = true
   doesDragHelper = true
   doesDragHighlight = false
+  slicingType: 'timed' = 'timed' // stupid TypeScript
 
   view: any // TODO: make more general and/or remove
   helperRenderer: any

+ 1 - 2
src/basic/DayGrid.ts

@@ -50,6 +50,7 @@ export default class DayGrid extends DateComponent {
   isInteractable = true
   doesDragHelper = false
   doesDragHighlight = true
+  slicingType: 'all-day' = 'all-day' // stupid TypeScript
 
   view: View // TODO: make more general and/or remove
   helperRenderer: any
@@ -70,8 +71,6 @@ export default class DayGrid extends DateComponent {
   // Relies on the view's colCnt and rowCnt. In the future, this component should probably be self-sufficient.
   isRigid: boolean = false
 
-  hasAllDayBusinessHours: boolean = true
-
   segPopover: Popover // the Popover that holds events that can't fit in a cell. null when not visible
   segPopoverTile: DayTile
 

+ 20 - 26
src/component/DateComponent.ts

@@ -8,8 +8,7 @@ import { DateMarker, DAY_IDS, addDays, startOfDay, diffDays, diffWholeDays } fro
 import { Duration, createDuration } from '../datelib/duration'
 import { DateSpan } from '../structs/date-span'
 import { EventRenderRange, sliceEventStore, computeEventDefUi, EventUiHash, computeEventDefUis } from '../component/event-rendering'
-import { EventStore } from '../structs/event-store'
-import { BusinessHoursDef, buildBusinessHours } from '../structs/business-hours'
+import { EventStore, expandEventStoreInstances } from '../structs/event-store'
 import { DateEnv } from '../datelib/env'
 import Theme from '../theme/Theme'
 import { EventInteractionState } from '../interactions/event-interaction-state'
@@ -24,7 +23,7 @@ import { parseEventDef, createEventInstance } from '../structs/event'
 
 export interface DateComponentRenderState {
   dateProfile: DateProfile | null
-  businessHoursDef: BusinessHoursDef // BusinessHoursDef's `false` is the empty state
+  businessHours: EventStore
   eventStore: EventStore
   eventUis: EventUiHash
   dateSelection: DateSpan | null
@@ -63,6 +62,8 @@ export default abstract class DateComponent extends Component {
   // TODO: port isTimeScale into same system?
   largeUnit: any
 
+  slicingType: 'timed' | 'all-day' | null = null
+
   eventRendererClass: any
   helperRendererClass: any
   fillRendererClass: any
@@ -77,14 +78,12 @@ export default abstract class DateComponent extends Component {
   helperRenderer: any
   fillRenderer: any
 
-  hasAllDayBusinessHours: boolean = false // TODO: unify with largeUnit and isTimeScale?
-
   renderedFlags: any = {}
   dirtySizeFlags: any = {}
   needHitsDepth: number = 0
 
   dateProfile: DateProfile = null
-  businessHoursDef: BusinessHoursDef = false
+  businessHours: EventStore = null
   eventStore: EventStore = null
   eventUis: EventUiHash = null
   dateSelection: DateSpan = null
@@ -320,7 +319,7 @@ export default abstract class DateComponent extends Component {
       skeleton: false,
       dates: renderState.dateProfile !== this.dateProfile,
       events: renderState.eventStore !== this.eventStore || renderState.eventUis !== this.eventUis,
-      businessHours: renderState.businessHoursDef !== this.businessHoursDef,
+      businessHours: renderState.businessHours !== this.businessHours,
       dateSelection: renderState.dateSelection !== this.dateSelection,
       eventSelection: renderState.eventSelection !== this.eventSelection,
       eventDrag: renderState.eventDrag !== this.eventDrag,
@@ -375,8 +374,8 @@ export default abstract class DateComponent extends Component {
       dirtySizeFlags.dates = true
     }
 
-    if (flags.businessHours && renderState.businessHoursDef) {
-      this.renderBusinessHours(renderState.businessHoursDef)
+    if (flags.businessHours && renderState.businessHours) {
+      this.renderBusinessHours(renderState.businessHours)
       renderedFlags.businessHours = true
       dirtySizeFlags.businessHours = true
     }
@@ -527,20 +526,16 @@ export default abstract class DateComponent extends Component {
   // ---------------------------------------------------------------------------------------------------------------
 
 
-  renderBusinessHours(businessHoursDef: BusinessHoursDef) {
+  renderBusinessHours(businessHours: EventStore) {
     if (this.fillRenderer) {
-      let eventStore = buildBusinessHours(
-        businessHoursDef,
-        this.hasAllDayBusinessHours,
-        this.dateProfile.activeRange,
-        this.getCalendar()
-      )
+      let expandedInstances = expandEventStoreInstances(businessHours, this.dateProfile.activeRange, this.getCalendar())
+      let expandedStore: EventStore = { defs: businessHours.defs, instances: expandedInstances }
 
       this.fillRenderer.renderSegs(
         'businessHours',
         this.eventStoreToSegs(
-          eventStore,
-          computeEventDefUis(eventStore.defs, {}, {})
+          expandedStore,
+          computeEventDefUis(expandedStore.defs, {}, {})
         ),
         {
           getClasses(seg) {
@@ -853,7 +848,12 @@ export default abstract class DateComponent extends Component {
 
   eventStoreToSegs(eventStore: EventStore, eventUis: EventUiHash): Seg[] {
     let activeRange = this.dateProfile.activeRange
-    let eventRenderRanges = sliceEventStore(eventStore, eventUis, activeRange)
+    let eventRenderRanges = sliceEventStore(
+      eventStore,
+      eventUis,
+      activeRange,
+      this.slicingType === 'all-day' ? this.nextDayThreshold : null
+    )
     let allSegs: Seg[] = []
 
     for (let eventRenderRange of eventRenderRanges) {
@@ -1046,15 +1046,9 @@ export default abstract class DateComponent extends Component {
   }
 
 
-  // Returns the date range of the full days the given range visually appears to occupy.
-  computeDayRange(range): DateRange {
-    return computeVisibleDayRange(range, this.nextDayThreshold)
-  }
-
-
   // Does the given range visually appear to occupy more than one day?
   isMultiDayRange(range) {
-    let dayRange = this.computeDayRange(range)
+    let dayRange = computeVisibleDayRange(range, this.nextDayThreshold)
 
     return diffDays(dayRange.start, dayRange.end) > 1
   }

+ 6 - 6
src/component/DayTableMixin.ts

@@ -175,11 +175,11 @@ export default class DayTableMixin extends Mixin implements DayTableInterface {
 
 
   // Slices up a date range into a segment for every week-row it intersects with
+  // range already normalized to start-of-day
   sliceRangeByRow(range) {
     let daysPerRow = this.daysPerRow
-    let normalRange = (this as any).view.computeDayRange(range) // make whole-day range, considering nextDayThreshold
-    let rangeFirst = this.getDateDayIndex(normalRange.start) // inclusive first index
-    let rangeLast = this.getDateDayIndex(addDays(normalRange.end, -1)) // inclusive last index
+    let rangeFirst = this.getDateDayIndex(range.start) // inclusive first index
+    let rangeLast = this.getDateDayIndex(addDays(range.end, -1)) // inclusive last index
     let segs = []
     let row
     let rowFirst
@@ -219,12 +219,12 @@ export default class DayTableMixin extends Mixin implements DayTableInterface {
 
 
   // Slices up a date range into a segment for every day-cell it intersects with.
+  // range already normalized to start-of-day
   // TODO: make more DRY with sliceRangeByRow somehow.
   sliceRangeByDay(range) {
     let daysPerRow = this.daysPerRow
-    let normalRange = (this as any).view.computeDayRange(range) // make whole-day range, considering nextDayThreshold
-    let rangeFirst = this.getDateDayIndex(normalRange.start) // inclusive first index
-    let rangeLast = this.getDateDayIndex(addDays(normalRange.end, -1)) // inclusive last index
+    let rangeFirst = this.getDateDayIndex(range.start) // inclusive first index
+    let rangeLast = this.getDateDayIndex(addDays(range.end, -1)) // inclusive last index
     let segs = []
     let row
     let rowFirst

+ 18 - 6
src/component/event-rendering.ts

@@ -4,6 +4,8 @@ import { DateRange, invertRanges } from '../datelib/date-range'
 import { EventSourceHash } from '../structs/event-source'
 import { mapHash } from '../util/object'
 import { parseClassName } from '../util/html'
+import { Duration } from '../datelib/duration'
+import { computeVisibleDayRange } from '../util/misc'
 
 export interface EventUi {
   startEditable: boolean
@@ -20,16 +22,17 @@ export type EventUiHash = { [defId: string]: EventUi }
 export interface EventRenderRange {
   eventDef: EventDef
   eventInstance?: EventInstance
-  range: DateRange
+  range: DateRange // NOT sliced by framingRange
   ui: EventUi
 }
 
 
 /*
-Does not slice ranges via framingRange into new ranges, but instead,
+DOES NOT ACTUALLY SLIE RANGES via framingRange into new ranges, but instead,
 keeps fg event ranges intact but more importantly slices inverse-BG events.
+Specifying nextDayThreshold signals that all-day ranges should be sliced.
 */
-export function sliceEventStore(eventStore: EventStore, eventUis: EventUiHash, framingRange: DateRange) {
+export function sliceEventStore(eventStore: EventStore, eventUis: EventUiHash, framingRange: DateRange, nextDayThreshold?: Duration) {
   let inverseBgByGroupId: { [groupId: string]: DateRange[] } = {}
   let inverseBgByDefId: { [defId: string]: DateRange[] } = {}
   let defByGroupId: { [groupId: string]: EventDef } = {}
@@ -56,18 +59,23 @@ export function sliceEventStore(eventStore: EventStore, eventUis: EventUiHash, f
     let instance = eventStore.instances[instanceId]
     let def = eventStore.defs[instance.defId]
     let ui = eventUis[def.defId]
+    let range = instance.range
+
+    if (!def.isAllDay && nextDayThreshold) {
+      range = computeVisibleDayRange(range, nextDayThreshold)
+    }
 
     if (ui.rendering === 'inverse-background') {
       if (def.groupId) {
-        inverseBgByGroupId[def.groupId].push(instance.range)
+        inverseBgByGroupId[def.groupId].push(range)
       } else {
-        inverseBgByDefId[instance.defId].push(instance.range)
+        inverseBgByDefId[instance.defId].push(range)
       }
     } else {
       renderRanges.push({
         eventDef: def,
         eventInstance: instance,
-        range: instance.range,
+        range: range,
         ui
       })
     }
@@ -105,6 +113,10 @@ export function sliceEventStore(eventStore: EventStore, eventUis: EventUiHash, f
   return renderRanges
 }
 
+export function hasBgRendering(ui: EventUi) {
+  return ui.rendering === 'background' || ui.rendering === 'inverse-background'
+}
+
 
 // UI Props
 // ----------------------------------------------------------------------------------------------------

+ 2 - 4
src/component/renderers/EventRenderer.ts

@@ -3,7 +3,7 @@ import { DateMarker } from '../../datelib/marker'
 import { createFormatter, DateFormatter } from '../../datelib/formatting'
 import { htmlToElements } from '../../util/dom-manip'
 import { compareByFieldSpecs } from '../../util/misc'
-import { EventRenderRange, EventUi } from '../event-rendering'
+import { EventRenderRange, EventUi, hasBgRendering } from '../event-rendering'
 import { Seg } from '../DateComponent'
 import EventApi from '../../api/EventApi'
 
@@ -66,9 +66,7 @@ export default class EventRenderer {
     let fgSegs: Seg[] = []
 
     for (let seg of allSegs) {
-      let rendering = seg.eventRange.ui.rendering
-
-      if (rendering === 'background' || rendering === 'inverse-background') {
+      if (hasBgRendering(seg.eventRange.ui)) {
         bgSegs.push(seg)
       } else {
         fgSegs.push(seg)

+ 1 - 1
src/exports.ts

@@ -9,7 +9,7 @@ export const internalApiVersion = 12
 // types
 export { OptionsInput } from './types/input-types'
 export { EventInput } from './structs/event'
-export { BusinessHoursDef } from './structs/business-hours'
+export { BusinessHoursInput } from './structs/business-hours'
 
 export {
   applyAll,

+ 2 - 2
src/interactions/constraint.ts

@@ -1,9 +1,9 @@
 import { DateRangeInput, rangeContainsRange } from '../datelib/date-range'
-import { BusinessHoursDef } from '../structs/business-hours'
+import { BusinessHoursInput } from '../structs/business-hours'
 import { EventStore } from '../structs/event-store'
 import { DateProfile } from '../DateProfileGenerator'
 
-export type ConstraintInput = DateRangeInput | BusinessHoursDef | 'businessHours'
+export type ConstraintInput = DateRangeInput | BusinessHoursInput | 'businessHours'
 
 export function isEventStoreValid(eventStore: EventStore, dateProfile: DateProfile) {
   let instanceHash = eventStore.instances

+ 12 - 13
src/reducers/eventStore.ts

@@ -2,7 +2,15 @@ import Calendar from '../Calendar'
 import { filterHash } from '../util/object'
 import { EventMutation, applyMutationToEventStore } from '../structs/event-mutation'
 import { EventDef, EventInstance, EventInput, EventInstanceHash } from '../structs/event'
-import { EventStore, parseEventStore, mergeEventStores, getRelatedEvents, createEmptyEventStore, expandEventDefInstances } from '../structs/event-store'
+import {
+  EventStore,
+  parseEventStore,
+  mergeEventStores,
+  getRelatedEvents,
+  createEmptyEventStore,
+  expandEventDefInstances,
+  filterEventStoreDefs
+} from '../structs/event-store'
 import { Action } from './types'
 import { EventSourceHash, EventSource, getEventSourceDef } from '../structs/event-source'
 import { DateRange } from '../datelib/date-range'
@@ -32,7 +40,7 @@ export default function(eventStore: EventStore, action: Action, sourceHash: Even
       return excludeInstances(eventStore, action.instances)
 
     case 'REMOVE_EVENT_DEF':
-      return filterDefs(eventStore, function(eventDef) {
+      return filterEventStoreDefs(eventStore, function(eventDef) {
         return eventDef.defId !== action.defId
       })
 
@@ -40,7 +48,7 @@ export default function(eventStore: EventStore, action: Action, sourceHash: Even
       return excludeEventsBySourceId(eventStore, action.sourceId)
 
     case 'REMOVE_ALL_EVENT_SOURCES':
-      return filterDefs(eventStore, function(eventDef: EventDef) {
+      return filterEventStoreDefs(eventStore, function(eventDef: EventDef) {
         return !eventDef.sourceId // only keep events with no source id
       })
 
@@ -116,20 +124,11 @@ function excludeInstances(eventStore: EventStore, removals: EventInstanceHash):
 }
 
 function excludeEventsBySourceId(eventStore, sourceId) {
-  return filterDefs(eventStore, function(eventDef: EventDef) {
+  return filterEventStoreDefs(eventStore, function(eventDef: EventDef) {
     return eventDef.sourceId !== sourceId
   })
 }
 
-// has extra bonus feature of removing temporary events
-function filterDefs(eventStore: EventStore, filterFunc: (eventDef: EventDef) => boolean): EventStore {
-  let defs = filterHash(eventStore.defs, filterFunc)
-  let instances = filterHash(eventStore.instances, function(instance: EventInstance) {
-    return defs[instance.defId] // still exists?
-  })
-  return { defs, instances }
-}
-
 function applyMutationToRelated(eventStore: EventStore, instanceId: string, mutation: EventMutation, calendar: Calendar): EventStore {
   let related = getRelatedEvents(eventStore, instanceId)
   related = applyMutationToEventStore(related, mutation, calendar)

+ 1 - 1
src/reducers/main.ts

@@ -18,7 +18,7 @@ export default function(state: CalendarState, action: Action, calendar: Calendar
     eventSources,
     eventStore: reduceEventStore(state.eventStore, action, eventSources, calendar),
     eventUis: state.eventUis, // TODO: should really be internal state
-    businessHoursDef: state.businessHoursDef,
+    businessHours: state.businessHours,
     dateSelection: reduceDateSelection(state.dateSelection, action),
     eventSelection: reduceSelectedEvent(state.eventSelection, action),
     eventDrag: reduceEventDrag(state.eventDrag, action),

+ 6 - 20
src/structs/business-hours.ts

@@ -2,14 +2,13 @@ import Calendar from '../Calendar'
 import { assignTo } from '../util/object'
 import { EventInput } from './event'
 import { EventStore, parseEventStore } from './event-store'
-import { DateRange } from '../datelib/date-range'
 
 /*
 Utils for converting raw business hour input into an EventStore,
 for both rendering and the constraint system.
 */
 
-export type BusinessHoursDef = boolean | EventInput | EventInput[]
+export type BusinessHoursInput = boolean | EventInput | EventInput[]
 
 const DEF_DEFAULTS = {
   startTime: '09:00',
@@ -20,21 +19,15 @@ const DEF_DEFAULTS = {
   groupId: '_businessHours' // so multiple defs get grouped
 }
 
-export function buildBusinessHours(
-  input: BusinessHoursDef,
-  isAllDay: boolean,
-  framingRange: DateRange,
-  calendar: Calendar
-): EventStore {
+export function parseBusinessHours(input: BusinessHoursInput, calendar: Calendar): EventStore {
   return parseEventStore(
-    refineInputs(input, isAllDay),
+    refineInputs(input),
     '',
-    calendar,
-    framingRange
+    calendar
   )
 }
 
-function refineInputs(input: BusinessHoursDef, isAllDay: boolean): EventInput[] {
+function refineInputs(input: BusinessHoursInput): EventInput[] {
   let rawDefs: EventInput[]
 
   if (input === true) {
@@ -51,14 +44,7 @@ function refineInputs(input: BusinessHoursDef, isAllDay: boolean): EventInput[]
   }
 
   rawDefs = rawDefs.map(function(rawDef) {
-    rawDef = assignTo({}, DEF_DEFAULTS, rawDef)
-
-    if (isAllDay) {
-      rawDef.startTime = null
-      rawDef.endTime = null
-    }
-
-    return rawDef
+    return assignTo({}, DEF_DEFAULTS, rawDef)
   })
 
   return rawDefs

+ 34 - 2
src/structs/event-store.ts

@@ -1,7 +1,16 @@
-import { EventInput, EventDefHash, EventInstanceHash, parseEventDef, parseEventDateSpan, createEventInstance, EventDef } from './event'
+import {
+  EventInput,
+  EventDef,
+  EventDefHash,
+  EventInstance,
+  EventInstanceHash,
+  parseEventDef,
+  parseEventDateSpan,
+  createEventInstance
+} from './event'
 import { parseEventDefRecurring, expandEventDef } from './recurring-event'
 import Calendar from '../Calendar'
-import { assignTo } from '../util/object'
+import { assignTo, filterHash } from '../util/object'
 import { DateRange } from '../datelib/date-range'
 
 /*
@@ -59,6 +68,21 @@ export function parseEventStore(
   return dest
 }
 
+export function expandEventStoreInstances(
+  eventStore: EventStore,
+  framingRange: DateRange,
+  calendar: Calendar
+): EventInstanceHash {
+  let dest: EventInstanceHash = {}
+
+  for (let defId in eventStore.defs) {
+    expandEventDefInstances(eventStore.defs[defId], framingRange, calendar, dest)
+  }
+
+  return dest
+}
+
+// TODO: be smarter about where this and expandEventStoreInstances are called
 export function expandEventDefInstances(
   def: EventDef,
   framingRange: DateRange,
@@ -118,3 +142,11 @@ export function mergeEventStores(store0: EventStore, store1: EventStore): EventS
     instances: assignTo({}, store0.instances, store1.instances)
   }
 }
+
+export function filterEventStoreDefs(eventStore: EventStore, filterFunc: (eventDef: EventDef) => boolean): EventStore {
+  let defs = filterHash(eventStore.defs, filterFunc)
+  let instances = filterHash(eventStore.instances, function(instance: EventInstance) {
+    return defs[instance.defId] // still exists?
+  })
+  return { defs, instances }
+}

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

@@ -9,7 +9,7 @@ import { Duration, DurationInput } from '../datelib/duration'
 import { DateInput } from '../datelib/env'
 import { FormatterInput } from '../datelib/formatting'
 import { DateRangeInput } from '../datelib/date-range'
-import { BusinessHoursDef } from '../structs/business-hours'
+import { BusinessHoursInput } from '../structs/business-hours'
 import { EventInput } from '../structs/event'
 import EventApi from '../api/EventApi'
 import { ConstraintInput } from '../interactions/constraint'
@@ -87,7 +87,7 @@ export interface OptionsInputBase {
   weekNumbers?: boolean
   weekNumbersWithinDays?: boolean
   weekNumberCalculation?: 'local' | 'ISO' | ((m: Date) => number)
-  businessHours?: BusinessHoursDef
+  businessHours?: BusinessHoursInput
   showNonCurrentDates?: boolean
   height?: number | 'auto' | 'parent' | (() => number)
   contentHeight?: number | 'auto' | (() => number)