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

fix up resizing, more dragging

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

+ 4 - 4
src/agenda/AgendaView.ts

@@ -454,15 +454,15 @@ function splitInteractionState(state: EventInteractionState) {
     allDay = {
       affectedEvents: affectedEventGroups.allDay,
       mutatedEvents: mutatedEventGroups.allDay,
-      origSeg: state.origSeg,
-      willCreateEvent: state.willCreateEvent
+      isEvent: state.isEvent,
+      origSeg: state.origSeg
     }
 
     timed = {
       affectedEvents: affectedEventGroups.timed,
       mutatedEvents: mutatedEventGroups.timed,
-      origSeg: state.origSeg,
-      willCreateEvent: state.willCreateEvent
+      isEvent: state.isEvent,
+      origSeg: state.origSeg
     }
   }
 

+ 10 - 2
src/common/browser-context.ts

@@ -104,7 +104,9 @@ export class BrowserContext {
   }
 
   reportDateSelection(calendar: Calendar, selection: DateSpan, pev?: PointerDragEvent) {
-    this.unselectDates(pev)
+    if (this.dateSelectedCalendar && this.dateSelectedCalendar !== calendar) {
+      this.unselectDates(pev)
+    }
 
     calendar.publiclyTrigger('select', [
       {
@@ -129,7 +131,13 @@ export class BrowserContext {
   }
 
   reportEventSelection(component: DateComponent) {
-    this.unselectEvent()
+    if (
+      this.eventSelectedComponent &&
+      this.eventSelectedComponent.getCalendar() !== component.getCalendar()
+    ) {
+      this.unselectEvent()
+    }
+
     this.eventSelectedComponent = component
   }
 

+ 15 - 13
src/component/DateComponent.ts

@@ -49,8 +49,8 @@ export default abstract class DateComponent extends Component {
 
   // self-config, overridable by subclasses
   isInteractable: boolean = false
-  doesDragHelper: boolean = false
-  doesDragHighlight: boolean = false
+  doesDragHelper: boolean = false // for events that ORIGINATE from this component
+  doesDragHighlight: boolean = false // for events that ORIGINATE from this component
   segSelector: string = '.fc-event-container > *' // what constitutes an event element?
 
   // if defined, holds the unit identified (ex: "year" or "month") that determines the level of granularity
@@ -581,7 +581,7 @@ export default abstract class DateComponent extends Component {
 
   renderDragState(dragState: EventInteractionState) {
     this.hideSegsByHash(dragState.affectedEvents.instances)
-    this.renderDrag(dragState.mutatedEvents, dragState.origSeg, dragState.willCreateEvent)
+    this.renderDrag(dragState.mutatedEvents, dragState.isEvent, dragState.origSeg)
   }
 
 
@@ -593,19 +593,21 @@ export default abstract class DateComponent extends Component {
 
   // Renders a visual indication of a event or external-element drag over the given drop zone.
   // If an external-element, seg will be `null`.
-  renderDrag(eventStore: EventStore, origSeg, willCreateEvent) {
+  renderDrag(eventStore: EventStore, isEvent: boolean, origSeg: Seg | null) {
     let segs = this.eventStoreToSegs(eventStore)
-    let noEvent = !origSeg && !willCreateEvent
-
-    if (
-      !noEvent &&
-      (this.doesDragHelper || origSeg && origSeg.component.doesDragHelper) &&
-      this.helperRenderer
-    ) {
-      this.helperRenderer.renderEventDraggingSegs(segs, origSeg)
+
+    // if the user is dragging something that is considered an event with real event data,
+    // and this component likes to do drag mirrors OR the component where the seg came from
+    // likes to do drag mirrors, then render a drag mirror.
+    if (isEvent && (this.doesDragHelper || origSeg && origSeg.component.doesDragHelper)) {
+      if (this.helperRenderer) {
+        this.helperRenderer.renderEventDraggingSegs(segs, origSeg)
+      }
     }
 
-    if (noEvent || this.doesDragHighlight) {
+    // if it would be impossible to render a drag mirror OR this component likes to render
+    // highlights, then render a highlight.
+    if (!isEvent || this.doesDragHighlight) {
       this.renderHighlightSegs(segs)
     }
   }

+ 2 - 1
src/interactions-external/ExternalElementDragging.ts

@@ -51,7 +51,8 @@ export default class ExternalElementDragging {
     this.renderDrag(receivingCalendar, {
       affectedEvents: { defs: {}, instances: {} },
       mutatedEvents: addableEventStore || { defs: {}, instances: {} }, // TODO: better way to make empty event-store
-      willCreateEvent: Boolean(this.eventCreationData.standardProps)
+      isEvent: Boolean(this.eventCreationData.standardProps),
+      origSeg: null
     })
 
     // show mirror if no already-rendered helper element OR if we are shutting down the mirror

+ 109 - 76
src/interactions/EventDragging.ts

@@ -7,7 +7,7 @@ import browserContext from '../common/browser-context'
 import { startOfDay } from '../datelib/marker'
 import { elementClosest } from '../util/dom-manip'
 import FeaturefulElementDragging from '../dnd/FeaturefulElementDragging'
-import { EventStore } from '../reducers/event-store'
+import { EventStore, createEmptyEventStore } from '../reducers/event-store'
 import Calendar from '../Calendar'
 import { EventInteractionState } from '../reducers/event-interaction'
 
@@ -16,35 +16,51 @@ export default class EventDragging {
   component: DateComponent
   dragging: FeaturefulElementDragging
   hitDragging: HitDragging
-  draggingSeg: Seg
-  receivingCalendar: Calendar
-  validMutation: EventMutation
-  mutatedRelatedEvents: EventStore
+
+  // internal state
+  draggingSeg: Seg | null = null
+  eventInstanceId: string = ''
+  relatedEvents: EventStore | null = null
+  receivingCalendar: Calendar | null = null
+  validMutation: EventMutation | null = null
+  mutatedRelatedEvents: EventStore | null = null
 
   constructor(component: DateComponent) {
     this.component = component
 
-    this.dragging = new FeaturefulElementDragging(component.el)
-    this.dragging.pointer.selector = '.fc-draggable'
-    this.dragging.touchScrollAllowed = false
+    let dragging = this.dragging = new FeaturefulElementDragging(component.el)
+    dragging.pointer.selector = '.fc-draggable'
+    dragging.touchScrollAllowed = false
 
     let hitDragging = this.hitDragging = new HitDragging(this.dragging, browserContext.componentHash)
     hitDragging.useSubjectCenter = true
-    hitDragging.emitter.on('pointerdown', this.onPointerDown)
-    hitDragging.emitter.on('dragstart', this.onDragStart)
-    hitDragging.emitter.on('hitupdate', this.onHitUpdate)
-    hitDragging.emitter.on('dragend', this.onDragEnd)
+    hitDragging.emitter.on('pointerdown', this.handlePointerDown)
+    hitDragging.emitter.on('dragstart', this.handleDragStart)
+    hitDragging.emitter.on('hitupdate', this.handleHitUpdate)
+    hitDragging.emitter.on('dragend', this.handleDragEnd)
   }
 
   destroy() {
     this.dragging.destroy()
   }
 
-  onPointerDown = (ev: PointerDragEvent) => {
-    let { dragging } = this
+  handlePointerDown = (ev: PointerDragEvent) => {
+    let { component, dragging } = this
+    let initialCalendar = component.getCalendar()
+    let draggingSeg = this.draggingSeg = getElSeg(ev.subjectEl as HTMLElement)!
+    let eventInstanceId = this.eventInstanceId = draggingSeg.eventRange!.eventInstance!.instanceId
 
-    dragging.minDistance = 5
-    dragging.delay = this.computeDragDelay(ev)
+    this.relatedEvents = getRelatedEvents(
+      initialCalendar.state.eventStore,
+      eventInstanceId
+    )
+
+    dragging.minDistance = ev.isTouch ? 0 : 5
+    dragging.delay =
+      // only do a touch delay if touch and this event hasn't been selected yet
+      (ev.isTouch && eventInstanceId !== component.selectedEventInstanceId) ?
+        getComponentTouchDelay(component) :
+        null
 
     // to prevent from cloning the sourceEl before it is selected
     dragging.setMirrorIsVisible(false)
@@ -57,46 +73,36 @@ export default class EventDragging {
     )
   }
 
-  computeDragDelay(ev: PointerDragEvent): number {
+  handleDragStart = (ev: PointerDragEvent) => {
     if (ev.isTouch) {
-      let seg = getElSeg(ev.subjectEl)
-      let eventInstanceId = seg.eventRange.eventInstance.instanceId
 
-      if (eventInstanceId !== this.component.selectedEventInstanceId) {
-        return 1000 // TODO: use setting
-      }
-    }
-  }
+      // need to select a different event?
+      if (this.eventInstanceId !== this.component.selectedEventInstanceId) {
+        let initialCalendar = this.component.getCalendar()
 
-  onDragStart = (ev: PointerDragEvent) => {
-    this.draggingSeg = getElSeg(ev.subjectEl)
-
-    if (ev.isTouch) {
-      let eventInstanceId = this.draggingSeg.eventRange.eventInstance.instanceId
+        initialCalendar.dispatch({
+          type: 'SELECT_EVENT',
+          eventInstanceId: this.eventInstanceId
+        })
 
-      this.component.getCalendar().dispatch({
-        type: 'SELECT_EVENT',
-        eventInstanceId
-      })
+        browserContext.reportEventSelection(this.component) // will unselect previous
+      }
 
-      browserContext.reportEventSelection(this.component) // will unselect previous
+    // if now using mouse, but was previous touch interaction, clear selected event
     } else {
       browserContext.unselectEvent()
     }
   }
 
-  onHitUpdate = (hit: Hit | null, isFinal: boolean) => {
-    let { initialHit } = this.hitDragging // guaranteed
-    let initialCalendar = initialHit.component.getCalendar()
-    let receivingCalendar: Calendar = null
-    let validMutation: EventMutation = null
-    let relatedEvents: EventStore = null
-    let mutatedRelatedEvents: EventStore = null
+  handleHitUpdate = (hit: Hit | null, isFinal: boolean) => {
+    let relatedEvents = this.relatedEvents!
+    let initialHit = this.hitDragging.initialHit!
+    let initialCalendar = this.component.getCalendar()
 
-    relatedEvents = getRelatedEvents( // TODO: compute this only once?
-      initialCalendar.state.eventStore,
-      this.draggingSeg.eventRange.eventInstance.instanceId
-    )
+    // states based on new hit
+    let receivingCalendar: Calendar | null = null
+    let validMutation: EventMutation | null = null
+    let mutatedRelatedEvents: EventStore | null = null
 
     if (hit) {
       receivingCalendar = hit.component.getCalendar()
@@ -113,13 +119,14 @@ export default class EventDragging {
       }
     }
 
-    this.renderDrag(receivingCalendar, {
-      affectedEvents: relatedEvents,
-      mutatedEvents: mutatedRelatedEvents || relatedEvents,
-      origSeg: this.draggingSeg
-    })
-
     if (!isFinal) {
+      this.displayDrag(receivingCalendar, {
+        affectedEvents: relatedEvents,
+        mutatedEvents: mutatedRelatedEvents || relatedEvents,
+        isEvent: true,
+        origSeg: this.draggingSeg
+      })
+
       this.dragging.setMirrorNeedsRevert(!validMutation)
 
       // render the mirror if no already-rendered helper
@@ -128,13 +135,17 @@ export default class EventDragging {
         !hit || !document.querySelector('.fc-helper')
       )
 
+      // assign states based on new hit
       this.receivingCalendar = receivingCalendar
       this.validMutation = validMutation
       this.mutatedRelatedEvents = mutatedRelatedEvents
+
+    } else {
+      this.clearDrag() // drag is ending
     }
   }
 
-  onDocumentPointerUp = (ev, wasTouchScroll) => {
+  onDocumentPointerUp = (ev: PointerDragEvent, wasTouchScroll: boolean) => {
     if (
       !wasTouchScroll &&
       !this.draggingSeg && // was never dragging
@@ -145,24 +156,28 @@ export default class EventDragging {
     }
   }
 
-  onDragEnd = () => {
-    this.unrenderDrag()
+  handleDragEnd = () => {
+    let initialCalendar = this.component.getCalendar()
+    let { receivingCalendar } = this
 
     if (this.validMutation) {
-      let initialCalendar = this.hitDragging.initialHit.component.getCalendar()
 
-      if (this.receivingCalendar === initialCalendar) {
-        this.receivingCalendar.dispatch({
+      // dropped within same calendar
+      if (receivingCalendar === initialCalendar) {
+        receivingCalendar.dispatch({
           type: 'MUTATE_EVENTS',
           mutation: this.validMutation,
-          instanceId: this.draggingSeg.eventRange.eventInstance.instanceId
+          instanceId: this.eventInstanceId
         })
-      } else {
+
+      // dropped in different calendar
+      // TODO: more public triggers
+      } else if (receivingCalendar) {
         initialCalendar.dispatch({
           type: 'REMOVE_EVENTS',
           eventStore: this.mutatedRelatedEvents
         })
-        this.receivingCalendar.dispatch({
+        receivingCalendar.dispatch({
           type: 'ADD_EVENTS',
           eventStore: this.mutatedRelatedEvents,
           stick: true // TODO: use this param
@@ -170,47 +185,55 @@ export default class EventDragging {
       }
     }
 
+    // reset all internal state
+    this.draggingSeg = null
+    this.eventInstanceId = ''
+    this.relatedEvents = null
     this.receivingCalendar = null
     this.validMutation = null
     this.mutatedRelatedEvents = null
-    this.draggingSeg = null
   }
 
-  renderDrag(newReceivingCalendar: Calendar | null, dragState: EventInteractionState) {
-    let initialCalendar = this.hitDragging.initialHit.component.getCalendar()
-    let prevReceivingCalendar = this.receivingCalendar
+  // render a drag state on the next receivingCalendar
+  displayDrag(nextCalendar: Calendar | null, dragState: EventInteractionState) {
+    let initialCalendar = this.component.getCalendar()
+    let prevCalendar = this.receivingCalendar
 
-    if (prevReceivingCalendar && prevReceivingCalendar !== newReceivingCalendar) {
-      if (prevReceivingCalendar === initialCalendar) {
-        prevReceivingCalendar.dispatch({
+    // does the previous calendar need to be cleared?
+    if (prevCalendar && prevCalendar !== nextCalendar) {
+
+      // does the initial calendar need to be cleared?
+      // if so, don't clear all the way. we still need to to hide the affectedEvents
+      if (prevCalendar === initialCalendar) {
+        prevCalendar.dispatch({
           type: 'SET_DRAG',
           dragState: {
             affectedEvents: dragState.affectedEvents,
-            mutatedEvents: { defs: {}, instances: {} }, // TODO: util
+            mutatedEvents: createEmptyEventStore(),
             origSeg: dragState.origSeg
           }
         })
+
+      // completely clear the old calendar if it wasn't the initial
       } else {
-        prevReceivingCalendar.dispatch({ type: 'CLEAR_DRAG' })
+        prevCalendar.dispatch({ type: 'CLEAR_DRAG' })
       }
     }
 
-    if (newReceivingCalendar) {
-      newReceivingCalendar.dispatch({
-        type: 'SET_DRAG',
-        dragState
-      })
+    if (nextCalendar) {
+      nextCalendar.dispatch({ type: 'SET_DRAG', dragState })
     }
   }
 
-  unrenderDrag() {
-    let initialCalendar = this.hitDragging.initialHit.component.getCalendar()
+  clearDrag() {
+    let initialCalendar = this.component.getCalendar()
     let { receivingCalendar } = this
 
     if (receivingCalendar) {
       receivingCalendar.dispatch({ type: 'CLEAR_DRAG' })
     }
 
+    // the initial calendar might have an dummy drag state from displayDrag
     if (initialCalendar !== receivingCalendar) {
       initialCalendar.dispatch({ type: 'CLEAR_DRAG' })
     }
@@ -252,3 +275,13 @@ function computeEventMutation(hit0: Hit, hit1: Hit): EventMutation {
     standardProps
   }
 }
+
+function getComponentTouchDelay(component: DateComponent): number | null {
+  let delay = component.opt('eventLongPressDelay')
+
+  if (delay == null) {
+    delay = component.opt('longPressDelay')
+  }
+
+  return delay
+}

+ 55 - 38
src/interactions/EventResizing.ts

@@ -6,53 +6,66 @@ import UnzonedRange from '../models/UnzonedRange'
 import FeaturefulElementDragging from '../dnd/FeaturefulElementDragging'
 import { PointerDragEvent } from '../dnd/PointerDragging'
 import { getElSeg } from '../component/renderers/EventRenderer'
+import { EventStore, EventInstance } from '../reducers/event-store'
 
 export default class EventDragging {
 
   component: DateComponent
   dragging: FeaturefulElementDragging
   hitDragging: HitDragging
-  draggingSeg: Seg
-  mutation: EventMutation
+
+  // internal state
+  draggingSeg: Seg | null = null
+  eventInstance: EventInstance | null = null
+  relatedEvents: EventStore | null = null
+  validMutation: EventMutation | null = null
 
   constructor(component: DateComponent) {
     this.component = component
 
-    this.dragging = new FeaturefulElementDragging(component.el)
-    this.dragging.pointer.selector = '.fc-resizer'
-    this.dragging.touchScrollAllowed = false
+    let dragging = this.dragging = new FeaturefulElementDragging(component.el)
+    dragging.pointer.selector = '.fc-resizer'
+    dragging.touchScrollAllowed = false
 
     let hitDragging = this.hitDragging = new HitDragging(this.dragging, component)
-    hitDragging.emitter.on('pointerdown', this.onPointerDown)
-    hitDragging.emitter.on('dragstart', this.onDragStart)
-    hitDragging.emitter.on('hitupdate', this.onHitUpdate)
-    hitDragging.emitter.on('dragend', this.onDragEnd)
+    hitDragging.emitter.on('pointerdown', this.handlePointerDown)
+    hitDragging.emitter.on('dragstart', this.handleDragStart)
+    hitDragging.emitter.on('hitupdate', this.handleHitUpdate)
+    hitDragging.emitter.on('dragend', this.handleDragEnd)
   }
 
   destroy() {
     this.dragging.destroy()
   }
 
-  onPointerDown = (ev) => {
-    let seg = this.querySeg(ev)
-    let eventInstanceId = seg.eventRange.eventInstance.instanceId
+  handlePointerDown = (ev: PointerDragEvent) => {
+    let seg = this.querySeg(ev)!
+    let eventInstance = this.eventInstance = seg.eventRange!.eventInstance!
 
     // if touch, need to be working with a selected event
     this.dragging.setIgnoreMove(
-      !this.component.isValidSegDownEl(ev.origEvent.target) ||
-      (ev.isTouch && this.component.selectedEventInstanceId !== eventInstanceId)
+      !this.component.isValidSegDownEl(ev.origEvent.target as HTMLElement) ||
+      (ev.isTouch && this.component.selectedEventInstanceId !== eventInstance.instanceId)
     )
   }
 
-  onDragStart = (ev) => {
+  handleDragStart = (ev: PointerDragEvent) => {
+    let calendar = this.component.getCalendar()
+
+    this.relatedEvents = getRelatedEvents(
+      calendar.state.eventStore,
+      this.eventInstance!.instanceId
+    )
+
     this.draggingSeg = this.querySeg(ev)
   }
 
-  onHitUpdate = (hit: Hit | null, isFinal: boolean, ev: PointerDragEvent) => {
+  handleHitUpdate = (hit: Hit | null, isFinal: boolean, ev: PointerDragEvent) => {
     let calendar = this.component.getCalendar()
-    let { initialHit } = this.hitDragging
-    let eventInstance = this.draggingSeg.eventRange.eventInstance
-    let mutation = null
+    let relatedEvents = this.relatedEvents!
+    let initialHit = this.hitDragging.initialHit!
+    let eventInstance = this.eventInstance!
+    let mutation: EventMutation | null = null
 
     if (hit) {
       mutation = computeMutation(
@@ -64,55 +77,57 @@ export default class EventDragging {
     }
 
     if (mutation) {
-      let related = getRelatedEvents(calendar.state.eventStore, eventInstance.instanceId)
-      let mutatedRelated = applyMutationToAll(related, mutation, calendar)
+      let mutatedRelated = applyMutationToAll(relatedEvents, mutation, calendar)
 
       calendar.dispatch({
         type: 'SET_EVENT_RESIZE',
         eventResizeState: {
-          affectedEvents: related,
+          affectedEvents: relatedEvents,
           mutatedEvents: mutatedRelated,
+          isEvent: true,
           origSeg: this.draggingSeg
         }
       })
     } else {
-      calendar.dispatch({
-        type: 'CLEAR_EVENT_RESIZE'
-      })
-    }
-
-    if (mutation && isHitsEqual(initialHit, hit)) {
-      mutation = null
+      calendar.dispatch({ type: 'CLEAR_EVENT_RESIZE' })
     }
 
     if (!isFinal) {
-      this.mutation = mutation
+
+      if (mutation && isHitsEqual(initialHit, hit)) {
+        mutation = null
+      }
+
+      this.validMutation = mutation
     }
   }
 
-  onDragEnd = (ev) => {
+  handleDragEnd = (ev: PointerDragEvent) => {
     let calendar = this.component.getCalendar()
 
-    if (this.mutation) {
+    if (this.validMutation) {
       calendar.dispatch({
         type: 'MUTATE_EVENTS',
-        mutation: this.mutation,
-        instanceId: this.draggingSeg.eventRange.eventInstance.instanceId
+        mutation: this.validMutation,
+        instanceId: this.eventInstance!.instanceId
       })
-
-      this.mutation = null
     }
 
+    // reset all internal state
     this.draggingSeg = null
+    this.relatedEvents = null
+    this.validMutation = null
+
+    // okay to keep eventInstance around. useful to set it in handlePointerDown
   }
 
-  querySeg(ev: PointerDragEvent): Seg {
+  querySeg(ev: PointerDragEvent): Seg | null {
     return getElSeg(elementClosest(ev.subjectEl as HTMLElement, this.component.segSelector))
   }
 
 }
 
-function computeMutation(hit0: Hit, hit1: Hit, isFromStart: boolean, instanceRange: UnzonedRange): EventMutation {
+function computeMutation(hit0: Hit, hit1: Hit, isFromStart: boolean, instanceRange: UnzonedRange): EventMutation | null {
   let dateEnv = hit0.component.getDateEnv()
   let date0 = hit0.dateSpan.range.start
   let date1 = hit1.dateSpan.range.start
@@ -132,4 +147,6 @@ function computeMutation(hit0: Hit, hit1: Hit, isFromStart: boolean, instanceRan
       return { endDelta: delta, standardProps: { hasEnd: true } }
     }
   }
+
+  return null
 }

+ 2 - 2
src/reducers/event-interaction.ts

@@ -4,6 +4,6 @@ import { Seg } from '../component/DateComponent'
 export interface EventInteractionState {
   affectedEvents: EventStore
   mutatedEvents: EventStore
-  origSeg?: Seg
-  willCreateEvent?: boolean // doesn't apply to resize :(
+  isEvent: boolean
+  origSeg: Seg | null
 }

+ 2 - 2
src/reducers/event-mutation.ts

@@ -132,9 +132,9 @@ function applyMutationToInstance(
 
 export function diffDates(date0, date1, dateEnv, largeUnit) {
   if (largeUnit === 'year') {
-    return createDuration(dateEnv.diffWholeYears(date0, date1), 'year')
+    return createDuration(dateEnv.diffWholeYears(date0, date1), 'year')!
   } else if (largeUnit === 'month') {
-    return createDuration(dateEnv.diffWholeMonths(date0, date1), 'month')
+    return createDuration(dateEnv.diffWholeMonths(date0, date1), 'month')!
   } else {
     return diffDayAndTime(date0, date1) // returns a duration
   }

+ 4 - 0
src/reducers/event-store.ts

@@ -286,3 +286,7 @@ function parseDateInfo(rawEvent: EventInput, sourceId: string, calendar: Calenda
     forcedEndTzo: endMeta ? endMeta.forcedTimeZoneOffset : null
   }
 }
+
+export function createEmptyEventStore(): EventStore {
+  return { defs: {}, instances: {} }
+}