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

eventOverlap is the only things that accepts a function

Adam Shaw 7 лет назад
Родитель
Сommit
7660be4e6a
7 измененных файлов с 104 добавлено и 100 удалено
  1. 18 5
      src/Calendar.ts
  2. 1 1
      src/api/EventApi.ts
  3. 8 23
      src/component/event-ui.ts
  4. 2 1
      src/exports.ts
  5. 16 2
      src/structs/event-source.ts
  6. 19 5
      src/types/input-types.ts
  7. 40 63
      src/validation.ts

+ 18 - 5
src/Calendar.ts

@@ -24,7 +24,7 @@ import { CalendarState, Action } from './reducers/types'
 import EventSourceApi from './api/EventSourceApi'
 import EventApi from './api/EventApi'
 import { createEmptyEventStore, EventStore, eventTupleToStore } from './structs/event-store'
-import { processScopedUiProps, EventUiHash, EventUi, processUnscopedUiProps } from './component/event-ui'
+import { processScopedUiProps, EventUiHash, EventUi } from './component/event-ui'
 import PointerDragging, { PointerDragEvent } from './dnd/PointerDragging'
 import EventDragging from './interactions/EventDragging'
 import { buildViewSpecs, ViewSpecHash, ViewSpec } from './structs/view-spec'
@@ -64,8 +64,8 @@ export default class Calendar {
 
   private buildDateEnv = memoize(buildDateEnv)
   private buildTheme = memoize(buildTheme)
-  private buildEventUiSingleBase = memoize(processScopedUiProps.bind(null, 'event') as typeof processUnscopedUiProps) // hack for ts
-  private buildSelectionConfig = memoize(processScopedUiProps.bind(null, 'select') as typeof processUnscopedUiProps) // hack for ts
+  private buildEventUiSingleBase = memoize(this._buildSelectionConfig)
+  private buildSelectionConfig = memoize(this._buildEventUiSingleBase)
   private buildEventUiBySource = memoizeOutput(buildEventUiBySource, isPropsEqual)
   private buildEventUiBases = memoize(buildEventUiBases)
 
@@ -406,7 +406,7 @@ export default class Calendar {
         this.renderableEventStore :
         state.eventStore
 
-    let eventUiSingleBase = this.buildEventUiSingleBase(viewSpec.options, this)
+    let eventUiSingleBase = this.buildEventUiSingleBase(viewSpec.options)
     let eventUiBySource = this.buildEventUiBySource(state.eventSources)
     let eventUiBases = this.eventUiBases = this.buildEventUiBases(renderableEventStore.defs, eventUiSingleBase, eventUiBySource)
 
@@ -545,7 +545,7 @@ export default class Calendar {
       options.cmdFormatter
     )
 
-    this.selectionConfig = this.buildSelectionConfig(options, this) // needs dateEnv. do after :(
+    this.selectionConfig = this.buildSelectionConfig(options) // needs dateEnv. do after :(
 
     // ineffecient to do every time?
     this.viewSpecs = buildViewSpecs(
@@ -561,6 +561,19 @@ export default class Calendar {
   }
 
 
+  _buildSelectionConfig(rawOpts) {
+    return processScopedUiProps('select', rawOpts, this)
+  }
+
+
+  _buildEventUiSingleBase(rawOpts) {
+    if (rawOpts.editable) { // so 'editable' affected events
+      rawOpts = Object.assign({}, rawOpts, { eventEditable: true })
+    }
+    return processScopedUiProps('event', rawOpts, this)
+  }
+
+
   // Trigger
   // -----------------------------------------------------------------------------------------------------------------
 

+ 1 - 1
src/api/EventApi.ts

@@ -259,7 +259,7 @@ export default class EventApi implements EventTuple {
   get startEditable(): boolean { return this.def.ui.startEditable }
   get durationEditable(): boolean { return this.def.ui.durationEditable }
   get constraint(): any { return this.def.ui.constraints[0] || null }
-  get overlap(): any { return this.def.ui.overlaps[0] || null }
+  get overlap(): any { return this.def.ui.overlap }
   get allow(): any { return this.def.ui.allows[0] || null }
   get backgroundColor(): string { return this.def.ui.backgroundColor }
   get borderColor(): string { return this.def.ui.borderColor }

+ 8 - 23
src/component/event-ui.ts

@@ -1,4 +1,4 @@
-import { Constraint, Allow, normalizeConstraint, ConstraintInput, Overlap } from '../validation'
+import { Constraint, AllowFunc, normalizeConstraint, ConstraintInput } from '../validation'
 import { parseClassName } from '../util/html'
 import { refineProps, capitaliseFirstLetter } from '../util/misc'
 import Calendar from '../Calendar'
@@ -12,8 +12,8 @@ export interface UnscopedEventUiInput {
   startEditable?: boolean
   durationEditable?: boolean
   constraint?: ConstraintInput
-  overlap?: Overlap
-  allow?: Allow
+  overlap?: boolean
+  allow?: AllowFunc
   className?: string[] | string
   classNames?: string[] | string
   backgroundColor?: string
@@ -22,27 +22,12 @@ export interface UnscopedEventUiInput {
   color?: string
 }
 
-export interface EventScopedEventUiInput { // has the word "event" in all the props
-  editable?: boolean // only one not scoped
-  eventStartEditable?: boolean
-  eventDurationEditable?: boolean
-  eventConstraint?: ConstraintInput
-  eventOverlap?: Overlap
-  eventAllow?: Allow
-  eventClassName?: string[] | string
-  eventClassNames?: string[] | string
-  eventBackgroundColor?: string
-  eventBorderColor?: string
-  eventTextColor?: string
-  eventColor?: string
-}
-
 export interface EventUi {
   startEditable: boolean | null
   durationEditable: boolean | null
   constraints: Constraint[]
-  overlaps: Overlap[]
-  allows: Allow[]
+  overlap: boolean | null
+  allows: AllowFunc[]
   backgroundColor: string
   borderColor: string
   textColor: string,
@@ -74,7 +59,7 @@ export function processUnscopedUiProps(rawProps: UnscopedEventUiInput, calendar:
     startEditable: props.startEditable != null ? props.startEditable : props.editable,
     durationEditable: props.durationEditable != null ? props.durationEditable : props.editable,
     constraints: constraint != null ? [ constraint ] : [],
-    overlaps: props.overlap != null ? [ props.overlap ] : [],
+    overlap: props.overlap,
     allows: props.allow != null ? [ props.allow ] : [],
     backgroundColor: props.backgroundColor || props.color,
     borderColor: props.borderColor || props.color,
@@ -101,7 +86,7 @@ const EMPTY_EVENT_UI: EventUi = {
   startEditable: null,
   durationEditable: null,
   constraints: [],
-  overlaps: [],
+  overlap: null,
   allows: [],
   backgroundColor: '',
   borderColor: '',
@@ -119,7 +104,7 @@ function combineTwoEventUis(item0: EventUi, item1: EventUi): EventUi { // hash1
     startEditable: item1.startEditable != null ? item1.startEditable : item0.startEditable,
     durationEditable: item1.durationEditable != null ? item1.durationEditable : item0.durationEditable,
     constraints: item0.constraints.concat(item1.constraints),
-    overlaps: item0.overlaps.concat(item1.overlaps),
+    overlap: typeof item1.overlap === 'boolean' ? item1.overlap : item0.overlap,
     allows: item0.allows.concat(item1.allows),
     backgroundColor: item1.backgroundColor || item0.backgroundColor,
     borderColor: item1.borderColor || item0.borderColor,

+ 2 - 1
src/exports.ts

@@ -61,7 +61,7 @@ export {
 } from './util/dom-manip'
 
 export { EventStore, filterEventStoreDefs, createEmptyEventStore } from './structs/event-store'
-export { EventUiHash, EventUi, processScopedUiProps, EventScopedEventUiInput, combineEventUis } from './component/event-ui'
+export { EventUiHash, EventUi, processScopedUiProps, combineEventUis } from './component/event-ui'
 export { default as Splitter, SplittableProps } from './component/event-splitting'
 export { buildGotoAnchorHtml, getAllDayHtml, getDayClasses } from './component/date-rendering'
 
@@ -161,3 +161,4 @@ export { default as DayTable, DayTableSeg, DayTableCell } from './common/DayTabl
 export { default as Slicer, SlicedProps } from './common/slicing-utils'
 
 export { EventMutation } from './structs/event-mutation'
+export { ConstraintInput, AllowFunc } from './validation'

+ 16 - 2
src/structs/event-source.ts

@@ -3,8 +3,9 @@ import { EventInput } from './event'
 import Calendar from '../Calendar'
 import { DateRange } from '../datelib/date-range'
 import { EventSourceFunc } from '../event-sources/func-event-source'
-import { EventScopedEventUiInput, processUnscopedUiProps } from '../component/event-ui'
+import { processUnscopedUiProps } from '../component/event-ui'
 import { EventUi } from '../component/event-ui'
+import { ConstraintInput, AllowFunc } from '../validation'
 
 /*
 Parsing and normalization of the EventSource data type, which defines how event data is fetched.
@@ -23,7 +24,7 @@ export type EventInputTransformer = (eventInput: EventInput) => EventInput | nul
 export type EventSourceSuccessResponseHandler = (rawData: any, response: any) => EventInput[] | void
 export type EventSourceErrorResponseHandler = (error: EventSourceError) => void
 
-export interface ExtendedEventSourceInput extends EventScopedEventUiInput {
+export interface ExtendedEventSourceInput {
   id?: string | number // only accept number?
   allDayDefault?: boolean
   eventDataTransform?: EventInputTransformer
@@ -43,6 +44,19 @@ export interface ExtendedEventSourceInput extends EventScopedEventUiInput {
   success?: EventSourceSuccessResponseHandler
   failure?: EventSourceErrorResponseHandler
 
+  editable?: boolean
+  startEditable?: boolean
+  durationEditable?: boolean
+  constraint?: ConstraintInput
+  overlap?: boolean
+  allow?: AllowFunc
+  className?: string[] | string
+  classNames?: string[] | string
+  backgroundColor?: string
+  borderColor?: string
+  textColor?: string
+  color?: string
+
   [otherProp: string]: any // in case plugins want more props
 }
 

+ 19 - 5
src/types/input-types.ts

@@ -11,9 +11,8 @@ import { FormatterInput } from '../datelib/formatting'
 import { DateRangeInput } from '../datelib/date-range'
 import { BusinessHoursInput } from '../structs/business-hours'
 import EventApi from '../api/EventApi'
-import { Allow, ConstraintInput, Overlap } from '../validation'
+import { AllowFunc, ConstraintInput, OverlapFunc } from '../validation'
 import { PluginDef } from '../plugin-system'
-import { UnscopedEventUiInput } from '../component/event-ui'
 
 
 export interface ToolbarInput {
@@ -71,7 +70,7 @@ export interface DropInfo {
   end: Date
 }
 
-export interface OptionsInputBase extends UnscopedEventUiInput {
+export interface OptionsInputBase {
   header?: boolean | ToolbarInput
   footer?: boolean | ToolbarInput
   customButtons?: { [name: string]: CustomButtonInput }
@@ -140,9 +139,24 @@ export interface OptionsInputBase extends UnscopedEventUiInput {
   selectMirror?: boolean
   unselectAuto?: boolean
   unselectCancel?: string
+
   selectConstraint?: ConstraintInput
-  selectOverlap?: Overlap
-  selectAllow?: Allow
+  selectOverlap?: boolean | OverlapFunc
+  selectAllow?: AllowFunc
+
+  editable?: boolean
+  eventStartEditable?: boolean
+  eventDurationEditable?: boolean
+  eventConstraint?: ConstraintInput
+  eventOverlap?: boolean | OverlapFunc // allows a function, unlike EventUi
+  eventAllow?: AllowFunc
+  eventClassName?: string[] | string
+  eventClassNames?: string[] | string
+  eventBackgroundColor?: string
+  eventBorderColor?: string
+  eventTextColor?: string
+  eventColor?: string
+
   events?: EventSourceInput
   eventSources?: EventSourceInput[]
   allDayDefault?: boolean

+ 40 - 63
src/validation.ts

@@ -1,7 +1,7 @@
 import { EventStore, expandRecurring, eventTupleToStore, filterEventStoreDefs, createEmptyEventStore } from './structs/event-store'
 import Calendar from './Calendar'
 import { DateSpan, parseOpenDateSpan, OpenDateSpanInput, OpenDateSpan, buildDateSpanApi, DateSpanApi } from './structs/date-span'
-import { EventInstance, EventDef, EventTuple, parseEvent } from './structs/event'
+import { EventTuple, parseEvent } from './structs/event'
 import { rangeContainsRange, rangesIntersect, DateRange, OpenDateRange } from './datelib/date-range'
 import EventApi from './api/EventApi'
 import { EventUiHash } from './component/event-ui'
@@ -12,8 +12,8 @@ import { excludeInstances } from './reducers/eventStore'
 // TODO: rename to "criteria" ?
 export type ConstraintInput = 'businessHours' | string | OpenDateSpanInput | { [timeOrRecurringProp: string]: any }
 export type Constraint = 'businessHours' | string | OpenDateSpan | EventTuple
-export type Overlap = boolean | ((stillEvent: EventApi, movingEvent: EventApi | null) => boolean)
-export type Allow = (span: DateSpanApi, movingEvent: EventApi | null) => boolean
+export type OverlapFunc = ((stillEvent: EventApi, movingEvent: EventApi | null) => boolean)
+export type AllowFunc = (span: DateSpanApi, movingEvent: EventApi | null) => boolean
 
 
 // high-level segmenting-aware tester functions
@@ -114,46 +114,55 @@ function isSegmentedEventsValid(
     let subjectRange = subjectInstance.range
     let subjectConfig = subjectConfigs[subjectInstance.defId]
     let subjectDef = subjectDefs[subjectInstance.defId]
-    let { constraints, overlaps, allows } = subjectConfig
 
     if (splitterMeta && !splitterMeta.eventAllowsKey(subjectDef, calendar, currentSegmentKey)) { // TODO: pass in EventUi
       return false
     }
 
     // constraint
-    for (let constraint of constraints) {
+    for (let subjectConstraint of subjectConfig.constraints) {
 
-      if (!constraintPasses(constraint, subjectRange, otherEventStore, businessHoursUnexpanded, calendar)) {
+      if (!constraintPasses(subjectConstraint, subjectRange, otherEventStore, businessHoursUnexpanded, calendar)) {
         return false
       }
 
-      if (splitterMeta && !splitterMeta.constraintAllowsKey(constraint, currentSegmentKey)) {
+      if (splitterMeta && !splitterMeta.constraintAllowsKey(subjectConstraint, currentSegmentKey)) {
         return false
       }
     }
 
     // overlap
+
+    let overlapFunc = calendar.opt('eventOverlap')
+    if (typeof overlapFunc !== 'function') { overlapFunc = null }
+
     for (let otherInstanceId in otherInstances) {
       let otherInstance = otherInstances[otherInstanceId]
-      let otherDef = otherDefs[otherInstance.defId]
 
       // intersect! evaluate
       if (rangesIntersect(subjectRange, otherInstance.range)) {
-        let otherOverlaps = otherConfigs[otherInstance.defId].overlaps
+        let otherOverlap = otherConfigs[otherInstance.defId].overlap
 
         // consider the other event's overlap. only do this if the subject event is a "real" event
-        if (!isntEvent && !allOverlapsPass(otherOverlaps, otherDef, otherInstance, subjectDef, subjectInstance, calendar)) {
+        if (otherOverlap === false && !isntEvent) {
+          return false
+        }
+
+        if (subjectConfig.overlap === false) {
           return false
         }
 
-        if (!allOverlapsPass(overlaps, subjectDef, subjectInstance, otherDef, otherInstance, calendar)) {
+        if (overlapFunc && !overlapFunc(
+          new EventApi(calendar, otherDefs[otherInstance.defId], otherInstance), // still event
+          new EventApi(calendar, subjectDef, subjectInstance) // moving event
+        )) {
           return false
         }
       }
     }
 
     // allow (a function)
-    for (let allow of allows) {
+    for (let subjectAllow of subjectConfig.allows) {
       let origDef = relevantEventStore.defs[subjectDef.defId]
       let origInstance = relevantEventStore.instances[subjectInstanceId]
 
@@ -163,7 +172,7 @@ function isSegmentedEventsValid(
         { range: subjectInstance.range, allDay: subjectDef.allDay }
       )
 
-      if (!allow(
+      if (!subjectAllow(
         buildDateSpanApi(subjectDateSpan, calendar.dateEnv),
         new EventApi(calendar, origDef, origInstance)
       )) {
@@ -184,39 +193,48 @@ function isSegmentedSelectionValid(
   splitterMeta: ValidationSplitterMeta | null,
   currentSegmentKey: string
 ): boolean {
-  let relevantInstances = relevantEventStore.instances
   let relevantDefs = relevantEventStore.defs
+  let relevantInstances = relevantEventStore.instances
   let selectionRange = selection.range
-  let { constraints, overlaps, allows } = calendar.selectionConfig
+  let { selectionConfig } = calendar
 
   // constraint
-  for (let constraint of constraints) {
+  for (let selectionConstraint of selectionConfig.constraints) {
 
-    if (!constraintPasses(constraint, selectionRange, relevantEventStore, businessHoursUnexpanded, calendar)) {
+    if (!constraintPasses(selectionConstraint, selectionRange, relevantEventStore, businessHoursUnexpanded, calendar)) {
       return false
     }
 
-    if (splitterMeta && !splitterMeta.constraintAllowsKey(constraint, currentSegmentKey)) {
+    if (splitterMeta && !splitterMeta.constraintAllowsKey(selectionConstraint, currentSegmentKey)) {
       return false
     }
   }
 
   // overlap
+
+  let overlapFunc = calendar.opt('selectOverlap')
+  if (typeof overlapFunc !== 'function') { overlapFunc = null }
+
   for (let relevantInstanceId in relevantInstances) {
     let relevantInstance = relevantInstances[relevantInstanceId]
-    let relevantDef = relevantDefs[relevantInstance.defId]
 
     // intersect! evaluate
     if (rangesIntersect(selectionRange, relevantInstance.range)) {
 
-      if (!allOverlapsPass(overlaps, null, null, relevantDef, relevantInstance, calendar)) {
+      if (selectionConfig.overlap === false) {
+        return false
+      }
+
+      if (overlapFunc && !overlapFunc(
+        new EventApi(calendar, relevantDefs.defs[relevantInstance.defId], relevantInstance)
+      )) {
         return false
       }
     }
   }
 
   // allow (a function)
-  for (let allow of allows) {
+  for (let selectionAllow of selectionConfig.allows) {
 
     let fullDateSpan = Object.assign(
       {},
@@ -224,7 +242,7 @@ function isSegmentedSelectionValid(
       selection,
     )
 
-    if (!allow(
+    if (!selectionAllow(
       buildDateSpanApi(fullDateSpan, calendar.dateEnv),
       null
     )) {
@@ -312,47 +330,6 @@ function anyRangesContainRange(outerRanges: DateRange[], innerRange: DateRange):
 }
 
 
-// Overlap Utils
-// ------------------------------------------------------------------------------------------------------------------------
-
-function allOverlapsPass(
-  overlaps: Overlap[],
-  subjectDef: EventDef | null,
-  subjectInstance: EventInstance | null,
-  otherDef: EventDef,
-  otherInstance: EventInstance,
-  calendar: Calendar
-) {
-  for (let overlap of overlaps) {
-    if (!overlapPasses(overlap, subjectDef, subjectInstance, otherDef, otherInstance, calendar)) {
-      return false
-    }
-  }
-
-  return true
-}
-
-function overlapPasses(
-  overlap: Overlap,
-  subjectDef: EventDef | null,
-  subjectInstance: EventInstance | null,
-  otherDef: EventDef,
-  otherInstance: EventInstance,
-  calendar: Calendar
-) {
-  if (overlap === false) {
-    return false
-  } else if (typeof overlap === 'function') {
-    return !overlap(
-      new EventApi(calendar, otherDef, otherInstance),
-      subjectDef ? new EventApi(calendar, subjectDef, subjectInstance) : null
-    )
-  }
-
-  return true
-}
-
-
 // Splitting Utils
 // ------------------------------------------------------------------------------------------------------------------------