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