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(WallpaperDirNameEnum.ORIGINAL,
89 dirEntry.getFile(fileName, {create: false}, setURL,
93 var success = function(dirEntry) {
94 dirEntry.getFile(fileName, {create: false}, setURL, fallback);
96 wallpaperDirectories.getDirectory(WallpaperDirNameEnum.THUMBNAIL,
100 getThumbnail(self.dataItem.baseURL);
102 case Constants.WallpaperSourceEnum.OEM:
103 case Constants.WallpaperSourceEnum.Online:
104 chrome.wallpaperPrivate.getThumbnail(this.dataItem.baseURL,
105 this.dataItem.source,
108 var blob = new Blob([new Int8Array(data)],
109 {'type': 'image\/png'});
110 imageEl.src = window.URL.createObjectURL(blob);
111 imageEl.addEventListener('load', function(e) {
112 self.callback(self.dataModelId);
113 window.URL.revokeObjectURL(this.src);
115 } else if (self.dataItem.source ==
116 Constants.WallpaperSourceEnum.Online) {
117 var xhr = new XMLHttpRequest();
118 xhr.open('GET', self.dataItem.baseURL + ThumbnailSuffix, true);
119 xhr.responseType = 'arraybuffer';
121 xhr.addEventListener('load', function(e) {
122 if (xhr.status === 200) {
123 chrome.wallpaperPrivate.saveThumbnail(self.dataItem.baseURL,
125 var blob = new Blob([new Int8Array(xhr.response)],
126 {'type' : 'image\/png'});
127 imageEl.src = window.URL.createObjectURL(blob);
128 // TODO(bshe): We currently use empty div to reserve space for
129 // thumbnail. Use a placeholder like "loading" image may
131 imageEl.addEventListener('load', function(e) {
132 self.callback(self.dataModelId);
133 window.URL.revokeObjectURL(this.src);
136 self.callback(self.dataModelId);
143 console.error('Unsupported image source.');
144 // Delay dispatching the completion callback until all items have
145 // begun loading and are tracked.
146 window.setTimeout(this.callback.bind(this, this.dataModelId), 0);
152 * Creates a selection controller that wraps selection on grid ends
153 * and translates Enter presses into 'activate' events.
154 * @param {cr.ui.ListSelectionModel} selectionModel The selection model to
156 * @param {cr.ui.Grid} grid The grid to interact with.
158 * @extends {cr.ui.GridSelectionController}
160 function WallpaperThumbnailsGridSelectionController(selectionModel, grid) {
161 GridSelectionController.call(this, selectionModel, grid);
164 WallpaperThumbnailsGridSelectionController.prototype = {
165 __proto__: GridSelectionController.prototype,
168 getIndexBefore: function(index) {
170 GridSelectionController.prototype.getIndexBefore.call(this, index);
171 return result == -1 ? this.getLastIndex() : result;
175 getIndexAfter: function(index) {
177 GridSelectionController.prototype.getIndexAfter.call(this, index);
178 return result == -1 ? this.getFirstIndex() : result;
182 handleKeyDown: function(e) {
183 if (e.keyIdentifier == 'Enter')
184 cr.dispatchSimpleEvent(this.grid_, 'activate');
186 GridSelectionController.prototype.handleKeyDown.call(this, e);
191 * Creates a new user images grid element.
192 * @param {Object=} opt_propertyBag Optional properties.
194 * @extends {cr.ui.Grid}
196 var WallpaperThumbnailsGrid = cr.ui.define('grid');
198 WallpaperThumbnailsGrid.prototype = {
199 __proto__: Grid.prototype,
202 * The checkbox element.
204 checkmark_: undefined,
207 * ID of spinner delay timer.
213 * The item in data model which should have a checkmark.
214 * @type {{baseURL: string, dynamicURL: string, layout: string,
215 * author: string, authorWebsite: string,
216 * availableOffline: boolean}}
217 * wallpaperInfo The information of the wallpaper to be set active.
219 activeItem_: undefined,
220 set activeItem(activeItem) {
221 if (this.activeItem_ != activeItem) {
222 this.activeItem_ = activeItem;
223 this.updateActiveThumb_();
228 * A unique ID that assigned to each set dataModel operation. Note that this
229 * id wont increase if the new dataModel is null or empty.
234 * The number of items that need to be generated after a new dataModel is
240 set dataModel(dataModel) {
241 if (this.dataModel_ == dataModel)
244 if (dataModel && dataModel.length != 0) {
246 // Clears old pending items. The new pending items will be counted when
247 // item is constructed in function itemConstructor below.
248 this.pendingItems_ = 0;
250 this.style.visibility = 'hidden';
251 // If spinner is hidden, schedule to show the spinner after
252 // ShowSpinnerDelayMs delay. Otherwise, keep it spinning.
253 if ($('spinner-container').hidden) {
254 this.spinnerTimeout_ = window.setTimeout(function() {
255 $('spinner-container').hidden = false;
256 }, ShowSpinnerDelayMs);
259 // Sets dataModel to null should hide spinner immedidately.
260 $('spinner-container').hidden = true;
263 var parentSetter = cr.ui.Grid.prototype.__lookupSetter__('dataModel');
264 parentSetter.call(this, dataModel);
268 return this.dataModel_;
272 createSelectionController: function(sm) {
273 return new WallpaperThumbnailsGridSelectionController(sm, this);
277 * Check if new thumbnail grid finished loading. This reduces the count of
278 * remaining items to be loaded and when 0, shows the thumbnail grid. Note
279 * it does not reduce the count on a previous |dataModelId|.
280 * @param {number} dataModelId A unique ID that a thumbnail item is
283 pendingItemComplete: function(dataModelId) {
284 if (dataModelId != this.dataModelId_)
286 this.pendingItems_--;
287 if (this.pendingItems_ == 0) {
288 this.style.visibility = 'visible';
289 window.clearTimeout(this.spinnerTimeout_);
290 this.spinnerTimeout_ = 0;
291 $('spinner-container').hidden = true;
296 decorate: function() {
297 Grid.prototype.decorate.call(this);
298 // checkmark_ needs to be initialized before set data model. Otherwise, we
299 // may try to access checkmark before initialization in
300 // updateActiveThumb_().
301 this.checkmark_ = cr.doc.createElement('div');
302 this.checkmark_.classList.add('check');
303 this.dataModel = new ArrayDataModel([]);
305 this.itemConstructor = function(value) {
306 var dataModelId = self.dataModelId_;
307 self.pendingItems_++;
308 return WallpaperThumbnailsGridItem(value, dataModelId,
309 self.pendingItemComplete.bind(self));
311 this.selectionModel = new ListSingleSelectionModel();
312 this.inProgramSelection_ = false;
316 * Should only be queried from the 'change' event listener, true if the
317 * change event was triggered by a programmatical selection change.
320 get inProgramSelection() {
321 return this.inProgramSelection_;
325 * Set index to the image selected.
326 * @type {number} index The index of selected image.
328 set selectedItemIndex(index) {
329 this.inProgramSelection_ = true;
330 this.selectionModel.selectedIndex = index;
331 this.inProgramSelection_ = false;
336 * @type {!Object} Wallpaper information inserted into the data model.
339 var index = this.selectionModel.selectedIndex;
340 return index != -1 ? this.dataModel.item(index) : null;
342 set selectedItem(selectedItem) {
343 var index = this.dataModel.indexOf(selectedItem);
344 this.inProgramSelection_ = true;
345 this.selectionModel.leadIndex = index;
346 this.selectionModel.selectedIndex = index;
347 this.inProgramSelection_ = false;
351 * Forces re-display, size re-calculation and focuses grid.
353 updateAndFocus: function() {
354 // Recalculate the measured item size.
355 this.measured_ = null;
362 * Shows a checkmark on the active thumbnail and clears previous active one
363 * if any. Note if wallpaper was not set successfully, checkmark should not
364 * show on that thumbnail.
366 updateActiveThumb_: function() {
367 var selectedGridItem = this.getListItem(this.activeItem_);
368 if (this.checkmark_.parentNode &&
369 this.checkmark_.parentNode == selectedGridItem) {
373 // Clears previous checkmark.
374 if (this.checkmark_.parentNode)
375 this.checkmark_.parentNode.removeChild(this.checkmark_);
377 if (!selectedGridItem)
379 selectedGridItem.appendChild(this.checkmark_);
383 * Redraws the viewport.
386 Grid.prototype.redraw.call(this);
387 // The active thumbnail maybe deleted in the above redraw(). Sets it again
388 // to make sure checkmark shows correctly.
389 this.updateActiveThumb_();
394 WallpaperThumbnailsGrid: WallpaperThumbnailsGrid