0

[FilesBanner] Update the "unrecognized file system" panel to banners

The format-panel was previously shown when a invalid file system is
identified on a USB device. This doesn't really align with our UI
treatment, so migrate this to the banners framework.

This adds a new handler for commands to be used from buttons of
StateBanners as well as removing all the previous code around the
format-panel.

Bug: 1295992
Test: browser_tests --gtest_filter=FileManagerJsTest.StateBanner
Test: Deploy to device and plug in corrupt USB
Change-Id: Idaabb44f078241f08be1cb017d0179e8b40ca37e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3450061
Reviewed-by: Luciano Pacheco <lucmult@chromium.org>
Commit-Queue: Ben Reich <benreich@chromium.org>
Cr-Commit-Position: refs/heads/main@{#969713}
This commit is contained in:
Ben Reich
2022-02-11 00:14:28 +00:00
committed by Chromium LUCI CQ
parent b83943fc63
commit d0ef37ee10
12 changed files with 165 additions and 67 deletions

@ -375,6 +375,7 @@ preprocess_if_expr("preprocess_generated") {
"file_manager/foreground/js/ui/banners/drive_welcome_banner.js",
"file_manager/foreground/js/ui/banners/educational_banner.js",
"file_manager/foreground/js/ui/banners/holding_space_welcome_banner.js",
"file_manager/foreground/js/ui/banners/invalid_usb_filesystem_banner.js",
"file_manager/foreground/js/ui/banners/local_disk_low_space_banner.js",
"file_manager/foreground/js/ui/banners/photos_welcome_banner.js",
"file_manager/foreground/js/ui/banners/shared_with_crostini_pluginvm_banner.js",

@ -2149,47 +2149,13 @@ cr-menu.chrome-menu hr {
font-size: 0;
}
/* Panel shown when USB device with no readable partitions
is plugged in. */
#format-panel {
bottom: 0;
color: var(--cros-text-color-primary);
display: none;
left: 0;
padding-inline-start: 50px;
padding-top: 20px;
position: absolute;
right: 0;
top: 0;
}
body[unformatted] .dialog-container #format-panel {
display: block;
}
body[drive='unmounted'] .dialog-container .filelist-panel,
body[drive='mounting'] .dialog-container .filelist-panel,
body[drive='error'] .dialog-container .filelist-panel,
body[unformatted] .dialog-container .filelist-panel {
/* Hide file list when Drive is not mounted.
Use opacity to avoid manual resizing.*/
body[drive='unmounted'] .dialog-container #list-container,
body[drive='mounting'] .dialog-container #list-container,
body[drive='error'] .dialog-container #list-container,
body[unformatted] .dialog-container #list-container {
opacity: 0;
}
#format-panel > * {
align-items: center;
display: none;
flex-direction: row;
height: 22px;
justify-content: flex-start;
margin-bottom: 10px;
}
body[unformatted] #format-panel > .error,
#format-panel > #format-button {
display: flex;
}
.buttonbar > * {
position: relative;
}

@ -200,6 +200,7 @@ js_library("banner_controller") {
"ui/banners:drive_low_space_banner",
"ui/banners:drive_welcome_banner",
"ui/banners:holding_space_welcome_banner",
"ui/banners:invalid_usb_filesystem_banner",
"ui/banners:local_disk_low_space_banner",
"ui/banners:shared_with_crostini_pluginvm_banner",
"ui/banners:trash_banner",

@ -20,6 +20,7 @@ import {TAG_NAME as DriveLowSpaceBanner} from './ui/banners/drive_low_space_bann
import {TAG_NAME as DriveOfflinePinningBannerTagName} from './ui/banners/drive_offline_pinning_banner.js';
import {TAG_NAME as DriveWelcomeBannerTagName} from './ui/banners/drive_welcome_banner.js';
import {TAG_NAME as HoldingSpaceWelcomeBannerTagName} from './ui/banners/holding_space_welcome_banner.js';
import {TAG_NAME as InvalidUSBFileSystemBanner} from './ui/banners/invalid_usb_filesystem_banner.js';
import {TAG_NAME as LocalDiskLowSpaceBannerTagName} from './ui/banners/local_disk_low_space_banner.js';
import {TAG_NAME as PhotosWelcomeBannerTagName} from './ui/banners/photos_welcome_banner.js';
import {TAG_NAME as SharedWithCrostiniPluginVmBanner} from './ui/banners/shared_with_crostini_pluginvm_banner.js';
@ -269,6 +270,7 @@ export class BannerController extends EventTarget {
PhotosWelcomeBannerTagName,
]);
this.setStateBannersInOrder([
InvalidUSBFileSystemBanner,
SharedWithCrostiniPluginVmBanner,
TrashBannerTagName,
]);
@ -307,6 +309,13 @@ export class BannerController extends EventTarget {
this.volumeSizeStats_[this.currentVolume_.volumeId].remainingSize
})
});
// Register a custom filter that checks if the removable device has an
// error and show the invalid USB file system banner.
this.registerCustomBannerFilter_(InvalidUSBFileSystemBanner, {
shouldShow: () => !!(this.currentVolume_ && this.currentVolume_.error),
context: () => ({error: this.currentVolume_.error}),
});
}
for (const banner of this.warningBanners_) {

@ -445,20 +445,8 @@ export class MainWindowComponent {
null;
// Update unformatted volume status.
if (newVolumeInfo && newVolumeInfo.error) {
this.ui_.element.setAttribute('unformatted', '');
if (newVolumeInfo.error ===
VolumeManagerCommon.VolumeError.UNSUPPORTED_FILESYSTEM) {
this.ui_.formatPanelError.textContent =
str('UNSUPPORTED_FILESYSTEM_WARNING');
} else {
this.ui_.formatPanelError.textContent =
str('UNKNOWN_FILESYSTEM_WARNING');
}
} else {
this.ui_.element.removeAttribute('unformatted');
}
const unformatted = !!(newVolumeInfo && newVolumeInfo.error);
this.ui_.element.toggleAttribute('unformatted', /*force=*/ unformatted);
if (event.newDirEntry) {
this.ui_.locationLine.show(event.newDirEntry);

@ -26,6 +26,7 @@ html_to_js("web_components") {
"drive_welcome_banner.js",
"educational_banner.js",
"holding_space_welcome_banner.js",
"invalid_usb_filesystem_banner.js",
"local_disk_low_space_banner.js",
"photos_welcome_banner.js",
"shared_with_crostini_pluginvm_banner.js",
@ -50,6 +51,7 @@ js_type_check("closure_compile_jsmodules") {
":drive_welcome_banner",
":educational_banner",
":holding_space_welcome_banner",
":invalid_usb_filesystem_banner",
":local_disk_low_space_banner",
":photos_welcome_banner",
":shared_with_crostini_pluginvm_banner",
@ -68,6 +70,15 @@ js_library("holding_space_welcome_banner") {
]
}
js_library("invalid_usb_filesystem_banner") {
deps = [
":state_banner",
"//ui/file_manager/file_manager/common/js:util",
"//ui/file_manager/file_manager/common/js:volume_manager_types",
"//ui/file_manager/file_manager/externs:banner",
]
}
js_library("local_disk_low_space_banner") {
deps = [
":warning_banner",

@ -0,0 +1,6 @@
<state-banner>
<span slot="text">$i18n{UNKNOWN_FILESYSTEM_WARNING}</span>
<cr-button slot="extra-button" command="#format">
$i18n{FORMAT_DEVICE_BUTTON_LABEL}
</cr-button>
</state-banner>

@ -0,0 +1,67 @@
// Copyright 2021 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 {html} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {str} from '../../../../common/js/util.js';
import {VolumeManagerCommon} from '../../../../common/js/volume_manager_types.js';
import {Banner} from '../../../../externs/banner.js';
import {StateBanner} from './state_banner.js';
/**
* The custom element tag name.
* @type {string}
*/
export const TAG_NAME = 'invalid-usb-filesystem-banner';
/** @const {!HTMLTemplateElement} */
const htmlTemplate = html`{__html_template__}`;
/**
* A banner that shows is a removable device is plugged in and it has a
* filesystem that is either unknown or unsupported. It includes an action
* button for the user to format the device.
*/
export class InvalidUSBFileSystemBanner extends StateBanner {
/**
* Returns the HTML template for this banner.
* @returns {!Node}
*/
getTemplate() {
return htmlTemplate.content.cloneNode(true);
}
/**
* Only show the banner when the user has navigated to the Removable root type
* this is used in conjunction with a custom filter to ensure only removable
* roots with errors are shown the banner.
* @returns {!Array<!Banner.AllowedVolume>}
*/
allowedVolumes() {
return [{root: VolumeManagerCommon.RootType.REMOVABLE}];
}
/**
* When the custom filter shows this banner in the controller, it passes the
* context to the banner. This is used to identify if the device has an
* unsupported OR unknown file system.
* @param {!Object} context The device error of the removable device.
*/
onFilteredContext(context) {
if (!context || !context.error) {
console.warn('Context not supplied or error key missing');
return;
}
const text = this.shadowRoot.querySelector('span[slot="text"]');
if (context.error ===
VolumeManagerCommon.VolumeError.UNSUPPORTED_FILESYSTEM) {
text.innerText = str('UNSUPPORTED_FILESYSTEM_WARNING');
return;
}
text.innerText = str('UNKNOWN_FILESYSTEM_WARNING');
}
}
customElements.define(TAG_NAME, InvalidUSBFileSystemBanner);

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {assertInstanceof} from 'chrome://resources/js/assert.m.js';
import {Command} from 'chrome://resources/js/cr/ui/command.m.js';
import {html} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {util} from '../../../../common/js/util.js';
@ -68,18 +70,30 @@ export class StateBanner extends Banner {
if (extraButton) {
extraButton.addEventListener('click', (e) => {
const href = extraButton.getAttribute('href');
if (!href) {
e.preventDefault();
return;
}
const chromeOsSettingsSubpage =
href.replace('chrome://os-settings/', '');
href && href.replace('chrome://os-settings/', '');
if (chromeOsSettingsSubpage && chromeOsSettingsSubpage !== href) {
chrome.fileManagerPrivate.openSettingsSubpage(
chromeOsSettingsSubpage);
e.preventDefault();
return;
}
const commandName = extraButton.getAttribute('command');
if (commandName) {
const command =
assertInstanceof(document.querySelector(commandName), Command);
// Unit tests don't enclose a StateBanner inside a concrete banner,
// so we want to ensure the event is appropriately dispatched from the
// outer scope otherwise it won't bubble up to the commands.
let bannerInstance = this;
const parentBanner = this.getRootNode() && this.getRootNode().host;
if (parentBanner && parentBanner instanceof StateBanner) {
bannerInstance = parentBanner;
}
command.execute(bannerInstance);
e.preventDefault();
return;
}
util.visitURL(extraButton.getAttribute('href'));
e.preventDefault();
});

@ -2,8 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {decorate} from 'chrome://resources/js/cr/ui.m.js';
import {Command} from 'chrome://resources/js/cr/ui/command.m.js';
import {assertEquals} from 'chrome://test/chai_assert.js';
import {mockUtilVisitURL} from '../../../../common/js/mock_util.js';
import {waitUntil} from '../../../../common/js/test_error_reporting.js';
import {StateBanner} from './state_banner.js';
@ -116,3 +120,44 @@ export async function testChromeOsSettingsNoSubpageLink() {
assertEquals(mockVisitURL.getURL(), osSettingsLink);
mockVisitURL.restoreVisitURL();
}
/**
* Test that an extra-button with a command triggers an Event of the correct
* type.
*/
export async function testCommandsCanBeUsedForExtraButtons(done) {
const html = `<command id="format">
<state-banner>
<span slot="text">Banner title</span>
<button slot="extra-button" command="#format">
Test Button
</button>
</state-banner>
`;
document.body.innerHTML = html;
decorate('command', Command);
// Add a listener to wait for the #format command to be received and keep
// track of the event it received. Given the actual command is not properly
// setup in the unittest environment, the event bubbles up to the body and
// we can listen for it there.
let commandReceived = false;
let commandEvent = null;
document.body.addEventListener('command', (e) => {
commandReceived = true;
commandEvent = e;
});
// Click the extra button with a command associated with it.
stateBanner =
/** @type{!StateBanner} */ (document.body.querySelector('state-banner'));
stateBanner.querySelector('[slot="extra-button"]').click();
// Wait until the command has been received.
await waitUntil(() => commandReceived == true);
// Assert the event type received is a command.
assertEquals(commandEvent.type, 'command');
done();
}

@ -289,12 +289,6 @@ export class FileManagerUI {
*/
this.listContainer;
/**
* @type {!HTMLElement}
*/
this.formatPanelError =
queryRequiredElement('#format-panel > .error', this.element);
/**
* @type {!MultiMenu}
* @const

@ -513,10 +513,6 @@
</div>
</div>
</div>
<div id="format-panel">
<div class="error"></div>
<button id="format-button" command="#format"></button>
</div>
</div>
<div class="files-feedback-panels" visibleif="full-page">
<xf-display-panel id="progress-panel" role="complementary"