OLD | NEW |
---|---|
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * @fileoverview Oobe user image screen implementation. | 6 * @fileoverview Oobe user image screen implementation. |
7 */ | 7 */ |
8 | 8 |
9 cr.define('oobe', function() { | 9 cr.define('oobe', function() { |
10 | |
11 var UserImagesGrid = options.UserImagesGrid; | 10 var UserImagesGrid = options.UserImagesGrid; |
12 var ButtonImages = UserImagesGrid.ButtonImages; | 11 var ButtonImages = UserImagesGrid.ButtonImages; |
13 | 12 |
14 /** | 13 /** |
15 * Array of button URLs used on this page. | 14 * Array of button URLs used on this page. |
16 * @type {Array.<string>} | 15 * @type {Array.<string>} |
16 * @const | |
17 */ | 17 */ |
18 const ButtonImageUrls = [ | 18 var ButtonImageUrls = [ |
19 ButtonImages.TAKE_PHOTO | 19 ButtonImages.TAKE_PHOTO |
20 ]; | 20 ]; |
21 | 21 |
22 /** | 22 /** |
23 * Creates a new oobe screen div. | 23 * Creates a new OOBE screen div. |
24 * @constructor | 24 * @constructor |
25 * @extends {HTMLDivElement} | 25 * @extends {HTMLDivElement} |
26 */ | 26 */ |
27 var UserImageScreen = cr.ui.define('div'); | 27 var UserImageScreen = cr.ui.define('div'); |
28 | 28 |
29 /** | 29 /** |
30 * Registers with Oobe. | 30 * Registers with Oobe. |
31 */ | 31 */ |
32 UserImageScreen.register = function() { | 32 UserImageScreen.register = function() { |
33 var screen = $('user-image'); | 33 var screen = $('user-image'); |
34 var isWebRTC = document.documentElement.getAttribute('camera') == 'webrtc'; | |
35 console.log('WebRTC: ' + isWebRTC); | |
Nikita (slow)
2012/06/07 15:26:55
Remove console output.
Ivan Korotkov
2012/06/09 14:19:01
Done.
| |
36 UserImageScreen.prototype = isWebRTC ? UserImageScreenWebRTCProto : | |
37 UserImageScreenOldProto; | |
34 UserImageScreen.decorate(screen); | 38 UserImageScreen.decorate(screen); |
35 Oobe.getInstance().registerScreen(screen); | 39 Oobe.getInstance().registerScreen(screen); |
36 }; | 40 }; |
37 | 41 |
38 UserImageScreen.prototype = { | 42 var UserImageScreenOldProto = { |
39 __proto__: HTMLDivElement.prototype, | 43 __proto__: HTMLDivElement.prototype, |
40 | 44 |
41 /** | 45 /** |
42 * Currently selected user image index (take photo button is with zero | 46 * Currently selected user image index (take photo button is with zero |
43 * index). | 47 * index). |
44 * @type {number} | 48 * @type {number} |
45 */ | 49 */ |
46 selectedUserImage_: -1, | 50 selectedUserImage_: -1, |
47 | 51 |
48 /** @inheritDoc */ | 52 /** @inheritDoc */ |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
95 | 99 |
96 /** | 100 /** |
97 * Buttons in oobe wizard's button strip. | 101 * Buttons in oobe wizard's button strip. |
98 * @type {array} Array of Buttons. | 102 * @type {array} Array of Buttons. |
99 */ | 103 */ |
100 get buttons() { | 104 get buttons() { |
101 var okButton = this.ownerDocument.createElement('button'); | 105 var okButton = this.ownerDocument.createElement('button'); |
102 okButton.id = 'ok-button'; | 106 okButton.id = 'ok-button'; |
103 okButton.textContent = localStrings.getString('okButtonText'); | 107 okButton.textContent = localStrings.getString('okButtonText'); |
104 okButton.addEventListener('click', this.acceptImage_.bind(this)); | 108 okButton.addEventListener('click', this.acceptImage_.bind(this)); |
105 return [ okButton ]; | 109 return [okButton]; |
106 }, | 110 }, |
107 | 111 |
108 /** | 112 /** |
109 * The caption to use for the Profile image preview. | 113 * The caption to use for the Profile image preview. |
110 * @type {string} | 114 * @type {string} |
111 */ | 115 */ |
112 get profileImageCaption() { | 116 get profileImageCaption() { |
113 return this.profileImageCaption_; | 117 return this.profileImageCaption_; |
114 }, | 118 }, |
115 set profileImageCaption(value) { | 119 set profileImageCaption(value) { |
(...skipping 199 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
315 * Updates the image preview caption. | 319 * Updates the image preview caption. |
316 * @private | 320 * @private |
317 */ | 321 */ |
318 updateCaption_: function() { | 322 updateCaption_: function() { |
319 $('user-image-preview-caption').textContent = | 323 $('user-image-preview-caption').textContent = |
320 this.profileImageSelected ? this.profileImageCaption : ''; | 324 this.profileImageSelected ? this.profileImageCaption : ''; |
321 }, | 325 }, |
322 | 326 |
323 /** | 327 /** |
324 * Updates localized content of the screen that is not updated via template. | 328 * Updates localized content of the screen that is not updated via template. |
325 * @public | |
326 */ | 329 */ |
327 updateLocalizedContent: function() { | 330 updateLocalizedContent: function() { |
328 this.updateProfileImageCaption_(); | 331 this.updateProfileImageCaption_(); |
332 }, | |
333 | |
334 /** | |
335 * Updates profile image caption. | |
336 * @private | |
337 */ | |
338 updateProfileImageCaption_: function() { | |
339 this.profileImageCaption = localStrings.getString( | |
340 this.profileImageLoading_ ? 'profilePhotoLoading' : 'profilePhoto'); | |
341 } | |
342 }; | |
343 | |
344 var UserImageScreenWebRTCProto = { | |
345 __proto__: HTMLDivElement.prototype, | |
346 | |
347 /** | |
348 * Currently selected user image index (take photo button is with zero | |
349 * index). | |
350 * @type {number} | |
351 */ | |
352 selectedUserImage_: -1, | |
353 | |
354 /** @inheritDoc */ | |
355 decorate: function(element) { | |
356 var imageGrid = $('user-image-grid'); | |
357 UserImagesGrid.decorate(imageGrid); | |
358 | |
359 imageGrid.addEventListener('change', | |
360 this.handleSelection_.bind(this)); | |
361 imageGrid.addEventListener('activate', | |
362 this.handleImageActivated_.bind(this)); | |
363 imageGrid.addEventListener('dblclick', | |
364 this.handleImageDblClick_.bind(this)); | |
365 | |
366 // Profile image data (if present). | |
367 this.profileImage_ = imageGrid.addItem( | |
368 ButtonImages.PROFILE_PICTURE, | |
369 undefined, undefined, undefined, | |
370 function(el) { // Custom decorator for Profile image element. | |
371 var spinner = el.ownerDocument.createElement('div'); | |
372 spinner.className = 'spinner'; | |
373 var spinnerBg = el.ownerDocument.createElement('div'); | |
374 spinnerBg.className = 'spinner-bg'; | |
375 spinnerBg.appendChild(spinner); | |
376 el.appendChild(spinnerBg); | |
377 el.id = 'profile-image'; | |
378 }); | |
379 this.profileImage_.type = 'profile'; | |
380 this.selectionType = 'default'; | |
381 | |
382 var video = $('user-image-stream'); | |
383 video.addEventListener('canplay', this.handleVideoStarted_.bind(this)); | |
384 video.addEventListener('timeupdate', this.handleVideoUpdate_.bind(this)); | |
385 $('take-photo').addEventListener('click', | |
386 this.handleTakePhoto_.bind(this)); | |
387 $('discard-photo').addEventListener('click', | |
388 this.handleDiscardPhoto_.bind(this)); | |
389 this.cameraImage = null; | |
390 this.checkVideo_(); | |
Nikita (slow)
2012/06/07 15:26:55
Not sure if this the right place for this check.
Ivan Korotkov
2012/06/09 14:19:01
Right, fixed.
| |
391 }, | |
392 | |
393 /** | |
394 * Header text of the screen. | |
395 * @type {string} | |
396 */ | |
397 get header() { | |
398 return localStrings.getString('userImageScreenTitle'); | |
399 }, | |
400 | |
401 /** | |
402 * Buttons in oobe wizard's button strip. | |
403 * @type {array} Array of Buttons. | |
404 */ | |
405 get buttons() { | |
406 var okButton = this.ownerDocument.createElement('button'); | |
407 okButton.id = 'ok-button'; | |
408 okButton.textContent = localStrings.getString('okButtonText'); | |
409 okButton.addEventListener('click', this.acceptImage_.bind(this)); | |
410 return [okButton]; | |
411 }, | |
412 | |
413 /** | |
414 * The caption to use for the Profile image preview. | |
415 * @type {string} | |
416 */ | |
417 get profileImageCaption() { | |
418 return this.profileImageCaption_; | |
419 }, | |
420 set profileImageCaption(value) { | |
421 this.profileImageCaption_ = value; | |
422 this.updateCaption_(); | |
423 }, | |
424 | |
425 /** | |
426 * True if the Profile image is being loaded. | |
427 * @type {boolean} | |
428 */ | |
429 get profileImageLoading() { | |
430 return this.profileImageLoading_; | |
431 }, | |
432 set profileImageLoading(value) { | |
433 this.profileImageLoading_ = value; | |
434 $('user-image-screen-main').classList[ | |
435 value ? 'add' : 'remove']('profile-image-loading'); | |
436 this.updateProfileImageCaption_(); | |
437 }, | |
438 | |
439 /** | |
440 * True when camera is in live mode (i.e. no still photo selected). | |
441 * @type {boolean} | |
442 */ | |
443 get cameraLive() { | |
444 return this.cameraLive_; | |
445 }, | |
446 set cameraLive(value) { | |
447 this.cameraLive_ = value; | |
448 $('user-image-preview').classList[value ? 'add' : 'remove']('live'); | |
449 }, | |
450 | |
451 /** | |
452 * Type of the selected image (one of 'default', 'profile', 'camera'). | |
453 * @type {string} | |
454 */ | |
455 get selectionType() { | |
456 return this.selectionType_; | |
457 }, | |
458 set selectionType(value) { | |
459 this.selectionType_ = value; | |
460 var previewClassList = $('user-image-preview').classList; | |
461 previewClassList[value == 'default' ? 'add' : 'remove']('default-image'); | |
462 previewClassList[value == 'profile' ? 'add' : 'remove']('profile-image'); | |
463 previewClassList[value == 'camera' ? 'add' : 'remove']('camera'); | |
464 this.updateCaption_(); | |
465 }, | |
466 | |
467 /** | |
468 * Handles image activation (by pressing Enter). | |
469 * @private | |
470 */ | |
471 handleImageActivated_: function() { | |
472 switch ($('user-image-grid').selectedItemUrl) { | |
473 case ButtonImages.TAKE_PHOTO: | |
474 this.handleTakePhoto_(); | |
475 break; | |
476 default: | |
477 this.acceptImage_(); | |
478 break; | |
479 } | |
480 }, | |
481 | |
482 /** | |
483 * Handles photo capture from the live camera stream. | |
484 * @private | |
485 */ | |
486 handleTakePhoto_: function() { | |
487 var self = this; | |
488 var photoURL = this.captureFrame_($('user-image-stream'), | |
489 {width: 320, height: 320}); | |
Nikita (slow)
2012/06/07 15:26:55
nit: Extract constants.
Ivan Korotkov
2012/06/09 14:19:01
Done.
| |
490 chrome.send('photoTaken', [photoURL]); | |
491 // Wait until image is loaded before displaying it. | |
492 var previewImg = new Image(); | |
493 previewImg.addEventListener('load', function(e) { | |
494 self.cameraImage = this.src; | |
495 }); | |
496 previewImg.src = photoURL; | |
497 }, | |
498 | |
499 /** | |
500 * Discard current photo and return to the live camera stream. | |
501 * @private | |
502 */ | |
503 handleDiscardPhoto_: function() { | |
504 this.cameraImage = null; | |
505 }, | |
506 | |
507 /** | |
508 * Capture a single still frame from a <video> element. | |
509 * @param {HTMLVideoElement} video Video element to capture from. | |
510 * @param {{width: number, height: number}} destSize Capture size. | |
511 * @return {string} Captured frame as a data URL. | |
512 * @private | |
513 */ | |
514 captureFrame_: function(video, destSize) { | |
515 var canvas = document.createElement('canvas'); | |
516 canvas.width = destSize.width; | |
517 canvas.height = destSize.height; | |
518 var ctx = canvas.getContext('2d'); | |
519 var W = video.videoWidth, H = video.videoHeight; | |
Nikita (slow)
2012/06/07 15:26:55
Split into 2 lines.
Nikita (slow)
2012/06/07 15:26:55
rename to width/height in lowercase
Ivan Korotkov
2012/06/09 14:19:01
Done.
Ivan Korotkov
2012/06/09 14:19:01
Done.
| |
520 if (W < destSize.width || H < destSize.height) | |
521 console.error('Video capture size too small: ' + W + 'x' + H + '!'); | |
522 var src = {}; | |
523 if (W / destSize.width > H / destSize.height) { | |
524 // Full height, crop left/right. | |
525 src.height = H; | |
526 src.width = H * destSize.width / destSize.height; | |
527 } else { | |
528 // Full width, crop top/bottom. | |
529 src.width = W; | |
530 src.height = W * destSize.height / destSize.width; | |
531 } | |
532 src.x = (W - src.width) / 2; | |
533 src.y = (H - src.height) / 2; | |
534 ctx.drawImage(video, src.x, src.y, src.width, src.height, | |
535 0, 0, destSize.width, destSize.height); | |
536 return canvas.toDataURL('image/png'); | |
537 }, | |
538 | |
539 /** | |
540 * Handles selection change. | |
541 * @private | |
542 */ | |
543 handleSelection_: function() { | |
544 var selectedItem = $('user-image-grid').selectedItem; | |
545 if (selectedItem === null) | |
546 return; | |
547 | |
548 // Update preview image URL. | |
549 var url = selectedItem.url; | |
550 $('user-image-preview-img').src = url; | |
551 | |
552 // Update current selection type. | |
553 this.selectionType = selectedItem.type; | |
554 | |
555 // Show grey silhouette with the same border as stock images. | |
556 if (/^chrome:\/\/theme\//.test(url)) | |
557 $('user-image-preview').classList.add('default-image'); | |
558 | |
559 if (ButtonImageUrls.indexOf(url) == -1) { | |
560 // Non-button image is selected. | |
561 $('ok-button').disabled = false; | |
562 chrome.send('selectImage', [url]); | |
563 } else { | |
564 $('ok-button').disabled = true; | |
565 } | |
566 }, | |
567 | |
568 /** | |
569 * Handles double click on the image grid. | |
570 * @param {Event} e Double click Event. | |
571 */ | |
572 handleImageDblClick_: function(e) { | |
573 // If an image is double-clicked and not the grid itself, handle this | |
574 // as 'OK' button button press. | |
575 if (e.target.id != 'user-image-grid') | |
576 this.acceptImage_(); | |
577 }, | |
578 | |
579 /** | |
580 * Event handler that is invoked just before the screen is shown. | |
581 * @param {object} data Screen init payload. | |
582 */ | |
583 onBeforeShow: function(data) { | |
584 Oobe.getInstance().headerHidden = true; | |
585 $('user-image-grid').updateAndFocus(); | |
586 chrome.send('onUserImageScreenShown'); | |
587 //this.checkVideo_(); | |
Nikita (slow)
2012/06/07 15:26:55
Remove or add a second part of the initialization
Ivan Korotkov
2012/06/09 14:19:01
Done.
| |
588 }, | |
589 | |
590 /** | |
591 * Accepts currently selected image, if possible. | |
592 * @private | |
593 */ | |
594 acceptImage_: function() { | |
595 var okButton = $('ok-button'); | |
596 if (!okButton.disabled) { | |
597 // This ensures that #ok-button won't be re-enabled again. | |
598 $('user-image-grid').disabled = true; | |
599 okButton.disabled = true; | |
600 chrome.send('onUserImageAccepted'); | |
601 } | |
602 }, | |
603 | |
604 /** | |
605 * @param {boolean} present Whether a camera is present or not. | |
606 */ | |
607 get cameraPresent() { | |
608 return this.cameraPresent_; | |
609 }, | |
610 set cameraPresent(value) { | |
611 this.cameraPresent_ = value; | |
612 if (this.cameraLive) | |
613 this.cameraImage = null; | |
614 }, | |
615 | |
616 /** | |
617 * Start camera presence check. | |
618 * @private | |
619 */ | |
620 checkVideo_: function() { | |
Nikita (slow)
2012/06/07 15:26:55
Rename to checkCameraPresence?
| |
621 $('user-image-preview').classList.remove('online'); | |
622 navigator.webkitGetUserMedia({video: true}, | |
623 this.handleVideoAvailable_.bind(this), | |
624 this.handleVideoFailed_.bind(this)); | |
625 }, | |
626 | |
627 /** | |
628 * Handles successful camera check. | |
629 * @param {MediaStream} stream Stream object as returned by getUserMedia. | |
630 * @private | |
631 */ | |
632 handleVideoAvailable_: function(stream) { | |
633 $('user-image-stream').src = window.webkitURL.createObjectURL(stream); | |
634 this.cameraPresent = true; | |
635 }, | |
636 | |
637 /** | |
638 * Handles camera check failure. | |
639 * @param {NavigatorUserMediaError} err Error object. | |
640 * @private | |
641 */ | |
642 handleVideoFailed_: function(err) { | |
643 this.cameraPresent = false; | |
644 }, | |
645 | |
646 /** | |
647 * Handles successful camera capture start. | |
648 * @private | |
649 */ | |
650 handleVideoStarted_: function() { | |
651 $('user-image-preview').classList.add('online'); | |
652 }, | |
653 | |
654 /** | |
655 * Handles camera stream update. Called regularly (at rate no greater then | |
656 * 4/sec) while camera stream is live. | |
657 * @private | |
658 */ | |
659 handleVideoUpdate_: function() { | |
660 if (!this.lastFrameTime_) { | |
661 this.lastFrameTime_ = new Date().getTime(); | |
662 return; | |
663 } | |
664 var newFrameTime = new Date().getTime(); | |
665 this.lastFrameTime_ = newFrameTime; | |
666 }, | |
667 | |
668 /** | |
669 * Current image captured from camera as data URL. Setting to null will | |
670 * return to the live camera stream. | |
671 * @type {string=} | |
672 */ | |
673 get cameraImage() { | |
674 return this.cameraImage_; | |
675 }, | |
676 set cameraImage(imageUrl) { | |
677 this.cameraLive = !imageUrl; | |
678 var imageGrid = $('user-image-grid'); | |
679 if (this.cameraPresent && !imageUrl) { | |
680 imageUrl = ButtonImages.TAKE_PHOTO; | |
681 } | |
682 if (imageUrl) { | |
683 this.cameraImage_ = this.cameraImage_ ? | |
684 imageGrid.updateItem(this.cameraImage_, imageUrl) : | |
685 imageGrid.addItem(imageUrl, undefined, undefined, 0); | |
686 this.cameraImage_.type = 'camera'; | |
687 } else { | |
688 imageGrid.removeItem(this.cameraImage_); | |
689 this.cameraImage_ = null; | |
690 } | |
691 imageGrid.selectedItem = this.cameraImage_; | |
692 imageGrid.focus(); | |
693 }, | |
694 | |
695 /** | |
696 * Updates user profile image. | |
697 * @param {?string} imageUrl Image encoded as data URL. If null, user has | |
698 * the default profile image, which we don't want to show. | |
699 * @private | |
700 */ | |
701 setProfileImage_: function(imageUrl) { | |
702 this.profileImageLoading = false; | |
703 if (imageUrl !== null) { | |
704 this.profileImage_ = | |
705 $('user-image-grid').updateItem(this.profileImage_, imageUrl); | |
706 } | |
707 }, | |
708 | |
709 /** | |
710 * Appends received images to the list. | |
711 * @param {Array.<string>} images An array of URLs to user images. | |
712 * @private | |
713 */ | |
714 setUserImages_: function(images) { | |
715 var imageGrid = $('user-image-grid'); | |
716 for (var i = 0, url; url = images[i]; i++) | |
717 imageGrid.addItem(url).type = 'default'; | |
718 }, | |
719 | |
720 /** | |
721 * Selects user image with the given URL. | |
722 * @param {string} url URL of the image to select. | |
723 * @private | |
724 */ | |
725 setSelectedImage_: function(url) { | |
726 var imageGrid = $('user-image-grid'); | |
727 imageGrid.selectedItemUrl = url; | |
728 imageGrid.focus(); | |
729 }, | |
730 | |
731 /** | |
732 * Updates the image preview caption. | |
733 * @private | |
734 */ | |
735 updateCaption_: function() { | |
736 $('user-image-preview-caption').textContent = | |
737 (this.selectionType == 'profile') ? this.profileImageCaption : ''; | |
738 }, | |
739 | |
740 /** | |
741 * Updates localized content of the screen that is not updated via template. | |
742 */ | |
743 updateLocalizedContent: function() { | |
744 this.updateProfileImageCaption_(); | |
329 }, | 745 }, |
330 | 746 |
331 /** | 747 /** |
332 * Updates profile image caption. | 748 * Updates profile image caption. |
333 * @private | 749 * @private |
334 */ | 750 */ |
335 updateProfileImageCaption_: function() { | 751 updateProfileImageCaption_: function() { |
336 this.profileImageCaption = localStrings.getString( | 752 this.profileImageCaption = localStrings.getString( |
337 this.profileImageLoading_ ? 'profilePhotoLoading' : 'profilePhoto'); | 753 this.profileImageLoading_ ? 'profilePhotoLoading' : 'profilePhoto'); |
338 } | 754 } |
339 }; | 755 }; |
340 | 756 |
341 // Forward public APIs to private implementations. | 757 // Forward public APIs to private implementations. |
342 [ | 758 [ |
343 'setCameraPresent', | 759 'setCameraPresent', |
Nikita (slow)
2012/06/07 15:26:55
setCameraPresent_ is not defined for UserImageScre
Ivan Korotkov
2012/06/09 14:19:01
It's not a problem as long as it doesn't get calle
| |
344 'setProfileImage', | 760 'setProfileImage', |
345 'setSelectedImage', | 761 'setSelectedImage', |
346 'setUserImages', | 762 'setUserImages', |
347 'setUserPhoto', | 763 'setUserPhoto', |
348 ].forEach(function(name) { | 764 ].forEach(function(name) { |
349 UserImageScreen[name] = function(value) { | 765 UserImageScreen[name] = function(value) { |
350 $('user-image')[name + '_'](value); | 766 $('user-image')[name + '_'](value); |
351 }; | 767 }; |
352 }); | 768 }); |
353 | 769 |
354 return { | 770 return { |
355 UserImageScreen: UserImageScreen | 771 UserImageScreen: UserImageScreen |
356 }; | 772 }; |
357 }); | 773 }); |
OLD | NEW |