Преглед изворни кода

beginnings of working validation

Adam Shaw пре 7 година
родитељ
комит
ad7fd92a93

+ 24 - 1
src/component/DateComponent.ts

@@ -15,10 +15,11 @@ import { EventInteractionUiState } from '../interactions/event-interaction-state
 import { assignTo } from '../util/object'
 import { assignTo } from '../util/object'
 import browserContext from '../common/browser-context'
 import browserContext from '../common/browser-context'
 import { Hit } from '../interactions/HitDragging'
 import { Hit } from '../interactions/HitDragging'
-import { DateRange, rangeContainsMarker } from '../datelib/date-range'
+import { DateRange, rangeContainsMarker, rangeContainsRange } from '../datelib/date-range'
 import EventApi from '../api/EventApi'
 import EventApi from '../api/EventApi'
 import { createEventInstance, parseEventDef } from '../structs/event'
 import { createEventInstance, parseEventDef } from '../structs/event'
 import EmitterMixin from '../common/EmitterMixin'
 import EmitterMixin from '../common/EmitterMixin'
+import { isEventsValid, isSelectionValid } from '../validation'
 
 
 
 
 export interface DateComponentRenderState {
 export interface DateComponentRenderState {
@@ -1118,4 +1119,26 @@ export default abstract class DateComponent extends Component {
     return popoverEl && popoverEl !== this.el // if the current component IS a popover, okay
     return popoverEl && popoverEl !== this.el // if the current component IS a popover, okay
   }
   }
 
 
+  isEventsValid(eventStore: EventStore) {
+    let { validRange } = this.dateProfile
+    let instances = eventStore.instances
+
+    for (let instanceId in instances) {
+      if (!rangeContainsRange(validRange, instances[instanceId].range)) {
+        return false
+      }
+    }
+
+    return isEventsValid(eventStore, this.getCalendar())
+  }
+
+  isSelectionValid(selection: DateSpan): boolean {
+
+    if (!rangeContainsRange(this.dateProfile.validRange, selection.range)) {
+      return false
+    }
+
+    return isSelectionValid(selection, this.getCalendar())
+  }
+
 }
 }

+ 7 - 0
src/datelib/date-range.ts

@@ -103,6 +103,13 @@ export function intersectRanges(range0: OpenDateRange, range1: OpenDateRange): O
   return newRange
   return newRange
 }
 }
 
 
+export function joinRanges(range0: DateRange, range1: DateRange): DateRange {
+  return {
+    start: range0.start < range1.start ? range0.start : range1.start,
+    end: range0.end > range1.end ? range0.end : range1.end
+  }
+}
+
 export function rangesEqual(range0: OpenDateRange, range1: OpenDateRange): boolean {
 export function rangesEqual(range0: OpenDateRange, range1: OpenDateRange): boolean {
   return (range0.start === null ? null : range0.start.valueOf()) === (range1.start === null ? null : range1.start.valueOf()) &&
   return (range0.start === null ? null : range0.start.valueOf()) === (range1.start === null ? null : range1.start.valueOf()) &&
     (range0.end === null ? null : range0.end.valueOf()) === (range1.end === null ? null : range1.end.valueOf())
     (range0.end === null ? null : range0.end.valueOf()) === (range1.end === null ? null : range1.end.valueOf())

+ 1 - 2
src/interactions/EventDragging.ts

@@ -13,7 +13,6 @@ import { EventInteractionState } from '../interactions/event-interaction-state'
 import { diffDates, enableCursor, disableCursor } from '../util/misc'
 import { diffDates, enableCursor, disableCursor } from '../util/misc'
 import { EventRenderRange } from '../component/event-rendering'
 import { EventRenderRange } from '../component/event-rendering'
 import EventApi from '../api/EventApi'
 import EventApi from '../api/EventApi'
-import { isEventStoreValid } from './constraint'
 
 
 export default class EventDragging { // TODO: rename to EventSelectingAndDragging
 export default class EventDragging { // TODO: rename to EventSelectingAndDragging
 
 
@@ -148,7 +147,7 @@ export default class EventDragging { // TODO: rename to EventSelectingAndDraggin
       if (mutation) {
       if (mutation) {
         mutatedRelatedEvents = applyMutationToEventStore(relatedEvents, mutation, receivingCalendar)
         mutatedRelatedEvents = applyMutationToEventStore(relatedEvents, mutation, receivingCalendar)
 
 
-        if (!isEventStoreValid(mutatedRelatedEvents, this.component.dateProfile)) {
+        if (!this.component.isEventsValid(mutatedRelatedEvents)) {
           isInvalid = true
           isInvalid = true
           mutation = null
           mutation = null
           mutatedRelatedEvents = null
           mutatedRelatedEvents = null

+ 1 - 2
src/interactions/EventResizing.ts

@@ -10,7 +10,6 @@ import { diffDates, enableCursor, disableCursor } from '../util/misc'
 import { DateRange } from '../datelib/date-range'
 import { DateRange } from '../datelib/date-range'
 import EventApi from '../api/EventApi'
 import EventApi from '../api/EventApi'
 import { EventRenderRange } from '../component/event-rendering'
 import { EventRenderRange } from '../component/event-rendering'
-import { isEventStoreValid } from './constraint'
 import { createDuration } from '../datelib/duration'
 import { createDuration } from '../datelib/duration'
 
 
 export default class EventDragging {
 export default class EventDragging {
@@ -100,7 +99,7 @@ export default class EventDragging {
     if (mutation) {
     if (mutation) {
       mutatedRelatedEvents = applyMutationToEventStore(relatedEvents, mutation, calendar)
       mutatedRelatedEvents = applyMutationToEventStore(relatedEvents, mutation, calendar)
 
 
-      if (!isEventStoreValid(mutatedRelatedEvents, this.component.dateProfile)) {
+      if (!this.component.isEventsValid(mutatedRelatedEvents)) {
         isInvalid = true
         isInvalid = true
         mutation = null
         mutation = null
         mutatedRelatedEvents = null
         mutatedRelatedEvents = null

+ 0 - 25
src/interactions/constraint.ts

@@ -1,25 +0,0 @@
-import { DateRangeInput, rangeContainsRange } from '../datelib/date-range'
-import { BusinessHoursInput } from '../structs/business-hours'
-import { EventStore } from '../structs/event-store'
-import { DateProfile } from '../DateProfileGenerator'
-
-export type ConstraintInput = DateRangeInput | BusinessHoursInput | 'businessHours'
-
-export function isEventStoreValid(eventStore: EventStore, dateProfile: DateProfile) {
-  let instanceHash = eventStore.instances
-
-  if (dateProfile) { // for Popover
-    for (let instanceId in instanceHash) {
-      if (
-        !rangeContainsRange(
-          dateProfile.validRange,
-          instanceHash[instanceId].range
-        )
-      ) {
-        return false
-      }
-    }
-  }
-
-  return true
-}

+ 64 - 32
src/structs/date-span.ts

@@ -1,4 +1,4 @@
-import { DateRange, rangesEqual } from '../datelib/date-range'
+import { DateRange, rangesEqual, OpenDateRange } from '../datelib/date-range'
 import { DateInput, DateEnv } from '../datelib/env'
 import { DateInput, DateEnv } from '../datelib/env'
 import { refineProps } from '../util/misc'
 import { refineProps } from '../util/misc'
 import { Duration } from '../datelib/duration'
 import { Duration } from '../datelib/duration'
@@ -8,19 +8,28 @@ A data-structure for a date-range that will be visually displayed.
 Contains other metadata like isAllDay, and anything else Components might like to store.
 Contains other metadata like isAllDay, and anything else Components might like to store.
 */
 */
 
 
-export interface DateSpanInput {
-  start: DateInput
-  end: DateInput
+export interface OpenDateSpanInput {
+  start?: DateInput
+  end?: DateInput
   isAllDay?: boolean
   isAllDay?: boolean
   [otherProp: string]: any
   [otherProp: string]: any
 }
 }
 
 
-export interface DateSpan {
-  range: DateRange
+export interface DateSpanInput extends OpenDateSpanInput {
+  start: DateInput
+  end: DateInput
+}
+
+export interface OpenDateSpan {
+  range: OpenDateRange
   isAllDay: boolean
   isAllDay: boolean
   [otherProp: string]: any
   [otherProp: string]: any
 }
 }
 
 
+export interface DateSpan extends OpenDateSpan {
+  range: DateRange
+}
+
 const STANDARD_PROPS = {
 const STANDARD_PROPS = {
   start: null,
   start: null,
   end: null,
   end: null,
@@ -28,54 +37,63 @@ const STANDARD_PROPS = {
 }
 }
 
 
 export function parseDateSpan(raw: DateSpanInput, dateEnv: DateEnv, defaultDuration?: Duration): DateSpan | null {
 export function parseDateSpan(raw: DateSpanInput, dateEnv: DateEnv, defaultDuration?: Duration): DateSpan | null {
+  let span = parseOpenDateSpan(raw, dateEnv)
+  let { range } = span
+
+  if (!range.start) {
+    return null
+  }
+
+  if (!range.end) {
+    if (defaultDuration == null) {
+      return null
+    } else {
+      range.end = dateEnv.add(range.start, defaultDuration)
+    }
+  }
+
+  return span as DateSpan
+}
+
+/*
+TODO: somehow combine with parseRange?
+*/
+export function parseOpenDateSpan(raw: OpenDateSpanInput, dateEnv: DateEnv): OpenDateSpan | null {
   let leftovers = {} as DateSpan
   let leftovers = {} as DateSpan
   let standardProps = refineProps(raw, STANDARD_PROPS, {}, leftovers)
   let standardProps = refineProps(raw, STANDARD_PROPS, {}, leftovers)
   let startMeta = standardProps.start ? dateEnv.createMarkerMeta(standardProps.start) : null
   let startMeta = standardProps.start ? dateEnv.createMarkerMeta(standardProps.start) : null
-  let startMarker
   let endMeta = standardProps.end ? dateEnv.createMarkerMeta(standardProps.end) : null
   let endMeta = standardProps.end ? dateEnv.createMarkerMeta(standardProps.end) : null
-  let endMarker
   let isAllDay = standardProps.isAllDay
   let isAllDay = standardProps.isAllDay
 
 
   if (startMeta) {
   if (startMeta) {
-    startMarker = startMeta.marker
 
 
     if (isAllDay == null) {
     if (isAllDay == null) {
-      isAllDay = startMeta.isTimeUnspecified && (!endMeta || endMeta.isTimeUnspecified)
-    }
-
-    if (endMeta) {
-      endMarker = endMeta.marker
-    } else if (defaultDuration) {
-      endMarker = dateEnv.add(startMarker, defaultDuration)
+      isAllDay = (startMeta && startMeta.isTimeUnspecified) &&
+        (!endMeta || endMeta.isTimeUnspecified)
     }
     }
 
 
-    if (endMarker) {
-
-      // use this leftover object as the selection object
-      leftovers.range = { start: startMarker, end: endMarker }
-      leftovers.isAllDay = isAllDay
+    // use this leftover object as the selection object
+    leftovers.range = { start: startMeta.marker, end: endMeta.marker }
+    leftovers.isAllDay = isAllDay
 
 
-      return leftovers
-    }
+    return leftovers
   }
   }
 
 
   return null
   return null
 }
 }
 
 
 export function isDateSpansEqual(span0: DateSpan, span1: DateSpan): boolean {
 export function isDateSpansEqual(span0: DateSpan, span1: DateSpan): boolean {
+  return rangesEqual(span0.range, span1.range) && isDateSpanPropsEqual(span0, span1)
+}
 
 
-  if (!rangesEqual(span0.range, span1.range)) {
-    return false
-  }
+// besides range
+export function isDateSpanPropsEqual(span0: DateSpan, span1: DateSpan): boolean {
 
 
-  for (let propName in span1) {
-    if (propName !== 'range') {
-      if (span0[propName] !== span1[propName]) {
-        return false
-      }
-    }
+  if (!isDateSpanPropsWithin(span0, span1)) {
+    return false
   }
   }
 
 
+  // are there any props that span0 has that span1 DOESN'T have?
   for (let propName in span0) {
   for (let propName in span0) {
     if (!(propName in span1)) {
     if (!(propName in span1)) {
       return false
       return false
@@ -84,3 +102,17 @@ export function isDateSpansEqual(span0: DateSpan, span1: DateSpan): boolean {
 
 
   return true
   return true
 }
 }
+
+// does subjectSpan have all the props that validationSpan has? (subjectSpan can be a superset)
+export function isDateSpanPropsWithin(subjectSpan: DateSpan, validationSpan: DateSpan): boolean {
+
+  for (let propName in validationSpan) {
+    if (propName !== 'range') {
+      if (subjectSpan[propName] !== validationSpan[propName]) {
+        return false
+      }
+    }
+  }
+
+  return true
+}

+ 10 - 6
src/structs/event-source.ts

@@ -4,6 +4,7 @@ import { EventInput } from './event'
 import Calendar from '../Calendar'
 import Calendar from '../Calendar'
 import { DateRange } from '../datelib/date-range'
 import { DateRange } from '../datelib/date-range'
 import { EventSourceFunc } from '../event-sources/func-event-source'
 import { EventSourceFunc } from '../event-sources/func-event-source'
+import { ConstraintInput, Constraint, normalizeConstraint, Allow } from '../validation'
 
 
 /*
 /*
 Parsing and normalization of the EventSource data type, which defines how event data is fetched.
 Parsing and normalization of the EventSource data type, which defines how event data is fetched.
@@ -29,8 +30,9 @@ export interface ExtendedEventSourceInput {
   editable?: boolean
   editable?: boolean
   startEditable?: boolean
   startEditable?: boolean
   durationEditable?: boolean
   durationEditable?: boolean
-  overlap?: any
-  constraint?: any
+  constraint?: ConstraintInput
+  overlap?: boolean
+  allow?: Allow
   rendering?: string
   rendering?: string
   className?: ClassNameInput
   className?: ClassNameInput
   color?: string
   color?: string
@@ -73,8 +75,9 @@ export interface EventSource {
   eventDataTransform: EventInputTransformer
   eventDataTransform: EventInputTransformer
   startEditable: boolean | null
   startEditable: boolean | null
   durationEditable: boolean | null
   durationEditable: boolean | null
-  overlap: any
-  constraint: any
+  constraint: Constraint | null
+  overlap: boolean | null // does not allow full Overlap data type
+  allow: Allow | null
   rendering: string
   rendering: string
   className: string[]
   className: string[]
   backgroundColor: string
   backgroundColor: string
@@ -107,8 +110,9 @@ const SIMPLE_SOURCE_PROPS = {
   eventDataTransform: Function,
   eventDataTransform: Function,
   startEditable: Boolean,
   startEditable: Boolean,
   durationEditable: Boolean,
   durationEditable: Boolean,
-  overlap: null,
-  constraint: null,
+  constraint: normalizeConstraint,
+  overlap: Boolean,
+  allow: null,
   rendering: String,
   rendering: String,
   className: parseClassName,
   className: parseClassName,
   backgroundColor: String,
   backgroundColor: String,

+ 18 - 1
src/structs/event-store.ts

@@ -11,7 +11,7 @@ import {
 import { expandRecurringRanges } from './recurring-event'
 import { expandRecurringRanges } from './recurring-event'
 import Calendar from '../Calendar'
 import Calendar from '../Calendar'
 import { assignTo, filterHash } from '../util/object'
 import { assignTo, filterHash } from '../util/object'
-import { DateRange } from '../datelib/date-range'
+import { DateRange, joinRanges } from '../datelib/date-range'
 
 
 /*
 /*
 A data structure that encapsulates EventDefs and EventInstances.
 A data structure that encapsulates EventDefs and EventInstances.
@@ -113,6 +113,23 @@ export function getRelatedEvents(eventStore: EventStore, instanceId: string): Ev
   return dest
   return dest
 }
 }
 
 
+/*
+Returns a date range that tightly contains all instances
+*/
+export function getStoreRange(store: EventStore): DateRange | null {
+  let instances = store.instances
+  let joinedRange: DateRange | null = null
+
+  for (let instanceId in instances) {
+    let instance = instances[instanceId]
+    joinedRange = joinedRange ?
+      joinRanges(joinedRange, instance.range) :
+      instance.range
+  }
+
+  return joinedRange
+}
+
 export function transformRawEvents(rawEvents, func) {
 export function transformRawEvents(rawEvents, func) {
   let refinedEvents
   let refinedEvents
 
 

+ 7 - 6
src/structs/event.ts

@@ -6,6 +6,7 @@ import { assignTo } from '../util/object'
 import { DateRange } from '../datelib/date-range'
 import { DateRange } from '../datelib/date-range'
 import { startOfDay } from '../datelib/marker'
 import { startOfDay } from '../datelib/marker'
 import { parseRecurring } from './recurring-event'
 import { parseRecurring } from './recurring-event'
+import { ConstraintInput, Constraint, normalizeConstraint } from '../validation'
 
 
 /*
 /*
 Utils for parsing event-input data. Each util parses a subset of the event-input's data.
 Utils for parsing event-input data. Each util parses a subset of the event-input's data.
@@ -22,8 +23,8 @@ export interface EventNonDateInput {
   editable?: boolean
   editable?: boolean
   startEditable?: boolean
   startEditable?: boolean
   durationEditable?: boolean
   durationEditable?: boolean
-  constraint?: any
-  overlap?: any
+  constraint?: ConstraintInput
+  overlap?: boolean
   rendering?: EventRenderingChoice
   rendering?: EventRenderingChoice
   classNames?: ClassNameInput // accept both
   classNames?: ClassNameInput // accept both
   className?: ClassNameInput //
   className?: ClassNameInput //
@@ -56,8 +57,8 @@ export interface EventDef {
   url: string
   url: string
   startEditable: boolean | null
   startEditable: boolean | null
   durationEditable: boolean | null
   durationEditable: boolean | null
-  constraint: any
-  overlap: any
+  constraint: Constraint | null
+  overlap: boolean | null // does not allow full Overlap data type
   rendering: EventRenderingChoice
   rendering: EventRenderingChoice
   classNames: string[]
   classNames: string[]
   backgroundColor: string
   backgroundColor: string
@@ -90,8 +91,8 @@ const NON_DATE_PROPS = {
   editable: Boolean,
   editable: Boolean,
   startEditable: Boolean,
   startEditable: Boolean,
   durationEditable: Boolean,
   durationEditable: Boolean,
-  constraint: null,
-  overlap: null,
+  constraint: normalizeConstraint,
+  overlap: Boolean,
   rendering: String,
   rendering: String,
   classNames: parseClassName,
   classNames: parseClassName,
   className: parseClassName,
   className: parseClassName,

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

@@ -12,7 +12,7 @@ import { DateRangeInput } from '../datelib/date-range'
 import { BusinessHoursInput } from '../structs/business-hours'
 import { BusinessHoursInput } from '../structs/business-hours'
 import { EventInput } from '../structs/event'
 import { EventInput } from '../structs/event'
 import EventApi from '../api/EventApi'
 import EventApi from '../api/EventApi'
-import { ConstraintInput } from '../interactions/constraint'
+import { Allow, ConstraintInput, Overlap } from '../validation'
 
 
 
 
 export interface ToolbarInput {
 export interface ToolbarInput {
@@ -143,8 +143,9 @@ export interface OptionsInputBase {
   selectHelper?: boolean
   selectHelper?: boolean
   unselectAuto?: boolean
   unselectAuto?: boolean
   unselectCancel?: string
   unselectCancel?: string
-  selectOverlap?: boolean | ((event: EventApi) => boolean)
   selectConstraint?: ConstraintInput
   selectConstraint?: ConstraintInput
+  selectOverlap?: Overlap
+  selectAllow?: Allow
   events?: EventSourceInput
   events?: EventSourceInput
   eventSources?: EventSourceInput[]
   eventSources?: EventSourceInput[]
   allDayDefault?: boolean
   allDayDefault?: boolean
@@ -164,9 +165,9 @@ export interface OptionsInputBase {
   dragRevertDuration?: number
   dragRevertDuration?: number
   dragOpacity?: number
   dragOpacity?: number
   dragScroll?: boolean
   dragScroll?: boolean
-  eventOverlap?: boolean | ((stillEvent: EventApi, movingEvent: EventApi) => boolean)
   eventConstraint?: ConstraintInput
   eventConstraint?: ConstraintInput
-  eventAllow?: ((dropInfo: DropInfo, draggedEvent: Event) => boolean)
+  eventOverlap?: Overlap
+  eventAllow?: Allow
   longPressDelay?: number
   longPressDelay?: number
   eventLongPressDelay?: number
   eventLongPressDelay?: number
   droppable?: boolean
   droppable?: boolean