Commit 05bd65e0 authored by ryoh's avatar ryoh Committed by Commit bot

Files app: Loading detailed information for single file.

BUG=274045

Review URL: https://codereview.chromium.org/1789593002

Cr-Commit-Position: refs/heads/master@{#381672}
parent be062302
...@@ -183,9 +183,10 @@ WRAPPED_INSTANTIATE_TEST_CASE_P( ...@@ -183,9 +183,10 @@ WRAPPED_INSTANTIATE_TEST_CASE_P(
WRAPPED_INSTANTIATE_TEST_CASE_P( WRAPPED_INSTANTIATE_TEST_CASE_P(
DetailsPanel, DetailsPanel,
FileManagerDetailsPanelBrowserTest, FileManagerDetailsPanelBrowserTest,
::testing::Values(TestParameter(NOT_IN_GUEST_MODE, "openDetailsPanel"), ::testing::Values(
TestParameter(NOT_IN_GUEST_MODE, TestParameter(NOT_IN_GUEST_MODE, "openDetailsPanel"),
"openDetailsPanelForSingleFile"))); TestParameter(NOT_IN_GUEST_MODE, "openDetailsPanelForSingleFile"),
TestParameter(NOT_IN_GUEST_MODE, "openSingleFileAndSeeDetailsPanel")));
#if defined(DISABLE_SLOW_FILESAPP_TESTS) #if defined(DISABLE_SLOW_FILESAPP_TESTS)
#define MAYBE_DirectoryTreeContextMenu DISABLED_DirectoryTreeContextMenu #define MAYBE_DirectoryTreeContextMenu DISABLED_DirectoryTreeContextMenu
......
...@@ -151,7 +151,7 @@ a:focus { ...@@ -151,7 +151,7 @@ a:focus {
.details-container > #single-file-details { .details-container > #single-file-details {
display: flex; display: flex;
flex: auto; flex: auto;
flex-direction: row; flex-direction: column;
width: 100%; width: 100%;
} }
...@@ -160,6 +160,77 @@ a:focus { ...@@ -160,6 +160,77 @@ a:focus {
width: 100%; 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 icon of details panel for single file */
#single-file-details > .thumbnail-container {
box-sizing: border-box;
padding: 6px;
position: relative;
width: 100%;
}
#single-file-details > .thumbnail-container:before {
content: "";
display: block;
padding-top: 100%;
}
.thumbnail-container > .thumbnail {
background-color: rgb(230, 230, 230);
background-position: center;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
.thumbnail-container > .thumbnail.loaded {
background-image: none;
}
.thumbnail > img {
height: 100%;
object-fit: contain;
width: 100%;
}
.thumbnail > video {
height: 100%;
object-fit: contain;
width: 100%;
}
.thumbnail > audio {
bottom: 0px;
left: 0px;
height: auto;
position: absolute;
width: 100%;
}
.details-list > li:not(.available) {
display: none;
}
/* Directory tree at the left. */ /* Directory tree at the left. */
.dialog-navigation-list { .dialog-navigation-list {
-webkit-border-end: 1px solid rgba(0, 0, 0, 0.15); -webkit-border-end: 1px solid rgba(0, 0, 0, 0.15);
......
...@@ -133,6 +133,7 @@ ...@@ -133,6 +133,7 @@
'./ui/file_grid.js', './ui/file_grid.js',
'./ui/file_list_selection_model.js', './ui/file_list_selection_model.js',
'./ui/file_manager_ui.js', './ui/file_manager_ui.js',
'./ui/file_metadata_formatter.js',
'./ui/file_table.js', './ui/file_table.js',
'./ui/file_table_list.js', './ui/file_table_list.js',
'./ui/files_alert_dialog.js', './ui/files_alert_dialog.js',
......
...@@ -833,6 +833,7 @@ FileManager.prototype = /** @struct */ { ...@@ -833,6 +833,7 @@ FileManager.prototype = /** @struct */ {
this.metadataModel_, this.metadataModel_,
this.volumeManager_, this.volumeManager_,
this.historyLoader_); this.historyLoader_);
var singlePanel = queryRequiredElement('#single-file-details', dom); var singlePanel = queryRequiredElement('#single-file-details', dom);
SingleFileDetailsPanel.decorate( SingleFileDetailsPanel.decorate(
assertInstanceof(singlePanel, HTMLDivElement), assertInstanceof(singlePanel, HTMLDivElement),
...@@ -980,6 +981,7 @@ FileManager.prototype = /** @struct */ { ...@@ -980,6 +981,7 @@ FileManager.prototype = /** @struct */ {
// Create metadata update controller. // Create metadata update controller.
this.metadataUpdateController_ = new MetadataUpdateController( this.metadataUpdateController_ = new MetadataUpdateController(
this.ui_.listContainer, this.ui_.listContainer,
assert(this.ui_.detailsContainer),
this.directoryModel_, this.directoryModel_,
this.metadataModel_); this.metadataModel_);
......
...@@ -148,6 +148,7 @@ ...@@ -148,6 +148,7 @@
//<include src="ui/error_dialog.js"> //<include src="ui/error_dialog.js">
//<include src="ui/file_grid.js"> //<include src="ui/file_grid.js">
//<include src="ui/file_manager_ui.js"> //<include src="ui/file_manager_ui.js">
//<include src="ui/file_metadata_formatter.js">
//<include src="ui/file_list_selection_model.js"> //<include src="ui/file_list_selection_model.js">
//<include src="ui/file_table.js"> //<include src="ui/file_table.js">
//<include src="ui/file_table_list.js"> //<include src="ui/file_table_list.js">
......
...@@ -5,12 +5,14 @@ ...@@ -5,12 +5,14 @@
/** /**
* Controller for list contents update. * Controller for list contents update.
* @param {!ListContainer} listContainer * @param {!ListContainer} listContainer
* @param {!DetailsContainer} detailsContainer
* @param {!DirectoryModel} directoryModel * @param {!DirectoryModel} directoryModel
* @param {!MetadataModel} metadataModel * @param {!MetadataModel} metadataModel
* @constructor * @constructor
* @struct * @struct
*/ */
function MetadataUpdateController(listContainer, function MetadataUpdateController(listContainer,
detailsContainer,
directoryModel, directoryModel,
metadataModel) { metadataModel) {
/** /**
...@@ -31,6 +33,12 @@ function MetadataUpdateController(listContainer, ...@@ -31,6 +33,12 @@ function MetadataUpdateController(listContainer,
*/ */
this.listContainer_ = listContainer; this.listContainer_ = listContainer;
/**
* @private {!DetailsContainer}
* @const
*/
this.detailsContainer_ = detailsContainer;
chrome.fileManagerPrivate.onPreferencesChanged.addListener( chrome.fileManagerPrivate.onPreferencesChanged.addListener(
this.onPreferencesChanged_.bind(this)); this.onPreferencesChanged_.bind(this));
this.onPreferencesChanged_(); this.onPreferencesChanged_();
...@@ -110,6 +118,7 @@ MetadataUpdateController.prototype.onPreferencesChanged_ = function() { ...@@ -110,6 +118,7 @@ MetadataUpdateController.prototype.onPreferencesChanged_ = function() {
chrome.fileManagerPrivate.getPreferences(function(prefs) { chrome.fileManagerPrivate.getPreferences(function(prefs) {
var use12hourClock = !prefs.use24hourClock; var use12hourClock = !prefs.use24hourClock;
this.listContainer_.table.setDateTimeFormat(use12hourClock); this.listContainer_.table.setDateTimeFormat(use12hourClock);
this.detailsContainer_.setDateTimeFormat(use12hourClock);
this.refreshCurrentDirectoryMetadata(); this.refreshCurrentDirectoryMetadata();
}.bind(this)); }.bind(this));
}; };
...@@ -47,14 +47,30 @@ function DetailsContainer(element, singlePanel, splitter, button, ...@@ -47,14 +47,30 @@ function DetailsContainer(element, singlePanel, splitter, button,
* @type {boolean} * @type {boolean}
*/ */
this.visible = false; this.visible = false;
/**
* @private {Array<!FileEntry>}
*/
this.pendingEntries_ = null;
this.setVisibility(false); this.setVisibility(false);
} }
DetailsContainer.prototype.onFileSelectionChanged = function(event) { DetailsContainer.prototype.onFileSelectionChanged = function(event) {
var entries = event.target.selection.entries; var entries = event.target.selection.entries;
if (this.visible) {
this.pendingEntries_ = null;
this.display_(entries);
} else {
this.pendingEntries_ = entries;
}
};
/**
* Disply details of entries
* @param {!Array<!FileEntry>} entries
*/
DetailsContainer.prototype.display_ = function(entries) {
if (entries.length === 0) { if (entries.length === 0) {
this.singlePanel_.removeAttribute('activated'); this.singlePanel_.removeAttribute('activated');
this.singlePanel_.classList.toggle('activated', false);
// TODO(ryoh): make a panel for empty selection // TODO(ryoh): make a panel for empty selection
} else if (entries.length === 1) { } else if (entries.length === 1) {
this.singlePanel_.setAttribute('activated', ''); this.singlePanel_.setAttribute('activated', '');
...@@ -69,13 +85,25 @@ DetailsContainer.prototype.onFileSelectionChanged = function(event) { ...@@ -69,13 +85,25 @@ DetailsContainer.prototype.onFileSelectionChanged = function(event) {
* @param {boolean} visibility True if the details panel is visible. * @param {boolean} visibility True if the details panel is visible.
*/ */
DetailsContainer.prototype.setVisibility = function(visibility) { DetailsContainer.prototype.setVisibility = function(visibility) {
this.visible = visibility;
if (visibility) { if (visibility) {
this.splitter_.setAttribute('activated', ''); this.splitter_.setAttribute('activated', '');
this.element_.setAttribute('activated', ''); this.element_.setAttribute('activated', '');
if (this.pendingEntries_) {
this.display_(this.pendingEntries_);
}
} else { } else {
this.splitter_.removeAttribute('activated'); this.splitter_.removeAttribute('activated');
this.element_.removeAttribute('activated'); this.element_.removeAttribute('activated');
} }
this.visible = visibility;
this.toggleRipple_.activated = visibility; this.toggleRipple_.activated = visibility;
this.singlePanel_.onVisibilityChanged(visibility);
};
/**
* Sets date and time format.
* @param {boolean} use12hourClock True if 12 hours clock, False if 24 hours.
*/
DetailsContainer.prototype.setDateTimeFormat = function(use12hourClock) {
this.singlePanel_.setDateTimeFormat(use12hourClock);
}; };
...@@ -352,19 +352,20 @@ FileManagerUI.prototype.initAdditionalUI = function( ...@@ -352,19 +352,20 @@ FileManagerUI.prototype.initAdditionalUI = function(
queryRequiredElement('#navigation-list-splitter', this.element)); queryRequiredElement('#navigation-list-splitter', this.element));
// Details container. // Details container.
var listDetailsSplitter =
queryRequiredElement('#list-details-splitter', this.element);
this.decorateSplitter_(listDetailsSplitter, true);
this.detailsContainer = new DetailsContainer(
queryRequiredElement('#details-container', this.element),
singlePanel,
listDetailsSplitter,
this.detailsButton,
this.detailsButtonToggleRipple_);
chrome.commandLinePrivate.hasSwitch('enable-files-details-panel', chrome.commandLinePrivate.hasSwitch('enable-files-details-panel',
function(enabled) { function(enabled) {
if (enabled) { if (enabled) {
this.detailsButton.style.display = 'block'; this.detailsButton.style.display = 'block';
var listDetailsSplitter =
queryRequiredElement('#list-details-splitter', this.element);
this.decorateSplitter_(listDetailsSplitter, true);
this.detailsContainer = new DetailsContainer(
queryRequiredElement('#details-container', this.element),
singlePanel,
listDetailsSplitter,
this.detailsButton,
this.detailsButtonToggleRipple_);
} }
}.bind(this)); }.bind(this));
......
// 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.
/**
* Formatter class for file metadatas.
* @constructor
*/
function FileMetadataFormatter() {
this.setDateTimeFormat(true);
}
/**
* Sets date and time format.
* @param {boolean} use12hourClock True if 12 hours clock, False if 24 hours.
*/
FileMetadataFormatter.prototype.setDateTimeFormat = function(use12hourClock) {
this.timeFormatter_ = new Intl.DateTimeFormat(
[] /* default locale */,
{hour: 'numeric', minute: 'numeric', hour12: use12hourClock});
this.dateFormatter_ = new Intl.DateTimeFormat(
[] /* default locale */,
{
year: 'numeric', month: 'short', day: 'numeric',
hour: 'numeric', minute: 'numeric', hour12: use12hourClock
});
};
/**
* Generates a formatted modification time text.
* @param {Date} modTime
* @return {string} A string that represents modification time.
*/
FileMetadataFormatter.prototype.formatModDate = function (modTime) {
if (!modTime) {
return '...';
}
var today = new Date();
today.setHours(0);
today.setMinutes(0);
today.setSeconds(0);
today.setMilliseconds(0);
/**
* Number of milliseconds in a day.
*/
var MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
if (isNaN(modTime.getTime())) {
// In case of 'Invalid Date'.
return '--';
} else if (modTime >= today &&
modTime < today.getTime() + MILLISECONDS_IN_DAY) {
return strf('TIME_TODAY', this.timeFormatter_.format(modTime));
} else if (modTime >= today - MILLISECONDS_IN_DAY && modTime < today) {
return strf('TIME_YESTERDAY', this.timeFormatter_.format(modTime));
} else {
return this.dateFormatter_.format(modTime);
}
};
/**
* Generates a formatted filesize text.
* @param {number=} size
* @param {boolean=} hosted
* @return {string} A string that represents a file size.
*/
FileMetadataFormatter.prototype.formatSize = function (size, hosted) {
if (size === null || size === undefined) {
return '...';
} else if (size === -1) {
return '--';
} else if (size === 0 && hosted) {
return '--';
} else {
return util.bytesToString(size);
}
};
...@@ -421,7 +421,8 @@ FileTable.decorate = function( ...@@ -421,7 +421,8 @@ FileTable.decorate = function(
var columnModel = new FileTableColumnModel(columns); var columnModel = new FileTableColumnModel(columns);
self.columnModel = columnModel; self.columnModel = columnModel;
self.setDateTimeFormat(true);
self.formatter_ = new FileMetadataFormatter();
self.setRenderFunction(self.renderTableRow_.bind(self, self.setRenderFunction(self.renderTableRow_.bind(self,
self.getRenderFunction())); self.getRenderFunction()));
...@@ -595,15 +596,7 @@ FileTable.prototype.setImportStatusVisible = function(visible) { ...@@ -595,15 +596,7 @@ FileTable.prototype.setImportStatusVisible = function(visible) {
* @param {boolean} use12hourClock True if 12 hours clock, False if 24 hours. * @param {boolean} use12hourClock True if 12 hours clock, False if 24 hours.
*/ */
FileTable.prototype.setDateTimeFormat = function(use12hourClock) { FileTable.prototype.setDateTimeFormat = function(use12hourClock) {
this.timeFormatter_ = new Intl.DateTimeFormat( this.formatter_.setDateTimeFormat(use12hourClock);
[] /* default locale */,
{hour: 'numeric', minute: 'numeric', hour12: use12hourClock});
this.dateFormatter_ = new Intl.DateTimeFormat(
[] /* default locale */,
{
year: 'numeric', month: 'short', day: 'numeric',
hour: 'numeric', minute: 'numeric', hour12: use12hourClock
});
}; };
/** /**
...@@ -725,15 +718,8 @@ FileTable.prototype.updateSize_ = function(div, entry) { ...@@ -725,15 +718,8 @@ FileTable.prototype.updateSize_ = function(div, entry) {
var metadata = this.metadataModel_.getCache( var metadata = this.metadataModel_.getCache(
[entry], ['size', 'hosted'])[0]; [entry], ['size', 'hosted'])[0];
var size = metadata.size; var size = metadata.size;
if (size === null || size === undefined) { var hosted = metadata.hosted;
div.textContent = '...'; div.textContent = this.formatter_.formatSize(size, hosted);
} else if (size === -1) {
div.textContent = '--';
} else if (size === 0 && metadata.hosted) {
div.textContent = '--';
} else {
div.textContent = util.bytesToString(size);
}
}; };
/** /**
...@@ -861,34 +847,7 @@ FileTable.prototype.updateDate_ = function(div, entry) { ...@@ -861,34 +847,7 @@ FileTable.prototype.updateDate_ = function(div, entry) {
var modTime = this.metadataModel_.getCache( var modTime = this.metadataModel_.getCache(
[entry], ['modificationTime'])[0].modificationTime; [entry], ['modificationTime'])[0].modificationTime;
if (!modTime) { div.textContent = this.formatter_.formatModDate(modTime);
div.textContent = '...';
return;
}
var today = new Date();
today.setHours(0);
today.setMinutes(0);
today.setSeconds(0);
today.setMilliseconds(0);
/**
* Number of milliseconds in a day.
*/
var MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
if (isNaN(modTime.getTime())) {
// In case of 'Invalid Date'.
div.textContent = '--';
} else if (modTime >= today &&
modTime < today.getTime() + MILLISECONDS_IN_DAY) {
div.textContent = strf('TIME_TODAY', this.timeFormatter_.format(modTime));
} else if (modTime >= today - MILLISECONDS_IN_DAY && modTime < today) {
div.textContent = strf('TIME_YESTERDAY',
this.timeFormatter_.format(modTime));
} else {
div.textContent = this.dateFormatter_.format(modTime);
}
}; };
/** /**
......
...@@ -412,8 +412,23 @@ ...@@ -412,8 +412,23 @@
<div class="splitter" id="list-details-splitter"></div> <div class="splitter" id="list-details-splitter"></div>
<div id="details-container" class="details-container"> <div id="details-container" class="details-container">
<div id="single-file-details"> <div id="single-file-details">
<div class="filename"></div> <div class="filename-container">
<ul class="details-list"></ul> <div class="filename-icon detail-icon"></div>
<div class="filename"></div>
</div>
<div class="thumbnail-container">
<div class="thumbnail"></div>
</div>
<ul class="details-list">
<li class='modification-time'>
<span i18n-content="DATE_COLUMN_LABEL"></span>:
<span class='content'></span>
</li>
<li class='file-size'>
<span i18n-content="SIZE_COLUMN_LABEL"></span>:
<span class='content'></span>
</li>
</ul>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -45,8 +45,7 @@ testcase.openDetailsPanelForSingleFile = function() { ...@@ -45,8 +45,7 @@ testcase.openDetailsPanelForSingleFile = function() {
}, },
function(result) { function(result) {
chrome.test.assertFalse(result.hidden); chrome.test.assertFalse(result.hidden);
remoteCall.waitForAFile('downloads', remoteCall.waitForAFile('downloads', 'hello.txt').then(this.next);
'hello.txt').then(this.next);
}, },
function(result) { function(result) {
remoteCall.waitForElement(appId, "#single-file-details").then(this.next); remoteCall.waitForElement(appId, "#single-file-details").then(this.next);
...@@ -57,3 +56,35 @@ testcase.openDetailsPanelForSingleFile = function() { ...@@ -57,3 +56,35 @@ testcase.openDetailsPanelForSingleFile = function() {
} }
]); ]);
}; };
testcase.openSingleFileAndSeeDetailsPanel = function() {
var appId;
StepsRunner.run([
function() {
setupAndWaitUntilReady(null, RootPath.DOWNLOADS, this.next);
},
function(results) {
appId = results.windowId;
remoteCall.callRemoteTestUtil('fakeEvent',
appId,
['#details-button', 'click'],
this.next);
},
function(result) {
remoteCall.waitForElement(appId, "#details-container").then(this.next);
},
function(result) {
chrome.test.assertFalse(result.hidden);
remoteCall.callRemoteTestUtil('selectFile', appId, ['hello.txt'],
this.next);
},
function(result) {
remoteCall.waitForElement(appId, "#single-file-details .filename")
.then(this.next);
},
function(result) {
chrome.test.assertEq('hello.txt', result.text);
checkIfNoErrorsOccured(this.next);
}
]);
};
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