add take picture option to user avatars

fixes CNVS-9758

test plan:
  * navigate to user profile page;
  * open the avatar dialog by clicking the user's current
    avatar;
  * verify that "take picture" exists as an option (unless
    you're using safari or IE, in which case it should be
    hidden);
  * click on the "take picture" tab, approve use of your
    camera, and verify that you see a feed from your
    webcam;
  * using the provided button, take a picture of yourself;
  * verify that you can save it as your profile picture.
  * test this with both the display webcam and the macbook
    built-in webcam.

Change-Id: I0446ffe1f453323fa11d115c7ac35edc87fbc80d
Reviewed-on: https://gerrit.instructure.com/26891
Tested-by: Jenkins <jenkins@instructure.com>
QA-Review: Steven Shepherd <sshepherd@instructure.com>
Reviewed-by: Braden Anderson <banderson@instructure.com>
Product-Review: Jon Willesen <jonw@instructure.com>
This commit is contained in:
Jon Willesen 2014-03-04 11:37:08 -07:00
parent db9191c9dc
commit 266da24068
6 changed files with 206 additions and 5 deletions

View File

@ -22,9 +22,10 @@ define [
'underscore'
'compiled/views/DialogBaseView'
'compiled/views/profiles/UploadFileView'
'compiled/views/profiles/TakePictureView'
'jst/profiles/avatarDialog'
'jst/profiles/avatar'
], (I18n, $, _, DialogBaseView, UploadFileView, template, avatarTemplate) ->
], (I18n, $, _, DialogBaseView, UploadFileView, TakePictureView, template, avatarTemplate) ->
class AvatarDialogView extends DialogBaseView
@ -34,7 +35,8 @@ define [
h: 128
w: 128
@child 'uploadFileView', '#upload-picture'
@child 'uploadFileView', '#upload-picture'
@child 'takePictureView', '#take-picture'
dialogOptions: ->
buttons: [
@ -60,6 +62,7 @@ define [
initialize: () ->
@uploadFileView = new UploadFileView(avatarSize: @AVATAR_SIZE)
@takePictureView = new TakePictureView(avatarSize: @AVATAR_SIZE)
super
show: ->
@ -176,8 +179,8 @@ define [
@currentView = $content.data('view')
$('.select_button').prop('disabled', true)
onReady: ->
$('.select_button').prop('disabled', false)
onReady: (ready = true) ->
$('.select_button').prop('disabled', !ready)
teardown: ->
_.each(@children, (child) -> child.teardown())

View File

@ -0,0 +1,149 @@
#
# Copyright (C) 2013 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
define [
'jquery'
'underscore'
'compiled/views/profiles/AvatarUploadBaseView'
'jst/profiles/takePictureView'
'compiled/util/BlobFactory'
], ($, _, BaseView, template, BlobFactory) ->
class TakePictureView extends BaseView
@optionProperty 'avatarSize'
template: template
events:
'click .take-snapshot-btn' : 'onSnapshot'
'click .retry-snapshot-btn' : 'onRetry'
els:
'.webcam-live-preview' : '$video'
'.webcam-clip' : '$clip'
'.webcam-preview' : '$preview'
'.webcam-capture-wrapper' : '$captureWrapper'
'.webcam-preview-wrapper' : '$previewWrapper'
'.webcam-preview-staging-area' : '$canvas'
getUserMedia: (navigator.getUserMedia or navigator.mozGetUserMedia or
navigator.msGetUserMedia or navigator.webkitGetUserMedia or $.noop).bind(navigator)
setup: ->
@startMedia()
teardown: ->
delete @img
delete @preview
startMedia: ->
@getUserMedia(video: true, @displayMedia, $.noop)
displayMedia: (stream) =>
@$video.removeClass('pending')
@$video.attr('src', window.URL.createObjectURL(stream))
@$video.on('onloadedmetadata loadedmetadata', _.once(@onMediaMetadata).bind(this))
onMediaMetadata: (e) ->
wait = window.setInterval(=>
return unless @$video[0].videoHeight != 0
window.clearInterval(wait)
clipSize = _.min([@$video.height(), @$video.width()])
@$clip.height(clipSize).width(clipSize)
if @$video.width() > clipSize
adjustment = ((@$video.width() - clipSize) / 2) * -1
@$video.css('left', adjustment)
, 100)
toggleView: ->
@$captureWrapper.toggle()
@$previewWrapper.toggle()
@trigger('ready', !!@preview)
getImage: ->
dfd = $.Deferred()
dfd.resolve(@img)
onSnapshot: ->
canvas = @$canvas[0]
video = @$video[0]
img = new Image
context = canvas.getContext('2d')
canvas.height = video.clientHeight
canvas.width = video.clientWidth
context.drawImage(
# source
video,
# x and y coordinates of the top-left corner of the source image to draw to destination
0, 0,
# width and height of the source image to draw to the destination
canvas.width, canvas.height
)
url = canvas.toDataURL()
img.onload = (e) =>
sX = (video.clientWidth - @$clip.width()) / 2
sY = (video.clientHeight - @$clip.height()) / 2
canvas.height = @$clip.height()
canvas.width = @$clip.width()
context.drawImage(
# source
img,
# x and y coordinates of the top-left corner of the source image to draw to destination
sX, sY,
# width and height of the source image to draw to the destination
@$clip.width(), @$clip.height(),
# x and y coordinates to start drawing to in the destination
0, 0,
# width and height of the image in the destination
@$clip.width(), @$clip.height()
)
@preview = canvas.toDataURL()
@toggleView()
@$preview.attr('src', @preview)
@img = BlobFactory.fromCanvas(canvas)
img.src = url
onRetry: (e) ->
@resetSnapshot()
resetSnapshot: ->
delete @preview
delete @img
@toggleView()
previewSrc: ->
return '' unless @preview
@preview.split(',')[1]
toJSON: ->
{ hasPreview: !!@preview, previewURL: @preview }

View File

@ -208,3 +208,33 @@
.avatar-preview-wrapper {
padding: 8px 0;
}
.webcam-live-preview {
background: url(/images/webcam_preview.png) center center;
display: block;
margin: 0 auto;
max-height: 350px;
max-width: 350px;
position: relative;
&.pending {
height: 250px;
width: 250px;
}
}
.webcam-clip {
margin: 0 auto 16px auto;
overflow: hidden;
}
.webcam-preview {
display: block;
margin: 0 auto 16px auto;
max-height: 350px;
max-width: 350px;
}
.webcam-preview-wrapper {
display: none;
}

View File

@ -21,7 +21,7 @@
<div class="avatar-content">
<div class="active" id="upload-picture"></div>
<div id="take-picture"></div>
<div id="take-picture" class="text-center"></div>
<div id="from-url"></div>
<div id="from-gravatar"></div>
</div>

View File

@ -0,0 +1,19 @@
<div class="webcam-preview-wrapper">
<img src="{{previewURL}}" alt="" class="webcam-preview" />
<button type="button" class="btn retry-snapshot-btn">
{{#t "retry"}}Retry{{/t}}
</button>
</div>
<div class="webcam-capture-wrapper">
<div class="webcam-clip">
<video autoplay class="webcam-live-preview pending"></video>
</div>
<button type="button" class="btn take-snapshot-btn">
{{#t "take_snapshot"}}Take Snapshot{{/t}}
</button>
</div>
<canvas class="webcam-preview-staging-area hidden"></canvas>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB