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