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.
6 * @fileoverview Oobe user image screen implementation.
9 cr.define('login', function() {
10 var UserImagesGrid = options.UserImagesGrid;
11 var ButtonImages = UserImagesGrid.ButtonImages;
14 * Array of button URLs used on this page.
15 * @type {Array.<string>}
18 var ButtonImageUrls = [
19 ButtonImages.TAKE_PHOTO
23 * Whether the web camera item should be preselected, if available.
27 var PRESELECT_CAMERA = false;
30 * Creates a new OOBE screen div.
32 * @extends {HTMLDivElement}
34 var UserImageScreen = cr.ui.define(login.Screen);
37 * Registers with Oobe.
38 * @param {boolean} lazyInit If true, screen is decorated on first show.
40 UserImageScreen.register = function(lazyInit) {
41 var screen = $('user-image');
43 screen.__proto__ = UserImageScreen.prototype;
44 screen.deferredDecorate = function() {
45 UserImageScreen.decorate(screen);
48 UserImageScreen.decorate(screen);
50 Oobe.getInstance().registerScreen(screen);
53 UserImageScreen.prototype = {
54 __proto__: login.Screen.prototype,
57 * Currently selected user image index (take photo button is with zero
61 selectedUserImage_: -1,
64 * Indicates if profile picture should be displayed on current screen.
66 profilePictureEnabled_: false,
69 * URL for profile picture.
71 profileImageUrl_: null,
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;
115 $('user-image-stream-crop').addEventListener(
116 'webkitTransitionEnd', function(e) {
117 previewElement.classList.remove('animation');
119 $('user-image-preview-img').addEventListener(
120 'webkitTransitionEnd', function(e) {
121 previewElement.classList.remove('animation');
124 this.updateLocalizedContent();
126 chrome.send('getImages');
130 * Header text of the screen.
134 return loadTimeData.getString('userImageScreenTitle');
138 * Buttons in oobe wizard's button strip.
139 * @type {array} Array of 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));
150 * The caption to use for the Profile image preview.
153 get profileImageCaption() {
154 return this.profileImageCaption_;
156 set profileImageCaption(value) {
157 this.profileImageCaption_ = value;
158 this.updateCaption_();
162 * True if the Profile image is being loaded.
165 get profileImageLoading() {
166 return this.profileImageLoading_;
168 set profileImageLoading(value) {
169 this.profileImageLoading_ = value;
170 $('user-image-screen-main').classList[
171 value ? 'add' : 'remove']('profile-image-loading');
172 this.updateProfileImageCaption_();
176 * Handles image activation (by pressing Enter).
179 handleImageActivated_: function() {
180 switch ($('user-image-grid').selectedItemUrl) {
181 case ButtonImages.TAKE_PHOTO:
182 this.handleTakePhoto_();
191 * Handles selection change.
192 * @param {Event} e Selection change event.
195 handleSelect_: function(e) {
196 var imageGrid = $('user-image-grid');
197 $('ok-button').disabled = false;
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;
207 imageGrid.previewElement.classList.add('phototaken');
208 this.notifyImageSelected_();
211 imageGrid.previewElement.classList.remove('phototaken');
212 $('flip-photo').tabIndex = -1;
213 this.notifyImageSelected_();
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(
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;
229 $('user-image-preview-img').classList.toggle('animated-transform',
231 imageGrid.stopCamera();
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';
245 * Handle photo capture from the live camera stream.
247 handleTakePhoto_: function(e) {
248 $('user-image-grid').takePhoto();
252 * Handle photo captured event.
253 * @param {Event} e Event with 'dataURL' property containing a data URL.
255 handlePhotoTaken_: function(e) {
256 chrome.send('photoTaken', [e.dataURL]);
257 this.announceAccessibleMessage_(
258 loadTimeData.getString('photoCaptureAccessibleText'));
262 * Handle photo updated event.
263 * @param {Event} e Event with 'dataURL' property containing a data URL.
265 handlePhotoUpdated_: function(e) {
266 chrome.send('photoTaken', [e.dataURL]);
270 * Handle discarding the captured photo.
272 handleDiscardPhoto_: function(e) {
273 var imageGrid = $('user-image-grid');
274 imageGrid.discardPhoto();
275 this.announceAccessibleMessage_(
276 loadTimeData.getString('photoDiscardAccessibleText'));
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.
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);
298 * Event handler that is invoked just before the screen is shown.
299 * @param {object} data Screen init payload.
301 onBeforeShow: function(data) {
302 Oobe.getInstance().headerHidden = true;
303 var imageGrid = $('user-image-grid');
304 imageGrid.updateAndFocus();
305 chrome.send('onUserImageScreenShown');
309 * Event handler that is invoked just before the screen is hidden.
311 onBeforeHide: function() {
312 $('user-image-grid').stopCamera();
316 * Accepts currently selected image, if possible.
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');
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.
335 setProfileImage_: function(imageUrl) {
336 this.profileImageLoading = false;
337 this.profileImageUrl_ = imageUrl;
338 if (imageUrl !== null) {
340 $('user-image-grid').updateItem(this.profileImage_, imageUrl);
345 * @param {boolean} present Whether camera is detected.
347 setCameraPresent_: function(present) {
348 $('user-image-grid').cameraPresent = present;
352 * Controls the profile image as one of image options.
353 * @param {enabled} Whether profile image option should be displayed.
356 setProfilePictureEnabled_: function(enabled) {
357 if (this.profilePictureEnabled_ == enabled)
359 this.profilePictureEnabled_ = enabled;
360 var imageGrid = $('user-image-grid');
362 var url = ButtonImages.PROFILE_PICTURE;
363 if (!this.profileImageLoading && this.profileImageUrl_ !== null) {
364 url = this.profileImageUrl_;
366 // Profile image data (if present).
367 this.profileImage_ = imageGrid.addItem(
369 loadTimeData.getString('profilePhoto'), // Title.
370 undefined, // Click handler.
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';
382 this.profileImage_.type = 'profile';
384 imageGrid.removeItem(this.profileImage_);
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.
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 || '';
402 chrome.send('screenReady');
406 * Selects user image with the given URL.
407 * @param {string} url URL of the image to select.
410 setSelectedImage_: function(url) {
411 var imageGrid = $('user-image-grid');
412 imageGrid.selectedItemUrl = url;
417 * Hides curtain with spinner.
420 hideCurtain_: function() {
421 this.classList.remove('loading');
422 Oobe.getInstance().updateScreenSize(this);
426 * Updates the image preview caption.
429 updateCaption_: function() {
430 $('user-image-preview-caption').textContent =
431 $('user-image-grid').selectionType == 'profile' ?
432 this.profileImageCaption : '';
436 * Updates localized content of the screen that is not updated via template.
438 updateLocalizedContent: function() {
439 this.updateProfileImageCaption_();
443 * Updates profile image caption.
446 updateProfileImageCaption_: function() {
447 this.profileImageCaption = loadTimeData.getString(
448 this.profileImageLoading_ ? 'profilePhotoLoading' : 'profilePhoto');
452 * Notifies chrome about image selection.
455 notifyImageSelected_: function() {
456 var imageGrid = $('user-image-grid');
457 chrome.send('selectImage',
458 [imageGrid.selectedItemUrl,
459 imageGrid.selectionType,
460 !imageGrid.inProgramSelection]);
464 // Forward public APIs to private implementations.
468 'setProfilePictureEnabled',
472 ].forEach(function(name) {
473 UserImageScreen[name] = function(value) {
474 $('user-image')[name + '_'](value);
479 UserImageScreen: UserImageScreen