Bladeren bron

change how EventUi is done

Adam Shaw 7 jaren geleden
bovenliggende
commit
4296be3ada
7 gewijzigde bestanden met toevoegingen van 142 en 93 verwijderingen
  1. 2 2
      src/Calendar.ts
  2. 33 15
      src/api/EventApi.ts
  3. 50 24
      src/component/event-ui.ts
  4. 2 2
      src/structs/event-source.ts
  5. 5 5
      src/structs/event.ts
  6. 0 1
      src/types/input-types.ts
  7. 50 44
      src/validation.ts

+ 2 - 2
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 { computeEventDefUis, processScopedUiProps, EventUi, EventUiHash } from './component/event-ui'
+import { computeEventDefUis, processScopedUiProps, EventUiPart, EventUiHash } from './component/event-ui'
 import PointerDragging, { PointerDragEvent } from './dnd/PointerDragging'
 import EventDragging from './interactions/EventDragging'
 import { buildViewSpecs, ViewSpecHash, ViewSpec } from './structs/view-spec'
@@ -67,7 +67,7 @@ export default class Calendar {
   buildBaseEventUi = reselector(processScopedUiProps)
   computeEventDefUis = reselector(computeEventDefUis)
 
-  baseEventUi: EventUi
+  baseEventUi: EventUiPart
   renderableEventUis: EventUiHash
 
   optionsManager: OptionsManager

+ 33 - 15
src/api/EventApi.ts

@@ -1,12 +1,12 @@
 import Calendar from '../Calendar'
-import { EventDef, EventInstance, EventTuple, pluckNonDateProps } from '../structs/event'
+import { EventDef, EventInstance, EventTuple, NON_DATE_PROPS, DATE_PROPS } from '../structs/event'
+import { UNSCOPED_EVENT_UI_PROPS } from '../component/event-ui'
 import { EventMutation } from '../structs/event-mutation'
 import { DateInput } from '../datelib/env'
 import { diffDates, computeAlignedDayRange } from '../util/misc'
 import { subtractDurations, DurationInput, createDuration } from '../datelib/duration'
 import { createFormatter, FormatterInput } from '../datelib/formatting'
 import EventSourceApi from './EventSourceApi'
-import { parseClassName } from '../util/html'
 
 export default class EventApi implements EventTuple {
 
@@ -20,26 +20,44 @@ export default class EventApi implements EventTuple {
     this.instance = instance || null
   }
 
+  /*
+  TODO: make event struct more responsible for this
+  */
   setProp(name: string, val: string) {
-    if (name.match(/^(start|end|date|allDay)$/)) {
+    if (name in DATE_PROPS) {
       // error. date-related props need other methods
-    } else {
-      let props
-
-      // TODO: consolidate this logic with event struct?
-      if (name === 'editable') {
-        props = { startEditable: val, durationEditable: val }
-      } else if (name === 'color') {
-        props = { backgroundColor: val, borderColor: val }
-      } else if (name === 'classNames') {
-        props = { classNames: parseClassName(val) }
+
+    } else if (name in NON_DATE_PROPS) {
+
+      if (typeof NON_DATE_PROPS[name] === 'function') {
+        val = NON_DATE_PROPS[name](val)
+      }
+
+      this.mutate({
+        standardProps: { [name]: val }
+      })
+
+    } else if (name in UNSCOPED_EVENT_UI_PROPS) {
+      let ui
+
+      if (typeof UNSCOPED_EVENT_UI_PROPS[name] === 'function') {
+        val = UNSCOPED_EVENT_UI_PROPS[name](val)
+      }
+
+      if (name === 'color') {
+        ui = { backgroundColor: val, borderColor: val }
+      } else if (name === 'editable') {
+        ui = { startEditable: val, durationEditable: val }
       } else {
-        props = { [name]: val }
+        ui = { [name]: val }
       }
 
       this.mutate({
-        standardProps: props
+        standardProps: { ui }
       })
+
+    } else {
+      // error
     }
   }
 

+ 50 - 24
src/component/event-ui.ts

@@ -1,4 +1,4 @@
-import { Constraint, Allow, normalizeConstraint, ConstraintInput } from '../validation'
+import { Constraint, Allow, normalizeConstraint, ConstraintInput, Overlap } from '../validation'
 import { parseClassName } from '../util/html'
 import { refineProps } from '../util/misc'
 import Calendar from '../Calendar'
@@ -11,7 +11,7 @@ export interface UnscopedEventUiInput {
   startEditable?: boolean
   durationEditable?: boolean
   constraint?: ConstraintInput
-  overlap?: boolean // does not allow full Overlap data type
+  overlap?: Overlap
   allow?: Allow
   rendering?: string
   className?: string[] | string
@@ -27,7 +27,7 @@ export interface ScopedEventUiInput {
   eventStartEditable?: boolean
   eventDurationEditable?: boolean
   eventConstraint?: ConstraintInput
-  eventOverlap?: boolean // does not allow full Overlap data type
+  eventOverlap?: Overlap
   eventAllow?: Allow
   eventRendering?: string
   eventClassName?: string[] | string
@@ -38,11 +38,11 @@ export interface ScopedEventUiInput {
   eventColor?: string
 }
 
-export interface EventUi {
+export interface EventUiPart {
   startEditable: boolean | null
   durationEditable: boolean | null
   constraint: Constraint | null
-  overlap: boolean | null
+  overlap: Overlap | null
   allow: Allow | null
   rendering: string
   backgroundColor: string
@@ -51,14 +51,27 @@ export interface EventUi {
   classNames: string[]
 }
 
+export interface EventUi {
+  startEditable: boolean
+  durationEditable: boolean
+  constraints: Constraint[]
+  overlaps: Overlap[]
+  allows: Allow[]
+  rendering: string
+  backgroundColor: string
+  borderColor: string
+  textColor: string,
+  classNames: string[]
+}
+
 export type EventUiHash = { [defId: string]: EventUi }
 
-const UNSCOPED_EVENT_UI_PROPS = {
+export const UNSCOPED_EVENT_UI_PROPS = {
   editable: Boolean,
   startEditable: Boolean,
   durationEditable: Boolean,
   constraint: null,
-  overlap: Boolean,
+  overlap: null,
   allow: null,
   rendering: String,
   className: parseClassName,
@@ -69,12 +82,12 @@ const UNSCOPED_EVENT_UI_PROPS = {
   textColor: String
 }
 
-const SCOPED_EVENT_UI_PROPS = {
+const SCOPED_EVENT_UI_PROPS = { // TODO: not very DRY. instead, map to UNSCOPED_EVENT_UI_PROPS
   editable: Boolean, // only one not scoped
   eventStartEditable: Boolean,
   eventDurationEditable: Boolean,
   eventConstraint: null,
-  eventOverlap: Boolean,
+  eventOverlap: null,
   eventAllow: null,
   eventRendering: String,
   eventClassName: parseClassName,
@@ -103,7 +116,7 @@ export function computeEventDefUi(eventDef: EventDef, eventSources: EventSourceH
   return combineEventUis(uis)
 }
 
-export function processUnscopedUiProps(rawProps: UnscopedEventUiInput, calendar: Calendar, leftovers?): EventUi {
+export function processUnscopedUiProps(rawProps: UnscopedEventUiInput, calendar: Calendar, leftovers?): EventUiPart {
   let props = refineProps(rawProps, UNSCOPED_EVENT_UI_PROPS, {}, leftovers)
 
   return {
@@ -120,7 +133,7 @@ export function processUnscopedUiProps(rawProps: UnscopedEventUiInput, calendar:
   }
 }
 
-export function processScopedUiProps(rawProps: ScopedEventUiInput, calendar: Calendar, leftovers?): EventUi {
+export function processScopedUiProps(rawProps: ScopedEventUiInput, calendar: Calendar, leftovers?): EventUiPart {
   let props = refineProps(rawProps, SCOPED_EVENT_UI_PROPS, {}, leftovers)
 
   return {
@@ -137,21 +150,34 @@ export function processScopedUiProps(rawProps: ScopedEventUiInput, calendar: Cal
   }
 }
 
-export function combineEventUis(uis: EventUi[]): EventUi {
-  return uis.reduce(combineTwoEventUis)
+export function combineEventUis(uis: EventUiPart[]): EventUi {
+  return uis.reduce(mergeEventUiPart, INITIAL_EVENT_UI)
+}
+
+const INITIAL_EVENT_UI: EventUi = {
+  startEditable: false,
+  durationEditable: false,
+  constraints: [],
+  overlaps: [],
+  allows: [],
+  rendering: '',
+  backgroundColor: '',
+  borderColor: '',
+  textColor: '',
+  classNames: []
 }
 
-function combineTwoEventUis(hash0: EventUi, hash1: EventUi): EventUi { // hash1 has higher precedence
+function mergeEventUiPart(accum: EventUi, part: EventUiPart): EventUi { // hash1 has higher precedence
   return {
-    startEditable: hash1.startEditable != null ? hash1.startEditable : hash0.startEditable,
-    durationEditable: hash1.durationEditable != null ? hash1.durationEditable : hash0.durationEditable,
-    constraint: hash1.constraint || hash0.constraint,
-    overlap: hash1.overlap != null ? hash1.overlap : hash0.overlap,
-    allow: hash1.allow || hash0.allow,
-    rendering: hash1.rendering || hash0.rendering,
-    backgroundColor: hash1.backgroundColor || hash0.backgroundColor,
-    borderColor: hash1.borderColor || hash0.borderColor,
-    textColor: hash1.textColor || hash0.textColor,
-    classNames: hash0.classNames.concat(hash1.classNames)
+    startEditable: part.startEditable != null ? part.startEditable : accum.startEditable,
+    durationEditable: part.durationEditable != null ? part.durationEditable : accum.durationEditable,
+    constraints: part.constraint != null ? accum.constraints.concat([ part.constraint ]) : accum.constraints,
+    overlaps: part.overlap != null ? accum.overlaps.concat([ part.overlap ]) : accum.overlaps,
+    allows: part.allow != null ? accum.allows.concat([ part.allow ]) : accum.allows,
+    rendering: part.rendering || accum.rendering,
+    backgroundColor: part.backgroundColor || accum.backgroundColor,
+    borderColor: part.borderColor || accum.borderColor,
+    textColor: part.textColor || accum.textColor,
+    classNames: part.classNames.concat(accum.classNames)
   }
 }

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

@@ -4,7 +4,7 @@ import Calendar from '../Calendar'
 import { DateRange } from '../datelib/date-range'
 import { EventSourceFunc } from '../event-sources/func-event-source'
 import { ScopedEventUiInput, processUnscopedUiProps } from '../component/event-ui'
-import { EventUi } from '../component/event-ui'
+import { EventUiPart } from '../component/event-ui'
 
 /*
 Parsing and normalization of the EventSource data type, which defines how event data is fetched.
@@ -61,7 +61,7 @@ export interface EventSource {
   fetchRange: DateRange | null
   allDayDefault: boolean | null
   eventDataTransform: EventInputTransformer
-  ui: EventUi
+  ui: EventUiPart
   success: EventSourceSuccessResponseHandler | null
   failure: EventSourceErrorResponseHandler | null
 }

+ 5 - 5
src/structs/event.ts

@@ -6,7 +6,7 @@ import { DateRange } from '../datelib/date-range'
 import { startOfDay } from '../datelib/marker'
 import { parseRecurring } from './recurring-event'
 import { Duration } from '../datelib/duration'
-import { UnscopedEventUiInput, EventUi, processUnscopedUiProps } from '../component/event-ui'
+import { UnscopedEventUiInput, EventUiPart, processUnscopedUiProps } from '../component/event-ui'
 
 /*
 Utils for parsing event-input data. Each util parses a subset of the event-input's data.
@@ -43,7 +43,7 @@ export interface EventDef {
   recurringDef: { typeId: number, typeData: any, duration: Duration | null } | null
   title: string
   url: string
-  ui: EventUi
+  ui: EventUiPart
   extendedProps: any
 }
 
@@ -63,7 +63,7 @@ export interface EventTuple {
 export type EventInstanceHash = { [instanceId: string]: EventInstance }
 export type EventDefHash = { [defId: string]: EventDef }
 
-const NON_DATE_PROPS = {
+export const NON_DATE_PROPS = {
   id: String,
   groupId: String,
   title: String,
@@ -71,7 +71,7 @@ const NON_DATE_PROPS = {
   extendedProps: null
 }
 
-const DATE_PROPS = {
+export const DATE_PROPS = {
   start: null,
   date: null, // alias for start
   end: null,
@@ -247,7 +247,7 @@ function pluckDateProps(raw: EventInput, leftovers: any) {
 }
 
 
-export function pluckNonDateProps(raw: EventInput, calendar: Calendar, leftovers?) {
+function pluckNonDateProps(raw: EventInput, calendar: Calendar, leftovers?) {
   let preLeftovers = {}
   let props = refineProps(raw, NON_DATE_PROPS, {}, preLeftovers)
   let ui = processUnscopedUiProps(preLeftovers, calendar, leftovers)

+ 0 - 1
src/types/input-types.ts

@@ -154,7 +154,6 @@ export interface OptionsInputBase extends UnscopedEventUiInput {
   rerenderDelay?: number | null
   dragRevertDuration?: number
   dragScroll?: boolean
-  eventOverlap?: Overlap // more than what EventUi offers
   longPressDelay?: number
   eventLongPressDelay?: number
   droppable?: boolean

+ 50 - 44
src/validation.ts

@@ -15,83 +15,89 @@ export type Allow = (span: DateSpanApi, movingEvent: EventApi | null) => boolean
 interface ValidationEntity {
   dateSpan: DateSpan
   event: EventTuple | null
-  constraint: Constraint | null // in addition to calendar's
-  overlap: boolean | null // in addition to calendar's. granular entities can't provide functions
-  allow: Allow | null // in addition to calendar's
+  constraints: Constraint[]
+  overlaps: Overlap[]
+  allows: Allow[]
 }
 
 export function isEventsValid(eventStore: EventStore, calendar: Calendar): boolean {
   return isEntitiesValid(
     eventStoreToEntities(eventStore, calendar.renderableEventUis),
-    normalizeConstraint(calendar.opt('eventConstraint'), calendar),
-    calendar.opt('eventOverlap'),
-    calendar.opt('eventAllow'),
     calendar
   )
 }
 
 export function isSelectionValid(selection: DateSpan, calendar: Calendar): boolean {
+
+  // TODO: separate util for this. in scoped part!?
+  let constraint = normalizeConstraint(calendar.opt('selectConstraint'), calendar)
+  let overlap = calendar.opt('selectOverlap')
+  let allow = calendar.opt('selectAllow')
+
   return isEntitiesValid(
-    [ { dateSpan: selection, event: null, constraint: null, overlap: null, allow: null } ],
-    normalizeConstraint(calendar.opt('selectConstraint'), calendar),
-    calendar.opt('selectOverlap'),
-    calendar.opt('selectAllow'),
+    [ {
+      dateSpan: selection,
+      event: null,
+      constraints: constraint != null ? [ constraint ] : [],
+      overlaps: overlap != null ? [ overlap ] : [],
+      allows: allow != null ? [ allow ] : []
+    } ],
     calendar
   )
 }
 
-function isEntitiesValid(
-  entities: ValidationEntity[],
-  globalConstraint: Constraint | null,
-  globalOverlap: Overlap | null,
-  globalAllow: Allow | null,
-  calendar: Calendar
-): boolean {
+function isEntitiesValid(entities: ValidationEntity[], calendar: Calendar): boolean {
 
   for (let entity of entities) {
-    if (
-      !isDateSpanWithinConstraint(entity.dateSpan, entity.constraint, calendar) ||
-      !isDateSpanWithinConstraint(entity.dateSpan, globalConstraint, calendar)
-    ) {
-      return false
+    for (let constraint of entity.constraints) {
+      if (!isDateSpanWithinConstraint(entity.dateSpan, constraint, calendar)) {
+        return false
+      }
     }
   }
 
+  // is this efficient?
   let eventEntities = eventStoreToEntities(calendar.state.eventStore, calendar.renderableEventUis)
 
   for (let subjectEntity of entities) {
     for (let eventEntity of eventEntities) {
-      if (
-        ( // not comparing the same/related event
-          !subjectEntity.event ||
-          !eventEntity.event ||
-          isEventsCollidable(subjectEntity.event, eventEntity.event)
-        ) &&
-        dateSpansCollide(subjectEntity.dateSpan, eventEntity.dateSpan) // a collision!
-      ) {
-        if (
-          subjectEntity.overlap === false ||
-          (eventEntity.overlap === false && subjectEntity.event) || // the eventEntity doesn't like two events colliding
-          !isOverlapValid(eventEntity.event, subjectEntity.event, globalOverlap, calendar)
-        ) {
-          return false
+      if (considerEntitiesForOverlap(subjectEntity, eventEntity)) {
+
+        for (let overlap of eventEntity.overlaps) {
+          if (!isOverlapValid(eventEntity.event, subjectEntity.event, overlap, calendar)) {
+            return false
+          }
+        }
+
+        for (let overlap of subjectEntity.overlaps) {
+          if (!isOverlapValid(eventEntity.event, subjectEntity.event, overlap, calendar)) {
+            return false
+          }
         }
       }
     }
   }
 
   for (let entity of entities) {
-    if (
-      !isDateSpanAllowed(entity.dateSpan, entity.event, entity.allow, calendar) ||
-      !isDateSpanAllowed(entity.dateSpan, entity.event, globalAllow, calendar)
-    ) {
-      return false
+    for (let allow of entity.allows) {
+      if (!isDateSpanAllowed(entity.dateSpan, entity.event, allow, calendar)) {
+        return false
+      }
     }
   }
 
   return true
 }
 
+function considerEntitiesForOverlap(entity0: ValidationEntity, entity1: ValidationEntity) {
+  return ( // not comparing the same/related event
+    !entity0.event ||
+    !entity1.event ||
+    isEventsCollidable(entity0.event, entity1.event)
+  ) &&
+  dateSpansCollide(entity0.dateSpan, entity1.dateSpan) // a collision!
+}
+
 // do we want to compare these events for collision?
 // say no if events are the same, or if they share a groupId
 function isEventsCollidable(event0: EventTuple, event1: EventTuple): boolean {
@@ -109,9 +115,9 @@ function eventStoreToEntities(eventStore: EventStore, eventUis: EventUiHash): Va
     return {
       dateSpan: eventToDateSpan(eventDef, eventInstance),
       event: { def: eventDef, instance: eventInstance },
-      constraint: eventUi.constraint,
-      overlap: eventUi.overlap,
-      allow: eventUi.allow
+      constraints: eventUi.constraints,
+      overlaps: eventUi.overlaps,
+      allows: eventUi.allows
     }
   })
 }