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

+ 2 - 2
src/Calendar.ts

@@ -359,7 +359,7 @@ export default class Calendar {
     }
 
     for (let rawSource of rawSources) {
-      let source = parseEventSource(rawSource)
+      let source = parseEventSource(rawSource, this)
       if (source) {
         sources.push(source)
       }
@@ -1205,7 +1205,7 @@ export default class Calendar {
 
 
   addEventSource(sourceInput: EventSourceInput): EventSourceApi {
-    let eventSource = parseEventSource(sourceInput)
+    let eventSource = parseEventSource(sourceInput, this)
 
     if (eventSource) { // TODO: error otherwise?
       this.dispatch({ type: 'ADD_EVENT_SOURCES', sources: [ eventSource ] })

+ 1 - 1
src/component/DateComponent.ts

@@ -924,7 +924,7 @@ export default abstract class DateComponent extends Component {
         editable: false,
         isAllDay: selection.isAllDay,
         hasEnd: true
-      }, '')
+      }, '', this.getCalendar())
       let eventRange = {
         def,
         ui: computeEventDefUi(def, {}, {}),

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

@@ -103,13 +103,6 @@ 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 - 1
src/interactions-external/ExternalElementDragging.ts

@@ -166,7 +166,7 @@ export default class ExternalElementDragging {
 // ----------------------------------------------------------------------------------------------------
 
 function computeEventForDateSpan(dateSpan: DateSpan, dragMeta: DragMeta, calendar: Calendar): EventTuple {
-  let def = parseEventDef(dragMeta.leftoverProps, '')
+  let def = parseEventDef(dragMeta.leftoverProps, '', calendar)
   def.isAllDay = dateSpan.isAllDay
   def.hasEnd = Boolean(dragMeta.duration)
 

+ 13 - 15
src/structs/date-span.ts

@@ -6,6 +6,8 @@ import { Duration } from '../datelib/duration'
 /*
 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.
+
+TODO: in future, put otherProps in own object.
 */
 
 export interface OpenDateSpanInput {
@@ -57,6 +59,7 @@ export function parseDateSpan(raw: DateSpanInput, dateEnv: DateEnv, defaultDurat
 
 /*
 TODO: somehow combine with parseRange?
+Will return null if the start/end props were present but parsed invalidly.
 */
 export function parseOpenDateSpan(raw: OpenDateSpanInput, dateEnv: DateEnv): OpenDateSpan | null {
   let leftovers = {} as DateSpan
@@ -65,24 +68,19 @@ export function parseOpenDateSpan(raw: OpenDateSpanInput, dateEnv: DateEnv): Ope
   let endMeta = standardProps.end ? dateEnv.createMarkerMeta(standardProps.end) : null
   let isAllDay = standardProps.isAllDay
 
-  if (startMeta) {
-
-    if (isAllDay == null) {
-      isAllDay = (startMeta && startMeta.isTimeUnspecified) &&
-        (!endMeta || endMeta.isTimeUnspecified)
-    }
-
-    // use this leftover object as the selection object
-    leftovers.range = {
-      start: startMeta ? startMeta.marker : null,
-      end: endMeta ? endMeta.marker : null
-    }
-    leftovers.isAllDay = isAllDay
+  if (isAllDay == null) {
+    isAllDay = (startMeta && startMeta.isTimeUnspecified) &&
+      (!endMeta || endMeta.isTimeUnspecified)
+  }
 
-    return leftovers
+  // use this leftover object as the selection object
+  leftovers.range = {
+    start: startMeta ? startMeta.marker : null,
+    end: endMeta ? endMeta.marker : null
   }
+  leftovers.isAllDay = isAllDay
 
-  return null
+  return leftovers
 }
 
 export function isDateSpansEqual(span0: DateSpan, span1: DateSpan): boolean {

+ 9 - 4
src/structs/event-source.ts

@@ -112,7 +112,7 @@ const SIMPLE_SOURCE_PROPS = {
   editable: Boolean,
   startEditable: Boolean,
   durationEditable: Boolean,
-  constraint: normalizeConstraint,
+  constraint: null,
   overlap: Boolean,
   allow: null,
   rendering: String,
@@ -143,7 +143,7 @@ export function doesSourceNeedRange(eventSource: EventSource) {
   return !defs[eventSource.sourceDefId].ignoreRange
 }
 
-export function parseEventSource(raw: EventSourceInput): EventSource | null {
+export function parseEventSource(raw: EventSourceInput, calendar: Calendar): EventSource | null {
   for (let i = defs.length - 1; i >= 0; i--) { // later-added plugins take precedence
     let def = defs[i]
     let meta = def.parseMeta(raw)
@@ -152,7 +152,8 @@ export function parseEventSource(raw: EventSourceInput): EventSource | null {
       return parseEventSourceProps(
         typeof raw === 'object' ? raw : {},
         meta,
-        i
+        i,
+        calendar
       )
     }
   }
@@ -163,7 +164,7 @@ export function parseEventSource(raw: EventSourceInput): EventSource | null {
 /*
 TODO: combine with pluckNonDateProps AND refineScopedUi
 */
-function parseEventSourceProps(raw: ExtendedEventSourceInput, meta: object, sourceDefId: number): EventSource {
+function parseEventSourceProps(raw: ExtendedEventSourceInput, meta: object, sourceDefId: number, calendar: Calendar): EventSource {
   let props = refineProps(raw, SIMPLE_SOURCE_PROPS) as (EventSource & { editable: boolean | null, color: string })
 
   props.isFetching = false
@@ -174,6 +175,10 @@ function parseEventSourceProps(raw: ExtendedEventSourceInput, meta: object, sour
   props.sourceDefId = sourceDefId
   props.meta = meta
 
+  if (props.constraint) {
+    props.constraint = normalizeConstraint(props.constraint, calendar)
+  }
+
   if (props.startEditable == null) {
     props.startEditable = props.editable
   }

+ 25 - 35
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, joinRanges } from '../datelib/date-range'
+import { DateRange } from '../datelib/date-range'
 
 /*
 A data structure that encapsulates EventDefs and EventInstances.
@@ -83,51 +83,41 @@ export function expandRecurring(eventStore: EventStore, framingRange: DateRange,
 
 // retrieves events that have the same groupId as the instance specified by `instanceId`
 export function getRelatedEvents(eventStore: EventStore, instanceId: string): EventStore {
-  let dest = createEmptyEventStore()
-  let eventInstance = eventStore.instances[instanceId]
-  let eventDef = eventStore.defs[eventInstance.defId]
-
-  if (eventDef && eventInstance) {
-    let matchGroupId = eventDef.groupId
+  let instance = eventStore.instances[instanceId]
 
-    for (let defId in eventStore.defs) {
-      let def = eventStore.defs[defId]
+  if (instance) {
+    let def = eventStore.defs[instance.defId]
 
-      if (def === eventDef || matchGroupId && matchGroupId === def.groupId) {
-        dest.defs[defId] = def
-      }
+    if (def.groupId) {
+      return getEventsByGroupId(eventStore, def.groupId) // will include the original def/instance
+    } else {
+      return eventTupleToStore({ def, instance })
     }
+  }
 
-    for (let instanceId in eventStore.instances) {
-      let instance = eventStore.instances[instanceId]
+  return createEmptyEventStore()
+}
 
-      if (
-        instance === eventInstance ||
-        matchGroupId && matchGroupId === eventStore.defs[instance.defId].groupId
-      ) {
-        dest.instances[instanceId] = instance
-      }
+export function getEventsByGroupId(eventStore: EventStore, groupId: string): EventStore {
+  let dest = createEmptyEventStore()
+
+  for (let defId in eventStore.defs) {
+    let def = eventStore.defs[defId]
+
+    if (def.groupId === groupId) {
+      dest.defs[defId] = def
     }
   }
 
-  return dest
-}
+  for (let instanceId in eventStore.instances) {
+    let instance = eventStore.instances[instanceId]
 
-/*
-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
+    if (eventStore.defs[instance.defId].groupId === groupId) {
+      dest.instances[instanceId] = instance
+    }
   }
 
-  return joinedRange
+  return dest
 }
 
 export function transformRawEvents(rawEvents, func) {

+ 10 - 5
src/structs/event.ts

@@ -91,7 +91,7 @@ const NON_DATE_PROPS = {
   editable: Boolean,
   startEditable: Boolean,
   durationEditable: Boolean,
-  constraint: normalizeConstraint,
+  constraint: null,
   overlap: Boolean,
   rendering: String,
   classNames: parseClassName,
@@ -117,7 +117,7 @@ export function parseEvent(raw: EventInput, sourceId: string, calendar: Calendar
   let leftovers0 = {} as any
   let dateProps = pluckDateProps(raw, leftovers0)
   let leftovers1 = {} as any
-  let def = parseEventDef(raw, sourceId, leftovers1)
+  let def = parseEventDef(raw, sourceId, calendar, leftovers1)
   let instance: EventInstance = null
 
   if (dateProps.start !== null) {
@@ -153,10 +153,11 @@ export function parseEvent(raw: EventInput, sourceId: string, calendar: Calendar
 
 /*
 Will NOT populate extendedProps with the leftover properties.
+Will NOT populate date-related props.
 The EventNonDateInput has been normalized (id => publicId, etc).
 */
-export function parseEventDef(raw: EventNonDateInput, sourceId: string, leftovers?: any): EventDef {
-  let def = pluckNonDateProps(raw, leftovers) as EventDef
+export function parseEventDef(raw: EventNonDateInput, sourceId: string, calendar: Calendar, leftovers?: any): EventDef {
+  let def = pluckNonDateProps(raw, calendar, leftovers) as EventDef
 
   def.defId = String(uid++)
   def.sourceId = sourceId
@@ -182,12 +183,16 @@ function pluckDateProps(raw: EventInput, leftovers: any) {
 }
 
 
-function pluckNonDateProps(raw: EventInput, leftovers: any) {
+function pluckNonDateProps(raw: EventInput, calendar: Calendar, leftovers: any) {
   let props = refineProps(raw, NON_DATE_PROPS, {}, leftovers)
 
   props.publicId = props.id
   props.classNames = props.classNames.concat(props.className)
 
+  if (props.constraint) {
+    props.constraint = normalizeConstraint(props.constraint, calendar)
+  }
+
   if (props.startEditable == null) {
     props.startEditable = props.editable
   }

+ 40 - 21
src/validation.ts

@@ -1,16 +1,14 @@
-import { EventStore, getRelatedEvents, expandRecurring, getStoreRange } from './structs/event-store'
+import { EventStore, expandRecurring, eventTupleToStore, getEventsByGroupId } from './structs/event-store'
 import Calendar from './Calendar'
 import { DateSpan, parseOpenDateSpan, OpenDateSpanInput, OpenDateSpan, isSpanPropsEqual, isSpanPropsMatching } from './structs/date-span'
-import { EventInstance, EventDef, EventTuple } from './structs/event'
+import { EventInstance, EventDef, EventTuple, parseEvent } from './structs/event'
 import { EventSource, EventSourceHash } from './structs/event-source'
 import { rangeContainsRange, rangesIntersect } from './datelib/date-range'
-import { DateEnv } from './datelib/env'
-import { CalendarState } from './reducers/types'
 import EventApi from './api/EventApi'
 
 // TODO: rename to "criteria" ?
-export type ConstraintInput = 'businessHours' | string | OpenDateSpanInput
-export type Constraint = 'businessHours' | string | OpenDateSpan
+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: DateSpan, movingEvent: EventApi | null) => boolean
 
@@ -25,7 +23,7 @@ interface ValidationEntity {
 export function isEventsValid(eventStore: EventStore, calendar: Calendar): boolean {
   return isEntitiesValid(
     eventStoreToEntities(eventStore, calendar.state.eventSources),
-    normalizeConstraint(calendar.opt('eventConstraint'), calendar.dateEnv),
+    normalizeConstraint(calendar.opt('eventConstraint'), calendar),
     calendar.opt('eventOverlap'),
     calendar.opt('eventAllow'),
     calendar
@@ -35,7 +33,7 @@ export function isEventsValid(eventStore: EventStore, calendar: Calendar): boole
 export function isSelectionValid(selection: DateSpan, calendar: Calendar): boolean {
   return isEntitiesValid(
     [ { dateSpan: selection, event: null, constraint: null, overlap: null, allow: null } ],
-    normalizeConstraint(calendar.opt('selectConstraint'), calendar.dateEnv),
+    normalizeConstraint(calendar.opt('selectConstraint'), calendar),
     calendar.opt('selectOverlap'),
     calendar.opt('selectAllow'),
     calendar
@@ -65,7 +63,7 @@ function isEntitiesValid(
   for (let subjectEntity of entities) {
     for (let eventEntity of eventEntities) {
       if (
-        (
+        ( // not comparing the same event
           !subjectEntity.event ||
           !eventEntity.event ||
           subjectEntity.event.def.defId !== eventEntity.event.def.defId
@@ -144,31 +142,45 @@ function mapEventInstances(
   return res
 }
 
-function isDateSpanWithinConstraint(subjectSpan: DateSpan, constraint: any, calendar: Calendar): boolean {
+function isDateSpanWithinConstraint(subjectSpan: DateSpan, constraint: Constraint | null, calendar: Calendar): boolean {
+
+  if (constraint === null) {
+    return true // doesn't care
+  }
+
   let constrainingSpans: DateSpan[] = constraintToSpans(constraint, subjectSpan, calendar)
 
   for (let constrainingSpan of constrainingSpans) {
-    if (!dateSpanContainsOther(constrainingSpan, subjectSpan)) {
-      return false
+    if (dateSpanContainsOther(constrainingSpan, subjectSpan)) {
+      return true
     }
   }
 
-  return true
+  return false // not contained by any one of the constrainingSpans
 }
 
 function constraintToSpans(constraint: Constraint, subjectSpan: DateSpan, calendar: Calendar): DateSpan[] {
 
   if (constraint === 'businessHours') {
-    let store = getPeerBusinessHours(subjectSpan, calendar.state)
-    store = expandRecurring(store, getStoreRange(store), calendar)
+    let store = getPeerBusinessHours(subjectSpan, calendar)
+    store = expandRecurring(store, subjectSpan.range, calendar)
     return eventStoreToDateSpans(store)
 
   } else if (typeof constraint === 'string') { // an ID
-    let store = getRelatedEvents(calendar.state.eventStore, constraint)
+    let store = getEventsByGroupId(calendar.state.eventStore, constraint)
     return eventStoreToDateSpans(store)
 
   } else if (typeof constraint === 'object' && constraint) { // non-null object
-    return [ constraint ] // already parsed
+
+    if ((constraint as EventTuple).def) { // an event definition (actually, a tuple)
+      let store = eventTupleToStore(constraint as EventTuple)
+      store = expandRecurring(store, subjectSpan.range, calendar)
+      return eventStoreToDateSpans(store)
+
+    } else {
+      return [ constraint as OpenDateSpan ] // already parsed datespan
+    }
+
   }
 
   return []
@@ -226,13 +238,20 @@ function eventToDateSpan(def: EventDef, instance: EventInstance): DateSpan {
 }
 
 // TODO: plugin
-function getPeerBusinessHours(subjectSpan: DateSpan, state: CalendarState): EventStore {
-  return state.eventStore
+function getPeerBusinessHours(subjectSpan: DateSpan, calendar: Calendar): EventStore {
+  return calendar.view.businessHours // accessing view :(
 }
 
-export function normalizeConstraint(input: ConstraintInput, dateEnv: DateEnv): Constraint {
+export function normalizeConstraint(input: ConstraintInput, calendar: Calendar): Constraint | null {
   if (typeof input === 'object' && input) { // non-null object
-    return parseOpenDateSpan(input, dateEnv)
+    let span = parseOpenDateSpan(input, calendar.dateEnv)
+
+    if (span === null || span.range.start || span.range.end) {
+      return span
+    } else { // if completely-open range, assume it's a recurring event (prolly with startTime/endTime)
+      return parseEvent(input, '', calendar)
+    }
+
   } else if (input != null) {
     return String(input)
   } else {