| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * Controller for searching. |
| * @param {!SearchBox} searchBox Search box UI element. |
| * @param {!LocationLine} locationLine Location line UI element. |
| * @param {!DirectoryModel} directoryModel Directory model. |
| * @param {!TaskController} taskController Task controller to execute the |
| * selected item. |
| * @constructor |
| */ |
| function SearchController( |
| searchBox, locationLine, directoryModel, volumeManager, taskController) { |
| /** |
| * @type {SearchBox} |
| * @private |
| */ |
| this.searchBox_ = searchBox; |
| |
| /** |
| * @type {LocationLine} |
| * @private |
| */ |
| this.locationLine_ = locationLine; |
| |
| /** |
| * @type {DirectoryModel} |
| * @private |
| */ |
| this.directoryModel_ = directoryModel; |
| |
| /** |
| * @type {VolumeManager} |
| * @private |
| */ |
| this.volumeManager_ = volumeManager; |
| |
| /** |
| * @type {!TaskController} |
| * @private |
| */ |
| this.taskController_ = taskController; |
| |
| searchBox.addEventListener( |
| SearchBox.EventType.TEXT_CHANGE, this.onTextChange_.bind(this)); |
| searchBox.addEventListener( |
| SearchBox.EventType.ITEM_SELECT, this.onItemSelect_.bind(this)); |
| directoryModel.addEventListener('directory-changed', this.clear.bind(this)); |
| } |
| |
| SearchController.prototype = { |
| /** |
| * Obtains current directory's locaiton info. |
| * @type {EntryLocation} |
| * @private |
| */ |
| get currentLocationInfo_() { |
| const entry = this.directoryModel_.getCurrentDirEntry(); |
| return entry && this.volumeManager_.getLocationInfo(entry); |
| }, |
| |
| /** |
| * Whether the current directory is on drive or not. |
| * @private |
| */ |
| get isOnDrive_() { |
| const currentLocationInfo = this.currentLocationInfo_; |
| return currentLocationInfo && currentLocationInfo.isDriveBased; |
| } |
| }; |
| |
| /** |
| * Clears the search state. |
| * @param {Event=} opt_event when called from "directory-changed" event. |
| */ |
| SearchController.prototype.clear = function(opt_event) { |
| this.directoryModel_.clearLastSearchQuery(); |
| this.searchBox_.clear(); |
| // Only update visibility if |clear| is called from "directory-changed" event. |
| if (opt_event) { |
| // My Files currently doesn't implement search so let's hide it. |
| const isMyFiles = |
| (opt_event.newDirEntry && |
| opt_event.newDirEntry.rootType === |
| VolumeManagerCommon.RootType.MY_FILES); |
| this.searchBox_.setHidden(isMyFiles); |
| } |
| }; |
| |
| /** |
| * Handles text change event. |
| * @private |
| */ |
| SearchController.prototype.onTextChange_ = function() { |
| const searchString = this.searchBox_.inputElement.value.trimLeft(); |
| |
| // On drive, incremental search is not invoked since we have an auto- |
| // complete suggestion instead. |
| if (!this.isOnDrive_) { |
| this.search_(searchString); |
| return; |
| } |
| |
| // When the search text is changed, finishes the search and showes back |
| // the last directory by passing an empty string to |
| // {@code DirectoryModel.search()}. |
| if (this.directoryModel_.isSearching() && |
| this.directoryModel_.getLastSearchQuery() != searchString) { |
| this.directoryModel_.search('', () => {}, () => {}); |
| } |
| |
| this.requestAutocompleteSuggestions_(); |
| }; |
| |
| /** |
| * Updates autocompletion items. |
| * @private |
| */ |
| SearchController.prototype.requestAutocompleteSuggestions_ = function() { |
| // Remember the most recent query. If there is an other request in progress, |
| // then it's result will be discarded and it will call a new request for |
| // this query. |
| const searchString = this.searchBox_.inputElement.value.trimLeft(); |
| this.lastAutocompleteQuery_ = searchString; |
| if (this.autocompleteSuggestionsBusy_) { |
| return; |
| } |
| |
| // Clear search if the query empty. |
| if (!searchString) { |
| this.searchBox_.autocompleteList.suggestions = []; |
| return; |
| } |
| |
| // Add header item. |
| const headerItem = /** @type {SearchItem} */ ( |
| {isHeaderItem: true, searchQuery: searchString}); |
| if (!this.searchBox_.autocompleteList.dataModel || |
| this.searchBox_.autocompleteList.dataModel.length == 0) { |
| this.searchBox_.autocompleteList.suggestions = [headerItem]; |
| } else { |
| // Updates only the head item to prevent a flickering on typing. |
| this.searchBox_.autocompleteList.dataModel.splice(0, 1, headerItem); |
| } |
| |
| // The autocomplete list should be resized and repositioned here as the |
| // search box is resized when it's focused. |
| this.searchBox_.autocompleteList.syncWidthAndPositionToInput(); |
| this.autocompleteSuggestionsBusy_ = true; |
| |
| chrome.fileManagerPrivate.searchDriveMetadata( |
| { |
| query: searchString, |
| types: 'ALL', |
| maxResults: 4 |
| }, |
| suggestions => { |
| this.autocompleteSuggestionsBusy_ = false; |
| |
| // Discard results for previous requests and fire a new search |
| // for the most recent query. |
| if (searchString != this.lastAutocompleteQuery_) { |
| this.requestAutocompleteSuggestions_(); |
| return; |
| } |
| |
| // Keeps the items in the suggestion list. |
| this.searchBox_.autocompleteList.suggestions = |
| [headerItem].concat(suggestions); |
| }); |
| }; |
| |
| /** |
| * Opens the currently selected suggestion item. |
| * @private |
| */ |
| SearchController.prototype.onItemSelect_ = function() { |
| const selectedItem = this.searchBox_.autocompleteList.selectedItem; |
| |
| // Clear the current auto complete list. |
| this.lastAutocompleteQuery_ = ''; |
| this.searchBox_.autocompleteList.suggestions = []; |
| |
| // If the entry is the search item or no entry is selected, just change to |
| // the search result. |
| if (!selectedItem || selectedItem.isHeaderItem) { |
| const query = selectedItem ? |
| selectedItem.searchQuery : this.searchBox_.inputElement.value; |
| this.search_(query); |
| return; |
| } |
| |
| // Clear the search box if an item except for the search item is |
| // selected. Eventually the following directory change clears the search box, |
| // but if the selected item is located just under /drive/other, the current |
| // directory will not changed. For handling the case, and for improving |
| // response time, clear the text manually here. |
| this.clear(); |
| |
| // If the entry is a directory, just change the directory. |
| const entry = selectedItem.entry; |
| if (entry.isDirectory) { |
| this.directoryModel_.changeDirectoryEntry(entry); |
| return; |
| } |
| |
| // Change the current directory to the directory that contains the |
| // selected file. Note that this is necessary for an image or a video, |
| // which should be opened in the gallery mode, as the gallery mode |
| // requires the entry to be in the current directory model. For |
| // consistency, the current directory is always changed regardless of |
| // the file type. |
| entry.getParent(parentEntry => { |
| // Check if the parent entry points /drive/other or not. |
| // If so it just opens the file. |
| const locationInfo = this.volumeManager_.getLocationInfo(parentEntry); |
| if (!locationInfo || |
| (locationInfo.isRootEntry && |
| locationInfo.rootType === VolumeManagerCommon.RootType.DRIVE_OTHER)) { |
| this.taskController_.executeEntryTask(entry); |
| return; |
| } |
| // If the parent entry can be /drive/other. |
| this.directoryModel_.changeDirectoryEntry( |
| parentEntry, |
| () => { |
| this.directoryModel_.selectEntry(entry); |
| this.taskController_.executeEntryTask(entry); |
| }); |
| }); |
| }; |
| |
| /** |
| * Search files and update the list with the search result. |
| * @param {string} searchString String to be searched with. |
| * @private |
| */ |
| SearchController.prototype.search_ = function(searchString) { |
| |
| const onSearchRescan = function() { |
| // If the current location is somewhere in Drive, all files in Drive can |
| // be listed as search results regardless of current location. |
| // In this case, showing current location is confusing, so use the Drive |
| // root "My Drive" as the current location. |
| if (this.isOnDrive_) { |
| const locationInfo = this.currentLocationInfo_; |
| const rootEntry = locationInfo.volumeInfo.displayRoot; |
| if (rootEntry) { |
| this.locationLine_.show(rootEntry); |
| } |
| } |
| }; |
| |
| const onClearSearch = function() { |
| this.locationLine_.show( |
| this.directoryModel_.getCurrentDirEntry()); |
| }; |
| |
| this.directoryModel_.search( |
| searchString, |
| onSearchRescan.bind(this), |
| onClearSearch.bind(this)); |
| }; |