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 cr.define('wallpapers', function() {
6 /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
7 /** @const */ var Grid = cr.ui.Grid;
8 /** @const */ var GridItem = cr.ui.GridItem;
9 /** @const */ var GridSelectionController = cr.ui.GridSelectionController;
10 /** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
11 /** @const */ var ThumbnailSuffix = '_thumbnail.png';
12 /** @const */ var ShowSpinnerDelayMs = 500;
15 * Creates a new wallpaper thumbnails grid item.
16 * @param {{baseURL: string, layout: string, source: string,
17 * availableOffline: boolean, opt_dynamicURL: string,
18 * opt_author: string, opt_authorWebsite: string}}
19 * wallpaperInfo Wallpaper data item in WallpaperThumbnailsGrid's data
21 * @param {number} dataModelId A unique ID that this item associated to.
22 * @param {function} callback The callback function when decoration finished.
24 * @extends {cr.ui.GridItem}
26 function WallpaperThumbnailsGridItem(wallpaperInfo, dataModelId, callback) {
27 var el = new GridItem(wallpaperInfo);
28 el.__proto__ = WallpaperThumbnailsGridItem.prototype;
29 el.dataModelId = dataModelId;
30 el.callback = callback;
34 WallpaperThumbnailsGridItem.prototype = {
35 __proto__: GridItem.prototype,
38 * The unique ID this thumbnail grid associated to.
44 * Called when the WallpaperThumbnailsGridItem is decorated or failed to
45 * decorate. If the decoration contains image, the callback function should
46 * be called after image loaded.
52 decorate: function() {
53 GridItem.prototype.decorate.call(this);
54 // Removes garbage created by GridItem.
56 var imageEl = cr.doc.createElement('img');
57 imageEl.classList.add('thumbnail');
58 cr.defineProperty(imageEl, 'offline', cr.PropertyKind.BOOL_ATTR);
59 imageEl.offline = this.dataItem.availableOffline;
60 this.appendChild(imageEl);
63 switch (this.dataItem.source) {
64 case Constants.WallpaperSourceEnum.AddNew:
66 this.addEventListener('click', function(e) {
67 var checkbox = $('surprise-me').querySelector('#checkbox');
68 if (!checkbox.classList.contains('checked'))
69 $('wallpaper-selection-container').hidden = false;
71 // Delay dispatching the completion callback until all items have
72 // begun loading and are tracked.
73 window.setTimeout(this.callback.bind(this, this.dataModelId), 0);
75 case Constants.WallpaperSourceEnum.Custom:
76 var errorHandler = function(e) {
77 self.callback(self.dataModelId);
78 console.error('Can not access file system.');
80 var wallpaperDirectories = WallpaperDirectories.getInstance();
81 var getThumbnail = function(fileName) {
82 var setURL = function(fileEntry) {
83 imageEl.src = fileEntry.toURL();
84 self.callback(self.dataModelId);
86 var fallback = function() {
87 wallpaperDirectories.getDirectory(
88 Constants.WallpaperDirNameEnum.ORIGINAL, function(dirEntry) {
89 dirEntry.getFile(fileName, {create: false}, setURL,
93 var success = function(dirEntry) {
94 dirEntry.getFile(fileName, {create: false}, setURL, fallback);
96 wallpaperDirectories.getDirectory(
97 Constants.WallpaperDirNameEnum.THUMBNAIL, success, errorHandler);
99 getThumbnail(self.dataItem.baseURL);
101 case Constants.WallpaperSourceEnum.OEM:
102 case Constants.WallpaperSourceEnum.Online:
103 chrome.wallpaperPrivate.getThumbnail(this.dataItem.baseURL,
104 this.dataItem.source,
107 var blob = new Blob([new Int8Array(data)],
108 {'type': 'image\/png'});
109 imageEl.src = window.URL.createObjectURL(blob);
110 imageEl.addEventListener('load', function(e) {
111 self.callback(self.dataModelId);
112 window.URL.revokeObjectURL(this.src);
114 } else if (self.dataItem.source ==
115 Constants.WallpaperSourceEnum.Online) {
116 var xhr = new XMLHttpRequest();
117 xhr.open('GET', self.dataItem.baseURL + ThumbnailSuffix, true);
118 xhr.responseType = 'arraybuffer';
120 xhr.addEventListener('load', function(e) {
121 if (xhr.status === 200) {
122 chrome.wallpaperPrivate.saveThumbnail(self.dataItem.baseURL,
124 var blob = new Blob([new Int8Array(xhr.response)],
125 {'type' : 'image\/png'});
126 imageEl.src = window.URL.createObjectURL(blob);
127 // TODO(bshe): We currently use empty div to reserve space for
128 // thumbnail. Use a placeholder like "loading" image may
130 imageEl.addEventListener('load', function(e) {
131 self.callback(self.dataModelId);
132 window.URL.revokeObjectURL(this.src);
135 self.callback(self.dataModelId);
142 console.error('Unsupported image source.');
143 // Delay dispatching the completion callback until all items have
144 // begun loading and are tracked.
145 window.setTimeout(this.callback.bind(this, this.dataModelId), 0);
151 * Creates a selection controller that wraps selection on grid ends
152 * and translates Enter presses into 'activate' events.
153 * @param {cr.ui.ListSelectionModel} selectionModel The selection model to
155 * @param {cr.ui.Grid} grid The grid to interact with.
157 * @extends {cr.ui.GridSelectionController}
159 function WallpaperThumbnailsGridSelectionController(selectionModel, grid) {
160 GridSelectionController.call(this, selectionModel, grid);
163 WallpaperThumbnailsGridSelectionController.prototype = {
164 __proto__: GridSelectionController.prototype,
167 getIndexBefore: function(index) {
169 GridSelectionController.prototype.getIndexBefore.call(this, index);
170 return result == -1 ? this.getLastIndex() : result;
174 getIndexAfter: function(index) {
176 GridSelectionController.prototype.getIndexAfter.call(this, index);
177 return result == -1 ? this.getFirstIndex() : result;
181 handleKeyDown: function(e) {
182 if (e.keyIdentifier == 'Enter')
183 cr.dispatchSimpleEvent(this.grid_, 'activate');
185 GridSelectionController.prototype.handleKeyDown.call(this, e);
190 * Creates a new user images grid element.
191 * @param {Object=} opt_propertyBag Optional properties.
193 * @extends {cr.ui.Grid}
195 var WallpaperThumbnailsGrid = cr.ui.define('grid');
197 WallpaperThumbnailsGrid.prototype = {
198 __proto__: Grid.prototype,
201 * The checkbox element.
203 checkmark_: undefined,
206 * ID of spinner delay timer.
212 * The item in data model which should have a checkmark.
213 * @type {{baseURL: string, dynamicURL: string, layout: string,
214 * author: string, authorWebsite: string,
215 * availableOffline: boolean}}
216 * wallpaperInfo The information of the wallpaper to be set active.
218 activeItem_: undefined,
219 set activeItem(activeItem) {
220 if (this.activeItem_ != activeItem) {
221 this.activeItem_ = activeItem;
222 this.updateActiveThumb_();
227 * A unique ID that assigned to each set dataModel operation. Note that this
228 * id wont increase if the new dataModel is null or empty.
233 * The number of items that need to be generated after a new dataModel is
239 set dataModel(dataModel) {
240 if (this.dataModel_ == dataModel)
243 if (dataModel && dataModel.length != 0) {
245 // Clears old pending items. The new pending items will be counted when
246 // item is constructed in function itemConstructor below.
247 this.pendingItems_ = 0;
249 this.style.visibility = 'hidden';
250 // If spinner is hidden, schedule to show the spinner after
251 // ShowSpinnerDelayMs delay. Otherwise, keep it spinning.
252 if ($('spinner-container').hidden) {
253 this.spinnerTimeout_ = window.setTimeout(function() {
254 $('spinner-container').hidden = false;
255 }, ShowSpinnerDelayMs);
258 // Sets dataModel to null should hide spinner immedidately.
259 $('spinner-container').hidden = true;
262 var parentSetter = cr.ui.Grid.prototype.__lookupSetter__('dataModel');
263 parentSetter.call(this, dataModel);
267 return this.dataModel_;
271 createSelectionController: function(sm) {
272 return new WallpaperThumbnailsGridSelectionController(sm, this);
276 * Check if new thumbnail grid finished loading. This reduces the count of
277 * remaining items to be loaded and when 0, shows the thumbnail grid. Note
278 * it does not reduce the count on a previous |dataModelId|.
279 * @param {number} dataModelId A unique ID that a thumbnail item is
282 pendingItemComplete: function(dataModelId) {
283 if (dataModelId != this.dataModelId_)
285 this.pendingItems_--;
286 if (this.pendingItems_ == 0) {
287 this.style.visibility = 'visible';
288 window.clearTimeout(this.spinnerTimeout_);
289 this.spinnerTimeout_ = 0;
290 $('spinner-container').hidden = true;
295 decorate: function() {
296 Grid.prototype.decorate.call(this);
297 // checkmark_ needs to be initialized before set data model. Otherwise, we
298 // may try to access checkmark before initialization in
299 // updateActiveThumb_().
300 this.checkmark_ = cr.doc.createElement('div');
301 this.checkmark_.classList.add('check');
302 this.dataModel = new ArrayDataModel([]);
304 this.itemConstructor = function(value) {
305 var dataModelId = self.dataModelId_;
306 self.pendingItems_++;
307 return WallpaperThumbnailsGridItem(value, dataModelId,
308 self.pendingItemComplete.bind(self));
310 this.selectionModel = new ListSingleSelectionModel();
311 this.inProgramSelection_ = false;
315 * Should only be queried from the 'change' event listener, true if the
316 * change event was triggered by a programmatical selection change.
319 get inProgramSelection() {
320 return this.inProgramSelection_;
324 * Set index to the image selected.
325 * @type {number} index The index of selected image.
327 set selectedItemIndex(index) {
328 this.inProgramSelection_ = true;
329 this.selectionModel.selectedIndex = index;
330 this.inProgramSelection_ = false;
335 * @type {!Object} Wallpaper information inserted into the data model.
338 var index = this.selectionModel.selectedIndex;
339 return index != -1 ? this.dataModel.item(index) : null;
341 set selectedItem(selectedItem) {
342 var index = this.dataModel.indexOf(selectedItem);
343 this.inProgramSelection_ = true;
344 this.selectionModel.leadIndex = index;
345 this.selectionModel.selectedIndex = index;
346 this.inProgramSelection_ = false;
350 * Forces re-display, size re-calculation and focuses grid.
352 updateAndFocus: function() {
353 // Recalculate the measured item size.
354 this.measured_ = null;
361 * Shows a checkmark on the active thumbnail and clears previous active one
362 * if any. Note if wallpaper was not set successfully, checkmark should not
363 * show on that thumbnail.
365 updateActiveThumb_: function() {
366 var selectedGridItem = this.getListItem(this.activeItem_);
367 if (this.checkmark_.parentNode &&
368 this.checkmark_.parentNode == selectedGridItem) {
372 // Clears previous checkmark.
373 if (this.checkmark_.parentNode)
374 this.checkmark_.parentNode.removeChild(this.checkmark_);
376 if (!selectedGridItem)
378 selectedGridItem.appendChild(this.checkmark_);
382 * Redraws the viewport.
385 Grid.prototype.redraw.call(this);
386 // The active thumbnail maybe deleted in the above redraw(). Sets it again
387 // to make sure checkmark shows correctly.
388 this.updateActiveThumb_();
393 WallpaperThumbnailsGrid: WallpaperThumbnailsGrid