summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorryoh <ryoh@chromium.org>2016-03-18 02:16:00 -0700
committerCommit bot <commit-bot@chromium.org>2016-03-18 09:17:02 +0000
commita7ec78c615697fd49c5a9e4c797a54ab9f0aeea4 (patch)
tree5d45b1c16c7ec2b8fc6447a68cdcfd528255a570
parentfa7d8b396b85d9b6440a4f430ff90eb6a4c4a680 (diff)
downloadchromium_src-a7ec78c615697fd49c5a9e4c797a54ab9f0aeea4.zip
chromium_src-a7ec78c615697fd49c5a9e4c797a54ab9f0aeea4.tar.gz
chromium_src-a7ec78c615697fd49c5a9e4c797a54ab9f0aeea4.tar.bz2
Files app: Implement details panel for multiple files.
BUG=274045 TEST=manually Review URL: https://codereview.chromium.org/1806433002 Cr-Commit-Position: refs/heads/master@{#381917}
-rw-r--r--chrome/app/chromeos_strings.grdp6
-rw-r--r--chrome/browser/chromeos/extensions/file_manager/private_api_strings.cc2
-rw-r--r--ui/file_manager/file_manager/foreground/css/file_manager.css98
-rw-r--r--ui/file_manager/file_manager/foreground/js/compiled_resources.gyp1
-rw-r--r--ui/file_manager/file_manager/foreground/js/file_manager.js6
-rw-r--r--ui/file_manager/file_manager/foreground/js/main_scripts.js1
-rw-r--r--ui/file_manager/file_manager/foreground/js/ui/details_container.js17
-rw-r--r--ui/file_manager/file_manager/foreground/js/ui/file_manager_ui.js4
-rw-r--r--ui/file_manager/file_manager/foreground/js/ui/multi_file_details.js168
-rw-r--r--ui/file_manager/file_manager/foreground/js/ui/single_file_details.js7
-rw-r--r--ui/file_manager/file_manager/main.html15
11 files changed, 281 insertions, 44 deletions
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index ca45478..2b9f41e 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -166,6 +166,12 @@ Press any key to continue exploring.
<message name="IDS_FILE_BROWSER_STATUS_COLUMN_LABEL" desc="Status column label. This column reflects the cloud import status of files. The status is an icon depicting whether a file has been imported, and if so, where to - for example, a Drive icon if a file has been imported to Google Drive. For unimported files, this column is blank. ">
Status
</message>
+ <message name="IDS_FILE_BROWSER_TOTAL_FILE_COUNT_LABEL" desc="Total count of files that user selects in Files app">
+ Total file count
+ </message>
+ <message name="IDS_FILE_BROWSER_TOTAL_FILE_SIZE_LABEL" desc="Total size of files that user selects in Files app">
+ Total file size
+ </message>
<message name="IDS_FILE_BROWSER_TYPE_COLUMN_LABEL" desc="Type column label.">
Type
</message>
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_strings.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_strings.cc
index 4c7f171..cf4cff8 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_strings.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_strings.cc
@@ -556,6 +556,8 @@ bool FileManagerPrivateGetStringsFunction::RunSync() {
SET_STRING("SIZE_TB", IDS_FILE_BROWSER_SIZE_TB);
SET_STRING("SPACE_AVAILABLE", IDS_FILE_BROWSER_SPACE_AVAILABLE);
SET_STRING("STATUS_COLUMN_LABEL", IDS_FILE_BROWSER_STATUS_COLUMN_LABEL);
+ SET_STRING("TOTAL_FILE_SIZE", IDS_FILE_BROWSER_TOTAL_FILE_SIZE_LABEL);
+ SET_STRING("TOTAL_FILE_COUNT", IDS_FILE_BROWSER_TOTAL_FILE_COUNT_LABEL);
SET_STRING("SUGGEST_DIALOG_INSTALLATION_FAILED",
IDS_FILE_BROWSER_SUGGEST_DIALOG_INSTALLATION_FAILED);
SET_STRING("SUGGEST_DIALOG_LINK_TO_WEBSTORE",
diff --git a/ui/file_manager/file_manager/foreground/css/file_manager.css b/ui/file_manager/file_manager/foreground/css/file_manager.css
index 012b594..ad4a3dd 100644
--- a/ui/file_manager/file_manager/foreground/css/file_manager.css
+++ b/ui/file_manager/file_manager/foreground/css/file_manager.css
@@ -148,52 +148,22 @@ a:focus {
display: none !important;
}
-.details-container > #single-file-details {
- display: flex;
- flex: auto;
- flex-direction: column;
- width: 100%;
-}
-
-#single-file-details > .details-list {
- flex: auto;
- width: 100%;
-}
-
-/* Filename header of details panel for single file */
-
-#single-file-details > .filename-container {
- align-items: center;
- display: flex;
- flex-direction: row;
- height: 40px;
- padding: 0 6px;
- width: 100%;
-}
-
-.filename-container > .filename {
- -webkit-padding-end: 6px;
- flex: auto;
- font-weight: 500;
- overflow: hidden;
-}
+/* Filetype icons in details panel */
-/* Filetype icon of details panel for single file */
-
-#single-file-details > .thumbnail-container {
+.details-container .thumbnail-container {
box-sizing: border-box;
padding: 6px;
position: relative;
width: 100%;
}
-#single-file-details > .thumbnail-container:before {
+.details-container .thumbnail-container:before {
content: "";
display: block;
padding-top: 100%;
}
-.thumbnail-container > .thumbnail {
+.details-container .thumbnail-container > .thumbnail {
background-color: rgb(230, 230, 230);
background-position: center;
position: absolute;
@@ -203,23 +173,21 @@ a:focus {
right: 0;
}
-.thumbnail-container > .thumbnail.loaded {
- background-image: none;
-}
+/* File thumbnails/quick previews in details panel */
-.thumbnail > img {
+.details-container .thumbnail > img {
height: 100%;
object-fit: contain;
width: 100%;
}
-.thumbnail > video {
+.details-container .thumbnail > video {
height: 100%;
object-fit: contain;
width: 100%;
}
-.thumbnail > audio {
+.details-container .thumbnail > audio {
bottom: 0px;
left: 0px;
height: auto;
@@ -227,10 +195,58 @@ a:focus {
width: 100%;
}
-.details-list > li:not(.available) {
+/* Details panel for single file */
+
+.details-container > #single-file-details {
+ display: flex;
+ flex: auto;
+ flex-direction: column;
+ width: 100%;
+}
+
+#single-file-details > .filename-container {
+ align-items: center;
+ display: flex;
+ flex-direction: row;
+ height: 40px;
+ padding: 0 6px;
+ width: 100%;
+}
+
+#single-file-details > .filename-container > .filename {
+ -webkit-padding-end: 6px;
+ flex: auto;
+ font-weight: 500;
+ overflow: hidden;
+}
+
+#single-file-details > .thumbnail-container > .thumbnail.loaded {
+ background-image: none;
+}
+
+#single-file-details > .details-list {
+ flex: auto;
+ width: 100%;
+}
+
+#single-file-details > .details-list > li:not(.available) {
display: none;
}
+/* Filetype icon of details panel for multiple files */
+
+.details-container > #multi-file-details {
+ display: flex;
+ flex: auto;
+ flex-direction: column;
+ width: 100%;
+}
+
+#multi-file-details > .details-list {
+ flex: auto;
+ width: 100%;
+}
+
/* Directory tree at the left. */
.dialog-navigation-list {
-webkit-border-end: 1px solid rgba(0, 0, 0, 0.15);
diff --git a/ui/file_manager/file_manager/foreground/js/compiled_resources.gyp b/ui/file_manager/file_manager/foreground/js/compiled_resources.gyp
index 69a9a78..8fd66ac 100644
--- a/ui/file_manager/file_manager/foreground/js/compiled_resources.gyp
+++ b/ui/file_manager/file_manager/foreground/js/compiled_resources.gyp
@@ -142,6 +142,7 @@
'./ui/gear_menu.js',
'./ui/list_container.js',
'./ui/location_line.js',
+ './ui/multi_file_details.js',
'./ui/multi_profile_share_dialog.js',
'./ui/progress_center_panel.js',
'./ui/providers_menu.js',
diff --git a/ui/file_manager/file_manager/foreground/js/file_manager.js b/ui/file_manager/file_manager/foreground/js/file_manager.js
index 4cb38ae..e33e6fd 100644
--- a/ui/file_manager/file_manager/foreground/js/file_manager.js
+++ b/ui/file_manager/file_manager/foreground/js/file_manager.js
@@ -839,12 +839,18 @@ FileManager.prototype = /** @struct */ {
assertInstanceof(singlePanel, HTMLDivElement),
this.metadataModel_);
+ var multiPanel = queryRequiredElement('#multi-file-details', dom);
+ MultiFileDetailsPanel.decorate(
+ assertInstanceof(multiPanel, HTMLDivElement),
+ this.metadataModel_);
+
this.addHistoryObserver_();
this.ui_.initAdditionalUI(
assertInstanceof(table, FileTable),
assertInstanceof(grid, FileGrid),
assertInstanceof(singlePanel, SingleFileDetailsPanel),
+ assertInstanceof(multiPanel, MultiFileDetailsPanel),
new LocationLine(
queryRequiredElement('#location-breadcrumbs', dom),
this.volumeManager_));
diff --git a/ui/file_manager/file_manager/foreground/js/main_scripts.js b/ui/file_manager/file_manager/foreground/js/main_scripts.js
index 821acd7..953bd12 100644
--- a/ui/file_manager/file_manager/foreground/js/main_scripts.js
+++ b/ui/file_manager/file_manager/foreground/js/main_scripts.js
@@ -158,6 +158,7 @@
//<include src="ui/gear_menu.js">
//<include src="ui/list_container.js">
//<include src="ui/location_line.js">
+//<include src="ui/multi_file_details.js">
//<include src="ui/multi_profile_share_dialog.js">
//<include src="ui/progress_center_panel.js">
//<include src="ui/providers_menu.js">
diff --git a/ui/file_manager/file_manager/foreground/js/ui/details_container.js b/ui/file_manager/file_manager/foreground/js/ui/details_container.js
index be21762..11541b3c 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/details_container.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/details_container.js
@@ -5,13 +5,14 @@
/**
* @param {!HTMLElement} element
* @param {!SingleFileDetailsPanel} singlePanel
+ * @param {!MultiFileDetailsPanel} multiPanel
* @param {!Element} splitter
* @param {!Element} button
* @param {!FilesToggleRipple} toggleRipple
* @constructor
* @struct
*/
-function DetailsContainer(element, singlePanel, splitter, button,
+function DetailsContainer(element, singlePanel, multiPanel, splitter, button,
toggleRipple) {
/**
* Container element.
@@ -44,6 +45,12 @@ function DetailsContainer(element, singlePanel, splitter, button,
*/
this.singlePanel_ = singlePanel;
/**
+ * Details panel for a multiple files.
+ * @private {!MultiFileDetailsPanel}
+ * @const
+ */
+ this.multiPanel_ = multiPanel;
+ /**
* @type {boolean}
*/
this.visible = false;
@@ -71,12 +78,18 @@ DetailsContainer.prototype.onFileSelectionChanged = function(event) {
DetailsContainer.prototype.display_ = function(entries) {
if (entries.length === 0) {
this.singlePanel_.removeAttribute('activated');
+ this.multiPanel_.removeAttribute('activated');
// TODO(ryoh): make a panel for empty selection
} else if (entries.length === 1) {
this.singlePanel_.setAttribute('activated', '');
+ this.multiPanel_.removeAttribute('activated');
this.singlePanel_.onFileSelectionChanged(entries[0]);
+ this.multiPanel_.cancelLoading();
} else {
- // TODO(ryoh): make a panel for multiple selection
+ this.singlePanel_.removeAttribute('activated');
+ this.multiPanel_.setAttribute('activated', '');
+ this.multiPanel_.onFileSelectionChanged(entries);
+ this.singlePanel_.cancelLoading();
}
};
diff --git a/ui/file_manager/file_manager/foreground/js/ui/file_manager_ui.js b/ui/file_manager/file_manager/foreground/js/ui/file_manager_ui.js
index d9c182b..5e11627 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/file_manager_ui.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/file_manager_ui.js
@@ -339,10 +339,11 @@ function FileManagerUI(providersModel, element, launchParam) {
* @param {!FileTable} table
* @param {!FileGrid} grid
* @param {!SingleFileDetailsPanel} singlePanel
+ * @param {!MultiFileDetailsPanel} multiPanel
* @param {!LocationLine} locationLine
*/
FileManagerUI.prototype.initAdditionalUI = function(
- table, grid, singlePanel, locationLine) {
+ table, grid, singlePanel, multiPanel, locationLine) {
// List container.
this.listContainer = new ListContainer(
queryRequiredElement('#list-container', this.element), table, grid);
@@ -358,6 +359,7 @@ FileManagerUI.prototype.initAdditionalUI = function(
this.detailsContainer = new DetailsContainer(
queryRequiredElement('#details-container', this.element),
singlePanel,
+ multiPanel,
listDetailsSplitter,
this.detailsButton,
this.detailsButtonToggleRipple_);
diff --git a/ui/file_manager/file_manager/foreground/js/ui/multi_file_details.js b/ui/file_manager/file_manager/foreground/js/ui/multi_file_details.js
new file mode 100644
index 0000000..f2b54cf
--- /dev/null
+++ b/ui/file_manager/file_manager/foreground/js/ui/multi_file_details.js
@@ -0,0 +1,168 @@
+// Copyright 2016 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.
+
+
+/**
+ * MultiFileDetailsPanel constructor.
+ *
+ * Represents grid for the details panel for a single file in Files app.
+ * @constructor
+ * @extends {HTMLDivElement}
+ */
+function MultiFileDetailsPanel() {
+ throw new Error('Use MultiFileDetailsPanel.decorate');
+}
+
+/**
+ * Inherits from cr.ui.List.
+ */
+MultiFileDetailsPanel.prototype = {
+ __proto__: HTMLDivElement.prototype,
+
+ /**
+ * @param {!Array<!FileEntry>} entries
+ */
+ onFileSelectionChanged: function(entries) {
+ this.ticket_++;
+ this.lastTargetEntries_ = entries;
+ this.aggregateRateLimitter_.run();
+ },
+
+ startAggregation: function() {
+ var aggregator = new MultiFileDetailsPanel.Aggregator(this.ticket_,
+ this.metadataModel_,
+ this.onAggregated_.bind(this));
+ aggregator.enqueue(this.lastTargetEntries_);
+ },
+
+ /**
+ * @param {number} ticket
+ * @param {number} totalCount Total file count.
+ * @param {number} totalSize Sum of file size.
+ * @return {boolean} Whether should we continue the aggregation or not.
+ */
+ onAggregated_: function(ticket, totalCount, totalSize) {
+ if (ticket !== this.ticket_) {
+ return false;
+ }
+ this.lastTotalSize_ = totalSize;
+ this.lastTotalCount_ = totalCount;
+ this.viewUpdateRateLimitter_.run();
+ return true;
+ },
+
+ /**
+ * @private
+ */
+ updateView_: function() {
+ queryRequiredElement('.file-size > .content', this.list_).textContent =
+ this.formatter_.formatSize(this.lastTotalSize_);
+ queryRequiredElement('.file-count > .content', this.list_).textContent =
+ this.lastTotalCount_;
+ },
+
+ /**
+ * Cancel loading task.
+ */
+ cancelLoading: function() {
+ this.ticket_++;
+ }
+};
+
+/**
+ * Aggregator class. That count files and calculate a sum of file size.
+ * @param {number} ticket
+ * @param {!MetadataModel} metadataModel
+ * @param {function(number, number, number)} callback Callback to update views.
+ * @constructor
+ */
+MultiFileDetailsPanel.Aggregator = function(ticket, metadataModel, callback) {
+ this.queue_ = [];
+ this.totalCount_ = 0;
+ this.totalSize_ = 0;
+ this.ticket_ = ticket;
+ this.metadataModel_ = metadataModel;
+ this.callback_ = callback;
+};
+
+/**
+ * Aggregates data of given files and enqueue directories to queue.
+ * @param {!Array<!FileEntry>} entries
+ * @private
+ */
+MultiFileDetailsPanel.Aggregator.prototype.enqueue = function(entries) {
+ var files = [];
+ var dirs = [];
+ var self = this;
+ for (var i = 0; i < entries.length; i++) {
+ var entry = entries[i];
+ if (entry.isFile) {
+ files.push(entry);
+ } else {
+ dirs.push(entry);
+ }
+ }
+ Array.prototype.push.apply(this.queue_, dirs);
+ this.metadataModel_.get(files, ['size'])
+ .then(function(metadatas) {
+ for (var i = 0; i < metadatas.length; i++) {
+ var metadata = metadatas[i];
+ self.totalCount_++;
+ self.totalSize_ += metadata.size;
+ }
+ if (self.update_()) {
+ self.dequeue_();
+ }
+ }, function(err) {
+ console.error(err);
+ }).then(function () {
+ if (self.update_()) {
+ self.dequeue_();
+ }
+ });
+};
+
+/**
+ * Updates views with current aggregate results.
+ * @return {boolean} Whether we should continue the aggregation or not.
+ * @private
+ */
+MultiFileDetailsPanel.Aggregator.prototype.update_ = function() {
+ return this.callback_(this.ticket_, this.totalCount_, this.totalSize_);
+};
+
+/**
+ * Gets one directory from queue and fetch metadata
+ * @private
+ */
+MultiFileDetailsPanel.Aggregator.prototype.dequeue_ = function() {
+ if (this.queue_.length === 0) {
+ return;
+ }
+ var self = this;
+ var next = this.queue_.shift();
+ var reader = next.createReader();
+ reader.readEntries(function(results) {
+ self.enqueue(results);
+ });
+};
+
+/**
+ * Decorates an HTML element to be a SingleFileDetailsList.
+ * @param {!HTMLDivElement} self The grid to decorate.
+ * @param {!MetadataModel} metadataModel File system metadata.
+ */
+MultiFileDetailsPanel.decorate = function(self, metadataModel) {
+ self.__proto__ = MultiFileDetailsPanel.prototype;
+ self.formatter_ = new FileMetadataFormatter();
+ self.metadataModel_ = metadataModel;
+ self.ticket_ = 0;
+ self.lastTotalSize_ = 0;
+ self.lastTotalCount_ = 0;
+ self.list_ = queryRequiredElement('.details-list', self);
+ self.aggregateRateLimitter_ =
+ new AsyncUtil.RateLimiter(self.startAggregation.bind(self));
+ self.viewUpdateRateLimitter_ =
+ new AsyncUtil.RateLimiter(self.updateView_.bind(self));
+};
diff --git a/ui/file_manager/file_manager/foreground/js/ui/single_file_details.js b/ui/file_manager/file_manager/foreground/js/ui/single_file_details.js
index 7cfe071..035b04c 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/single_file_details.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/single_file_details.js
@@ -241,6 +241,13 @@ SingleFileDetailsPanel.prototype = {
this.preview_.pause();
}
}
+ },
+
+ /**
+ * Cancel loading task.
+ */
+ cancelLoading: function() {
+ this.ticket_++;
}
};
diff --git a/ui/file_manager/file_manager/main.html b/ui/file_manager/file_manager/main.html
index cd28482..a1c4612 100644
--- a/ui/file_manager/file_manager/main.html
+++ b/ui/file_manager/file_manager/main.html
@@ -430,6 +430,21 @@
</li>
</ul>
</div>
+ <div id="multi-file-details">
+ <div class="thumbnail-container">
+ <div class="thumbnail" generic-thumbnail="folder"></div>
+ </div>
+ <ul class="details-list">
+ <li class='file-count'>
+ <span i18n-content="TOTAL_FILE_COUNT"></span>:
+ <span class='content'></span>
+ </li>
+ <li class='file-size'>
+ <span i18n-content="TOTAL_FILE_SIZE"></span>:
+ <span class='content'></span>
+ </li>
+ </ul>
+ </div>
</div>
</div>
<div class="dialog-footer progressable" tabindex="-1"