Skip to content

Commit

Permalink
Files F2: Add support for search and Backspace key for new Breadcrumbs
Browse files Browse the repository at this point in the history
For the Backspace key shortcut, it uses the Store current state to
find the FileKey for the parent directory.

For search, this CL adds a new state `search` with the searched term
and the status.  This is simplified, it only pushes the search term to
the store after the search result scanner has finished.  It also updates
the Store when the search is cleared.

The new breadcrumbs container listens to the search state and displays
the Drive root when searching within Drive.

Bug: b:228114099
Change-Id: I289a3ed7085463949655649b21288ce3d28d15a2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3811077
Reviewed-by: Ben Reich <benreich@chromium.org>
Commit-Queue: Luciano Pacheco <lucmult@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1032431}
  • Loading branch information
Luciano Pacheco authored and Chromium LUCI CQ committed Aug 8, 2022
1 parent 083d224 commit 8f4d9a8
Show file tree
Hide file tree
Showing 14 changed files with 142 additions and 34 deletions.
6 changes: 6 additions & 0 deletions chrome/browser/ash/file_manager/file_manager_browsertest.cc
Expand Up @@ -1134,6 +1134,9 @@ WRAPPED_INSTANTIATE_TEST_SUITE_P(
TestCase("drivePressEnterToSearch").FilesSwa(),
TestCase("driveSearchAlwaysDisplaysMyDrive"),
TestCase("driveSearchAlwaysDisplaysMyDrive").FilesSwa(),
TestCase("driveSearchAlwaysDisplaysMyDrive")
.FilesSwa()
.FilesExperimental(),
TestCase("drivePressClearSearch"),
TestCase("drivePressClearSearch").FilesSwa(),
TestCase("drivePressCtrlAFromSearch"),
Expand Down Expand Up @@ -1742,6 +1745,9 @@ WRAPPED_INSTANTIATE_TEST_SUITE_P(
TestCase("showMyFiles").EnableTrash().FilesSwa(),
TestCase("myFilesDisplaysAndOpensEntries"),
TestCase("myFilesDisplaysAndOpensEntries").FilesSwa(),
TestCase("myFilesDisplaysAndOpensEntries")
.FilesSwa()
.FilesExperimental(),
TestCase("myFilesFolderRename"),
TestCase("myFilesFolderRename").FilesSwa(),
TestCase("myFilesUpdatesWhenAndroidVolumeMounts").DontMountVolumes(),
Expand Down
43 changes: 30 additions & 13 deletions ui/file_manager/file_manager/containers/breadcrumb_container.ts
Expand Up @@ -5,7 +5,8 @@
import '../widgets/xf_breadcrumb.js';

import {metrics} from '../common/js/metrics.js';
import {CurrentDirectory, PropStatus, State} from '../externs/ts/state.js';
import {VolumeManagerCommon} from '../common/js/volume_manager_types.js';
import {PathComponent, PropStatus, State} from '../externs/ts/state.js';
import {changeDirectory} from '../state/actions.js';
import {FileKey} from '../state/file_key.js';
import {getStore, Store} from '../state/store.js';
Expand All @@ -31,16 +32,34 @@ export class BreadcrumbContainer {
}

onStateChanged(state: State) {
const currentDir = state.currentDirectory;
const key = currentDir && currentDir.key;
if (!key || !currentDir) {
const {currentDirectory, search} = state;
let key = currentDirectory && currentDirectory.key;
if (!key || !currentDirectory) {
this.hide_();
return;
}

if (currentDir.status == PropStatus.SUCCESS &&
// If the current location is somewhere in Drive, all files in Drive can
// be listed as search results regardless of current location.
// In this case, showing current location is confusing, so use the Drive
// root "My Drive" as the current location.
if (search && search.term && search.status === PropStatus.SUCCESS) {
const entry = state.allEntries[currentDirectory.key];
if (entry && entry.volumeType === VolumeManagerCommon.VolumeType.DRIVE) {
const root = currentDirectory.pathComponents[0];
if (root) {
key = root.key;
this.show_(root.key!, [root]);
return;
}
}
}

if (currentDirectory.status == PropStatus.SUCCESS &&
this.currentFileKey_ !== key) {
this.show_(state.currentDirectory);
this.show_(
state.currentDirectory?.key || '',
state.currentDirectory?.pathComponents || []);
}
}

Expand All @@ -51,22 +70,20 @@ export class BreadcrumbContainer {
}
}

private show_(currentDir?: CurrentDirectory) {
private show_(key: FileKey, pathComponents: PathComponent[]) {
let breadcrumb = document.querySelector('xf-breadcrumb');
if (!breadcrumb) {
breadcrumb = document.createElement('xf-breadcrumb');
breadcrumb.id = 'breadcrumbs';
breadcrumb.addEventListener(
BREADCRUMB_CLICKED, this.breadcrumbClick_.bind(this));
this.container_.appendChild(breadcrumb);
}

const path = !currentDir ?
'' :
currentDir.pathComponents.map(p => p.label).join('/');
const path = pathComponents.map(p => p.label).join('/');
breadcrumb!.path = path;
this.currentFileKey_ = currentDir ? currentDir.key : null;
this.pathKeys_ =
currentDir ? currentDir.pathComponents.map(p => p.key) : [];
this.currentFileKey_ = key;
this.pathKeys_ = pathComponents.map(p => p.key);
}

private breadcrumbClick_(event: BreadcrumbClickedEvent) {
Expand Down
12 changes: 12 additions & 0 deletions ui/file_manager/file_manager/externs/ts/state.js
Expand Up @@ -2,12 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {VolumeManagerCommon} from '../../common/js/volume_manager_types.js';
import {FilesAppEntry} from '../files_app_entry_interfaces.js';

/**
* The data for each individual file/entry.
* @typedef {{
* entry: (Entry|FilesAppEntry),
* volumeType: (VolumeManagerCommon.VolumeType|null),
* }}
*/
export let FileData;
Expand Down Expand Up @@ -46,11 +48,21 @@ export const PropStatus = {
*/
export let CurrentDirectory;

/**
* Data for search. It should be empty `{}` when the user isn't searching.
* @typedef {{
* status: (PropStatus|undefined),
* term: (string|undefined),
* }}
*/
export let SearchData;

/**
* Files app's state.
* @typedef {{
* allEntries: !Object<string, FileData>,
* currentDirectory: (CurrentDirectory|undefined),
* search: (!SearchData|undefined),
* }}
*/
export let State;
13 changes: 11 additions & 2 deletions ui/file_manager/file_manager/foreground/js/directory_model.js
Expand Up @@ -20,7 +20,7 @@ import {PropStatus, State} from '../../externs/ts/state.js';
import {Store} from '../../externs/ts/store.js';
import {VolumeInfo} from '../../externs/volume_info.js';
import {VolumeManager} from '../../externs/volume_manager.js';
import {changeDirectory} from '../../state/actions.js';
import {changeDirectory, searchAction} from '../../state/actions.js';
import {getStore} from '../../state/store.js';

import {constants} from './constants.js';
Expand Down Expand Up @@ -1679,7 +1679,14 @@ export class DirectoryModel extends EventTarget {
return;
}

this.onSearchCompleted_ = onSearchRescan;
this.onSearchCompleted_ = (...args) => {
// Notify the caller via callback, for non-store based callers.
onSearchRescan(...args);

// Notify the store-aware parts.
this.store_.dispatch(
searchAction({term: query, status: PropStatus.SUCCESS}));
};
this.onClearSearch_ = onClearSearch;
this.addEventListener('scan-completed', this.onSearchCompleted_);
this.clearAndScan_(newDirContents, callback);
Expand All @@ -1705,6 +1712,8 @@ export class DirectoryModel extends EventTarget {
this.onClearSearch_();
this.onClearSearch_ = null;
}

this.store_.dispatch(searchAction({}));
}

/**
Expand Down
Expand Up @@ -8,6 +8,8 @@ import {str, util} from '../../common/js/util.js';
import {VolumeManagerCommon} from '../../common/js/volume_manager_types.js';
import {DirectoryChangeEvent} from '../../externs/directory_change_event.js';
import {VolumeManager} from '../../externs/volume_manager.js';
import {changeDirectory} from '../../state/actions.js';
import {getStore} from '../../state/store.js';

import {AppStateController} from './app_state_controller.js';
import {FileFilter} from './directory_contents.js';
Expand Down Expand Up @@ -417,16 +419,27 @@ export class MainWindowComponent {
switch (util.getKeyModifiers(event) + event.key) {
case 'Backspace': // Backspace => Up one directory.
event.preventDefault();
const components =
this.ui_.breadcrumbController.getCurrentPathComponents();
if (components.length < 2) {
break;
if (util.isFilesAppExperimental()) {
const store = getStore();
const state = store.getState();
const components = state.currentDirectory?.pathComponents;
if (!components || components.length < 2) {
break;
}
const parent = components[components.length - 2];
store.dispatch(changeDirectory({toKey: parent.key}));
} else {
const components =
this.ui_.breadcrumbController.getCurrentPathComponents();
if (components.length < 2) {
break;
}
const parentPathComponent = components[components.length - 2];
parentPathComponent.resolveEntry().then((parentEntry) => {
this.directoryModel_.changeDirectoryEntry(
/** @type {!DirectoryEntry} */ (parentEntry));
});
}
const parentPathComponent = components[components.length - 2];
parentPathComponent.resolveEntry().then((parentEntry) => {
this.directoryModel_.changeDirectoryEntry(
/** @type {!DirectoryEntry} */ (parentEntry));
});
break;

case 'Enter': // Enter => Change directory or perform default action.
Expand Down
16 changes: 16 additions & 0 deletions ui/file_manager/file_manager/state/actions.ts
Expand Up @@ -20,6 +20,7 @@ export interface Action extends BaseAction {
export const enum Actions {
CHANGE_DIRECTORY = 'change-directory',
CLEAR_STALE_CACHED_ENTRIES = 'clear-stale-cached-entries',
SEARCH = 'search',
}

/** Action to request to change the Current Directory. */
Expand All @@ -29,6 +30,12 @@ export interface ChangeDirectoryAction extends Action {
status: PropStatus;
}

/** Action to update the search state. */
export interface SearchAction extends Action {
term?: string;
status?: PropStatus;
}

/** Factory for the ChangeDirectoryAction. */
export function changeDirectory(
{to, toKey, status}: {to?: Entry, toKey: FileKey, status?: PropStatus}):
Expand All @@ -40,3 +47,12 @@ export function changeDirectory(
status: status ? status : PropStatus.STARTED,
};
}

export function searchAction(
{term, status}: {term?: string, status?: PropStatus}): SearchAction {
return {
type: Actions.SEARCH,
term,
status,
};
}
8 changes: 7 additions & 1 deletion ui/file_manager/file_manager/state/reducers/all_entries.ts
Expand Up @@ -88,9 +88,15 @@ export function cacheEntries(currentState: State, action: Action): State {
return currentState;
}

// TODO(lucmult): Find a correct way to grab the VolumeManager.
const volumeManager = window.fileManager?.volumeManager as any;
const volumeInfo = volumeManager?.getVolumeInfo(entry as Entry);
const volumeType = volumeInfo?.volumeType;

const entryData = allEntries[key] || {};
allEntries[key] = Object.assign(entryData, {
entry: entry,
entry,
volumeType,
});

currentState.allEntries = allEntries;
Expand Down
Expand Up @@ -8,7 +8,7 @@ import {MockVolumeManager} from '../../background/js/mock_volume_manager.js';
import {MockFileSystem} from '../../common/js/mock_entry.js';
import {waitUntil} from '../../common/js/test_error_reporting.js';
import {Actions, changeDirectory} from '../actions.js';
import {getStore, Store} from '../store.js';
import {getEmptyState, getStore, Store} from '../store.js';

import {clearCachedEntries} from './all_entries.js';

Expand All @@ -24,10 +24,7 @@ export function setUp() {
volumeManager: volumeManager,
};

store.init({
allEntries: {},
currentDirectory: undefined,
});
store.init(getEmptyState());

fileSystem = volumeManager.getCurrentProfileVolumeInfo(
'downloads')!.fileSystem as MockFileSystem;
Expand Down
Expand Up @@ -32,7 +32,7 @@ export function changeDirectory(
}

const components =
PathComponent.computeComponentsFromEntry(fileData.entry, volumeManager);
PathComponent.computeComponentsFromEntry(fileData.entry!, volumeManager);

return Object.assign(currentState.currentDirectory || {}, {
status: action.status,
Expand Down
6 changes: 6 additions & 0 deletions ui/file_manager/file_manager/state/reducers/root.ts
Expand Up @@ -7,6 +7,7 @@ import {Action, Actions, ChangeDirectoryAction} from '../actions.js';

import {cacheEntries, clearCachedEntries} from './all_entries.js';
import {changeDirectory} from './current_directory.js';
import {search} from './search.js';

/**
* Root reducer for Files app.
Expand All @@ -27,6 +28,11 @@ export function rootReducer(currentState: State, action: Action): State {
case Actions.CLEAR_STALE_CACHED_ENTRIES:
return clearCachedEntries(state, action);

case Actions.SEARCH:
return Object.assign(state, {
search: search(state, action),
});

default:
console.error(`invalid action: ${action.type}`);
return state;
Expand Down
13 changes: 13 additions & 0 deletions ui/file_manager/file_manager/state/reducers/search.ts
@@ -0,0 +1,13 @@
// Copyright 2022 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.

import {SearchData, State} from '../../externs/ts/state.js';
import {SearchAction} from '../actions.js';

export function search(_state: State, action: SearchAction): SearchData {
return {
term: action.term,
status: action.status,
};
}
12 changes: 12 additions & 0 deletions ui/file_manager/file_manager/state/store.ts
Expand Up @@ -35,3 +35,15 @@ export function getStore(): Store {

return store;
}

export function getEmptyState(): State {
// TODO(b/241707820): Migrate State to allow optional attributes.
return {
allEntries: {},
currentDirectory: undefined,
search: {
term: undefined,
status: undefined,
},
};
}
1 change: 1 addition & 0 deletions ui/file_manager/file_names.gni
Expand Up @@ -260,6 +260,7 @@ ts_files = [
"file_manager/state/reducers/root.ts",
"file_manager/state/reducers/all_entries.ts",
"file_manager/state/reducers/current_directory.ts",
"file_manager/state/reducers/search.ts",
"file_manager/state/actions.ts",
"file_manager/state/file_key.ts",
"file_manager/widgets/xf_breadcrumb.ts",
Expand Down
6 changes: 3 additions & 3 deletions ui/file_manager/integration_tests/file_manager/breadcrumbs.js
Expand Up @@ -282,7 +282,7 @@ testcase.breadcrumbsMainButtonClick = async () => {

// Check: the breadcrumb path should be updated due to navigation.
await remoteCall.waitForElement(
appId, ['bread-crumb[path="My files/Downloads"']);
appId, [`${breadcrumbsTag}[path="My files/Downloads"]`]);
};

/**
Expand Down Expand Up @@ -317,7 +317,7 @@ testcase.breadcrumbsMainButtonEnterKey = async () => {

// Check: the breadcrumb path should be updated due to navigation.
await remoteCall.waitForElement(
appId, ['bread-crumb[path="My files/Downloads"']);
appId, [`${breadcrumbsTag}[path="My files/Downloads"]`]);
};

/**
Expand Down Expand Up @@ -512,7 +512,7 @@ testcase.breadcrumbsEliderMenuItemClick = async () => {

// Check: the breadcrumb path should be updated due to navigation.
await remoteCall.waitForElement(
appId, ['bread-crumb[path="My files/Downloads"']);
appId, [`${breadcrumbsTag}[path="My files/Downloads"]`]);
};

/**
Expand Down

0 comments on commit 8f4d9a8

Please sign in to comment.