EventDragging.ts 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import { default as DateComponent, Seg } from '../component/DateComponent'
  2. import { PointerDragEvent } from '../dnd/PointerDragging'
  3. import HitDragging, { isHitsEqual, Hit } from './HitDragging'
  4. import { EventMutation, diffDates, getRelatedEvents, applyMutationToAll } from '../reducers/event-mutation'
  5. import browserContext from '../common/browser-context'
  6. import { startOfDay } from '../datelib/marker'
  7. import { elementClosest } from '../util/dom-manip'
  8. import FeaturefulElementDragging from '../dnd/FeaturefulElementDragging'
  9. export default class EventDragging {
  10. component: DateComponent
  11. dragging: FeaturefulElementDragging
  12. hitDragging: HitDragging
  13. draggingSeg: Seg
  14. mutation: EventMutation
  15. constructor(component: DateComponent) {
  16. this.component = component
  17. this.dragging = new FeaturefulElementDragging(component.el)
  18. this.dragging.pointer.selector = '.fc-draggable'
  19. this.dragging.touchScrollAllowed = false
  20. let hitDragging = this.hitDragging = new HitDragging(this.dragging, browserContext.componentHash)
  21. hitDragging.useSubjectCenter = true
  22. hitDragging.emitter.on('pointerdown', this.onPointerDown)
  23. hitDragging.emitter.on('dragstart', this.onDragStart)
  24. hitDragging.emitter.on('hitover', this.onHitOver)
  25. hitDragging.emitter.on('hitout', this.onHitOut)
  26. hitDragging.emitter.on('dragend', this.onDragEnd)
  27. }
  28. destroy() {
  29. this.dragging.destroy()
  30. }
  31. onPointerDown = (ev: PointerDragEvent) => {
  32. let { dragging } = this
  33. dragging.delay = this.computeDragDelay(ev)
  34. // to prevent from cloning the sourceEl before it is selected
  35. dragging.setMirrorIsVisible(false)
  36. let origTarget = ev.origEvent.target as HTMLElement
  37. dragging.setIgnoreMove(
  38. !this.component.isValidSegInteraction(origTarget) ||
  39. Boolean(elementClosest(origTarget, '.fc-resizer'))
  40. )
  41. }
  42. computeDragDelay(ev: PointerDragEvent): number {
  43. if (ev.isTouch) {
  44. let seg = (ev.subjectEl as any).fcSeg
  45. let eventInstanceId = seg.eventRange.eventInstance.instanceId
  46. if (eventInstanceId !== this.component.selectedEventInstanceId) {
  47. return 1000 // TODO: use setting
  48. }
  49. }
  50. }
  51. onDragStart = (ev: PointerDragEvent) => {
  52. browserContext.unselectEvent()
  53. this.draggingSeg = (ev.subjectEl as any).fcSeg
  54. if (ev.isTouch) {
  55. let eventInstanceId = this.draggingSeg.eventRange.eventInstance.instanceId
  56. this.component.getCalendar().dispatch({
  57. type: 'SELECT_EVENT',
  58. eventInstanceId
  59. })
  60. browserContext.reportEventSelection(this.component)
  61. }
  62. }
  63. onHitOver = (hit) => {
  64. let { initialHit } = this.hitDragging
  65. let calendar = hit.component.getCalendar()
  66. let mutation = computeEventMutation(initialHit, hit)
  67. let related = getRelatedEvents(
  68. calendar.state.eventStore,
  69. this.draggingSeg.eventRange.eventInstance.instanceId
  70. )
  71. let mutatedRelated = applyMutationToAll(related, mutation, calendar)
  72. calendar.dispatch({
  73. type: 'SET_DRAG',
  74. dragState: {
  75. eventStore: mutatedRelated,
  76. origSeg: this.draggingSeg
  77. }
  78. })
  79. let { dragging } = this
  80. // render the mirror if no already-rendered helper
  81. // TODO: wish we could somehow wait for dispatch to guarantee render
  82. dragging.setMirrorIsVisible(
  83. !document.querySelector('.fc-helper')
  84. )
  85. let isSame = isHitsEqual(initialHit, hit)
  86. dragging.setMirrorNeedsRevert(isSame)
  87. if (!isSame) {
  88. this.mutation = mutation
  89. }
  90. }
  91. onHitOut = (hit) => { // TODO: onHitChange?
  92. this.mutation = null
  93. // we still want to notify calendar about invalid drag
  94. // because we want related events to stay hidden
  95. hit.component.getCalendar().dispatch({
  96. type: 'SET_DRAG',
  97. dragState: {
  98. eventStore: { defs: {}, instances: {} }, // TODO: better way to make empty event-store
  99. origSeg: this.draggingSeg
  100. }
  101. })
  102. let { dragging } = this
  103. dragging.setMirrorIsVisible(true)
  104. dragging.setMirrorNeedsRevert(true)
  105. }
  106. onDocumentPointerUp = (ev, wasTouchScroll) => {
  107. if (
  108. !this.mutation &&
  109. !wasTouchScroll &&
  110. // was the previously event-selected component?
  111. browserContext.eventSelectedComponent === this.component
  112. ) {
  113. this.component.getCalendar().dispatch({
  114. type: 'CLEAR_SELECTED_EVENT'
  115. })
  116. }
  117. }
  118. onDragEnd = () => {
  119. let { initialHit } = this.hitDragging
  120. let initialCalendar = initialHit.component.getCalendar()
  121. // TODO: what about across calendars?
  122. initialCalendar.dispatch({
  123. type: 'CLEAR_DRAG'
  124. })
  125. if (this.mutation) {
  126. initialCalendar.dispatch({
  127. type: 'MUTATE_EVENTS',
  128. mutation: this.mutation,
  129. instanceId: this.draggingSeg.eventRange.eventInstance.instanceId
  130. })
  131. }
  132. this.mutation = null
  133. this.draggingSeg = null
  134. }
  135. }
  136. function computeEventMutation(hit0: Hit, hit1: Hit): EventMutation {
  137. let dateSpan0 = hit0.dateSpan
  138. let dateSpan1 = hit1.dateSpan
  139. let date0 = dateSpan0.range.start
  140. let date1 = dateSpan1.range.start
  141. let standardProps = null
  142. if (dateSpan0.isAllDay !== dateSpan1.isAllDay) {
  143. standardProps = {
  144. hasEnd: false, // TODO: make this a setting
  145. isAllDay: dateSpan1.isAllDay
  146. }
  147. if (dateSpan1.isAllDay) {
  148. // means date1 is already start-of-day,
  149. // but date0 needs to be converted
  150. date0 = startOfDay(date0)
  151. }
  152. }
  153. let delta = diffDates(
  154. date0, date1,
  155. hit0.component.getDateEnv(),
  156. hit0.component === hit1.component ?
  157. hit0.component.largeUnit :
  158. null
  159. )
  160. return {
  161. startDelta: delta,
  162. endDelta: delta,
  163. standardProps
  164. }
  165. }