summaryrefslogtreecommitdiffstats
path: root/components/newtab/dropPreview.js
blob: fd7587a35e18fbe9ca44ed4e1b6c64ebf1efca0c (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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
#ifdef 0
/* 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/. */
#endif

/**
 * This singleton provides the ability to re-arrange the current grid to
 * indicate the transformation that results from dropping a cell at a certain
 * position.
 */
var gDropPreview = {
  /**
   * Rearranges the sites currently contained in the grid when a site would be
   * dropped onto the given cell.
   * @param aCell The drop target cell.
   * @return The re-arranged array of sites.
   */
  rearrange: function DropPreview_rearrange(aCell) {
    let sites = gGrid.sites;

    // Insert the dragged site into the current grid.
    this._insertDraggedSite(sites, aCell);

    // After the new site has been inserted we need to correct the positions
    // of all pinned tabs that have been moved around.
    this._repositionPinnedSites(sites, aCell);

    return sites;
  },

  /**
   * Inserts the currently dragged site into the given array of sites.
   * @param aSites The array of sites to insert into.
   * @param aCell The drop target cell.
   */
  _insertDraggedSite: function DropPreview_insertDraggedSite(aSites, aCell) {
    let dropIndex = aCell.index;
    let draggedSite = gDrag.draggedSite;

    // We're currently dragging a site.
    if (draggedSite) {
      let dragCell = draggedSite.cell;
      let dragIndex = dragCell.index;

      // Move the dragged site into its new position.
      if (dragIndex != dropIndex) {
        aSites.splice(dragIndex, 1);
        aSites.splice(dropIndex, 0, draggedSite);
      }
    // We're handling an external drag item.
    } else {
      aSites.splice(dropIndex, 0, null);
    }
  },

  /**
   * Correct the position of all pinned sites that might have been moved to
   * different positions after the dragged site has been inserted.
   * @param aSites The array of sites containing the dragged site.
   * @param aCell The drop target cell.
   */
  _repositionPinnedSites:
    function DropPreview_repositionPinnedSites(aSites, aCell) {

    // Collect all pinned sites.
    let pinnedSites = this._filterPinnedSites(aSites, aCell);

    // Correct pinned site positions.
    pinnedSites.forEach(function (aSite) {
      aSites[aSites.indexOf(aSite)] = aSites[aSite.cell.index];
      aSites[aSite.cell.index] = aSite;
    }, this);

    // There might be a pinned cell that got pushed out of the grid, try to
    // sneak it in by removing a lower-priority cell.
    if (this._hasOverflowedPinnedSite(aSites, aCell))
      this._repositionOverflowedPinnedSite(aSites, aCell);
  },

  /**
   * Filter pinned sites out of the grid that are still on their old positions
   * and have not moved.
   * @param aSites The array of sites to filter.
   * @param aCell The drop target cell.
   * @return The filtered array of sites.
   */
  _filterPinnedSites: function DropPreview_filterPinnedSites(aSites, aCell) {
    let draggedSite = gDrag.draggedSite;

    // When dropping on a cell that contains a pinned site make sure that all
    // pinned cells surrounding the drop target are moved as well.
    let range = this._getPinnedRange(aCell);

    return aSites.filter(function (aSite, aIndex) {
      // The site must be valid, pinned and not the dragged site.
      if (!aSite || aSite == draggedSite || !aSite.isPinned())
        return false;

      let index = aSite.cell.index;

      // If it's not in the 'pinned range' it's a valid pinned site.
      return (index > range.end || index < range.start);
    });
  },

  /**
   * Determines the range of pinned sites surrounding the drop target cell.
   * @param aCell The drop target cell.
   * @return The range of pinned cells.
   */
  _getPinnedRange: function DropPreview_getPinnedRange(aCell) {
    let dropIndex = aCell.index;
    let range = {start: dropIndex, end: dropIndex};

    // We need a pinned range only when dropping on a pinned site.
    if (aCell.containsPinnedSite()) {
      let links = gPinnedLinks.links;

      // Find all previous siblings of the drop target that are pinned as well.
      while (range.start && links[range.start - 1])
        range.start--;

      let maxEnd = links.length - 1;

      // Find all next siblings of the drop target that are pinned as well.
      while (range.end < maxEnd && links[range.end + 1])
        range.end++;
    }

    return range;
  },

  /**
   * Checks if the given array of sites contains a pinned site that has
   * been pushed out of the grid.
   * @param aSites The array of sites to check.
   * @param aCell The drop target cell.
   * @return Whether there is an overflowed pinned cell.
   */
  _hasOverflowedPinnedSite:
    function DropPreview_hasOverflowedPinnedSite(aSites, aCell) {

    // If the drop target isn't pinned there's no way a pinned site has been
    // pushed out of the grid so we can just exit here.
    if (!aCell.containsPinnedSite())
      return false;

    let cells = gGrid.cells;

    // No cells have been pushed out of the grid, nothing to do here.
    if (aSites.length <= cells.length)
      return false;

    let overflowedSite = aSites[cells.length];

    // Nothing to do if the site that got pushed out of the grid is not pinned.
    return (overflowedSite && overflowedSite.isPinned());
  },

  /**
   * We have a overflowed pinned site that we need to re-position so that it's
   * visible again. We try to find a lower-priority cell (empty or containing
   * an unpinned site) that we can move it to.
   * @param aSites The array of sites.
   * @param aCell The drop target cell.
   */
  _repositionOverflowedPinnedSite:
    function DropPreview_repositionOverflowedPinnedSite(aSites, aCell) {

    // Try to find a lower-priority cell (empty or containing an unpinned site).
    let index = this._indexOfLowerPrioritySite(aSites, aCell);

    if (index > -1) {
      let cells = gGrid.cells;
      let dropIndex = aCell.index;

      // Move all pinned cells to their new positions to let the overflowed
      // site fit into the grid.
      for (let i = index + 1, lastPosition = index; i < aSites.length; i++) {
        if (i != dropIndex) {
          aSites[lastPosition] = aSites[i];
          lastPosition = i;
        }
      }

      // Finally, remove the overflowed site from its previous position.
      aSites.splice(cells.length, 1);
    }
  },

  /**
   * Finds the index of the last cell that is empty or contains an unpinned
   * site. These are considered to be of a lower priority.
   * @param aSites The array of sites.
   * @param aCell The drop target cell.
   * @return The cell's index.
   */
  _indexOfLowerPrioritySite:
    function DropPreview_indexOfLowerPrioritySite(aSites, aCell) {

    let cells = gGrid.cells;
    let dropIndex = aCell.index;

    // Search (beginning with the last site in the grid) for a site that is
    // empty or unpinned (an thus lower-priority) and can be pushed out of the
    // grid instead of the pinned site.
    for (let i = cells.length - 1; i >= 0; i--) {
      // The cell that is our drop target is not a good choice.
      if (i == dropIndex)
        continue;

      let site = aSites[i];

      // We can use the cell only if it's empty or the site is un-pinned.
      if (!site || !site.isPinned())
        return i;
    }

    return -1;
  }
};