• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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