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

Side by Side Diff: chrome/browser/resources/file_manager/foreground/js/file_transfer_controller.js

Issue 247123002: Move Files.app files to ui/file_manager (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fix the test failure on non-chromeos Created 6 years, 8 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 (c) 2012 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 'use strict';
6
7 /**
8 * Global (placed in the window object) variable name to hold internal
9 * file dragging information. Needed to show visual feedback while dragging
10 * since DataTransfer object is in protected state. Reachable from other
11 * file manager instances.
12 */
13 var DRAG_AND_DROP_GLOBAL_DATA = '__drag_and_drop_global_data';
14
15 /**
16 * @param {HTMLDocument} doc Owning document.
17 * @param {FileOperationManager} fileOperationManager File operation manager
18 * instance.
19 * @param {MetadataCache} metadataCache Metadata cache service.
20 * @param {DirectoryModel} directoryModel Directory model instance.
21 * @param {VolumeManagerWrapper} volumeManager Volume manager instance.
22 * @param {MultiProfileShareDialog} multiProfileShareDialog Share dialog to be
23 * used to share files from another profile.
24 * @constructor
25 */
26 function FileTransferController(doc,
27 fileOperationManager,
28 metadataCache,
29 directoryModel,
30 volumeManager,
31 multiProfileShareDialog) {
32 this.document_ = doc;
33 this.fileOperationManager_ = fileOperationManager;
34 this.metadataCache_ = metadataCache;
35 this.directoryModel_ = directoryModel;
36 this.volumeManager_ = volumeManager;
37 this.multiProfileShareDialog_ = multiProfileShareDialog;
38
39 this.directoryModel_.getFileList().addEventListener(
40 'change', function(event) {
41 if (this.directoryModel_.getFileListSelection().
42 getIndexSelected(event.index)) {
43 this.onSelectionChanged_();
44 }
45 }.bind(this));
46 this.directoryModel_.getFileListSelection().addEventListener('change',
47 this.onSelectionChanged_.bind(this));
48
49 /**
50 * DOM element to represent selected file in drag operation. Used if only
51 * one element is selected.
52 * @type {HTMLElement}
53 * @private
54 */
55 this.preloadedThumbnailImageNode_ = null;
56
57 /**
58 * File objects for selected files.
59 *
60 * @type {Array.<File>}
61 * @private
62 */
63 this.selectedFileObjects_ = [];
64
65 /**
66 * Drag selector.
67 * @type {DragSelector}
68 * @private
69 */
70 this.dragSelector_ = new DragSelector();
71
72 /**
73 * Whether a user is touching the device or not.
74 * @type {boolean}
75 * @private
76 */
77 this.touching_ = false;
78 }
79
80 FileTransferController.prototype = {
81 __proto__: cr.EventTarget.prototype,
82
83 /**
84 * @this {FileTransferController}
85 * @param {cr.ui.List} list Items in the list will be draggable.
86 */
87 attachDragSource: function(list) {
88 list.style.webkitUserDrag = 'element';
89 list.addEventListener('dragstart', this.onDragStart_.bind(this, list));
90 list.addEventListener('dragend', this.onDragEnd_.bind(this, list));
91 list.addEventListener('touchstart', this.onTouchStart_.bind(this));
92 list.addEventListener('touchend', this.onTouchEnd_.bind(this));
93 },
94
95 /**
96 * @this {FileTransferController}
97 * @param {cr.ui.List} list List itself and its directory items will could
98 * be drop target.
99 * @param {boolean=} opt_onlyIntoDirectories If true only directory list
100 * items could be drop targets. Otherwise any other place of the list
101 * accetps files (putting it into the current directory).
102 */
103 attachFileListDropTarget: function(list, opt_onlyIntoDirectories) {
104 list.addEventListener('dragover', this.onDragOver_.bind(this,
105 !!opt_onlyIntoDirectories, list));
106 list.addEventListener('dragenter',
107 this.onDragEnterFileList_.bind(this, list));
108 list.addEventListener('dragleave', this.onDragLeave_.bind(this, list));
109 list.addEventListener('drop',
110 this.onDrop_.bind(this, !!opt_onlyIntoDirectories));
111 },
112
113 /**
114 * @this {FileTransferController}
115 * @param {DirectoryTree} tree Its sub items will could be drop target.
116 */
117 attachTreeDropTarget: function(tree) {
118 tree.addEventListener('dragover', this.onDragOver_.bind(this, true, tree));
119 tree.addEventListener('dragenter', this.onDragEnterTree_.bind(this, tree));
120 tree.addEventListener('dragleave', this.onDragLeave_.bind(this, tree));
121 tree.addEventListener('drop', this.onDrop_.bind(this, true));
122 },
123
124 /**
125 * @this {FileTransferController}
126 * @param {NavigationList} tree Its sub items will could be drop target.
127 */
128 attachNavigationListDropTarget: function(list) {
129 list.addEventListener('dragover',
130 this.onDragOver_.bind(this, true /* onlyIntoDirectories */, list));
131 list.addEventListener('dragenter',
132 this.onDragEnterVolumesList_.bind(this, list));
133 list.addEventListener('dragleave', this.onDragLeave_.bind(this, list));
134 list.addEventListener('drop',
135 this.onDrop_.bind(this, true /* onlyIntoDirectories */));
136 },
137
138 /**
139 * Attach handlers of copy, cut and paste operations to the document.
140 *
141 * @this {FileTransferController}
142 */
143 attachCopyPasteHandlers: function() {
144 this.document_.addEventListener('beforecopy',
145 this.onBeforeCopy_.bind(this));
146 this.document_.addEventListener('copy',
147 this.onCopy_.bind(this));
148 this.document_.addEventListener('beforecut',
149 this.onBeforeCut_.bind(this));
150 this.document_.addEventListener('cut',
151 this.onCut_.bind(this));
152 this.document_.addEventListener('beforepaste',
153 this.onBeforePaste_.bind(this));
154 this.document_.addEventListener('paste',
155 this.onPaste_.bind(this));
156 this.copyCommand_ = this.document_.querySelector('command#copy');
157 },
158
159 /**
160 * Write the current selection to system clipboard.
161 *
162 * @this {FileTransferController}
163 * @param {DataTransfer} dataTransfer DataTransfer from the event.
164 * @param {string} effectAllowed Value must be valid for the
165 * |dataTransfer.effectAllowed| property ('move', 'copy', 'copyMove').
166 */
167 cutOrCopy_: function(dataTransfer, effectAllowed) {
168 // Existence of the volumeInfo is checked in canXXX methods.
169 var volumeInfo = this.volumeManager_.getVolumeInfo(
170 this.currentDirectoryContentEntry);
171 // Tag to check it's filemanager data.
172 dataTransfer.setData('fs/tag', 'filemanager-data');
173 dataTransfer.setData('fs/sourceRootURL',
174 volumeInfo.fileSystem.root.toURL());
175 var sourceURLs = util.entriesToURLs(this.selectedEntries_);
176 dataTransfer.setData('fs/sources', sourceURLs.join('\n'));
177 dataTransfer.effectAllowed = effectAllowed;
178 dataTransfer.setData('fs/effectallowed', effectAllowed);
179 dataTransfer.setData('fs/missingFileContents',
180 !this.isAllSelectedFilesAvailable_());
181
182 for (var i = 0; i < this.selectedFileObjects_.length; i++) {
183 dataTransfer.items.add(this.selectedFileObjects_[i]);
184 }
185 },
186
187 /**
188 * @this {FileTransferController}
189 * @return {Object.<string, string>} Drag and drop global data object.
190 */
191 getDragAndDropGlobalData_: function() {
192 if (window[DRAG_AND_DROP_GLOBAL_DATA])
193 return window[DRAG_AND_DROP_GLOBAL_DATA];
194
195 // Dragging from other tabs/windows.
196 var views = chrome && chrome.extension ? chrome.extension.getViews() : [];
197 for (var i = 0; i < views.length; i++) {
198 if (views[i][DRAG_AND_DROP_GLOBAL_DATA])
199 return views[i][DRAG_AND_DROP_GLOBAL_DATA];
200 }
201 return null;
202 },
203
204 /**
205 * Extracts source root URL from the |dataTransfer| object.
206 *
207 * @this {FileTransferController}
208 * @param {DataTransfer} dataTransfer DataTransfer object from the event.
209 * @return {string} URL or an empty string (if unknown).
210 */
211 getSourceRootURL_: function(dataTransfer) {
212 var sourceRootURL = dataTransfer.getData('fs/sourceRootURL');
213 if (sourceRootURL)
214 return sourceRootURL;
215
216 // |dataTransfer| in protected mode.
217 var globalData = this.getDragAndDropGlobalData_();
218 if (globalData)
219 return globalData.sourceRootURL;
220
221 // Unknown source.
222 return '';
223 },
224
225 /**
226 * @this {FileTransferController}
227 * @param {DataTransfer} dataTransfer DataTransfer object from the event.
228 * @return {boolean} Returns true when missing some file contents.
229 */
230 isMissingFileContents_: function(dataTransfer) {
231 var data = dataTransfer.getData('fs/missingFileContents');
232 if (!data) {
233 // |dataTransfer| in protected mode.
234 var globalData = this.getDragAndDropGlobalData_();
235 if (globalData)
236 data = globalData.missingFileContents;
237 }
238 return data === 'true';
239 },
240
241 /**
242 * Obtains entries that need to share with me.
243 * The method also observers child entries of the given entries.
244 * @param {Array.<Entries>} entries Entries.
245 * @return {Promise} Promise to be fulfilled with the entries that need to
246 * share.
247 */
248 getMultiProfileShareEntries_: function(entries) {
249 // Utility function to concat arrays.
250 var concatArrays = function(arrays) {
251 return Array.prototype.concat.apply([], arrays);
252 };
253
254 // Call processEntry for each item of entries.
255 var processEntries = function(entries) {
256 return Promise.all(entries.map(processEntry)).then(concatArrays);
257 };
258
259 // Check entry type and do particular instructions.
260 var processEntry = function(entry) {
261 if (entry.isFile) {
262 // The entry is file. Obtain metadata.
263 return new Promise(function(callback) {
264 chrome.fileBrowserPrivate.getDriveEntryProperties(entry.toURL(),
265 callback);
266 }).
267 then(function(metadata) {
268 if (metadata &&
269 metadata.isHosted &&
270 !metadata.sharedWithMe) {
271 return [entry];
272 } else {
273 return [];
274 }
275 });
276 } else {
277 // The entry is directory. Check child entries.
278 return readEntries(entry.createReader());
279 }
280 }.bind(this);
281
282 // Read entries from DirectoryReader and call processEntries for the chunk
283 // of entries.
284 var readEntries = function(reader) {
285 return new Promise(reader.readEntries.bind(reader)).then(
286 function(entries) {
287 if (entries.length > 0) {
288 return Promise.all(
289 [processEntries(entries), readEntries(reader)]).
290 then(concatArrays);
291 } else {
292 return [];
293 }
294 },
295 function(error) {
296 console.warn(
297 'Error happens while reading directory.', error);
298 return [];
299 });
300 }.bind(this);
301
302 // Filter entries that is owned by the current user, and call
303 // processEntries.
304 return processEntries(entries.filter(function(entry) {
305 // If the volumeInfo is found, the entry belongs to the current user.
306 return !this.volumeManager_.getVolumeInfo(entry);
307 }.bind(this)));
308 },
309
310 /**
311 * Queue up a file copy operation based on the current system clipboard.
312 *
313 * @this {FileTransferController}
314 * @param {DataTransfer} dataTransfer System data transfer object.
315 * @param {DirectoryEntry=} opt_destinationEntry Paste destination.
316 * @param {string=} opt_effect Desired drop/paste effect. Could be
317 * 'move'|'copy' (default is copy). Ignored if conflicts with
318 * |dataTransfer.effectAllowed|.
319 * @return {string} Either "copy" or "move".
320 */
321 paste: function(dataTransfer, opt_destinationEntry, opt_effect) {
322 var sourceURLs = dataTransfer.getData('fs/sources') ?
323 dataTransfer.getData('fs/sources').split('\n') : [];
324 // effectAllowed set in copy/paste handlers stay uninitialized. DnD handlers
325 // work fine.
326 var effectAllowed = dataTransfer.effectAllowed !== 'uninitialized' ?
327 dataTransfer.effectAllowed : dataTransfer.getData('fs/effectallowed');
328 var toMove = effectAllowed === 'move' ||
329 (effectAllowed === 'copyMove' && opt_effect === 'move');
330 var destinationEntry =
331 opt_destinationEntry || this.currentDirectoryContentEntry;
332 var entries;
333 var failureUrls;
334
335 util.URLsToEntries(sourceURLs).
336 then(function(result) {
337 entries = result.entries;
338 failureUrls = result.failureUrls;
339 // Check if cross share is needed or not.
340 return this.getMultiProfileShareEntries_(entries);
341 }.bind(this)).
342 then(function(shareEntries) {
343 if (shareEntries.length === 0)
344 return;
345 return this.multiProfileShareDialog_.show(shareEntries.length > 1).
346 then(function(dialogResult) {
347 if (dialogResult === 'cancel')
348 return Promise.reject('ABORT');
349 // Do cross share.
350 // TODO(hirono): Make the loop cancellable.
351 var requestDriveShare = function(index) {
352 if (index >= shareEntries.length)
353 return Promise.cast();
354 return new Promise(function(fulfill) {
355 chrome.fileBrowserPrivate.requestDriveShare(
356 shareEntries[index].toURL(),
357 dialogResult,
358 function() {
359 // TODO(hirono): Check chrome.runtime.lastError here.
360 fulfill();
361 });
362 }).then(requestDriveShare.bind(null, index + 1));
363 };
364 return requestDriveShare(0);
365 });
366 }.bind(this)).
367 then(function() {
368 // Start the pasting operation.
369 this.fileOperationManager_.paste(
370 entries, destinationEntry, toMove);
371
372 // Publish events for failureUrls.
373 for (var i = 0; i < failureUrls.length; i++) {
374 var fileName =
375 decodeURIComponent(failureUrls[i].replace(/^.+\//, ''));
376 var event = new Event('source-not-found');
377 event.fileName = fileName;
378 event.progressType =
379 toMove ? ProgressItemType.MOVE : ProgressItemType.COPY;
380 this.dispatchEvent(event);
381 }
382 }.bind(this)).
383 catch(function(error) {
384 if (error !== 'ABORT')
385 console.error(error.stack ? error.stack : error);
386 });
387 return toMove ? 'move' : 'copy';
388 },
389
390 /**
391 * Preloads an image thumbnail for the specified file entry.
392 *
393 * @this {FileTransferController}
394 * @param {Entry} entry Entry to preload a thumbnail for.
395 */
396 preloadThumbnailImage_: function(entry) {
397 var metadataTypes = 'thumbnail|filesystem';
398 var thumbnailContainer = this.document_.createElement('div');
399 this.preloadedThumbnailImageNode_ = thumbnailContainer;
400 this.preloadedThumbnailImageNode_.className = 'img-container';
401 this.metadataCache_.get(
402 entry,
403 metadataTypes,
404 function(metadata) {
405 new ThumbnailLoader(entry,
406 ThumbnailLoader.LoaderType.IMAGE,
407 metadata).
408 load(thumbnailContainer,
409 ThumbnailLoader.FillMode.FILL);
410 }.bind(this));
411 },
412
413 /**
414 * Renders a drag-and-drop thumbnail.
415 *
416 * @this {FileTransferController}
417 * @return {HTMLElement} Element containing the thumbnail.
418 */
419 renderThumbnail_: function() {
420 var length = this.selectedEntries_.length;
421
422 var container = this.document_.querySelector('#drag-container');
423 var contents = this.document_.createElement('div');
424 contents.className = 'drag-contents';
425 container.appendChild(contents);
426
427 var thumbnailImage;
428 if (this.preloadedThumbnailImageNode_)
429 thumbnailImage = this.preloadedThumbnailImageNode_.querySelector('img');
430
431 // Option 1. Multiple selection, render only a label.
432 if (length > 1) {
433 var label = this.document_.createElement('div');
434 label.className = 'label';
435 label.textContent = strf('DRAGGING_MULTIPLE_ITEMS', length);
436 contents.appendChild(label);
437 return container;
438 }
439
440 // Option 2. Thumbnail image available, then render it without
441 // a label.
442 if (thumbnailImage) {
443 thumbnailImage.classList.add('drag-thumbnail');
444 contents.classList.add('for-image');
445 contents.appendChild(this.preloadedThumbnailImageNode_);
446 return container;
447 }
448
449 // Option 3. Thumbnail not available. Render an icon and a label.
450 var entry = this.selectedEntries_[0];
451 var icon = this.document_.createElement('div');
452 icon.className = 'detail-icon';
453 icon.setAttribute('file-type-icon', FileType.getIcon(entry));
454 contents.appendChild(icon);
455 var label = this.document_.createElement('div');
456 label.className = 'label';
457 label.textContent = entry.name;
458 contents.appendChild(label);
459 return container;
460 },
461
462 /**
463 * @this {FileTransferController}
464 * @param {cr.ui.List} list Drop target list
465 * @param {Event} event A dragstart event of DOM.
466 */
467 onDragStart_: function(list, event) {
468 // If a user is touching, Files.app does not receive drag operations.
469 if (this.touching_) {
470 event.preventDefault();
471 return;
472 }
473
474 // Check if a drag selection should be initiated or not.
475 if (list.shouldStartDragSelection(event)) {
476 this.dragSelector_.startDragSelection(list, event);
477 return;
478 }
479
480 // Nothing selected.
481 if (!this.selectedEntries_.length) {
482 event.preventDefault();
483 return;
484 }
485
486 var dt = event.dataTransfer;
487 var canCopy = this.canCopyOrDrag_(dt);
488 var canCut = this.canCutOrDrag_(dt);
489 if (canCopy || canCut) {
490 if (canCopy && canCut) {
491 this.cutOrCopy_(dt, 'copyMove');
492 } else if (canCopy) {
493 this.cutOrCopy_(dt, 'copy');
494 } else {
495 this.cutOrCopy_(dt, 'move');
496 }
497 } else {
498 event.preventDefault();
499 return;
500 }
501
502 var dragThumbnail = this.renderThumbnail_();
503 dt.setDragImage(dragThumbnail, 1000, 1000);
504
505 window[DRAG_AND_DROP_GLOBAL_DATA] = {
506 sourceRootURL: dt.getData('fs/sourceRootURL'),
507 missingFileContents: dt.getData('fs/missingFileContents'),
508 };
509 },
510
511 /**
512 * @this {FileTransferController}
513 * @param {cr.ui.List} list Drop target list.
514 * @param {Event} event A dragend event of DOM.
515 */
516 onDragEnd_: function(list, event) {
517 var container = this.document_.querySelector('#drag-container');
518 container.textContent = '';
519 this.clearDropTarget_();
520 delete window[DRAG_AND_DROP_GLOBAL_DATA];
521 },
522
523 /**
524 * @this {FileTransferController}
525 * @param {boolean} onlyIntoDirectories True if the drag is only into
526 * directories.
527 * @param {cr.ui.List} list Drop target list.
528 * @param {Event} event A dragover event of DOM.
529 */
530 onDragOver_: function(onlyIntoDirectories, list, event) {
531 event.preventDefault();
532 var entry = this.destinationEntry_ ||
533 (!onlyIntoDirectories && this.currentDirectoryContentEntry);
534 event.dataTransfer.dropEffect = this.selectDropEffect_(event, entry);
535 event.preventDefault();
536 },
537
538 /**
539 * @this {FileTransferController}
540 * @param {cr.ui.List} list Drop target list.
541 * @param {Event} event A dragenter event of DOM.
542 */
543 onDragEnterFileList_: function(list, event) {
544 event.preventDefault(); // Required to prevent the cursor flicker.
545 this.lastEnteredTarget_ = event.target;
546 var item = list.getListItemAncestor(event.target);
547 item = item && list.isItem(item) ? item : null;
548 if (item === this.dropTarget_)
549 return;
550
551 var entry = item && list.dataModel.item(item.listIndex);
552 if (entry)
553 this.setDropTarget_(item, event.dataTransfer, entry);
554 else
555 this.clearDropTarget_();
556 },
557
558 /**
559 * @this {FileTransferController}
560 * @param {DirectoryTree} tree Drop target tree.
561 * @param {Event} event A dragenter event of DOM.
562 */
563 onDragEnterTree_: function(tree, event) {
564 event.preventDefault(); // Required to prevent the cursor flicker.
565 this.lastEnteredTarget_ = event.target;
566 var item = event.target;
567 while (item && !(item instanceof DirectoryItem)) {
568 item = item.parentNode;
569 }
570
571 if (item === this.dropTarget_)
572 return;
573
574 var entry = item && item.entry;
575 if (entry) {
576 this.setDropTarget_(item, event.dataTransfer, entry);
577 } else {
578 this.clearDropTarget_();
579 }
580 },
581
582 /**
583 * @this {FileTransferController}
584 * @param {NavigationList} list Drop target list.
585 * @param {Event} event A dragenter event of DOM.
586 */
587 onDragEnterVolumesList_: function(list, event) {
588 event.preventDefault(); // Required to prevent the cursor flicker.
589
590 this.lastEnteredTarget_ = event.target;
591 var item = list.getListItemAncestor(event.target);
592 item = item && list.isItem(item) ? item : null;
593 if (item === this.dropTarget_)
594 return;
595
596 var modelItem = item && list.dataModel.item(item.listIndex);
597 if (modelItem && modelItem.isShortcut) {
598 this.setDropTarget_(item, event.dataTransfer, modelItem.entry);
599 return;
600 }
601 if (modelItem && modelItem.isVolume && modelItem.volumeInfo.displayRoot) {
602 this.setDropTarget_(
603 item, event.dataTransfer, modelItem.volumeInfo.displayRoot);
604 return;
605 }
606
607 this.clearDropTarget_();
608 },
609
610 /**
611 * @this {FileTransferController}
612 * @param {cr.ui.List} list Drop target list.
613 * @param {Event} event A dragleave event of DOM.
614 */
615 onDragLeave_: function(list, event) {
616 // If mouse moves from one element to another the 'dragenter'
617 // event for the new element comes before the 'dragleave' event for
618 // the old one. In this case event.target !== this.lastEnteredTarget_
619 // and handler of the 'dragenter' event has already caried of
620 // drop target. So event.target === this.lastEnteredTarget_
621 // could only be if mouse goes out of listened element.
622 if (event.target === this.lastEnteredTarget_) {
623 this.clearDropTarget_();
624 this.lastEnteredTarget_ = null;
625 }
626 },
627
628 /**
629 * @this {FileTransferController}
630 * @param {boolean} onlyIntoDirectories True if the drag is only into
631 * directories.
632 * @param {Event} event A dragleave event of DOM.
633 */
634 onDrop_: function(onlyIntoDirectories, event) {
635 if (onlyIntoDirectories && !this.dropTarget_)
636 return;
637 var destinationEntry = this.destinationEntry_ ||
638 this.currentDirectoryContentEntry;
639 if (!this.canPasteOrDrop_(event.dataTransfer, destinationEntry))
640 return;
641 event.preventDefault();
642 this.paste(event.dataTransfer, destinationEntry,
643 this.selectDropEffect_(event, destinationEntry));
644 this.clearDropTarget_();
645 },
646
647 /**
648 * Sets the drop target.
649 *
650 * @this {FileTransferController}
651 * @param {Element} domElement Target of the drop.
652 * @param {DataTransfer} dataTransfer Data transfer object.
653 * @param {DirectoryEntry} destinationEntry Destination entry.
654 */
655 setDropTarget_: function(domElement, dataTransfer, destinationEntry) {
656 if (this.dropTarget_ === domElement)
657 return;
658
659 // Remove the old drop target.
660 this.clearDropTarget_();
661
662 // Set the new drop target.
663 this.dropTarget_ = domElement;
664
665 if (!domElement ||
666 !destinationEntry.isDirectory ||
667 !this.canPasteOrDrop_(dataTransfer, destinationEntry)) {
668 return;
669 }
670
671 // Add accept class if the domElement can accept the drag.
672 domElement.classList.add('accepts');
673 this.destinationEntry_ = destinationEntry;
674
675 // Start timer changing the directory.
676 this.navigateTimer_ = setTimeout(function() {
677 if (domElement instanceof DirectoryItem)
678 // Do custom action.
679 (/** @type {DirectoryItem} */ domElement).doDropTargetAction();
680 this.directoryModel_.changeDirectoryEntry(destinationEntry);
681 }.bind(this), 2000);
682 },
683
684 /**
685 * Handles touch start.
686 */
687 onTouchStart_: function() {
688 this.touching_ = true;
689 },
690
691 /**
692 * Handles touch end.
693 */
694 onTouchEnd_: function(event) {
695 if (event.touches.length === 0)
696 this.touching_ = false;
697 },
698
699 /**
700 * Clears the drop target.
701 * @this {FileTransferController}
702 */
703 clearDropTarget_: function() {
704 if (this.dropTarget_ && this.dropTarget_.classList.contains('accepts'))
705 this.dropTarget_.classList.remove('accepts');
706 this.dropTarget_ = null;
707 this.destinationEntry_ = null;
708 if (this.navigateTimer_ !== undefined) {
709 clearTimeout(this.navigateTimer_);
710 this.navigateTimer_ = undefined;
711 }
712 },
713
714 /**
715 * @this {FileTransferController}
716 * @return {boolean} Returns false if {@code <input type="text">} element is
717 * currently active. Otherwise, returns true.
718 */
719 isDocumentWideEvent_: function() {
720 return this.document_.activeElement.nodeName.toLowerCase() !== 'input' ||
721 this.document_.activeElement.type.toLowerCase() !== 'text';
722 },
723
724 /**
725 * @this {FileTransferController}
726 */
727 onCopy_: function(event) {
728 if (!this.isDocumentWideEvent_() ||
729 !this.canCopyOrDrag_()) {
730 return;
731 }
732 event.preventDefault();
733 this.cutOrCopy_(event.clipboardData, 'copy');
734 this.notify_('selection-copied');
735 },
736
737 /**
738 * @this {FileTransferController}
739 */
740 onBeforeCopy_: function(event) {
741 if (!this.isDocumentWideEvent_())
742 return;
743
744 // queryCommandEnabled returns true if event.defaultPrevented is true.
745 if (this.canCopyOrDrag_())
746 event.preventDefault();
747 },
748
749 /**
750 * @this {FileTransferController}
751 * @return {boolean} Returns true if all selected files are available to be
752 * copied.
753 */
754 isAllSelectedFilesAvailable_: function() {
755 if (!this.currentDirectoryContentEntry)
756 return false;
757 var volumeInfo = this.volumeManager_.getVolumeInfo(
758 this.currentDirectoryContentEntry);
759 if (!volumeInfo)
760 return false;
761 var isDriveOffline = this.volumeManager_.getDriveConnectionState().type ===
762 util.DriveConnectionType.OFFLINE;
763 if (this.isOnDrive &&
764 isDriveOffline &&
765 !this.allDriveFilesAvailable)
766 return false;
767 return true;
768 },
769
770 /**
771 * @this {FileTransferController}
772 * @return {boolean} Returns true if some files are selected and all the file
773 * on drive is available to be copied. Otherwise, returns false.
774 */
775 canCopyOrDrag_: function() {
776 return this.isAllSelectedFilesAvailable_() &&
777 this.selectedEntries_.length > 0;
778 },
779
780 /**
781 * @this {FileTransferController}
782 */
783 onCut_: function(event) {
784 if (!this.isDocumentWideEvent_() ||
785 !this.canCutOrDrag_()) {
786 return;
787 }
788 event.preventDefault();
789 this.cutOrCopy_(event.clipboardData, 'move');
790 this.notify_('selection-cut');
791 },
792
793 /**
794 * @this {FileTransferController}
795 */
796 onBeforeCut_: function(event) {
797 if (!this.isDocumentWideEvent_())
798 return;
799 // queryCommandEnabled returns true if event.defaultPrevented is true.
800 if (this.canCutOrDrag_())
801 event.preventDefault();
802 },
803
804 /**
805 * @this {FileTransferController}
806 * @return {boolean} Returns true if the current directory is not read only.
807 */
808 canCutOrDrag_: function() {
809 return !this.readonly && this.selectedEntries_.length > 0;
810 },
811
812 /**
813 * @this {FileTransferController}
814 */
815 onPaste_: function(event) {
816 // Need to update here since 'beforepaste' doesn't fire.
817 if (!this.isDocumentWideEvent_() ||
818 !this.canPasteOrDrop_(event.clipboardData,
819 this.currentDirectoryContentEntry)) {
820 return;
821 }
822 event.preventDefault();
823 var effect = this.paste(event.clipboardData);
824
825 // On cut, we clear the clipboard after the file is pasted/moved so we don't
826 // try to move/delete the original file again.
827 if (effect === 'move') {
828 this.simulateCommand_('cut', function(event) {
829 event.preventDefault();
830 event.clipboardData.setData('fs/clear', '');
831 });
832 }
833 },
834
835 /**
836 * @this {FileTransferController}
837 */
838 onBeforePaste_: function(event) {
839 if (!this.isDocumentWideEvent_())
840 return;
841 // queryCommandEnabled returns true if event.defaultPrevented is true.
842 if (this.canPasteOrDrop_(event.clipboardData,
843 this.currentDirectoryContentEntry)) {
844 event.preventDefault();
845 }
846 },
847
848 /**
849 * @this {FileTransferController}
850 * @param {DataTransfer} dataTransfer Data transfer object.
851 * @param {DirectoryEntry} destinationEntry Destination entry.
852 * @return {boolean} Returns true if items stored in {@code dataTransfer} can
853 * be pasted to {@code destinationEntry}. Otherwise, returns false.
854 */
855 canPasteOrDrop_: function(dataTransfer, destinationEntry) {
856 if (!destinationEntry)
857 return false;
858 var destinationLocationInfo =
859 this.volumeManager_.getLocationInfo(destinationEntry);
860 if (!destinationLocationInfo || destinationLocationInfo.isReadOnly)
861 return false;
862 if (!dataTransfer.types || dataTransfer.types.indexOf('fs/tag') === -1)
863 return false; // Unsupported type of content.
864
865 // Copying between different sources requires all files to be available.
866 if (this.getSourceRootURL_(dataTransfer) !==
867 destinationLocationInfo.volumeInfo.fileSystem.root.toURL() &&
868 this.isMissingFileContents_(dataTransfer))
869 return false;
870
871 return true;
872 },
873
874 /**
875 * Execute paste command.
876 *
877 * @this {FileTransferController}
878 * @return {boolean} Returns true, the paste is success. Otherwise, returns
879 * false.
880 */
881 queryPasteCommandEnabled: function() {
882 if (!this.isDocumentWideEvent_()) {
883 return false;
884 }
885
886 // HACK(serya): return this.document_.queryCommandEnabled('paste')
887 // should be used.
888 var result;
889 this.simulateCommand_('paste', function(event) {
890 result = this.canPasteOrDrop_(event.clipboardData,
891 this.currentDirectoryContentEntry);
892 }.bind(this));
893 return result;
894 },
895
896 /**
897 * Allows to simulate commands to get access to clipboard.
898 *
899 * @this {FileTransferController}
900 * @param {string} command 'copy', 'cut' or 'paste'.
901 * @param {function} handler Event handler.
902 */
903 simulateCommand_: function(command, handler) {
904 var iframe = this.document_.querySelector('#command-dispatcher');
905 var doc = iframe.contentDocument;
906 doc.addEventListener(command, handler);
907 doc.execCommand(command);
908 doc.removeEventListener(command, handler);
909 },
910
911 /**
912 * @this {FileTransferController}
913 */
914 onSelectionChanged_: function(event) {
915 var entries = this.selectedEntries_;
916 var files = this.selectedFileObjects_ = [];
917 this.preloadedThumbnailImageNode_ = null;
918
919 var fileEntries = [];
920 for (var i = 0; i < entries.length; i++) {
921 if (entries[i].isFile)
922 fileEntries.push(entries[i]);
923 }
924
925 if (entries.length === 1) {
926 // For single selection, the dragged element is created in advance,
927 // otherwise an image may not be loaded at the time the 'dragstart' event
928 // comes.
929 this.preloadThumbnailImage_(entries[0]);
930 }
931
932 // File object must be prepeared in advance for clipboard operations
933 // (copy, paste and drag). DataTransfer object closes for write after
934 // returning control from that handlers so they may not have
935 // asynchronous operations.
936 var prepareFileObjects = function() {
937 for (var i = 0; i < fileEntries.length; i++) {
938 fileEntries[i].file(function(file) { files.push(file); });
939 }
940 };
941
942 if (this.isOnDrive) {
943 this.allDriveFilesAvailable = false;
944 this.metadataCache_.get(
945 entries, 'drive', function(props) {
946 // We consider directories not available offline for the purposes of
947 // file transfer since we cannot afford to recursive traversal.
948 this.allDriveFilesAvailable =
949 entries.filter(function(e) {
950 return e.isDirectory;
951 }).length === 0 &&
952 props.filter(function(p) {
953 return !p.availableOffline;
954 }).length === 0;
955 // |Copy| is the only menu item affected by allDriveFilesAvailable.
956 // It could be open right now, update its UI.
957 this.copyCommand_.disabled = !this.canCopyOrDrag_();
958
959 if (this.allDriveFilesAvailable)
960 prepareFileObjects();
961 }.bind(this));
962 } else {
963 prepareFileObjects();
964 }
965 },
966
967 /**
968 * Obains directory that is displaying now.
969 * @this {FileTransferController}
970 * @return {DirectoryEntry} Entry of directry that is displaying now.
971 */
972 get currentDirectoryContentEntry() {
973 return this.directoryModel_.getCurrentDirEntry();
974 },
975
976 /**
977 * @this {FileTransferController}
978 * @return {boolean} True if the current directory is read only.
979 */
980 get readonly() {
981 return this.directoryModel_.isReadOnly();
982 },
983
984 /**
985 * @this {FileTransferController}
986 * @return {boolean} True if the current directory is on Drive.
987 */
988 get isOnDrive() {
989 var currentDir = this.directoryModel_.getCurrentDirEntry();
990 if (!currentDir)
991 return false;
992 var locationInfo = this.volumeManager_.getLocationInfo(currentDir);
993 if (!locationInfo)
994 return false;
995 return locationInfo.isDriveBased;
996 },
997
998 /**
999 * @this {FileTransferController}
1000 */
1001 notify_: function(eventName) {
1002 var self = this;
1003 // Set timeout to avoid recursive events.
1004 setTimeout(function() {
1005 cr.dispatchSimpleEvent(self, eventName);
1006 }, 0);
1007 },
1008
1009 /**
1010 * @this {FileTransferController}
1011 * @return {Array.<Entry>} Array of the selected entries.
1012 */
1013 get selectedEntries_() {
1014 var list = this.directoryModel_.getFileList();
1015 var selectedIndexes = this.directoryModel_.getFileListSelection().
1016 selectedIndexes;
1017 var entries = selectedIndexes.map(function(index) {
1018 return list.item(index);
1019 });
1020
1021 // TODO(serya): Diagnostics for http://crbug/129642
1022 if (entries.indexOf(undefined) !== -1) {
1023 var index = entries.indexOf(undefined);
1024 entries = entries.filter(function(e) { return !!e; });
1025 console.error('Invalid selection found: list items: ', list.length,
1026 'wrong indexe value: ', selectedIndexes[index],
1027 'Stack trace: ', new Error().stack);
1028 }
1029 return entries;
1030 },
1031
1032 /**
1033 * @param {Event} event Drag event.
1034 * @param {DirectoryEntry} destinationEntry Destination entry.
1035 * @this {FileTransferController}
1036 * @return {string} Returns the appropriate drop query type ('none', 'move'
1037 * or copy') to the current modifiers status and the destination.
1038 */
1039 selectDropEffect_: function(event, destinationEntry) {
1040 if (!destinationEntry)
1041 return 'none';
1042 var destinationLocationInfo =
1043 this.volumeManager_.getLocationInfo(destinationEntry);
1044 if (!destinationLocationInfo)
1045 return 'none';
1046 if (destinationLocationInfo.isReadOnly)
1047 return 'none';
1048 if (event.dataTransfer.effectAllowed === 'move')
1049 return 'move';
1050 // TODO(mtomasz): Use volumeId instead of comparing roots, as soon as
1051 // volumeId gets unique.
1052 if (event.dataTransfer.effectAllowed === 'copyMove' &&
1053 this.getSourceRootURL_(event.dataTransfer) ===
1054 destinationLocationInfo.volumeInfo.fileSystem.root.toURL() &&
1055 !event.ctrlKey) {
1056 return 'move';
1057 }
1058 if (event.dataTransfer.effectAllowed === 'copyMove' &&
1059 event.shiftKey) {
1060 return 'move';
1061 }
1062 return 'copy';
1063 },
1064 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698