Allow only one bookmark to be added for multiple fast starring
[chromium-blink-merge.git] / chrome / browser / resources / chromeos / wallpaper_manager / js / wallpaper_manager.js
blob759b33b7095e6bb0359c4b510adc6dbdc5c53bab
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /**
6 * WallpaperManager constructor.
8 * WallpaperManager objects encapsulate the functionality of the wallpaper
9 * manager extension.
11 * @constructor
12 * @param {HTMLElement} dialogDom The DOM node containing the prototypical
13 * extension UI.
16 function WallpaperManager(dialogDom) {
17 this.dialogDom_ = dialogDom;
18 this.document_ = dialogDom.ownerDocument;
19 this.enableOnlineWallpaper_ = loadTimeData.valueExists('manifestBaseURL');
20 this.selectedItem_ = null;
21 this.progressManager_ = new ProgressManager();
22 this.customWallpaperData_ = null;
23 this.currentWallpaper_ = null;
24 this.wallpaperRequest_ = null;
25 this.wallpaperDirs_ = WallpaperDirectories.getInstance();
26 this.preManifestDomInit_();
27 this.fetchManifest_();
30 // Anonymous 'namespace'.
31 // TODO(bshe): Get rid of anonymous namespace.
32 (function() {
34 /**
35 * URL of the learn more page for wallpaper picker.
37 /** @const */ var LearnMoreURL =
38 'https://support.google.com/chromebook/?p=wallpaper_fileerror&hl=' +
39 navigator.language;
41 /**
42 * Index of the All category. It is the first category in wallpaper picker.
44 /** @const */ var AllCategoryIndex = 0;
46 /**
47 * Index offset of categories parsed from manifest. The All category is added
48 * before them. So the offset is 1.
50 /** @const */ var OnlineCategoriesOffset = 1;
52 /**
53 * Returns a translated string.
55 * Wrapper function to make dealing with translated strings more concise.
57 * @param {string} id The id of the string to return.
58 * @return {string} The translated string.
60 function str(id) {
61 return loadTimeData.getString(id);
64 /**
65 * Returns the base name for |file_path|.
66 * @param {string} file_path The path of the file.
67 * @return {string} The base name of the file.
69 function getBaseName(file_path) {
70 return file_path.substring(file_path.lastIndexOf('/') + 1);
73 /**
74 * Retruns the current selected layout.
75 * @return {string} The selected layout.
77 function getSelectedLayout() {
78 var setWallpaperLayout = $('set-wallpaper-layout');
79 return setWallpaperLayout.options[setWallpaperLayout.selectedIndex].value;
82 /**
83 * Loads translated strings.
85 WallpaperManager.initStrings = function(callback) {
86 chrome.wallpaperPrivate.getStrings(function(strings) {
87 loadTimeData.data = strings;
88 if (callback)
89 callback();
90 });
93 /**
94 * Requests wallpaper manifest file from server.
96 WallpaperManager.prototype.fetchManifest_ = function() {
97 var locale = navigator.language;
98 if (!this.enableOnlineWallpaper_) {
99 this.postManifestDomInit_();
100 return;
103 var urls = [
104 str('manifestBaseURL') + locale + '.json',
105 // Fallback url. Use 'en' locale by default.
106 str('manifestBaseURL') + 'en.json'];
108 var asyncFetchManifestFromUrls = function(urls, func, successCallback,
109 failureCallback) {
110 var index = 0;
111 var loop = {
112 next: function() {
113 if (index < urls.length) {
114 func(loop, urls[index]);
115 index++;
116 } else {
117 failureCallback();
121 success: function(response) {
122 successCallback(response);
125 failure: function() {
126 failureCallback();
129 loop.next();
132 var fetchManifestAsync = function(loop, url) {
133 var xhr = new XMLHttpRequest();
134 try {
135 xhr.addEventListener('loadend', function(e) {
136 if (this.status == 200 && this.responseText != null) {
137 try {
138 var manifest = JSON.parse(this.responseText);
139 loop.success(manifest);
140 } catch (e) {
141 loop.failure();
143 } else {
144 loop.next();
147 xhr.open('GET', url, true);
148 xhr.send(null);
149 } catch (e) {
150 loop.failure();
154 if (navigator.onLine) {
155 asyncFetchManifestFromUrls(urls, fetchManifestAsync,
156 this.onLoadManifestSuccess_.bind(this),
157 this.onLoadManifestFailed_.bind(this));
158 } else {
159 // If device is offline, fetches manifest from local storage.
160 // TODO(bshe): Always loading the offline manifest first and replacing
161 // with the online one when available.
162 this.onLoadManifestFailed_();
167 * Shows error message in a centered dialog.
168 * @private
169 * @param {string} errroMessage The string to show in the error dialog.
171 WallpaperManager.prototype.showError_ = function(errorMessage) {
172 document.querySelector('.error-message').textContent = errorMessage;
173 $('error-container').hidden = false;
177 * Sets manifest loaded from server. Called after manifest is successfully
178 * loaded.
179 * @param {object} manifest The parsed manifest file.
181 WallpaperManager.prototype.onLoadManifestSuccess_ = function(manifest) {
182 this.manifest_ = manifest;
183 WallpaperUtil.saveToLocalStorage(Constants.AccessLocalManifestKey,
184 manifest);
185 this.postManifestDomInit_();
188 // Sets manifest to previously saved object if any and shows connection error.
189 // Called after manifest failed to load.
190 WallpaperManager.prototype.onLoadManifestFailed_ = function() {
191 var accessManifestKey = Constants.AccessLocalManifestKey;
192 var self = this;
193 Constants.WallpaperLocalStorage.get(accessManifestKey, function(items) {
194 self.manifest_ = items[accessManifestKey] ?
195 items[accessManifestKey] : null;
196 self.showError_(str('connectionFailed'));
197 self.postManifestDomInit_();
198 $('wallpaper-grid').classList.add('image-picker-offline');
203 * Toggle surprise me feature of wallpaper picker. It fires an storage
204 * onChanged event. Event handler for that event is in event_page.js.
205 * @private
207 WallpaperManager.prototype.toggleSurpriseMe_ = function() {
208 var self = this;
209 var checkbox = $('surprise-me').querySelector('#checkbox');
210 var shouldEnable = !checkbox.classList.contains('checked');
211 var onSuccess = function() {
212 if (chrome.runtime.lastError == null) {
213 if (shouldEnable) {
214 self.document_.body.removeAttribute('surprise-me-disabled');
215 checkbox.classList.add('checked');
216 // Hides the wallpaper set by message if there is any.
217 $('wallpaper-set-by-message').textContent = '';
218 } else {
219 // Unchecking the "Surprise me" checkbox falls back to the previous
220 // wallpaper before "Surprise me" was turned on.
221 if (self.wallpaperGrid_.activeItem) {
222 self.setSelectedWallpaper_(self.wallpaperGrid_.activeItem);
223 self.onWallpaperChanged_(self.wallpaperGrid_.activeItem,
224 self.currentWallpaper_);
226 checkbox.classList.remove('checked');
227 self.document_.body.setAttribute('surprise-me-disabled', '');
229 $('categories-list').disabled = shouldEnable;
230 $('wallpaper-grid').disabled = shouldEnable;
231 } else {
232 // TODO(bshe): show error message to user.
233 console.error('Failed to save surprise me option to chrome storage.');
237 // To prevent the onChanged event being fired twice, we only save the value
238 // to sync storage if the sync theme is enabled, otherwise save it to local
239 // storage.
240 WallpaperUtil.enabledSyncThemesCallback(function(syncEnabled) {
241 if (syncEnabled)
242 WallpaperUtil.saveToSyncStorage(
243 Constants.AccessSyncSurpriseMeEnabledKey, shouldEnable, onSuccess);
244 else
245 WallpaperUtil.saveToLocalStorage(
246 Constants.AccessLocalSurpriseMeEnabledKey, shouldEnable, onSuccess);
251 * One-time initialization of various DOM nodes. Fetching manifest may take a
252 * long time due to slow connection. Dom nodes that do not depend on manifest
253 * should be initialized here to unblock from manifest fetching.
255 WallpaperManager.prototype.preManifestDomInit_ = function() {
256 $('window-close-button').addEventListener('click', function() {
257 window.close();
259 this.document_.defaultView.addEventListener(
260 'resize', this.onResize_.bind(this));
261 this.document_.defaultView.addEventListener(
262 'keydown', this.onKeyDown_.bind(this));
263 $('learn-more').href = LearnMoreURL;
264 $('close-error').addEventListener('click', function() {
265 $('error-container').hidden = true;
267 $('close-wallpaper-selection').addEventListener('click', function() {
268 $('wallpaper-selection-container').hidden = true;
269 $('set-wallpaper-layout').disabled = true;
274 * One-time initialization of various DOM nodes. Dom nodes that do depend on
275 * manifest should be initialized here.
277 WallpaperManager.prototype.postManifestDomInit_ = function() {
278 i18nTemplate.process(this.document_, loadTimeData);
279 this.initCategoriesList_();
280 this.initThumbnailsGrid_();
281 this.presetCategory_();
283 $('file-selector').addEventListener(
284 'change', this.onFileSelectorChanged_.bind(this));
285 $('set-wallpaper-layout').addEventListener(
286 'change', this.onWallpaperLayoutChanged_.bind(this));
288 if (loadTimeData.valueExists('wallpaperAppName')) {
289 $('wallpaper-set-by-message').textContent = loadTimeData.getStringF(
290 'currentWallpaperSetByMessage', str('wallpaperAppName'));
293 if (this.enableOnlineWallpaper_) {
294 var self = this;
295 self.document_.body.setAttribute('surprise-me-disabled', '');
296 $('surprise-me').hidden = false;
297 $('surprise-me').addEventListener('click',
298 this.toggleSurpriseMe_.bind(this));
299 var onSurpriseMeEnabled = function() {
300 $('surprise-me').querySelector('#checkbox').classList.add('checked');
301 $('categories-list').disabled = true;
302 $('wallpaper-grid').disabled = true;
303 self.document_.body.removeAttribute('surprise-me-disabled');
306 WallpaperUtil.enabledSyncThemesCallback(function(syncEnabled) {
307 // Surprise me has been moved from local to sync storage, prefer
308 // values from sync, but if unset check local and update synced pref
309 // if applicable.
310 if (syncEnabled) {
311 Constants.WallpaperSyncStorage.get(
312 Constants.AccessSyncSurpriseMeEnabledKey, function(items) {
313 if (items.hasOwnProperty(
314 Constants.AccessSyncSurpriseMeEnabledKey)) {
315 if (items[Constants.AccessSyncSurpriseMeEnabledKey]) {
316 onSurpriseMeEnabled();
318 } else {
319 Constants.WallpaperLocalStorage.get(
320 Constants.AccessLocalSurpriseMeEnabledKey, function(items) {
321 if (items.hasOwnProperty(
322 Constants.AccessLocalSurpriseMeEnabledKey)) {
323 WallpaperUtil.saveToSyncStorage(
324 Constants.AccessSyncSurpriseMeEnabledKey,
325 items[Constants.AccessLocalSurpriseMeEnabledKey]);
326 if (items[Constants.AccessLocalSurpriseMeEnabledKey]) {
327 onSurpriseMeEnabled();
333 } else {
334 Constants.WallpaperLocalStorage.get(
335 Constants.AccessLocalSurpriseMeEnabledKey, function(items) {
336 if (items.hasOwnProperty(
337 Constants.AccessLocalSurpriseMeEnabledKey)) {
338 if (items[Constants.AccessLocalSurpriseMeEnabledKey]) {
339 onSurpriseMeEnabled();
346 window.addEventListener('offline', function() {
347 chrome.wallpaperPrivate.getOfflineWallpaperList(function(lists) {
348 if (!self.downloadedListMap_)
349 self.downloadedListMap_ = {};
350 for (var i = 0; i < lists.length; i++) {
351 self.downloadedListMap_[lists[i]] = true;
353 var thumbnails = self.document_.querySelectorAll('.thumbnail');
354 for (var i = 0; i < thumbnails.length; i++) {
355 var thumbnail = thumbnails[i];
356 var url = self.wallpaperGrid_.dataModel.item(i).baseURL;
357 var fileName = getBaseName(url) + Constants.HighResolutionSuffix;
358 if (self.downloadedListMap_ &&
359 self.downloadedListMap_.hasOwnProperty(encodeURI(fileName))) {
360 thumbnail.offline = true;
364 $('wallpaper-grid').classList.add('image-picker-offline');
366 window.addEventListener('online', function() {
367 self.downloadedListMap_ = null;
368 $('wallpaper-grid').classList.remove('image-picker-offline');
372 this.onResize_();
373 this.initContextMenuAndCommand_();
374 WallpaperUtil.testSendMessage('launched');
378 * One-time initialization of context menu and command.
380 WallpaperManager.prototype.initContextMenuAndCommand_ = function() {
381 this.wallpaperContextMenu_ = $('wallpaper-context-menu');
382 cr.ui.Menu.decorate(this.wallpaperContextMenu_);
383 cr.ui.contextMenuHandler.setContextMenu(this.wallpaperGrid_,
384 this.wallpaperContextMenu_);
385 var commands = this.dialogDom_.querySelectorAll('command');
386 for (var i = 0; i < commands.length; i++)
387 cr.ui.Command.decorate(commands[i]);
389 var doc = this.document_;
390 doc.addEventListener('command', this.onCommand_.bind(this));
391 doc.addEventListener('canExecute', this.onCommandCanExecute_.bind(this));
395 * Handles a command being executed.
396 * @param {Event} event A command event.
398 WallpaperManager.prototype.onCommand_ = function(event) {
399 if (event.command.id == 'delete') {
400 var wallpaperGrid = this.wallpaperGrid_;
401 var selectedIndex = wallpaperGrid.selectionModel.selectedIndex;
402 var item = wallpaperGrid.dataModel.item(selectedIndex);
403 if (!item || item.source != Constants.WallpaperSourceEnum.Custom)
404 return;
405 this.removeCustomWallpaper(item.baseURL);
406 wallpaperGrid.dataModel.splice(selectedIndex, 1);
407 // Calculate the number of remaining custom wallpapers. The add new button
408 // in data model needs to be excluded.
409 var customWallpaperCount = wallpaperGrid.dataModel.length - 1;
410 if (customWallpaperCount == 0) {
411 // Active custom wallpaper is also copied in chronos data dir. It needs
412 // to be deleted.
413 chrome.wallpaperPrivate.resetWallpaper();
414 this.onWallpaperChanged_(null, null);
415 WallpaperUtil.saveWallpaperInfo('', '',
416 Constants.WallpaperSourceEnum.Default);
417 } else {
418 selectedIndex = Math.min(selectedIndex, customWallpaperCount - 1);
419 wallpaperGrid.selectionModel.selectedIndex = selectedIndex;
421 event.cancelBubble = true;
426 * Decides if a command can be executed on current target.
427 * @param {Event} event A command event.
429 WallpaperManager.prototype.onCommandCanExecute_ = function(event) {
430 switch (event.command.id) {
431 case 'delete':
432 var wallpaperGrid = this.wallpaperGrid_;
433 var selectedIndex = wallpaperGrid.selectionModel.selectedIndex;
434 var item = wallpaperGrid.dataModel.item(selectedIndex);
435 if (selectedIndex != this.wallpaperGrid_.dataModel.length - 1 &&
436 item && item.source == Constants.WallpaperSourceEnum.Custom) {
437 event.canExecute = true;
438 break;
440 default:
441 event.canExecute = false;
446 * Preset to the category which contains current wallpaper.
448 WallpaperManager.prototype.presetCategory_ = function() {
449 this.currentWallpaper_ = str('currentWallpaper');
450 // The currentWallpaper_ is either a url contains HightResolutionSuffix or a
451 // custom wallpaper file name converted from an integer value represent
452 // time (e.g., 13006377367586070).
453 if (!this.enableOnlineWallpaper_ || (this.currentWallpaper_ &&
454 this.currentWallpaper_.indexOf(Constants.HighResolutionSuffix) == -1)) {
455 // Custom is the last one in the categories list.
456 this.categoriesList_.selectionModel.selectedIndex =
457 this.categoriesList_.dataModel.length - 1;
458 return;
460 var self = this;
461 var presetCategoryInner_ = function() {
462 // Selects the first category in the categories list of current
463 // wallpaper as the default selected category when showing wallpaper
464 // picker UI.
465 var presetCategory = AllCategoryIndex;
466 if (self.currentWallpaper_) {
467 for (var key in self.manifest_.wallpaper_list) {
468 var url = self.manifest_.wallpaper_list[key].base_url +
469 Constants.HighResolutionSuffix;
470 if (url.indexOf(self.currentWallpaper_) != -1 &&
471 self.manifest_.wallpaper_list[key].categories.length > 0) {
472 presetCategory = self.manifest_.wallpaper_list[key].categories[0] +
473 OnlineCategoriesOffset;
474 break;
478 self.categoriesList_.selectionModel.selectedIndex = presetCategory;
480 if (navigator.onLine) {
481 presetCategoryInner_();
482 } else {
483 // If device is offline, gets the available offline wallpaper list first.
484 // Wallpapers which are not in the list will display a grayscaled
485 // thumbnail.
486 chrome.wallpaperPrivate.getOfflineWallpaperList(function(lists) {
487 if (!self.downloadedListMap_)
488 self.downloadedListMap_ = {};
489 for (var i = 0; i < lists.length; i++)
490 self.downloadedListMap_[lists[i]] = true;
491 presetCategoryInner_();
497 * Constructs the thumbnails grid.
499 WallpaperManager.prototype.initThumbnailsGrid_ = function() {
500 this.wallpaperGrid_ = $('wallpaper-grid');
501 wallpapers.WallpaperThumbnailsGrid.decorate(this.wallpaperGrid_);
503 this.wallpaperGrid_.addEventListener('change', this.onChange_.bind(this));
504 this.wallpaperGrid_.addEventListener('dblclick', this.onClose_.bind(this));
508 * Handles change event dispatched by wallpaper grid.
510 WallpaperManager.prototype.onChange_ = function() {
511 // splice may dispatch a change event because the position of selected
512 // element changing. But the actual selected element may not change after
513 // splice. Check if the new selected element equals to the previous selected
514 // element before continuing. Otherwise, wallpaper may reset to previous one
515 // as described in http://crbug.com/229036.
516 if (this.selectedItem_ == this.wallpaperGrid_.selectedItem)
517 return;
518 this.selectedItem_ = this.wallpaperGrid_.selectedItem;
519 this.onSelectedItemChanged_();
523 * Closes window if no pending wallpaper request.
525 WallpaperManager.prototype.onClose_ = function() {
526 if (this.wallpaperRequest_) {
527 this.wallpaperRequest_.addEventListener('loadend', function() {
528 // Close window on wallpaper loading finished.
529 window.close();
531 } else {
532 window.close();
537 * Moves the check mark to |activeItem| and hides the wallpaper set by third
538 * party message if any. Called when wallpaper changed successfully.
539 * @param {?Object} activeItem The active item in WallpaperThumbnailsGrid's
540 * data model.
541 * @param {?string} currentWallpaperURL The URL or filename of current
542 * wallpaper.
544 WallpaperManager.prototype.onWallpaperChanged_ = function(
545 activeItem, currentWallpaperURL) {
546 this.wallpaperGrid_.activeItem = activeItem;
547 this.currentWallpaper_ = currentWallpaperURL;
548 // Hides the wallpaper set by message.
549 $('wallpaper-set-by-message').textContent = '';
553 * Sets wallpaper to the corresponding wallpaper of selected thumbnail.
554 * @param {{baseURL: string, layout: string, source: string,
555 * availableOffline: boolean, opt_dynamicURL: string,
556 * opt_author: string, opt_authorWebsite: string}}
557 * selectedItem the selected item in WallpaperThumbnailsGrid's data
558 * model.
560 WallpaperManager.prototype.setSelectedWallpaper_ = function(selectedItem) {
561 var self = this;
562 switch (selectedItem.source) {
563 case Constants.WallpaperSourceEnum.Custom:
564 var errorHandler = this.onFileSystemError_.bind(this);
565 var success = function(dirEntry) {
566 dirEntry.getFile(selectedItem.baseURL, {create: false},
567 function(fileEntry) {
568 fileEntry.file(function(file) {
569 var reader = new FileReader();
570 reader.readAsArrayBuffer(file);
571 reader.addEventListener('error', errorHandler);
572 reader.addEventListener('load', function(e) {
573 self.setCustomWallpaper(e.target.result, selectedItem.layout,
574 false, selectedItem.baseURL,
575 function(thumbnailData) {
576 self.onWallpaperChanged_(selectedItem,
577 selectedItem.baseURL, thumbnailData);
578 WallpaperUtil.storeWallpaperToSyncFS(
579 selectedItem.baseURL, e.target.result);
580 WallpaperUtil.storeWallpaperToSyncFS(
581 selectedItem.baseURL +
582 Constants.CustomWallpaperThumbnailSuffix,
583 thumbnailData);
585 errorHandler);
587 }, errorHandler);
588 }, errorHandler);
590 this.wallpaperDirs_.getDirectory(
591 Constants.WallpaperDirNameEnum.ORIGINAL, success, errorHandler);
592 break;
593 case Constants.WallpaperSourceEnum.OEM:
594 // Resets back to default wallpaper.
595 chrome.wallpaperPrivate.resetWallpaper();
596 this.onWallpaperChanged_(selectedItem, selectedItem.baseURL);
597 WallpaperUtil.saveWallpaperInfo(wallpaperURL, selectedItem.layout,
598 selectedItem.source);
599 break;
600 case Constants.WallpaperSourceEnum.Online:
601 var wallpaperURL = selectedItem.baseURL +
602 Constants.HighResolutionSuffix;
603 var selectedGridItem = this.wallpaperGrid_.getListItem(selectedItem);
605 chrome.wallpaperPrivate.setWallpaperIfExists(wallpaperURL,
606 selectedItem.layout,
607 function(exists) {
608 if (exists) {
609 self.onWallpaperChanged_(selectedItem, wallpaperURL);
610 WallpaperUtil.saveWallpaperInfo(wallpaperURL, selectedItem.layout,
611 selectedItem.source);
612 return;
615 // Falls back to request wallpaper from server.
616 if (self.wallpaperRequest_)
617 self.wallpaperRequest_.abort();
619 self.wallpaperRequest_ = new XMLHttpRequest();
620 self.progressManager_.reset(self.wallpaperRequest_, selectedGridItem);
622 var onSuccess = function(xhr) {
623 var image = xhr.response;
624 chrome.wallpaperPrivate.setWallpaper(image, selectedItem.layout,
625 wallpaperURL,
626 function() {
627 self.progressManager_.hideProgressBar(selectedGridItem);
629 if (chrome.runtime.lastError != undefined &&
630 chrome.runtime.lastError.message !=
631 str('canceledWallpaper')) {
632 self.showError_(chrome.runtime.lastError.message);
633 } else {
634 self.onWallpaperChanged_(selectedItem, wallpaperURL);
637 WallpaperUtil.saveWallpaperInfo(wallpaperURL, selectedItem.layout,
638 selectedItem.source);
639 self.wallpaperRequest_ = null;
641 var onFailure = function(status) {
642 self.progressManager_.hideProgressBar(selectedGridItem);
643 self.showError_(str('downloadFailed'));
644 self.wallpaperRequest_ = null;
646 WallpaperUtil.fetchURL(wallpaperURL, 'arraybuffer', onSuccess,
647 onFailure, self.wallpaperRequest_);
649 break;
650 default:
651 console.error('Unsupported wallpaper source.');
656 * Removes the oldest custom wallpaper. If the oldest one is set as current
657 * wallpaper, removes the second oldest one to free some space. This should
658 * only be called when exceeding wallpaper quota.
660 WallpaperManager.prototype.removeOldestWallpaper_ = function() {
661 // Custom wallpapers should already sorted when put to the data model. The
662 // last element is the add new button, need to exclude it as well.
663 var oldestIndex = this.wallpaperGrid_.dataModel.length - 2;
664 var item = this.wallpaperGrid_.dataModel.item(oldestIndex);
665 if (!item || item.source != Constants.WallpaperSourceEnum.Custom)
666 return;
667 if (item.baseURL == this.currentWallpaper_)
668 item = this.wallpaperGrid_.dataModel.item(--oldestIndex);
669 if (item) {
670 this.removeCustomWallpaper(item.baseURL);
671 this.wallpaperGrid_.dataModel.splice(oldestIndex, 1);
676 * Shows an error message to user and log the failed reason in console.
678 WallpaperManager.prototype.onFileSystemError_ = function(e) {
679 var msg = '';
680 switch (e.code) {
681 case FileError.QUOTA_EXCEEDED_ERR:
682 msg = 'QUOTA_EXCEEDED_ERR';
683 // Instead of simply remove oldest wallpaper, we should consider a
684 // better way to handle this situation. See crbug.com/180890.
685 this.removeOldestWallpaper_();
686 break;
687 case FileError.NOT_FOUND_ERR:
688 msg = 'NOT_FOUND_ERR';
689 break;
690 case FileError.SECURITY_ERR:
691 msg = 'SECURITY_ERR';
692 break;
693 case FileError.INVALID_MODIFICATION_ERR:
694 msg = 'INVALID_MODIFICATION_ERR';
695 break;
696 case FileError.INVALID_STATE_ERR:
697 msg = 'INVALID_STATE_ERR';
698 break;
699 default:
700 msg = 'Unknown Error';
701 break;
703 console.error('Error: ' + msg);
704 this.showError_(str('accessFileFailure'));
708 * Handles changing of selectedItem in wallpaper manager.
710 WallpaperManager.prototype.onSelectedItemChanged_ = function() {
711 this.setWallpaperAttribution_(this.selectedItem_);
713 if (!this.selectedItem_ || this.selectedItem_.source == 'ADDNEW')
714 return;
716 if (this.selectedItem_.baseURL && !this.wallpaperGrid_.inProgramSelection) {
717 if (this.selectedItem_.source == Constants.WallpaperSourceEnum.Custom) {
718 var items = {};
719 var key = this.selectedItem_.baseURL;
720 var self = this;
721 Constants.WallpaperLocalStorage.get(key, function(items) {
722 self.selectedItem_.layout =
723 items[key] ? items[key] : 'CENTER_CROPPED';
724 self.setSelectedWallpaper_(self.selectedItem_);
726 } else {
727 this.setSelectedWallpaper_(this.selectedItem_);
733 * Set attributions of wallpaper with given URL. If URL is not valid, clear
734 * the attributions.
735 * @param {{baseURL: string, dynamicURL: string, layout: string,
736 * author: string, authorWebsite: string, availableOffline: boolean}}
737 * selectedItem selected wallpaper item in grid.
738 * @private
740 WallpaperManager.prototype.setWallpaperAttribution_ = function(selectedItem) {
741 // Only online wallpapers have author and website attributes. All other type
742 // of wallpapers should not show attributions.
743 if (selectedItem &&
744 selectedItem.source == Constants.WallpaperSourceEnum.Online) {
745 $('author-name').textContent = selectedItem.author;
746 $('author-website').textContent = $('author-website').href =
747 selectedItem.authorWebsite;
748 chrome.wallpaperPrivate.getThumbnail(selectedItem.baseURL,
749 selectedItem.source,
750 function(data) {
751 var img = $('attribute-image');
752 if (data) {
753 var blob = new Blob([new Int8Array(data)], {'type' : 'image\/png'});
754 img.src = window.URL.createObjectURL(blob);
755 img.addEventListener('load', function(e) {
756 window.URL.revokeObjectURL(this.src);
758 } else {
759 img.src = '';
762 $('wallpaper-attribute').hidden = false;
763 $('attribute-image').hidden = false;
764 return;
766 $('wallpaper-attribute').hidden = true;
767 $('attribute-image').hidden = true;
768 $('author-name').textContent = '';
769 $('author-website').textContent = $('author-website').href = '';
770 $('attribute-image').src = '';
774 * Resize thumbnails grid and categories list to fit the new window size.
776 WallpaperManager.prototype.onResize_ = function() {
777 this.wallpaperGrid_.redraw();
778 this.categoriesList_.redraw();
782 * Close the last opened overlay or app window on pressing the Escape key.
783 * @param {Event} event A keydown event.
785 WallpaperManager.prototype.onKeyDown_ = function(event) {
786 if (event.keyCode == 27) {
787 // The last opened overlay coincides with the first match of querySelector
788 // because the Error Container is declared in the DOM before the Wallpaper
789 // Selection Container.
790 // TODO(bshe): Make the overlay selection not dependent on the DOM.
791 var closeButtonSelector = '.overlay-container:not([hidden]) .close';
792 var closeButton = this.document_.querySelector(closeButtonSelector);
793 if (closeButton) {
794 closeButton.click();
795 event.preventDefault();
796 } else {
797 this.onClose_();
803 * Constructs the categories list.
805 WallpaperManager.prototype.initCategoriesList_ = function() {
806 this.categoriesList_ = $('categories-list');
807 wallpapers.WallpaperCategoriesList.decorate(this.categoriesList_);
809 this.categoriesList_.selectionModel.addEventListener(
810 'change', this.onCategoriesChange_.bind(this));
812 if (this.enableOnlineWallpaper_ && this.manifest_) {
813 // Adds all category as first category.
814 this.categoriesList_.dataModel.push(str('allCategoryLabel'));
815 for (var key in this.manifest_.categories) {
816 this.categoriesList_.dataModel.push(this.manifest_.categories[key]);
819 // Adds custom category as last category.
820 this.categoriesList_.dataModel.push(str('customCategoryLabel'));
824 * Handles the custom wallpaper which user selected from file manager. Called
825 * when users select a file.
827 WallpaperManager.prototype.onFileSelectorChanged_ = function() {
828 var files = $('file-selector').files;
829 if (files.length != 1)
830 console.error('More than one files are selected or no file selected');
831 if (!files[0].type.match('image/jpeg') &&
832 !files[0].type.match('image/png')) {
833 this.showError_(str('invalidWallpaper'));
834 return;
836 var layout = getSelectedLayout();
837 var self = this;
838 var errorHandler = this.onFileSystemError_.bind(this);
839 var setSelectedFile = function(file, layout, fileName) {
840 var saveThumbnail = function(thumbnail) {
841 var success = function(dirEntry) {
842 dirEntry.getFile(fileName, {create: true}, function(fileEntry) {
843 fileEntry.createWriter(function(fileWriter) {
844 fileWriter.onwriteend = function(e) {
845 $('set-wallpaper-layout').disabled = false;
846 var wallpaperInfo = {
847 baseURL: fileName,
848 layout: layout,
849 source: Constants.WallpaperSourceEnum.Custom,
850 availableOffline: true
852 self.wallpaperGrid_.dataModel.splice(0, 0, wallpaperInfo);
853 self.wallpaperGrid_.selectedItem = wallpaperInfo;
854 self.onWallpaperChanged_(wallpaperInfo, fileName);
855 WallpaperUtil.saveToLocalStorage(self.currentWallpaper_,
856 layout);
859 fileWriter.onerror = errorHandler;
861 var blob = new Blob([new Int8Array(thumbnail)],
862 {'type' : 'image\/jpeg'});
863 fileWriter.write(blob);
864 }, errorHandler);
865 }, errorHandler);
867 self.wallpaperDirs_.getDirectory(
868 Constants.WallpaperDirNameEnum.THUMBNAIL, success, errorHandler);
870 var onCustomWallpaperSuccess = function(thumbnailData, wallpaperData) {
871 WallpaperUtil.storeWallpaperToSyncFS(fileName, wallpaperData);
872 WallpaperUtil.storeWallpaperToSyncFS(
873 fileName + Constants.CustomWallpaperThumbnailSuffix,
874 thumbnailData);
875 saveThumbnail(thumbnailData);
877 var success = function(dirEntry) {
878 dirEntry.getFile(fileName, {create: true}, function(fileEntry) {
879 fileEntry.createWriter(function(fileWriter) {
880 fileWriter.addEventListener('writeend', function(e) {
881 var reader = new FileReader();
882 reader.readAsArrayBuffer(file);
883 reader.addEventListener('error', errorHandler);
884 reader.addEventListener('load', function(e) {
885 self.setCustomWallpaper(e.target.result, layout, true, fileName,
886 function(thumbnail) {
887 onCustomWallpaperSuccess(thumbnail, e.target.result);
889 function() {
890 self.removeCustomWallpaper(fileName);
891 errorHandler();
895 fileWriter.addEventListener('error', errorHandler);
896 fileWriter.write(file);
897 }, errorHandler);
898 }, errorHandler);
900 self.wallpaperDirs_.getDirectory(Constants.WallpaperDirNameEnum.ORIGINAL,
901 success,
902 errorHandler);
904 setSelectedFile(files[0], layout, new Date().getTime().toString());
908 * Removes wallpaper and thumbnail with fileName from FileSystem.
909 * @param {string} fileName The file name of wallpaper and thumbnail to be
910 * removed.
912 WallpaperManager.prototype.removeCustomWallpaper = function(fileName) {
913 var errorHandler = this.onFileSystemError_.bind(this);
914 var self = this;
915 var removeFile = function(fileName) {
916 var success = function(dirEntry) {
917 dirEntry.getFile(fileName, {create: false}, function(fileEntry) {
918 fileEntry.remove(function() {
919 WallpaperUtil.deleteWallpaperFromSyncFS(fileName);
920 }, errorHandler);
921 }, errorHandler);
924 // Removes copy of original.
925 self.wallpaperDirs_.getDirectory(Constants.WallpaperDirNameEnum.ORIGINAL,
926 success,
927 errorHandler);
929 // Removes generated thumbnail.
930 self.wallpaperDirs_.getDirectory(Constants.WallpaperDirNameEnum.THUMBNAIL,
931 success,
932 errorHandler);
934 removeFile(fileName);
938 * Sets current wallpaper and generate thumbnail if generateThumbnail is true.
939 * @param {ArrayBuffer} wallpaper The binary representation of wallpaper.
940 * @param {string} layout The user selected wallpaper layout.
941 * @param {boolean} generateThumbnail True if need to generate thumbnail.
942 * @param {string} fileName The unique file name of wallpaper.
943 * @param {function(thumbnail):void} success Success callback. If
944 * generateThumbnail is true, the callback parameter should have the
945 * generated thumbnail.
946 * @param {function(e):void} failure Failure callback. Called when there is an
947 * error from FileSystem.
949 WallpaperManager.prototype.setCustomWallpaper = function(wallpaper,
950 layout,
951 generateThumbnail,
952 fileName,
953 success,
954 failure) {
955 var self = this;
956 var onFinished = function(opt_thumbnail) {
957 if (chrome.runtime.lastError != undefined &&
958 chrome.runtime.lastError.message != str('canceledWallpaper')) {
959 self.showError_(chrome.runtime.lastError.message);
960 $('set-wallpaper-layout').disabled = true;
961 failure();
962 } else {
963 success(opt_thumbnail);
964 // Custom wallpapers are not synced yet. If login on a different
965 // computer after set a custom wallpaper, wallpaper wont change by sync.
966 WallpaperUtil.saveWallpaperInfo(fileName, layout,
967 Constants.WallpaperSourceEnum.Custom);
971 chrome.wallpaperPrivate.setCustomWallpaper(wallpaper, layout,
972 generateThumbnail,
973 fileName, onFinished);
977 * Handles the layout setting change of custom wallpaper.
979 WallpaperManager.prototype.onWallpaperLayoutChanged_ = function() {
980 var layout = getSelectedLayout();
981 var self = this;
982 chrome.wallpaperPrivate.setCustomWallpaperLayout(layout, function() {
983 if (chrome.runtime.lastError != undefined &&
984 chrome.runtime.lastError.message != str('canceledWallpaper')) {
985 self.showError_(chrome.runtime.lastError.message);
986 self.removeCustomWallpaper(fileName);
987 $('set-wallpaper-layout').disabled = true;
988 } else {
989 WallpaperUtil.saveToLocalStorage(self.currentWallpaper_, layout);
990 self.onWallpaperChanged_(self.wallpaperGrid_.activeItem,
991 self.currentWallpaper_);
997 * Handles user clicking on a different category.
999 WallpaperManager.prototype.onCategoriesChange_ = function() {
1000 var categoriesList = this.categoriesList_;
1001 var selectedIndex = categoriesList.selectionModel.selectedIndex;
1002 if (selectedIndex == -1)
1003 return;
1004 var selectedListItem = categoriesList.getListItemByIndex(selectedIndex);
1005 var bar = $('bar');
1006 bar.style.left = selectedListItem.offsetLeft + 'px';
1007 bar.style.width = selectedListItem.offsetWidth + 'px';
1009 var wallpapersDataModel = new cr.ui.ArrayDataModel([]);
1010 var selectedItem = null;
1011 if (selectedListItem.custom) {
1012 this.document_.body.setAttribute('custom', '');
1013 var errorHandler = this.onFileSystemError_.bind(this);
1014 var toArray = function(list) {
1015 return Array.prototype.slice.call(list || [], 0);
1018 var self = this;
1019 var processResults = function(entries) {
1020 for (var i = 0; i < entries.length; i++) {
1021 var entry = entries[i];
1022 var wallpaperInfo = {
1023 // Set wallpaperId to null to avoid duplicate thumbnail images,
1024 // see crbug.com/506135 for details.
1025 wallpaperId: null,
1026 baseURL: entry.name,
1027 // The layout will be replaced by the actual value saved in
1028 // local storage when requested later. Layout is not important
1029 // for constructing thumbnails grid, we use CENTER_CROPPED here
1030 // to speed up the process of constructing. So we do not need to
1031 // wait for fetching correct layout.
1032 layout: 'CENTER_CROPPED',
1033 source: Constants.WallpaperSourceEnum.Custom,
1034 availableOffline: true
1036 wallpapersDataModel.push(wallpaperInfo);
1038 if (loadTimeData.getBoolean('isOEMDefaultWallpaper')) {
1039 var oemDefaultWallpaperElement = {
1040 wallpaperId: null,
1041 baseURL: 'OemDefaultWallpaper',
1042 layout: 'CENTER_CROPPED',
1043 source: Constants.WallpaperSourceEnum.OEM,
1044 availableOffline: true
1046 wallpapersDataModel.push(oemDefaultWallpaperElement);
1048 for (var i = 0; i < wallpapersDataModel.length; i++) {
1049 // For custom wallpapers, the file name of |currentWallpaper_|
1050 // includes the first directory level (corresponding to user id hash).
1051 if (getBaseName(self.currentWallpaper_) ==
1052 wallpapersDataModel.item(i).baseURL) {
1053 selectedItem = wallpapersDataModel.item(i);
1056 var lastElement = {
1057 baseURL: '',
1058 layout: '',
1059 source: Constants.WallpaperSourceEnum.AddNew,
1060 availableOffline: true
1062 wallpapersDataModel.push(lastElement);
1063 self.wallpaperGrid_.dataModel = wallpapersDataModel;
1064 if (selectedItem) {
1065 self.wallpaperGrid_.selectedItem = selectedItem;
1066 self.wallpaperGrid_.activeItem = selectedItem;
1070 var success = function(dirEntry) {
1071 var dirReader = dirEntry.createReader();
1072 var entries = [];
1073 // All of a directory's entries are not guaranteed to return in a single
1074 // call.
1075 var readEntries = function() {
1076 dirReader.readEntries(function(results) {
1077 if (!results.length) {
1078 processResults(entries.sort());
1079 } else {
1080 entries = entries.concat(toArray(results));
1081 readEntries();
1083 }, errorHandler);
1085 readEntries(); // Start reading dirs.
1087 this.wallpaperDirs_.getDirectory(Constants.WallpaperDirNameEnum.ORIGINAL,
1088 success, errorHandler);
1089 } else {
1090 this.document_.body.removeAttribute('custom');
1091 // Need this check for test purpose.
1092 var numOnlineWallpaper = (this.enableOnlineWallpaper_ && this.manifest_) ?
1093 this.manifest_.wallpaper_list.length : 0;
1094 for (var i = 0; i < numOnlineWallpaper; i++) {
1095 if (selectedIndex == AllCategoryIndex ||
1096 this.manifest_.wallpaper_list[i].categories.indexOf(
1097 selectedIndex - OnlineCategoriesOffset) != -1) {
1098 var wallpaperInfo = {
1099 wallpaperId: i,
1100 baseURL: this.manifest_.wallpaper_list[i].base_url,
1101 layout: this.manifest_.wallpaper_list[i].default_layout,
1102 source: Constants.WallpaperSourceEnum.Online,
1103 availableOffline: false,
1104 author: this.manifest_.wallpaper_list[i].author,
1105 authorWebsite: this.manifest_.wallpaper_list[i].author_website,
1106 dynamicURL: this.manifest_.wallpaper_list[i].dynamic_url
1108 var fileName = getBaseName(wallpaperInfo.baseURL) +
1109 Constants.HighResolutionSuffix;
1110 if (this.downloadedListMap_ &&
1111 this.downloadedListMap_.hasOwnProperty(encodeURI(fileName))) {
1112 wallpaperInfo.availableOffline = true;
1114 wallpapersDataModel.push(wallpaperInfo);
1115 var url = this.manifest_.wallpaper_list[i].base_url +
1116 Constants.HighResolutionSuffix;
1117 if (url == this.currentWallpaper_) {
1118 selectedItem = wallpaperInfo;
1122 this.wallpaperGrid_.dataModel = wallpapersDataModel;
1123 if (selectedItem) {
1124 this.wallpaperGrid_.selectedItem = selectedItem;
1125 this.wallpaperGrid_.activeItem = selectedItem;
1130 })();