blob: 08c76235e9f460c034e11e79af3953214c62fd93 [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.
// TODO(crbug.com/800945): Rename it to WallpaperPicker for consistency.
/**
* WallpaperManager constructor.
*
* WallpaperManager objects encapsulate the functionality of the wallpaper
* manager extension.
*
* @constructor
* @param {HTMLElement} dialogDom The DOM node containing the prototypical
* extension UI.
*/
function WallpaperManager(dialogDom) {
this.dialogDom_ = dialogDom;
this.document_ = dialogDom.ownerDocument;
this.useNewWallpaperPicker_ =
loadTimeData.getBoolean('useNewWallpaperPicker');
this.enableOnlineWallpaper_ = loadTimeData.valueExists('manifestBaseURL') ||
this.useNewWallpaperPicker_;
this.selectedItem_ = null;
this.progressManager_ = new ProgressManager();
this.customWallpaperData_ = null;
this.currentWallpaper_ = null;
this.wallpaperRequest_ = null;
this.wallpaperDirs_ = WallpaperDirectories.getInstance();
this.preDownloadDomInit_();
// Uses the redesigned wallpaper picker if |useNewWallpaperPicker| is true.
//
// The old wallpaper picker fetches the manifest file once, and parse the info
// (such as image url) from the file every time when the images need to be
// displayed.
// The new wallpaper picker has two steps: it first fetches a list of
// collection names (ie. categories such as Art, Landscape etc.) via extension
// API, and then fetches the info specific to each collection and caches the
// info in a map.
// After the url and relevant info of the images are fetched, the two share
// the same path, ie. pass the info to |WallpaperThumbnailsGridItem| for
// actual image rendering.
this.document_.body.classList.add(this.useNewWallpaperPicker_ ? 'v2' : 'v1');
if (this.useNewWallpaperPicker_) {
// |collectionsInfo_| represents the list of wallpaper collections. Each
// collection contains the display name and a unique id.
this.collectionsInfo_ = null;
// |imagesInfoMap_| caches the mapping between each collection id and the
// images that belong to this collection. Each image is represented by a set
// of info including the image url, author name, layout etc. Such info will
// be used by |WallpaperThumbnailsGridItem| to display the images.
this.imagesInfoMap_ = {};
// The total count of images whose info has been fetched.
this.imagesInfoCount_ = 0;
// |dailyRefreshInfo_| stores the info related to the daily refresh feature
// on the new picker. Its value should be consistent with the sync/local
// storage.
this.dailyRefreshInfo_ = null;
// |pendingDailyRefreshInfo_| stores the up-to-date daily refresh info that
// hasn't been confirmed by user (e.g. when user is previewing the image).
// Its value will either replace |dailyRefreshInfo_| or be discarded.
this.pendingDailyRefreshInfo_ = null;
this.placeWallpaperPicker_();
this.getCollectionsInfo_();
} else {
this.fetchManifest_();
}
}
// Anonymous 'namespace'.
// TODO(bshe): Get rid of anonymous namespace.
(function() {
/**
* URL of the learn more page for wallpaper picker.
*
* @const
*/
var LearnMoreURL =
'https://support.google.com/chromebook/?p=wallpaper_fileerror&hl=' +
navigator.language;
/**
* Index of the All category. It is the first category in wallpaper picker.
*
* @const
*/
var AllCategoryIndex = 0;
/**
* Index offset of categories parsed from manifest. The All category is added
* before them. So the offset is 1.
*
* @const
*/
var OnlineCategoriesOffset = 1;
/**
* The following values should be kept in sync with the style sheet.
*/
var GRID_IMAGE_WIDTH_CSS = 160;
var GRID_IMAGE_PADDING_CSS = 8;
var DIALOG_TOP_BAR_WIDTH = 192;
/**
* Returns a translated string.
*
* Wrapper function to make dealing with translated strings more concise.
*
* @param {string} id The id of the string to return.
* @return {string} The translated string.
*/
function str(id) {
return loadTimeData.getString(id);
}
/**
* Returns the base name for |file_path|.
* @param {string} file_path The path of the file.
* @return {string} The base name of the file.
*/
function getBaseName(file_path) {
return file_path.substring(file_path.lastIndexOf('/') + 1);
}
/**
* Helper function to center the element.
* @param {Object} element The element to be centered.
* @param {number} totalWidth The total width. An empty value disables centering
* horizontally.
* @param {number} totalHeight The total height. An empty value disables
* centering vertically.
*/
function centerElement(element, totalWidth, totalHeight) {
if (totalWidth)
element.style.left = (totalWidth - element.offsetWidth) / 2 + 'px';
if (totalHeight)
element.style.top = (totalHeight - element.offsetHeight) / 2 + 'px';
}
/**
* Loads translated strings.
*/
WallpaperManager.initStrings = function(callback) {
chrome.wallpaperPrivate.getStrings(function(strings) {
loadTimeData.data = strings;
if (callback)
callback();
});
};
/**
* Requests wallpaper manifest file from server.
*/
WallpaperManager.prototype.fetchManifest_ = function() {
var locale = navigator.language;
if (!this.enableOnlineWallpaper_) {
this.postDownloadDomInit_();
return;
}
var urls = [
str('manifestBaseURL') + locale + '.json',
// Fallback url. Use 'en' locale by default.
str('manifestBaseURL') + 'en.json'
];
var asyncFetchManifestFromUrls = function(
urls, func, successCallback, failureCallback) {
var index = 0;
var loop = {
next: function() {
if (index < urls.length) {
func(loop, urls[index]);
index++;
} else {
failureCallback();
}
},
success: function(response) {
successCallback(response);
},
failure: function() {
failureCallback();
}
};
loop.next();
};
var fetchManifestAsync = function(loop, url) {
var xhr = new XMLHttpRequest();
try {
xhr.addEventListener('loadend', function(e) {
if (this.status == 200 && this.responseText != null) {
try {
var manifest = JSON.parse(this.responseText);
loop.success(manifest);
} catch (e) {
loop.failure();
}
} else {
loop.next();
}
});
xhr.open('GET', url, true);
xhr.send(null);
} catch (e) {
loop.failure();
}
};
if (navigator.onLine) {
asyncFetchManifestFromUrls(
urls, fetchManifestAsync, this.onLoadManifestSuccess_.bind(this),
this.onLoadManifestFailed_.bind(this));
} else {
// If device is offline, fetches manifest from local storage.
// TODO(bshe): Always loading the offline manifest first and replacing
// with the online one when available.
this.onLoadManifestFailed_();
}
};
/**
* Fetches wallpaper collection info.
* @private
*/
WallpaperManager.prototype.getCollectionsInfo_ = function() {
// Prefer to use the saved image info in local storage (if it exists) for
// faster loading.
Constants.WallpaperLocalStorage
.get(
Constants.AccessLocalImagesInfoKey, items => {
var imagesInfoJson = items[Constants.AccessLocalImagesInfoKey];
if (imagesInfoJson) {
var imagesInfoMap = JSON.parse(imagesInfoJson);
Object.entries(imagesInfoMap).forEach(([
collectionId, imagesInfo
]) => {
var wallpapersDataModel =
new cr.ui.ArrayDataModel(imagesInfo.array_);
this.imagesInfoMap_[collectionId] = wallpapersDataModel;
if (wallpapersDataModel.length > 0) {
var imageInfo = wallpapersDataModel.item(0);
// Prefer to build |collectionsInfo_| from |imagesInfoMap_|
// than to save it in local storage separately, in case of
// version mismatch.
if (!this.collectionsInfo_)
this.collectionsInfo_ = [];
this.collectionsInfo_.push({
collectionId: imageInfo.collectionId,
collectionName: imageInfo.collectionName
});
}
});
}
// There're four cases to consider:
// 1) First-time user / Network error: only show the "My images"
// category.
// 2) First-time user / No network error: show the "My images"
// category first (to avoid empty UI in case of slow connection),
// and call |postDownloadDomInit_| again to show the complete
// category list after the server responds.
// 3) Non-first-time user / Network error: show the complete
// cateogry list based on the image info in local storage.
// 4) Non-first-time user / No network error: the same with 3). If
// the image info fetched from server contain updates, save them to
// local storage to be used next time (avoid updating the already
// initialized category list).
this.postDownloadDomInit_();
chrome.wallpaperPrivate.getCollectionsInfo(collectionsInfo => {
if (chrome.runtime.lastError) {
// TODO(crbug.com/800945): Distinguish the error types and show
// custom error messages.
this.showError_(str('connectionFailed'));
$('wallpaper-grid').classList.add('image-picker-offline');
return;
}
var imagesInfoMap = {};
var getIndividualCollectionInfo = index => {
var collectionId = collectionsInfo[index]['collectionId'];
chrome.wallpaperPrivate.getImagesInfo(
collectionId, imagesInfo => {
var wallpapersDataModel = new cr.ui.ArrayDataModel([]);
if (!chrome.runtime.lastError) {
for (var i = 0; i < imagesInfo.length; ++i) {
var wallpaperInfo = {
// Use the next available unique id.
wallpaperId: this.imagesInfoCount_,
baseURL: imagesInfo[i]['imageUrl'],
highResolutionURL: imagesInfo[i]['imageUrl'] +
str('highResolutionSuffix'),
layout: Constants.WallpaperThumbnailDefaultLayout,
source: Constants.WallpaperSourceEnum.Online,
availableOffline: false,
displayText: imagesInfo[i]['displayText'],
authorWebsite: imagesInfo[i]['actionUrl'],
collectionName:
collectionsInfo[index]['collectionName'],
collectionId: collectionId,
ariaLabel: imagesInfo[i]['displayText'][0],
// The display order of the collections.
collectionIndex: index,
previewable: true
};
wallpapersDataModel.push(wallpaperInfo);
++this.imagesInfoCount_;
}
}
// Save info to the map. The data model is empty if
// there's a |chrome.runtime.lastError|.
imagesInfoMap[collectionId] = wallpapersDataModel;
++index;
if (index >= collectionsInfo.length) {
if (!this.collectionsInfo_) {
// Update the UI to show the complete category list,
// corresponding to case 2) above.
this.collectionsInfo_ = collectionsInfo;
this.imagesInfoMap_ = imagesInfoMap;
this.postDownloadDomInit_();
}
WallpaperUtil.saveToLocalStorage(
Constants.AccessLocalImagesInfoKey,
JSON.stringify(imagesInfoMap));
} else {
// Fetch the info for the next collection.
getIndividualCollectionInfo(index);
}
});
};
getIndividualCollectionInfo(0 /*index=*/);
});
});
};
/**
* Displays images that belong to the particular collection on the new wallpaper
* picker.
* @param {number} index The index of the collection in |collectionsInfo_| list.
* @private
*/
WallpaperManager.prototype.showCollection_ = function(index) {
this.wallpaperGrid_.dataModel = null;
var collectionId = this.collectionsInfo_[index]['collectionId'];
if (!(collectionId in this.imagesInfoMap_)) {
console.error('Attempt to display images with an unknown collection id.');
this.updateNoImagesVisibility_(true);
return;
}
if (this.imagesInfoMap_[collectionId].length == 0) {
this.updateNoImagesVisibility_(true);
return;
}
this.updateNoImagesVisibility_(false);
this.wallpaperGrid_.dataModel = this.imagesInfoMap_[collectionId];
};
/**
* Places the main dialog in the center of the screen, and the header bar at the
* top. Only used for the new wallpaper picker.
* @private
*/
WallpaperManager.prototype.placeWallpaperPicker_ = function() {
if (!this.useNewWallpaperPicker_)
return;
// Wallpaper preview must always be in full screen. Exit preview if the
// window is not in full screen for any reason (e.g. when device locks).
if (!chrome.app.window.current().isFullscreen() && this.isDuringPreview_())
$('cancel-preview-wallpaper').click();
var totalWidth = this.document_.body.offsetWidth;
var totalHeight = this.document_.body.offsetHeight;
centerElement($('message-container'), totalWidth, null);
centerElement($('top-header'), totalWidth, null);
centerElement($('preview-spinner'), totalWidth, totalHeight);
centerElement(
$('no-images-message'), $('no-images-message').parentNode.offsetWidth,
$('no-images-message').parentNode.offsetHeight);
var icon = $('no-images-message').querySelector('.icon');
var text = $('no-images-message').querySelector('.text');
// Adjust the relative position of the "no images" icon and text.
if (text.offsetWidth > icon.offsetWidth) {
icon.style.marginInlineStart =
(text.offsetWidth - icon.offsetWidth) / 2 + 'px';
} else {
text.style.marginInlineStart =
(icon.offsetWidth - text.offsetWidth) / 2 + 'px';
}
// Position the entire image grid.
var isHorizontal =
chrome.app.window.current().isFullscreen() && totalWidth > totalHeight;
var totalPadding = DIALOG_TOP_BAR_WIDTH + (isHorizontal ? 88 : 48);
var columnWidth = GRID_IMAGE_WIDTH_CSS + GRID_IMAGE_PADDING_CSS;
var columnCount = Math.floor((totalWidth - totalPadding) / columnWidth);
var imageGridTotalWidth = columnCount * columnWidth;
this.document_.querySelector('.dialog-main').style.marginInlineStart =
(totalWidth - imageGridTotalWidth - totalPadding) + 'px';
$('current-wallpaper-info-bar').style.width =
(imageGridTotalWidth - GRID_IMAGE_PADDING_CSS) + 'px';
var moreInfoColumnPadding;
if (isHorizontal) {
moreInfoColumnPadding = 96;
} else if (chrome.app.window.current().isFullscreen()) {
moreInfoColumnPadding = 24;
} else {
moreInfoColumnPadding = 24 +
(this.document_.body.offsetWidth -
chrome.app.window.current().innerBounds.minWidth) /
6;
}
// The current wallpaper more info column should occupy as much remaining
// space as possible.
$('current-wallpaper-more-info').style.width =
(imageGridTotalWidth - GRID_IMAGE_PADDING_CSS - moreInfoColumnPadding -
$('current-wallpaper-image').offsetWidth -
$('current-wallpaper-image').style.marginInlineEnd -
$('current-wallpaper-more-options').offsetWidth) +
'px';
};
/**
* Shows error message in a centered dialog.
* @private
* @param {string} errroMessage The string to show in the error dialog.
*/
WallpaperManager.prototype.showError_ = function(errorMessage) {
if (this.useNewWallpaperPicker_) {
$('message-container').textContent = errorMessage;
centerElement(
$('message-container'), this.document_.body.offsetWidth, null);
$('message-container').style.visibility = 'visible';
return;
}
document.querySelector('.error-message').textContent = errorMessage;
$('error-container').hidden = false;
};
/**
* Sets manifest loaded from server. Called after manifest is successfully
* loaded.
* @param {object} manifest The parsed manifest file.
*/
WallpaperManager.prototype.onLoadManifestSuccess_ = function(manifest) {
this.manifest_ = manifest;
WallpaperUtil.saveToLocalStorage(Constants.AccessLocalManifestKey, manifest);
this.postDownloadDomInit_();
};
// Sets manifest to previously saved object if any and shows connection error.
// Called after manifest failed to load.
WallpaperManager.prototype.onLoadManifestFailed_ = function() {
var accessManifestKey = Constants.AccessLocalManifestKey;
var self = this;
Constants.WallpaperLocalStorage.get(accessManifestKey, function(items) {
self.manifest_ = items[accessManifestKey] ? items[accessManifestKey] : null;
self.showError_(str('connectionFailed'));
self.postDownloadDomInit_();
$('wallpaper-grid').classList.add('image-picker-offline');
});
};
/**
* Toggle surprise me feature of wallpaper picker. It fires an storage
* onChanged event. Event handler for that event is in event_page.js.
*/
WallpaperManager.prototype.toggleSurpriseMe = function() {
var shouldEnable = !WallpaperUtil.getSurpriseMeCheckboxValue();
var onSuccess = () => {
if (chrome.runtime.lastError == null) {
if (shouldEnable) {
// Hides the wallpaper set by message if there is any.
$('wallpaper-set-by-message').textContent = '';
} else if (this.wallpaperGrid_.activeItem) {
// Unchecking the "Surprise me" checkbox falls back to the previous
// wallpaper before "Surprise me" was turned on.
this.setSelectedWallpaper_(this.wallpaperGrid_.activeItem);
this.onWallpaperChanged_(
this.wallpaperGrid_.activeItem, this.currentWallpaper_);
}
this.onSurpriseMeStateChanged_(shouldEnable);
} else {
// TODO(bshe): show error message to user.
console.error('Failed to save surprise me option to chrome storage.');
}
};
// To prevent the onChanged event being fired twice, we only save the value
// to sync storage if the sync theme is enabled, otherwise save it to local
// storage.
WallpaperUtil.enabledSyncThemesCallback(syncEnabled => {
if (syncEnabled)
WallpaperUtil.saveToSyncStorage(
Constants.AccessSyncSurpriseMeEnabledKey, shouldEnable, onSuccess);
else
WallpaperUtil.saveToLocalStorage(
Constants.AccessLocalSurpriseMeEnabledKey, shouldEnable, onSuccess);
});
};
/**
* One-time initialization of various DOM nodes. Fetching manifest or the
* collection info may take a long time due to slow connection. Dom nodes that
* do not depend on the download should be initialized here.
*/
WallpaperManager.prototype.preDownloadDomInit_ = function() {
this.document_.defaultView.addEventListener(
'resize', this.onResize_.bind(this));
this.document_.defaultView.addEventListener(
'keydown', this.onKeyDown_.bind(this));
if (this.useNewWallpaperPicker_) {
$('minimize-button').addEventListener('click', function() {
chrome.app.window.current().minimize();
});
$('close-button').addEventListener('click', function() {
window.close();
});
window.addEventListener(Constants.WallpaperChangedBy3rdParty, e => {
this.currentWallpaper_ = e.detail.wallpaperFileName;
this.decorateCurrentWallpaperInfoBar_();
// Clear the check mark (if any). Do not try to locate the new wallpaper
// in the picker to avoid changing the selected category abruptly.
this.wallpaperGrid_.selectedItem = null;
this.disableDailyRefresh_();
});
var imagePicker = this.document_.body.querySelector('.image-picker');
imagePicker.addEventListener('scroll', function() {
var scrollTimer;
return () => {
imagePicker.classList.add('show-scroll-bar');
window.clearTimeout(scrollTimer);
scrollTimer = window.setTimeout(() => {
imagePicker.classList.remove('show-scroll-bar');
}, 500);
};
}());
} else {
$('window-close-button').addEventListener('click', function() {
window.close();
});
$('learn-more').href = LearnMoreURL;
$('close-error').addEventListener('click', function() {
$('error-container').hidden = true;
});
$('close-wallpaper-selection').addEventListener('click', function() {
$('wallpaper-selection-container').hidden = true;
$('set-wallpaper-layout').disabled = true;
});
}
};
/**
* One-time initialization of various DOM nodes. Dom nodes that do depend on
* the download should be initialized here.
*/
WallpaperManager.prototype.postDownloadDomInit_ = function() {
i18nTemplate.process(this.document_, loadTimeData);
this.initCategoriesList_();
this.initThumbnailsGrid_();
this.presetCategory_();
$('file-selector')
.addEventListener('change', this.onFileSelectorChanged_.bind(this));
$('set-wallpaper-layout')
.addEventListener('change', this.onWallpaperLayoutChanged_.bind(this));
// Always prefer the value from local filesystem to avoid the time window
// of setting the third party app name and the third party wallpaper.
var getThirdPartyAppName = function(callback) {
Constants.WallpaperLocalStorage.get(
Constants.AccessLocalWallpaperInfoKey, function(items) {
var localInfo = items[Constants.AccessLocalWallpaperInfoKey];
if (localInfo && localInfo.hasOwnProperty('appName'))
callback(localInfo.appName);
else
callback('');
});
};
getThirdPartyAppName(function(appName) {
if (!!appName) {
$('wallpaper-set-by-message').textContent =
loadTimeData.getStringF('currentWallpaperSetByMessage', appName);
$('wallpaper-grid').classList.add('small');
} else {
$('wallpaper-grid').classList.remove('small');
}
});
this.initializeDailyRefreshStates_();
if (this.enableOnlineWallpaper_ && !this.useNewWallpaperPicker_) {
this.document_.body.setAttribute('surprise-me-disabled', '');
$('surprise-me').hidden = false;
$('surprise-me')
.addEventListener('click', this.toggleSurpriseMe.bind(this));
WallpaperUtil.enabledSyncThemesCallback(syncEnabled => {
// Surprise me has been moved from local to sync storage, prefer
// values from sync, but if unset check local and update synced pref
// if applicable.
if (syncEnabled) {
Constants.WallpaperSyncStorage.get(
Constants.AccessSyncSurpriseMeEnabledKey, items => {
if (items.hasOwnProperty(
Constants.AccessSyncSurpriseMeEnabledKey)) {
if (items[Constants.AccessSyncSurpriseMeEnabledKey])
this.onSurpriseMeStateChanged_(true /*enabled=*/);
} else {
Constants.WallpaperLocalStorage.get(
Constants.AccessLocalSurpriseMeEnabledKey, items => {
if (items.hasOwnProperty(
Constants.AccessLocalSurpriseMeEnabledKey)) {
WallpaperUtil.saveToSyncStorage(
Constants.AccessSyncSurpriseMeEnabledKey,
items[Constants.AccessLocalSurpriseMeEnabledKey]);
if (items[Constants.AccessLocalSurpriseMeEnabledKey])
this.onSurpriseMeStateChanged_(true /*enabled=*/);
}
});
}
});
} else {
Constants.WallpaperLocalStorage.get(
Constants.AccessLocalSurpriseMeEnabledKey, items => {
if (items.hasOwnProperty(
Constants.AccessLocalSurpriseMeEnabledKey)) {
if (items[Constants.AccessLocalSurpriseMeEnabledKey])
this.onSurpriseMeStateChanged_(true /*enabled=*/);
}
});
}
});
}
if (this.enableOnlineWallpaper_) {
window.addEventListener('offline', () => {
$('wallpaper-grid').classList.add('image-picker-offline');
if (this.useNewWallpaperPicker_) {
this.showError_(str('connectionFailed'));
$('wallpaper-grid').highlightOfflineWallpapers();
return;
}
chrome.wallpaperPrivate.getOfflineWallpaperList(lists => {
if (!this.downloadedListMap_)
this.downloadedListMap_ = {};
for (var i = 0; i < lists.length; i++) {
this.downloadedListMap_[lists[i]] = true;
}
var thumbnails = this.document_.querySelectorAll('.thumbnail');
for (var i = 0; i < thumbnails.length; i++) {
var thumbnail = thumbnails[i];
var url = this.wallpaperGrid_.dataModel.item(i).baseURL;
var fileName = getBaseName(url) + str('highResolutionSuffix');
if (this.downloadedListMap_ &&
this.downloadedListMap_.hasOwnProperty(encodeURI(fileName))) {
thumbnail.offline = true;
}
}
});
});
window.addEventListener('online', () => {
if (this.useNewWallpaperPicker_) {
// Fetch the collection info (if not yet) when device gets online.
if (!this.collectionsInfo_)
this.getCollectionsInfo_();
// Force refreshing the images.
this.wallpaperGrid_.dataModel = null;
this.onCategoriesChange_();
}
$('message-container').style.visibility = 'hidden';
this.downloadedListMap_ = null;
$('wallpaper-grid').classList.remove('image-picker-offline');
});
}
this.decorateCurrentWallpaperInfoBar_();
this.onResize_();
this.initContextMenuAndCommand_();
WallpaperUtil.testSendMessage('launched');
};
/**
* One-time initialization of context menu and command.
*/
WallpaperManager.prototype.initContextMenuAndCommand_ = function() {
this.wallpaperContextMenu_ = $('wallpaper-context-menu');
cr.ui.Menu.decorate(this.wallpaperContextMenu_);
cr.ui.contextMenuHandler.setContextMenu(
this.wallpaperGrid_, this.wallpaperContextMenu_);
var commands = this.dialogDom_.querySelectorAll('command');
for (var i = 0; i < commands.length; i++)
cr.ui.Command.decorate(commands[i]);
var doc = this.document_;
doc.addEventListener('command', this.onCommand_.bind(this));
doc.addEventListener('canExecute', this.onCommandCanExecute_.bind(this));
};
/**
* Handles a command being executed.
* @param {Event} event A command event.
*/
WallpaperManager.prototype.onCommand_ = function(event) {
if (event.command.id == 'delete') {
var wallpaperGrid = this.wallpaperGrid_;
var selectedIndex = wallpaperGrid.selectionModel.selectedIndex;
var item = wallpaperGrid.dataModel.item(selectedIndex);
if (!item || item.source != Constants.WallpaperSourceEnum.Custom)
return;
this.removeCustomWallpaper(item.baseURL);
wallpaperGrid.dataModel.splice(selectedIndex, 1);
// Calculate the number of remaining custom wallpapers. The add new button
// in data model needs to be excluded.
var customWallpaperCount = wallpaperGrid.dataModel.length - 1;
if (customWallpaperCount == 0) {
// Active custom wallpaper is also copied in chronos data dir. It needs
// to be deleted.
chrome.wallpaperPrivate.resetWallpaper();
this.onWallpaperChanged_(null, null);
} else {
selectedIndex = Math.min(selectedIndex, customWallpaperCount - 1);
wallpaperGrid.selectionModel.selectedIndex = selectedIndex;
}
event.cancelBubble = true;
}
};
/**
* Decides if a command can be executed on current target.
* @param {Event} event A command event.
*/
WallpaperManager.prototype.onCommandCanExecute_ = function(event) {
switch (event.command.id) {
case 'delete':
var wallpaperGrid = this.wallpaperGrid_;
var selectedIndex = wallpaperGrid.selectionModel.selectedIndex;
var item = wallpaperGrid.dataModel.item(selectedIndex);
if (selectedIndex != this.wallpaperGrid_.dataModel.length - 1 && item &&
item.source == Constants.WallpaperSourceEnum.Custom) {
event.canExecute = true;
break;
}
default:
event.canExecute = false;
}
};
/**
* Preset to the category which contains current wallpaper.
*/
WallpaperManager.prototype.presetCategory_ = function() {
// |currentWallpaper| is either a url containing |highResolutionSuffix| or a
// custom wallpaper file name.
this.currentWallpaper_ = str('currentWallpaper');
this.currentWallpaperLayout_ = str('currentWallpaperLayout');
if (this.useNewWallpaperPicker_) {
// The default category is the last one (the custom category).
var categoryIndex = this.categoriesList_.dataModel.length - 1;
Object.entries(this.imagesInfoMap_).forEach(([
collectionId, imagesInfo
]) => {
for (var i = 0; i < imagesInfo.length; ++i) {
if (this.currentWallpaper_.includes(imagesInfo.item(i).baseURL)) {
for (var index = 0; index < this.collectionsInfo_.length; ++index) {
// Find the index of the category which the current wallpaper
// belongs to based on the collection id.
if (this.collectionsInfo_[index]['collectionId'] == collectionId)
categoryIndex = index;
}
}
}
});
this.categoriesList_.selectionModel.selectedIndex = categoryIndex;
return;
}
if (!this.enableOnlineWallpaper_ ||
(this.currentWallpaper_ &&
this.currentWallpaper_.indexOf(str('highResolutionSuffix')) == -1)) {
// Custom is the last one in the categories list.
this.categoriesList_.selectionModel.selectedIndex =
this.categoriesList_.dataModel.length - 1;
return;
}
var self = this;
var presetCategoryInner = function() {
// Selects the first category in the categories list of current
// wallpaper as the default selected category when showing wallpaper
// picker UI.
var presetCategory = AllCategoryIndex;
if (self.currentWallpaper_) {
for (var key in self.manifest_.wallpaper_list) {
var url = self.manifest_.wallpaper_list[key].base_url +
str('highResolutionSuffix');
if (url.indexOf(self.currentWallpaper_) != -1 &&
self.manifest_.wallpaper_list[key].categories.length > 0) {
presetCategory = self.manifest_.wallpaper_list[key].categories[0] +
OnlineCategoriesOffset;
break;
}
}
}
self.categoriesList_.selectionModel.selectedIndex = presetCategory;
};
if (navigator.onLine) {
presetCategoryInner();
} else {
// If device is offline, gets the available offline wallpaper list first.
// Wallpapers which are not in the list will display a grayscaled
// thumbnail.
chrome.wallpaperPrivate.getOfflineWallpaperList(function(lists) {
if (!self.downloadedListMap_)
self.downloadedListMap_ = {};
for (var i = 0; i < lists.length; i++)
self.downloadedListMap_[lists[i]] = true;
presetCategoryInner();
});
}
};
/**
* Decorate the info bar for current wallpaper which shows the image thumbnail,
* title and description.
* @private
*/
WallpaperManager.prototype.decorateCurrentWallpaperInfoBar_ = function() {
// The info bar only exists in new wallpaper picker.
if (!this.useNewWallpaperPicker_)
return;
var decorateCurrentWallpaperInfoBarImpl =
currentWallpaperInfo => {
// Initialize the "more options" buttons.
var isOnlineWallpaper = !!currentWallpaperInfo;
var isDefaultWallpaper = !this.currentWallpaper_;
var visibleItemList = [];
$('refresh').hidden = !isOnlineWallpaper || !this.dailyRefreshInfo_ ||
!this.dailyRefreshInfo_.enabled;
if (!$('refresh').hidden) {
this.addEventToButton_($('refresh'), () => {
this.pendingDailyRefreshInfo_ = this.dailyRefreshInfo_;
this.setDailyRefreshWallpaper_();
});
visibleItemList.push($('refresh'));
}
$('explore').hidden =
!isOnlineWallpaper || !currentWallpaperInfo.authorWebsite;
if (!$('explore').hidden) {
this.addEventToButton_($('explore'), () => {
window.open(currentWallpaperInfo.authorWebsite);
});
visibleItemList.push($('explore'));
}
$('center').hidden = isOnlineWallpaper || isDefaultWallpaper;
if (!$('center').hidden) {
this.addEventToButton_(
$('center'), this.setCustomWallpaperLayout_.bind(this, 'CENTER'));
visibleItemList.push($('center'));
}
$('center-cropped').hidden = isOnlineWallpaper || isDefaultWallpaper;
if (!$('center-cropped').hidden) {
this.addEventToButton_(
$('center-cropped'),
this.setCustomWallpaperLayout_.bind(this, 'CENTER_CROPPED'));
visibleItemList.push($('center-cropped'));
}
if (visibleItemList.length == 1) {
visibleItemList[0].style.marginTop =
($('current-wallpaper-info-bar').offsetHeight -
visibleItemList[0].offsetHeight) /
2 +
'px';
} else if (visibleItemList.length == 2) {
// There are at most two visible elements.
var topMargin = ($('current-wallpaper-info-bar').offsetHeight -
visibleItemList[0].offsetHeight -
visibleItemList[1].offsetHeight) *
0.4;
visibleItemList[0].style.marginTop = topMargin + 'px';
visibleItemList[1].style.marginTop = topMargin / 2 + 'px';
}
// Add necessary padding and make sure all the texts are centered. Clear
// the existing padding first.
for (var item of visibleItemList) {
item.style.paddingLeft = item.style.paddingRight = '0px';
}
var totalWidth = $('current-wallpaper-more-options').offsetWidth;
for (var item of visibleItemList) {
var padding = 15 +
(totalWidth -
(item.querySelector('.icon').offsetWidth +
item.querySelector('.text').offsetWidth)) /
2;
item.style.paddingLeft = item.style.paddingRight = padding + 'px';
}
// Clear the existing contents (needed if the wallpaper changes while
// the picker is open).
$('current-wallpaper-description').innerHTML = '';
if (isOnlineWallpaper) {
// Set the image title and description.
$('current-wallpaper-title').textContent =
currentWallpaperInfo.displayText[0];
$('current-wallpaper-description')
.classList.toggle(
'small-font',
currentWallpaperInfo.displayText.length > 2 &&
!chrome.app.window.current().isFullscreen() &&
!chrome.app.window.current().isMaximized());
for (var i = 1; i < currentWallpaperInfo.displayText.length; ++i) {
$('current-wallpaper-description')
.appendChild(document.createTextNode(
currentWallpaperInfo.displayText[i]));
$('current-wallpaper-description')
.appendChild(document.createElement('br'));
}
}
var imageElement = $('current-wallpaper-image');
var isFromOldPicker =
currentWallpaperInfo && currentWallpaperInfo.isFromOldPicker;
if (isOnlineWallpaper && !isFromOldPicker) {
WallpaperUtil.displayThumbnail(
imageElement, currentWallpaperInfo.baseURL,
Constants.WallpaperSourceEnum.Online);
} else {
// Request the thumbnail of the current wallpaper as fallback, since
// the picker doesn't have access to thumbnails of other types of
// wallpapers.
var currentWallpaper = this.currentWallpaper_;
chrome.wallpaperPrivate.getCurrentWallpaperThumbnail(
imageElement.offsetHeight, imageElement.offsetWidth,
thumbnail => {
// If the current wallpaper already changed when this function
// returns, do nothing.
if (currentWallpaper != this.currentWallpaper_)
return;
WallpaperUtil.displayImage(
imageElement, thumbnail, null /*opt_callback=*/);
// Show a placeholder as the image title.
$('current-wallpaper-title').textContent =
str('customCategoryLabel');
});
}
this.toggleLayoutButtonStates_(this.currentWallpaperLayout_);
this.placeWallpaperPicker_();
$('current-wallpaper-info-bar').classList.add('show-info-bar');
};
// Try finding the current wallpaper in the online wallpaper collection.
var currentWallpaperInfo;
Object.values(this.imagesInfoMap_).forEach(imagesInfo => {
for (var i = 0; i < imagesInfo.length; ++i) {
if (this.currentWallpaper_.includes(imagesInfo.item(i).baseURL))
currentWallpaperInfo = imagesInfo.item(i);
}
});
if (currentWallpaperInfo) {
decorateCurrentWallpaperInfoBarImpl(currentWallpaperInfo);
} else {
// Migration: it's possible that the wallpaper was selected from the online
// collection of the old picker. Try finding its info from local storage.
var accessManifestKey = Constants.AccessLocalManifestKey;
Constants.WallpaperLocalStorage.get(accessManifestKey, items => {
var manifest = items[accessManifestKey];
if (manifest) {
for (var i = 0; i < manifest.wallpaper_list.length; i++) {
if (this.currentWallpaper_.includes(
manifest.wallpaper_list[i].base_url)) {
currentWallpaperInfo = {
displayText: ['', manifest.wallpaper_list[i].author],
authorWebsite: manifest.wallpaper_list[i].author_website,
isFromOldPicker: true
};
}
}
}
decorateCurrentWallpaperInfoBarImpl(currentWallpaperInfo);
});
}
};
/**
* Constructs the thumbnails grid.
*/
WallpaperManager.prototype.initThumbnailsGrid_ = function() {
this.wallpaperGrid_ = $('wallpaper-grid');
wallpapers.WallpaperThumbnailsGrid.decorate(this.wallpaperGrid_);
this.wallpaperGrid_.addEventListener('change', this.onChange_.bind(this));
if (!this.useNewWallpaperPicker_)
this.wallpaperGrid_.addEventListener('dblclick', this.onClose_.bind(this));
};
/**
* Handles change event dispatched by wallpaper grid.
*/
WallpaperManager.prototype.onChange_ = function() {
// splice may dispatch a change event because the position of selected
// element changing. But the actual selected element may not change after
// splice. Check if the new selected element equals to the previous selected
// element before continuing. Otherwise, wallpaper may reset to previous one
// as described in http://crbug.com/229036.
if (this.selectedItem_ == this.wallpaperGrid_.selectedItem)
return;
this.selectedItem_ = this.wallpaperGrid_.selectedItem;
this.onSelectedItemChanged_();
};
/**
* Closes window if no pending wallpaper request.
*/
WallpaperManager.prototype.onClose_ = function() {
if (this.wallpaperRequest_) {
this.wallpaperRequest_.addEventListener('loadend', function() {
// Close window on wallpaper loading finished.
window.close();
});
} else {
window.close();
}
};
/**
* Moves the check mark to |activeItem| and hides the wallpaper set by third
* party message if any. And saves the wallpaper's information to local & sync
* storage. Called when wallpaper changed successfully.
* @param {?Object} activeItem The active item in WallpaperThumbnailsGrid's
* data model.
* @param {?string} currentWallpaperURL The URL or filename of current
* wallpaper.
*/
WallpaperManager.prototype.onWallpaperChanged_ = function(
activeItem, currentWallpaperURL) {
$('message-container').style.visibility = 'hidden';
this.wallpaperGrid_.activeItem = activeItem;
this.currentWallpaper_ = currentWallpaperURL;
this.decorateCurrentWallpaperInfoBar_();
// Hides the wallpaper set by message.
$('wallpaper-set-by-message').textContent = '';
$('wallpaper-grid').classList.remove('small');
// Disables daily refresh if user selects a non-daily wallpaper.
if (activeItem && activeItem.source !== Constants.WallpaperSourceEnum.Daily)
this.disableDailyRefresh_();
if (activeItem) {
WallpaperUtil.saveWallpaperInfo(
currentWallpaperURL, activeItem.layout, activeItem.source, '');
} else {
WallpaperUtil.saveWallpaperInfo(
'', '', Constants.WallpaperSourceEnum.Default, '');
}
};
/**
* Sets wallpaper to the corresponding wallpaper of selected thumbnail.
* @param {Object} selectedItem The selected item in WallpaperThumbnailsGrid's
* data model.
* @private
*/
WallpaperManager.prototype.setSelectedWallpaper_ = function(selectedItem) {
switch (selectedItem.source) {
case Constants.WallpaperSourceEnum.Custom:
this.setSelectedCustomWallpaper_(selectedItem);
break;
case Constants.WallpaperSourceEnum.OEM:
// Resets back to default wallpaper.
chrome.wallpaperPrivate.resetWallpaper();
this.onWallpaperChanged_(selectedItem, selectedItem.baseURL);
break;
case Constants.WallpaperSourceEnum.Online:
var previewMode = this.shouldPreviewWallpaper_();
var successCallback = () => {
this.updateSpinnerVisibility_(false);
if (previewMode) {
this.onPreviewModeStarted_(
selectedItem,
this.onWallpaperChanged_.bind(
this, selectedItem, selectedItem.highResolutionURL),
/*optCancelCallback=*/null, /*optOnRefreshClicked=*/null);
} else {
this.onWallpaperChanged_(
selectedItem, selectedItem.highResolutionURL);
}
};
this.setSelectedOnlineWallpaper_(selectedItem, successCallback, () => {
this.updateSpinnerVisibility_(false);
}, previewMode);
break;
case Constants.WallpaperSourceEnum.Daily:
case Constants.WallpaperSourceEnum.ThirdParty:
default:
console.error('Unsupported wallpaper source.');
}
};
/**
* Implementation of |setSelectedWallpaper_| for custom wallpapers.
* @param {Object} selectedItem The selected item in WallpaperThumbnailsGrid's
* data model.
* @param {function} successCallback The success callback.
* @private
*/
WallpaperManager.prototype.setSelectedCustomWallpaper_ = function(
selectedItem, successCallback) {
if (selectedItem.source != Constants.WallpaperSourceEnum.Custom) {
console.error(
'|setSelectedCustomWallpaper_| is called but the wallpaper source ' +
'is not custom.');
return;
}
var successCallback = (imageData, optThumbnailData) => {
this.onWallpaperChanged_(selectedItem, selectedItem.baseURL);
this.saveCustomWallpaperToSyncFS_(
selectedItem.baseURL, this.getSelectedLayout_(), imageData,
optThumbnailData, this.onFileSystemError_.bind(this));
};
if (this.useNewWallpaperPicker_)
this.setCustomWallpaperSelectedOnNewPicker_(selectedItem, successCallback);
else
this.setCustomWallpaperSelectedOnOldPicker_(selectedItem, successCallback);
};
/**
* Implementation of |setSelectedCustomWallpaper_| for the new wallpaper picker.
* @param {Object} selectedItem The selected item in WallpaperThumbnailsGrid's
* data model.
* @param {function} successCallback The success callback.
* @private
*/
WallpaperManager.prototype.setCustomWallpaperSelectedOnNewPicker_ = function(
selectedItem, successCallback) {
// Read the image data from |filePath| and set the wallpaper with the data.
chrome.wallpaperPrivate.getLocalImageData(
selectedItem.filePath, imageData => {
if (chrome.runtime.lastError || !imageData) {
this.showError_(str('downloadFailed'));
return;
}
var previewMode = this.shouldPreviewWallpaper_();
if (!previewMode) {
chrome.wallpaperPrivate.setCustomWallpaper(
imageData, selectedItem.layout, false /*generateThumbnail=*/,
selectedItem.baseURL, false /*previewMode=*/,
optThumbnailData => {
if (chrome.runtime.lastError) {
this.showError_(str('downloadFailed'));
return;
}
successCallback(imageData, optThumbnailData);
});
return;
}
var decorateLayoutButton = layout => {
if (layout != 'CENTER' && layout != 'CENTER_CROPPED') {
console.error('Wallpaper layout ' + layout + ' is not supported.');
return;
}
var layoutButton = this.document_.querySelector(
layout == 'CENTER' ? '.center-button' : '.center-cropped-button');
this.addEventToButton_(layoutButton, () => {
chrome.wallpaperPrivate.setCustomWallpaper(
imageData, layout, false /*generateThumbnail=*/,
selectedItem.baseURL, true /*previewMode=*/,
optThumbnailData => {
if (chrome.runtime.lastError) {
this.showError_(str('downloadFailed'));
return;
}
this.currentlySelectedLayout_ = layout;
this.document_.querySelector('.center-button')
.classList.toggle('disabled', layout == 'CENTER');
this.document_.querySelector('.center-cropped-button')
.classList.toggle('disabled', layout == 'CENTER_CROPPED');
this.onPreviewModeStarted_(
selectedItem,
successCallback.bind(null, imageData, optThumbnailData),
/*optCancelCallback=*/null,
/*optOnRefreshClicked=*/null);
});
});
};
decorateLayoutButton('CENTER');
decorateLayoutButton('CENTER_CROPPED');
// The default layout is CENTER_CROPPED.
this.document_.querySelector('.center-cropped-button').click();
});
};
/**
* TODO(crbug.com/787134): Delete the method after the old picker is deprecated.
*
* Implementation of |setSelectedCustomWallpaper_| for the old wallpaper picker.
* @param {Object} selectedItem The selected item in WallpaperThumbnailsGrid's
* data model.
* @param {function} successCallback The success callback.
* @private
*/
WallpaperManager.prototype.setCustomWallpaperSelectedOnOldPicker_ = function(
selectedItem, successCallback) {
var errorHandler = this.onFileSystemError_.bind(this);
var success = dirEntry => {
dirEntry.getFile(selectedItem.baseURL, {create: false}, fileEntry => {
fileEntry.file(file => {
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.addEventListener('error', errorHandler);
reader.addEventListener('load', e => {
// The thumbnail already exists at this point. There's no need to
// regenerate it.
this.setCustomWallpaperSelectedOnOldPickerImpl_(
e.target.result, selectedItem.layout,
false /*generateThumbnail=*/, selectedItem.baseURL,
successCallback.bind(null, e.target.result), errorHandler);
});
}, errorHandler);
}, errorHandler);
};
this.wallpaperDirs_.getDirectory(
Constants.WallpaperDirNameEnum.ORIGINAL, success, errorHandler);
};
/**
* Implementation of |setSelectedWallpaper_| for online wallpapers.
* @param {Object} selectedItem The selected item in WallpaperThumbnailsGrid's
* data model.
* @param {function} successCallback The callback after the wallpaper is set
* successfully.
* @param {function} failureCallback The callback after setting the wallpaper
* fails.
* @param {boolean} previewMode True if the wallpaper should be previewed.
* @private
*/
WallpaperManager.prototype.setSelectedOnlineWallpaper_ = function(
selectedItem, successCallback, failureCallback, previewMode) {
// Cancel any ongoing wallpaper request, otherwise the wallpaper being set in
// the end may not be the one that the user selected the last, because the
// time needed to set each wallpaper may vary (e.g. some wallpapers already
// exist in the local file system but others need to be fetched from server).
if (this.wallpaperRequest_) {
this.wallpaperRequest_.abort();
this.wallpaperRequest_ = null;
}
var selectedGridItem = this.wallpaperGrid_.getListItem(selectedItem);
chrome.wallpaperPrivate.setWallpaperIfExists(
selectedItem.highResolutionURL, selectedItem.layout, previewMode,
exists => {
if (exists) {
successCallback();
return;
}
// Falls back to request wallpaper from server.
this.wallpaperRequest_ = new XMLHttpRequest();
this.progressManager_.reset(this.wallpaperRequest_, selectedGridItem);
var onSuccess =
xhr => {
var image = xhr.response;
chrome.wallpaperPrivate.setWallpaper(
image, selectedItem.layout, selectedItem.highResolutionURL,
previewMode, () => {
this.progressManager_.hideProgressBar(selectedGridItem);
if (chrome.runtime.lastError != undefined &&
chrome.runtime.lastError.message !=
str('canceledWallpaper')) {
// The user doesn't need to distinguish this error (most
// likely due to a decode failure) from a download
// failure.
this.showError_(str('downloadFailed'));
failureCallback();
return;
}
successCallback();
});
this.wallpaperRequest_ = null;
};
var onFailure = status => {
this.progressManager_.hideProgressBar(selectedGridItem);
this.showError_(str('downloadFailed'));
this.wallpaperRequest_ = null;
failureCallback();
};
WallpaperUtil.fetchURL(
selectedItem.highResolutionURL, 'arraybuffer', onSuccess, onFailure,
this.wallpaperRequest_);
});
};
/**
* Handles the UI changes when the preview mode is started.
* @param {Object} wallpaperInfo The info related to the wallpaper image.
* @param {function} optConfirmCallback The callback after preview
* wallpaper is set.
* @param {function} optCancelCallback The callback after preview
* is canceled.
* @param {function} optOnRefreshClicked The event listener for the refresh
* button. Must be non-null when the wallpaper type is daily.
* @private
*/
WallpaperManager.prototype.onPreviewModeStarted_ = function(
wallpaperInfo, optConfirmCallback, optCancelCallback, optOnRefreshClicked) {
if (this.isDuringPreview_())
return;
this.document_.body.classList.add('preview-animation');
chrome.wallpaperPrivate.minimizeInactiveWindows();
window.setTimeout(() => {
chrome.app.window.current().fullscreen();
this.document_.body.classList.add('preview-mode');
this.document_.body.classList.toggle(
'custom-wallpaper',
wallpaperInfo.source == Constants.WallpaperSourceEnum.Custom);
this.document_.body.classList.toggle(
'daily-wallpaper',
wallpaperInfo.source == Constants.WallpaperSourceEnum.Daily);
}, 800);
var onConfirmClicked = () => {
chrome.wallpaperPrivate.confirmPreviewWallpaper(() => {
if (optConfirmCallback)
optConfirmCallback();
this.showSuccessMessageAndQuit_();
});
};
this.addEventToButton_($('confirm-preview-wallpaper'), onConfirmClicked);
var onRefreshClicked = () => {
if (optOnRefreshClicked)
optOnRefreshClicked();
};
this.addEventToButton_($('refresh-wallpaper'), onRefreshClicked);
// Enable swiping to show the previous/next preview wallpaper.
var onTouchStarted = e => {
// Do not enable swiping if a non-null |optOnRefreshClicked| is provided.
if (optOnRefreshClicked)
return;
this.xStart_ = e.touches[0].clientX;
this.yStart_ = e.touches[0].clientY;
};
$('preview-canvas').addEventListener('touchstart', onTouchStarted);
var onTouchMoved = e => {
if (!this.xStart_ || !this.yStart_)
return;
var xDiff = e.touches[0].clientX - this.xStart_;
var yDiff = e.touches[0].clientY - this.yStart_;
// Reset these to prevent duplicate handling of a single swipe event.
this.xStart_ = null;
this.yStart_ = null;
// Ignore vertical swipes.
if (Math.abs(xDiff) <= Math.abs(yDiff))
return;
// When swiping to the left(right), the next(previous) wallpaper should
// be previewed.
// TODO(crbug.com/837355): Consider flipping this for RTL languages.
var onScreenSwiped = left => {
var dataModel = this.wallpaperGrid_.dataModel;
// Get the index of the wallpaper that's being previewed. This is only
// needed for the first swipe event.
if (this.currentPreviewIndex_ == null) {
for (var i = 0; i < dataModel.length; ++i) {
if (dataModel.item(i) == wallpaperInfo) {
this.currentPreviewIndex_ = i;
break;
}
}
}
// The wallpaper being previewed may not come from the data model
// (e.g. when it's from the current wallpaper info bar). In this case
// do nothing.
if (this.currentPreviewIndex_ == null)
return;
// Find the previous/next wallpaper to be previewed based on the swipe
// direction.
var getNextPreviewIndex = (index, left) => {
var getNextPreviewIndexImpl = (index, left) => {
index += left ? 1 : -1;
index = index % dataModel.length;
if (index < 0)
index += dataModel.length;
return index;
};
index = getNextPreviewIndexImpl(index, left);
var firstFoundIndex = index;
// The item must be previewable.
while (!dataModel.item(index).previewable) {
index = getNextPreviewIndexImpl(index, left);
// Return null if none of the items within the data model is
// previewable.
if (firstFoundIndex === index)
return null;
}
return index;
};
// Start previewing the next wallpaper.
var nextPreviewIndex =
getNextPreviewIndex(this.currentPreviewIndex_, left);
if (nextPreviewIndex === null) {
console.error(
'Cannot find any previewable wallpaper. This should never happen.');
return;
}
var nextPreviewImage = dataModel.item(nextPreviewIndex);
if (nextPreviewImage.source == Constants.WallpaperSourceEnum.Online)
this.updateSpinnerVisibility_(true);
$('message-container').style.visibility = 'hidden';
this.setWallpaperAttribution(nextPreviewImage);
this.setSelectedWallpaper_(nextPreviewImage);
this.currentPreviewIndex_ = nextPreviewIndex;
};
// |xDiff < 0| indicates a left swipe.
onScreenSwiped(xDiff < 0);
};
$('preview-canvas').addEventListener('touchmove', onTouchMoved);
var onCancelClicked = () => {
$('preview-canvas').removeEventListener('touchstart', onTouchStarted);
$('preview-canvas').removeEventListener('touchmove', onTouchMoved);
chrome.wallpaperPrivate.cancelPreviewWallpaper(() => {
if (optCancelCallback)
optCancelCallback();
// Deselect the image.
this.wallpaperGrid_.selectedItem = null;
this.currentlySelectedLayout_ = null;
this.currentPreviewIndex_ = null;
this.document_.body.classList.remove('preview-mode');
this.document_.body.classList.remove('preview-animation');
this.updateSpinnerVisibility_(false);
// Exit full screen, but the window should still be maximized.
chrome.app.window.current().maximize();
// TODO(crbug.com/841968): Force refreshing the images. This is a
// workaround until the issue is fixed.
this.wallpaperGrid_.dataModel = null;
this.onCategoriesChange_();
});
};
this.addEventToButton_($('cancel-preview-wallpaper'), onCancelClicked);
$('message-container').style.visibility = 'hidden';
};
/*
* Removes the oldest custom wallpaper. If the oldest one is set as current
* wallpaper, removes the second oldest one to free some space. This should
* only be called when exceeding wallpaper quota.
*/
WallpaperManager.prototype.removeOldestWallpaper_ = function() {
// Custom wallpapers should already sorted when put to the data model. The
// last element is the add new button, need to exclude it as well.
var oldestIndex = this.wallpaperGrid_.dataModel.length - 2;
var item = this.wallpaperGrid_.dataModel.item(oldestIndex);
if (!item || item.source != Constants.WallpaperSourceEnum.Custom)
return;
if (item.baseURL == this.currentWallpaper_)
item = this.wallpaperGrid_.dataModel.item(--oldestIndex);
if (item) {
this.removeCustomWallpaper(item.baseURL);
this.wallpaperGrid_.dataModel.splice(oldestIndex, 1);
}
};
/*
* Shows a success message and closes the window.
* @private
*/
WallpaperManager.prototype.showSuccessMessageAndQuit_ = function() {
this.document_.body.classList.add('wallpaper-set-successfully');
$('message-container').textContent = str('setSuccessfullyMessage');
// Success message must be shown in full screen mode.
chrome.app.window.current().fullscreen();
centerElement($('message-container'), this.document_.body.offsetWidth, null);
$('message-container').style.visibility = 'visible';
// Close the window after showing the success message.
window.setTimeout(() => {
window.close();
}, 800);
};
/*
* Shows an error message to user and log the failed reason in console.
*/
WallpaperManager.prototype.onFileSystemError_ = function(e) {
var msg = '';
switch (e.code) {
case FileError.QUOTA_EXCEEDED_ERR:
msg = 'QUOTA_EXCEEDED_ERR';
// Instead of simply remove oldest wallpaper, we should consider a
// better way to handle this situation. See crbug.com/180890.
this.removeOldestWallpaper_();
break;
case FileError.NOT_FOUND_ERR:
msg = 'NOT_FOUND_ERR';
break;
case FileError.SECURITY_ERR:
msg = 'SECURITY_ERR';
break;
case FileError.INVALID_MODIFICATION_ERR:
msg = 'INVALID_MODIFICATION_ERR';
break;
case FileError.INVALID_STATE_ERR:
msg = 'INVALID_STATE_ERR';
break;
default:
msg = 'Unknown Error';
break;
}
console.error('Error: ' + msg);
this.showError_(str('accessFileFailure'));
};
/**
* Handles changing of selectedItem in wallpaper manager.
*/
WallpaperManager.prototype.onSelectedItemChanged_ = function() {
if (!this.selectedItem_ || this.selectedItem_.source == 'ADDNEW')
return;
this.setWallpaperAttribution(this.selectedItem_);
if (this.selectedItem_.baseURL &&
(this.useNewWallpaperPicker_ ||
!this.wallpaperGrid_.inProgramSelection)) {
if (this.selectedItem_.source == Constants.WallpaperSourceEnum.Custom) {
var items = {};
var key = this.selectedItem_.baseURL;
var self = this;
Constants.WallpaperLocalStorage.get(key, function(items) {
self.selectedItem_.layout =
items[key] ? items[key] : Constants.WallpaperThumbnailDefaultLayout;
self.setSelectedWallpaper_(self.selectedItem_);
});
} else {
this.setSelectedWallpaper_(this.selectedItem_);
}
}
};
/**
* Set attributions of wallpaper with given URL. If URL is not valid, clear
* the attributions.
* @param {Object} selectedItem the selected item in WallpaperThumbnailsGrid's
* data model.
*/
WallpaperManager.prototype.setWallpaperAttribution = function(selectedItem) {
if (this.useNewWallpaperPicker_) {
$('image-title').textContent = '';
$('wallpaper-description').textContent = '';
if (selectedItem) {
// The first element in |displayText| is used as title.
if (selectedItem.displayText)
$('image-title').textContent = selectedItem.displayText[0];
if (selectedItem.displayText && selectedItem.displayText.length > 1) {
for (var i = 1; i < selectedItem.displayText.length; ++i) {
$('wallpaper-description').textContent +=
selectedItem.displayText[i] + ' ';
}
} else if (selectedItem.collectionName) {
// Display the collection name as backup.
$('wallpaper-description').textContent = selectedItem.collectionName;
}
}
return;
}
// Only online wallpapers have author and website attributes. All other type
// of wallpapers should not show attributions.
if (!selectedItem ||
selectedItem.source != Constants.WallpaperSourceEnum.Online) {
$('wallpaper-attribute').hidden = true;
$('attribute-image').hidden = true;
$('author-name').textContent = '';
$('author-website').textContent = $('author-website').href = '';
$('attribute-image').src = '';
return;
}
$('author-name').textContent = selectedItem.author;
$('author-website').textContent = $('author-website').href =
selectedItem.authorWebsite;
var img = $('attribute-image');
WallpaperUtil.displayThumbnail(
img, selectedItem.baseURL, selectedItem.source);
img.hidden = false;
$('wallpaper-attribute').hidden = false;
};
/**
* Resize thumbnails grid and categories list to fit the new window size.
*/
WallpaperManager.prototype.onResize_ = function() {
this.placeWallpaperPicker_();
this.wallpaperGrid_.redraw();
this.categoriesList_.redraw();
};
/**
* Close the last opened overlay or app window on pressing the Escape key.
* @param {Event} event A keydown event.
*/
WallpaperManager.prototype.onKeyDown_ = function(event) {
if (event.keyCode == 27) {
// The last opened overlay coincides with the first match of querySelector
// because the Error Container is declared in the DOM before the Wallpaper
// Selection Container.
// TODO(bshe): Make the overlay selection not dependent on the DOM.
var closeButtonSelector = '.overlay-container:not([hidden]) .close';
var closeButton = this.document_.querySelector(closeButtonSelector);
if (closeButton) {
closeButton.click();
event.preventDefault();
} else {
this.onClose_();
}
}
};
/**
* Constructs the categories list.
*/
WallpaperManager.prototype.initCategoriesList_ = function() {
this.categoriesList_ = $('categories-list');
wallpapers.WallpaperCategoriesList.decorate(this.categoriesList_);
this.categoriesList_.selectionModel.addEventListener(
'change', this.onCategoriesChange_.bind(this));
if (this.useNewWallpaperPicker_) {
if (this.collectionsInfo_) {
for (var colletionInfo of this.collectionsInfo_)
this.categoriesList_.dataModel.push(colletionInfo['collectionName']);
}
} else if (this.enableOnlineWallpaper_ && this.manifest_) {
// Adds all category as first category.
this.categoriesList_.dataModel.push(str('allCategoryLabel'));
for (var key in this.manifest_.categories) {
this.categoriesList_.dataModel.push(this.manifest_.categories[key]);
}
}
// Adds custom category as last category.
this.categoriesList_.dataModel.push(str('customCategoryLabel'));
};
/**
* Handles the custom wallpaper which user selected from file manager. Called
* when users select a file.
*/
WallpaperManager.prototype.onFileSelectorChanged_ = function() {
var files = $('file-selector').files;
if (files.length != 1)
console.error('More than one files are selected or no file selected');
if (!files[0].type.match('image/jpeg') && !files[0].type.match('image/png')) {
this.showError_(str('invalidWallpaper'));
return;
}
var layout = this.getSelectedLayout_();
var self = this;
var errorHandler = this.onFileSystemError_.bind(this);
var setSelectedFile = function(file, layout, fileName) {
var success = function(dirEntry) {
dirEntry.getFile(fileName, {create: true}, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
fileWriter.addEventListener('writeend', function(e) {
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.addEventListener('error', errorHandler);
reader.addEventListener('load', function(e) {
self.setCustomWallpaperSelectedOnOldPickerImpl_(
e.target.result, layout, true /*generateThumbnail=*/,
fileName,
function(thumbnail) {
self.saveCustomWallpaperToSyncFS_(
fileName, layout, e.target.result, thumbnail,
errorHandler);
},
function() {
self.removeCustomWallpaper(fileName);
errorHandler();
});
});
});
fileWriter.addEventListener('error', errorHandler);
fileWriter.write(file);
}, errorHandler);
}, errorHandler);
};
self.wallpaperDirs_.getDirectory(
Constants.WallpaperDirNameEnum.ORIGINAL, success, errorHandler);
};
setSelectedFile(files[0], layout, new Date().getTime().toString());
};
/**
* Removes wallpaper and thumbnail with fileName from FileSystem.
* @param {string} fileName The file name of wallpaper and thumbnail to be
* removed.
*/
WallpaperManager.prototype.removeCustomWallpaper = function(fileName) {
var errorHandler = this.onFileSystemError_.bind(this);
var self = this;
var removeFile = function(fileName) {
var success = function(dirEntry) {
dirEntry.getFile(fileName, {create: false}, function(fileEntry) {
fileEntry.remove(function() {
WallpaperUtil.deleteWallpaperFromSyncFS(fileName);
}, errorHandler);
}, errorHandler);
};
// Removes copy of original.
self.wallpaperDirs_.getDirectory(
Constants.WallpaperDirNameEnum.ORIGINAL, success, errorHandler);
// Removes generated thumbnail.
self.wallpaperDirs_.getDirectory(
Constants.WallpaperDirNameEnum.THUMBNAIL, success, errorHandler);
};
removeFile(fileName);
};
/**
* TODO(crbug.com/787134): Delete the method after the old picker is deprecated.
*
* Implementation of |setCustomWallpaperSelectedOnOldPicker_|.
* @param {ArrayBuffer} wallpaper The binary representation of wallpaper.
* @param {string} layout The user selected wallpaper layout.
* @param {boolean} generateThumbnail True if need to generate thumbnail.
* @param {string} fileName The unique file name of wallpaper.
* @param {function(thumbnail):void} success Success callback. If
* generateThumbnail is true, the callback parameter should have the
* generated thumbnail.
* @param {function(e):void} failure Failure callback. Called when there is an
* error from FileSystem.
* @private
*/
WallpaperManager.prototype.setCustomWallpaperSelectedOnOldPickerImpl_ =
function(wallpaper, layout, generateThumbnail, fileName, success, failure) {
var onFinished = opt_thumbnail => {
if (chrome.runtime.lastError != undefined &&
chrome.runtime.lastError.message != str('canceledWallpaper')) {
this.showError_(chrome.runtime.lastError.message);
$('set-wallpaper-layout').disabled = true;
failure();
} else {
success(opt_thumbnail);
}
};
chrome.wallpaperPrivate.setCustomWallpaper(
wallpaper, layout, generateThumbnail, fileName, false /*previewMode=*/,
onFinished);
};
/**
* Handles the layout setting change of custom wallpaper.
*/
WallpaperManager.prototype.onWallpaperLayoutChanged_ = function() {
this.setCustomWallpaperLayout_(this.getSelectedLayout_());
};
/**
* Saves the custom wallpaper and thumbnail (if any) to the sync file system.
* @param {string} fileName The file name of the wallpaper.
* @param {string} layout The desired layout of the wallpaper.
* @param {string} wallpaperData The data of the full-size wallpaper.
* @param {?string} optThumbnailData The data of the thumbnail-size wallpaper.
* @param {?function} optErrorCallbacka The error callback. Must be non-null if
* optThumbnailData is non-null.
* @private
*/
WallpaperManager.prototype.saveCustomWallpaperToSyncFS_ = function(
fileName, layout, wallpaperData, optThumbnailData, optErrorCallback) {
WallpaperUtil.storeWallpaperToSyncFS(fileName, wallpaperData);
if (!optThumbnailData)
return;
WallpaperUtil.storeWallpaperToSyncFS(
fileName + Constants.CustomWallpaperThumbnailSuffix, optThumbnailData);
var success = dirEntry => {
dirEntry.getFile(fileName, {create: true}, fileEntry => {
fileEntry.createWriter(fileWriter => {
fileWriter.onwriteend = e => {
$('set-wallpaper-layout').disabled = false;
var wallpaperInfo = {
baseURL: fileName,
layout: layout,
source: Constants.WallpaperSourceEnum.Custom,
availableOffline: true
};
this.wallpaperGrid_.dataModel.splice(0, 0, wallpaperInfo);
this.wallpaperGrid_.selectedItem = wallpaperInfo;
this.onWallpaperChanged_(wallpaperInfo, fileName);
WallpaperUtil.saveToLocalStorage(this.currentWallpaper_, layout);
};
fileWriter.onerror = optErrorCallback;
fileWriter.write(WallpaperUtil.createPngBlob(optThumbnailData));
}, optErrorCallback);
}, optErrorCallback);
};
this.wallpaperDirs_.getDirectory(
Constants.WallpaperDirNameEnum.THUMBNAIL, success, optErrorCallback);
};
/**
* Updates the layout of the currently set custom wallpaper. No-op if the
* current wallpaper is not a custom wallpaper.
* @param {string} newLayout The new wallpaper layout.
* @private
*/
WallpaperManager.prototype.setCustomWallpaperLayout_ = function(newLayout) {
var setCustomWallpaperLayoutImpl = (layout, onSuccess) => {
chrome.wallpaperPrivate.setCustomWallpaperLayout(layout, () => {
if (chrome.runtime.lastError != undefined &&
chrome.runtime.lastError.message != str('canceledWallpaper')) {
if (!this.useNewWallpaperPicker_) {
this.showError_(chrome.runtime.lastError.message);
this.removeCustomWallpaper(fileName);
$('set-wallpaper-layout').disabled = true;
}
} else {
WallpaperUtil.saveToLocalStorage(this.currentWallpaper_, layout);
if (this.useNewWallpaperPicker_) {
this.toggleLayoutButtonStates_(layout);
WallpaperUtil.saveWallpaperInfo(
this.currentWallpaper_, layout,
Constants.WallpaperSourceEnum.Custom, '');
} else {
this.onWallpaperChanged_(
this.wallpaperGrid_.activeItem, this.currentWallpaper_);
}
if (onSuccess)
onSuccess();
}
});
};
if (!this.shouldPreviewWallpaper_()) {
setCustomWallpaperLayoutImpl(newLayout, null /*onSuccess=*/);
this.currentWallpaperLayout_ = newLayout;
return;
}
// TODO(crbug.com/836396): Add |previewMode| option to
// |setCustomWallpaperLayout| instead.
var forcePreviewMode = () => {
chrome.wallpaperPrivate.getCurrentWallpaperThumbnail(
this.document_.body.offsetHeight, this.document_.body.offsetWidth,
image => {
chrome.wallpaperPrivate.setCustomWallpaper(
image, Constants.WallpaperThumbnailDefaultLayout,
false /*generateThumbnail=*/, this.currentWallpaper_,
true /*previewMode=*/, () => {
chrome.wallpaperPrivate.cancelPreviewWallpaper(() => {});
});
});
};
forcePreviewMode();
var decorateLayoutButton = layout => {
if (layout != 'CENTER' && layout != 'CENTER_CROPPED') {
console.error('Wallpaper layout ' + layout + ' is not supported.');
return;
}
var layoutButton = this.document_.querySelector(
layout == 'CENTER' ? '.center-button' : '.center-cropped-button');
var newLayoutButton = layoutButton.cloneNode(true);
layoutButton.parentNode.replaceChild(newLayoutButton, layoutButton);
newLayoutButton.addEventListener('click', () => {
setCustomWallpaperLayoutImpl(layout, () => {
this.document_.querySelector('.center-button')
.classList.toggle('disabled', layout == 'CENTER');
this.document_.querySelector('.center-cropped-button')
.classList.toggle('disabled', layout == 'CENTER_CROPPED');
this.onPreviewModeStarted_(
{source: Constants.WallpaperSourceEnum.Custom},
null /*optConfirmCallback=*/,
setCustomWallpaperLayoutImpl.bind(
null, this.currentWallpaperLayout_),
/*optOnRefreshClicked=*/null);
});
});
};
decorateLayoutButton('CENTER');
decorateLayoutButton('CENTER_CROPPED');
this.document_
.querySelector(
newLayout == 'CENTER' ? '.center-button' : '.center-cropped-button')
.click();
this.setWallpaperAttribution({collectionName: str('customCategoryLabel')});
};
/**
* Handles UI changes based on whether surprise me is enabled.
* @param enabled Whether surprise me is enabled.
* @private
*/
WallpaperManager.prototype.onSurpriseMeStateChanged_ = function(enabled) {
WallpaperUtil.setSurpriseMeCheckboxValue(enabled);
$('categories-list').disabled = enabled;
$('wallpaper-grid').disabled = enabled;
if (enabled)
this.document_.body.removeAttribute('surprise-me-disabled');
else
this.document_.body.setAttribute('surprise-me-disabled', '');
};
/**
* Handles user clicking on a different category.
*/
WallpaperManager.prototype.onCategoriesChange_ = function() {
var categoriesList = this.categoriesList_;
var selectedIndex = categoriesList.selectionModel.selectedIndex;
if (selectedIndex == -1)
return;
var selectedListItem = categoriesList.getListItemByIndex(selectedIndex);
var bar = $('bar');
bar.style.left = selectedListItem.offsetLeft + 'px';
bar.style.width = selectedListItem.offsetWidth + 'px';
var wallpapersDataModel = new cr.ui.ArrayDataModel([]);
var selectedItem = null;
// Cancel any ongoing wallpaper request if user clicks on another category.
if (this.useNewWallpaperPicker_ && this.wallpaperRequest_) {
this.wallpaperRequest_.abort();
this.wallpaperRequest_ = null;
}
if (selectedListItem.custom) {
if (this.useNewWallpaperPicker_) {
chrome.wallpaperPrivate.getLocalImagePaths(localImagePaths => {
// Show a 'no images' message to user if there's no local image.
this.updateNoImagesVisibility_(localImagePaths.length == 0);
var wallpapersDataModel = new cr.ui.ArrayDataModel([]);
for (var imagePath of localImagePaths) {
var wallpaperInfo = {
// The absolute file path, used for retrieving the image data
// if user chooses to set this wallpaper.
filePath: imagePath,
// Used as the file name when saving the wallpaper to local and
// sync storage, which only happens after user chooses to set
// this wallpaper. The name 'baseURL' is for consistency with
// the old wallpaper picker.
// TODO(crbug.com/812085): Rename it to fileName after the new
// wallpaper picker is enabled by default.
baseURL: new Date().getTime().toString(),
layout: Constants.WallpaperThumbnailDefaultLayout,
source: Constants.WallpaperSourceEnum.Custom,
availableOffline: true,
collectionName: str('customCategoryLabel'),
// Use file name as aria-label.
ariaLabel: imagePath.split(/[/\\]/).pop(),
previewable: true
};
wallpapersDataModel.push(wallpaperInfo);
}
// Display the images.
this.wallpaperGrid_.dataModel = wallpapersDataModel;
});
return;
}
this.document_.body.setAttribute('custom', '');
var errorHandler = this.onFileSystemError_.bind(this);
var toArray = function(list) {
return Array.prototype.slice.call(list || [], 0);
};
var self = this;
var processResults = function(entries) {
for (var i = 0; i < entries.length; i++) {
var entry = entries[i];
var wallpaperInfo = {
// Set wallpaperId to null to avoid duplicate thumbnail images,
// see crbug.com/506135 for details.
wallpaperId: null,
baseURL: entry.name,
// The layout will be replaced by the actual value saved in
// local storage when requested later. Layout is not important
// for constructing thumbnails grid, we use the default here
// to speed up the process of constructing. So we do not need to
// wait for fetching correct layout.
layout: Constants.WallpaperThumbnailDefaultLayout,
source: Constants.WallpaperSourceEnum.Custom,
availableOffline: true
};
wallpapersDataModel.push(wallpaperInfo);
}
if (loadTimeData.getBoolean('isOEMDefaultWallpaper')) {
var oemDefaultWallpaperElement = {
wallpaperId: null,
baseURL: 'OemDefaultWallpaper',
layout: Constants.WallpaperThumbnailDefaultLayout,
source: Constants.WallpaperSourceEnum.OEM,
availableOffline: true
};
wallpapersDataModel.push(oemDefaultWallpaperElement);
}
for (var i = 0; i < wallpapersDataModel.length; i++) {
// For custom wallpapers, the file name of |currentWallpaper_|
// includes the first directory level (corresponding to user id hash).
if (getBaseName(self.currentWallpaper_) ==
wallpapersDataModel.item(i).baseURL) {
selectedItem = wallpapersDataModel.item(i);
}
}
var lastElement = {
baseURL: '',
layout: '',
source: Constants.WallpaperSourceEnum.AddNew,
availableOffline: true
};
wallpapersDataModel.push(lastElement);
self.wallpaperGrid_.dataModel = wallpapersDataModel;
if (selectedItem) {
self.wallpaperGrid_.selectedItem = selectedItem;
self.wallpaperGrid_.activeItem = selectedItem;
}
};
var success = function(dirEntry) {
var dirReader = dirEntry.createReader();
var entries = [];
// All of a directory's entries are not guaranteed to return in a single
// call.
var readEntries = function() {
dirReader.readEntries(function(results) {
if (!results.length) {
processResults(entries.sort());
} else {
entries = entries.concat(toArray(results));
readEntries();
}
}, errorHandler);
};
readEntries(); // Start reading dirs.
};
this.wallpaperDirs_.getDirectory(
Constants.WallpaperDirNameEnum.ORIGINAL, success, errorHandler);
} else {
this.document_.body.removeAttribute('custom');
if (this.useNewWallpaperPicker_ && this.collectionsInfo_) {
this.showCollection_(selectedIndex);
return;
}
// Need this check for test purpose.
var numOnlineWallpaper = (this.enableOnlineWallpaper_ && this.manifest_) ?
this.manifest_.wallpaper_list.length :
0;
for (var i = 0; i < numOnlineWallpaper; i++) {
if (selectedIndex == AllCategoryIndex ||
this.manifest_.wallpaper_list[i].categories.indexOf(
selectedIndex - OnlineCategoriesOffset) != -1) {
var wallpaperInfo = {
wallpaperId: i,
baseURL: this.manifest_.wallpaper_list[i].base_url,
highResolutionURL: this.manifest_.wallpaper_list[i].base_url +
str('highResolutionSuffix'),
layout: this.manifest_.wallpaper_list[i].default_layout,
source: Constants.WallpaperSourceEnum.Online,
availableOffline: false,
author: this.manifest_.wallpaper_list[i].author,
authorWebsite: this.manifest_.wallpaper_list[i].author_website,
dynamicURL: this.manifest_.wallpaper_list[i].dynamic_url
};
var fileName =
getBaseName(wallpaperInfo.baseURL) + str('highResolutionSuffix');
if (this.downloadedListMap_ &&
this.downloadedListMap_.hasOwnProperty(encodeURI(fileName))) {
wallpaperInfo.availableOffline = true;
}
wallpapersDataModel.push(wallpaperInfo);
if (wallpaperInfo.highResolutionURL == this.currentWallpaper_) {
selectedItem = wallpaperInfo;
}
}
}
this.wallpaperGrid_.dataModel = wallpapersDataModel;
if (selectedItem) {
this.wallpaperGrid_.selectedItem = selectedItem;
this.wallpaperGrid_.activeItem = selectedItem;
}
}
};
/**
* Updates the visibility of the "no images" message.
* @param {boolean} visible Whether the message should be visible.
* @private
*/
WallpaperManager.prototype.updateNoImagesVisibility_ = function(visible) {
if (visible == this.document_.body.classList.contains('no-images'))
return;
this.document_.body.classList.toggle('no-images', visible);
this.placeWallpaperPicker_();
};
/**
* Updates the visibility of the spinners. At most one spinner can be visible at
* a time.
* @param {boolean} visible Whether the spinner should be visible.
* @private
*/
WallpaperManager.prototype.updateSpinnerVisibility_ = function(visible) {
$('preview-spinner').hidden = !visible || !this.isDuringPreview_();
$('current-wallpaper-spinner').hidden = !visible || this.isDuringPreview_();
};
/**
* Returns if wallpaper should be previewed before being set.
* @return {boolean} If wallpaper should be previewed.
* @private
*/
WallpaperManager.prototype.shouldPreviewWallpaper_ = function() {
return this.useNewWallpaperPicker_ &&
(chrome.app.window.current().isFullscreen() ||
chrome.app.window.current().isMaximized());
};
/**
* Returns whether preview mode is currently on.
* @return {boolean} Whether preview mode is currently on.
* @private
*/
WallpaperManager.prototype.isDuringPreview_ = function() {
return this.document_.body.classList.contains('preview-mode');
};
/**
* Notifies the wallpaper manager that the scroll bar position changes.
* @param {number} scrollTop The distance between the scroll bar and the top.
*/
WallpaperManager.prototype.onScrollPositionChanged = function(scrollTop) {
// The current wallpaper info bar should scroll together with the image grid.
$('current-wallpaper-info-bar').style.top = -scrollTop + 'px';
};
/**
* Returns the currently selected layout.
* @return {string} The selected layout.
* @private
*/
WallpaperManager.prototype.getSelectedLayout_ = function() {
if (this.useNewWallpaperPicker_) {
return this.currentlySelectedLayout_ ?
this.currentlySelectedLayout_ :
Constants.WallpaperThumbnailDefaultLayout;
}
var setWallpaperLayout = $('set-wallpaper-layout');
return setWallpaperLayout.options[setWallpaperLayout.selectedIndex].value;
};
/**
* Toggles the disabled states of the layout buttons on the top header bar based
* on the currently selected layout.
* @param {string} layout The currently selected layout.
* @private
*/
WallpaperManager.prototype.toggleLayoutButtonStates_ = function(layout) {
$('center').classList.toggle('disabled', layout == 'CENTER');
$('center-cropped').classList.toggle('disabled', layout == 'CENTER_CROPPED');
};
/**
* Fetches the info related to the daily refresh feature and updates the UI for
* the items. Only used by the new wallpaper picker.
* @private
*/
WallpaperManager.prototype.initializeDailyRefreshStates_ = function() {
if (!this.useNewWallpaperPicker_)
return;
var initializeDailyRefreshStatesImpl = dailyRefreshInfo => {
if (dailyRefreshInfo) {
this.dailyRefreshInfo_ = dailyRefreshInfo;
} else {
// We reach here if it's the first time the new picker is in use, or the
// unlikely case that the data was corrupted (it's safe to assume daily
// refresh is disabled if this ever happens).
this.dailyRefreshInfo_ = {
enabled: false,
collectionId: null,
resumeToken: null
};
}
this.updateDailyRefreshItemStates_(this.dailyRefreshInfo_);
this.decorateCurrentWallpaperInfoBar_();
};
WallpaperUtil.getDailyRefreshInfo(
initializeDailyRefreshStatesImpl.bind(null));
};
/**
* Updates the UI of all the daily refresh items based on the info.
* @param {Object} dailyRefreshInfo The daily refresh info.
* @private
*/
WallpaperManager.prototype.updateDailyRefreshItemStates_ = function(
dailyRefreshInfo) {
if (!this.dailyRefreshItemMap_ || !dailyRefreshInfo)
return;
Object.entries(this.dailyRefreshItemMap_)
.forEach(([collectionId, dailyRefreshItem]) => {
var enabled = dailyRefreshInfo.enabled &&
dailyRefreshInfo.collectionId === collectionId;
dailyRefreshItem.classList.toggle('checked', enabled);
dailyRefreshItem.querySelector('.daily-refresh-slider')
.setAttribute('aria-checked', enabled);
});
};
/**
* Decorates the UI and registers event listener for the item.
* @param {string} collectionId The collection id that this item is associated
* with.
* @param {Object} dailyRefreshItem The daily refresh item.
*/
WallpaperManager.prototype.decorateDailyRefreshItem = function(
collectionId, dailyRefreshItem) {
if (!this.dailyRefreshItemMap_)
this.dailyRefreshItemMap_ = {};
this.dailyRefreshItemMap_[collectionId] = dailyRefreshItem;
this.updateDailyRefreshItemStates_(this.dailyRefreshInfo_);
dailyRefreshItem.addEventListener('click', () => {
var isItemEnabled = dailyRefreshItem.classList.contains('checked');
var isCollectionEnabled =
collectionId === this.dailyRefreshInfo_.collectionId;
if (isItemEnabled !== isCollectionEnabled) {
console.error(
'There is a mismatch between the enabled daily refresh collection ' +
'and the item state. This should never happen.');
return;
}
if (isItemEnabled) {
this.disableDailyRefresh_();
} else {
// Enable daily refresh but do not overwrite |dailyRefreshInfo_| yet
// (since it's still possible to revert). The resume token is left empty
// for now.
this.pendingDailyRefreshInfo_ = {
enabled: true,
collectionId: collectionId,
resumeToken: null
};
this.setDailyRefreshWallpaper_();
}
var toggleRippleAnimation = enabled => {
dailyRefreshItem.classList.toggle('ripple-animation', enabled);
};
toggleRippleAnimation(true);
window.setTimeout(() => {
toggleRippleAnimation(false);
}, 360);
});
dailyRefreshItem.addEventListener('keypress', e => {
if (e.keyCode == 13)
dailyRefreshItem.click();
});
dailyRefreshItem.addEventListener('mousedown', e => {
e.preventDefault();
});
dailyRefreshItem.setAttribute('aria-label', str('surpriseMeLabel'));
};
/**
* Fetches the image for daily refresh based on |pendingDailyRefreshInfo_|.
* Either sets it directly or enters preview mode.
* @private
*/
WallpaperManager.prototype.setDailyRefreshWallpaper_ = function() {
if (!this.pendingDailyRefreshInfo_)
return;
// There should be immediate UI update even though the info hasn't been saved.
this.updateDailyRefreshItemStates_(this.pendingDailyRefreshInfo_);
this.updateSpinnerVisibility_(true);
var retryCount = 0;
var getDailyRefreshImage = () => {
chrome.wallpaperPrivate.getSurpriseMeImage(
this.pendingDailyRefreshInfo_.collectionId,
this.pendingDailyRefreshInfo_.resumeToken,
(imageInfo, nextResumeToken) => {
var failureCallback = () => {
this.pendingDailyRefreshInfo_ = null;
// Restore the original states.
this.updateDailyRefreshItemStates_(this.dailyRefreshInfo_);
this.updateSpinnerVisibility_(false);
};
if (chrome.runtime.lastError) {
console.error(
'Error fetching daily refresh wallpaper for collection id: ' +
this.pendingDailyRefreshInfo_.collectionId);
failureCallback();
return;
}
// If the randomly selected wallpaper happens to be the current
// wallpaper, try again.
if (this.currentWallpaper_.includes(imageInfo['imageUrl']) &&
retryCount < 5) {
++retryCount;
getDailyRefreshImage();
return;
}
this.pendingDailyRefreshInfo_.resumeToken = nextResumeToken;
// Find the name of the collection based on its id for display
// purpose.
var collectionName;
for (var i = 0; i < this.collectionsInfo_.length; ++i) {
if (this.collectionsInfo_[i]['collectionId'] ===
this.pendingDailyRefreshInfo_.collectionId) {
collectionName = this.collectionsInfo_[i]['collectionName'];
}
}
var dailyRefreshImageInfo = {
highResolutionURL:
imageInfo['imageUrl'] + str('highResolutionSuffix'),
layout: Constants.WallpaperThumbnailDefaultLayout,
source: Constants.WallpaperSourceEnum.Daily,
displayText: imageInfo['displayText'],
authorWebsite: imageInfo['actionUrl'],
collectionName: collectionName
};
var previewMode = this.shouldPreviewWallpaper_();
var successCallback = () => {
this.updateSpinnerVisibility_(false);
var onWallpaperConfirmed = () => {
this.dailyRefreshInfo_ = this.pendingDailyRefreshInfo_;
this.pendingDailyRefreshInfo_ = null;
this.onWallpaperChanged_(
dailyRefreshImageInfo,
dailyRefreshImageInfo.highResolutionURL);
var date = new Date().toDateString();
WallpaperUtil.saveToLocalStorage(
Constants.AccessLastSurpriseWallpaperChangedDate, date,
() => {
WallpaperUtil.enabledSyncThemesCallback(syncEnabled => {
var saveInfo = WallpaperUtil.saveDailyRefreshInfo.bind(
WallpaperUtil, this.dailyRefreshInfo_);
if (syncEnabled) {
WallpaperUtil.saveToSyncStorage(
Constants.AccessLastSurpriseWallpaperChangedDate,
date, saveInfo);
} else {
saveInfo();
}
});
});
};
if (previewMode) {
this.setWallpaperAttribution(dailyRefreshImageInfo);
this.onPreviewModeStarted_(
dailyRefreshImageInfo, onWallpaperConfirmed, failureCallback,
this.setDailyRefreshWallpaper_.bind(this));
} else {
onWallpaperConfirmed();
}
};
this.setSelectedOnlineWallpaper_(
dailyRefreshImageInfo, successCallback, failureCallback,
previewMode);
});
};
getDailyRefreshImage();
};
/**
* Helper function to register event listener for the button.
* @param {Object} button The button object.
* @param {function} eventListener The function to be called when the button is
* clicked or the Enter key is pressed.
* @private
*/
WallpaperManager.prototype.addEventToButton_ = function(button, eventListener) {
// Replace the button with a clone to clear all previous event listeners.
var newButton = button.cloneNode(true);
button.parentNode.replaceChild(newButton, button);
button = newButton;
button.addEventListener('click', eventListener);
button.addEventListener('keypress', e => {
if (e.keyCode == 13)
button.click();
});
button.setAttribute('role', 'button');
// The button should receive tab focus.
button.tabIndex = 0;
// Prevent showing the focused style of the button (e.g. an outline) when
// it's clicked.
button.addEventListener('mousedown', e => {
e.preventDefault();
});
};
/**
* Helper function to disable daily refresh on the new wallpaper picker.
* Discards the current values of collection id and resume token. No-op if it's
* already disabled.
* @private
*/
WallpaperManager.prototype.disableDailyRefresh_ = function() {
if (!this.dailyRefreshInfo_ || !this.dailyRefreshInfo_.enabled)
return;
this.dailyRefreshInfo_ = {
enabled: false,
collectionId: null,
resumeToken: null
};
WallpaperUtil.saveDailyRefreshInfo(this.dailyRefreshInfo_);
this.updateDailyRefreshItemStates_(this.dailyRefreshInfo_);
this.decorateCurrentWallpaperInfoBar_();
};
})();