ECOM-859: Add support for an input image capture.
Add support for HTML Media Capture (file input) so that iOS users can use the camera.
This commit is contained in:
@@ -1129,6 +1129,7 @@ verify_student_js = [
|
||||
'js/src/string_utils.js',
|
||||
'js/verify_student/models/verification_model.js',
|
||||
'js/verify_student/views/error_view.js',
|
||||
'js/verify_student/views/image_input_view.js',
|
||||
'js/verify_student/views/webcam_photo_view.js',
|
||||
'js/verify_student/views/step_view.js',
|
||||
'js/verify_student/views/intro_step_view.js',
|
||||
|
||||
@@ -423,6 +423,16 @@
|
||||
},
|
||||
'js/verify_student/views/webcam_photo_view': {
|
||||
exports: 'edx.verify_student.WebcamPhotoView',
|
||||
deps: [
|
||||
'jquery',
|
||||
'underscore',
|
||||
'backbone',
|
||||
'gettext',
|
||||
'js/verify_student/views/image_input_view'
|
||||
]
|
||||
},
|
||||
'js/verify_student/views/image_input_view': {
|
||||
exports: 'edx.verify_student.ImageInputView',
|
||||
deps: [ 'jquery', 'underscore', 'backbone', 'gettext' ]
|
||||
},
|
||||
'js/verify_student/views/step_view': {
|
||||
@@ -540,6 +550,7 @@
|
||||
'lms/include/js/spec/student_profile/profile_spec.js',
|
||||
'lms/include/js/spec/verify_student/pay_and_verify_view_spec.js',
|
||||
'lms/include/js/spec/verify_student/webcam_photo_view_spec.js',
|
||||
'lms/include/js/spec/verify_student/image_input_spec.js',
|
||||
'lms/include/js/spec/verify_student/review_photos_step_view_spec.js',
|
||||
'lms/include/js/spec/verify_student/make_payment_step_view_spec.js',
|
||||
'lms/include/js/spec/edxnotes/utils/logger_spec.js',
|
||||
|
||||
156
lms/static/js/spec/verify_student/image_input_spec.js
Normal file
156
lms/static/js/spec/verify_student/image_input_spec.js
Normal file
@@ -0,0 +1,156 @@
|
||||
define([
|
||||
'jquery',
|
||||
'backbone',
|
||||
'js/common_helpers/template_helpers',
|
||||
'js/common_helpers/ajax_helpers',
|
||||
'js/verify_student/views/image_input_view',
|
||||
'js/verify_student/models/verification_model'
|
||||
], function( $, Backbone, TemplateHelpers, AjaxHelpers, ImageInputView, VerificationModel ) {
|
||||
'use strict';
|
||||
|
||||
describe( 'edx.verify_student.ImageInputView', function() {
|
||||
|
||||
var IMAGE_DATA = 'abcd1234';
|
||||
|
||||
var createView = function() {
|
||||
return new ImageInputView({
|
||||
el: $( '#current-step-container' ),
|
||||
model: new VerificationModel({}),
|
||||
modelAttribute: 'faceImage',
|
||||
errorModel: new ( Backbone.Model.extend({}) )(),
|
||||
submitButton: $( '#submit_button' ),
|
||||
}).render();
|
||||
};
|
||||
|
||||
var uploadImage = function( view, fileType, callback ) {
|
||||
var imageCapturedEvent = false,
|
||||
errorEvent = false;
|
||||
|
||||
// Since image upload is an asynchronous process,
|
||||
// we need to wait for the upload to complete
|
||||
// before checking the outcome.
|
||||
runs(function() {
|
||||
var fakeFile,
|
||||
fakeEvent = { target: { files: [] } };
|
||||
|
||||
// If no file type is specified, don't add any files.
|
||||
// This simulates what happens when the user clicks
|
||||
// "cancel" after clicking the input.
|
||||
if ( fileType !== null) {
|
||||
fakeFile = new Blob(
|
||||
[ IMAGE_DATA ],
|
||||
{ type: 'image/' + fileType }
|
||||
);
|
||||
fakeEvent.target.files = [ fakeFile ];
|
||||
}
|
||||
|
||||
// Wait for either a successful upload or an error
|
||||
view.on( 'imageCaptured', function() {
|
||||
imageCapturedEvent = true;
|
||||
});
|
||||
view.on( 'error', function() {
|
||||
errorEvent = true;
|
||||
});
|
||||
|
||||
// Trigger the file input change
|
||||
// It's impossible to trigger this directly due
|
||||
// to browser security restrictions, so we call
|
||||
// the handler instead.
|
||||
view.handleInputChange( fakeEvent );
|
||||
});
|
||||
|
||||
// Check that the image upload has completed,
|
||||
// either successfully or with an error.
|
||||
waitsFor(function() {
|
||||
return ( imageCapturedEvent || errorEvent );
|
||||
});
|
||||
|
||||
// Execute the callback to check expectations.
|
||||
runs( callback );
|
||||
};
|
||||
|
||||
var expectPreview = function( view, fileType ) {
|
||||
var previewImage = view.$preview.attr('src');
|
||||
if ( fileType ) {
|
||||
expect( previewImage ).toContain( 'data:image/' + fileType );
|
||||
} else {
|
||||
expect( previewImage ).toEqual( '' );
|
||||
}
|
||||
};
|
||||
|
||||
var expectSubmitEnabled = function( isEnabled ) {
|
||||
var appearsDisabled = $( '#submit_button' ).hasClass( 'is-disabled' ),
|
||||
isDisabled = $( '#submit_button' ).prop( 'disabled' );
|
||||
|
||||
expect( !appearsDisabled ).toEqual( isEnabled );
|
||||
expect( !isDisabled ).toEqual( isEnabled );
|
||||
};
|
||||
|
||||
var expectImageData = function( view, fileType ) {
|
||||
var imageData = view.model.get( view.modelAttribute );
|
||||
if ( fileType ) {
|
||||
expect( imageData ).toContain( 'data:image/' + fileType );
|
||||
} else {
|
||||
expect( imageData ).toEqual( '' );
|
||||
}
|
||||
};
|
||||
|
||||
var expectError = function( view ) {
|
||||
expect( view.errorModel.get('shown') ).toBe(true);
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
setFixtures(
|
||||
'<div id="current-step-container"></div>' +
|
||||
'<input type="button" id="submit_button"></input>'
|
||||
);
|
||||
TemplateHelpers.installTemplate( 'templates/verify_student/image_input' );
|
||||
});
|
||||
|
||||
it( 'initially disables the submit button', function() {
|
||||
createView();
|
||||
expectSubmitEnabled( false );
|
||||
});
|
||||
|
||||
it( 'uploads a png image', function() {
|
||||
var view = createView();
|
||||
|
||||
uploadImage( view, 'png', function() {
|
||||
expectPreview( view, 'png' );
|
||||
expectSubmitEnabled( true );
|
||||
expectImageData( view, 'png' );
|
||||
});
|
||||
});
|
||||
|
||||
it( 'uploads a jpeg image', function() {
|
||||
var view = createView();
|
||||
|
||||
uploadImage( view, 'jpeg', function() {
|
||||
expectPreview( view, 'jpeg' );
|
||||
expectSubmitEnabled( true );
|
||||
expectImageData( view, 'jpeg' );
|
||||
} );
|
||||
});
|
||||
|
||||
it( 'hides the preview when the user cancels the upload', function() {
|
||||
var view = createView();
|
||||
|
||||
uploadImage( view, null, function() {
|
||||
expectPreview( view, null );
|
||||
expectSubmitEnabled( false );
|
||||
expectImageData( view, null );
|
||||
} );
|
||||
});
|
||||
|
||||
it( 'shows an error if the file type is not supported', function() {
|
||||
var view = createView();
|
||||
|
||||
uploadImage( view, 'txt', function() {
|
||||
expectPreview( view, null );
|
||||
expectError( view );
|
||||
expectSubmitEnabled( false );
|
||||
expectImageData( view, null );
|
||||
} );
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -13,7 +13,8 @@ define(['jquery', 'js/common_helpers/template_helpers', 'js/verify_student/views
|
||||
'make_payment_step',
|
||||
'payment_confirmation_step',
|
||||
'review_photos_step',
|
||||
'webcam_photo'
|
||||
'webcam_photo',
|
||||
'image_input'
|
||||
];
|
||||
|
||||
var INTRO_STEP = {
|
||||
|
||||
@@ -45,14 +45,14 @@ define([
|
||||
};
|
||||
};
|
||||
|
||||
var createView = function( backends ) {
|
||||
var createView = function( backendStub ) {
|
||||
return new WebcamPhotoView({
|
||||
el: $( '#current-step-container' ),
|
||||
model: new VerificationModel({}),
|
||||
modelAttribute: 'faceImage',
|
||||
errorModel: new ( Backbone.Model.extend({}) )(),
|
||||
submitButton: $( '#submit_button' ),
|
||||
backends: backends
|
||||
backend: backendStub
|
||||
}).render();
|
||||
};
|
||||
|
||||
@@ -91,7 +91,7 @@ define([
|
||||
});
|
||||
|
||||
it( 'takes a snapshot', function() {
|
||||
var view = createView( [ StubBackend( "html5" ) ] );
|
||||
var view = createView( new StubBackend( "html5" ) );
|
||||
|
||||
// Spy on the backend
|
||||
spyOn( view.backend, 'snapshot' ).andCallThrough();
|
||||
@@ -122,7 +122,7 @@ define([
|
||||
});
|
||||
|
||||
it( 'resets the camera', function() {
|
||||
var view = createView( [ StubBackend( "html5" ) ]);
|
||||
var view = createView( new StubBackend( "html5" ) );
|
||||
|
||||
// Spy on the backend
|
||||
spyOn( view.backend, 'reset' ).andCallThrough();
|
||||
@@ -145,30 +145,8 @@ define([
|
||||
expect( view.model.get( 'faceImage' ) ).toEqual( "" );
|
||||
});
|
||||
|
||||
it( 'falls back to a second video capture backend', function() {
|
||||
var backends = [ StubBackend( "html5", false ), StubBackend( "flash", true ) ],
|
||||
view = createView( backends );
|
||||
|
||||
// Expect that the second backend is chosen
|
||||
expect( view.backend.name ).toEqual( backends[1].name );
|
||||
});
|
||||
|
||||
it( 'displays an error if no video backend is supported', function() {
|
||||
var backends = [ StubBackend( "html5", false ), StubBackend( "flash", false ) ],
|
||||
view = createView( backends );
|
||||
|
||||
// Expect an error
|
||||
expect( view.errorModel.get( 'errorTitle' ) ).toEqual( 'Flash Not Detected' );
|
||||
expect( view.errorModel.get( 'errorMsg' ) ).toContain( 'Get Flash' );
|
||||
expect( view.errorModel.get( 'shown' ) ).toBe( true );
|
||||
|
||||
// Expect that submission is disabled
|
||||
expectSubmitEnabled( false );
|
||||
});
|
||||
|
||||
it( 'displays an error if the snapshot fails', function() {
|
||||
var backends = [ StubBackend( "html5", true, false ) ],
|
||||
view = createView( backends );
|
||||
var view = createView( new StubBackend( "html5", true, false ) );
|
||||
|
||||
// Take a snapshot
|
||||
takeSnapshot();
|
||||
@@ -189,7 +167,7 @@ define([
|
||||
});
|
||||
|
||||
it( 'displays an error triggered by the backend', function() {
|
||||
var view = createView( [ StubBackend( "html5") ] );
|
||||
var view = createView( new StubBackend( "html5") );
|
||||
|
||||
// Simulate an error triggered by the backend
|
||||
// This could occur at any point, including
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
errorMsg: "",
|
||||
shown: false
|
||||
});
|
||||
this.listenToOnce( this.model, 'change', this.render );
|
||||
this.listenTo( this.model, 'change', this.render );
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
||||
@@ -17,7 +17,7 @@ var edx = edx || {};
|
||||
},
|
||||
|
||||
postRender: function() {
|
||||
var webcam = new edx.verify_student.WebcamPhotoView({
|
||||
var webcam = edx.verify_student.getSupportedWebcamView({
|
||||
el: $( '#facecam' ),
|
||||
model: this.model,
|
||||
modelAttribute: 'faceImage',
|
||||
|
||||
@@ -17,7 +17,7 @@ var edx = edx || {};
|
||||
},
|
||||
|
||||
postRender: function() {
|
||||
var webcam = new edx.verify_student.WebcamPhotoView({
|
||||
var webcam = edx.verify_student.getSupportedWebcamView({
|
||||
el: $( '#idcam' ),
|
||||
model: this.model,
|
||||
modelAttribute: 'identificationImage',
|
||||
|
||||
112
lms/static/js/verify_student/views/image_input_view.js
Normal file
112
lms/static/js/verify_student/views/image_input_view.js
Normal file
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Allow users to upload an image using a file input.
|
||||
*
|
||||
* This uses HTML Media Capture so that iOS will
|
||||
* allow users to use their camera instead of choosing
|
||||
* a file.
|
||||
*/
|
||||
|
||||
var edx = edx || {};
|
||||
|
||||
(function( $, _, Backbone, gettext ) {
|
||||
'use strict';
|
||||
|
||||
edx.verify_student = edx.verify_student || {};
|
||||
|
||||
edx.verify_student.ImageInputView = Backbone.View.extend({
|
||||
|
||||
template: '#image_input-tpl',
|
||||
|
||||
initialize: function( obj ) {
|
||||
this.$submitButton = obj.submitButton ? $( obj.submitButton ) : '';
|
||||
this.modelAttribute = obj.modelAttribute || '';
|
||||
this.errorModel = obj.errorModel || null;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var renderedHtml = _.template( $( this.template ).html(), {} );
|
||||
$( this.el ).html( renderedHtml );
|
||||
|
||||
// Set the submit button to disabled by default
|
||||
this.setSubmitButtonEnabled( false );
|
||||
|
||||
this.$input = $( 'input.image-upload' );
|
||||
this.$preview = $( 'img.preview' );
|
||||
this.$input.on('change', _.bind( this.handleInputChange, this ) );
|
||||
|
||||
// Initially hide the preview
|
||||
this.displayImage( false );
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
handleInputChange: function( event ) {
|
||||
var files = event.target.files,
|
||||
reader = new FileReader();
|
||||
if ( files[0] && files[0].type.match( 'image.[png|jpg|jpeg]' ) ) {
|
||||
reader.onload = _.bind( this.handleImageUpload, this );
|
||||
reader.onerror = _.bind( this.handleUploadError, this );
|
||||
reader.readAsDataURL( files[0] );
|
||||
} else if ( files.length === 0 ) {
|
||||
this.handleUploadError( false );
|
||||
} else {
|
||||
this.handleUploadError( true );
|
||||
}
|
||||
},
|
||||
|
||||
handleImageUpload: function( event ) {
|
||||
var imageData = event.target.result;
|
||||
this.model.set( this.modelAttribute, imageData );
|
||||
this.displayImage( imageData );
|
||||
this.setSubmitButtonEnabled( true );
|
||||
|
||||
// Hide any errors we may have displayed previously
|
||||
if ( this.errorModel ) {
|
||||
this.errorModel.set({ shown: false });
|
||||
}
|
||||
|
||||
this.trigger( 'imageCaptured' );
|
||||
},
|
||||
|
||||
displayImage: function( imageData ) {
|
||||
if ( imageData ) {
|
||||
this.$preview
|
||||
.attr( 'src', imageData )
|
||||
.removeClass('is-hidden')
|
||||
.attr('aria-hidden', 'false');
|
||||
} else {
|
||||
this.$preview
|
||||
.attr( 'src', '' )
|
||||
.addClass('is-hidden')
|
||||
.attr('aria-hidden', 'true');
|
||||
}
|
||||
},
|
||||
|
||||
handleUploadError: function( displayError ) {
|
||||
this.displayImage( null );
|
||||
this.setSubmitButtonEnabled( false );
|
||||
if ( this.errorModel ) {
|
||||
if ( displayError ) {
|
||||
this.errorModel.set({
|
||||
errorTitle: gettext( 'Image Upload Error' ),
|
||||
errorMsg: gettext( 'Please verify that you have uploaded a valid image (PNG and JPEG).' ),
|
||||
shown: true
|
||||
});
|
||||
} else {
|
||||
this.errorModel.set({
|
||||
shown: false
|
||||
});
|
||||
}
|
||||
}
|
||||
this.trigger( 'error' );
|
||||
},
|
||||
|
||||
setSubmitButtonEnabled: function( isEnabled ) {
|
||||
this.$submitButton
|
||||
.toggleClass( 'is-disabled', !isEnabled )
|
||||
.prop( 'disabled', !isEnabled )
|
||||
.attr('aria-disabled', !isEnabled);
|
||||
}
|
||||
});
|
||||
|
||||
})( jQuery, _, Backbone, gettext );
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Interface for retrieving webcam photos.
|
||||
* Supports both HTML5 and Flash.
|
||||
* Supports HTML5 and Flash.
|
||||
*/
|
||||
var edx = edx || {};
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
|
||||
template: "#webcam_photo-tpl",
|
||||
|
||||
backends: [
|
||||
{
|
||||
backends: {
|
||||
"html5": {
|
||||
name: "html5",
|
||||
|
||||
initialize: function( obj ) {
|
||||
@@ -24,18 +24,21 @@
|
||||
this.stream = null;
|
||||
|
||||
// Start the capture
|
||||
this.getUserMediaFunc()(
|
||||
{
|
||||
video: true,
|
||||
var getUserMedia = this.getUserMediaFunc();
|
||||
if ( getUserMedia ) {
|
||||
getUserMedia(
|
||||
{
|
||||
video: true,
|
||||
|
||||
// Specify the `fake` constraint if we detect we are running in a test
|
||||
// environment. In Chrome, this will do nothing, but in Firefox, it will
|
||||
// instruct the browser to use a fake video device.
|
||||
fake: window.location.hostname === 'localhost'
|
||||
},
|
||||
_.bind( this.getUserMediaCallback, this ),
|
||||
_.bind( this.handleVideoFailure, this )
|
||||
);
|
||||
// Specify the `fake` constraint if we detect we are running in a test
|
||||
// environment. In Chrome, this will do nothing, but in Firefox, it will
|
||||
// instruct the browser to use a fake video device.
|
||||
fake: window.location.hostname === 'localhost'
|
||||
},
|
||||
_.bind( this.getUserMediaCallback, this ),
|
||||
_.bind( this.handleVideoFailure, this )
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
isSupported: function() {
|
||||
@@ -98,16 +101,14 @@
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"flash": {
|
||||
|
||||
name: "flash",
|
||||
|
||||
initialize: function( obj ) {
|
||||
this.wrapper = obj.wrapper || "";
|
||||
this.imageData = "";
|
||||
|
||||
// Replace the camera section with the flash object
|
||||
$( this.wrapper ).html( this.flashObjectTag() );
|
||||
|
||||
// Wait for the player to load, then verify camera support
|
||||
// Trigger an error if no camera is available.
|
||||
this.checkCameraSupported();
|
||||
@@ -203,36 +204,26 @@
|
||||
// so we don't need to keep checking.
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
|
||||
initialize: function( obj ) {
|
||||
this.submitButton = obj.submitButton || "";
|
||||
this.modelAttribute = obj.modelAttribute || "";
|
||||
this.errorModel = obj.errorModel || null;
|
||||
this.backend = _.find(
|
||||
obj.backends || this.backends,
|
||||
function( backend ) {
|
||||
return backend.isSupported();
|
||||
}
|
||||
);
|
||||
this.backend = this.backends[obj.backendName] || obj.backend;
|
||||
|
||||
if ( !this.backend ) {
|
||||
this.handleError(
|
||||
gettext( "Flash Not Detected" ),
|
||||
gettext( "You don't seem to have Flash installed." ) + " " +
|
||||
_.sprintf(
|
||||
gettext( "%(a_start)s Get Flash %(a_end)s to continue your enrollment." ),
|
||||
{
|
||||
a_start: '<a rel="external" href="http://get.adobe.com/flashplayer/">',
|
||||
a_end: '</a>'
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
_.extend( this.backend, Backbone.Events );
|
||||
this.listenTo( this.backend, 'error', this.handleError );
|
||||
}
|
||||
this.backend.initialize({
|
||||
wrapper: "#camera",
|
||||
video: '#photo_id_video',
|
||||
canvas: '#photo_id_canvas'
|
||||
});
|
||||
|
||||
_.extend( this.backend, Backbone.Events );
|
||||
this.listenTo( this.backend, 'error', this.handleError );
|
||||
},
|
||||
|
||||
isSupported: function() {
|
||||
return this.backend.isSupported();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
@@ -242,26 +233,18 @@
|
||||
this.setSubmitButtonEnabled( false );
|
||||
|
||||
// Load the template for the webcam into the DOM
|
||||
renderedHtml = _.template( $( this.template ).html(), {} );
|
||||
renderedHtml = _.template(
|
||||
$( this.template ).html(),
|
||||
{ backendName: this.backend.name }
|
||||
);
|
||||
$( this.el ).html( renderedHtml );
|
||||
|
||||
// Initialize the video capture backend
|
||||
// We need to do this after rendering the template
|
||||
// so that the backend has the opportunity to modify the DOM.
|
||||
if ( this.backend ) {
|
||||
this.backend.initialize({
|
||||
wrapper: "#camera",
|
||||
video: '#photo_id_video',
|
||||
canvas: '#photo_id_canvas'
|
||||
});
|
||||
// Install event handlers
|
||||
$( "#webcam_reset_button", this.el ).on( 'click', _.bind( this.reset, this ) );
|
||||
$( "#webcam_capture_button", this.el ).on( 'click', _.bind( this.capture, this ) );
|
||||
|
||||
// Install event handlers
|
||||
$( "#webcam_reset_button", this.el ).on( 'click', _.bind( this.reset, this ) );
|
||||
$( "#webcam_capture_button", this.el ).on( 'click', _.bind( this.capture, this ) );
|
||||
|
||||
// Show the capture button
|
||||
$( "#webcam_capture_button", this.el ).removeClass('is-hidden');
|
||||
}
|
||||
// Show the capture button
|
||||
$( "#webcam_capture_button", this.el ).removeClass('is-hidden');
|
||||
|
||||
return this;
|
||||
},
|
||||
@@ -325,4 +308,38 @@
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Retrieve a supported webcam view implementation.
|
||||
*
|
||||
* The priority order from most to least preferable is:
|
||||
* 1) HTML5
|
||||
* 2) Flash
|
||||
* 3) File input
|
||||
*
|
||||
* @param {Object} obj Parameters to the webcam view.
|
||||
* @return {Object} A Backbone view.
|
||||
*/
|
||||
edx.verify_student.getSupportedWebcamView = function( obj ) {
|
||||
var view = null;
|
||||
|
||||
// First choice is HTML5, supported by most web browsers
|
||||
obj.backendName = "html5";
|
||||
view = new edx.verify_student.WebcamPhotoView( obj );
|
||||
if ( view.isSupported() ) {
|
||||
return view;
|
||||
}
|
||||
|
||||
// Second choice is Flash, required for older versions of IE
|
||||
obj.backendName = "flash";
|
||||
view = new edx.verify_student.WebcamPhotoView( obj );
|
||||
if ( view.isSupported() ) {
|
||||
return view;
|
||||
}
|
||||
|
||||
// Last resort is HTML file input with image capture.
|
||||
// This will work everywhere, and on iOS it will
|
||||
// allow users to take a photo with the camera.
|
||||
return new edx.verify_student.ImageInputView( obj );
|
||||
};
|
||||
|
||||
})( jQuery, _, Backbone, gettext );
|
||||
|
||||
@@ -973,6 +973,11 @@
|
||||
.controls {
|
||||
height: ($baseline*4);
|
||||
}
|
||||
|
||||
.preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// ====================
|
||||
|
||||
5
lms/templates/verify_student/image_input.underscore
Normal file
5
lms/templates/verify_student/image_input.underscore
Normal file
@@ -0,0 +1,5 @@
|
||||
<img class="preview" alt="<%- gettext("Preview of uploaded image") %>"/>
|
||||
<label>
|
||||
<span class="sr"><%- gettext("Upload an image or capture one with your web or phone camera.") %></span>
|
||||
<input class="image-upload" type="file" accept="image/*;capture=camera">
|
||||
</label>
|
||||
@@ -23,7 +23,7 @@ from verify_student.views import PayAndVerifyView
|
||||
<%block name="header_extras">
|
||||
<%
|
||||
template_names = (
|
||||
["webcam_photo", "error"] +
|
||||
["webcam_photo", "image_input", "error"] +
|
||||
[step['templateName'] for step in display_steps]
|
||||
)
|
||||
%>
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
<div class="placeholder-cam" id="camera">
|
||||
<div class="placeholder-art">
|
||||
<p class="copy"><%- gettext( "Don't see your picture? Make sure to allow your browser to use your camera when it asks for permission." ) %></p>
|
||||
</div>
|
||||
<% if ( backendName === 'html5' ) { %>
|
||||
<div class="placeholder-art">
|
||||
<p class="copy"><%- gettext( "Don't see your picture? Make sure to allow your browser to use your camera when it asks for permission." ) %></p>
|
||||
</div>
|
||||
|
||||
<video id="photo_id_video" aria-label="<%- gettext( 'Live view of webcam' ) %>" autoplay></video><br/>
|
||||
<canvas id="photo_id_canvas" style="display:none;" width="640" height="480"></canvas>
|
||||
<video id="photo_id_video" aria-label="<%- gettext( 'Live view of webcam' ) %>" autoplay></video><br/>
|
||||
<canvas id="photo_id_canvas" style="display:none;" width="640" height="480"></canvas>
|
||||
<% } else if ( backendName === 'flash' ) { %>
|
||||
<object type="application/x-shockwave-flash"
|
||||
id="flash_video"
|
||||
name="flash_video"
|
||||
data="/static/js/verify_student/CameraCapture.swf"
|
||||
width="500"
|
||||
height="375">
|
||||
<param name="quality" value="high">
|
||||
<param name="allowscriptaccess" value="sameDomain">
|
||||
</object>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<div class="controls photo-controls">
|
||||
|
||||
Reference in New Issue
Block a user