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:
parent
db9191c9dc
commit
266da24068
|
@ -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())
|
||||
|
|
|
@ -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 }
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 |
Loading…
Reference in New Issue