Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(237)

Side by Side Diff: chrome/browser/resources/enhanced_bookmark_manager/js/dnd.js

Issue 200063003: Remove enhanced bookmarks extension (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 cr.define('dnd', function() {
6 'use strict';
7
8 /** @const */ var BookmarkList = bmm.BookmarkList;
9 /** @const */ var ListItem = cr.ui.ListItem;
10 /** @const */ var TreeItem = cr.ui.TreeItem;
11
12 /**
13 * Enumeration of valid drop locations relative to an element. These are
14 * bit masks to allow combining multiple locations in a single value.
15 * @enum {number}
16 * @const
17 */
18 var DropPosition = {
19 NONE: 0,
20 ABOVE: 1,
21 ON: 2,
22 BELOW: 4
23 };
24
25 /**
26 * @type {Object} Drop information calculated in |handleDragOver|.
27 */
28 var dropDestination = null;
29
30 /**
31 * @type {number} Timer id used to help minimize flicker.
32 */
33 var removeDropIndicatorTimer;
34
35 /**
36 * The element that had a style applied it to indicate the drop location.
37 * This is used to easily remove the style when necessary.
38 * @type {Element}
39 */
40 var lastIndicatorElement;
41
42 /**
43 * The style that was applied to indicate the drop location.
44 * @type {string}
45 */
46 var lastIndicatorClassName;
47
48 var dropIndicator = {
49 /**
50 * Applies the drop indicator style on the target element and stores that
51 * information to easily remove the style in the future.
52 */
53 addDropIndicatorStyle: function(indicatorElement, position) {
54 var indicatorStyleName = position == DropPosition.ABOVE ? 'drag-above' :
55 position == DropPosition.BELOW ? 'drag-below' :
56 'drag-on';
57
58 lastIndicatorElement = indicatorElement;
59 lastIndicatorClassName = indicatorStyleName;
60
61 indicatorElement.classList.add(indicatorStyleName);
62 },
63
64 /**
65 * Clears the drop indicator style from the last element was the drop target
66 * so the drop indicator is no longer for that element.
67 */
68 removeDropIndicatorStyle: function() {
69 if (!lastIndicatorElement || !lastIndicatorClassName)
70 return;
71 lastIndicatorElement.classList.remove(lastIndicatorClassName);
72 lastIndicatorElement = null;
73 lastIndicatorClassName = null;
74 },
75
76 /**
77 * Displays the drop indicator on the current drop target to give the
78 * user feedback on where the drop will occur.
79 */
80 update: function(dropDest) {
81 window.clearTimeout(removeDropIndicatorTimer);
82
83 var indicatorElement = dropDest.element;
84 var position = dropDest.position;
85 if (dropDest.element instanceof BookmarkList) {
86 // For an empty bookmark list use 'drop-above' style.
87 position = DropPosition.ABOVE;
88 } else if (dropDest.element instanceof TreeItem) {
89 indicatorElement = indicatorElement.querySelector('.tree-row');
90 }
91 dropIndicator.removeDropIndicatorStyle();
92 dropIndicator.addDropIndicatorStyle(indicatorElement, position);
93 },
94
95 /**
96 * Stop displaying the drop indicator.
97 */
98 finish: function() {
99 // The use of a timeout is in order to reduce flickering as we move
100 // between valid drop targets.
101 window.clearTimeout(removeDropIndicatorTimer);
102 removeDropIndicatorTimer = window.setTimeout(function() {
103 dropIndicator.removeDropIndicatorStyle();
104 }, 100);
105 }
106 };
107
108 /**
109 * Delay for expanding folder when pointer hovers on folder in tree view in
110 * milliseconds.
111 * @type {number}
112 * @const
113 */
114 // TODO(yosin): EXPAND_FOLDER_DELAY should follow system settings. 400ms is
115 // taken from Windows default settings.
116 var EXPAND_FOLDER_DELAY = 400;
117
118 /**
119 * The timestamp when the mouse was over a folder during a drag operation.
120 * Used to open the hovered folder after a certain time.
121 * @type {number}
122 */
123 var lastHoverOnFolderTimeStamp = 0;
124
125 /**
126 * Expand a folder if the user has hovered for longer than the specified
127 * time during a drag action.
128 */
129 function updateAutoExpander(eventTimeStamp, overElement) {
130 // Expands a folder in tree view when pointer hovers on it longer than
131 // EXPAND_FOLDER_DELAY.
132 var hoverOnFolderTimeStamp = lastHoverOnFolderTimeStamp;
133 lastHoverOnFolderTimeStamp = 0;
134 if (hoverOnFolderTimeStamp) {
135 if (eventTimeStamp - hoverOnFolderTimeStamp >= EXPAND_FOLDER_DELAY)
136 overElement.expanded = true;
137 else
138 lastHoverOnFolderTimeStamp = hoverOnFolderTimeStamp;
139 } else if (overElement instanceof TreeItem &&
140 bmm.isFolder(overElement.bookmarkNode) &&
141 overElement.hasChildren &&
142 !overElement.expanded) {
143 lastHoverOnFolderTimeStamp = eventTimeStamp;
144 }
145 }
146
147 /**
148 * Stores the information abou the bookmark and folders being dragged.
149 * @type {Object}
150 */
151 var dragData = null;
152 var dragInfo = {
153 handleChromeDragEnter: function(newDragData) {
154 dragData = newDragData;
155 },
156 clearDragData: function() {
157 dragData = null;
158 },
159 isDragValid: function() {
160 return !!dragData;
161 },
162 isSameProfile: function() {
163 return dragData && dragData.sameProfile;
164 },
165 isDraggingFolders: function() {
166 return dragData && dragData.elements.some(function(node) {
167 return !node.url;
168 });
169 },
170 isDraggingBookmark: function(bookmarkId) {
171 return dragData && dragData.elements.some(function(node) {
172 return node.id == bookmarkId;
173 });
174 },
175 isDraggingChildBookmark: function(folderId) {
176 return dragData && dragData.elements.some(function(node) {
177 return node.parentId == folderId;
178 });
179 },
180 isDraggingFolderToDescendant: function(bookmarkNode) {
181 return dragData && dragData.elements.some(function(node) {
182 var dragFolder = bmm.treeLookup[node.id];
183 var dragFolderNode = dragFolder && dragFolder.bookmarkNode;
184 return dragFolderNode && bmm.contains(dragFolderNode, bookmarkNode);
185 });
186 }
187 };
188
189 /**
190 * External function to select folders or bookmarks after a drop action.
191 * @type {function}
192 */
193 var selectItemsAfterUserAction = null;
194
195 function getBookmarkElement(el) {
196 while (el && !el.bookmarkNode) {
197 el = el.parentNode;
198 }
199 return el;
200 }
201
202 // If we are over the list and the list is showing search result, we cannot
203 // drop.
204 function isOverSearch(overElement) {
205 return list.isSearch() && list.contains(overElement);
206 }
207
208 /**
209 * Determines the valid drop positions for the given target element.
210 * @param {!HTMLElement} overElement The element that we are currently
211 * dragging over.
212 * @return {DropPosition} An bit field enumeration of valid drop locations.
213 */
214 function calculateValidDropTargets(overElement) {
215 if (!dragInfo.isDragValid() || isOverSearch(overElement))
216 return DropPosition.NONE;
217
218 if (dragInfo.isSameProfile() &&
219 (dragInfo.isDraggingBookmark(overElement.bookmarkNode.id) ||
220 dragInfo.isDraggingFolderToDescendant(overElement.bookmarkNode))) {
221 return DropPosition.NONE;
222 }
223
224 var canDropInfo = calculateDropAboveBelow(overElement);
225 if (canDropOn(overElement))
226 canDropInfo |= DropPosition.ON;
227
228 return canDropInfo;
229 }
230
231 function calculateDropAboveBelow(overElement) {
232 if (overElement instanceof BookmarkList)
233 return DropPosition.NONE;
234
235 // We cannot drop between Bookmarks bar and Other bookmarks.
236 if (overElement.bookmarkNode.parentId == bmm.ROOT_ID)
237 return DropPosition.NONE;
238
239 var isOverTreeItem = overElement instanceof TreeItem;
240 var isOverExpandedTree = isOverTreeItem && overElement.expanded;
241 var isDraggingFolders = dragInfo.isDraggingFolders();
242
243 // We can only drop between items in the tree if we have any folders.
244 if (isOverTreeItem && !isDraggingFolders)
245 return DropPosition.NONE;
246
247 // When dragging from a different profile we do not need to consider
248 // conflicts between the dragged items and the drop target.
249 if (!dragInfo.isSameProfile()) {
250 // Don't allow dropping below an expanded tree item since it is confusing
251 // to the user anyway.
252 return isOverExpandedTree ? DropPosition.ABOVE :
253 (DropPosition.ABOVE | DropPosition.BELOW);
254 }
255
256 var resultPositions = DropPosition.NONE;
257
258 // Cannot drop above if the item above is already in the drag source.
259 var previousElem = overElement.previousElementSibling;
260 if (!previousElem || !dragInfo.isDraggingBookmark(previousElem.bookmarkId))
261 resultPositions |= DropPosition.ABOVE;
262
263 // Don't allow dropping below an expanded tree item since it is confusing
264 // to the user anyway.
265 if (isOverExpandedTree)
266 return resultPositions;
267
268 // Cannot drop below if the item below is already in the drag source.
269 var nextElement = overElement.nextElementSibling;
270 if (!nextElement || !dragInfo.isDraggingBookmark(nextElement.bookmarkId))
271 resultPositions |= DropPosition.BELOW;
272
273 return resultPositions;
274 }
275
276 /**
277 * Determine whether we can drop the dragged items on the drop target.
278 * @param {!HTMLElement} overElement The element that we are currently
279 * dragging over.
280 * @return {boolean} Whether we can drop the dragged items on the drop
281 * target.
282 */
283 function canDropOn(overElement) {
284 // We can only drop on a folder.
285 if (!bmm.isFolder(overElement.bookmarkNode))
286 return false;
287
288 if (!dragInfo.isSameProfile())
289 return true;
290
291 if (overElement instanceof BookmarkList) {
292 // We are trying to drop an item past the last item. This is
293 // only allowed if dragged item is different from the last item
294 // in the list.
295 var listItems = list.items;
296 var len = listItems.length;
297 if (!len || !dragInfo.isDraggingBookmark(listItems[len - 1].bookmarkId))
298 return true;
299 }
300
301 return !dragInfo.isDraggingChildBookmark(overElement.bookmarkNode.id);
302 }
303
304 /**
305 * Callback for the dragstart event.
306 * @param {Event} e The dragstart event.
307 */
308 function handleDragStart(e) {
309 // Determine the selected bookmarks.
310 var target = e.target;
311 var draggedNodes = [];
312 if (target instanceof ListItem) {
313 // Use selected items.
314 draggedNodes = target.parentNode.selectedItems;
315 } else if (target instanceof TreeItem) {
316 draggedNodes.push(target.bookmarkNode);
317 }
318
319 // We manage starting the drag by using the extension API.
320 e.preventDefault();
321
322 if (draggedNodes.length) {
323 // If we are dragging a single link, we can do the *Link* effect.
324 // Otherwise, we only allow copy and move.
325 e.dataTransfer.effectAllowed = draggedNodes.length == 1 &&
326 !bmm.isFolder(draggedNodes[0]) ? 'copyMoveLink' : 'copyMove';
327
328 chrome.bookmarkManagerPrivate.startDrag(draggedNodes.map(function(node) {
329 return node.id;
330 }));
331 }
332 }
333
334 function handleDragEnter(e) {
335 e.preventDefault();
336 }
337
338 /**
339 * Calback for the dragover event.
340 * @param {Event} e The dragover event.
341 */
342 function handleDragOver(e) {
343 // Allow DND on text inputs.
344 if (e.target.tagName != 'INPUT') {
345 // The default operation is to allow dropping links etc to do navigation.
346 // We never want to do that for the bookmark manager.
347 e.preventDefault();
348
349 // Set to none. This will get set to something if we can do the drop.
350 e.dataTransfer.dropEffect = 'none';
351 }
352
353 if (!dragInfo.isDragValid())
354 return;
355
356 var overElement = getBookmarkElement(e.target) ||
357 (e.target == list ? list : null);
358 if (!overElement)
359 return;
360
361 updateAutoExpander(e.timeStamp, overElement);
362
363 var canDropInfo = calculateValidDropTargets(overElement);
364 if (canDropInfo == DropPosition.NONE)
365 return;
366
367 // Now we know that we can drop. Determine if we will drop above, on or
368 // below based on mouse position etc.
369
370 dropDestination = calcDropPosition(e.clientY, overElement, canDropInfo);
371 if (!dropDestination) {
372 e.dataTransfer.dropEffect = 'none';
373 return;
374 }
375
376 e.dataTransfer.dropEffect = dragInfo.isSameProfile() ? 'move' : 'copy';
377 dropIndicator.update(dropDestination);
378 }
379
380 /**
381 * This function determines where the drop will occur relative to the element.
382 * @return {?Object} If no valid drop position is found, null, otherwise
383 * an object containing the following parameters:
384 * element - The target element that will receive the drop.
385 * position - A |DropPosition| relative to the |element|.
386 */
387 function calcDropPosition(elementClientY, overElement, canDropInfo) {
388 if (overElement instanceof BookmarkList) {
389 // Dropping on the BookmarkList either means dropping below the last
390 // bookmark element or on the list itself if it is empty.
391 var length = overElement.items.length;
392 if (length)
393 return {
394 element: overElement.getListItemByIndex(length - 1),
395 position: DropPosition.BELOW
396 };
397 return {element: overElement, position: DropPosition.ON};
398 }
399
400 var above = canDropInfo & DropPosition.ABOVE;
401 var below = canDropInfo & DropPosition.BELOW;
402 var on = canDropInfo & DropPosition.ON;
403 var rect = overElement.getBoundingClientRect();
404 var yRatio = (elementClientY - rect.top) / rect.height;
405
406 if (above && (yRatio <= .25 || yRatio <= .5 && (!below || !on)))
407 return {element: overElement, position: DropPosition.ABOVE};
408 if (below && (yRatio > .75 || yRatio > .5 && (!above || !on)))
409 return {element: overElement, position: DropPosition.BELOW};
410 if (on)
411 return {element: overElement, position: DropPosition.ON};
412 return null;
413 }
414
415 function calculateDropInfo(eventTarget, dropDestination) {
416 if (!dropDestination || !dragInfo.isDragValid())
417 return null;
418
419 var dropPos = dropDestination.position;
420 var relatedNode = dropDestination.element.bookmarkNode;
421 var dropInfoResult = {
422 selectTarget: null,
423 selectedTreeId: -1,
424 parentId: dropPos == DropPosition.ON ? relatedNode.id :
425 relatedNode.parentId,
426 index: -1,
427 relatedIndex: -1
428 };
429
430 // Try to find the index in the dataModel so we don't have to always keep
431 // the index for the list items up to date.
432 var overElement = getBookmarkElement(eventTarget);
433 if (overElement instanceof ListItem) {
434 dropInfoResult.relatedIndex =
435 overElement.parentNode.dataModel.indexOf(relatedNode);
436 dropInfoResult.selectTarget = list;
437 } else if (overElement instanceof BookmarkList) {
438 dropInfoResult.relatedIndex = overElement.dataModel.length - 1;
439 dropInfoResult.selectTarget = list;
440 } else {
441 // Tree
442 dropInfoResult.relatedIndex = relatedNode.index;
443 dropInfoResult.selectTarget = tree;
444 dropInfoResult.selectedTreeId =
445 tree.selectedItem ? tree.selectedItem.bookmarkId : null;
446 }
447
448 if (dropPos == DropPosition.ABOVE)
449 dropInfoResult.index = dropInfoResult.relatedIndex;
450 else if (dropPos == DropPosition.BELOW)
451 dropInfoResult.index = dropInfoResult.relatedIndex + 1;
452
453 return dropInfoResult;
454 }
455
456 function handleDragLeave(e) {
457 dropIndicator.finish();
458 }
459
460 function handleDrop(e) {
461 var dropInfo = calculateDropInfo(e.target, dropDestination);
462 if (dropInfo) {
463 selectItemsAfterUserAction(dropInfo.selectTarget,
464 dropInfo.selectedTreeId);
465 if (dropInfo.index != -1)
466 chrome.bookmarkManagerPrivate.drop(dropInfo.parentId, dropInfo.index);
467 else
468 chrome.bookmarkManagerPrivate.drop(dropInfo.parentId);
469
470 e.preventDefault();
471 }
472 dropDestination = null;
473 dropIndicator.finish();
474 }
475
476 function clearDragData() {
477 dragInfo.clearDragData();
478 dropDestination = null;
479 }
480
481 function init(selectItemsAfterUserActionFunction) {
482 function deferredClearData() {
483 setTimeout(clearDragData);
484 }
485
486 selectItemsAfterUserAction = selectItemsAfterUserActionFunction;
487
488 document.addEventListener('dragstart', handleDragStart);
489 document.addEventListener('dragenter', handleDragEnter);
490 document.addEventListener('dragover', handleDragOver);
491 document.addEventListener('dragleave', handleDragLeave);
492 document.addEventListener('drop', handleDrop);
493 document.addEventListener('dragend', deferredClearData);
494 document.addEventListener('mouseup', deferredClearData);
495
496 chrome.bookmarkManagerPrivate.onDragEnter.addListener(
497 dragInfo.handleChromeDragEnter);
498 chrome.bookmarkManagerPrivate.onDragLeave.addListener(deferredClearData);
499 chrome.bookmarkManagerPrivate.onDrop.addListener(deferredClearData);
500 }
501 return {init: init};
502 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698