Commit 3d4f3ff1 authored by Luciano Pacheco's avatar Luciano Pacheco Committed by Commit Bot

[Files app] Convert file_selection.js to es6 classes

Used lebab to convert to ES6 class with the following command:
lebab --replace \
 ui/file_manager/file_manager/foreground/js/file_selection.js \
 --transform class

Manual fixes:
- Fix closure markup to go to constructor() instead of class
definition.
- Fix Mock class to be ES6 class too, since closure doesn't allow
old style classes to extends from ES6 classes.
- Rename MockFileSelectionHandler to FakeSelectionHandler. This fake
lies to closure by saying that it extends FileSelectionHandler to allow
it to be passed as FileSelectionHandler in closure compilation.
- Add addEventListener() method to FakeFileSelectionHandler to fix
tests that use this method.

Bug: 778674
Change-Id: I39fb5ef7d7d8ef9c7fbc2b64244a26b9ebe23584
Reviewed-on: https://chromium-review.googlesource.com/c/1457700Reviewed-by: default avatarJoel Hockey <joelhockey@chromium.org>
Commit-Queue: Luciano Pacheco <lucmult@chromium.org>
Cr-Commit-Position: refs/heads/master@{#629911}
parent c471f24e
...@@ -4,178 +4,285 @@ ...@@ -4,178 +4,285 @@
/** /**
* The current selection object. * The current selection object.
* @param {!Array<number>} indexes
* @param {!Array<Entry>} entries
* @constructor
* @struct
*/ */
function FileSelection(indexes, entries) { class FileSelection {
/** /**
* @public {!Array<number>} * @param {!Array<number>} indexes
* @const * @param {!Array<Entry>} entries
*/ */
this.indexes = indexes; constructor(indexes, entries) {
/**
/** * @public {!Array<number>}
* @public {!Array<!Entry>} * @const
* @const */
*/ this.indexes = indexes;
this.entries = entries;
/**
* @public {!Array<!Entry>}
* @const
*/
this.entries = entries;
/**
* @public {!Array<string>}
*/
this.mimeTypes = [];
/**
* @public {number}
*/
this.totalCount = 0;
/**
* @public {number}
*/
this.fileCount = 0;
/**
* @public {number}
*/
this.directoryCount = 0;
/**
* @public {boolean}
*/
this.anyFilesNotInCache = true;
/**
* @public {boolean}
*/
this.anyFilesHosted = true;
/**
* @public {?string}
*/
this.iconType = null;
/**
* @private {Promise<boolean>}
*/
this.additionalPromise_ = null;
entries.forEach(entry => {
if (this.iconType == null) {
this.iconType = FileType.getIcon(entry);
} else if (this.iconType != 'unknown') {
const iconType = FileType.getIcon(entry);
if (this.iconType != iconType) {
this.iconType = 'unknown';
}
}
/** if (entry.isFile) {
* @public {!Array<string>} this.fileCount += 1;
*/ } else {
this.mimeTypes = []; this.directoryCount += 1;
}
this.totalCount++;
});
}
/** computeAdditional(metadataModel) {
* @public {number} if (!this.additionalPromise_) {
*/ this.additionalPromise_ =
this.totalCount = 0; metadataModel
.get(
this.entries,
constants.FILE_SELECTION_METADATA_PREFETCH_PROPERTY_NAMES)
.then(props => {
const present = props.filter(p => {
// If no availableOffline property, then assume it's
// available.
return !('availableOffline' in p) || p.availableOffline;
});
const hosted = props.filter(p => {
return p.hosted;
});
this.anyFilesNotInCache = present.length !== props.length;
this.anyFilesHosted = !!hosted.length;
this.mimeTypes = props.map(value => {
return value.contentMimeType || '';
});
return true;
});
}
return this.additionalPromise_;
}
}
/**
* This object encapsulates everything related to current selection.
*/
class FileSelectionHandler extends cr.EventTarget {
/** /**
* @public {number} * @param {!DirectoryModel} directoryModel
* @param {!FileOperationManager} fileOperationManager
* @param {!ListContainer} listContainer
* @param {!MetadataModel} metadataModel
* @param {!VolumeManager} volumeManager
* @param {!AllowedPaths} allowedPaths
* @struct
*/ */
this.fileCount = 0; constructor(
directoryModel, fileOperationManager, listContainer, metadataModel,
volumeManager, allowedPaths) {
super();
/**
* @private {DirectoryModel}
* @const
*/
this.directoryModel_ = directoryModel;
/**
* @private {ListContainer}
* @const
*/
this.listContainer_ = listContainer;
/**
* @private {MetadataModel}
* @const
*/
this.metadataModel_ = metadataModel;
/**
* @private {VolumeManager}
* @const
*/
this.volumeManager_ = volumeManager;
/**
* @type {FileSelection}
*/
this.selection = new FileSelection([], []);
/**
* @private {?number}
*/
this.selectionUpdateTimer_ = 0;
/**
* @private {number}
*/
this.lastFileSelectionTime_ = Date.now();
/**
* @private {AllowedPaths}
* @const
*/
this.allowedPaths_ = allowedPaths;
util.addEventListenerToBackgroundComponent(
assert(fileOperationManager), 'entries-changed',
this.onFileSelectionChanged.bind(this));
// Register evnets to update file selections.
directoryModel.addEventListener(
'directory-changed', this.onFileSelectionChanged.bind(this));
}
/** /**
* @public {number} * Update the UI when the selection model changes.
*/ */
this.directoryCount = 0; onFileSelectionChanged() {
const indexes = this.listContainer_.selectionModel.selectedIndexes;
const entries = indexes.map(index => {
return /** @type {!Entry} */ (
this.directoryModel_.getFileList().item(index));
});
this.selection = new FileSelection(indexes, entries);
if (this.selectionUpdateTimer_) {
clearTimeout(this.selectionUpdateTimer_);
this.selectionUpdateTimer_ = null;
}
/** // The rest of the selection properties are computed via (sometimes lengthy)
* @public {boolean} // asynchronous calls. We initiate these calls after a timeout. If the
*/ // selection is changing quickly we only do this once when it slows down.
this.anyFilesNotInCache = true;
/** let updateDelay = FileSelectionHandler.UPDATE_DELAY;
* @public {boolean} const now = Date.now();
*/
this.anyFilesHosted = true;
/** if (now > (this.lastFileSelectionTime_ || 0) + updateDelay &&
* @public {?string} indexes.length <
*/ FileSelectionHandler.NUMBER_OF_ITEMS_HEAVY_TO_COMPUTE) {
this.iconType = null; // The previous selection change happened a while ago and there is few
// selected items, so computation is lightweight. Update the UI without
/** // delay.
* @private {Promise<boolean>} updateDelay = 0;
*/
this.additionalPromise_ = null;
entries.forEach(entry => {
if (this.iconType == null) {
this.iconType = FileType.getIcon(entry);
} else if (this.iconType != 'unknown') {
const iconType = FileType.getIcon(entry);
if (this.iconType != iconType) {
this.iconType = 'unknown';
}
} }
this.lastFileSelectionTime_ = now;
if (entry.isFile) { const selection = this.selection;
this.fileCount += 1; this.selectionUpdateTimer_ = setTimeout(() => {
} else { this.selectionUpdateTimer_ = null;
this.directoryCount += 1; if (this.selection === selection) {
} this.updateFileSelectionAsync_(selection);
this.totalCount++; }
}); }, updateDelay);
}
FileSelection.prototype.computeAdditional = function(metadataModel) { cr.dispatchSimpleEvent(this, FileSelectionHandler.EventType.CHANGE);
if (!this.additionalPromise_) {
this.additionalPromise_ =
metadataModel
.get(
this.entries,
constants.FILE_SELECTION_METADATA_PREFETCH_PROPERTY_NAMES)
.then(props => {
const present = props.filter(p => {
// If no availableOffline property, then assume it's available.
return !('availableOffline' in p) || p.availableOffline;
});
const hosted = props.filter(p => {
return p.hosted;
});
this.anyFilesNotInCache = present.length !== props.length;
this.anyFilesHosted = !!hosted.length;
this.mimeTypes = props.map(value => {
return value.contentMimeType || '';
});
return true;
});
} }
return this.additionalPromise_;
};
/**
* This object encapsulates everything related to current selection.
*
* @param {!DirectoryModel} directoryModel
* @param {!FileOperationManager} fileOperationManager
* @param {!ListContainer} listContainer
* @param {!MetadataModel} metadataModel
* @param {!VolumeManager} volumeManager
* @param {!AllowedPaths} allowedPaths
* @extends {cr.EventTarget}
* @constructor
* @struct
*/
function FileSelectionHandler(
directoryModel, fileOperationManager, listContainer, metadataModel,
volumeManager, allowedPaths) {
cr.EventTarget.call(this);
/**
* @private {DirectoryModel}
* @const
*/
this.directoryModel_ = directoryModel;
/** /**
* @private {ListContainer} * Calculates async selection stats and updates secondary UI elements.
* @const *
* @param {FileSelection} selection The selection object.
* @private
*/ */
this.listContainer_ = listContainer; updateFileSelectionAsync_(selection) {
if (this.selection !== selection) {
return;
}
/** // Calculate all additional and heavy properties.
* @private {MetadataModel} selection.computeAdditional(this.metadataModel_).then(() => {
* @const if (this.selection !== selection) {
*/ return;
this.metadataModel_ = metadataModel; }
/** cr.dispatchSimpleEvent(
* @private {VolumeManager} this, FileSelectionHandler.EventType.CHANGE_THROTTLED);
* @const });
*/ }
this.volumeManager_ = volumeManager;
/** /**
* @type {FileSelection} * Returns true if all files in the selection files are selectable.
* @return {boolean}
*/ */
this.selection = new FileSelection([], []); isAvailable() {
if (!this.directoryModel_.isOnDrive()) {
return true;
}
/** return !(
* @private {?number} this.isOfflineWithUncachedFilesSelected_() ||
*/ this.isDialogWithHostedFilesSelected_());
this.selectionUpdateTimer_ = 0; }
/** /**
* @private {number} * Returns true if we're offline with any selected files absent from the
* cache.
* @return {boolean}
* @private
*/ */
this.lastFileSelectionTime_ = Date.now(); isOfflineWithUncachedFilesSelected_() {
return this.volumeManager_.getDriveConnectionState().type ===
VolumeManagerCommon.DriveConnectionType.OFFLINE &&
this.selection.anyFilesNotInCache;
}
/** /**
* @private {AllowedPaths} * Returns true if we're a dialog requiring real files with hosted files
* @const * selected.
* @return {boolean}
* @private
*/ */
this.allowedPaths_ = allowedPaths; isDialogWithHostedFilesSelected_() {
return this.allowedPaths_ !== AllowedPaths.ANY_PATH_OR_URL &&
util.addEventListenerToBackgroundComponent( this.selection.anyFilesHosted;
assert(fileOperationManager), 'entries-changed', }
this.onFileSelectionChanged.bind(this));
// Register evnets to update file selections.
directoryModel.addEventListener(
'directory-changed', this.onFileSelectionChanged.bind(this));
} }
/** /**
...@@ -210,111 +317,3 @@ FileSelectionHandler.UPDATE_DELAY = 200; ...@@ -210,111 +317,3 @@ FileSelectionHandler.UPDATE_DELAY = 200;
* @const {number} * @const {number}
*/ */
FileSelectionHandler.NUMBER_OF_ITEMS_HEAVY_TO_COMPUTE = 100; FileSelectionHandler.NUMBER_OF_ITEMS_HEAVY_TO_COMPUTE = 100;
/**
* FileSelectionHandler extends cr.EventTarget.
*/
FileSelectionHandler.prototype.__proto__ = cr.EventTarget.prototype;
/**
* Update the UI when the selection model changes.
*/
FileSelectionHandler.prototype.onFileSelectionChanged = function() {
const indexes = this.listContainer_.selectionModel.selectedIndexes;
const entries = indexes.map(index => {
return /** @type {!Entry} */ (
this.directoryModel_.getFileList().item(index));
});
this.selection = new FileSelection(indexes, entries);
if (this.selectionUpdateTimer_) {
clearTimeout(this.selectionUpdateTimer_);
this.selectionUpdateTimer_ = null;
}
// The rest of the selection properties are computed via (sometimes lengthy)
// asynchronous calls. We initiate these calls after a timeout. If the
// selection is changing quickly we only do this once when it slows down.
let updateDelay = FileSelectionHandler.UPDATE_DELAY;
const now = Date.now();
if (now > (this.lastFileSelectionTime_ || 0) + updateDelay &&
indexes.length < FileSelectionHandler.NUMBER_OF_ITEMS_HEAVY_TO_COMPUTE) {
// The previous selection change happened a while ago and there is few
// selected items, so computation is lightweight. Update the UI without
// delay.
updateDelay = 0;
}
this.lastFileSelectionTime_ = now;
const selection = this.selection;
this.selectionUpdateTimer_ = setTimeout(() => {
this.selectionUpdateTimer_ = null;
if (this.selection === selection) {
this.updateFileSelectionAsync_(selection);
}
}, updateDelay);
cr.dispatchSimpleEvent(this, FileSelectionHandler.EventType.CHANGE);
};
/**
* Calculates async selection stats and updates secondary UI elements.
*
* @param {FileSelection} selection The selection object.
* @private
*/
FileSelectionHandler.prototype.updateFileSelectionAsync_ = function(selection) {
if (this.selection !== selection) {
return;
}
// Calculate all additional and heavy properties.
selection.computeAdditional(this.metadataModel_).then(() => {
if (this.selection !== selection) {
return;
}
cr.dispatchSimpleEvent(
this, FileSelectionHandler.EventType.CHANGE_THROTTLED);
});
};
/**
* Returns true if all files in the selection files are selectable.
* @return {boolean}
*/
FileSelectionHandler.prototype.isAvailable = function() {
if (!this.directoryModel_.isOnDrive()) {
return true;
}
return !(
this.isOfflineWithUncachedFilesSelected_() ||
this.isDialogWithHostedFilesSelected_());
};
/**
* Returns true if we're offline with any selected files absent from the cache.
* @return {boolean}
* @private
*/
FileSelectionHandler.prototype.isOfflineWithUncachedFilesSelected_ =
function() {
return this.volumeManager_.getDriveConnectionState().type ===
VolumeManagerCommon.DriveConnectionType.OFFLINE &&
this.selection.anyFilesNotInCache;
};
/**
* Returns true if we're a dialog requiring real files with hosted files
* selected.
* @return {boolean}
* @private
*/
FileSelectionHandler.prototype.isDialogWithHostedFilesSelected_ = function() {
return this.allowedPaths_ !== AllowedPaths.ANY_PATH_OR_URL &&
this.selection.anyFilesHosted;
};
...@@ -108,32 +108,32 @@ function createTaskController(fileSelectionHandler) { ...@@ -108,32 +108,32 @@ function createTaskController(fileSelectionHandler) {
/** /**
* Mock FileSelectionHandler. * Mock FileSelectionHandler.
* @constructor
* @extends {FileSelectionHandler} * @extends {FileSelectionHandler}
*/ */
function MockFileSelectionHandler() { class FakeFileSelectionHandler {
this.computeAdditionalCallback = function() {}; constructor() {
this.selection = /** @type {!FileSelection} */ ({}); this.selection = /** @type {!FileSelection} */ ({});
this.updateSelection([], []); this.updateSelection([], []);
} this.eventTarget_ = new cr.EventTarget();
}
MockFileSelectionHandler.prototype = /** @struct */ { computeAdditionalCallback() {}
__proto__: cr.EventTarget.prototype, updateSelection(entries, mimeTypes) {
}; this.selection = /** @type {!FileSelection} */ ({
entries: entries,
mimeTypes: mimeTypes,
computeAdditional: (metadataModel) => {
this.computeAdditionalCallback();
return new Promise((resolve) => {
resolve();
});
},
});
}
MockFileSelectionHandler.prototype.updateSelection = function( addEventListener(...args) {
entries, mimeTypes) { return this.eventTarget_.addEventListener(...args);
this.selection = /** @type {!FileSelection} */ ({ }
entries: entries, }
mimeTypes: mimeTypes,
computeAdditional: (metadataModel) => {
this.computeAdditionalCallback();
return new Promise((resolve) => {
resolve();
});
},
});
};
/** /**
* Setup test case fileManagerPrivate. * Setup test case fileManagerPrivate.
...@@ -165,7 +165,7 @@ function setupFileManagerPrivate() { ...@@ -165,7 +165,7 @@ function setupFileManagerPrivate() {
* Tests that executeEntryTask() runs the expected task. * Tests that executeEntryTask() runs the expected task.
*/ */
function testExecuteEntryTask(callback) { function testExecuteEntryTask(callback) {
var selectionHandler = new MockFileSelectionHandler(); var selectionHandler = new FakeFileSelectionHandler();
var fileSystem = new MockFileSystem('volumeId'); var fileSystem = new MockFileSystem('volumeId');
fileSystem.entries['/test.png'] = new MockFileEntry(fileSystem, '/test.png'); fileSystem.entries['/test.png'] = new MockFileEntry(fileSystem, '/test.png');
...@@ -188,7 +188,7 @@ function testExecuteEntryTask(callback) { ...@@ -188,7 +188,7 @@ function testExecuteEntryTask(callback) {
* multiple times when the selected entries are not changed. * multiple times when the selected entries are not changed.
*/ */
function testGetFileTasksShouldNotBeCalledMultipleTimes(callback) { function testGetFileTasksShouldNotBeCalledMultipleTimes(callback) {
var selectionHandler = new MockFileSelectionHandler(); var selectionHandler = new FakeFileSelectionHandler();
var fileSystem = new MockFileSystem('volumeId'); var fileSystem = new MockFileSystem('volumeId');
selectionHandler.updateSelection( selectionHandler.updateSelection(
...@@ -225,7 +225,7 @@ function testGetFileTasksShouldNotBeCalledMultipleTimes(callback) { ...@@ -225,7 +225,7 @@ function testGetFileTasksShouldNotBeCalledMultipleTimes(callback) {
* called. * called.
*/ */
function testGetFileTasksShouldNotReturnObsoletePromise(callback) { function testGetFileTasksShouldNotReturnObsoletePromise(callback) {
var selectionHandler = new MockFileSelectionHandler(); var selectionHandler = new FakeFileSelectionHandler();
var fileSystem = new MockFileSystem('volumeId'); var fileSystem = new MockFileSystem('volumeId');
selectionHandler.updateSelection( selectionHandler.updateSelection(
...@@ -256,7 +256,7 @@ function testGetFileTasksShouldNotReturnObsoletePromise(callback) { ...@@ -256,7 +256,7 @@ function testGetFileTasksShouldNotReturnObsoletePromise(callback) {
* the getFileTasks() promise to reject. * the getFileTasks() promise to reject.
*/ */
function testGetFileTasksShouldNotCacheRejectedPromise(callback) { function testGetFileTasksShouldNotCacheRejectedPromise(callback) {
var selectionHandler = new MockFileSelectionHandler(); var selectionHandler = new FakeFileSelectionHandler();
var fileSystem = new MockFileSystem('volumeId'); var fileSystem = new MockFileSystem('volumeId');
selectionHandler.updateSelection( selectionHandler.updateSelection(
......
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