Skip to content

Commit

Permalink
Reland "[Files F2] Add actions/reducers for volume/navigation"
Browse files Browse the repository at this point in the history
This is a reland of commit 11e5c12,
the previous failure was related to "update rootType for VolumeEntry",
that line is now removed. There's a also a TODO added when creating
ScannerFactory: instead of solely depending on rootType, we need to
aso make sure only creating CrostniMounter for fake crostini entry.

Failed tast tests mentioned in b/272557385 has been tested manually,
they all passed locally on my DUT.

Original change's description:
> [Files F2] Add actions/reducers for volume/navigation
>
> * Add actions/reducers for:
>   * volume: volume information (e.g. data from VolumeInfo and
>   VolumeMetadata)
>   * folderShortcuts: shortcut information
>   * uiEntries: fake entries on the UI (e.g. FakeDriveRoot, Trash)
>   * androidApps: Android app information (for File picker)
>   * navigation: roots for the navigation tree
> * Add an action reducer to read child entries and store the children
> in the store.
> * Replicate all nesting logic from NavigationListModel in the store.
>
> Bug: b:228139957
> Change-Id: I33a68e9f3fea5a7a0da757aab7c63fed6d680e25
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4150614
> Commit-Queue: Wenbo Jie <wenbojie@chromium.org>
> Reviewed-by: Luciano Pacheco <lucmult@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1114259}

Bug: b:228139957
Change-Id: I483bae1e9545d94eb58bf05e78f1a4f53dc09f9d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4337056
Reviewed-by: Luciano Pacheco <lucmult@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1117313}
  • Loading branch information
PinkyJie committed Mar 15, 2023
1 parent 353ca9f commit c1a4323
Show file tree
Hide file tree
Showing 32 changed files with 3,689 additions and 86 deletions.
24 changes: 24 additions & 0 deletions chrome/browser/ash/file_manager/file_manager_jstest.cc
Expand Up @@ -310,6 +310,10 @@ IN_PROC_BROWSER_TEST_F(FileManagerJsTest, ActionsProducer) {
RunTestURL("lib/actions_producer_unittest.js");
}

IN_PROC_BROWSER_TEST_F(FileManagerJsTest, ActionsProducerAllEntries) {
RunTestURL("state/actions_producers/all_entries_unittest.js");
}

IN_PROC_BROWSER_TEST_F(FileManagerJsTest, BaseStore) {
RunTestURL("lib/base_store_unittest.js");
}
Expand All @@ -318,14 +322,34 @@ IN_PROC_BROWSER_TEST_F(FileManagerJsTest, ReducerAllEntries) {
RunTestURL("state/reducers/all_entries_unittest.js");
}

IN_PROC_BROWSER_TEST_F(FileManagerJsTest, ReducerAndroidApps) {
RunTestURL("state/reducers/android_apps_unittest.js");
}

IN_PROC_BROWSER_TEST_F(FileManagerJsTest, ReducerFolderShortcuts) {
RunTestURL("state/reducers/folder_shortcuts_unittest.js");
}

IN_PROC_BROWSER_TEST_F(FileManagerJsTest, ReducerCurrentDirectory) {
RunTestURL("state/reducers/current_directory_unittest.js");
}

IN_PROC_BROWSER_TEST_F(FileManagerJsTest, ReducerNavigation) {
RunTestURL("state/reducers/navigation_unittest.js");
}

IN_PROC_BROWSER_TEST_F(FileManagerJsTest, ReducerSearch) {
RunTestURL("state/reducers/search_unittest.js");
}

IN_PROC_BROWSER_TEST_F(FileManagerJsTest, ReducerUiEntries) {
RunTestURL("state/reducers/ui_entries_unittest.js");
}

IN_PROC_BROWSER_TEST_F(FileManagerJsTest, ReducerVolumes) {
RunTestURL("state/reducers/volumes_unittest.js");
}

IN_PROC_BROWSER_TEST_F(FileManagerJsTest, NudgeContainer) {
RunTestURL("containers/nudge_container_unittest.js");
}
Expand Down
108 changes: 107 additions & 1 deletion ui/file_manager/file_manager/common/js/entry_utils.ts
Expand Up @@ -2,9 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {FilesAppEntry} from '../../externs/files_app_entry_interfaces.js';
import {EntryType, FileData} from '../../externs/ts/state.js';
import {driveRootEntryListKey, myFilesEntryListKey} from '../../state/reducers/volumes.js';

import type {VolumeEntry} from './files_app_entry_types.js';
import {EntryList, FakeEntryImpl, VolumeEntry} from './files_app_entry_types.js';
import {util} from './util.js';
import {VolumeManagerCommon} from './volume_manager_types.js';

/**
* Type guard used to identify if a generic FileSystemEntry is actually a
Expand Down Expand Up @@ -37,3 +41,105 @@ export function getNativeEntry(fileData: FileData): Entry|null {
}
return null;
}

/**
* Type guard used to identify if a given entry is actually a
* VolumeEntry.
*/
export function isVolumeEntry(entry: Entry|
FilesAppEntry): entry is VolumeEntry {
return 'volumeInfo' in entry;
}

/**
* Check if the entry is MyFiles or not.
* Note: if the return value is true, the input entry is guaranteed to be
* EntryList or VolumeEntry type.
*/
export function isMyFilesEntry(entry: Entry|FilesAppEntry|
null): entry is VolumeEntry|EntryList {
if (!entry) {
return false;
}
if (entry instanceof EntryList && entry.toURL() === myFilesEntryListKey) {
return true;
}
if (isVolumeEntry(entry) &&
entry.rootType === VolumeManagerCommon.RootType.DOWNLOADS) {
return true;
}

return false;
}

/**
* Check if the entry is the drive root entry list ("Google Drive" wrapper).
* Note: if the return value is true, the input entry is guaranteed to be
* EntryList type.
*/
export function isDriveRootEntryList(entry: Entry|FilesAppEntry|
null): entry is EntryList {
if (!entry) {
return false;
}
return entry.toURL() === driveRootEntryListKey;
}

/**
* Given an entry, check if it's a grand root ("Shared drives" and
* "Computers") inside Drive.
* Note: if the return value is true, the input entry is guaranteed to be
* DirectoryEntry type.
*/
export function isGrandRootEntryInDrives(entry: Entry|FilesAppEntry):
entry is DirectoryEntry {
const {fullPath} = entry;
return fullPath === VolumeManagerCommon.SHARED_DRIVES_DIRECTORY_PATH ||
fullPath === VolumeManagerCommon.COMPUTERS_DIRECTORY_PATH;
}

/**
* Given an entry, check if it's a fake entry ("Shared with me" and "Offline")
* inside Drive.
*/
export function isFakeEntryInDrives(entry: Entry|
FilesAppEntry): entry is FakeEntryImpl {
if (!(entry instanceof FakeEntryImpl)) {
return false;
}
const {rootType} = entry;

return rootType === VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME ||
rootType === VolumeManagerCommon.RootType.DRIVE_OFFLINE;
}

/** Sort the entries based on the filter and the names. */
export function sortEntries(
parentEntry: Entry|FilesAppEntry,
entries: Array<Entry|FilesAppEntry>): Array<Entry|FilesAppEntry> {
if (entries.length === 0) {
return [];
}
// TODO: proper way to get directory model and volume manager.
const {directoryModel, volumeManager} = window.fileManager;
const fileFilter = directoryModel.getFileFilter();
// For entries under My Files we need to use a different sorting logic
// because we need to make sure curtain files are always at the bottom.
if (isMyFilesEntry(parentEntry)) {
// Use locationInfo from first entry because it only compare within the
// same volume.
// TODO(b/271485133): Do not use getLocationInfo() for sorting.
const locationInfo = volumeManager.getLocationInfo(entries[0]!);
if (locationInfo) {
const compareFunction = util.compareLabelAndGroupBottomEntries(
locationInfo,
// Only Linux/Play/GuestOS files are in the UI children.
parentEntry.getUIChildren(),
);
return entries.filter(entry => fileFilter.filter(entry))
.sort(compareFunction);
}
}
return entries.filter(entry => fileFilter.filter(entry))
.sort(util.compareName);
}
1 change: 1 addition & 0 deletions ui/file_manager/file_manager/definitions/file_manager.d.ts
Expand Up @@ -17,6 +17,7 @@ interface FileManager {
selectionHandler: FileSelectionHandler;
taskController: TaskController;
dialogType: DialogType;
directoryModel: DirectoryModel;
}

/**
Expand Down
4 changes: 4 additions & 0 deletions ui/file_manager/file_manager/externs/ts/state.js
Expand Up @@ -36,6 +36,10 @@ export const EntryType = {
* * `icon` can be either a string or a IconSet which is an object including
* both high/low DPI icon data.
*
* TODO(b/271485133): `children` here only store sub directories for now, it
* should store all children including files, it's up to the container to do
* filter and sorting if needed.
*
* @typedef {{
* entry: (Entry|FilesAppEntry),
* icon: (!string|!chrome.fileManagerPrivate.IconSet),
Expand Down
Expand Up @@ -16,7 +16,7 @@ import {createTrashReaders} from '../../common/js/trash.js';
import {util} from '../../common/js/util.js';
import {VolumeManagerCommon} from '../../common/js/volume_manager_types.js';
import {EntryLocation} from '../../externs/entry_location.js';
import {FakeEntry, FilesAppDirEntry} from '../../externs/files_app_entry_interfaces.js';
import {FakeEntry, FilesAppDirEntry, FilesAppEntry} from '../../externs/files_app_entry_interfaces.js';
import {SearchFileType, SearchLocation, SearchOptions, SearchRecency} from '../../externs/ts/state.js';
import {VolumeManager} from '../../externs/volume_manager.js';
import {getDefaultSearchOptions} from '../../state/store.js';
Expand Down Expand Up @@ -705,8 +705,8 @@ export class FileFilter extends EventTarget {

/**
* @param {string} name Filter identifier.
* @param {function(Entry)} callback A filter - a function receiving an Entry,
* and returning bool.
* @param {function((Entry|FilesAppEntry))} callback A filter - a function
* receiving an Entry, and returning bool.
*/
addFilter(name, callback) {
this.filters_[name] = callback;
Expand Down Expand Up @@ -807,7 +807,7 @@ export class FileFilter extends EventTarget {
}

/**
* @param {Entry} entry File entry.
* @param {Entry|FilesAppEntry} entry File entry.
* @return {boolean} True if the file should be shown, false otherwise.
*/
filter(entry) {
Expand Down
2 changes: 2 additions & 0 deletions ui/file_manager/file_manager/foreground/js/directory_model.js
Expand Up @@ -1468,6 +1468,8 @@ export class DirectoryModel extends EventTarget {
);
};
}
// TODO(b/271485133): Make sure the entry here is a fake entry, not real
// volume entry.
if (entry.rootType == VolumeManagerCommon.RootType.CROSTINI) {
return () => {
return new CrostiniMounter();
Expand Down
26 changes: 23 additions & 3 deletions ui/file_manager/file_manager/state/actions.ts
Expand Up @@ -5,8 +5,13 @@
import {SearchData} from '../externs/ts/state.js';
import {BaseAction} from '../lib/base_store.js';

import {ClearStaleCachedEntriesAction, UpdateMetadataAction} from './actions/all_entries.js';
import {AddChildEntriesAction, ClearStaleCachedEntriesAction, UpdateMetadataAction} from './actions/all_entries.js';
import {AddAndroidAppsAction} from './actions/android_apps.js';
import {ChangeDirectoryAction, ChangeFileTasksAction, ChangeSelectionAction, UpdateDirectoryContentAction} from './actions/current_directory.js';
import {AddFolderShortcutAction, RefreshFolderShortcutAction, RemoveFolderShortcutAction} from './actions/folder_shortcuts.js';
import {RefreshNavigationRootsAction, UpdateNavigationEntryAction} from './actions/navigation.js';
import {AddUiEntryAction, RemoveUiEntryAction} from './actions/ui_entries.js';
import {AddVolumeAction, RemoveVolumeAction} from './actions/volumes.js';

/**
* Union of all types of Actions in Files app.
Expand All @@ -15,20 +20,35 @@ import {ChangeDirectoryAction, ChangeFileTasksAction, ChangeSelectionAction, Upd
* A good explanation of this feature is here:
* https://mariusschulz.com/blog/tagged-union-types-in-typescript
*/
export type Action = ChangeDirectoryAction|ChangeSelectionAction|
export type Action = AddVolumeAction|RemoveVolumeAction|
RefreshNavigationRootsAction|ChangeDirectoryAction|ChangeSelectionAction|
ChangeFileTasksAction|ClearStaleCachedEntriesAction|SearchAction|
UpdateDirectoryContentAction|UpdateMetadataAction;
AddUiEntryAction|RemoveUiEntryAction|UpdateDirectoryContentAction|
UpdateMetadataAction|RefreshFolderShortcutAction|AddFolderShortcutAction|
RemoveFolderShortcutAction|AddAndroidAppsAction|AddChildEntriesAction|
UpdateNavigationEntryAction;


/** Enum to identify every Action in Files app. */
export const enum ActionType {
ADD_VOLUME = 'add-volume',
REMOVE_VOLUME = 'remove-volume',
ADD_UI_ENTRY = 'add-ui-entry',
REMOVE_UI_ENTRY = 'remove-ui-entry',
REFRESH_FOLDER_SHORTCUT = 'refresh-folder-shortcut',
ADD_FOLDER_SHORTCUT = 'add-folder-shortcut',
REMOVE_FOLDER_SHORTCUT = 'remove-folder-shortcut',
ADD_ANDROID_APPS = 'add-android-apps',
REFRESH_NAVIGATION_ROOTS = 'refresh-navigation-roots',
UPDATE_NAVIGATION_ENTRY = 'update-navigation-entry',
CHANGE_DIRECTORY = 'change-directory',
CHANGE_SELECTION = 'change-selection',
CHANGE_FILE_TASKS = 'change-file-tasks',
CLEAR_STALE_CACHED_ENTRIES = 'clear-stale-cached-entries',
SEARCH = 'search',
UPDATE_DIRECTORY_CONTENT = 'update-directory-content',
UPDATE_METADATA = 'update-metadata',
ADD_CHILD_ENTRIES = 'add-child-entries',
}


Expand Down
20 changes: 20 additions & 0 deletions ui/file_manager/file_manager/state/actions/all_entries.ts
Expand Up @@ -6,6 +6,7 @@ import {FilesAppEntry} from '../../externs/files_app_entry_interfaces.js';
import {MetadataItem} from '../../foreground/js/metadata/metadata_item.js';
import {BaseAction} from '../../lib/base_store.js';
import {ActionType} from '../actions.js';
import {FileKey} from '../file_key.js';

/**
* Processes the allEntries and removes any entry that isn't in use any more.
Expand All @@ -30,6 +31,16 @@ export interface UpdateMetadataAction extends BaseAction {
};
}

/** Action to add child entries to a given parent entry. */
export interface AddChildEntriesAction extends BaseAction {
type: ActionType.ADD_CHILD_ENTRIES;
payload: {
parentKey: FileKey,
entries: Array<Entry|FilesAppEntry>,
};
}


/** Factory for the UpdateMetadataAction. */
export function updateMetadata(payload: UpdateMetadataAction['payload']):
UpdateMetadataAction {
Expand All @@ -38,3 +49,12 @@ export function updateMetadata(payload: UpdateMetadataAction['payload']):
payload,
};
}

/** Action factory to add child entries to a given parent entry. */
export function addChildEntries(payload: AddChildEntriesAction['payload']):
AddChildEntriesAction {
return {
type: ActionType.ADD_CHILD_ENTRIES,
payload,
};
}
31 changes: 31 additions & 0 deletions ui/file_manager/file_manager/state/actions/android_apps.ts
@@ -0,0 +1,31 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {BaseAction} from '../../lib/base_store.js';
import {ActionType} from '../actions.js';

/**
* Actions for Android apps.
*
* Android App is something we get from private API
* `chrome.fileManagerPrivate.getAndroidPickerApps`, it will be shown as
* a directory item in FilePicker mode.
*/

/** Action to add all android app config to the store. */
export interface AddAndroidAppsAction extends BaseAction {
type: ActionType.ADD_ANDROID_APPS;
payload: {
apps: chrome.fileManagerPrivate.AndroidApp[],
};
}

/** Action factory to add all android app config to the store. */
export function addAndroidApps(payload: AddAndroidAppsAction['payload']):
AddAndroidAppsAction {
return {
type: ActionType.ADD_ANDROID_APPS,
payload,
};
}

0 comments on commit c1a4323

Please sign in to comment.