浏览代码

beginnings of new event-ui system

Adam Shaw 7 年之前
父节点
当前提交
07b94dae4d

+ 16 - 15
src/Calendar.ts

@@ -18,13 +18,13 @@ import reselector from './util/reselector'
 import { mapHash, assignTo } from './util/object'
 import { DateRangeInput } from './datelib/date-range'
 import DateProfileGenerator from './DateProfileGenerator'
-import { EventSourceInput, parseEventSource, EventSourceHash } from './structs/event-source'
-import { EventInput, EventDefHash, parseEvent } from './structs/event'
+import { EventSourceInput, parseEventSource } from './structs/event-source'
+import { EventInput, parseEvent } from './structs/event'
 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, EventUiHash } from './component/event-rendering'
+import { computeEventDefUis, processScopedUiProps, EventUi, EventUiHash } from './component/event-ui'
 import PointerDragging, { PointerDragEvent } from './dnd/PointerDragging'
 import EventDragging from './interactions/EventDragging'
 import { buildViewSpecs, ViewSpecHash, ViewSpec } from './structs/view-spec'
@@ -62,9 +62,13 @@ export default class Calendar {
   triggerWith: EmitterInterface['triggerWith']
   hasHandlers: EmitterInterface['hasHandlers']
 
-  buildDateEnv: any
-  buildTheme: any
-  computeEventDefUis: (eventDefs: EventDefHash, eventSources: EventSourceHash, options: any) => EventUiHash
+  buildDateEnv = reselector(buildDateEnv)
+  buildTheme = reselector(buildTheme)
+  buildBaseEventUi = reselector(processScopedUiProps)
+  computeEventDefUis = reselector(computeEventDefUis)
+
+  baseEventUi: EventUi
+  renderableEventUis: EventUiHash
 
   optionsManager: OptionsManager
   viewSpecs: ViewSpecHash
@@ -92,7 +96,7 @@ export default class Calendar {
   isRendering: boolean = false // currently in the executeRender function?
   renderingPauseDepth: number = 0
   renderableEventStore: EventStore
-  buildDelayedRerender: any
+  buildDelayedRerender = reselector(buildDelayedRerender)
   delayedRerender: any
   afterSizingTriggers: any = {}
   isViewNew: boolean = false
@@ -108,11 +112,6 @@ export default class Calendar {
     this.optionsManager = new OptionsManager(overrides)
     this.pluginSystem = new PluginSystem()
 
-    this.buildDateEnv = reselector(buildDateEnv)
-    this.buildTheme = reselector(buildTheme)
-    this.buildDelayedRerender = reselector(buildDelayedRerender)
-    this.computeEventDefUis = reselector(computeEventDefUis)
-
     // only do once. don't do in handleOptions. because can't remove plugins
     let pluginDefs = Calendar.defaultPlugins.concat(
       this.optionsManager.computed.plugins || []
@@ -389,6 +388,8 @@ export default class Calendar {
       throw new Error(`View type "${viewType}" is not valid`)
     }
 
+    this.baseEventUi = this.buildBaseEventUi(viewSpec.options, this)
+
     // if event sources are still loading and progressive rendering hasn't been enabled,
     // keep rendering the last fully loaded set of events
     let renderableEventStore = this.renderableEventStore =
@@ -396,10 +397,10 @@ export default class Calendar {
         this.renderableEventStore :
         state.eventStore
 
-    let eventUis = this.computeEventDefUis(
+    this.renderableEventUis = this.computeEventDefUis( // will access baseEventUi
       renderableEventStore.defs,
       state.eventSources,
-      viewSpec.options
+      this
     )
 
     if (needsFull || !component) {
@@ -423,7 +424,7 @@ export default class Calendar {
         dateProfile: state.dateProfile,
         dateProfileGenerator: this.dateProfileGenerators[viewType],
         eventStore: renderableEventStore,
-        eventUis,
+        eventUis: this.renderableEventUis,
         dateSelection: state.dateSelection,
         eventSelection: state.eventSelection,
         eventDrag: state.eventDrag,

+ 1 - 1
src/CalendarComponent.ts

@@ -7,7 +7,7 @@ import { prependToElement, createElement, removeElement, appendToElement, applyS
 import { rangeContainsMarker, DateRange } from './datelib/date-range';
 import { assignTo } from './util/object';
 import { EventStore } from './structs/event-store'
-import { EventUiHash } from './component/event-rendering'
+import { EventUiHash } from './component/event-ui'
 import { DateSpan } from './structs/date-span'
 import { EventInteractionUiState } from './interactions/event-interaction-state'
 import { BusinessHoursInput, parseBusinessHours } from './structs/business-hours'

+ 2 - 1
src/View.ts

@@ -9,7 +9,8 @@ import { createElement } from './util/dom-manip'
 import { ComponentContext } from './component/Component'
 import DateComponent from './component/DateComponent'
 import { EventStore } from './structs/event-store'
-import { EventUiHash, sliceEventStore, EventRenderRange } from './component/event-rendering'
+import { EventUiHash } from './component/event-ui'
+import { sliceEventStore, EventRenderRange } from './component/event-rendering'
 import { DateSpan } from './structs/date-span'
 import { EventInteractionUiState } from './interactions/event-interaction-state'
 import { memoizeRendering } from './component/memoized-rendering'

+ 2 - 1
src/agenda/AbstractAgendaView.ts

@@ -15,12 +15,13 @@ import { createFormatter } from '../datelib/formatting'
 import { EventStore, filterEventStoreDefs } from '../structs/event-store'
 import { EventInteractionUiState } from '../interactions/event-interaction-state'
 import reselector from '../util/reselector'
-import { EventUiHash, hasBgRendering } from '../component/event-rendering'
+import { hasBgRendering } from '../component/event-rendering'
 import { buildGotoAnchorHtml, getAllDayHtml } from '../component/date-rendering'
 import { diffDays } from '../datelib/marker'
 import { ComponentContext } from '../component/Component'
 import { ViewSpec } from '../structs/view-spec'
 import DateProfileGenerator from '../DateProfileGenerator'
+import { EventUiHash } from '../component/event-ui'
 
 const AGENDA_ALL_DAY_EVENT_LIMIT = 5
 const WEEK_HEADER_FORMAT = createFormatter({ week: 'short' })

+ 1 - 1
src/agenda/SimpleTimeGrid.ts

@@ -2,7 +2,7 @@ import TimeGrid, { TimeGridSeg } from './TimeGrid'
 import DateComponent from '../component/DateComponent'
 import { DateProfile } from '../DateProfileGenerator'
 import { EventStore } from '../structs/event-store'
-import { EventUiHash } from '../component/event-rendering'
+import { EventUiHash } from '../component/event-ui'
 import { EventInteractionUiState } from '../interactions/event-interaction-state'
 import { DateSpan } from '../structs/date-span'
 import reselector from '../util/reselector'

+ 10 - 10
src/api/EventApi.ts

@@ -1,5 +1,5 @@
 import Calendar from '../Calendar'
-import { EventDef, EventInstance, EventTuple } from '../structs/event'
+import { EventDef, EventInstance, EventTuple, pluckNonDateProps } from '../structs/event'
 import { EventMutation } from '../structs/event-mutation'
 import { DateInput } from '../datelib/env'
 import { diffDates, computeAlignedDayRange } from '../util/misc'
@@ -237,17 +237,17 @@ export default class EventApi implements EventTuple {
   get allDay(): boolean { return this.def.allDay }
   get title(): string { return this.def.title }
   get url(): string { return this.def.url }
-  get startEditable(): boolean { return this.def.startEditable }
-  get durationEditable(): boolean { return this.def.durationEditable }
-  get constraint(): any { return this.def.constraint }
-  get overlap(): any { return this.def.overlap }
-  get rendering(): string { return this.def.rendering }
-  get backgroundColor(): string { return this.def.backgroundColor }
-  get borderColor(): string { return this.def.borderColor }
-  get textColor(): string { return this.def.textColor }
+  get startEditable(): boolean { return this.def.ui.startEditable }
+  get durationEditable(): boolean { return this.def.ui.durationEditable }
+  get constraint(): any { return this.def.ui.constraint }
+  get overlap(): any { return this.def.ui.overlap }
+  get rendering(): string { return this.def.ui.rendering }
+  get backgroundColor(): string { return this.def.ui.backgroundColor }
+  get borderColor(): string { return this.def.ui.borderColor }
+  get textColor(): string { return this.def.ui.textColor }
 
   // NOTE: user can't modify these because Object.freeze was called in event-def parsing
-  get classNames(): string[] { return this.def.classNames }
+  get classNames(): string[] { return this.def.ui.classNames }
   get extendedProps(): any { return this.def.extendedProps }
 
 }

+ 1 - 1
src/basic/SimpleDayGrid.ts

@@ -1,7 +1,7 @@
 import DayGrid, { DayGridSeg } from './DayGrid'
 import { DateProfile } from '../DateProfileGenerator'
 import { EventStore } from '../structs/event-store'
-import { EventUiHash } from '../component/event-rendering'
+import { EventUiHash } from '../component/event-ui'
 import { DateSpan } from '../structs/date-span'
 import { EventInteractionUiState } from '../interactions/event-interaction-state'
 import DayTable from '../common/DayTable'

+ 2 - 1
src/common/slicing-utils.ts

@@ -1,6 +1,7 @@
 import { DateRange } from '../datelib/date-range'
 import { EventStore } from '../structs/event-store'
-import { EventUiHash, sliceEventStore, EventRenderRange } from '../component/event-rendering'
+import { EventUiHash } from '../component/event-ui'
+import { sliceEventStore, EventRenderRange } from '../component/event-rendering'
 import { DateProfile } from '../DateProfileGenerator'
 import { Seg, EventSegUiInteractionState } from '../component/DateComponent'
 import { DateSpan, fabricateEventRange } from '../structs/date-span'

+ 2 - 80
src/component/event-rendering.ts

@@ -1,26 +1,12 @@
-import { EventDef, EventDefHash, EventTuple } from '../structs/event'
+import { EventDef, EventTuple } from '../structs/event'
 import { EventStore } from '../structs/event-store'
 import { DateRange, invertRanges, intersectRanges } from '../datelib/date-range'
-import { EventSourceHash } from '../structs/event-source'
-import { mapHash } from '../util/object'
-import { parseClassName } from '../util/html'
 import { Duration } from '../datelib/duration'
 import { computeVisibleDayRange } from '../util/misc'
 import { Seg } from './DateComponent'
 import View from '../View'
 import EventApi from '../api/EventApi'
-
-export interface EventUi {
-  startEditable: boolean
-  durationEditable: boolean
-  backgroundColor: string
-  borderColor: string
-  textColor: string,
-  rendering: string,
-  classNames: string[]
-}
-
-export type EventUiHash = { [defId: string]: EventUi }
+import { EventUi, EventUiHash } from './event-ui'
 
 export interface EventRenderRange extends EventTuple {
   ui: EventUi
@@ -29,7 +15,6 @@ export interface EventRenderRange extends EventTuple {
   isEnd: boolean
 }
 
-
 /*
 Specifying nextDayThreshold signals that all-day ranges should be sliced.
 */
@@ -176,66 +161,3 @@ function setElSeg(el: HTMLElement, seg: Seg) {
 export function getElSeg(el: HTMLElement): Seg | null {
   return (el as any).fcSeg || null
 }
-
-
-// UI Props
-// ----------------------------------------------------------------------------------------------------
-
-export function computeEventDefUis(eventDefs: EventDefHash, eventSources: EventSourceHash, options) {
-  return mapHash(eventDefs, function(eventDef) {
-    return computeEventDefUi(eventDef, eventSources, options)
-  })
-}
-
-export function computeEventDefUi(eventDef: EventDef, eventSources: EventSourceHash, options) {
-
-  // lowest to highest priority
-  // TODO: hook for resources, using refineScopedUi
-  let refinedHashes = [
-    refineScopedUi(options),
-    refineUnscopedUi(eventSources[eventDef.sourceId] || {}),
-    refineUnscopedUi(eventDef)
-  ]
-
-  return refinedHashes.reduce(combineUis)
-}
-
-// has word "event" in prop names
-// FYI: startEditable/durationEditable might end up being null
-function refineScopedUi(input): EventUi {
-  return {
-    startEditable: (input.startEditable != null) ? input.startEditable : input.editable,
-    durationEditable: (input.durationEditable != null) ? input.durationEditable : input.editable,
-    backgroundColor: input.eventBackgroundColor || input.eventColor || '',
-    borderColor: input.eventBorderColor || input.eventColor || '',
-    textColor: input.eventTextColor || '',
-    rendering: input.eventRendering || '',
-    classNames: parseClassName(input.eventClassNames || input.eventClassName) // probs already parsed
-  }
-}
-
-// does NOT have the word "event" in prop names
-// FYI: startEditable/durationEditable might end up being null
-function refineUnscopedUi(input): EventUi {
-  return {
-    startEditable: (input.startEditable != null) ? input.startEditable : input.editable,
-    durationEditable: (input.durationEditable != null) ? input.durationEditable : input.editable,
-    backgroundColor: input.backgroundColor || input.color || '',
-    borderColor: input.borderColor || input.color || '',
-    textColor: input.textColor || '',
-    rendering: input.rendering || '',
-    classNames: parseClassName(input.classNames || input.className) // probs already parsed
-  }
-}
-
-function combineUis(hash0, hash1): EventUi { // hash1 has higher precedence
-  return {
-    startEditable: (hash1.startEditable != null) ? hash1.startEditable : hash0.startEditable,
-    durationEditable: (hash1.durationEditable != null) ? hash1.durationEditable : hash0.durationEditable,
-    backgroundColor: hash1.backgroundColor || hash0.backgroundColor,
-    borderColor: hash1.borderColor || hash0.borderColor,
-    textColor: hash1.textColor || hash0.textColor,
-    rendering: hash1.rendering || hash0.rendering,
-    classNames: hash0.classNames.concat(hash1.classNames)
-  }
-}

+ 157 - 0
src/component/event-ui.ts

@@ -0,0 +1,157 @@
+import { Constraint, Allow, normalizeConstraint, ConstraintInput } from '../validation'
+import { parseClassName } from '../util/html'
+import { refineProps } from '../util/misc'
+import Calendar from '../Calendar'
+import { EventDefHash, EventDef } from '../structs/event'
+import { EventSourceHash } from '../structs/event-source'
+import { mapHash } from '../util/object'
+
+export interface UnscopedEventUiInput {
+  editable?: boolean
+  startEditable?: boolean
+  durationEditable?: boolean
+  constraint?: ConstraintInput
+  overlap?: boolean // does not allow full Overlap data type
+  allow?: Allow
+  rendering?: string
+  className?: string[] | string
+  classNames?: string[] | string
+  backgroundColor?: string
+  borderColor?: string
+  textColor?: string
+  color?: string
+}
+
+export interface ScopedEventUiInput {
+  editable?: boolean // only one not scoped
+  eventStartEditable?: boolean
+  eventDurationEditable?: boolean
+  eventConstraint?: ConstraintInput
+  eventOverlap?: boolean // does not allow full Overlap data type
+  eventAllow?: Allow
+  eventRendering?: string
+  eventClassName?: string[] | string
+  eventClassNames?: string[] | string
+  eventBackgroundColor?: string
+  eventBorderColor?: string
+  eventTextColor?: string
+  eventColor?: string
+}
+
+export interface EventUi {
+  startEditable: boolean | null
+  durationEditable: boolean | null
+  constraint: Constraint | null
+  overlap: boolean | null
+  allow: Allow | null
+  rendering: string
+  backgroundColor: string
+  borderColor: string
+  textColor: string,
+  classNames: string[]
+}
+
+export type EventUiHash = { [defId: string]: EventUi }
+
+const UNSCOPED_EVENT_UI_PROPS = {
+  editable: Boolean,
+  startEditable: Boolean,
+  durationEditable: Boolean,
+  constraint: null,
+  overlap: Boolean,
+  allow: null,
+  rendering: String,
+  className: parseClassName,
+  classNames: parseClassName,
+  color: String,
+  backgroundColor: String,
+  borderColor: String,
+  textColor: String
+}
+
+const SCOPED_EVENT_UI_PROPS = {
+  editable: Boolean, // only one not scoped
+  eventStartEditable: Boolean,
+  eventDurationEditable: Boolean,
+  eventConstraint: null,
+  eventOverlap: Boolean,
+  eventAllow: null,
+  eventRendering: String,
+  eventClassName: parseClassName,
+  eventClassNames: parseClassName,
+  eventColor: String,
+  eventBackgroundColor: String,
+  eventBorderColor: String,
+  eventTextColor: String
+}
+
+export function computeEventDefUis(eventDefs: EventDefHash, eventSources: EventSourceHash, calendar: Calendar): EventUiHash {
+  return mapHash(eventDefs, function(eventDef) {
+    return computeEventDefUi(eventDef, eventSources, calendar)
+  })
+}
+
+export function computeEventDefUi(eventDef: EventDef, eventSources: EventSourceHash, calendar: Calendar): EventUi {
+  let uis = [ calendar.baseEventUi ]
+
+  if (eventDef.sourceId && eventSources[eventDef.sourceId]) {
+    uis.push(eventSources[eventDef.sourceId].ui)
+  }
+
+  uis.push(eventDef.ui)
+
+  return combineEventUis(uis)
+}
+
+export function processUnscopedUiProps(rawProps: UnscopedEventUiInput, calendar: Calendar, leftovers?): EventUi {
+  let props = refineProps(rawProps, UNSCOPED_EVENT_UI_PROPS, {}, leftovers)
+
+  return {
+    startEditable: props.startEditable != null ? props.startEditable : props.editable,
+    durationEditable: props.durationEditable != null ? props.durationEditable : props.editable,
+    constraint: normalizeConstraint(props.constraint, calendar),
+    overlap: props.overlap,
+    allow: props.allow,
+    rendering: props.rendering,
+    backgroundColor: props.backgroundColor || props.color,
+    borderColor: props.borderColor || props.color,
+    textColor: props.textColor,
+    classNames: props.classNames.concat(props.className)
+  }
+}
+
+export function processScopedUiProps(rawProps: ScopedEventUiInput, calendar: Calendar, leftovers?): EventUi {
+  let props = refineProps(rawProps, SCOPED_EVENT_UI_PROPS, {}, leftovers)
+
+  return {
+    startEditable: props.eventStartEditable != null ? props.eventStartEditable : props.editable,
+    durationEditable: props.eventDurationEditable != null ? props.eventDurationEditable : props.editable,
+    constraint: normalizeConstraint(props.eventConstraint, calendar),
+    overlap: props.eventOverlap,
+    allow: props.eventAllow,
+    rendering: props.eventRendering,
+    backgroundColor: props.eventBackgroundColor || props.eventColor,
+    borderColor: props.eventBorderColor || props.eventColor,
+    textColor: props.eventTextColor,
+    classNames: props.eventClassNames.concat(props.eventClassName)
+  }
+}
+
+export function combineEventUis(uis: EventUi[]): EventUi {
+  return uis.reduce(combineTwoEventUis)
+}
+
+function combineTwoEventUis(hash0: EventUi, hash1: EventUi): 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)
+  }
+}

+ 2 - 1
src/component/renderers/FgEventRenderer.ts

@@ -2,7 +2,8 @@ import { DateMarker } from '../../datelib/marker'
 import { createFormatter, DateFormatter } from '../../datelib/formatting'
 import { htmlToElements } from '../../util/dom-manip'
 import { compareByFieldSpecs } from '../../util/misc'
-import { EventRenderRange, EventUi, filterSegsViaEls } from '../event-rendering'
+import { EventUi } from '../event-ui'
+import { EventRenderRange, filterSegsViaEls } from '../event-rendering'
 import { Seg } from '../DateComponent'
 import { assignTo } from '../../util/object'
 import { ComponentContext } from '../Component'

+ 3 - 1
src/component/renderers/FillRenderer.ts

@@ -106,11 +106,13 @@ export default abstract class FillRenderer { // use for highlight, background ev
     let css = null
     let classNames = []
 
-    if (seg.eventRange) {
+    if (type !== 'highlight' && type !== 'businessHours') {
       css = {
         'background-color': seg.eventRange.ui.backgroundColor
       }
+    }
 
+    if (type !== 'highlight') {
       classNames = classNames.concat(seg.eventRange.ui.classNames)
     }
 

+ 2 - 1
src/exports.ts

@@ -63,7 +63,8 @@ export {
 } from './util/dom-manip'
 
 export { EventStore, filterEventStoreDefs, createEmptyEventStore } from './structs/event-store'
-export { EventUiHash, hasBgRendering } from './component/event-rendering'
+export { hasBgRendering } from './component/event-rendering'
+export { EventUiHash } from './component/event-ui'
 export { buildGotoAnchorHtml, getAllDayHtml, getDayClasses } from './component/date-rendering'
 
 export {

+ 1 - 1
src/interactions/event-interaction-state.ts

@@ -1,6 +1,6 @@
 import { EventStore } from '../structs/event-store'
 import { Seg } from '../component/DateComponent'
-import { EventUiHash } from '../component/event-rendering'
+import { EventUiHash } from '../component/event-ui'
 
 export interface EventInteractionState { // is this ever used alone?
   affectedEvents: EventStore

+ 2 - 1
src/list/ListView.ts

@@ -11,7 +11,8 @@ import DateProfileGenerator, { DateProfile } from '../DateProfileGenerator'
 import { buildGotoAnchorHtml } from '../component/date-rendering'
 import { ComponentContext } from '../component/Component'
 import { ViewSpec } from '../structs/view-spec'
-import { EventRenderRange, EventUiHash, sliceEventStore } from '../component/event-rendering'
+import { EventUiHash } from '../component/event-ui'
+import { EventRenderRange, sliceEventStore } from '../component/event-rendering'
 import { EventStore } from '../structs/event-store'
 
 /*

+ 3 - 3
src/reducers/main.ts

@@ -6,7 +6,7 @@ import { DateSpan } from '../structs/date-span'
 import { EventInteractionUiState } from '../interactions/event-interaction-state'
 import { CalendarState, Action } from './types'
 import { EventSourceHash } from '../structs/event-source'
-import { computeEventDefUis } from '../component/event-rendering'
+import { computeEventDefUis } from '../component/event-ui'
 import { assignTo } from '../util/object'
 
 export default function(state: CalendarState, action: Action, calendar: Calendar): CalendarState {
@@ -98,7 +98,7 @@ function reduceEventDrag(currentDrag: EventInteractionUiState | null, action: Ac
       let eventUis = computeEventDefUis(
         newDrag.mutatedEvents.defs,
         sources,
-        calendar.viewOpts()
+        calendar
       )
       return {
         affectedEvents: newDrag.affectedEvents,
@@ -124,7 +124,7 @@ function reduceEventResize(currentResize: EventInteractionUiState | null, action
       let eventUis = computeEventDefUis(
         newResize.mutatedEvents.defs,
         sources,
-        calendar.viewOpts()
+        calendar
       )
       return {
         affectedEvents: newResize.affectedEvents,

+ 3 - 2
src/structs/business-hours.ts

@@ -4,7 +4,8 @@ import { EventInput } from './event'
 import { EventStore, parseEvents, expandRecurring } from './event-store'
 import { DateRange } from '../datelib/date-range'
 import { Duration } from '../datelib/duration'
-import { EventRenderRange, sliceEventStore, computeEventDefUis } from '../component/event-rendering'
+import { EventRenderRange, sliceEventStore } from '../component/event-rendering'
+import { computeEventDefUis } from '../component/event-ui'
 
 /*
 Utils for converting raw business hour input into an EventStore,
@@ -62,7 +63,7 @@ export function sliceBusinessHours(businessHours: EventStore, range: DateRange,
 
   return sliceEventStore(
     expandedStore,
-    computeEventDefUis(expandedStore.defs, {}, {}),
+    computeEventDefUis(expandedStore.defs, {}, calendar),
     range,
     nextDayThreshold
   ).bg

+ 4 - 3
src/structs/date-span.ts

@@ -2,8 +2,9 @@ import { DateRange, rangesEqual, OpenDateRange } from '../datelib/date-range'
 import { DateInput, DateEnv } from '../datelib/env'
 import { refineProps } from '../util/misc'
 import { Duration } from '../datelib/duration'
-import { parseEventDef, createEventInstance } from './event';
-import { computeEventDefUi, EventRenderRange } from '../component/event-rendering';
+import { parseEventDef, createEventInstance } from './event'
+import { EventRenderRange } from '../component/event-rendering'
+import { computeEventDefUi } from '../component/event-ui'
 import Calendar from '../Calendar'
 
 /*
@@ -168,7 +169,7 @@ export function fabricateEventRange(dateSpan: DateSpan, calendar: Calendar): Eve
 
   return {
     def,
-    ui: computeEventDefUi(def, {}, {}),
+    ui: computeEventDefUi(def, {}, calendar),
     instance: createEventInstance(def.defId, dateSpan.range),
     range: dateSpan.range,
     isStart: true,

+ 3 - 1
src/structs/event-mutation.ts

@@ -52,7 +52,9 @@ function applyMutationToEventDef(eventDef: EventDef, mutation: EventMutation, ap
     standardProps.hasEnd = true
   }
 
-  assignTo(copy, standardProps)
+  assignTo(copy, standardProps, {
+    ui: assignTo({}, copy.ui, standardProps.ui) // the only prop we want to recursively overlay
+  })
 
   if (mutation.extendedProps) {
     copy.extendedProps = assignTo({}, copy.extendedProps, mutation.extendedProps)

+ 8 - 64
src/structs/event-source.ts

@@ -1,10 +1,10 @@
-import { ClassNameInput, parseClassName } from '../util/html'
 import { refineProps } from '../util/misc'
 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'
+import { ScopedEventUiInput, processUnscopedUiProps } from '../component/event-ui'
+import { EventUi } from '../component/event-ui'
 
 /*
 Parsing and normalization of the EventSource data type, which defines how event data is fetched.
@@ -23,22 +23,10 @@ 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 {
+export interface ExtendedEventSourceInput extends ScopedEventUiInput {
   id?: string | number // only accept number?
   allDayDefault?: boolean
   eventDataTransform?: EventInputTransformer
-  editable?: boolean
-  startEditable?: boolean
-  durationEditable?: boolean
-  constraint?: ConstraintInput
-  overlap?: boolean
-  allow?: Allow
-  rendering?: string
-  className?: ClassNameInput
-  color?: string
-  backgroundColor?: string
-  borderColor?: string
-  textColor?: string
 
   // array (TODO: how to move this to array-event-source?)
   events?: EventInput[]
@@ -73,16 +61,7 @@ export interface EventSource {
   fetchRange: DateRange | null
   allDayDefault: boolean | null
   eventDataTransform: EventInputTransformer
-  startEditable: boolean | null
-  durationEditable: boolean | null
-  constraint: Constraint | null
-  overlap: boolean | null // does not allow full Overlap data type
-  allow: Allow | null
-  rendering: string
-  className: string[]
-  backgroundColor: string
-  borderColor: string
-  textColor: string
+  ui: EventUi
   success: EventSourceSuccessResponseHandler | null
   failure: EventSourceErrorResponseHandler | null
 }
@@ -109,18 +88,6 @@ const SIMPLE_SOURCE_PROPS = {
   id: String,
   allDayDefault: Boolean,
   eventDataTransform: Function,
-  editable: Boolean,
-  startEditable: Boolean,
-  durationEditable: Boolean,
-  constraint: null,
-  overlap: Boolean,
-  allow: null,
-  rendering: String,
-  className: parseClassName,
-  color: String,
-  backgroundColor: String,
-  borderColor: String,
-  textColor: String,
   success: Function,
   failure: Function
 }
@@ -161,11 +128,10 @@ export function parseEventSource(raw: EventSourceInput, calendar: Calendar): Eve
   return null
 }
 
-/*
-TODO: combine with pluckNonDateProps AND refineScopedUi
-*/
 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 })
+  let leftovers = {}
+  let props = refineProps(raw, SIMPLE_SOURCE_PROPS, {}, leftovers)
+  let ui = processUnscopedUiProps(leftovers, calendar)
 
   props.isFetching = false
   props.latestFetchId = ''
@@ -174,29 +140,7 @@ function parseEventSourceProps(raw: ExtendedEventSourceInput, meta: object, sour
   props.sourceId = String(uid++)
   props.sourceDefId = sourceDefId
   props.meta = meta
-
-  if (props.constraint) {
-    props.constraint = normalizeConstraint(props.constraint, calendar)
-  }
-
-  if (props.startEditable == null) {
-    props.startEditable = props.editable
-  }
-
-  if (props.durationEditable == null) {
-    props.durationEditable = props.editable
-  }
-
-  if (!props.backgroundColor) {
-    props.backgroundColor = props.color
-  }
-
-  if (!props.borderColor) {
-    props.borderColor = props.color
-  }
-
-  delete props.editable
-  delete props.color
+  props.ui = ui
 
   return props as EventSource
 }

+ 10 - 64
src/structs/event.ts

@@ -1,13 +1,12 @@
 import { refineProps } from '../util/misc'
-import { parseClassName, ClassNameInput } from '../util/html'
 import { DateInput } from '../datelib/env'
 import Calendar from '../Calendar'
 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'
 import { Duration } from '../datelib/duration'
+import { UnscopedEventUiInput, EventUi, processUnscopedUiProps } from '../component/event-ui'
 
 /*
 Utils for parsing event-input data. Each util parses a subset of the event-input's data.
@@ -16,23 +15,11 @@ It's up to the caller to stitch them together into an aggregate object like an E
 
 export type EventRenderingChoice = '' | 'background' | 'inverse-background' | 'none'
 
-export interface EventNonDateInput {
+export interface EventNonDateInput extends UnscopedEventUiInput {
   id?: string | number
   groupId?: string | number
   title?: string
   url?: string
-  editable?: boolean
-  startEditable?: boolean
-  durationEditable?: boolean
-  constraint?: ConstraintInput
-  overlap?: boolean
-  rendering?: EventRenderingChoice
-  classNames?: ClassNameInput // accept both
-  className?: ClassNameInput //
-  color?: string
-  backgroundColor?: string
-  borderColor?: string
-  textColor?: string
   extendedProps?: object
   [extendedProp: string]: any
 }
@@ -56,15 +43,7 @@ export interface EventDef {
   recurringDef: { typeId: number, typeData: any, duration: Duration | null } | null
   title: string
   url: string
-  startEditable: boolean | null
-  durationEditable: boolean | null
-  constraint: Constraint | null
-  overlap: boolean | null // does not allow full Overlap data type
-  rendering: EventRenderingChoice
-  classNames: string[]
-  backgroundColor: string
-  borderColor: string
-  textColor: string
+  ui: EventUi
   extendedProps: any
 }
 
@@ -89,18 +68,6 @@ const NON_DATE_PROPS = {
   groupId: String,
   title: String,
   url: String,
-  editable: Boolean,
-  startEditable: Boolean,
-  durationEditable: Boolean,
-  constraint: null,
-  overlap: Boolean,
-  rendering: String,
-  classNames: parseClassName,
-  className: parseClassName,
-  color: String,
-  backgroundColor: String,
-  borderColor: String,
-  textColor: String,
   extendedProps: null
 }
 
@@ -178,7 +145,7 @@ export function parseEventDef(raw: EventNonDateInput, sourceId: string, allDay:
   def.extendedProps = assignTo(leftovers, def.extendedProps || {})
 
   // help out EventApi from having user modify props
-  Object.freeze(def.classNames)
+  Object.freeze(def.ui.classNames)
   Object.freeze(def.extendedProps)
 
   return def
@@ -280,36 +247,15 @@ function pluckDateProps(raw: EventInput, leftovers: any) {
 }
 
 
-function pluckNonDateProps(raw: EventInput, calendar: Calendar, leftovers: any) {
-  let props = refineProps(raw, NON_DATE_PROPS, {}, leftovers)
+export function pluckNonDateProps(raw: EventInput, calendar: Calendar, leftovers?) {
+  let preLeftovers = {}
+  let props = refineProps(raw, NON_DATE_PROPS, {}, preLeftovers)
+  let ui = processUnscopedUiProps(preLeftovers, calendar, 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
-  }
-
-  if (props.durationEditable == null) {
-    props.durationEditable = props.editable
-  }
-
-  if (!props.backgroundColor) {
-    props.backgroundColor = props.color
-  }
-
-  if (!props.borderColor) {
-    props.borderColor = props.color
-  }
-
   delete props.id
-  delete props.className
-  delete props.editable
-  delete props.color
+
+  props.ui = ui
 
   return props
 }

+ 3 - 11
src/types/input-types.ts

@@ -13,6 +13,7 @@ import { BusinessHoursInput } from '../structs/business-hours'
 import EventApi from '../api/EventApi'
 import { Allow, ConstraintInput, Overlap } from '../validation'
 import { PluginDef } from '../plugin-system'
+import { UnscopedEventUiInput } from '../component/event-ui'
 
 
 export interface ToolbarInput {
@@ -70,7 +71,7 @@ export interface DropInfo {
   end: Date
 }
 
-export interface OptionsInputBase {
+export interface OptionsInputBase extends UnscopedEventUiInput {
   header?: boolean | ToolbarInput
   footer?: boolean | ToolbarInput
   customButtons?: { [name: string]: CustomButtonInput }
@@ -148,21 +149,12 @@ export interface OptionsInputBase {
   startParam?: string
   endParam?: string
   lazyFetching?: boolean
-  eventColor?: string
-  eventBackgroundColor?: string
-  eventBorderColor?: string
-  eventTextColor?: string
   nextDayThreshold?: DurationInput
   eventOrder?: string | Array<((a: EventApi, b: EventApi) => number) | (string | ((a: EventApi, b: EventApi) => number))>
   rerenderDelay?: number | null
-  editable?: boolean
-  eventStartEditable?: boolean
-  eventDurationEditable?: boolean
   dragRevertDuration?: number
   dragScroll?: boolean
-  eventConstraint?: ConstraintInput
-  eventOverlap?: Overlap
-  eventAllow?: Allow
+  eventOverlap?: Overlap // more than what EventUi offers
   longPressDelay?: number
   eventLongPressDelay?: number
   droppable?: boolean

+ 8 - 23
src/validation.ts

@@ -2,9 +2,9 @@ import { EventStore, expandRecurring, eventTupleToStore, mapEventInstances, filt
 import Calendar from './Calendar'
 import { DateSpan, parseOpenDateSpan, OpenDateSpanInput, OpenDateSpan, isSpanPropsEqual, isSpanPropsMatching, buildDateSpanApi, DateSpanApi } from './structs/date-span'
 import { EventInstance, EventDef, EventTuple, parseEvent } from './structs/event'
-import { EventSourceHash } from './structs/event-source'
 import { rangeContainsRange, rangesIntersect } from './datelib/date-range'
 import EventApi from './api/EventApi'
+import { EventUiHash } from './component/event-ui'
 
 // TODO: rename to "criteria" ?
 export type ConstraintInput = 'businessHours' | string | OpenDateSpanInput | { [timeOrRecurringProp: string]: any }
@@ -22,7 +22,7 @@ interface ValidationEntity {
 
 export function isEventsValid(eventStore: EventStore, calendar: Calendar): boolean {
   return isEntitiesValid(
-    eventStoreToEntities(eventStore, calendar.state.eventSources),
+    eventStoreToEntities(eventStore, calendar.renderableEventUis),
     normalizeConstraint(calendar.opt('eventConstraint'), calendar),
     calendar.opt('eventOverlap'),
     calendar.opt('eventAllow'),
@@ -47,7 +47,6 @@ function isEntitiesValid(
   globalAllow: Allow | null,
   calendar: Calendar
 ): boolean {
-  let state = calendar.state
 
   for (let entity of entities) {
     if (
@@ -58,7 +57,7 @@ function isEntitiesValid(
     }
   }
 
-  let eventEntities = eventStoreToEntities(state.eventStore, state.eventSources)
+  let eventEntities = eventStoreToEntities(calendar.state.eventStore, calendar.renderableEventUis)
 
   for (let subjectEntity of entities) {
     for (let eventEntity of eventEntities) {
@@ -103,30 +102,16 @@ function isEventsCollidable(event0: EventTuple, event1: EventTuple): boolean {
   return !isEventDefsGrouped(event0.def, event1.def)
 }
 
-function eventStoreToEntities(eventStore: EventStore, eventSources: EventSourceHash): ValidationEntity[] {
+function eventStoreToEntities(eventStore: EventStore, eventUis: EventUiHash): ValidationEntity[] {
   return mapEventInstances(eventStore, function(eventInstance: EventInstance, eventDef: EventDef): ValidationEntity {
-    let eventSource = eventSources[eventDef.sourceId]
-    let constraint = eventDef.constraint as Constraint
-    let overlap = eventDef.overlap as boolean
-
-    if (constraint == null && eventSource) {
-      constraint = eventSource.constraint
-    }
-
-    if (overlap == null && eventSource) {
-      overlap = eventSource.overlap
-
-      if (overlap == null) {
-        overlap = true
-      }
-    }
+    let eventUi = eventUis[eventDef.defId]
 
     return {
       dateSpan: eventToDateSpan(eventDef, eventInstance),
       event: { def: eventDef, instance: eventInstance },
-      constraint,
-      overlap,
-      allow: eventSource ? eventSource.allow : null
+      constraint: eventUi.constraint,
+      overlap: eventUi.overlap,
+      allow: eventUi.allow
     }
   })
 }