Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / chromeos / wallpaper_manager / js / wallpaper_images_grid.js
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.
4
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;
13
14   /**
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
20    *     model.
21    * @param {number} dataModelId A unique ID that this item associated to.
22    * @param {function} callback The callback function when decoration finished.
23    * @constructor
24    * @extends {cr.ui.GridItem}
25    */
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;
31     return el;
32   }
33
34   WallpaperThumbnailsGridItem.prototype = {
35     __proto__: GridItem.prototype,
36
37     /**
38      * The unique ID this thumbnail grid associated to.
39      * @type {number}
40      */
41     dataModelId: null,
42
43     /**
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.
47      * @type {function}
48      */
49     callback: null,
50
51     /** @override */
52     decorate: function() {
53       GridItem.prototype.decorate.call(this);
54       // Removes garbage created by GridItem.
55       this.innerText = '';
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);
61       var self = this;
62
63       switch (this.dataItem.source) {
64         case Constants.WallpaperSourceEnum.AddNew:
65           this.id = 'add-new';
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;
70           });
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);
74           break;
75         case Constants.WallpaperSourceEnum.Custom:
76           var errorHandler = function(e) {
77             self.callback(self.dataModelId);
78             console.error('Can not access file system.');
79           };
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);
85             };
86             var fallback = function() {
87               wallpaperDirectories.getDirectory(
88                   Constants.WallpaperDirNameEnum.ORIGINAL, function(dirEntry) {
89                 dirEntry.getFile(fileName, {create: false}, setURL,
90                                  errorHandler);
91               }, errorHandler);
92             };
93             var success = function(dirEntry) {
94               dirEntry.getFile(fileName, {create: false}, setURL, fallback);
95             };
96             wallpaperDirectories.getDirectory(
97                Constants.WallpaperDirNameEnum.THUMBNAIL, success, errorHandler);
98           };
99           getThumbnail(self.dataItem.baseURL);
100           break;
101         case Constants.WallpaperSourceEnum.OEM:
102         case Constants.WallpaperSourceEnum.Online:
103           chrome.wallpaperPrivate.getThumbnail(this.dataItem.baseURL,
104                                                this.dataItem.source,
105                                                function(data) {
106             if (data) {
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);
113               });
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';
119               xhr.send(null);
120               xhr.addEventListener('load', function(e) {
121                 if (xhr.status === 200) {
122                   chrome.wallpaperPrivate.saveThumbnail(self.dataItem.baseURL,
123                                                         xhr.response);
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
129                   // better.
130                   imageEl.addEventListener('load', function(e) {
131                     self.callback(self.dataModelId);
132                     window.URL.revokeObjectURL(this.src);
133                   });
134                 } else {
135                   self.callback(self.dataModelId);
136                 }
137               });
138             }
139           });
140           break;
141         default:
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);
146       }
147     },
148   };
149
150   /**
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
154    *     interact with.
155    * @param {cr.ui.Grid} grid The grid to interact with.
156    * @constructor
157    * @extends {cr.ui.GridSelectionController}
158    */
159   function WallpaperThumbnailsGridSelectionController(selectionModel, grid) {
160     GridSelectionController.call(this, selectionModel, grid);
161   }
162
163   WallpaperThumbnailsGridSelectionController.prototype = {
164     __proto__: GridSelectionController.prototype,
165
166     /** @override */
167     getIndexBefore: function(index) {
168       var result =
169           GridSelectionController.prototype.getIndexBefore.call(this, index);
170       return result == -1 ? this.getLastIndex() : result;
171     },
172
173     /** @override */
174     getIndexAfter: function(index) {
175       var result =
176           GridSelectionController.prototype.getIndexAfter.call(this, index);
177       return result == -1 ? this.getFirstIndex() : result;
178     },
179
180     /** @override */
181     handleKeyDown: function(e) {
182       if (e.keyIdentifier == 'Enter')
183         cr.dispatchSimpleEvent(this.grid_, 'activate');
184       else
185         GridSelectionController.prototype.handleKeyDown.call(this, e);
186     },
187   };
188
189   /**
190    * Creates a new user images grid element.
191    * @param {Object=} opt_propertyBag Optional properties.
192    * @constructor
193    * @extends {cr.ui.Grid}
194    */
195   var WallpaperThumbnailsGrid = cr.ui.define('grid');
196
197   WallpaperThumbnailsGrid.prototype = {
198     __proto__: Grid.prototype,
199
200     /**
201      * The checkbox element.
202      */
203     checkmark_: undefined,
204
205     /**
206      * ID of spinner delay timer.
207      * @private
208      */
209     spinnerTimeout_: 0,
210
211     /**
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.
217      */
218     activeItem_: undefined,
219     set activeItem(activeItem) {
220       if (this.activeItem_ != activeItem) {
221         this.activeItem_ = activeItem;
222         this.updateActiveThumb_();
223       }
224     },
225
226     /**
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.
229      */
230     dataModelId_: 0,
231
232     /**
233      * The number of items that need to be generated after a new dataModel is
234      * set.
235      */
236     pendingItems_: 0,
237
238     /** @override */
239     set dataModel(dataModel) {
240       if (this.dataModel_ == dataModel)
241         return;
242
243       if (dataModel && dataModel.length != 0) {
244         this.dataModelId_++;
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;
248
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);
256         }
257       } else {
258         // Sets dataModel to null should hide spinner immedidately.
259         $('spinner-container').hidden = true;
260       }
261
262       var parentSetter = cr.ui.Grid.prototype.__lookupSetter__('dataModel');
263       parentSetter.call(this, dataModel);
264     },
265
266     get dataModel() {
267       return this.dataModel_;
268     },
269
270     /** @override */
271     createSelectionController: function(sm) {
272       return new WallpaperThumbnailsGridSelectionController(sm, this);
273     },
274
275     /**
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
280      *     associated to.
281      */
282     pendingItemComplete: function(dataModelId) {
283       if (dataModelId != this.dataModelId_)
284         return;
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;
291       }
292     },
293
294     /** @override */
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([]);
303       var self = this;
304       this.itemConstructor = function(value) {
305         var dataModelId = self.dataModelId_;
306         self.pendingItems_++;
307         return WallpaperThumbnailsGridItem(value, dataModelId,
308             self.pendingItemComplete.bind(self));
309       };
310       this.selectionModel = new ListSingleSelectionModel();
311       this.inProgramSelection_ = false;
312     },
313
314     /**
315      * Should only be queried from the 'change' event listener, true if the
316      * change event was triggered by a programmatical selection change.
317      * @type {boolean}
318      */
319     get inProgramSelection() {
320       return this.inProgramSelection_;
321     },
322
323     /**
324      * Set index to the image selected.
325      * @type {number} index The index of selected image.
326      */
327     set selectedItemIndex(index) {
328       this.inProgramSelection_ = true;
329       this.selectionModel.selectedIndex = index;
330       this.inProgramSelection_ = false;
331     },
332
333     /**
334      * The selected item.
335      * @type {!Object} Wallpaper information inserted into the data model.
336      */
337     get selectedItem() {
338       var index = this.selectionModel.selectedIndex;
339       return index != -1 ? this.dataModel.item(index) : null;
340     },
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;
347     },
348
349     /**
350      * Forces re-display, size re-calculation and focuses grid.
351      */
352     updateAndFocus: function() {
353       // Recalculate the measured item size.
354       this.measured_ = null;
355       this.columns = 0;
356       this.redraw();
357       this.focus();
358     },
359
360     /**
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.
364      */
365     updateActiveThumb_: function() {
366       var selectedGridItem = this.getListItem(this.activeItem_);
367       if (this.checkmark_.parentNode &&
368           this.checkmark_.parentNode == selectedGridItem) {
369         return;
370       }
371
372       // Clears previous checkmark.
373       if (this.checkmark_.parentNode)
374         this.checkmark_.parentNode.removeChild(this.checkmark_);
375
376       if (!selectedGridItem)
377         return;
378       selectedGridItem.appendChild(this.checkmark_);
379     },
380
381     /**
382      * Redraws the viewport.
383      */
384     redraw: function() {
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_();
389     }
390   };
391
392   return {
393     WallpaperThumbnailsGrid: WallpaperThumbnailsGrid
394   };
395 });