Commit 99908d51 authored by Austin Tankiang's avatar Austin Tankiang Committed by Commit Bot

Migrate format notifications to visual signals

Bug: 988586
Change-Id: Ia2868d3a7ccdda994c1bfddd123e78d46f72e934
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1978337Reviewed-by: default avatarAlex Danilo <adanilo@chromium.org>
Reviewed-by: default avatarNoel Gordon <noel@chromium.org>
Commit-Queue: Austin Tankiang <austinct@chromium.org>
Cr-Commit-Position: refs/heads/master@{#727339}
parent 6b419012
...@@ -555,6 +555,10 @@ std::unique_ptr<base::DictionaryValue> GetFileManagerStrings() { ...@@ -555,6 +555,10 @@ std::unique_ptr<base::DictionaryValue> GetFileManagerStrings() {
IDS_FILE_BROWSER_FORMAT_DIALOG_FORMAT_LABEL); IDS_FILE_BROWSER_FORMAT_DIALOG_FORMAT_LABEL);
SET_STRING("FORMAT_DIALOG_CONFIRM_LABEL", SET_STRING("FORMAT_DIALOG_CONFIRM_LABEL",
IDS_FILE_BROWSER_FORMAT_DIALOG_CONFIRM_LABEL); IDS_FILE_BROWSER_FORMAT_DIALOG_CONFIRM_LABEL);
SET_STRING("FORMAT_PROGRESS_MESSAGE",
IDS_FILE_BROWSER_FORMAT_PROGRESS_MESSAGE);
SET_STRING("FORMAT_SUCCESS_MESSAGE", IDS_FILE_BROWSER_FORMAT_SUCCESS_MESSAGE);
SET_STRING("FORMAT_FAILURE_MESSAGE", IDS_FILE_BROWSER_FORMAT_FAILURE_MESSAGE);
SET_STRING("SHARE_BUTTON_TOOLTIP", IDS_FILE_BROWSER_SHARE_BUTTON_TOOLTIP); SET_STRING("SHARE_BUTTON_TOOLTIP", IDS_FILE_BROWSER_SHARE_BUTTON_TOOLTIP);
SET_STRING("SORT_BUTTON_TOOLTIP", IDS_FILE_BROWSER_SORT_BUTTON_TOOLTIP); SET_STRING("SORT_BUTTON_TOOLTIP", IDS_FILE_BROWSER_SORT_BUTTON_TOOLTIP);
SET_STRING("GEAR_BUTTON_TOOLTIP", IDS_FILE_BROWSER_GEAR_BUTTON_TOOLTIP); SET_STRING("GEAR_BUTTON_TOOLTIP", IDS_FILE_BROWSER_GEAR_BUTTON_TOOLTIP);
......
...@@ -913,6 +913,16 @@ ...@@ -913,6 +913,16 @@
Erase and Format Erase and Format
</message> </message>
<message name="IDS_FILE_BROWSER_FORMAT_PROGRESS_MESSAGE" desc="Notification message displayed while formatting an external drive">
Formatting <ph name="DRIVE_NAME">$1<ex>My USB</ex></ph>...
</message>
<message name="IDS_FILE_BROWSER_FORMAT_SUCCESS_MESSAGE" desc="Notification message displayed when formatting of an external drive has succeeded">
Formatted <ph name="DRIVE_NAME">$1<ex>My USB</ex></ph>
</message>
<message name="IDS_FILE_BROWSER_FORMAT_FAILURE_MESSAGE" desc="Notification message displayed if formatting of an external drive has failed">
Could not format <ph name="DRIVE_NAME">$1<ex>My USB</ex></ph>
</message>
<message name="IDS_FILE_BROWSER_SUGGEST_DIALOG_TITLE" desc="Title of the suggest app dialog, which shows the list of the apps which supports the selected file."> <message name="IDS_FILE_BROWSER_SUGGEST_DIALOG_TITLE" desc="Title of the suggest app dialog, which shows the list of the apps which supports the selected file.">
Select an app to open this file Select an app to open this file
</message> </message>
......
3f513edddc965117bc53f0c5f57d5b5d39417fea
\ No newline at end of file
795c542fbd79365530657f9345656fa2c84bfcc2
\ No newline at end of file
ea2c280af3fb9e06ded0a6a4b80b075723027811
\ No newline at end of file
...@@ -178,9 +178,11 @@ js_unittest("crostini_unittest") { ...@@ -178,9 +178,11 @@ js_unittest("crostini_unittest") {
js_library("device_handler") { js_library("device_handler") {
deps = [ deps = [
":progress_center",
":volume_manager_factory", ":volume_manager_factory",
"//ui/file_manager/file_manager/common/js:async_util", "//ui/file_manager/file_manager/common/js:async_util",
"//ui/file_manager/file_manager/common/js:importer_common", "//ui/file_manager/file_manager/common/js:importer_common",
"//ui/file_manager/file_manager/common/js:progress_center_common",
"//ui/webui/resources/js:cr", "//ui/webui/resources/js:cr",
"//ui/webui/resources/js/cr:event_target", "//ui/webui/resources/js/cr:event_target",
] ]
...@@ -189,6 +191,7 @@ js_library("device_handler") { ...@@ -189,6 +191,7 @@ js_library("device_handler") {
js_unittest("device_handler_unittest") { js_unittest("device_handler_unittest") {
deps = [ deps = [
":device_handler", ":device_handler",
":mock_progress_center",
":mock_volume_manager", ":mock_volume_manager",
"//ui/file_manager/base/js:mock_chrome", "//ui/file_manager/base/js:mock_chrome",
"//ui/file_manager/base/js:test_error_reporting", "//ui/file_manager/base/js:test_error_reporting",
......
...@@ -42,7 +42,7 @@ class FileBrowserBackgroundImpl extends BackgroundBase { ...@@ -42,7 +42,7 @@ class FileBrowserBackgroundImpl extends BackgroundBase {
* Event handler for C++ sides notifications. * Event handler for C++ sides notifications.
* @private {!DeviceHandler} * @private {!DeviceHandler}
*/ */
this.deviceHandler_ = new DeviceHandler(); this.deviceHandler_ = new DeviceHandler(this.progressCenter);
// Handle device navigation requests. // Handle device navigation requests.
this.deviceHandler_.addEventListener( this.deviceHandler_.addEventListener(
...@@ -169,7 +169,7 @@ class FileBrowserBackgroundImpl extends BackgroundBase { ...@@ -169,7 +169,7 @@ class FileBrowserBackgroundImpl extends BackgroundBase {
*/ */
volumeManager => { volumeManager => {
if (event.devicePath) { if (event.devicePath) {
let volume = volumeManager.findByDevicePath(event.devicePath); const volume = volumeManager.findByDevicePath(event.devicePath);
if (volume) { if (volume) {
this.navigateToVolumeRoot_(volume, event.filePath); this.navigateToVolumeRoot_(volume, event.filePath);
} else { } else {
...@@ -330,7 +330,7 @@ class FileBrowserBackgroundImpl extends BackgroundBase { ...@@ -330,7 +330,7 @@ class FileBrowserBackgroundImpl extends BackgroundBase {
} }
}); });
} }
let appState = {}; const appState = {};
let launchType = LaunchType.FOCUS_ANY_OR_CREATE; let launchType = LaunchType.FOCUS_ANY_OR_CREATE;
if (urls) { if (urls) {
appState.selectionURL = urls[0]; appState.selectionURL = urls[0];
...@@ -456,9 +456,9 @@ class FileBrowserBackgroundImpl extends BackgroundBase { ...@@ -456,9 +456,9 @@ class FileBrowserBackgroundImpl extends BackgroundBase {
// mounted volume. // mounted volume.
this.findFocusedWindow_() this.findFocusedWindow_()
.then(key => { .then(key => {
let statusOK = event.status === 'success' || const statusOK = event.status === 'success' ||
event.status === 'error_path_already_mounted'; event.status === 'error_path_already_mounted';
let volumeTypeOK = event.volumeMetadata.volumeType === const volumeTypeOK = event.volumeMetadata.volumeType ===
VolumeManagerCommon.VolumeType.PROVIDED && VolumeManagerCommon.VolumeType.PROVIDED &&
event.volumeMetadata.source === VolumeManagerCommon.Source.FILE; event.volumeMetadata.source === VolumeManagerCommon.Source.FILE;
if (key === null && event.eventType === 'mount' && statusOK && if (key === null && event.eventType === 'mount' && statusOK &&
......
...@@ -4,9 +4,18 @@ ...@@ -4,9 +4,18 @@
/** Handler of device event. */ /** Handler of device event. */
class DeviceHandler extends cr.EventTarget { class DeviceHandler extends cr.EventTarget {
constructor() { /** @param {!ProgressCenter} progressCenter */
constructor(progressCenter) {
super(); super();
/**
* Progress center to notify for format events.
* @type {!ProgressCenter}
* @const
* @private
*/
this.progressCenter_ = progressCenter;
/** /**
* Map of device path and mount status of devices. * Map of device path and mount status of devices.
* @private {Object<DeviceHandler.MountStatus>} * @private {Object<DeviceHandler.MountStatus>}
...@@ -54,15 +63,9 @@ class DeviceHandler extends cr.EventTarget { ...@@ -54,15 +63,9 @@ class DeviceHandler extends cr.EventTarget {
DeviceHandler.Notification.DEVICE_HARD_UNPLUGGED.show(event.devicePath); DeviceHandler.Notification.DEVICE_HARD_UNPLUGGED.show(event.devicePath);
break; break;
case 'format_start': case 'format_start':
DeviceHandler.Notification.FORMAT_START.show(event.devicePath);
break;
case 'format_success': case 'format_success':
DeviceHandler.Notification.FORMAT_START.hide(event.devicePath);
DeviceHandler.Notification.FORMAT_SUCCESS.show(event.devicePath);
break;
case 'format_fail': case 'format_fail':
DeviceHandler.Notification.FORMAT_START.hide(event.devicePath); this.handleFormatEvent_(event);
DeviceHandler.Notification.FORMAT_FAIL.show(event.devicePath);
break; break;
case 'rename_fail': case 'rename_fail':
DeviceHandler.Notification.RENAME_FAIL.show(event.devicePath); DeviceHandler.Notification.RENAME_FAIL.show(event.devicePath);
...@@ -73,6 +76,51 @@ class DeviceHandler extends cr.EventTarget { ...@@ -73,6 +76,51 @@ class DeviceHandler extends cr.EventTarget {
} }
} }
/**
* Handles format events and displays a notification in the progress center.
* @param {chrome.fileManagerPrivate.DeviceEvent} event Device event.
* @private
*/
handleFormatEvent_(event) {
const item = new ProgressCenterItem();
item.id = 'format:' + event.devicePath;
item.type = ProgressItemType.FORMAT;
item.itemCount = 1;
item.progressMax = 1;
let notificationType;
switch (event.type) {
case 'format_start':
item.state = ProgressItemState.PROGRESSING;
item.message = strf('FORMAT_PROGRESS_MESSAGE', event.deviceLabel);
item.progressValue = 0;
notificationType = DeviceHandler.Notification.Type.FORMAT_START;
break;
case 'format_success':
item.state = ProgressItemState.COMPLETED;
item.message = strf('FORMAT_SUCCESS_MESSAGE', event.deviceLabel);
item.progressValue = 1;
notificationType = DeviceHandler.Notification.Type.FORMAT_SUCCESS;
break;
case 'format_fail':
item.state = ProgressItemState.ERROR;
item.message = strf('FORMAT_FAILURE_MESSAGE', event.deviceLabel);
item.progressValue = 0;
notificationType = DeviceHandler.Notification.Type.FORMAT_FAIL;
break;
default:
console.error('Unknown format event type: ' + event.type);
break;
}
this.progressCenter_.updateItem(item);
requestIdleCallback(
() => metrics.recordEnum(
'Notification.Show', notificationType,
DeviceHandler.Notification.TypesForUMA));
}
/** /**
* Handles mount completed events to show notifications for removable devices. * Handles mount completed events to show notifications for removable devices.
* @param {chrome.fileManagerPrivate.MountCompletedEvent} event Mount * @param {chrome.fileManagerPrivate.MountCompletedEvent} event Mount
...@@ -747,32 +795,6 @@ DeviceHandler.Notification.DEVICE_HARD_UNPLUGGED = ...@@ -747,32 +795,6 @@ DeviceHandler.Notification.DEVICE_HARD_UNPLUGGED =
DeviceHandler.Notification.Type.DEVICE_HARD_UNPLUGGED, 'hardUnplugged', DeviceHandler.Notification.Type.DEVICE_HARD_UNPLUGGED, 'hardUnplugged',
'DEVICE_HARD_UNPLUGGED_TITLE', 'DEVICE_HARD_UNPLUGGED_MESSAGE'); 'DEVICE_HARD_UNPLUGGED_TITLE', 'DEVICE_HARD_UNPLUGGED_MESSAGE');
/**
* @type {DeviceHandler.Notification}
* @const
*/
DeviceHandler.Notification.FORMAT_START = new DeviceHandler.Notification(
DeviceHandler.Notification.Type.FORMAT_START, 'formatStart',
'FORMATTING_OF_DEVICE_PENDING_TITLE',
'FORMATTING_OF_DEVICE_PENDING_MESSAGE');
/**
* @type {DeviceHandler.Notification}
* @const
*/
DeviceHandler.Notification.FORMAT_SUCCESS = new DeviceHandler.Notification(
DeviceHandler.Notification.Type.FORMAT_SUCCESS, 'formatSuccess',
'FORMATTING_OF_DEVICE_FINISHED_TITLE',
'FORMATTING_FINISHED_SUCCESS_MESSAGE');
/**
* @type {DeviceHandler.Notification}
* @const
*/
DeviceHandler.Notification.FORMAT_FAIL = new DeviceHandler.Notification(
DeviceHandler.Notification.Type.FORMAT_FAIL, 'formatFail',
'FORMATTING_OF_DEVICE_FAILED_TITLE', 'FORMATTING_FINISHED_FAILURE_MESSAGE');
/** /**
* @type {DeviceHandler.Notification} * @type {DeviceHandler.Notification}
* @const * @const
......
...@@ -6,6 +6,9 @@ ...@@ -6,6 +6,9 @@
/** @type {!MockVolumeManager} */ /** @type {!MockVolumeManager} */
let volumeManager; let volumeManager;
/** @type {!MockProgressCenter} */
let progressCenter;
/** @type {!DeviceHandler} */ /** @type {!DeviceHandler} */
let deviceHandler; let deviceHandler;
...@@ -27,6 +30,9 @@ function setUp() { ...@@ -27,6 +30,9 @@ function setUp() {
DEVICE_UNSUPPORTED_MESSAGE: 'DEVICE_UNSUPPORTED: $1', DEVICE_UNSUPPORTED_MESSAGE: 'DEVICE_UNSUPPORTED: $1',
DEVICE_UNKNOWN_MESSAGE: 'DEVICE_UNKNOWN: $1', DEVICE_UNKNOWN_MESSAGE: 'DEVICE_UNKNOWN: $1',
MULTIPART_DEVICE_UNSUPPORTED_MESSAGE: 'MULTIPART_DEVICE_UNSUPPORTED: $1', MULTIPART_DEVICE_UNSUPPORTED_MESSAGE: 'MULTIPART_DEVICE_UNSUPPORTED: $1',
FORMAT_PROGRESS_MESSAGE: 'FORMAT_PROGRESS_MESSAGE: $1',
FORMAT_SUCCESS_MESSAGE: 'FORMAT_SUCCESS_MESSAGE: $1',
FORMAT_FAILURE_MESSAGE: 'FORMAT_FAILURE_MESSAGE: $1',
}; };
window.loadTimeData.getString = id => { window.loadTimeData.getString = id => {
return window.loadTimeData.data_[id] || id; return window.loadTimeData.data_[id] || id;
...@@ -42,7 +48,9 @@ function setUp() { ...@@ -42,7 +48,9 @@ function setUp() {
volumeManager = new MockVolumeManager(); volumeManager = new MockVolumeManager();
MockVolumeManager.installMockSingleton(volumeManager); MockVolumeManager.installMockSingleton(volumeManager);
deviceHandler = new DeviceHandler(); progressCenter = new MockProgressCenter();
deviceHandler = new DeviceHandler(progressCenter);
} }
function setUpInIncognitoContext() { function setUpInIncognitoContext() {
...@@ -566,34 +574,37 @@ function testDisabledDevice() { ...@@ -566,34 +574,37 @@ function testDisabledDevice() {
function testFormatSucceeded() { function testFormatSucceeded() {
mockChrome.fileManagerPrivate.onDeviceChanged.dispatch( mockChrome.fileManagerPrivate.onDeviceChanged.dispatch(
{type: 'format_start', devicePath: '/device/path'}); {type: 'format_start', devicePath: '/device/path', deviceLabel: 'label'});
assertEquals(1, Object.keys(mockChrome.notifications.items).length); assertEquals(1, progressCenter.getItemCount());
assertEquals( assertEquals(
'FORMATTING_OF_DEVICE_PENDING_MESSAGE', 'FORMAT_PROGRESS_MESSAGE: label',
mockChrome.notifications.items['formatStart:/device/path'].message); progressCenter.getItemById('format:/device/path').message);
mockChrome.fileManagerPrivate.onDeviceChanged.dispatch( mockChrome.fileManagerPrivate.onDeviceChanged.dispatch({
{type: 'format_success', devicePath: '/device/path'}); type: 'format_success',
assertEquals(1, Object.keys(mockChrome.notifications.items).length); devicePath: '/device/path',
deviceLabel: 'label'
});
assertEquals(1, progressCenter.getItemCount());
assertEquals( assertEquals(
'FORMATTING_FINISHED_SUCCESS_MESSAGE', 'FORMAT_SUCCESS_MESSAGE: label',
mockChrome.notifications.items['formatSuccess:/device/path'].message); progressCenter.getItemById('format:/device/path').message);
} }
function testFormatFailed() { function testFormatFailed() {
mockChrome.fileManagerPrivate.onDeviceChanged.dispatch( mockChrome.fileManagerPrivate.onDeviceChanged.dispatch(
{type: 'format_start', devicePath: '/device/path'}); {type: 'format_start', devicePath: '/device/path', deviceLabel: 'label'});
assertEquals(1, Object.keys(mockChrome.notifications.items).length); assertEquals(1, progressCenter.getItemCount());
assertEquals( assertEquals(
'FORMATTING_OF_DEVICE_PENDING_MESSAGE', 'FORMAT_PROGRESS_MESSAGE: label',
mockChrome.notifications.items['formatStart:/device/path'].message); progressCenter.getItemById('format:/device/path').message);
mockChrome.fileManagerPrivate.onDeviceChanged.dispatch( mockChrome.fileManagerPrivate.onDeviceChanged.dispatch(
{type: 'format_fail', devicePath: '/device/path'}); {type: 'format_fail', devicePath: '/device/path', deviceLabel: 'label'});
assertEquals(1, Object.keys(mockChrome.notifications.items).length); assertEquals(1, progressCenter.getItemCount());
assertEquals( assertEquals(
'FORMATTING_FINISHED_FAILURE_MESSAGE', 'FORMAT_FAILURE_MESSAGE: label',
mockChrome.notifications.items['formatFail:/device/path'].message); progressCenter.getItemById('format:/device/path').message);
} }
function testRenameSucceeded() { function testRenameSucceeded() {
...@@ -654,10 +665,9 @@ function testNotificationClicked(callback) { ...@@ -654,10 +665,9 @@ function testNotificationClicked(callback) {
function testMiscMessagesInIncognito() { function testMiscMessagesInIncognito() {
setUpInIncognitoContext(); setUpInIncognitoContext();
mockChrome.fileManagerPrivate.onDeviceChanged.dispatch( mockChrome.fileManagerPrivate.onDeviceChanged.dispatch(
{type: 'format_start', devicePath: '/device/path'}); {type: 'format_start', devicePath: '/device/path', deviceLabel: 'label'});
// No notification sent by this instance in incognito context. // No notification sent by this instance in incognito context.
assertEquals(0, Object.keys(mockChrome.notifications.items).length); assertEquals(0, progressCenter.getItemCount());
assertFalse(mockChrome.notifications.resolver.settled);
} }
function testMountCompleteInIncognito() { function testMountCompleteInIncognito() {
......
...@@ -48,7 +48,9 @@ const ProgressItemType = { ...@@ -48,7 +48,9 @@ const ProgressItemType = {
SYNC: 'sync', SYNC: 'sync',
// The item is general file transfer operation. // The item is general file transfer operation.
// This is used for the mixed operation of summarized item. // This is used for the mixed operation of summarized item.
TRANSFER: 'transfer' TRANSFER: 'transfer',
// The item is external drive format operation.
FORMAT: 'format'
}; };
Object.freeze(ProgressItemType); Object.freeze(ProgressItemType);
......
...@@ -79,6 +79,7 @@ ...@@ -79,6 +79,7 @@
<defs> <defs>
<g id='success'><path fill='#34A853' d='M14 23.6L8.4 18l-1.9 1.9 7.5 7.4 16-16-1.9-1.8z'/></g> <g id='success'><path fill='#34A853' d='M14 23.6L8.4 18l-1.9 1.9 7.5 7.4 16-16-1.9-1.8z'/></g>
<g id='failure' stroke='#EA4335' stroke-width='2.6'><circle cx='18' cy='18' r='12.1' fill='none'/><path d='M18 11v8.5m0 2.6v2.6'/></g> <g id='failure' stroke='#EA4335' stroke-width='2.6'><circle cx='18' cy='18' r='12.1' fill='none'/><path d='M18 11v8.5m0 2.6v2.6'/></g>
<g id='hard-drive' transform='scale(1.8,1.8)' fill='#1A73E8C0'><path d="M15 2c1.1046 0 2 .8954 2 2v12c0 1.1046-.8954 2-2 2H5c-1.1046 0-2-.8954-2-2V4c0-1.1046.8954-2 2-2h10zm0 10H5v4h10v-4zm-2 1c.5523 0 1 .4477 1 1s-.4477 1-1 1-1-.4477-1-1 .4477-1 1-1zm2-9H5v6h10V4z"/></g>
</defs> </defs>
</svg> </svg>
</iron-iconset-svg> </iron-iconset-svg>
...@@ -26,6 +26,7 @@ class PanelItem extends HTMLElement { ...@@ -26,6 +26,7 @@ class PanelItem extends HTMLElement {
this.panelTypeDone = 2; this.panelTypeDone = 2;
this.panelTypeError = 3; this.panelTypeError = 3;
this.panelTypeInfo = 4; this.panelTypeInfo = 4;
this.panelTypeFormatProgress = 5;
/** @private {number} */ /** @private {number} */
this.panelType_ = this.panelTypeDefault; this.panelType_ = this.panelTypeDefault;
...@@ -260,6 +261,10 @@ class PanelItem extends HTMLElement { ...@@ -260,6 +261,10 @@ class PanelItem extends HTMLElement {
break; break;
case this.panelTypeInfo: case this.panelTypeInfo:
break; break;
case this.panelTypeFormatProgress:
this.setAttribute('indicator', 'status');
this.setAttribute('status', 'hard-drive');
break;
} }
this.panelType_ = type; this.panelType_ = type;
......
...@@ -407,7 +407,11 @@ class ProgressCenterPanel { ...@@ -407,7 +407,11 @@ class ProgressCenterPanel {
setTimeout(() => { setTimeout(() => {
this.feedbackHost_.attachPanelItem(panelItem); this.feedbackHost_.attachPanelItem(panelItem);
}, this.PENDING_TIME_MS_); }, this.PENDING_TIME_MS_);
if (item.type === 'format') {
panelItem.panelType = panelItem.panelTypeFormatProgress;
} else {
panelItem.panelType = panelItem.panelTypeProgress; panelItem.panelType = panelItem.panelTypeProgress;
}
panelItem.userData = { panelItem.userData = {
'source': item.sourceMessage, 'source': item.sourceMessage,
'destination': item.destinationMessage, 'destination': item.destinationMessage,
...@@ -437,9 +441,10 @@ class ProgressCenterPanel { ...@@ -437,9 +441,10 @@ class ProgressCenterPanel {
panelItem.progress = item.progressRateInPercent.toString(); panelItem.progress = item.progressRateInPercent.toString();
switch (item.state) { switch (item.state) {
case 'completed': case 'completed':
// Create a completed panel for copies and moves. // Create a completed panel for copies, moves and formats.
// TODO(crbug.com/947388) decide if we want these for delete, etc. // TODO(crbug.com/947388) decide if we want these for delete, etc.
if (item.type === 'copy' || item.type === 'move') { if (item.type === 'copy' || item.type === 'move' ||
item.type === 'format') {
const donePanelItem = this.feedbackHost_.addPanelItem(item.id); const donePanelItem = this.feedbackHost_.addPanelItem(item.id);
donePanelItem.panelType = donePanelItem.panelTypeDone; donePanelItem.panelType = donePanelItem.panelTypeDone;
donePanelItem.primaryText = donePanelItem.primaryText =
......
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