summaryrefslogtreecommitdiffstats
path: root/comm/calendar/base/content/widgets/calendar-dnd-widgets.js
blob: f0d75745b609d4ae31f8795dd642182a3b314dab (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* globals currentView MozElements MozXULElement */

"use strict";

// Wrap in a block to prevent leaking to window scope.
{
  var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm");

  /**
   * An abstract class to handle drag on drop for calendar.
   *
   * @abstract
   */
  class CalendarDnDContainer extends MozXULElement {
    constructor() {
      super();
      this.addEventListener("dragstart", this.onDragStart);
      this.addEventListener("dragover", this.onDragOver);
      this.addEventListener("dragenter", this.onDragEnter);
      this.addEventListener("drop", this.onDrop);
      this.addEventListener("dragend", this.onDragEnd);
      this.mCalendarView = null;
    }

    connectedCallback() {
      if (this.delayConnectedCallback()) {
        return;
      }
      this.hasConnected = true;
    }

    /**
     * The ViewController that supports the interface 'calICalendarView'.
     *
     * @returns {calICalendarView}
     */
    get calendarView() {
      return this.mCalendarView;
    }

    set calendarView(val) {
      this.mCalendarView = val;
    }

    /**
     * Method to add individual code e.g to set up the new item during 'ondrop'.
     */
    onDropItem(aItem) {
      // method that may be overridden by derived bindings...
    }

    /**
     * Adds the dropshadows to the children of the binding.
     * The dropshadows are added at the first position of the children.
     */
    addDropShadows() {
      let offset = this.calendarView.mShadowOffset;
      let shadowStartDate = this.date.clone();
      shadowStartDate.addDuration(offset);
      this.calendarView.mDropShadows = [];
      for (let i = 0; i < this.calendarView.mDropShadowsLength; i++) {
        let box = this.calendarView.findDayBoxForDate(shadowStartDate);
        if (box) {
          box.setDropShadow(true);
          this.calendarView.mDropShadows.push(box);
        }
        shadowStartDate.day += 1;
      }
    }

    /**
     * Removes all dropShadows from the binding.
     * Dropshadows are recognized as such by carrying an attribute "dropshadow".
     */
    removeDropShadows() {
      // method that may be overwritten by derived bindings...
      if (this.calendarView.mDropShadows) {
        for (let box of this.calendarView.mDropShadows) {
          box.setDropShadow(false);
        }
      }
      this.calendarView.mDropShadows = null;
    }

    /**
     * By setting the attribute "dropbox" to "true" or "false" the
     * dropshadows are added or removed.
     */
    setAttribute(aAttr, aVal) {
      if (aAttr == "dropbox") {
        let session = cal.dragService.getCurrentSession();
        if (session) {
          session.canDrop = true;
          // no shadows when dragging in the initial position
          if (aVal == "true" && !this.contains(session.sourceNode)) {
            this.addDropShadows();
          } else {
            this.removeDropShadows();
          }
        }
      }
      return XULElement.prototype.setAttribute.call(this, aAttr, aVal);
    }

    onDragStart(event) {
      let draggedDOMNode = document.monthDragEvent || event.target;
      if (!draggedDOMNode?.occurrence || !this.contains(draggedDOMNode)) {
        return;
      }
      let item = draggedDOMNode.occurrence.clone();
      let beginMoveDate = draggedDOMNode.mParentBox.date;
      let itemStartDate = (item.startDate || item.entryDate || item.dueDate).getInTimezone(
        this.calendarView.mTimezone
      );
      let itemEndDate = (item.endDate || item.dueDate || item.entryDate).getInTimezone(
        this.calendarView.mTimezone
      );
      let oneMoreDay = itemEndDate.hour > 0 || itemEndDate.minute > 0;
      itemStartDate.isDate = true;
      itemEndDate.isDate = true;
      let offsetDuration = itemStartDate.subtractDate(beginMoveDate);
      let lenDuration = itemEndDate.subtractDate(itemStartDate);
      let len = lenDuration.weeks * 7 + lenDuration.days;

      this.calendarView.mShadowOffset = offsetDuration;
      this.calendarView.mDropShadowsLength = oneMoreDay ? len + 1 : len;
    }

    onDragOver(event) {
      let session = cal.dragService.getCurrentSession();
      if (!session?.sourceNode?.sourceObject) {
        // No source item? Then this is not for us.
        return;
      }

      // We handled the event.
      event.preventDefault();
    }

    onDragEnter(event) {
      let session = cal.dragService.getCurrentSession();
      if (!session?.sourceNode?.sourceObject) {
        // No source item? Then this is not for us.
        return;
      }

      // We can drop now, tell the drag service.
      event.preventDefault();

      if (!this.hasAttribute("dropbox") || this.getAttribute("dropbox") == "false") {
        // As it turned out it was not possible to remove the remaining dropshadows
        // at the "dragleave" event, majorly because it was not reliably
        // fired.
        // So we have to remove them at the currentView(). The restriction of course is
        // that these containers so far may not be used for drag and drop from/to e.g.
        // the today-pane.
        currentView().removeDropShadows();
      }
      this.setAttribute("dropbox", "true");
    }

    onDrop(event) {
      let session = cal.dragService.getCurrentSession();
      let item = session?.sourceNode?.sourceObject;
      if (!item) {
        // No source node? Not our drag.
        return;
      }
      this.setAttribute("dropbox", "false");
      let newItem = this.onDropItem(item).clone();
      let newStart = newItem.startDate || newItem.entryDate || newItem.dueDate;
      let newEnd = newItem.endDate || newItem.dueDate || newItem.entryDate;
      let offset = this.calendarView.mShadowOffset;
      newStart.addDuration(offset);
      newEnd.addDuration(offset);
      this.calendarView.controller.modifyOccurrence(item, newStart, newEnd);

      // We handled the event.
      event.stopPropagation();
    }

    onDragEnd(event) {
      currentView().removeDropShadows();
    }
  }

  MozElements.CalendarDnDContainer = CalendarDnDContainer;
}