blob: 1d9f1e409290a40a8969e421c93931d002201843 [file] [log] [blame]
// Copyright (c) 2013 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.
'use strict';
document.addEventListener('DOMContentLoaded', function() {
ActionChoice.load();
});
/**
* The main ActionChoice object.
*
* @param {HTMLElement} dom Container.
* @param {FileSystem} filesystem Local file system.
* @param {VolumeManager} volumeManager VolueManager of the app.
* @param {Object} params Parameters.
* @constructor
*/
function ActionChoice(dom, filesystem, volumeManager, params) {
this.dom_ = dom;
this.filesystem_ = filesystem;
this.params_ = params;
this.document_ = this.dom_.ownerDocument;
this.metadataCache_ = this.params_.metadataCache;
this.volumeManager_ = volumeManager;
this.volumeManager_.addEventListener('externally-unmounted',
this.onDeviceUnmounted_.bind(this));
this.initDom_();
// Load defined actions and remembered choice, then initialize volumes.
this.actions_ = [];
this.actionsById_ = {};
this.rememberedChoice_ = null;
ActionChoiceUtil.getDefinedActions(loadTimeData, function(actions) {
for (var i = 0; i < actions.length; i++) {
this.registerAction_(actions[i]);
}
this.viewFilesAction_ = this.actionsById_['view-files'];
this.importPhotosToDriveAction_ =
this.actionsById_['import-photos-to-drive'];
this.watchSingleVideoAction_ =
this.actionsById_['watch-single-video'];
// Special case: if Google+ Photos is installed, then do not show Drive.
for (var i = 0; i < actions.length; i++) {
if (actions[i].extensionId == ActionChoice.GPLUS_PHOTOS_EXTENSION_ID) {
this.importPhotosToDriveAction_.hidden = true;
break;
}
}
if (this.params_.advancedMode) {
// In the advanced mode, skip auto-choice.
this.initializeVolumes_();
} else {
// Get the remembered action before initializing volumes.
ActionChoiceUtil.getRememberedActionId(function(actionId) {
this.rememberedChoice_ = actionId;
this.initializeVolumes_();
}.bind(this));
}
this.renderList_();
}.bind(this));
// Try to render, what is already available.
this.renderList_();
}
ActionChoice.prototype = { __proto__: cr.EventTarget.prototype };
/**
* The number of previews shown.
* @type {number}
* @const
*/
ActionChoice.PREVIEW_COUNT = 3;
/**
* Extension id of Google+ Photos app.
* @type {string}
* @const
*/
ActionChoice.GPLUS_PHOTOS_EXTENSION_ID = 'efjnaogkjbogokcnohkmnjdojkikgobo';
/**
* Loads app in the document body.
* @param {FileSystem=} opt_filesystem Local file system.
* @param {Object=} opt_params Parameters.
*/
ActionChoice.load = function(opt_filesystem, opt_params) {
ImageUtil.metrics = metrics;
var hash = location.hash ? decodeURIComponent(location.hash.substr(1)) : '';
var query =
location.search ? decodeURIComponent(location.search.substr(1)) : '';
var params = opt_params || {};
if (!params.source) params.source = hash;
if (!params.advancedMode) params.advancedMode = (query == 'advanced-mode');
if (!params.metadataCache) params.metadataCache = MetadataCache.createFull();
var onFilesystem = function(filesystem) {
var dom = document.querySelector('.action-choice');
VolumeManager.getInstance(function(volumeManager) {
ActionChoice.instance = new ActionChoice(
dom, filesystem, volumeManager, params);
});
};
chrome.fileBrowserPrivate.getStrings(function(strings) {
loadTimeData.data = strings;
i18nTemplate.process(document, loadTimeData);
if (opt_filesystem) {
onFilesystem(opt_filesystem);
} else {
chrome.fileBrowserPrivate.requestFileSystem('compatible', onFilesystem);
}
});
};
/**
* Registers an action.
* @param {Object} action Action item.
* @private
*/
ActionChoice.prototype.registerAction_ = function(action) {
this.actions_.push(action);
this.actionsById_[action.id] = action;
};
/**
* Initializes the source and Drive. If the remembered choice is available,
* then performs the action.
* @private
*/
ActionChoice.prototype.initializeVolumes_ = function() {
var checkDriveFinished = false;
var loadSourceFinished = false;
var maybeRunRememberedAction = function() {
if (!checkDriveFinished || !loadSourceFinished)
return;
// Run the remembered action if it is available.
if (this.rememberedChoice_) {
var action = this.actionsById_[this.rememberedChoice_];
if (action && !action.disabled)
this.runAction_(action);
}
}.bind(this);
var onCheckDriveFinished = function() {
checkDriveFinished = true;
maybeRunRememberedAction();
};
var onLoadSourceFinished = function() {
loadSourceFinished = true;
maybeRunRememberedAction();
};
this.checkDrive_(onCheckDriveFinished);
this.loadSource_(this.params_.source, onLoadSourceFinished);
};
/**
* One-time initialization of dom elements.
* @private
*/
ActionChoice.prototype.initDom_ = function() {
this.list_ = new cr.ui.List();
this.list_.id = 'actions-list';
this.document_.querySelector('.choices').appendChild(this.list_);
var self = this; // .bind(this) doesn't work on constructors.
this.list_.itemConstructor = function(item) {
return self.renderItem(item);
};
this.list_.selectionModel = new cr.ui.ListSingleSelectionModel();
this.list_.dataModel = new cr.ui.ArrayDataModel([]);
this.list_.autoExpands = true;
var acceptActionBound = function() {
this.acceptAction_();
}.bind(this);
this.list_.activateItemAtIndex = acceptActionBound;
this.list_.addEventListener('click', acceptActionBound);
this.previews_ = this.document_.querySelector('.previews');
this.counter_ = this.document_.querySelector('.counter');
this.document_.addEventListener('keydown', this.onKeyDown_.bind(this));
metrics.startInterval('PhotoImport.Load');
this.dom_.setAttribute('loading', '');
};
/**
* Renders the list.
* @private
*/
ActionChoice.prototype.renderList_ = function() {
var currentItem = this.list_.dataModel.item(
this.list_.selectionModel.selectedIndex);
this.list_.startBatchUpdates();
this.list_.dataModel.splice(0, this.list_.dataModel.length);
for (var i = 0; i < this.actions_.length; i++) {
if (!this.actions_[i].hidden)
this.list_.dataModel.push(this.actions_[i]);
}
for (var i = 0; i < this.list_.dataModel.length; i++) {
if (this.list_.dataModel.item(i) == currentItem) {
this.list_.selectionModel.selectedIndex = i;
break;
}
}
this.list_.endBatchUpdates();
};
/**
* Renders an item in the list.
* @param {Object} item Item to render.
* @return {Element} DOM element with representing the item.
*/
ActionChoice.prototype.renderItem = function(item) {
var result = this.document_.createElement('li');
var div = this.document_.createElement('div');
if (item.disabled && item.disabledTitle)
div.textContent = item.disabledTitle;
else
div.textContent = item.title;
if (item.class)
div.classList.add(item.class);
if (item.icon100 && item.icon200)
div.style.backgroundImage = '-webkit-image-set(' +
'url(' + item.icon100 + ') 1x,' +
'url(' + item.icon200 + ') 2x)';
if (item.disabled)
div.classList.add('disabled');
cr.defineProperty(result, 'lead', cr.PropertyKind.BOOL_ATTR);
cr.defineProperty(result, 'selected', cr.PropertyKind.BOOL_ATTR);
result.appendChild(div);
return result;
};
/**
* Checks whether Drive is reachable.
*
* @param {function()} callback Completion callback.
* @private
*/
ActionChoice.prototype.checkDrive_ = function(callback) {
var onVolumeManagerReady = function() {
this.volumeManager_.removeEventListener('ready', onVolumeManagerReady);
this.importPhotosToDriveAction_.disabled =
!this.volumeManager_.getVolumeInfo(RootDirectory.DRIVE);
this.renderList_();
callback();
}.bind(this);
if (this.volumeManager_.isReady()) {
onVolumeManagerReady();
} else {
this.volumeManager_.addEvventListener('ready', onVolumeManagerReady);
}
};
/**
* Load the source contents.
*
* @param {string} source Path to source.
* @param {function()} callback Completion callback.
* @private
*/
ActionChoice.prototype.loadSource_ = function(source, callback) {
var onTraversed = function(results) {
metrics.recordInterval('PhotoImport.Scan');
var videos = results.filter(FileType.isVideo);
if (videos.length == 1) {
this.singleVideo_ = videos[0];
this.watchSingleVideoAction_.title = loadTimeData.getStringF(
'ACTION_CHOICE_WATCH_SINGLE_VIDEO', videos[0].name);
this.watchSingleVideoAction_.hidden = false;
this.watchSingleVideoAction_.disabled = false;
this.renderList_();
}
var mediaFiles = results.filter(FileType.isImageOrVideo);
if (mediaFiles.length == 0) {
// If we have no media files, the only choice is view files. So, don't
// confuse user with a single choice, and just open file manager.
this.viewFiles_();
this.recordAction_('view-files-auto');
this.close_();
}
if (mediaFiles.length < ActionChoice.PREVIEW_COUNT) {
this.counter_.textContent = loadTimeData.getStringF(
'ACTION_CHOICE_COUNTER_NO_MEDIA', results.length);
} else {
this.counter_.textContent = loadTimeData.getStringF(
'ACTION_CHOICE_COUNTER', mediaFiles.length);
}
var previews = mediaFiles.length ? mediaFiles : results;
var previewsCount = Math.min(ActionChoice.PREVIEW_COUNT, previews.length);
this.renderPreview_(previews, previewsCount);
callback();
}.bind(this);
var onEntry = function(entry) {
this.sourceEntry_ = entry;
this.document_.querySelector('title').textContent = entry.name;
var volumeInfo = this.volumeManager_.getVolumeInfo(entry.fullPath);
var deviceType = volumeInfo && volumeInfo.deviceType;
if (deviceType != 'sd') deviceType = 'usb';
this.dom_.querySelector('.device-type').setAttribute('device-type',
deviceType);
this.dom_.querySelector('.loading-text').textContent =
loadTimeData.getString('ACTION_CHOICE_LOADING_' +
deviceType.toUpperCase());
var entryList = [];
util.traverseTree(
entry,
function(traversedEntry) {
if (!FileType.isVisible(traversedEntry))
return false;
entryList.push(traversedEntry);
return true;
},
function() {
onTraversed(entryList);
},
function(error) {
console.error(
'Failed to traverse [' + entry.fullPath + ']: ' + error.code);
});
}.bind(this);
var onReady = function() {
// TODO(hidehiko): Replace filesystem by volumeManager.
util.resolvePath(this.filesystem_.root, source, onEntry, function() {
this.recordAction_('error');
this.close_();
}.bind(this));
}.bind(this);
this.sourceEntry_ = null;
metrics.startInterval('PhotoImport.Scan');
if (!this.volumeManager_.isReady())
this.volumeManager_.addEventListener('ready', onReady);
else
onReady();
};
/**
* Renders a preview for a media entry.
* @param {Array.<FileEntry>} entries The entries.
* @param {number} count Remaining count.
* @private
*/
ActionChoice.prototype.renderPreview_ = function(entries, count) {
var entry = entries.shift();
var box = this.document_.createElement('div');
box.className = 'img-container';
var done = function() {
this.dom_.removeAttribute('loading');
metrics.recordInterval('PhotoImport.Load');
}.bind(this);
var onSuccess = function() {
this.previews_.appendChild(box);
if (--count == 0) {
done();
} else {
this.renderPreview_(entries, count);
}
}.bind(this);
var onError = function() {
if (entries.length == 0) {
// Append one image with generic thumbnail.
this.previews_.appendChild(box);
done();
} else {
this.renderPreview_(entries, count);
}
}.bind(this);
this.metadataCache_.get(entry, 'thumbnail|filesystem',
function(metadata) {
new ThumbnailLoader(entry.toURL(),
ThumbnailLoader.LoaderType.IMAGE,
metadata).load(
box,
ThumbnailLoader.FillMode.FILL,
ThumbnailLoader.OptimizationMode.NEVER_DISCARD,
onSuccess,
onError,
onError);
});
};
/**
* Closes the window.
* @private
*/
ActionChoice.prototype.close_ = function() {
window.close();
};
/**
* Keydown event handler.
* @param {Event} e The event.
* @private
*/
ActionChoice.prototype.onKeyDown_ = function(e) {
switch (util.getKeyModifiers(e) + e.keyCode) {
case '13':
this.acceptAction_();
break;
case '27':
this.recordAction_('close');
this.close_();
break;
}
};
/**
* Runs an action.
* @param {Object} action Action item to perform.
* @private
*/
ActionChoice.prototype.runAction_ = function(action) {
// TODO(mtomasz): Remove these predefined actions in Apps v2.
if (action == this.importPhotosToDriveAction_) {
var url = chrome.runtime.getURL('photo_import.html') +
'#' + this.sourceEntry_.fullPath;
var width = 728;
var height = 656;
var top = Math.round((window.screen.availHeight - height) / 2);
var left = Math.round((window.screen.availWidth - width) / 2);
chrome.app.window.create(url,
{height: height, width: width, left: left, top: top});
this.recordAction_('import-photos-to-drive');
this.close_();
return;
}
if (action == this.watchSingleVideoAction_) {
util.viewFilesInBrowser([this.singleVideo_.toURL()],
function(success) {});
this.recordAction_('watch-single-video');
this.close_();
return;
}
if (action == this.viewFilesAction_) {
this.viewFiles_();
this.recordAction_('view-files');
this.close_();
return;
}
if (!action.extensionId) {
console.error('Unknown predefined action.');
return;
}
// Run the media galleries handler.
chrome.mediaGalleriesPrivate.launchHandler(action.extensionId,
action.actionId,
this.params_.source);
this.close_();
};
/**
* Handles accepting an action. Checks if the action is available, remembers
* and runs it.
* @private
*/
ActionChoice.prototype.acceptAction_ = function() {
var action =
this.list_.dataModel.item(this.list_.selectionModel.selectedIndex);
if (!action || action.hidden || action.disabled)
return;
this.runAction_(action);
ActionChoiceUtil.setRememberedActionId(action.id);
};
/**
* Called when some device is unmounted.
* @param {Event} event Event object.
* @private
*/
ActionChoice.prototype.onDeviceUnmounted_ = function(event) {
if (this.sourceEntry_ && event.mountPath == this.sourceEntry_.fullPath)
window.close();
};
/**
* Perform the 'view files' action.
* @private
*/
ActionChoice.prototype.viewFiles_ = function() {
var path = this.sourceEntry_.fullPath;
chrome.runtime.getBackgroundPage(function(bg) {
bg.launchFileManager({defaultPath: path});
});
};
/**
* Records an action chosen.
* @param {string} action Action name.
* @private
*/
ActionChoice.prototype.recordAction_ = function(action) {
metrics.recordEnum('PhotoImport.Action', action,
['import-photos-to-drive',
'view-files',
'view-files-auto',
'watch-single-video',
'error',
'close']);
};