Commit 10195bc6 authored by Luciano Pacheco's avatar Luciano Pacheco Committed by Commit Bot

Add My Files parent for Downloads (behind flag)

Add My Files parent on navigation tree, navigation_list_model.js
creates the "My Files" entry as a EntryList and EntryListItem on
directory_tree.js is the UI element to display it.

This is a continuation of:
 * crrev.com/c/1086680
 * crrev.com/c/1116400
 * crrev.com/c/1116517

Some changes required to display EntryList:
 * isDescendantEntry: Inspect on each of EntryList's children.
 * FileManager.initializeUI: Fetch asynchronously the flag to be able
to use before displaying the navigation tree.
 * extract the switch (modelItem.type) to its own function, to be able
to reuse it.

Design doc: https://docs.google.com/document/d/1X5XSLKJd0yerL-qFhpb2z9ibUVb_W3gG_tfIV7T_Qt0

Bug: 846587, 846592, 846591, 846590, 846589, 846588, 846586
Cq-Include-Trybots: luci.chromium.try:closure_compilation
Change-Id: Id8f27f09763d4478b93d52dd32e4ed851c7e448d
Reviewed-on: https://chromium-review.googlesource.com/1113258
Commit-Queue: Luciano Pacheco <lucmult@chromium.org>
Reviewed-by: default avatarNaoki Fukino <fukino@chromium.org>
Reviewed-by: default avatarNoel Gordon <noel@chromium.org>
Reviewed-by: default avatarSasha Morrissey <sashab@chromium.org>
Cr-Commit-Position: refs/heads/master@{#572507}
parent d7b0fb62
......@@ -256,6 +256,9 @@
<message name="IDS_FILE_BROWSER_LINUX_FILES_ROOT_LABEL" desc="A label for the 'Linux Files' root which shows crostini files.">
Linux Files
</message>
<message name="IDS_FILE_BROWSER_MY_FILES_ROOT_LABEL" desc="A label for the 'My Files' root which is parent of Downloads, Linux and Android files.">
My Files
</message>
<message name="IDS_FILE_BROWSER_MEDIA_VIEW_IMAGES_ROOT_LABEL" desc="A label for the 'Images' root of media views.">
Images
</message>
......
cef0df144e133cdff7b8997a6b0ecee405e8ec53
\ No newline at end of file
......@@ -737,6 +737,7 @@ ExtensionFunction::ResponseAction FileManagerPrivateGetStringsFunction::Run() {
SET_STRING("SHOW_ALL_ANDROID_FOLDERS_OPTION",
IDS_FILE_BROWSER_SHOW_ALL_ANDROID_FOLDERS_OPTION);
SET_STRING("LINUX_FILES_ROOT_LABEL", IDS_FILE_BROWSER_LINUX_FILES_ROOT_LABEL);
SET_STRING("MY_FILES_ROOT_LABEL", IDS_FILE_BROWSER_MY_FILES_ROOT_LABEL);
SET_STRING("MEDIA_ARTIST_COLUMN_LABEL",
IDS_FILE_BROWSER_MEDIA_ARTIST_COLUMN_LABEL);
SET_STRING("MEDIA_TITLE_COLUMN_LABEL",
......
......@@ -42,11 +42,17 @@ struct TestCase {
return *this;
}
TestCase& EnableMyFiles() {
enable_new_navigation = true;
return *this;
}
const char* test_case_name = nullptr;
GuestMode guest_mode = NOT_IN_GUEST_MODE;
bool trusted_events = false;
bool tablet_mode = false;
bool enable_drivefs = false;
bool enable_new_navigation = false;
};
// EventCase: FilesAppBrowserTest with trusted JS Events.
......@@ -77,6 +83,11 @@ class FilesAppBrowserTest : public FileManagerBrowserTestBase,
if (GetParam().tablet_mode) {
command_line->AppendSwitchASCII("force-tablet-mode", "touch_view");
}
// If requested, enable the new-files-app-navigation flag.
if (GetParam().enable_new_navigation) {
command_line->AppendSwitchASCII("new-files-app-navigation", "");
}
}
GuestMode GetGuestMode() const override { return GetParam().guest_mode; }
......@@ -122,6 +133,9 @@ std::string PostTestCaseName(const ::testing::TestParamInfo<TestCase>& test) {
if (test.param.enable_drivefs)
name.append("_DriveFs");
if (test.param.enable_new_navigation)
name.append("_MyFiles");
return name;
}
......@@ -456,6 +470,11 @@ WRAPPED_INSTANTIATE_TEST_CASE_P(
FilesAppBrowserTest,
::testing::Values(TestCase("mountCrostiniContainer")));
WRAPPED_INSTANTIATE_TEST_CASE_P(
MyFiles, /* my_files.js */
FilesAppBrowserTest,
::testing::Values(TestCase("showMyFiles").EnableMyFiles()));
// Structure to describe an account info.
struct TestAccountInfo {
const char* const gaia_id;
......
......@@ -292,7 +292,7 @@ VolumeManagerImpl.prototype.getCurrentProfileVolumeInfo = function(volumeType) {
VolumeManagerImpl.prototype.getLocationInfo = function(entry) {
var volumeInfo = this.volumeInfoList.findByEntry(entry);
if (util.isFakeEntry(entry) || entry.type_name == 'EntryList') {
if (util.isFakeEntry(entry)) {
return new EntryLocationImpl(
volumeInfo, entry.rootType,
true /* the entry points a root directory. */,
......
......@@ -651,9 +651,16 @@ Object.freeze(util.EntryChangedKind);
* Obtains whether an entry is fake or not.
* @param {(!Entry|!FakeEntry|!FilesAppEntry)} entry Entry or a fake entry.
* @return {boolean} True if the given entry is fake.
* @suppress {missingProperties} Closure compiler doesn't allow to call isNative
* on Entry which is native and thus doesn't define this property, however we
* handle undefined accordingly.
* TODO(lucmult): Remove @suppress once all entries are sub-type of
* FilesAppEntry.
*/
util.isFakeEntry = function(entry) {
return (entry.getParent === undefined);
return (
entry.getParent === undefined ||
(entry.isNativeType !== undefined && !entry.isNativeType));
};
/**
......@@ -905,6 +912,14 @@ util.isChildEntry = function(entry, directory) {
util.isDescendantEntry = function(ancestorEntry, childEntry) {
if (!ancestorEntry.isDirectory)
return false;
if (ancestorEntry instanceof EntryList) {
let entryList = /** @type {EntryList} */ (ancestorEntry);
return entryList.children.some(ancestorChild => {
let volumeEntry = ancestorChild.rootEntry;
return util.isSameEntry(volumeEntry, childEntry) ||
util.isDescendantEntry(volumeEntry, childEntry);
});
}
if (!util.isSameFileSystem(ancestorEntry.filesystem, childEntry.filesystem))
return false;
if (util.isSameEntry(ancestorEntry, childEntry))
......@@ -1121,6 +1136,8 @@ util.getRootTypeLabel = function(locationInfo) {
return str('RECENT_ROOT_LABEL');
case VolumeManagerCommon.RootType.CROSTINI:
return str('LINUX_FILES_ROOT_LABEL');
case VolumeManagerCommon.RootType.MY_FILES:
return str('MY_FILES_ROOT_LABEL');
case VolumeManagerCommon.RootType.MEDIA_VIEW:
var mediaViewRootType =
VolumeManagerCommon.getMediaViewRootTypeFromVolumeId(
......
......@@ -106,8 +106,11 @@ VolumeManagerCommon.RootType = {
// Root for crostini 'Linux Files'.
CROSTINI: 'crostini',
// Root for android files,
// Root for android files.
ANDROID_FILES: 'android_files',
// My Files root, which aggregates DOWNLOADS, ANDROID_FILES and CROSTINI.
MY_FILES: 'my_files',
};
Object.freeze(VolumeManagerCommon.RootType);
......@@ -139,6 +142,7 @@ VolumeManagerCommon.RootTypesForUMA = [
VolumeManagerCommon.RootType.ADD_NEW_SERVICES_MENU,
VolumeManagerCommon.RootType.CROSTINI,
VolumeManagerCommon.RootType.ANDROID_FILES,
VolumeManagerCommon.RootType.MY_FILES,
];
console.assert(
Object.keys(VolumeManagerCommon.RootType).length ===
......@@ -223,6 +227,7 @@ VolumeManagerCommon.VolumeType = {
MEDIA_VIEW: 'media_view',
CROSTINI: 'crostini',
ANDROID_FILES: 'android_files',
MY_FILES: 'my_files',
};
/**
......@@ -284,6 +289,8 @@ VolumeManagerCommon.getVolumeTypeFromRootType = function(rootType) {
return VolumeManagerCommon.VolumeType.CROSTINI;
case VolumeManagerCommon.RootType.ANDROID_FILES:
return VolumeManagerCommon.VolumeType.ANDROID_FILES;
case VolumeManagerCommon.RootType.MY_FILES:
return VolumeManagerCommon.VolumeType.MY_FILES;
}
assertNotReached('Unknown root type: ' + rootType);
};
......
......@@ -1226,6 +1226,10 @@ DirectoryModel.prototype.createDirectoryContents_ =
return DirectoryContents.createForCrostiniMounter(
context, /** @type {!FakeEntry} */ (entry));
}
if (entry.rootType == VolumeManagerCommon.RootType.MY_FILES) {
return DirectoryContents.createForDirectory(
context, /** @type {!FilesAppDirEntry} */ (entry));
}
if (query && canUseDriveSearch) {
// Drive search.
return DirectoryContents.createForDriveSearch(
......
......@@ -351,6 +351,14 @@ function FileManager() {
* @private
*/
this.initBackgroundPagePromise_ = null;
/**
* Flags async retrieved once at startup and can be used to switch behaviour
* on sync functions.
* @dict
* @private
*/
this.commandLineFlags_ = {};
}
FileManager.prototype = /** @struct */ {
......@@ -506,12 +514,16 @@ FileManager.prototype = /** @struct */ {
FileManager.prototype.startInitSettings_ = function() {
metrics.startInterval('Load.InitSettings');
this.appStateController_ = new AppStateController(this.dialogType);
return new Promise(function(resolve) {
this.appStateController_.loadInitialViewOptions().then(function() {
metrics.recordInterval('Load.InitSettings');
resolve();
});
}.bind(this));
return Promise
.all([
this.appStateController_.loadInitialViewOptions(),
util.isNewNavigationEnabled(),
])
.then(values => {
this.commandLineFlags_['new-files-app-navigation'] =
/** @type {boolean} */ (values[1]);
metrics.recordInterval('Load.InitSettings');
});
};
/**
......@@ -797,20 +809,20 @@ FileManager.prototype = /** @struct */ {
this.document_ = this.dialogDom_.ownerDocument;
metrics.startInterval('Load.InitDocuments');
return Promise.all([
this.initBackgroundPagePromise_,
window.importElementsPromise
]).then(function() {
metrics.recordInterval('Load.InitDocuments');
metrics.startInterval('Load.InitUI');
this.initEssentialUI_();
this.initAdditionalUI_();
return this.initSettingsPromise_;
}.bind(this)).then(function() {
this.initFileSystemUI_();
this.initUIFocus_();
metrics.recordInterval('Load.InitUI');
}.bind(this));
return Promise
.all([this.initBackgroundPagePromise_, window.importElementsPromise])
.then(function() {
metrics.recordInterval('Load.InitDocuments');
metrics.startInterval('Load.InitUI');
this.initEssentialUI_();
this.initAdditionalUI_();
return this.initSettingsPromise_;
}.bind(this))
.then(function() {
this.initFileSystemUI_();
this.initUIFocus_();
metrics.recordInterval('Load.InitUI');
}.bind(this));
};
/**
......@@ -1207,7 +1219,8 @@ FileManager.prototype = /** @struct */ {
new NavigationModelMenuItem(
str('ADD_NEW_SERVICES_BUTTON_LABEL'), '#add-new-services-menu',
'add-new-services') :
null);
null,
this.commandLineFlags_['new-files-app-navigation']);
this.setupCrostini_();
this.ui_.initDirectoryTree(directoryTree);
};
......
......@@ -11,6 +11,7 @@ var NavigationModelItemType = {
MENU: 'menu',
RECENT: 'recent',
CROSTINI: 'crostini',
ENTRY_LIST: 'entry-list',
};
/**
......@@ -139,11 +140,14 @@ NavigationModelFakeItem.prototype = /** @struct */ {
* The list of folder shortcut.
* @param {NavigationModelFakeItem} recentModelItem Recent folder.
* @param {NavigationModelMenuItem} addNewServicesItem Add new services item.
* @param {boolean=} opt_useNewNavigation true if should use the new navigation
* style, value should come from flag new-files-app-navigation.
* @constructor
* @extends {cr.EventTarget}
*/
function NavigationListModel(
volumeManager, shortcutListModel, recentModelItem, addNewServicesItem) {
volumeManager, shortcutListModel, recentModelItem, addNewServicesItem,
opt_useNewNavigation) {
cr.EventTarget.call(this);
/**
......@@ -219,6 +223,9 @@ function NavigationListModel(
this.shortcutList_.push(entryToModelItem(shortcutEntry));
}
// True if the flag new-files-app-navigation is enabled.
this.useNewNavigation_ = !!opt_useNewNavigation;
// Reorder volumes, shortcuts, and optional items for initial display.
this.reorderNavigationItems_();
......@@ -351,6 +358,19 @@ NavigationListModel.prototype = {
},
};
/**
* Reorder navigation items when command line flag new-files-app-navigation is
* enabled it nests Downloads, Linux and Android files under "My Files"; when
* it's disabled it has a flat structure with Linux Files after Recent menu.
*/
NavigationListModel.prototype.reorderNavigationItems_ = function() {
if (this.useNewNavigation_) {
return this.orderAndNestItems_();
} else {
return this.flatNavigationItems_();
}
};
/**
* Reorder navigation items in the following order:
* 1. Volumes.
......@@ -362,7 +382,7 @@ NavigationListModel.prototype = {
* 4. Add new services if it exists.
* @private
*/
NavigationListModel.prototype.reorderNavigationItems_ = function() {
NavigationListModel.prototype.flatNavigationItems_ = function() {
// Check if Linux files already mounted.
let linuxFilesMounted = false;
for (let i = 0; i < this.volumeList_.length; i++) {
......@@ -387,6 +407,128 @@ NavigationListModel.prototype.reorderNavigationItems_ = function() {
this.navigationItems_.push(this.addNewServicesItem_);
};
/**
* Reorder navigation items and nest some within "Downloads"
* which will be displayed as "My-Files". Desired order:
* 1. Recents.
* 2. Shortcuts.
* 3. "My-Files" (grouping), actually Downloads volume.
* 3.1. Downloads
* 3.2. Play files (android volume) (if enabled).
* 3.3. Linux files (crostini volume or fake item) (if enabled).
* 4. Other volumes (MTP, ARCHIVE, REMOVABLE).
* 5. Drive volumes.
* 6. Other FSP (File System Provider) (when mounted).
* 7. Add new services if (it exists).
* @private
*/
NavigationListModel.prototype.orderAndNestItems_ = function() {
const volumeIndexes = {};
const volumeList = this.volumeList_;
// Find the index of each volumeType from the array volumeList_,
// for volumes that can have multiple entries it saves as list
// of indexes, otherwise saves the index as int directly.
for (let i = 0; i < volumeList.length; i++) {
const volumeType = volumeList[i].volumeInfo.volumeType;
switch (volumeType) {
case VolumeManagerCommon.VolumeType.CROSTINI:
case VolumeManagerCommon.VolumeType.DOWNLOADS:
case VolumeManagerCommon.VolumeType.ANDROID_FILES:
volumeIndexes[volumeType] = i;
break;
case VolumeManagerCommon.VolumeType.REMOVABLE:
case VolumeManagerCommon.VolumeType.ARCHIVE:
case VolumeManagerCommon.VolumeType.MTP:
case VolumeManagerCommon.VolumeType.DRIVE:
case VolumeManagerCommon.VolumeType.PROVIDED:
case VolumeManagerCommon.VolumeType.MEDIA_VIEW:
if (!volumeIndexes[volumeType]) {
volumeIndexes[volumeType] = [i];
} else {
volumeIndexes[volumeType].push(i);
}
break;
default:
assertNotReached(`No explict order for VolumeType: "${volumeType}"`);
break;
}
}
/**
* @param {!VolumeManagerCommon.VolumeType} volumeType the desired volume type
* to be filtered from volumeList.
* @return {NavigationModelVolumeItem}
*/
const getSingleVolume = function(volumeType) {
return volumeList[volumeIndexes[volumeType]];
};
/**
* @param {!VolumeManagerCommon.VolumeType} volumeType the desired volume type
* to be filtered from volumeList.
* @return Array<!NavigationModelVolumeItem>
*/
const getVolumes = function(volumeType) {
const indexes = volumeIndexes[volumeType] || [];
return indexes.map(idx => volumeList[idx]);
};
// Items as per required order.
this.navigationItems_ = [];
if (this.recentModelItem_)
this.navigationItems_.push(this.recentModelItem_);
for (const shortcut of this.shortcutList_)
this.navigationItems_.push(shortcut);
const myFilesEntry = new EntryList(
str('MY_FILES_ROOT_LABEL'), VolumeManagerCommon.RootType.MY_FILES);
const myFilesModel = new NavigationModelFakeItem(
myFilesEntry.label, NavigationModelItemType.ENTRY_LIST, myFilesEntry);
const downloadsVolume =
getSingleVolume(VolumeManagerCommon.VolumeType.DOWNLOADS);
if (downloadsVolume)
myFilesEntry.addEntry(new VolumeEntry(downloadsVolume.volumeInfo));
this.navigationItems_.push(myFilesModel);
const androidVolume =
getSingleVolume(VolumeManagerCommon.VolumeType.ANDROID_FILES);
if (androidVolume)
myFilesEntry.addEntry(new VolumeEntry(androidVolume.volumeInfo));
const crostiniVolume =
getSingleVolume(VolumeManagerCommon.VolumeType.CROSTINI);
if (crostiniVolume) {
// Crostini is mounted so add it.
myFilesEntry.addEntry(new crostiniVolume.volumeInfo);
} else if (this.linuxFilesItem_) {
// Here it's just a fake item.
myFilesEntry.addEntry(this.linuxFilesItem_.entry);
}
// Join MEDIA_VIEW, MTP, ARCHIVE and REMOVABLE.
// TODO(lucmult) sort based on the index number to preserve original order.
const otherVolumes = [].concat(
getVolumes(VolumeManagerCommon.VolumeType.MEDIA_VIEW),
getVolumes(VolumeManagerCommon.VolumeType.REMOVABLE),
getVolumes(VolumeManagerCommon.VolumeType.ARCHIVE),
getVolumes(VolumeManagerCommon.VolumeType.MTP));
for (const volume of otherVolumes)
this.navigationItems_.push(volume);
for (const driveItem of getVolumes(VolumeManagerCommon.VolumeType.DRIVE))
this.navigationItems_.push(driveItem);
for (const provided of getVolumes(VolumeManagerCommon.VolumeType.PROVIDED))
this.navigationItems_.push(provided);
if (this.addNewServicesItem_)
this.navigationItems_.push(this.addNewServicesItem_);
};
/**
* Returns the item at the given index.
* @param {number} index The index of the entry to get.
......
......@@ -11,8 +11,18 @@ var hoge;
// Set up string assets.
loadTimeData.data = {
DRIVE_DIRECTORY_LABEL: 'My Drive',
DRIVE_MY_DRIVE_LABEL: 'My Drive',
DRIVE_TEAM_DRIVES_LABEL: 'Team Drives',
DRIVE_OFFLINE_COLLECTION_LABEL: 'Offline',
DRIVE_SHARED_WITH_ME_COLLECTION_LABEL: 'Shared with me',
DRIVE_RECENT_COLLECTION_LABEL: 'Recents',
DOWNLOADS_DIRECTORY_LABEL: 'Downloads',
LINUX_FILES_ROOT_LABEL: 'Linux Files',
MY_FILES_ROOT_LABEL: 'My Files',
RECENT_ROOT_LABEL: 'Recent',
MEDIA_VIEW_IMAGES_ROOT_LABEL: 'Images',
MEDIA_VIEW_VIDEOS_ROOT_LABEL: 'Videos',
MEDIA_VIEW_AUDIO_ROOT_LABEL: 'Audio',
};
function setUp() {
......
......@@ -145,6 +145,10 @@ function DirectoryItem(label, tree) {
// prototype.
var labelId = item.labelElement.id;
item.__proto__ = DirectoryItem.prototype;
if (window.IN_TEST) {
item.setAttribute('dir-type', 'DirectoryItem');
item.setAttribute('entry-label', label);
}
item.parentTree_ = tree;
item.directoryModel_ = tree.directoryModel;
item.fileFilter_ = tree.directoryModel.getFileFilter();
......@@ -536,6 +540,8 @@ function SubDirectoryItem(label, dirEntry, parentDirItem, tree) {
var item = new DirectoryItem(label, tree);
item.__proto__ = SubDirectoryItem.prototype;
if (window.IN_TEST)
item.setAttribute('dir-type', 'SubDirectoryItem');
item.entry = dirEntry;
item.delayExpansion = parentDirItem.delayExpansion;
......@@ -595,6 +601,96 @@ SubDirectoryItem.prototype.updateSharedStatusIcon = function() {
});
};
/**
* A directory of entries. Each element represents an entry.
*
* @param {VolumeManagerCommon.RootType} rootType The root type to record.
* @param {!NavigationModelFakeItem} modelItem NavigationModelItem of this
* volume.
* @param {DirectoryTree} tree Current tree, which contains this item.
* @extends {DirectoryItem}
* @constructor
*/
function EntryListItem(rootType, modelItem, tree) {
var item = new DirectoryItem(modelItem.label, tree);
// Get the original label id defined by TreeItem, before overwriting
// prototype.
item.__proto__ = EntryListItem.prototype;
if (window.IN_TEST)
item.setAttribute('dir-type', 'EntryListItem');
item.entries_ = [];
item.rootType_ = rootType;
item.modelItem_ = modelItem;
item.dirEntry_ = modelItem.entry;
item.parentTree_ = tree;
var icon = queryRequiredElement('.icon', item);
icon.classList.add('item-icon');
item.setAttribute('root-type-icon', rootType);
return item;
}
EntryListItem.prototype = {
__proto__: DirectoryItem.prototype,
/**
* The DirectoryEntry corresponding to this DirectoryItem. This may be
* a dummy DirectoryEntry.
* @type {DirectoryEntry|Object}
*/
get entry() {
return this.dirEntry_;
},
/**
* The element containing the label text and the icon.
* @type {!HTMLElement}
* @override
*/
get labelElement() {
return this.firstElementChild.querySelector('.label');
},
/**
* @type {!NavigationModelVolumeItem}
*/
get modelItem() {
return this.modelItem_;
}
};
/**
* Retrieves the subdirectories and update them on the tree. Runs synchronously,
* since EntryList has its subdirectories already in memory.
* @param {boolean} recursive True if the update is recursively.
* @param {function()=} opt_successCallback Callback called on success.
* @param {function()=} opt_errorCallback Callback called on error.
*/
EntryListItem.prototype.updateSubDirectories = function(
recursive, opt_successCallback, opt_errorCallback) {
if (!this.entry) {
opt_errorCallback && opt_errorCallback();
return;
}
this.entries_ = [];
if (this.entry && this.entry.children) {
for (let childEntry of this.entry.children) {
if (childEntry instanceof VolumeEntry) {
// For VolumeEntry we wan't to display its root.
this.entries_.push(childEntry.rootEntry);
} else {
this.entries_.push(childEntry);
}
}
}
if (this.entries_.length > 0) {
this.expanded = true;
}
this.updateSubElementsFromList(recursive);
opt_successCallback && opt_successCallback();
};
////////////////////////////////////////////////////////////////////////////////
// VolumeItem
......@@ -621,8 +717,10 @@ function VolumeItem(modelItem, tree) {
item.delayExpansion = (item.volumeInfo.volumeType === 'provided');
// Set helper attribute for testing.
if (window.IN_TEST)
if (window.IN_TEST) {
item.setAttribute('volume-type-for-testing', item.volumeInfo_.volumeType);
item.setAttribute('dir-type', 'VolumeItem');
}
item.setupIcon_(item.querySelector('.icon'), item.volumeInfo_);
......@@ -797,6 +895,8 @@ function DriveVolumeItem(modelItem, tree) {
var item = new VolumeItem(modelItem, tree);
item.__proto__ = DriveVolumeItem.prototype;
item.classList.add('drive-volume');
if (window.IN_TEST)
item.setAttribute('dir-type', 'DriveVolumeItem');
return item;
}
......@@ -960,6 +1060,8 @@ function ShortcutItem(modelItem, tree) {
var labelId = item.labelElement.id;
item.__proto__ = ShortcutItem.prototype;
if (window.IN_TEST)
item.setAttribute('dir-type', 'ShortcutItem');
item.parentTree_ = tree;
item.dirEntry_ = modelItem.entry;
item.modelItem_ = modelItem;
......@@ -1093,6 +1195,10 @@ function MenuItem(modelItem, tree) {
// prototype.
var labelId = item.labelElement.id;
item.__proto__ = MenuItem.prototype;
if (window.IN_TEST) {
item.setAttribute('dir-type', 'MenuItem');
item.setAttribute('entry-label', modelItem.label);
}
item.parentTree_ = tree;
item.modelItem_ = modelItem;
......@@ -1177,6 +1283,10 @@ function FakeItem(rootType, modelItem, tree) {
// prototype.
var labelId = item.labelElement.id;
item.__proto__ = FakeItem.prototype;
if (window.IN_TEST) {
item.setAttribute('dir-type', 'FakeItem');
item.setAttribute('entry-label', modelItem.label);
}
item.rootType_ = rootType;
item.parentTree_ = tree;
......@@ -1185,6 +1295,7 @@ function FakeItem(rootType, modelItem, tree) {
item.innerHTML = TREE_ITEM_INNER_HTML;
item.labelElement.id = labelId;
item.label = modelItem.label;
item.directoryModel_ = tree.directoryModel;
var icon = queryRequiredElement('.icon', item);
icon.classList.add('item-icon');
......@@ -1239,6 +1350,15 @@ FakeItem.prototype.activate = function() {
this.parentTree_.directoryModel.activateDirectoryEntry(this.entry);
};
/**
* FakeItem doesn't really have sub-directories, it's defined here only to have
* the same API of other Items on this file.
*/
FakeItem.prototype.updateSubDirectories = function(
recursive, opt_successCallback, opt_errorCallback) {
return opt_successCallback && opt_successCallback();
};
////////////////////////////////////////////////////////////////////////////////
// DirectoryTree
......@@ -1335,6 +1455,53 @@ DirectoryTree.prototype = {
cr.defineProperty(DirectoryTree, 'contextMenuForSubitems', cr.PropertyKind.JS);
cr.defineProperty(DirectoryTree, 'contextMenuForRootItems', cr.PropertyKind.JS);
/**
* Creates a new DirectoryItem based on |modelItem|.
* @param {NavigationModelItem} modelItem, model that will determine the type of
* DirectoryItem to be created.
* @param {!DirectoryTree} tree The tree to add the new DirectoryItem to.
* @return {!cr.ui.TreeItem} a newly created instance of a
* DirectoryItem type.
*/
DirectoryTree.createDirectoryItem = function(modelItem, tree) {
switch (modelItem.type) {
case NavigationModelItemType.VOLUME:
const volumeModelItem =
/** @type {NavigationModelVolumeItem} */ (modelItem);
if (volumeModelItem.volumeInfo.volumeType ===
VolumeManagerCommon.VolumeType.DRIVE) {
return new DriveVolumeItem(volumeModelItem, tree);
} else {
return new VolumeItem(volumeModelItem, tree);
}
break;
case NavigationModelItemType.SHORTCUT:
return new ShortcutItem(
/** @type {!NavigationModelShortcutItem} */ (modelItem), tree);
break;
case NavigationModelItemType.MENU:
return new MenuItem(
/** @type {!NavigationModelMenuItem} */ (modelItem), tree);
break;
case NavigationModelItemType.RECENT:
return new FakeItem(
VolumeManagerCommon.RootType.RECENT,
/** @type {!NavigationModelFakeItem} */ (modelItem), tree);
break;
case NavigationModelItemType.CROSTINI:
return new FakeItem(
VolumeManagerCommon.RootType.CROSTINI,
/** @type {!NavigationModelFakeItem} */ (modelItem), tree);
break;
case NavigationModelItemType.ENTRY_LIST:
return new EntryListItem(
VolumeManagerCommon.RootType.MY_FILES,
/** @type {!NavigationModelFakeItem} */ (modelItem), tree);
break;
}
assertNotReached(`No DirectoryItem model: "${modelItem.type}"`);
};
/**
* Updates and selects new directory.
* @param {!DirectoryEntry} parentDirectory Parent directory of new directory.
......@@ -1409,33 +1576,10 @@ DirectoryTree.prototype.updateSubElementsFromList = function(recursive) {
this.items[itemIndex].updateSubDirectories(true);
} else {
var modelItem = this.dataModel.item(modelIndex);
switch (modelItem.type) {
case NavigationModelItemType.VOLUME:
if (modelItem.volumeInfo.volumeType ===
VolumeManagerCommon.VolumeType.DRIVE) {
this.addAt(new DriveVolumeItem(modelItem, this), itemIndex);
} else {
this.addAt(new VolumeItem(modelItem, this), itemIndex);
}
break;
case NavigationModelItemType.SHORTCUT:
this.addAt(new ShortcutItem(modelItem, this), itemIndex);
break;
case NavigationModelItemType.MENU:
this.addAt(new MenuItem(modelItem, this), itemIndex);
break;
case NavigationModelItemType.RECENT:
this.addAt(
new FakeItem(
VolumeManagerCommon.RootType.RECENT, modelItem, this),
itemIndex);
break;
case NavigationModelItemType.CROSTINI:
this.addAt(
new FakeItem(
VolumeManagerCommon.RootType.CROSTINI, modelItem, this),
itemIndex);
break;
if (modelItem) {
var item = DirectoryTree.createDirectoryItem(modelItem, this);
if (item)
this.addAt(item, itemIndex);
}
}
itemIndex++;
......
......@@ -60,7 +60,7 @@ LocationLine.prototype.replaceRootName_ = function(url, newRoot) {
/**
* Get components for the path of entry.
* @param {!Entry|!FakeEntry} entry An entry.
* @param {!Entry|!FakeEntry|!FilesAppEntry} entry An entry.
* @return {!Array<!LocationLine.PathComponent>} Components.
* @private
*/
......
// Copyright 2018 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.
/**
* Tests if MyFiles is displayed when flag is true.
*/
testcase.showMyFiles = function() {
let appId;
const expectedElementLabels = [
'Recent: FakeItem',
'My Files: EntryListItem',
'Downloads: SubDirectoryItem',
'Linux Files: SubDirectoryItem',
'Google Drive: DriveVolumeItem',
'My Drive: SubDirectoryItem',
'Shared with me: SubDirectoryItem',
'Offline: SubDirectoryItem',
'Add new services: MenuItem',
];
StepsRunner.run([
// Open Files app on local Downloads.
function() {
setupAndWaitUntilReady(
null, RootPath.DOWNLOADS, this.next, [ENTRIES.beautiful], []);
},
// Get the directory tree elements.
function(results) {
appId = results.windowId;
const dirTreeQuery = ['#directory-tree [dir-type]'];
remoteCall.callRemoteTestUtil('queryAllElements', appId, dirTreeQuery)
.then(this.next);
},
// Check tree elements for the correct order and label/element type.
function(elements) {
var visibleElements = [];
for (let element of elements) {
if (!element.hidden) { // Ignore hidden elements.
visibleElements.push(
element.attributes['entry-label'] + ': ' +
element.attributes['dir-type']);
}
}
chrome.test.assertEq(expectedElementLabels, visibleElements);
this.next();
},
function() {
checkIfNoErrorsOccured(this.next);
},
]);
};
......@@ -27,6 +27,7 @@
"file_manager/gear_menu.js",
"file_manager/grid_view.js",
"file_manager/keyboard_operations.js",
"file_manager/my_files.js",
"file_manager/open_audio_files.js",
"file_manager/open_image_files.js",
"file_manager/open_video_files.js",
......
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