1// Copyright (c) 2012 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/** 6 * @fileoverview Oobe user image screen implementation. 7 */ 8 9cr.define('login', function() { 10 var UserImagesGrid = options.UserImagesGrid; 11 var ButtonImages = UserImagesGrid.ButtonImages; 12 13 /** 14 * Array of button URLs used on this page. 15 * @type {Array.<string>} 16 * @const 17 */ 18 var ButtonImageUrls = [ 19 ButtonImages.TAKE_PHOTO 20 ]; 21 22 /** 23 * Whether the web camera item should be preselected, if available. 24 * @type {boolean} 25 * @const 26 */ 27 var PRESELECT_CAMERA = false; 28 29 /** 30 * Creates a new OOBE screen div. 31 * @constructor 32 * @extends {HTMLDivElement} 33 */ 34 var UserImageScreen = cr.ui.define(login.Screen); 35 36 /** 37 * Registers with Oobe. 38 * @param {boolean} lazyInit If true, screen is decorated on first show. 39 */ 40 UserImageScreen.register = function(lazyInit) { 41 var screen = $('user-image'); 42 if (lazyInit) { 43 screen.__proto__ = UserImageScreen.prototype; 44 screen.deferredDecorate = function() { 45 UserImageScreen.decorate(screen); 46 }; 47 } else { 48 UserImageScreen.decorate(screen); 49 } 50 Oobe.getInstance().registerScreen(screen); 51 }; 52 53 UserImageScreen.prototype = { 54 __proto__: login.Screen.prototype, 55 56 /** 57 * Currently selected user image index (take photo button is with zero 58 * index). 59 * @type {number} 60 */ 61 selectedUserImage_: -1, 62 63 /** 64 * URL for profile picture. 65 */ 66 profileImageUrl_: null, 67 68 /** @override */ 69 decorate: function(element) { 70 var imageGrid = $('user-image-grid'); 71 UserImagesGrid.decorate(imageGrid); 72 73 // Preview image will track the selected item's URL. 74 var previewElement = $('user-image-preview'); 75 previewElement.oncontextmenu = function(e) { e.preventDefault(); }; 76 77 imageGrid.previewElement = previewElement; 78 imageGrid.selectionType = 'default'; 79 imageGrid.flipPhotoElement = $('flip-photo'); 80 81 imageGrid.addEventListener('select', 82 this.handleSelect_.bind(this)); 83 imageGrid.addEventListener('activate', 84 this.handleImageActivated_.bind(this)); 85 imageGrid.addEventListener('phototaken', 86 this.handlePhotoTaken_.bind(this)); 87 imageGrid.addEventListener('photoupdated', 88 this.handlePhotoUpdated_.bind(this)); 89 90 // Set the title for camera item in the grid. 91 imageGrid.setCameraTitles( 92 loadTimeData.getString('takePhoto'), 93 loadTimeData.getString('photoFromCamera')); 94 95 this.profileImageLoading = true; 96 97 // Profile image data (if present). 98 this.profileImage_ = imageGrid.addItem( 99 ButtonImages.PROFILE_PICTURE, // Image URL. 100 loadTimeData.getString('profilePhoto'), // Title. 101 undefined, // Click handler. 102 0, // Position. 103 function(el) { 104 // Custom decorator for Profile image element. 105 var spinner = el.ownerDocument.createElement('div'); 106 spinner.className = 'spinner'; 107 var spinnerBg = el.ownerDocument.createElement('div'); 108 spinnerBg.className = 'spinner-bg'; 109 spinnerBg.appendChild(spinner); 110 el.appendChild(spinnerBg); 111 el.id = 'profile-image'; 112 }); 113 this.profileImage_.type = 'profile'; 114 115 $('take-photo').addEventListener( 116 'click', this.handleTakePhoto_.bind(this)); 117 $('discard-photo').addEventListener( 118 'click', this.handleDiscardPhoto_.bind(this)); 119 120 // Toggle 'animation' class for the duration of WebKit transition. 121 $('flip-photo').addEventListener( 122 'click', this.handleFlipPhoto_.bind(this)); 123 $('user-image-stream-crop').addEventListener( 124 'webkitTransitionEnd', function(e) { 125 previewElement.classList.remove('animation'); 126 }); 127 $('user-image-preview-img').addEventListener( 128 'webkitTransitionEnd', function(e) { 129 previewElement.classList.remove('animation'); 130 }); 131 132 this.updateLocalizedContent(); 133 134 chrome.send('getImages'); 135 }, 136 137 /** 138 * Header text of the screen. 139 * @type {string} 140 */ 141 get header() { 142 return loadTimeData.getString('userImageScreenTitle'); 143 }, 144 145 /** 146 * Buttons in oobe wizard's button strip. 147 * @type {array} Array of Buttons. 148 */ 149 get buttons() { 150 var okButton = this.ownerDocument.createElement('button'); 151 okButton.id = 'ok-button'; 152 okButton.textContent = loadTimeData.getString('okButtonText'); 153 okButton.addEventListener('click', this.acceptImage_.bind(this)); 154 return [okButton]; 155 }, 156 157 /** 158 * The caption to use for the Profile image preview. 159 * @type {string} 160 */ 161 get profileImageCaption() { 162 return this.profileImageCaption_; 163 }, 164 set profileImageCaption(value) { 165 this.profileImageCaption_ = value; 166 this.updateCaption_(); 167 }, 168 169 /** 170 * True if the Profile image is being loaded. 171 * @type {boolean} 172 */ 173 get profileImageLoading() { 174 return this.profileImageLoading_; 175 }, 176 set profileImageLoading(value) { 177 this.profileImageLoading_ = value; 178 $('user-image-screen-main').classList.toggle('profile-image-loading', 179 value); 180 if (value) 181 announceAccessibleMessage(loadTimeData.getString('syncingPreferences')); 182 this.updateProfileImageCaption_(); 183 }, 184 185 /** 186 * Handles image activation (by pressing Enter). 187 * @private 188 */ 189 handleImageActivated_: function() { 190 switch ($('user-image-grid').selectedItemUrl) { 191 case ButtonImages.TAKE_PHOTO: 192 this.handleTakePhoto_(); 193 break; 194 default: 195 this.acceptImage_(); 196 break; 197 } 198 }, 199 200 /** 201 * Handles selection change. 202 * @param {Event} e Selection change event. 203 * @private 204 */ 205 handleSelect_: function(e) { 206 var imageGrid = $('user-image-grid'); 207 $('ok-button').disabled = false; 208 209 // Camera selection 210 if (imageGrid.selectionType == 'camera') { 211 $('flip-photo').tabIndex = 1; 212 // No current image selected. 213 if (imageGrid.cameraLive) { 214 imageGrid.previewElement.classList.remove('phototaken'); 215 $('ok-button').disabled = true; 216 } else { 217 imageGrid.previewElement.classList.add('phototaken'); 218 this.notifyImageSelected_(); 219 } 220 } else { 221 imageGrid.previewElement.classList.remove('phototaken'); 222 $('flip-photo').tabIndex = -1; 223 this.notifyImageSelected_(); 224 } 225 // Start/stop camera on (de)selection. 226 if (!imageGrid.inProgramSelection && 227 imageGrid.selectionType != e.oldSelectionType) { 228 if (imageGrid.selectionType == 'camera') { 229 // Programmatic selection of camera item is done in 230 // startCamera callback where streaming is started by itself. 231 imageGrid.startCamera( 232 function() { 233 // Start capture if camera is still the selected item. 234 $('user-image-preview-img').classList.toggle( 235 'animated-transform', true); 236 return imageGrid.selectedItem == imageGrid.cameraImage; 237 }); 238 } else { 239 $('user-image-preview-img').classList.toggle('animated-transform', 240 false); 241 imageGrid.stopCamera(); 242 } 243 } 244 this.updateCaption_(); 245 // Update image attribution text. 246 var image = imageGrid.selectedItem; 247 $('user-image-author-name').textContent = image.author; 248 $('user-image-author-website').textContent = image.website; 249 $('user-image-author-website').href = image.website; 250 $('user-image-attribution').style.visibility = 251 (image.author || image.website) ? 'visible' : 'hidden'; 252 }, 253 254 /** 255 * Handle camera-photo flip. 256 */ 257 handleFlipPhoto_: function() { 258 var imageGrid = $('user-image-grid'); 259 imageGrid.previewElement.classList.add('animation'); 260 imageGrid.flipPhoto = !imageGrid.flipPhoto; 261 var flipMessageId = imageGrid.flipPhoto ? 262 'photoFlippedAccessibleText' : 'photoFlippedBackAccessibleText'; 263 announceAccessibleMessage(loadTimeData.getString(flipMessageId)); 264 }, 265 266 /** 267 * Handle photo capture from the live camera stream. 268 */ 269 handleTakePhoto_: function(e) { 270 $('user-image-grid').takePhoto(); 271 chrome.send('takePhoto'); 272 }, 273 274 /** 275 * Handle photo captured event. 276 * @param {Event} e Event with 'dataURL' property containing a data URL. 277 */ 278 handlePhotoTaken_: function(e) { 279 chrome.send('photoTaken', [e.dataURL]); 280 announceAccessibleMessage( 281 loadTimeData.getString('photoCaptureAccessibleText')); 282 }, 283 284 /** 285 * Handle photo updated event. 286 * @param {Event} e Event with 'dataURL' property containing a data URL. 287 */ 288 handlePhotoUpdated_: function(e) { 289 chrome.send('photoTaken', [e.dataURL]); 290 }, 291 292 /** 293 * Handle discarding the captured photo. 294 */ 295 handleDiscardPhoto_: function(e) { 296 var imageGrid = $('user-image-grid'); 297 imageGrid.discardPhoto(); 298 chrome.send('discardPhoto'); 299 announceAccessibleMessage( 300 loadTimeData.getString('photoDiscardAccessibleText')); 301 }, 302 303 /** 304 * Event handler that is invoked just before the screen is shown. 305 * @param {object} data Screen init payload. 306 */ 307 onBeforeShow: function(data) { 308 Oobe.getInstance().headerHidden = true; 309 var imageGrid = $('user-image-grid'); 310 imageGrid.updateAndFocus(); 311 chrome.send('onUserImageScreenShown'); 312 }, 313 314 /** 315 * Event handler that is invoked just before the screen is hidden. 316 */ 317 onBeforeHide: function() { 318 $('user-image-grid').stopCamera(); 319 }, 320 321 /** 322 * Accepts currently selected image, if possible. 323 * @private 324 */ 325 acceptImage_: function() { 326 var okButton = $('ok-button'); 327 if (!okButton.disabled) { 328 // This ensures that #ok-button won't be re-enabled again. 329 $('user-image-grid').disabled = true; 330 okButton.disabled = true; 331 chrome.send('onUserImageAccepted'); 332 } 333 }, 334 335 /** 336 * Updates user profile image. 337 * @param {?string} imageUrl Image encoded as data URL. If null, user has 338 * the default profile image, which we don't want to show. 339 * @private 340 */ 341 setProfileImage_: function(imageUrl) { 342 this.profileImageLoading = false; 343 this.profileImageUrl_ = imageUrl; 344 if (imageUrl !== null) { 345 this.profileImage_ = 346 $('user-image-grid').updateItem(this.profileImage_, imageUrl); 347 } 348 }, 349 350 /** 351 * @param {boolean} present Whether camera is detected. 352 */ 353 setCameraPresent_: function(present) { 354 $('user-image-grid').cameraPresent = present; 355 }, 356 357 /** 358 * Controls the profile image as one of image options. 359 * @param {enabled} Whether profile image option should be displayed. 360 * @private 361 */ 362 setProfilePictureEnabled_: function(enabled) { 363 var imageGrid = $('user-image-grid'); 364 if (enabled) { 365 } else { 366 imageGrid.removeItem(this.profileImage_); 367 } 368 }, 369 370 /** 371 * Appends default images to the image grid. Should only be called once. 372 * @param {Array.<{url: string, author: string, website: string}>} images 373 * An array of default images data, including URL, author and website. 374 * @private 375 */ 376 setDefaultImages_: function(imagesData) { 377 var imageGrid = $('user-image-grid'); 378 for (var i = 0, data; data = imagesData[i]; i++) { 379 var item = imageGrid.addItem(data.url, data.title); 380 item.type = 'default'; 381 item.author = data.author || ''; 382 item.website = data.website || ''; 383 } 384 chrome.send('screenReady'); 385 }, 386 387 /** 388 * Selects user image with the given URL. 389 * @param {string} url URL of the image to select. 390 * @private 391 */ 392 setSelectedImage_: function(url) { 393 var imageGrid = $('user-image-grid'); 394 imageGrid.selectedItemUrl = url; 395 imageGrid.focus(); 396 }, 397 398 /** 399 * Hides curtain with spinner. 400 * @private 401 */ 402 hideCurtain_: function() { 403 this.classList.remove('loading'); 404 Oobe.getInstance().updateScreenSize(this); 405 }, 406 407 /** 408 * Updates the image preview caption. 409 * @private 410 */ 411 updateCaption_: function() { 412 $('user-image-preview-caption').textContent = 413 $('user-image-grid').selectionType == 'profile' ? 414 this.profileImageCaption : ''; 415 }, 416 417 /** 418 * Updates localized content of the screen that is not updated via template. 419 */ 420 updateLocalizedContent: function() { 421 this.updateProfileImageCaption_(); 422 }, 423 424 /** 425 * Updates profile image caption. 426 * @private 427 */ 428 updateProfileImageCaption_: function() { 429 this.profileImageCaption = loadTimeData.getString( 430 this.profileImageLoading_ ? 'profilePhotoLoading' : 'profilePhoto'); 431 }, 432 433 /** 434 * Notifies chrome about image selection. 435 * @private 436 */ 437 notifyImageSelected_: function() { 438 var imageGrid = $('user-image-grid'); 439 chrome.send('selectImage', 440 [imageGrid.selectedItemUrl, 441 imageGrid.selectionType, 442 !imageGrid.inProgramSelection]); 443 }, 444 }; 445 446 // Forward public APIs to private implementations. 447 cr.makePublic(UserImageScreen, [ 448 'setDefaultImages', 449 'setCameraPresent', 450 'setProfilePictureEnabled', 451 'setProfileImage', 452 'setSelectedImage', 453 'hideCurtain' 454 ], 'user-image'); 455 456 return { 457 UserImageScreen: UserImageScreen 458 }; 459}); 460 461