Commit 4de14608 authored by Naoki Fukino's avatar Naoki Fukino Committed by Commit Bot

[Files] Add file-type filter buttons in Recents view.

This adds file-type filter buttons (Audio, Images, Videos) above the
file list only in the Recents view.
Expected behavior:
  Users can toggle(enable/disable) file-type filters by clicking it.
  Only one filter can be applied at the same time.
  Active filter should be cleared when a user leaves Recents view.

Bug: 1114721
Test: Manually tested with flag #files-filters-in-recents
Change-Id: I50ecd9c1c2f9a587d63e6ae89f88d6b5fe0cba4a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2354115
Commit-Queue: Naoki Fukino <fukino@chromium.org>
Reviewed-by: default avatarNoel Gordon <noel@chromium.org>
Cr-Commit-Position: refs/heads/master@{#798490}
parent f4309d15
......@@ -1408,6 +1408,14 @@ util.isUnifiedMediaViewEnabled = () => {
return loadTimeData.getBoolean('UNIFIED_MEDIA_VIEW_ENABLED');
};
/**
* Returns true if filters in Recents view is enabled.
* @return {boolean}
*/
util.isRecentsFilterEnabled = () => {
return loadTimeData.getBoolean('FILTERS_IN_RECENTS_ENABLED');
};
/**
* Returns true when FilesZipMount feature is enabled.
* TODO(crbug.com/912236) Remove once transition to new ZIP system is finished.
......
......@@ -3697,6 +3697,37 @@ files-toast {
z-index: 520;
}
/* File type filter buttons in Recents view.
TODO(fukino): The style is temporary. Revisit it before the feature is
shipped. */
#file-type-filter-container {
display: flex;
font-size: 13px;
height: 30x;
margin-top: 5px;
}
.file-type-filter-button {
align-items: center;
border: 1px solid rgba(0, 0, 0, 15%);
border-radius: 20px;
box-sizing: border-box;
color: var(--google-grey-700);
cursor: pointer;
display: flex;
font-weight: 500;
height: 32px;
margin-inline-end: 3px;
margin-inline-start: 3px;
padding: 0 10px;
}
.file-type-filter-button.active {
background-color: var(--google-blue-50);
border: 1px solid transparent;
color: var(--google-blue-600);
}
/*
* Preventing FOUC
*/
......
......@@ -45,6 +45,7 @@ js_type_check("closure_compile_module") {
":file_selection",
":file_tasks",
":file_transfer_controller",
":file_type_filters_controller",
":file_watcher",
":folder_shortcuts_data_model",
":gear_menu_controller",
......@@ -358,6 +359,7 @@ js_library("file_manager") {
":empty_folder_controller",
":file_selection",
":file_transfer_controller",
":file_type_filters_controller",
":folder_shortcuts_data_model",
":gear_menu_controller",
":import_controller",
......@@ -488,6 +490,13 @@ js_unittest("file_transfer_controller_unittest") {
]
}
js_library("file_type_filters_controller") {
deps = [
":directory_model",
"//ui/file_manager/file_manager/common/js:files_app_entry_types",
]
}
js_library("file_watcher") {
deps = [
"//ui/file_manager/base/js:volume_manager_types",
......
......@@ -259,6 +259,9 @@ class FileManager extends cr.EventTarget {
/** @private {?QuickViewController} */
this.quickViewController_ = null;
/** @private {?FileTypeFiltersController} */
this.fileTypeFiltersController_ = null;
/**
* Records histograms of directory-changed event.
* @private {?NavigationUma}
......@@ -327,6 +330,12 @@ class FileManager extends cr.EventTarget {
* @private {?NavigationModelFakeItem}
*/
this.fakeDriveItem_ = null;
/**
* A fake entry for Recents.
* @private {?FakeEntry}
*/
this.recentEntry_ = null;
}
/**
......@@ -1051,6 +1060,10 @@ class FileManager extends cr.EventTarget {
this.launchParams_.showAndroidPickerApps,
this.launchParams_.includeAllFiles, this.launchParams_.typeList);
this.recentEntry_ = new FakeEntry(
str('RECENT_ROOT_LABEL'), VolumeManagerCommon.RootType.RECENT,
this.getSourceRestriction_());
assert(this.launchParams_);
this.selectionHandler_ = new FileSelectionHandler(
assert(this.directoryModel_), assert(this.fileOperationManager_),
......@@ -1121,6 +1134,13 @@ class FileManager extends cr.EventTarget {
this.metadataModel_, this.volumeManager_, this.fileFilter_,
this.namingController_, this.selectionHandler_, this.launchParams_);
// Create file-type filter controller.
if (util.isRecentsFilterEnabled()) {
this.fileTypeFiltersController_ = new FileTypeFiltersController(
this.ui_.fileTypeFilterContainer, this.directoryModel_,
this.recentEntry_);
}
return directoryTreePromise;
}
......@@ -1144,10 +1164,7 @@ class FileManager extends cr.EventTarget {
!DialogType.isFolderDialog(this.launchParams_.type) ?
new NavigationModelFakeItem(
str('RECENT_ROOT_LABEL'), NavigationModelItemType.RECENT,
new FakeEntry(
str('RECENT_ROOT_LABEL'),
VolumeManagerCommon.RootType.RECENT,
this.getSourceRestriction_())) :
assert(this.recentEntry_)) :
null,
assert(this.directoryModel_), assert(this.androidAppListModel_));
......
// Copyright 2020 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.
/**
* This class controls wires file-type filter UI and the filter settings in
* Recents view.
*/
class FileTypeFiltersController {
/**
* @param {!HTMLElement} fileTypeFilterContainer
* @param {!DirectoryModel} directoryModel
* @param {!FakeEntry} recentEntry
*/
constructor(fileTypeFilterContainer, directoryModel, recentEntry) {
/**
* @private {!HTMLElement}
* @const
*/
this.container_ = fileTypeFilterContainer;
/**
* @private {!DirectoryModel}
* @const
*/
this.directoryModel_ = directoryModel;
/**
* @private {!FakeEntry}
* @const
*/
this.recentEntry_ = recentEntry;
/**
* @private {!HTMLElement}
* @const
*/
this.audioFilterButton_ =
this.createFilterButton_(str('MEDIA_VIEW_AUDIO_ROOT_LABEL'));
/**
* @private {!HTMLElement}
* @const
*/
this.imageFilterButton_ =
this.createFilterButton_(str('MEDIA_VIEW_IMAGES_ROOT_LABEL'));
/**
* @private {!HTMLElement}
* @const
*/
this.videoFilterButton_ =
this.createFilterButton_(str('MEDIA_VIEW_VIDEOS_ROOT_LABEL'));
this.directoryModel_.addEventListener(
'directory-changed', this.onCurrentDirectoryChanged_.bind(this));
}
/**
* Creates filter button's UI element.
* @param {string} label Label of the filter button.
* @private
*/
createFilterButton_(label) {
const button = util.createChild(this.container_, 'file-type-filter-button');
button.textContent = label;
button.addEventListener('click', this.onFilterButtonClicked_.bind(this));
return button;
}
/**
* Updates the UI when the current directory changes.
* @param {!Event} event Event.
* @private
*/
onCurrentDirectoryChanged_(event) {
// We show filter buttons only in Recents view at this moment.
this.container_.hidden = !(event.newDirEntry == this.recentEntry_);
// Reset the filter buttons' active state on leaving Recents view.
if (event.previousDirEntry == this.recentEntry_ &&
event.newDirEntry != this.recentEntry_) {
this.audioFilterButton_.classList.toggle('active', false);
this.imageFilterButton_.classList.toggle('active', false);
this.videoFilterButton_.classList.toggle('active', false);
}
}
/**
* Updates the UI when one of the filter buttons is clicked.
* @param {!Event} event Event.
* @private
*/
onFilterButtonClicked_(event) {
// Toggle active state of clicked filter. When one filter button is clicked,
// other filter buttons should become inactive.
this.audioFilterButton_.classList.toggle(
'active', event.target == this.audioFilterButton_ ? undefined : false);
this.imageFilterButton_.classList.toggle(
'active', event.target == this.imageFilterButton_ ? undefined : false);
this.videoFilterButton_.classList.toggle(
'active', event.target == this.videoFilterButton_ ? undefined : false);
this.refreshRecentView_();
}
/**
* Refreshes the current directory based on the filter settings.
* @private
*/
refreshRecentView_() {
// Update the Recent entry's setting based on the 'active' state of
// filter buttons.
let fileType = chrome.fileManagerPrivate.RecentFileType.ALL;
if (this.audioFilterButton_.classList.contains('active')) {
fileType = chrome.fileManagerPrivate.RecentFileType.AUDIO;
} else if (this.imageFilterButton_.classList.contains('active')) {
fileType = chrome.fileManagerPrivate.RecentFileType.IMAGE;
} else if (this.videoFilterButton_.classList.contains('active')) {
fileType = chrome.fileManagerPrivate.RecentFileType.VIDEO;
}
this.recentEntry_.recentFileType = fileType;
// Refresh current directory with the updated Recent setting.
// We don't need to invalidate the cached metadata for this rescan.
this.directoryModel_.rescan(false);
}
}
......@@ -122,6 +122,7 @@
// <include src="file_selection.js">
// <include src="file_tasks.js">
// <include src="file_transfer_controller.js">
// <include src="file_type_filters_controller.js">
// <include src="file_watcher.js">
// <include src="folder_shortcuts_data_model.js">
// <include src="sort_menu_controller.js">
......
......@@ -373,6 +373,13 @@ class FileManagerUI {
this.toast =
/** @type {!FilesToast} */ (document.querySelector('files-toast'));
/**
* Container of file-type filter buttons.
* @const {!HTMLElement}
*/
this.fileTypeFilterContainer =
queryRequiredElement('#file-type-filter-container', this.element);
/**
* A hidden div that can be used to announce text to screen
* reader/ChromeVox.
......
......@@ -533,6 +533,7 @@
</div>
<div class="downloads-warning" hidden></div>
<files-message id='files-message' hidden></files-message>
<div id="file-type-filter-container" hidden></div>
<div id="list-container" role="main">
<div id="more-actions-info" hidden>$i18n{SEE_MENU_FOR_ACTIONS}</div>
<div id="sort-column-asc" hidden>$i18n{COLUMN_ASC_SORT_MESSAGE}</div>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment