Browse Source

change how event draggability/resizability is determined, plugin hook. fixes #4930

Adam Shaw 6 years ago
parent
commit
481a88db2e

+ 1 - 1
packages-premium

@@ -1 +1 @@
-Subproject commit 1dc2ebb113ecf9ae1806b030cb07f7e33b08ee26
+Subproject commit 2b8bc2917f4d7828d9d60eaaa8b59cea0865f1ac

+ 21 - 1
packages/core/src/View.ts

@@ -8,12 +8,13 @@ import { createElement } from './util/dom-manip'
 import { ComponentContext } from './component/Component'
 import DateComponent from './component/DateComponent'
 import { EventStore } from './structs/event-store'
-import { EventUiHash } from './component/event-ui'
+import { EventUiHash, EventUi } from './component/event-ui'
 import { sliceEventStore, EventRenderRange } from './component/event-rendering'
 import { DateSpan } from './structs/date-span'
 import { EventInteractionState } from './interactions/event-interaction-state'
 import { memoizeRendering } from './component/memoized-rendering'
 import { __assign } from 'tslib'
+import { EventDef } from './structs/event'
 
 export interface ViewProps {
   dateProfile: DateProfile
@@ -218,6 +219,25 @@ export default abstract class View extends DateComponent<ViewProps> {
     ).fg
   }
 
+  computeEventDraggable(eventDef: EventDef, eventUi: EventUi) {
+    let transformers = this.calendar.pluginSystem.hooks.isDraggableTransformers
+    let val = eventUi.startEditable
+
+    for (let transformer of transformers) {
+      val = transformer(val, eventDef, eventUi, this)
+    }
+
+    return val
+  }
+
+  computeEventStartResizable(eventDef: EventDef, eventUi: EventUi) {
+    return eventUi.durationEditable && this.opt('eventResizableFromStart')
+  }
+
+  computeEventEndResizable(eventDef: EventDef, eventUi: EventUi) {
+    return eventUi.durationEditable
+  }
+
 
   // Event Selection
   // -----------------------------------------------------------------------------------------------------------------

+ 14 - 11
packages/core/src/api/EventApi.ts

@@ -4,7 +4,7 @@ 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 { DurationInput, createDuration, durationsEqual } from '../datelib/duration'
 import { createFormatter, FormatterInput } from '../datelib/formatting'
 import EventSourceApi from './EventSourceApi'
 
@@ -74,15 +74,12 @@ export default class EventApi {
     if (start && this._instance) { // TODO: warning if parsed bad
       let instanceRange = this._instance.range
       let startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity) // what if parsed bad!?
-      let endDelta = null
 
       if (options.maintainDuration) {
-        let origDuration = diffDates(instanceRange.start, instanceRange.end, dateEnv, options.granularity)
-        let newDuration = diffDates(start, instanceRange.end, dateEnv, options.granularity)
-        endDelta = subtractDurations(origDuration, newDuration)
+        this.mutate({ datesDelta: startDelta })
+      } else {
+        this.mutate({ startDelta })
       }
-
-      this.mutate({ startDelta, endDelta })
     }
   }
 
@@ -139,10 +136,16 @@ export default class EventApi {
 
       if (end) {
         let endDelta = diffDates(instanceRange.end, end, dateEnv, options.granularity)
-        this.mutate({ startDelta, endDelta, standardProps })
-      } else {
+
+        if (durationsEqual(startDelta, endDelta)) {
+          this.mutate({ datesDelta: startDelta, standardProps })
+        } else {
+          this.mutate({ startDelta, endDelta, standardProps })
+        }
+
+      } else { // means "clear the end"
         standardProps.hasEnd = false
-        this.mutate({ startDelta, standardProps })
+        this.mutate({ datesDelta: startDelta, standardProps })
       }
     }
   }
@@ -167,7 +170,7 @@ export default class EventApi {
     let delta = createDuration(deltaInput)
 
     if (delta) { // TODO: warning if parsed bad
-      this.mutate({ startDelta: delta, endDelta: delta })
+      this.mutate({ datesDelta: delta })
     }
   }
 

+ 4 - 0
packages/core/src/interactions/event-dragging.ts

@@ -1,6 +1,10 @@
 import Calendar from '../Calendar'
 import { EventMutation } from '../structs/event-mutation'
 import { Hit } from './hit'
+import { EventDef } from '../structs/event'
+import { EventUi } from '../component/event-ui'
+import { View } from '@fullcalendar/core'
 
 export type eventDragMutationMassager = (mutation: EventMutation, hit0: Hit, hit1: Hit) => void
 export type EventDropTransformers = (mutation: EventMutation, calendar: Calendar) => any
+export type eventIsDraggableTransformer = (val: boolean, eventDef: EventDef, eventUi: EventUi, view: View) => boolean

+ 6 - 1
packages/core/src/plugin-system.ts

@@ -7,7 +7,7 @@ import { ViewSpec } from './structs/view-spec'
 import View, { ViewProps } from './View'
 import { CalendarComponentProps } from './CalendarComponent'
 import { isPropsValidTester } from './validation'
-import { eventDragMutationMassager, EventDropTransformers } from './interactions/event-dragging'
+import { eventDragMutationMassager, eventIsDraggableTransformer, EventDropTransformers } from './interactions/event-dragging'
 import { dateSelectionJoinTransformer } from './interactions/date-selecting'
 import { EventResizeJoinTransforms } from './interactions/event-resizing'
 import { ExternalDefTransform } from './interactions/external-element-dragging'
@@ -25,6 +25,7 @@ export interface PluginDefInput {
   deps?: PluginDef[]
   reducers?: reducerFunc[]
   eventDefParsers?: eventDefParserFunc[]
+  isDraggableTransformers?: eventIsDraggableTransformer[]
   eventDragMutationMassagers?: eventDragMutationMassager[]
   eventDefMutationAppliers?: eventDefMutationApplier[]
   dateSelectionTransformers?: dateSelectionJoinTransformer[]
@@ -52,6 +53,7 @@ export interface PluginDefInput {
 export interface PluginHooks {
   reducers: reducerFunc[]
   eventDefParsers: eventDefParserFunc[]
+  isDraggableTransformers: eventIsDraggableTransformer[]
   eventDragMutationMassagers: eventDragMutationMassager[]
   eventDefMutationAppliers: eventDefMutationApplier[]
   dateSelectionTransformers: dateSelectionJoinTransformer[]
@@ -98,6 +100,7 @@ export function createPlugin(input: PluginDefInput): PluginDef {
     deps: input.deps || [],
     reducers: input.reducers || [],
     eventDefParsers: input.eventDefParsers || [],
+    isDraggableTransformers: input.isDraggableTransformers || [],
     eventDragMutationMassagers: input.eventDragMutationMassagers || [],
     eventDefMutationAppliers: input.eventDefMutationAppliers || [],
     dateSelectionTransformers: input.dateSelectionTransformers || [],
@@ -132,6 +135,7 @@ export class PluginSystem {
     this.hooks = {
       reducers: [],
       eventDefParsers: [],
+      isDraggableTransformers: [],
       eventDragMutationMassagers: [],
       eventDefMutationAppliers: [],
       dateSelectionTransformers: [],
@@ -176,6 +180,7 @@ function combineHooks(hooks0: PluginHooks, hooks1: PluginHooks): PluginHooks {
   return {
     reducers: hooks0.reducers.concat(hooks1.reducers),
     eventDefParsers: hooks0.eventDefParsers.concat(hooks1.eventDefParsers),
+    isDraggableTransformers: hooks0.isDraggableTransformers.concat(hooks1.isDraggableTransformers),
     eventDragMutationMassagers: hooks0.eventDragMutationMassagers.concat(hooks1.eventDragMutationMassagers),
     eventDefMutationAppliers: hooks0.eventDefMutationAppliers.concat(hooks1.eventDefMutationAppliers),
     dateSelectionTransformers: hooks0.dateSelectionTransformers.concat(hooks1.dateSelectionTransformers),

+ 18 - 34
packages/core/src/structs/event-mutation.ts

@@ -12,8 +12,9 @@ A data structure for how to modify an EventDef/EventInstance within an EventStor
 */
 
 export interface EventMutation {
-  startDelta?: Duration
-  endDelta?: Duration
+  datesDelta?: Duration // body start+end moving together. for dragging
+  startDelta?: Duration // for resizing
+  endDelta?: Duration // for resizing
   standardProps?: any // for the def. should not include extendedProps
   extendedProps?: any // for the def
 }
@@ -51,10 +52,7 @@ function applyMutationToEventDef(eventDef: EventDef, eventConfig: EventUi, mutat
   if (
     standardProps.hasEnd == null &&
     eventConfig.durationEditable &&
-    willDeltasAffectDuration(
-      eventConfig.startEditable ? mutation.startDelta : null,
-      mutation.endDelta || null
-    )
+    (mutation.startDelta || mutation.endDelta)
   ) {
     standardProps.hasEnd = true // TODO: is this mutation okay?
   }
@@ -80,20 +78,6 @@ function applyMutationToEventDef(eventDef: EventDef, eventConfig: EventUi, mutat
   return copy
 }
 
-function willDeltasAffectDuration(startDelta: Duration | null, endDelta: Duration | null) {
-  if (startDelta && !asRoughMs(startDelta)) { startDelta = null }
-  if (endDelta && !asRoughMs(endDelta)) { endDelta = null }
-
-  if (!startDelta && !endDelta) {
-    return false
-  }
-
-  if (Boolean(startDelta) !== Boolean(endDelta)) {
-    return true
-  }
-
-  return !durationsEqual(startDelta, endDelta)
-}
 
 function applyMutationToEventInstance(
   eventInstance: EventInstance,
@@ -111,31 +95,31 @@ function applyMutationToEventInstance(
     copy.range = computeAlignedDayRange(copy.range)
   }
 
-  if (mutation.startDelta && eventConfig.startEditable) {
+  if (mutation.datesDelta && eventConfig.startEditable) {
+    copy.range = {
+      start: dateEnv.add(copy.range.start, mutation.datesDelta),
+      end: dateEnv.add(copy.range.end, mutation.datesDelta)
+    }
+  }
+
+  if (mutation.startDelta && eventConfig.durationEditable) {
     copy.range = {
       start: dateEnv.add(copy.range.start, mutation.startDelta),
       end: copy.range.end
     }
   }
 
-  if (clearEnd) {
+  if (mutation.endDelta && eventConfig.durationEditable) {
     copy.range = {
       start: copy.range.start,
-      end: calendar.getDefaultEventEnd(eventDef.allDay, copy.range.start)
+      end: dateEnv.add(copy.range.end, mutation.endDelta)
     }
-  } else if (
-    mutation.endDelta &&
-    (
-      eventConfig.durationEditable ||
-      !willDeltasAffectDuration( // TODO: nonDRY logic above
-        eventConfig.startEditable ? mutation.startDelta : null,
-        mutation.endDelta
-      )
-    )
-  ) {
+  }
+
+  if (clearEnd) {
     copy.range = {
       start: copy.range.start,
-      end: dateEnv.add(copy.range.end, mutation.endDelta)
+      end: calendar.getDefaultEventEnd(eventDef.allDay, copy.range.start)
     }
   }
 

+ 4 - 4
packages/daygrid/src/SimpleDayGridEventRenderer.ts

@@ -14,14 +14,14 @@ export default abstract class SimpleDayGridEventRenderer extends FgEventRenderer
 
   // Builds the HTML to be used for the default element for an individual segment
   renderSegHtml(seg: Seg, mirrorInfo) {
-    let { options } = this.context
+    let { view, options } = this.context
     let eventRange = seg.eventRange
     let eventDef = eventRange.def
     let eventUi = eventRange.ui
     let allDay = eventDef.allDay
-    let isDraggable = eventUi.startEditable
-    let isResizableFromStart = allDay && seg.isStart && eventUi.durationEditable && options.eventResizableFromStart
-    let isResizableFromEnd = allDay && seg.isEnd && eventUi.durationEditable
+    let isDraggable = view.computeEventDraggable(eventDef, eventUi)
+    let isResizableFromStart = allDay && seg.isStart && view.computeEventStartResizable(eventDef, eventUi)
+    let isResizableFromEnd = allDay && seg.isEnd && view.computeEventEndResizable(eventDef, eventUi)
     let classes = this.getSegClasses(seg, isDraggable, isResizableFromStart || isResizableFromEnd, mirrorInfo)
     let skinCss = cssToStr(this.getSkinCss(eventUi))
     let timeHtml = ''

+ 5 - 6
packages/interaction/src/interactions/EventDragging.ts

@@ -214,7 +214,7 @@ export default class EventDragging extends Interaction { // TODO: rename to Even
     if (this.isDragging) {
       let initialCalendar = this.component.calendar
       let initialView = this.component.view
-      let { receivingCalendar } = this
+      let { receivingCalendar, validMutation } = this
       let eventDef = this.eventRange!.def
       let eventInstance = this.eventRange!.instance
       let eventApi = new EventApi(initialCalendar, eventDef, eventInstance)
@@ -233,7 +233,7 @@ export default class EventDragging extends Interaction { // TODO: rename to Even
         }
       ])
 
-      if (this.validMutation) {
+      if (validMutation) {
 
         // dropped within same calendar
         if (receivingCalendar === initialCalendar) {
@@ -246,13 +246,13 @@ export default class EventDragging extends Interaction { // TODO: rename to Even
           let transformed: ReturnType<EventDropTransformers> = {}
 
           for (let transformer of initialCalendar.pluginSystem.hooks.eventDropTransformers) {
-            __assign(transformed, transformer(this.validMutation, initialCalendar))
+            __assign(transformed, transformer(validMutation, initialCalendar))
           }
 
           const eventDropArg = {
             ...transformed, // don't use __assign here because it's not type-safe
             el: ev.subjectEl as HTMLElement,
-            delta: this.validMutation.startDelta!,
+            delta: validMutation.datesDelta!,
             oldEvent: eventApi,
             event: new EventApi( // the data AFTER the mutation
               initialCalendar,
@@ -417,8 +417,7 @@ function computeEventMutation(hit0: Hit, hit1: Hit, massagers: eventDragMutation
   }
 
   let mutation: EventMutation = {
-    startDelta: delta,
-    endDelta: delta,
+    datesDelta: delta,
     standardProps
   }
 

+ 1 - 1
packages/interaction/src/interactions/EventResizing.ts

@@ -225,7 +225,7 @@ function computeMutation(hit0: Hit, hit1: Hit, isFromStart: boolean, instanceRan
     hit0.component.largeUnit
   )
 
-  let props = {} as any
+  let props = {} as EventMutation
 
   for (let transform of transforms) {
     let res = transform(hit0, hit1)

+ 4 - 3
packages/timegrid/src/TimeGridEventRenderer.ts

@@ -102,13 +102,14 @@ export default class TimeGridEventRenderer extends FgEventRenderer {
 
   // Renders the HTML for a single event segment's default rendering
   renderSegHtml(seg: Seg, mirrorInfo) {
+    let { view } = this.context
     let eventRange = seg.eventRange
     let eventDef = eventRange.def
     let eventUi = eventRange.ui
     let allDay = eventDef.allDay
-    let isDraggable = eventUi.startEditable
-    let isResizableFromStart = seg.isStart && eventUi.durationEditable && this.context.options.eventResizableFromStart
-    let isResizableFromEnd = seg.isEnd && eventUi.durationEditable
+    let isDraggable = view.computeEventDraggable(eventDef, eventUi)
+    let isResizableFromStart = seg.isStart && view.computeEventStartResizable(eventDef, eventUi)
+    let isResizableFromEnd = seg.isEnd && view.computeEventEndResizable(eventDef, eventUi)
     let classes = this.getSegClasses(seg, isDraggable, isResizableFromStart || isResizableFromEnd, mirrorInfo)
     let skinCss = cssToStr(this.getSkinCss(eventUi))
     let timeText