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 5cr.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 13 /** 14 * Creates a new wallpaper thumbnails grid item. 15 * @param {{baseURL: string, layout: string, source: string, 16 * availableOffline: boolean, opt_dynamicURL: string, 17 * opt_author: string, opt_authorWebsite: string}} 18 * wallpaperInfo Wallpaper data item in WallpaperThumbnailsGrid's data 19 * model. 20 * @constructor 21 * @extends {cr.ui.GridItem} 22 */ 23 function WallpaperThumbnailsGridItem(wallpaperInfo) { 24 var el = new GridItem(wallpaperInfo); 25 el.__proto__ = WallpaperThumbnailsGridItem.prototype; 26 return el; 27 } 28 29 WallpaperThumbnailsGridItem.prototype = { 30 __proto__: GridItem.prototype, 31 32 /** @override */ 33 decorate: function() { 34 GridItem.prototype.decorate.call(this); 35 // Removes garbage created by GridItem. 36 this.innerText = ''; 37 var imageEl = cr.doc.createElement('img'); 38 imageEl.classList.add('thumbnail'); 39 cr.defineProperty(imageEl, 'offline', cr.PropertyKind.BOOL_ATTR); 40 imageEl.offline = this.dataItem.availableOffline; 41 this.appendChild(imageEl); 42 var self = this; 43 44 switch (this.dataItem.source) { 45 case Constants.WallpaperSourceEnum.AddNew: 46 this.id = 'add-new'; 47 this.addEventListener('click', function(e) { 48 $('wallpaper-selection-container').hidden = false; 49 }); 50 break; 51 case Constants.WallpaperSourceEnum.Custom: 52 var errorHandler = function(e) { 53 console.error('Can not access file system.'); 54 }; 55 var wallpaperDirectories = WallpaperDirectories.getInstance(); 56 var getThumbnail = function(fileName) { 57 var setURL = function(fileEntry) { 58 imageEl.src = fileEntry.toURL(); 59 }; 60 var fallback = function() { 61 wallpaperDirectories.getDirectory(WallpaperDirNameEnum.ORIGINAL, 62 function(dirEntry) { 63 dirEntry.getFile(fileName, {create: false}, setURL, 64 errorHandler); 65 }, errorHandler); 66 }; 67 var success = function(dirEntry) { 68 dirEntry.getFile(fileName, {create: false}, setURL, fallback); 69 }; 70 wallpaperDirectories.getDirectory(WallpaperDirNameEnum.THUMBNAIL, 71 success, 72 errorHandler); 73 } 74 getThumbnail(self.dataItem.baseURL); 75 break; 76 case Constants.WallpaperSourceEnum.OEM: 77 case Constants.WallpaperSourceEnum.Online: 78 chrome.wallpaperPrivate.getThumbnail(this.dataItem.baseURL, 79 this.dataItem.source, 80 function(data) { 81 if (data) { 82 var blob = new Blob([new Int8Array(data)], 83 {'type': 'image\/png'}); 84 imageEl.src = window.URL.createObjectURL(blob); 85 imageEl.addEventListener('load', function(e) { 86 window.URL.revokeObjectURL(this.src); 87 }); 88 } else if (self.dataItem.source == 89 Constants.WallpaperSourceEnum.Online) { 90 var xhr = new XMLHttpRequest(); 91 xhr.open('GET', self.dataItem.baseURL + ThumbnailSuffix, true); 92 xhr.responseType = 'arraybuffer'; 93 xhr.send(null); 94 xhr.addEventListener('load', function(e) { 95 if (xhr.status === 200) { 96 chrome.wallpaperPrivate.saveThumbnail(self.dataItem.baseURL, 97 xhr.response); 98 var blob = new Blob([new Int8Array(xhr.response)], 99 {'type' : 'image\/png'}); 100 imageEl.src = window.URL.createObjectURL(blob); 101 // TODO(bshe): We currently use empty div to reserve space for 102 // thumbnail. Use a placeholder like "loading" image may 103 // better. 104 imageEl.addEventListener('load', function(e) { 105 window.URL.revokeObjectURL(this.src); 106 }); 107 } 108 }); 109 } 110 }); 111 break; 112 default: 113 console.error('Unsupported image source.'); 114 } 115 }, 116 }; 117 118 /** 119 * Creates a selection controller that wraps selection on grid ends 120 * and translates Enter presses into 'activate' events. 121 * @param {cr.ui.ListSelectionModel} selectionModel The selection model to 122 * interact with. 123 * @param {cr.ui.Grid} grid The grid to interact with. 124 * @constructor 125 * @extends {cr.ui.GridSelectionController} 126 */ 127 function WallpaperThumbnailsGridSelectionController(selectionModel, grid) { 128 GridSelectionController.call(this, selectionModel, grid); 129 } 130 131 WallpaperThumbnailsGridSelectionController.prototype = { 132 __proto__: GridSelectionController.prototype, 133 134 /** @override */ 135 getIndexBefore: function(index) { 136 var result = 137 GridSelectionController.prototype.getIndexBefore.call(this, index); 138 return result == -1 ? this.getLastIndex() : result; 139 }, 140 141 /** @override */ 142 getIndexAfter: function(index) { 143 var result = 144 GridSelectionController.prototype.getIndexAfter.call(this, index); 145 return result == -1 ? this.getFirstIndex() : result; 146 }, 147 148 /** @override */ 149 handleKeyDown: function(e) { 150 if (e.keyIdentifier == 'Enter') 151 cr.dispatchSimpleEvent(this.grid_, 'activate'); 152 else 153 GridSelectionController.prototype.handleKeyDown.call(this, e); 154 }, 155 }; 156 157 /** 158 * Creates a new user images grid element. 159 * @param {Object=} opt_propertyBag Optional properties. 160 * @constructor 161 * @extends {cr.ui.Grid} 162 */ 163 var WallpaperThumbnailsGrid = cr.ui.define('grid'); 164 165 WallpaperThumbnailsGrid.prototype = { 166 __proto__: Grid.prototype, 167 168 /** 169 * The checkbox element. 170 */ 171 checkmark_: undefined, 172 173 /** 174 * The item in data model which should have a checkmark. 175 * @type {{baseURL: string, dynamicURL: string, layout: string, 176 * author: string, authorWebsite: string, 177 * availableOffline: boolean}} 178 * wallpaperInfo The information of the wallpaper to be set active. 179 */ 180 activeItem_: undefined, 181 set activeItem(activeItem) { 182 if (this.activeItem_ != activeItem) { 183 this.activeItem_ = activeItem; 184 this.updateActiveThumb_(); 185 } 186 }, 187 188 /** @override */ 189 createSelectionController: function(sm) { 190 return new WallpaperThumbnailsGridSelectionController(sm, this); 191 }, 192 193 /** @override */ 194 decorate: function() { 195 Grid.prototype.decorate.call(this); 196 // checkmark_ needs to be initialized before set data model. Otherwise, we 197 // may try to access checkmark before initialization in 198 // updateActiveThumb_(). 199 this.checkmark_ = cr.doc.createElement('div'); 200 this.checkmark_.classList.add('check'); 201 this.dataModel = new ArrayDataModel([]); 202 this.itemConstructor = WallpaperThumbnailsGridItem; 203 this.selectionModel = new ListSingleSelectionModel(); 204 this.inProgramSelection_ = false; 205 }, 206 207 /** 208 * Should only be queried from the 'change' event listener, true if the 209 * change event was triggered by a programmatical selection change. 210 * @type {boolean} 211 */ 212 get inProgramSelection() { 213 return this.inProgramSelection_; 214 }, 215 216 /** 217 * Set index to the image selected. 218 * @type {number} index The index of selected image. 219 */ 220 set selectedItemIndex(index) { 221 this.inProgramSelection_ = true; 222 this.selectionModel.selectedIndex = index; 223 this.inProgramSelection_ = false; 224 }, 225 226 /** 227 * The selected item. 228 * @type {!Object} Wallpaper information inserted into the data model. 229 */ 230 get selectedItem() { 231 var index = this.selectionModel.selectedIndex; 232 return index != -1 ? this.dataModel.item(index) : null; 233 }, 234 set selectedItem(selectedItem) { 235 var index = this.dataModel.indexOf(selectedItem); 236 this.inProgramSelection_ = true; 237 this.selectionModel.leadIndex = index; 238 this.selectionModel.selectedIndex = index; 239 this.inProgramSelection_ = false; 240 }, 241 242 /** 243 * Forces re-display, size re-calculation and focuses grid. 244 */ 245 updateAndFocus: function() { 246 // Recalculate the measured item size. 247 this.measured_ = null; 248 this.columns = 0; 249 this.redraw(); 250 this.focus(); 251 }, 252 253 /** 254 * Shows a checkmark on the active thumbnail and clears previous active one 255 * if any. Note if wallpaper was not set successfully, checkmark should not 256 * show on that thumbnail. 257 */ 258 updateActiveThumb_: function() { 259 var selectedGridItem = this.getListItem(this.activeItem_); 260 if (this.checkmark_.parentNode && 261 this.checkmark_.parentNode == selectedGridItem) { 262 return; 263 } 264 265 // Clears previous checkmark. 266 if (this.checkmark_.parentNode) 267 this.checkmark_.parentNode.removeChild(this.checkmark_); 268 269 if (!selectedGridItem) 270 return; 271 selectedGridItem.appendChild(this.checkmark_); 272 }, 273 274 /** 275 * Redraws the viewport. 276 */ 277 redraw: function() { 278 Grid.prototype.redraw.call(this); 279 // The active thumbnail maybe deleted in the above redraw(). Sets it again 280 // to make sure checkmark shows correctly. 281 this.updateActiveThumb_(); 282 } 283 }; 284 285 return { 286 WallpaperThumbnailsGrid: WallpaperThumbnailsGrid 287 }; 288}); 289