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

beginnings of working validation

Adam Shaw 7 лет назад
Родитель
Сommit
ad7fd92a93

+ 24 - 1
src/component/DateComponent.ts

@@ -15,10 +15,11 @@ import { EventInteractionUiState } from '../interactions/event-interaction-state
 import { assignTo } from '../util/object'
 import browserContext from '../common/browser-context'
 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 { createEventInstance, parseEventDef } from '../structs/event'
 import EmitterMixin from '../common/EmitterMixin'
+import { isEventsValid, isSelectionValid } from '../validation'
 
 
 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
   }
 
+  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
 }
 
+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 {
   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())

+ 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 { EventRenderRange } from '../component/event-rendering'
 import EventApi from '../api/EventApi'
-import { isEventStoreValid } from './constraint'
 
 export default class EventDragging { // TODO: rename to EventSelectingAndDragging
 
@@ -148,7 +147,7 @@ export default class EventDragging { // TODO: rename to EventSelectingAndDraggin
       if (mutation) {
         mutatedRelatedEvents = applyMutationToEventStore(relatedEvents, mutation, receivingCalendar)
 
-        if (!isEventStoreValid(mutatedRelatedEvents, this.component.dateProfile)) {
+        if (!this.component.isEventsValid(mutatedRelatedEvents)) {
           isInvalid = true
           mutation = 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 EventApi from '../api/EventApi'
 import { EventRenderRange } from '../component/event-rendering'
-import { isEventStoreValid } from './constraint'
 import { createDuration } from '../datelib/duration'
 
 export default class EventDragging {
@@ -100,7 +99,7 @@ export default class EventDragging {
     if (mutation) {
       mutatedRelatedEvents = applyMutationToEventStore(relatedEvents, mutation, calendar)
 
-      if (!isEventStoreValid(mutatedRelatedEvents, this.component.dateProfile)) {
+      if (!this.component.isEventsValid(mutatedRelatedEvents)) {
         isInvalid = true
         mutation = 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 { refineProps } from '../util/misc'
 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.
 */
 
-export interface DateSpanInput {
-  start: DateInput
-  end: DateInput
+export interface OpenDateSpanInput {
+  start?: DateInput
+  end?: DateInput
   isAllDay?: boolean
   [otherProp: string]: any
 }
 
-export interface DateSpan {
-  range: DateRange
+export interface DateSpanInput extends OpenDateSpanInput {
+  start: DateInput
+  end: DateInput
+}
+
+export interface OpenDateSpan {
+  range: OpenDateRange
   isAllDay: boolean
   [otherProp: string]: any
 }
 
+export interface DateSpan extends OpenDateSpan {
+  range: DateRange
+}
+
 const STANDARD_PROPS = {
   start: null,
   end: null,
@@ -28,54 +37,63 @@ const STANDARD_PROPS = {
 }
 
 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 standardProps = refineProps(raw, STANDARD_PROPS, {}, leftovers)
   let startMeta = standardProps.start ? dateEnv.createMarkerMeta(standardProps.start) : null
-  let startMarker
   let endMeta = standardProps.end ? dateEnv.createMarkerMeta(standardProps.end) : null
-  let endMarker
   let isAllDay = standardProps.isAllDay
 
   if (startMeta) {
-    startMarker = startMeta.marker
 
     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
 }
 
 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) {
     if (!(propName in span1)) {
       return false
@@ -84,3 +102,17 @@ export function isDateSpansEqual(span0: DateSpan, span1: DateSpan): boolean {
 
   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 { DateRange } from '../datelib/date-range'
 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.
@@ -29,8 +30,9 @@ export interface ExtendedEventSourceInput {
   editable?: boolean
   startEditable?: boolean
   durationEditable?: boolean
-  overlap?: any
-  constraint?: any
+  constraint?: ConstraintInput
+  overlap?: boolean
+  allow?: Allow
   rendering?: string
   className?: ClassNameInput
   color?: string
@@ -73,8 +75,9 @@ export interface EventSource {
   eventDataTransform: EventInputTransformer
   startEditable: 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
   className: string[]
   backgroundColor: string
@@ -107,8 +110,9 @@ const SIMPLE_SOURCE_PROPS = {
   eventDataTransform: Function,
   startEditable: Boolean,
   durationEditable: Boolean,
-  overlap: null,
-  constraint: null,
+  constraint: normalizeConstraint,
+  overlap: Boolean,
+  allow: null,
   rendering: String,
   className: parseClassName,
   backgroundColor: String,

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

@@ -11,7 +11,7 @@ import {
 import { expandRecurringRanges } from './recurring-event'
 import Calendar from '../Calendar'
 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.
@@ -113,6 +113,23 @@ export function getRelatedEvents(eventStore: EventStore, instanceId: string): Ev
   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) {
   let refinedEvents
 

+ 7 - 6
src/structs/event.ts

@@ -6,6 +6,7 @@ import { assignTo } from '../util/object'
 import { DateRange } from '../datelib/date-range'
 import { startOfDay } from '../datelib/marker'
 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.
@@ -22,8 +23,8 @@ export interface EventNonDateInput {
   editable?: boolean
   startEditable?: boolean
   durationEditable?: boolean
-  constraint?: any
-  overlap?: any
+  constraint?: ConstraintInput
+  overlap?: boolean
   rendering?: EventRenderingChoice
   classNames?: ClassNameInput // accept both
   className?: ClassNameInput //
@@ -56,8 +57,8 @@ export interface EventDef {
   url: string
   startEditable: boolean | null
   durationEditable: boolean | null
-  constraint: any
-  overlap: any
+  constraint: Constraint | null
+  overlap: boolean | null // does not allow full Overlap data type
   rendering: EventRenderingChoice
   classNames: string[]
   backgroundColor: string
@@ -90,8 +91,8 @@ const NON_DATE_PROPS = {
   editable: Boolean,
   startEditable: Boolean,
   durationEditable: Boolean,
-  constraint: null,
-  overlap: null,
+  constraint: normalizeConstraint,
+  overlap: Boolean,
   rendering: String,
   classNames: 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 { EventInput } from '../structs/event'
 import EventApi from '../api/EventApi'
-import { ConstraintInput } from '../interactions/constraint'
+import { Allow, ConstraintInput, Overlap } from '../validation'
 
 
 export interface ToolbarInput {
@@ -143,8 +143,9 @@ export interface OptionsInputBase {
   selectHelper?: boolean
   unselectAuto?: boolean
   unselectCancel?: string
-  selectOverlap?: boolean | ((event: EventApi) => boolean)
   selectConstraint?: ConstraintInput
+  selectOverlap?: Overlap
+  selectAllow?: Allow
   events?: EventSourceInput
   eventSources?: EventSourceInput[]
   allDayDefault?: boolean
@@ -164,9 +165,9 @@ export interface OptionsInputBase {
   dragRevertDuration?: number
   dragOpacity?: number
   dragScroll?: boolean
-  eventOverlap?: boolean | ((stillEvent: EventApi, movingEvent: EventApi) => boolean)
   eventConstraint?: ConstraintInput
-  eventAllow?: ((dropInfo: DropInfo, draggedEvent: Event) => boolean)
+  eventOverlap?: Overlap
+  eventAllow?: Allow
   longPressDelay?: number
   eventLongPressDelay?: number
   droppable?: boolean