refactor: Drop the unused legacy video upload page.

The legacy video uploads page in Studio has been replaced with a new
view in the Authoring MFE.  The legacy page has not been available for
some time, so it's all dead code. This PR removes it.

Please note that there's a waffle flag which enables the MFE version
of the video uploads page: `contentstore.new_studio_mfe.use_new_video_uploads_page`.
Unlike the other Studio MFE waffles, we're NOT going to remove this one now,
because the video uploads page has always been broken for sites other
than edx.org (or sites that have reverse-engineered their video pipeline) so
we'd like to keep the flag until it's either fixed for the community or
removed (https://github.com/openedx/openedx-platform/issues/37972).

This work is part of https://github.com/openedx/edx-platform/issues/36108

Co-Authored-By: Kyle McCormick <kyle@axim.org>
This commit is contained in:
Feanil Patel
2025-10-30 15:59:07 -04:00
committed by Kyle McCormick
parent 0a9f7898c6
commit 7c9f468d56
45 changed files with 31 additions and 5982 deletions

View File

@@ -122,7 +122,12 @@ class CourseWaffleFlagsSerializer(serializers.Serializer):
def get_use_new_video_uploads_page(self, obj):
"""
Method to get the use_new_video_uploads_page switch
Method to get the use_new_video_uploads_page switch.
This is off by default because the video uploads page requires the edX
video pipeline which is not available to the open source community.
See https://github.com/openedx/openedx-platform/issues/37972
"""
course_key = self.get_course_key()
return toggles.use_new_video_uploads_page(course_key)

View File

@@ -259,19 +259,22 @@ def use_new_export_page(course_key):
# .. toggle_name: contentstore.new_studio_mfe.use_new_video_uploads_page
# .. toggle_implementation: CourseWaffleFlag
# .. toggle_default: False
# .. toggle_description: This flag enables the use of the new studio video uploads page mfe
# .. toggle_use_cases: temporary
# .. toggle_creation_date: 2023-5-15
# .. toggle_target_removal_date: 2023-8-31
# .. toggle_tickets: TNL-10619
# .. toggle_warning:
# .. toggle_description: This flag enables the use of the new studio video uploads page MFE.
# Note: This page only works on edx.org or other sites that have reverse-engineered
# the edX video pipeline. It is off by default for the community.
# .. toggle_use_cases: opt_in
# .. toggle_creation_date: 2023-05-15
# .. toggle_tickets: https://github.com/openedx/openedx-platform/issues/37972
ENABLE_NEW_STUDIO_VIDEO_UPLOADS_PAGE = CourseWaffleFlag(
f'{CONTENTSTORE_NAMESPACE}.new_studio_mfe.use_new_video_uploads_page', __name__)
def use_new_video_uploads_page(course_key):
"""
Returns a boolean if new studio video uploads mfe is enabled
Returns a boolean if new studio video uploads MFE is enabled.
This is off by default because the video uploads page requires the edX
video pipeline which is not available to the open source community.
"""
return ENABLE_NEW_STUDIO_VIDEO_UPLOADS_PAGE.is_enabled(course_key)

View File

@@ -51,7 +51,6 @@ from cms.djangoapps.contentstore.toggles import (
use_new_import_page,
use_new_schedule_details_page,
use_new_unit_page,
use_new_video_uploads_page,
)
from cms.djangoapps.models.settings.course_grading import CourseGradingModel
from cms.djangoapps.models.settings.course_metadata import CourseMetadata
@@ -425,11 +424,10 @@ def get_video_uploads_url(course_locator) -> str:
Gets course authoring microfrontend URL for files and uploads page view.
"""
video_uploads_url = None
if use_new_video_uploads_page(course_locator):
mfe_base_url = get_course_authoring_url(course_locator)
course_mfe_url = f'{mfe_base_url}/course/{course_locator}/videos/'
if mfe_base_url:
video_uploads_url = course_mfe_url
mfe_base_url = get_course_authoring_url(course_locator)
course_mfe_url = f'{mfe_base_url}/course/{course_locator}/videos/'
if mfe_base_url:
video_uploads_url = course_mfe_url
return video_uploads_url

View File

@@ -50,7 +50,6 @@ from rest_framework.response import Response
from tempfile import NamedTemporaryFile, mkdtemp
from wsgiref.util import FileWrapper
from common.djangoapps.edxmako.shortcuts import render_to_response
from common.djangoapps.util.json_request import JsonResponse
from openedx.core.djangoapps.video_config.models import VideoTranscriptEnabledFlag
from openedx.core.djangoapps.video_config.toggles import PUBLIC_VIDEO_SHARE
@@ -62,8 +61,8 @@ from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
from .models import VideoUploadConfig
from .toggles import use_new_video_uploads_page, use_mock_video_uploads
from .utils import get_video_uploads_url, get_course_videos_context
from .toggles import use_mock_video_uploads
from .utils import get_video_uploads_url
from .video_utils import validate_video_image
from .views.course import get_course_and_check_access
@@ -740,13 +739,7 @@ def videos_index_html(course, pagination_conf=None):
"""
Returns an HTML page to display previous video uploads and allow new ones
"""
if use_new_video_uploads_page(course.id):
return redirect(get_video_uploads_url(course.id))
context = get_course_videos_context(
course,
pagination_conf,
)
return render_to_response('videos_index.html', context)
return redirect(get_video_uploads_url(course.id))
def videos_index_json(course):

View File

@@ -43,12 +43,10 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order
from ..videos import (
ENABLE_VIDEO_UPLOAD_PAGINATION,
KEY_EXPIRATION_IN_SECONDS,
VIDEO_IMAGE_UPLOAD_ENABLED,
)
from cms.djangoapps.contentstore.video_storage_handlers import (
_get_default_video_image_url,
TranscriptProvider,
StatusDisplayStrings,
convert_video_status,
@@ -478,24 +476,14 @@ class VideosHandlerTestCase(
if response_video['edx_video_id'] == self.previous_uploads[0]['edx_video_id']:
self.assertEqual(response_video.get('transcripts', []), expected_transcripts)
def test_get_html(self):
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
self.assertRegex(response["Content-Type"], "^text/html(;.*)?$")
self.assertContains(response, _get_default_video_image_url())
# Crude check for presence of data in returned HTML
for video in self.previous_uploads:
self.assertContains(response, video["edx_video_id"])
self.assertNotContains(response, 'video_upload_pagination')
@override_waffle_flag(ENABLE_VIDEO_UPLOAD_PAGINATION, active=True)
def test_get_html_paginated(self):
def test_get_redirects_to_video_uploads_url(self):
"""
Tests that response is paginated.
Test that GET requests redirect to the MFE video uploads page.
"""
from cms.djangoapps.contentstore.utils import get_video_uploads_url
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'video_upload_pagination')
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, get_video_uploads_url(self.course.id))
@override_settings(AWS_ACCESS_KEY_ID="test_key_id", AWS_SECRET_ACCESS_KEY="test_secret")
@patch("cms.djangoapps.contentstore.video_storage_handlers.boto3.resource")
@@ -869,23 +857,6 @@ class VideosHandlerTestCase(
self.assert_video_status(url, edx_video_id, expected_video_status_text)
@ddt.data(True, False)
@patch('openedx.core.djangoapps.video_config.models.VideoTranscriptEnabledFlag.feature_enabled')
def test_video_index_transcript_feature_enablement(self, is_video_transcript_enabled, video_transcript_feature):
"""
Test that when video transcript is enabled/disabled, correct response is rendered.
"""
video_transcript_feature.return_value = is_video_transcript_enabled
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
# Verify that course video button is present in the response if videos transcript feature is enabled.
button_html = '<button class="button course-video-settings-button">'
if is_video_transcript_enabled:
self.assertContains(response, button_html)
else:
self.assertNotContains(response, button_html)
@ddt.ddt
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_VIDEO_UPLOAD_PIPELINE": True})

View File

@@ -28,8 +28,7 @@
'js/factories/outline',
'js/factories/settings',
'js/factories/settings_advanced',
'js/factories/settings_graders',
'js/factories/videos_index'
'js/factories/settings_graders'
]),
/**
* By default all the configuration for optimization happens from the command

View File

@@ -248,12 +248,6 @@
'js/spec/utils/drag_and_drop_spec',
'js/spec/utils/handle_iframe_binding_spec',
'js/spec/utils/module_spec',
'js/spec/views/active_video_upload_list_spec',
'js/spec/views/previous_video_upload_spec',
'js/spec/views/video_thumbnail_spec',
'js/spec/views/course_video_settings_spec',
'js/spec/views/video_transcripts_spec',
'js/spec/views/previous_video_upload_list_spec',
'js/spec/views/assets_spec',
'js/spec/views/baseview_spec',
'js/spec/views/paged_container_spec',

View File

@@ -1,81 +0,0 @@
define([
'jquery', 'backbone', 'js/views/active_video_upload_list',
'js/views/previous_video_upload_list', 'js/views/active_video_upload'
], function($, Backbone, ActiveVideoUploadListView, PreviousVideoUploadListView, ActiveVideoUpload) {
'use strict';
var VideosIndexFactory = function(
$contentWrapper,
videoImageUploadURL,
videoHandlerUrl,
encodingsDownloadUrl,
defaultVideoImageURL,
concurrentUploadLimit,
courseVideoSettingsButton,
previousUploads,
videoSupportedFileFormats,
videoUploadMaxFileSizeInGB,
activeTranscriptPreferences,
transcriptOrganizationCredentials,
videoTranscriptSettings,
isVideoTranscriptEnabled,
videoImageSettings,
transcriptAvailableLanguages
) {
var activeView = new ActiveVideoUploadListView({
postUrl: videoHandlerUrl,
concurrentUploadLimit: concurrentUploadLimit,
courseVideoSettingsButton: courseVideoSettingsButton,
videoSupportedFileFormats: videoSupportedFileFormats,
videoUploadMaxFileSizeInGB: videoUploadMaxFileSizeInGB,
videoImageSettings: videoImageSettings,
activeTranscriptPreferences: activeTranscriptPreferences,
transcriptOrganizationCredentials: transcriptOrganizationCredentials,
videoTranscriptSettings: videoTranscriptSettings,
isVideoTranscriptEnabled: isVideoTranscriptEnabled,
onFileUploadDone: function(activeVideos) {
$.ajax({
url: videoHandlerUrl,
contentType: 'application/json',
dataType: 'json',
type: 'GET'
}).done(function(responseData) {
var updatedCollection = new Backbone.Collection(responseData.videos).filter(function(video) {
// Include videos that are not in the active video upload list,
// or that are marked as Upload Complete
var isActive = activeVideos.where({videoId: video.get('edx_video_id')});
return isActive.length === 0
|| isActive[0].get('status') === ActiveVideoUpload.STATUS_COMPLETE;
}),
updatedView = new PreviousVideoUploadListView({
videoImageUploadURL: videoImageUploadURL,
defaultVideoImageURL: defaultVideoImageURL,
videoHandlerUrl: videoHandlerUrl,
collection: updatedCollection,
encodingsDownloadUrl: encodingsDownloadUrl,
videoImageSettings: videoImageSettings,
videoTranscriptSettings: videoTranscriptSettings,
transcriptAvailableLanguages: transcriptAvailableLanguages,
videoSupportedFileFormats: videoSupportedFileFormats
});
$contentWrapper.find('.wrapper-assets').replaceWith(updatedView.render().$el);
});
}
}),
previousView = new PreviousVideoUploadListView({
videoImageUploadURL: videoImageUploadURL,
defaultVideoImageURL: defaultVideoImageURL,
videoHandlerUrl: videoHandlerUrl,
collection: new Backbone.Collection(previousUploads),
encodingsDownloadUrl: encodingsDownloadUrl,
videoImageSettings: videoImageSettings,
videoTranscriptSettings: videoTranscriptSettings,
transcriptAvailableLanguages: transcriptAvailableLanguages,
videoSupportedFileFormats: videoSupportedFileFormats
});
$contentWrapper.append(activeView.render().$el);
$contentWrapper.append(previousView.render().$el);
};
return VideosIndexFactory;
});

View File

@@ -1,38 +0,0 @@
define(
['backbone', 'gettext'],
function(Backbone, gettext) {
'use strict';
var statusStrings = {
// Translators: This is the status of a video upload that is queued
// waiting for other uploads to complete
STATUS_QUEUED: gettext('Queued'),
// Translators: This is the status of an active video upload
STATUS_UPLOADING: gettext('Uploading'),
// Translators: This is the status of a video upload that has
// completed successfully
STATUS_COMPLETED: gettext('Upload completed'),
// Translators: This is the status of a video upload that has failed
STATUS_FAILED: gettext('Upload failed')
};
var ActiveVideoUpload = Backbone.Model.extend(
{
defaults: {
videoId: null,
status: statusStrings.STATUS_QUEUED,
progress: 0,
failureMessage: null
},
uploading: function() {
var status = this.get('status');
return (this.get('progress') < 1) && ((status === statusStrings.STATUS_UPLOADING));
}
},
statusStrings
);
return ActiveVideoUpload;
}
);

View File

@@ -3,7 +3,7 @@ define(
'jquery', 'underscore', 'backbone',
'js/views/video/transcripts/utils', 'js/views/video/transcripts/message_manager',
'js/views/video/transcripts/file_uploader', 'sinon',
'xmodule'
'xmodule', 'accessibility'
],
function($, _, Backbone, Utils, MessageManager, FileUploader, sinon) {
'use strict';

View File

@@ -1,625 +0,0 @@
/* global _ */
define(
[
'jquery',
'js/models/active_video_upload',
'js/views/active_video_upload_list',
'edx-ui-toolkit/js/utils/string-utils',
'common/js/spec_helpers/template_helpers',
'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
'accessibility',
'mock-ajax'
],
function($, ActiveVideoUpload, ActiveVideoUploadListView, StringUtils, TemplateHelpers, AjaxHelpers) {
'use strict';
var concurrentUploadLimit = 2,
POST_URL = '/test/post/url',
VIDEO_ID = 'video101',
UPLOAD_STATUS = {
s3Fail: 's3_upload_failed',
fail: 'upload_failed',
success: 'upload_completed'
},
videoUploadMaxFileSizeInGB = 5,
videoSupportedFileFormats = ['.mp4', '.mov'],
createActiveUploadListView,
$courseVideoSettingsButton,
makeUploadUrl,
getSentRequests,
verifyUploadViewInfo,
getStatusUpdateRequest,
verifyStatusUpdateRequest,
sendUploadPostResponse,
verifyA11YMessage,
verifyUploadPostRequest;
createActiveUploadListView = function(isVideoTranscriptEnabled) {
return new ActiveVideoUploadListView({
concurrentUploadLimit: concurrentUploadLimit,
postUrl: POST_URL,
courseVideoSettingsButton: $courseVideoSettingsButton,
videoSupportedFileFormats: videoSupportedFileFormats,
videoUploadMaxFileSizeInGB: videoUploadMaxFileSizeInGB,
activeTranscriptPreferences: {},
videoTranscriptSettings: {
transcript_preferences_handler_url: '',
transcription_plans: null
},
isVideoTranscriptEnabled: isVideoTranscriptEnabled
});
};
describe('ActiveVideoUploadListView', function() {
beforeEach(function() {
setFixtures(
'<div id="page-prompt"></div>'
+ '<div id="page-notification"></div>'
+ '<div id="reader-feedback"></div>'
+ '<div class="video-transcript-settings-wrapper"></div>'
+ '<button class="button course-video-settings-button"></button>'
);
TemplateHelpers.installTemplate('active-video-upload');
TemplateHelpers.installTemplate('active-video-upload-list');
$courseVideoSettingsButton = $('.course-video-settings-button');
this.view = createActiveUploadListView(true);
this.view.render();
jasmine.Ajax.install();
});
// Remove window unload handler triggered by the upload requests
afterEach(function() {
$(window).off('beforeunload');
jasmine.Ajax.uninstall();
if (this.view.courseVideoSettingsView) {
this.view.courseVideoSettingsView = null;
}
});
it('renders correct text in file drag/drop area', function() {
var messages = {
'.video-uploads-header': this.view.uploadHeader,
'.video-upload-text': this.view.uploadText.toString(),
'.video-max-file-size-text': this.view.maxSizeText,
'.video-allowed-extensions-text': this.view.supportedVideosText
},
self = this;
Object.keys(messages).forEach(function(messageClass) {
expect(self.view.$(messageClass).html()).toEqual(messages[messageClass]);
});
});
it('should trigger file selection when the drop zone is clicked', function() {
var clickSpy = jasmine.createSpy();
clickSpy.and.callFake(function(event) { event.preventDefault(); });
this.view.$('.js-file-input').on('click', clickSpy);
this.view.$('.file-drop-area').click();
expect(clickSpy).toHaveBeenCalled();
clickSpy.calls.reset();
});
it('shows course video settings pane when course video settings button is clicked', function() {
$courseVideoSettingsButton.click();
expect(this.view.courseVideoSettingsView).toBeDefined();
expect(this.view.courseVideoSettingsView.$el.find('.course-video-settings-container')).toExist();
});
it('should not initiate course video settings view when video transcript is disabled', function() {
this.view = createActiveUploadListView(false);
$courseVideoSettingsButton.click();
expect(this.view.courseVideoSettingsView).toBeUndefined();
});
it('should not show a notification message if there are no active video uploads', function() {
expect(this.view.onBeforeUnload()).toBeUndefined();
});
makeUploadUrl = function(fileName) {
return 'http://www.example.com/test_url/' + fileName;
};
getSentRequests = function() {
return jasmine.Ajax.requests.filter(function(request) {
return request.readyState > 0;
});
};
verifyUploadViewInfo = function(view, expectedTitle, expectedMessage) {
expect(view.$('.video-detail-status').text().trim()).toEqual('Upload failed');
expect(view.$('.more-details-action').contents().get(0).nodeValue.trim()).toEqual('Read More');
expect(view.$('.more-details-action .sr').text().trim()).toEqual('details about the failure');
view.$('a.more-details-action').click();
expect($('#prompt-warning-title').text().trim()).toEqual(expectedTitle);
expect($('#prompt-warning-description').text().trim()).toEqual(expectedMessage);
};
getStatusUpdateRequest = function() {
var sentRequests = getSentRequests();
return sentRequests.filter(function(request) {
return request.method === 'POST' && _.has(
JSON.parse(request.params)[0], 'status'
);
})[0];
};
verifyStatusUpdateRequest = function(videoId, status, message, expectedRequest) {
var request = expectedRequest || getStatusUpdateRequest(),
expectedData = JSON.stringify({
edxVideoId: videoId,
status: status,
message: message
});
expect(request.method).toEqual('POST');
expect(request.url).toEqual(POST_URL);
if (_.has(request, 'requestBody')) {
expect(_.isMatch(request.requestBody, expectedData)).toBeTruthy();
} else {
expect(_.isMatch(request.params, expectedData)).toBeTruthy();
}
};
verifyUploadPostRequest = function(requestParams) {
// get latest requestParams.length requests
var postRequests = getSentRequests().slice(-requestParams.length);
_.each(postRequests, function(postRequest, index) {
expect(postRequest.method).toEqual('POST');
expect(postRequest.url).toEqual(POST_URL);
expect(postRequest.params).toEqual(requestParams[index]);
});
};
verifyA11YMessage = function(message) {
expect($('#reader-feedback').text().trim()).toEqual(message);
};
sendUploadPostResponse = function(request, fileNames, url) {
request.respondWith({
status: 200,
responseText: JSON.stringify({
files: _.map(
fileNames,
function(fileName) {
return {
edx_video_id: VIDEO_ID,
file_name: fileName,
upload_url: url || makeUploadUrl(fileName)
};
}
)
})
});
};
describe('errors', function() {
it('should show error notification for status update request in case of server error', function() {
this.view.sendStatusUpdate([
{
edxVideoId: '101',
status: 'upload_completed',
message: 'Uploaded completed'
}
]);
getStatusUpdateRequest().respondWith(
{
status: 500,
responseText: JSON.stringify({
error: '500 server errror'
})
}
);
expect($('#notification-error-title').text().trim()).toEqual(
"Studio's having trouble saving your work"
);
expect($('#notification-error-description').text().trim()).toEqual('500 server errror');
});
it('should correctly parse and show S3 error response xml', function() {
var fileInfo = {name: 'video.mp4', size: 10000},
videos = {
files: [
fileInfo
]
},
S3Url = 'http://s3.aws.com/upload/videos/' + fileInfo.name,
requests;
// this is required so that we can use AjaxHelpers ajax mock utils instead of jasmine mock-ajax.js
jasmine.Ajax.uninstall();
requests = AjaxHelpers.requests(this);
this.view.$uploadForm.fileupload('add', videos);
AjaxHelpers.respond(requests, {
status: 200,
body: {
files: [{
edx_video_id: VIDEO_ID,
file_name: fileInfo.name,
upload_url: S3Url
}]
}
});
expect(requests.length).toEqual(2);
AjaxHelpers.respond(
requests,
{
statusCode: 403,
contentType: 'application/xml',
body: '<Error><Message>Invalid access key.</Message></Error>'
}
);
verifyUploadViewInfo(
this.view.itemViews[0],
'Your file could not be uploaded',
'Invalid access key.'
);
verifyStatusUpdateRequest(
VIDEO_ID,
UPLOAD_STATUS.s3Fail,
'Invalid access key.',
AjaxHelpers.currentRequest(requests)
);
verifyA11YMessage(
StringUtils.interpolate('Upload failed for video {fileName}', {fileName: fileInfo.name})
);
// this is required otherwise mock-ajax will throw an exception when it tries to uninstall Ajax in
// outer afterEach
jasmine.Ajax.install();
});
});
describe('upload cancelled', function() {
it('should send correct status update request', function() {
var fileInfo = {name: 'video.mp4'},
videos = {
files: [
fileInfo
]
},
sentRequests,
uploadCancelledRequest;
this.view.$uploadForm.fileupload('add', videos);
sendUploadPostResponse(getSentRequests()[0], [fileInfo.name]);
sentRequests = getSentRequests();
// no upload cancel request should be sent because `uploading` attribute is not set on model
this.view.onUnload();
expect(getSentRequests().length).toEqual(sentRequests.length);
// set `uploading` attribute on each model
this.view.collection.each(function(model) {
model.set('uploading', true);
});
// upload_cancelled request should be sent
this.view.onUnload();
uploadCancelledRequest = jasmine.Ajax.requests.mostRecent();
expect(uploadCancelledRequest.params).toEqual(
JSON.stringify(
[{
edxVideoId: VIDEO_ID,
status: 'upload_cancelled',
message: 'User cancelled video upload'
}]
)
);
});
});
describe('file formats', function() {
it('should not fail upload for supported file formats', function() {
var supportedFiles = {
files: [
{name: 'test-1.mp4'},
{name: 'test-1.mov'}
]
},
requestParams = _.map(supportedFiles.files, function(file) {
return JSON.stringify({files: [{file_name: file.name}]});
});
this.view.$uploadForm.fileupload('add', supportedFiles);
verifyUploadPostRequest(requestParams);
});
it('should fail upload for unspported file formats', function() {
var files = [
{name: 'test-3.txt', size: 0},
{name: 'test-4.png', size: 0}
],
unSupportedFiles = {
files: files
},
self = this;
this.view.$uploadForm.fileupload('add', unSupportedFiles);
_.each(this.view.itemViews, function(uploadView, index) {
verifyUploadViewInfo(
uploadView,
'Your file could not be uploaded',
StringUtils.interpolate(
'{fileName} is not in a supported file format. Supported file formats are {supportedFormats}.', // eslint-disable-line max-len
{fileName: files[index].name, supportedFormats: videoSupportedFileFormats.join(' and ')} // eslint-disable-line max-len
)
);
});
});
});
describe('Upload file', function() {
_.each(
[
{desc: 'larger than', additionalBytes: 1},
{desc: 'equal to', additionalBytes: 0},
{desc: 'smaller than', additionalBytes: -1}
],
function(caseInfo) {
it(caseInfo.desc + 'max file size', function() {
var maxFileSizeInBytes = this.view.getMaxFileSizeInBytes(),
fileSize = maxFileSizeInBytes + caseInfo.additionalBytes,
fileToUpload = {
files: [
{name: 'file.mp4', size: fileSize}
]
},
requestParams = _.map(fileToUpload.files, function(file) {
return JSON.stringify({files: [{file_name: file.name}]});
}),
uploadView;
this.view.$uploadForm.fileupload('add', fileToUpload);
if (fileSize > maxFileSizeInBytes) {
uploadView = this.view.itemViews[0];
verifyUploadViewInfo(
uploadView,
'Your file could not be uploaded',
'file.mp4 exceeds maximum size of ' + videoUploadMaxFileSizeInGB + ' GB.'
);
verifyA11YMessage(
StringUtils.interpolate(
'Upload failed for video {fileName}', {fileName: 'file.mp4'}
)
);
} else {
verifyUploadPostRequest(requestParams);
sendUploadPostResponse(
getSentRequests()[0],
[fileToUpload.files[0].name]
);
getSentRequests()[1].respondWith(
{status: 200}
);
verifyStatusUpdateRequest(
VIDEO_ID,
UPLOAD_STATUS.success,
'Uploaded completed'
);
verifyA11YMessage(
StringUtils.interpolate(
'Upload completed for video {fileName}', {fileName: fileToUpload.files[0].name}
)
);
}
});
}
);
});
_.each(
[
{desc: 'a single file', numFiles: 1},
{desc: 'multiple files', numFiles: concurrentUploadLimit},
{desc: 'more files than upload limit', numFiles: concurrentUploadLimit + 1}
],
function(caseInfo) {
var fileNames = _.map(
_.range(caseInfo.numFiles),
function(i) { return 'test' + i + '.mp4'; }
);
describe('on selection of ' + caseInfo.desc, function() {
beforeEach(function() {
// The files property cannot be set on a file input for
// security reasons, so we must mock the access mechanism
// that jQuery-File-Upload uses to retrieve it.
var realProp = $.prop;
spyOn($, 'prop').and.callFake(function(el, propName) {
if (arguments.length === 2 && propName === 'files') {
return _.map(
fileNames,
function(fileName) { return {name: fileName}; }
);
} else {
realProp.apply(this, arguments);
}
});
this.view.$('.js-file-input').change();
this.request = jasmine.Ajax.requests.mostRecent();
});
it('should trigger the correct request', function() {
var request,
self = this;
expect(jasmine.Ajax.requests.count()).toEqual(caseInfo.numFiles);
_.each(_.range(caseInfo.numFiles), function(index) {
request = jasmine.Ajax.requests.at(index);
expect(request.url).toEqual(POST_URL);
expect(request.method).toEqual('POST');
expect(request.requestHeaders['Content-Type']).toEqual('application/json');
expect(request.requestHeaders.Accept).toContain('application/json');
expect(JSON.parse(request.params)).toEqual({
files: [{file_name: fileNames[index]}]
});
});
});
describe('and successful server response', function() {
beforeEach(function() {
jasmine.Ajax.requests.reset();
sendUploadPostResponse(this.request, fileNames);
this.$uploadElems = this.view.$('.active-video-upload');
});
it('should start uploads', function() {
var spec = this;
var sentRequests = getSentRequests();
expect(sentRequests.length).toEqual(
_.min([concurrentUploadLimit, caseInfo.numFiles])
);
_.each(
sentRequests,
function(uploadRequest, i) {
expect(uploadRequest.url).toEqual(
makeUploadUrl(fileNames[i])
);
expect(uploadRequest.method).toEqual('PUT');
}
);
});
it('should display upload status and progress', function() {
expect(this.$uploadElems.length).toEqual(caseInfo.numFiles);
this.$uploadElems.each(function(i, uploadElem) {
var $uploadElem = $(uploadElem);
var queued = i >= concurrentUploadLimit;
expect($.trim($uploadElem.find('.video-detail-name').text())).toEqual(
fileNames[i]
);
expect($.trim($uploadElem.find('.video-detail-status').text())).toEqual(
queued
? ActiveVideoUpload.STATUS_QUEUED
: ActiveVideoUpload.STATUS_UPLOADING
);
expect($uploadElem.find('.video-detail-progress').val()).toEqual(0);
expect($uploadElem).not.toHaveClass('success');
expect($uploadElem).not.toHaveClass('error');
expect($uploadElem.hasClass('queued')).toEqual(queued);
});
});
it('should show a notification message when there are active video uploads', function() {
expect(this.view.onBeforeUnload()).toBe('Your video uploads are not complete.');
});
// TODO: test progress update; the libraries we are using to mock ajax
// do not currently support progress events. If we upgrade to Jasmine
// 2.0, the latest version of jasmine-ajax (mock-ajax.js) does have the
// necessary support.
_.each([true, false],
function(isViewRefresh) {
var refreshDescription = isViewRefresh ? ' (refreshed)' : ' (not refreshed)';
var subCases = [
{
desc: 'completion' + refreshDescription,
responseStatus: 204,
statusText: ActiveVideoUpload.STATUS_COMPLETED,
progressValue: 1,
presentClass: 'success',
absentClass: 'error',
isViewRefresh: isViewRefresh
},
{
desc: 'failure' + refreshDescription,
responseStatus: 500,
statusText: ActiveVideoUpload.STATUS_FAILED,
progressValue: 0,
presentClass: 'error',
absentClass: 'success',
isViewRefresh: isViewRefresh
}
];
_.each(subCases,
function(subCaseInfo) {
describe('and upload ' + subCaseInfo.desc, function() {
var refreshSpy = null;
beforeEach(function() {
refreshSpy = subCaseInfo.isViewRefresh ? jasmine.createSpy() : null;
this.view.onFileUploadDone = refreshSpy;
getSentRequests()[0].respondWith(
{status: subCaseInfo.responseStatus}
);
// after successful upload, status update request is sent to server
// we re-render views after success response is received from server
if (subCaseInfo.statusText === ActiveVideoUpload.STATUS_COMPLETED) {
getStatusUpdateRequest().respondWith(
{status: 200}
);
}
});
it('should update status and progress', function() {
var $uploadElem = this.view.$('.active-video-upload:first');
if (subCaseInfo.isViewRefresh
&& subCaseInfo.responseStatus === 204) {
expect(refreshSpy).toHaveBeenCalled();
if ($uploadElem.length > 0) {
expect(
$.trim($uploadElem.find('.video-detail-status').text())
).not.toEqual(ActiveVideoUpload.STATUS_COMPLETED);
expect(
$uploadElem.find('.video-detail-progress').val()
).not.toEqual(1);
expect($uploadElem).not.toHaveClass('success');
}
} else {
expect($uploadElem.length).toEqual(1);
expect(
$.trim($uploadElem.find('.video-detail-status').text())
).toEqual(subCaseInfo.statusText);
expect(
$uploadElem.find('.video-detail-progress').val()
).toEqual(subCaseInfo.progressValue);
expect($uploadElem).toHaveClass(subCaseInfo.presentClass);
expect($uploadElem).not.toHaveClass(subCaseInfo.absentClass);
}
});
if (caseInfo.numFiles > concurrentUploadLimit) {
it('should start a new upload', function() {
var $uploadElem = $(this.$uploadElems[concurrentUploadLimit]);
// we try to upload 3 files. 2 files(2 requests) will start
// uploading immediately and third one will be queued, after
// an upload is completed, queued file(3rd request) will start
// uploading, 4th request will be sent to server to update
// status for completed upload
expect(getSentRequests().length).toEqual(
concurrentUploadLimit + 1 + 1
);
expect(
$.trim($uploadElem.find('.video-detail-status').text())
).toEqual(ActiveVideoUpload.STATUS_UPLOADING);
expect($uploadElem).not.toHaveClass('queued');
});
}
// If we're uploading more files than the one we've closed above,
// the unload warning should still be shown
if (caseInfo.numFiles > 1) {
it('should show notification when videos are still uploading',
function() {
expect(this.view.onBeforeUnload()).toBe(
'Your video uploads are not complete.');
});
} else {
it('should not show notification once video uploads are complete',
function() {
expect(this.view.onBeforeUnload()).toBeUndefined();
});
}
});
}
);
}
);
});
});
}
);
});
}
);

View File

@@ -1,805 +0,0 @@
define(
['jquery', 'underscore', 'backbone', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
'js/views/course_video_settings', 'common/js/spec_helpers/template_helpers'],
function($, _, Backbone, AjaxHelpers, CourseVideoSettingsView, TemplateHelpers) {
'use strict';
describe('CourseVideoSettingsView', function() {
var $courseVideoSettingsEl,
courseVideoSettingsView,
renderCourseVideoSettingsView,
destroyCourseVideoSettingsView,
verifyTranscriptPreferences,
verifyTranscriptPreferencesView,
verifyOrganizationCredentialsView,
verifyCredentialFieldsPresent,
verifyOrganizationCredentialField,
verifyMessage,
verifyPreferanceErrorState,
selectPreference,
verifyProviderList,
verifyProviderSelectedView,
verifyCredentialsSaved,
resetProvider,
changeProvider,
submitOrganizationCredentials,
transcriptPreferencesUrl = '/transcript_preferences/course-v1:edX+DemoX+Demo_Course',
transcriptCredentialsHandlerUrl = '/transcript_credentials/course-v1:edX+DemoX+Demo_Course',
INTERNAL_SERVER_ERROR = 'An error has occurred. Wait a few minutes, and then try again.',
activeTranscriptPreferences = {
provider: 'Cielo24',
cielo24_fidelity: 'PROFESSIONAL',
cielo24_turnaround: 'PRIORITY',
three_play_turnaround: '',
video_source_language: 'en',
preferred_languages: ['fr', 'en'],
modified: '2017-08-27T12:28:17.421260Z'
},
transcriptOrganizationCredentials = {
Cielo24: true,
'3PlayMedia': true
},
transcriptionPlans = {
'3PlayMedia': {
languages: {
fr: 'French',
en: 'English',
ur: 'Urdu'
},
turnaround: {
default: '4-Day/Default',
same_day_service: 'Same Day',
rush_service: '24-hour/Rush',
extended_service: '10-Day/Extended',
expedited_service: '2-Day/Expedited'
},
translations: {
es: ['en'],
en: ['en', 'ur']
},
display_name: '3PlayMedia'
},
Cielo24: {
turnaround: {
PRIORITY: 'Priority, 24h',
STANDARD: 'Standard, 48h'
},
fidelity: {
PROFESSIONAL: {
languages: {
ru: 'Russian',
fr: 'French',
en: 'English'
},
display_name: 'Professional, 99% Accuracy'
},
PREMIUM: {
languages: {
en: 'English'
},
display_name: 'Premium, 95% Accuracy'
},
MECHANICAL: {
languages: {
fr: 'French',
en: 'English',
nl: 'Dutch'
},
display_name: 'Mechanical, 75% Accuracy'
}
},
display_name: 'Cielo24'
}
},
providers = {
none: {
key: 'none',
value: '',
displayName: 'None'
},
Cielo24: {
key: 'Cielo24',
value: 'Cielo24',
displayName: 'Cielo24'
},
'3PlayMedia': {
key: '3PlayMedia',
value: '3PlayMedia',
displayName: '3Play Media'
}
};
renderCourseVideoSettingsView = function(activeTranscriptPreferencesData, transcriptionPlansData, transcriptOrganizationCredentialsData) { // eslint-disable-line max-len
// First destroy old referance to the view if present.
destroyCourseVideoSettingsView();
courseVideoSettingsView = new CourseVideoSettingsView({
activeTranscriptPreferences: activeTranscriptPreferencesData || null,
videoTranscriptSettings: {
transcript_preferences_handler_url: transcriptPreferencesUrl,
transcript_credentials_handler_url: transcriptCredentialsHandlerUrl,
transcription_plans: transcriptionPlansData || null
},
transcriptOrganizationCredentials: transcriptOrganizationCredentialsData || null
});
$courseVideoSettingsEl = courseVideoSettingsView.render().$el;
};
destroyCourseVideoSettingsView = function() {
if (courseVideoSettingsView) {
courseVideoSettingsView.closeCourseVideoSettings();
courseVideoSettingsView = null;
}
};
verifyPreferanceErrorState = function($preferanceContainerEl, hasError) {
var $errorIconHtml = hasError ? '<span class="icon fa fa-info-circle" aria-hidden="true"></span>' : '',
requiredText = hasError ? 'Required' : '';
expect($preferanceContainerEl.hasClass('error')).toEqual(hasError);
expect($preferanceContainerEl.find('.error-icon').html()).toEqual($errorIconHtml);
expect($preferanceContainerEl.find('.error-info').html()).toEqual(requiredText);
};
selectPreference = function(preferenceSelector, preferanceValue) {
var $preference = $courseVideoSettingsEl.find(preferenceSelector);
// Select a vlaue for preference.
$preference.val(preferanceValue);
// Trigger on change event.
$preference.change();
};
verifyMessage = function(state, message) {
var icon = state === 'error' ? 'fa-info-circle' : 'fa-check-circle';
expect($courseVideoSettingsEl.find('.course-video-settings-message-wrapper.' + state).html()).toEqual(
'<div class="course-video-settings-message">'
+ '<span class="icon fa ' + icon + '" aria-hidden="true"></span>'
+ '<span>' + message + '</span>'
+ '</div>'
);
};
verifyProviderList = function(selectedProvider) {
var $transcriptProvidersListEl = $courseVideoSettingsEl.find('.transcript-provider-wrapper .transcript-provider-group'); // eslint-disable-line max-len
// Check None provider is selected.
expect($transcriptProvidersListEl.find('input[type=radio]:checked').val()).toEqual(selectedProvider.value); // eslint-disable-line max-len
_.each(providers, function(provider, key) {
$transcriptProvidersListEl.find('label[for=transcript-provider-' + key + ']').val(provider.displayName); // eslint-disable-line max-len
});
};
verifyTranscriptPreferences = function() {
expect($courseVideoSettingsEl.find('#transcript-turnaround').val()).toEqual(
activeTranscriptPreferences.cielo24_turnaround
);
expect($courseVideoSettingsEl.find('#transcript-fidelity').val()).toEqual(
activeTranscriptPreferences.cielo24_fidelity
);
expect($courseVideoSettingsEl.find('.transcript-language-container').length).toEqual(
activeTranscriptPreferences.preferred_languages.length
);
// Now check values are assigned correctly.
expect(courseVideoSettingsView.selectedTurnaroundPlan, activeTranscriptPreferences.cielo24_turnaround);
expect(courseVideoSettingsView.selectedFidelityPlan, activeTranscriptPreferences.cielo24_fidelity);
expect(courseVideoSettingsView.selectedLanguages, activeTranscriptPreferences.preferred_languages);
};
verifyProviderSelectedView = function() {
// Verify provider
expect(
$courseVideoSettingsEl.find('.selected-transcript-provider .title').html()
).toEqual(courseVideoSettingsView.selectedProvider);
expect($courseVideoSettingsEl.find('.selected-transcript-provider .action-change-provider')).toExist();
expect(
$courseVideoSettingsEl.find('.selected-transcript-provider .action-change-provider .sr').html()
).toEqual('Press change to change selected transcript provider.');
};
verifyTranscriptPreferencesView = function() {
expect($courseVideoSettingsEl.find('.course-video-transcript-preferances-wrapper')).toExist();
};
verifyOrganizationCredentialsView = function() {
expect($courseVideoSettingsEl.find('.organization-credentials-content')).toExist();
};
verifyCredentialFieldsPresent = function(fields) {
// Verify correct number of input fields are shown.
expect(
$courseVideoSettingsEl.find(
'.organization-credentials-wrapper .transcript-preferance-wrapper input'
).length
).toEqual(_.keys(fields).length
);
// Verify individual field has correct label and key.
_.each(fields, function(label, fieldName) {
verifyOrganizationCredentialField(fieldName, label);
});
};
verifyOrganizationCredentialField = function(fieldName, label) {
var elementSelector = courseVideoSettingsView.selectedProvider + '-' + fieldName;
// Verify that correct label is shown.
expect(
$courseVideoSettingsEl.find('.' + elementSelector + '-wrapper label .title').html()
).toEqual(label);
// Verify that credential field is shown.
expect(
$courseVideoSettingsEl.find('.' + elementSelector + '-wrapper .' + elementSelector)
).toExist();
};
verifyCredentialsSaved = function() {
// Verify that success message is shown.
verifyMessage(
'success',
transcriptionPlans[courseVideoSettingsView.selectedProvider].display_name + ' credentials saved'
);
// Also verify that transcript credential state is updated.
expect(
courseVideoSettingsView.transcriptOrganizationCredentials[courseVideoSettingsView.selectedProvider]
).toBeTruthy();
// Verify that selected provider view after credentials are saved.
verifyProviderSelectedView();
};
changeProvider = function(selectedProvider) {
// If Provider Selected view is show, first click on "Change Provider" button to
// show all list of providers.
if ($courseVideoSettingsEl.find('.selected-transcript-provider').length) {
$courseVideoSettingsEl.find('.selected-transcript-provider .action-change-provider').click();
}
$courseVideoSettingsEl.find('#transcript-provider-' + selectedProvider).click();
};
resetProvider = function() {
var requests = AjaxHelpers.requests(this);
// Set no provider selected
changeProvider('none');
$courseVideoSettingsEl.find('.action-update-course-video-settings').click();
AjaxHelpers.expectRequest(
requests,
'DELETE',
transcriptPreferencesUrl
);
// Send successful empty content response.
AjaxHelpers.respondWithJson(requests, {});
};
submitOrganizationCredentials = function(fieldValues, statusCode, errorMessage) {
var requests = AjaxHelpers.requests(this);
// Click change button to render organization credentials view.
$courseVideoSettingsEl.find('.action-change-provider').click();
// Provide organization credentials.
_.each(fieldValues, function(key) {
$courseVideoSettingsEl.find('.' + courseVideoSettingsView.selectedProvider + '-' + key).val(key);
});
// Click save organization credentials button to save credentials.
$courseVideoSettingsEl.find('.action-update-org-credentials').click();
AjaxHelpers.expectRequest(
requests,
'POST',
transcriptCredentialsHandlerUrl,
JSON.stringify(
_.extend(
{provider: courseVideoSettingsView.selectedProvider},
fieldValues,
{global: false}
)
)
);
if (statusCode === 400) {
// Send bad request error response.
AjaxHelpers.respondWithError(requests, statusCode, {error: errorMessage});
} else if (statusCode === 500) {
// Send internal server error response.
AjaxHelpers.respondWithError(requests, statusCode);
} else {
// Send empty response.
AjaxHelpers.respondWithJson(requests, {});
}
};
beforeEach(function() {
setFixtures(
'<div class="video-transcript-settings-wrapper"></div>'
+ '<button class="button course-video-settings-button"></button>'
);
TemplateHelpers.installTemplate('course-video-settings');
renderCourseVideoSettingsView(activeTranscriptPreferences, transcriptionPlans);
});
afterEach(function() {
destroyCourseVideoSettingsView();
});
it('renders as expected', function() {
expect($courseVideoSettingsEl.find('.course-video-settings-container')).toExist();
});
it('closes course video settings pane when close button is clicked', function() {
expect($courseVideoSettingsEl.find('.course-video-settings-container')).toExist();
$courseVideoSettingsEl.find('.action-close-course-video-settings').click();
expect($courseVideoSettingsEl.find('.course-video-settings-container')).not.toExist();
});
it('closes course video settings pane when clicked outside course video settings pane', function() {
expect($courseVideoSettingsEl.find('.course-video-settings-container')).toExist();
$('body').click();
expect($courseVideoSettingsEl.find('.course-video-settings-container')).not.toExist();
});
it('does not close course video settings pane when clicked inside course video settings pane', function() {
expect($courseVideoSettingsEl.find('.course-video-settings-container')).toExist();
$courseVideoSettingsEl.find('.transcript-provider-group').click();
expect($courseVideoSettingsEl.find('.course-video-settings-container')).toExist();
});
it('does not populate transcription plans if transcription plans are not provided', function() {
// Create view with empty data.
renderCourseVideoSettingsView();
// Checking turnaround is sufficient to check preferences are are shown or not.
expect($courseVideoSettingsEl.find('.transcript-turnaround-wrapper')).not.toExist();
});
it('populates transcription plans correctly', function() {
// Only check transcript-provider radio buttons for now, because other preferances are based on either
// user selection or activeTranscriptPreferences.
expect($courseVideoSettingsEl.find('.transcript-provider-group').html()).not.toEqual('');
});
it('populates active preferances correctly', function() {
// First check preferance are selected correctly in HTML.
verifyTranscriptPreferences();
});
it('resets to active preferences when clicked on cancel', function() {
var selectedProvider = '3PlayMedia';
renderCourseVideoSettingsView(
activeTranscriptPreferences,
transcriptionPlans,
transcriptOrganizationCredentials
);
// First check preferance are selected correctly in HTML.
verifyTranscriptPreferences();
expect(courseVideoSettingsView.selectedProvider, providers.Cielo24);
// Now change preferences.
// Select provider.
changeProvider(selectedProvider);
expect(courseVideoSettingsView.selectedProvider, selectedProvider);
// Select turnaround.
selectPreference(
'.transcript-turnaround',
transcriptionPlans[selectedProvider].turnaround.default
);
expect(
courseVideoSettingsView.selectedTurnaroundPlan,
transcriptionPlans[selectedProvider].turnaround.default
);
// Now click cancel button and verify active preferences are shown.
$courseVideoSettingsEl.find('.action-cancel-course-video-settings').click();
verifyTranscriptPreferences();
expect(courseVideoSettingsView.selectedProvider, providers.Cielo24);
});
it('shows video source language directly in case of 3Play provider', function() {
var sourceLanguages,
selectedProvider = '3PlayMedia';
renderCourseVideoSettingsView(null, transcriptionPlans, transcriptOrganizationCredentials);
// Select provider
changeProvider(selectedProvider);
expect(courseVideoSettingsView.selectedProvider).toEqual(selectedProvider);
// Verify source langauges menu is shown.
sourceLanguages = courseVideoSettingsView.getSourceLanguages();
expect($courseVideoSettingsEl.find('.video-source-language option')).toExist();
expect($courseVideoSettingsEl.find('.video-source-language option').length).toEqual(
_.keys(sourceLanguages).length + 1
);
expect(_.keys(transcriptionPlans[selectedProvider].translations)).toEqual(_.keys(sourceLanguages));
});
it('shows source language when fidelity is selected', function() {
var sourceLanguages,
selectedProvider = 'Cielo24',
selectedFidelity = 'PROFESSIONAL';
renderCourseVideoSettingsView(null, transcriptionPlans, transcriptOrganizationCredentials);
// Select provider
changeProvider(selectedProvider);
expect(courseVideoSettingsView.selectedProvider).toEqual(selectedProvider);
// Verify source language is not shown.
sourceLanguages = courseVideoSettingsView.getSourceLanguages();
expect($courseVideoSettingsEl.find('.video-source-language option')).not.toExist();
expect(sourceLanguages).toBeUndefined();
// Select fidelity
selectPreference('.transcript-fidelity', selectedFidelity);
expect(courseVideoSettingsView.selectedFidelityPlan).toEqual(selectedFidelity);
// Verify source langauges menu is shown.
sourceLanguages = courseVideoSettingsView.getSourceLanguages();
expect($courseVideoSettingsEl.find('.video-source-language option')).toExist();
expect($courseVideoSettingsEl.find('.video-source-language option').length).toEqual(
_.keys(sourceLanguages).length + 1
);
// Verify getSourceLangaues return a list of langauges.
expect(sourceLanguages).toBeDefined();
expect(transcriptionPlans[selectedProvider].fidelity[selectedFidelity].languages).toEqual(
sourceLanguages
);
});
it('shows target language when source language is selected', function() {
var targetLanguages,
selectedSourceLanguage = 'en',
selectedProvider = 'Cielo24',
selectedFidelity = 'PROFESSIONAL';
renderCourseVideoSettingsView(null, transcriptionPlans, transcriptOrganizationCredentials);
// Select provider
changeProvider(selectedProvider);
expect(courseVideoSettingsView.selectedProvider).toEqual(selectedProvider);
// Select fidelity
selectPreference('.transcript-fidelity', selectedFidelity);
expect(courseVideoSettingsView.selectedFidelityPlan).toEqual(selectedFidelity);
// Verify target langauges not shown.
expect($courseVideoSettingsEl.find('.transcript-language-menu:visible option')).not.toExist();
// Select source language
selectPreference('.video-source-language', selectedSourceLanguage);
expect(courseVideoSettingsView.selectedVideoSourceLanguage).toEqual(selectedSourceLanguage);
// Verify target languages are shown.
targetLanguages = courseVideoSettingsView.getTargetLanguages();
expect($courseVideoSettingsEl.find('.transcript-language-menu:visible option')).toExist();
expect($courseVideoSettingsEl.find('.transcript-language-menu:visible option').length).toEqual(
_.keys(targetLanguages).length + 1
);
});
it('shows target language same as selected source language in case of mechanical fidelity', function() {
var targetLanguages,
selectedSourceLanguage = 'en',
selectedProvider = 'Cielo24',
selectedFidelity = 'MECHANICAL';
renderCourseVideoSettingsView(null, transcriptionPlans, transcriptOrganizationCredentials);
// Select provider
changeProvider(selectedProvider);
expect(courseVideoSettingsView.selectedProvider).toEqual(selectedProvider);
// Select fidelity
selectPreference('.transcript-fidelity', selectedFidelity);
expect(courseVideoSettingsView.selectedFidelityPlan).toEqual(selectedFidelity);
// Select source language
selectPreference('.video-source-language', selectedSourceLanguage);
expect(courseVideoSettingsView.selectedVideoSourceLanguage).toEqual(selectedSourceLanguage);
// Verify target languages are shown.
targetLanguages = courseVideoSettingsView.getTargetLanguages();
expect($courseVideoSettingsEl.find('.transcript-language-menu:visible option')).toExist();
expect($courseVideoSettingsEl.find('.transcript-language-menu:visible option').length).toEqual(
_.keys(targetLanguages).length + 1
);
// Also verify that target language are same as selected source language.
expect(_.keys(targetLanguages).length).toEqual(1);
expect(_.keys(targetLanguages)).toEqual([selectedSourceLanguage]);
});
it('saves transcript settings on update settings button click if preferances are selected', function() {
var requests = AjaxHelpers.requests(this);
$courseVideoSettingsEl.find('.action-update-course-video-settings').click();
AjaxHelpers.expectRequest(
requests,
'POST',
transcriptPreferencesUrl,
JSON.stringify({
provider: activeTranscriptPreferences.provider,
cielo24_fidelity: activeTranscriptPreferences.cielo24_fidelity,
cielo24_turnaround: activeTranscriptPreferences.cielo24_turnaround,
three_play_turnaround: activeTranscriptPreferences.three_play_turnaround,
preferred_languages: activeTranscriptPreferences.preferred_languages,
video_source_language: activeTranscriptPreferences.video_source_language,
global: false
})
);
// Send successful response.
AjaxHelpers.respondWithJson(requests, {
transcript_preferences: activeTranscriptPreferences
});
// Verify that success message is shown.
verifyMessage('success', 'Settings updated');
});
it('removes transcript settings on update settings button click when no provider is selected', function() {
// Reset to None provider
resetProvider();
verifyProviderList(providers.none);
// Verify that success message is shown.
verifyMessage('success', 'Automatic transcripts are disabled.');
});
it('shows error message if server sends error', function() {
var requests = AjaxHelpers.requests(this);
$courseVideoSettingsEl.find('.action-update-course-video-settings').click();
AjaxHelpers.expectRequest(
requests,
'POST',
transcriptPreferencesUrl,
JSON.stringify({
provider: activeTranscriptPreferences.provider,
cielo24_fidelity: activeTranscriptPreferences.cielo24_fidelity,
cielo24_turnaround: activeTranscriptPreferences.cielo24_turnaround,
three_play_turnaround: activeTranscriptPreferences.three_play_turnaround,
preferred_languages: activeTranscriptPreferences.preferred_languages,
video_source_language: activeTranscriptPreferences.video_source_language,
global: false
})
);
// Send error response.
AjaxHelpers.respondWithError(requests, 400, {
error: 'Error message'
});
// Verify that error message is shown.
verifyMessage('error', 'Error message');
});
it('implies preferences are required if not selected when saving preferances', function() {
// Reset so that no preferance is selected.
courseVideoSettingsView.selectedTurnaroundPlan = '';
courseVideoSettingsView.selectedFidelityPlan = '';
courseVideoSettingsView.selectedLanguages = [];
$courseVideoSettingsEl.find('.action-update-course-video-settings').click();
verifyPreferanceErrorState($courseVideoSettingsEl.find('.transcript-turnaround-wrapper'), true);
verifyPreferanceErrorState($courseVideoSettingsEl.find('.transcript-fidelity-wrapper'), true);
verifyPreferanceErrorState($courseVideoSettingsEl.find('.transcript-languages-wrapper'), true);
});
it('removes error state on preferances if selected', function() {
// Provide values for preferances.
selectPreference('.transcript-turnaround', activeTranscriptPreferences.cielo24_turnaround);
selectPreference('.transcript-fidelity', activeTranscriptPreferences.cielo24_fidelity);
selectPreference('.video-source-language', activeTranscriptPreferences.video_source_language);
selectPreference('.transcript-language-menu', activeTranscriptPreferences.preferred_languages[0]);
verifyPreferanceErrorState($courseVideoSettingsEl.find('.transcript-turnaround-wrapper'), false);
verifyPreferanceErrorState($courseVideoSettingsEl.find('.transcript-fidelity-wrapper'), false);
verifyPreferanceErrorState($courseVideoSettingsEl.find('.transcript-languages-wrapper'), false);
});
it('shows provider selected view if active provider is present', function() {
var $selectedProviderContainerEl = $courseVideoSettingsEl.find('.transcript-provider-wrapper .selected-transcript-provider'); // eslint-disable-line max-len
expect($selectedProviderContainerEl.find('span').html()).toEqual(courseVideoSettingsView.selectedProvider); // eslint-disable-line max-len
expect($selectedProviderContainerEl.find('button.action-change-provider')).toExist();
// Verify provider list view is not shown.
expect($courseVideoSettingsEl.find('.transcript-provider-wrapper .transcript-provider-group')).not.toExist(); // eslint-disable-line max-len
});
it('does not show transcript preferences or organization credentials if None provider is saved', function() { // eslint-disable-line max-len
renderCourseVideoSettingsView(null, transcriptionPlans);
// Check None provider
resetProvider();
verifyProviderList(providers.none);
// Verify selected provider view is not shown.
expect($courseVideoSettingsEl.find('.transcript-provider-wrapper .selected-transcript-provider')).not.toExist(); // eslint-disable-line max-len
});
it('does not show transcript preferences or organization credentials if None provider is checked', function() { // eslint-disable-line max-len
renderCourseVideoSettingsView(null, transcriptionPlans);
// Check None provider
resetProvider();
verifyProviderList(providers.none);
// Verify selected provider view is not shown.
expect($courseVideoSettingsEl.find('.transcript-provider-wrapper .selected-transcript-provider')).not.toExist(); // eslint-disable-line max-len
// Verify transcript preferences are not shown.
expect($courseVideoSettingsEl.find('.course-video-transcript-preferances-wrapper')).not.toExist();
// Verify org credentials are not shown.
expect($courseVideoSettingsEl.find('.organization-credentials-content')).not.toExist();
});
it('shows organization credentials when organization credentials for selected provider are not present', function() { // eslint-disable-line max-len
renderCourseVideoSettingsView(null, transcriptionPlans);
// Check Cielo24 provider
changeProvider(providers.Cielo24.key);
verifyProviderList(providers.Cielo24);
// Verify organization credentials are shown.
verifyOrganizationCredentialsView();
// Verify transcript preferences are not shown.
expect($courseVideoSettingsEl.find('.course-video-transcript-preferances-wrapper')).not.toExist();
});
it('shows transcript preferences when organization credentials for selected provider are present', function() { // eslint-disable-line max-len
renderCourseVideoSettingsView(null, transcriptionPlans, transcriptOrganizationCredentials);
// Check Cielo24 provider
changeProvider('Cielo24');
verifyProviderList(providers.Cielo24);
// Verify organization credentials are not shown.
expect($courseVideoSettingsEl.find('.organization-credentials-content')).not.toExist();
// Verify transcript preferences are shown.
verifyTranscriptPreferencesView();
});
it('shows organization credentials view if clicked on change provider button', function() {
// Verify organization credentials view is not shown initially.
expect($courseVideoSettingsEl.find('.organization-credentials-content')).not.toExist();
verifyProviderSelectedView();
// Click change button to render organization credentials view.
$courseVideoSettingsEl.find('.action-change-provider').click();
// Verify organization credentials is now shown.
verifyOrganizationCredentialsView();
});
it('shows cielo specific organization credentials fields only', function() {
verifyProviderSelectedView();
// Click change button to render organization credentials view.
$courseVideoSettingsEl.find('.action-change-provider').click();
// Verify api key is present.
verifyCredentialFieldsPresent({
'api-key': 'API Key',
username: 'Username'
});
});
it('shows 3play specific organization credentials fields only', function() {
// Set selected provider to 3Play Media
changeProvider('3PlayMedia');
// Verify api key and api secret input fields are present.
verifyCredentialFieldsPresent({
'api-key': 'API Key',
'api-secret': 'API Secret'
});
});
it('shows warning message when changing organization credentials if present already', function() {
// Set selectedProvider organization credentials.
courseVideoSettingsView.transcriptOrganizationCredentials[courseVideoSettingsView.selectedProvider] = true; // eslint-disable-line max-len
verifyProviderSelectedView();
// Click change button to render organization credentials view.
$courseVideoSettingsEl.find('.action-change-provider').click();
// Verify credentials are shown
verifyOrganizationCredentialsView();
// Verify warning message is shown.
expect($courseVideoSettingsEl.find('.transcription-account-details.warning')).toExist();
// Verify message
expect($courseVideoSettingsEl.find('.transcription-account-details').html()).toEqual(
'<span>This action updates the ' + courseVideoSettingsView.selectedProvider
+ ' information for your entire organization.</span>'
);
});
it('does not show warning message when changing organization credentials if not present already', function() { // eslint-disable-line max-len
verifyProviderSelectedView();
// Click change button to render organization credentials view.
$courseVideoSettingsEl.find('.action-change-provider').click();
// Verify warning message is not shown.
expect($courseVideoSettingsEl.find('.transcription-account-details.warning')).not.toExist();
// Initial detail message is shown instead.
expect($courseVideoSettingsEl.find('.transcription-account-details').html()).toEqual(
'<span>Enter the account information for your organization.</span>'
);
});
it('shows validation errors if no organization credentials are provided when saving credentials', function() { // eslint-disable-line max-len
// Set selected provider to 3Play Media
changeProvider('3PlayMedia');
// Click save organization credentials button to save credentials.
$courseVideoSettingsEl.find('.action-update-org-credentials').click();
verifyPreferanceErrorState(
$courseVideoSettingsEl.find('.' + courseVideoSettingsView.selectedProvider + '-api-key-wrapper'),
true
);
verifyPreferanceErrorState(
$courseVideoSettingsEl.find('.' + courseVideoSettingsView.selectedProvider + '-api-secret-wrapper'),
true
);
});
it('saves cielo organization credentials on clicking save credentials button', function() {
verifyProviderSelectedView();
submitOrganizationCredentials({
api_key: 'api-key',
username: 'username'
});
verifyCredentialsSaved();
});
it('saves 3Play organization credentials on clicking save credentials button', function() {
verifyProviderSelectedView();
// Set selected provider to 3Play Media
changeProvider('3PlayMedia');
submitOrganizationCredentials({
api_key: 'api-key',
api_secret_key: 'api-secret'
});
verifyCredentialsSaved();
});
it('shows error message on saving organization credentials if server sends bad request error', function() {
verifyProviderSelectedView();
submitOrganizationCredentials({
api_key: 'api-key',
username: 'username'
}, 400, 'Error saving credentials.');
// Verify that bad request error message is shown.
verifyMessage('error', 'Error saving credentials.');
});
it('shows error message on saving organization credentials if server sends error', function() {
verifyProviderSelectedView();
submitOrganizationCredentials({
api_key: 'api-key',
username: 'username'
}, 500);
// Verify that server error message is shown.
verifyMessage('error', INTERNAL_SERVER_ERROR);
});
// TODO: Add more tests like clicking on add language, remove and their scenarios and some other tests
// for specific preferance selected tests etc. - See EDUCATOR-1478
});
}
);

View File

@@ -1,110 +0,0 @@
define(
['jquery', 'underscore', 'backbone', 'js/views/previous_video_upload_list',
'common/js/spec_helpers/template_helpers', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers'],
function($, _, Backbone, PreviousVideoUploadListView, TemplateHelpers, AjaxHelpers) {
'use strict';
describe('PreviousVideoUploadListView', function() {
var videoHandlerUrl = '/videos/course-v1:org.0+course_0+Run_0',
render = function(numModels) {
var modelData = {
client_video_id: 'foo.mp4',
duration: 42,
created: '2014-11-25T23:13:05',
edx_video_id: 'dummy_id',
status: 'uploading',
transcripts: []
};
var collection = new Backbone.Collection(
_.map(
_.range(numModels),
function(num, index) {
return new Backbone.Model(
_.extend({}, modelData, {edx_video_id: 'dummy_id_' + index})
);
}
)
);
var view = new PreviousVideoUploadListView({
collection: collection,
videoHandlerUrl: videoHandlerUrl,
transcriptAvailableLanguages: [],
videoSupportedFileFormats: [],
videoTranscriptSettings: {},
videoImageSettings: {}
});
return view.render().$el;
},
verifyVideosInfo;
beforeEach(function() {
setFixtures('<div id="page-prompt"></div>');
TemplateHelpers.installTemplate('previous-video-upload');
TemplateHelpers.installTemplate('previous-video-upload-list');
});
verifyVideosInfo = function(test, confirmRemove) {
var firstVideo,
numVideos = 5,
$el = render(numVideos),
firstVideoId = 'dummy_id_0',
requests = AjaxHelpers.requests(test),
firstVideoSelector = '.js-table-body .video-row:first-child';
// total number of videos should be 5 before remove
expect($el.find('.js-table-body .video-row').length).toEqual(numVideos);
// get first video element
firstVideo = $el.find(firstVideoSelector);
// verify first video id before removal
expect(firstVideo.find('.video-id-col').text()).toEqual(firstVideoId);
// remove first video in the table
firstVideo.find('.remove-video-button.action-button').click();
if (confirmRemove) {
// click on Remove button on confirmation popup
$('.action-primary').click();
AjaxHelpers.expectJsonRequest(requests, 'DELETE', videoHandlerUrl + '/dummy_id_0');
AjaxHelpers.respondWithNoContent(requests);
numVideos -= 1;
firstVideoId = 'dummy_id_1';
} else {
// click on Cancel button on confirmation popup
$('.action-secondary').click();
expect(requests.length).toEqual(0);
}
// verify total number of videos after Remove/Cancel
expect($el.find('.js-table-body .video-row').length).toEqual(numVideos);
// verify first video id after Remove/Cancel
firstVideo = $el.find(firstVideoSelector);
expect(firstVideo.find('.video-id-col').text()).toEqual(firstVideoId);
};
it('should render an empty collection', function() {
var $el = render(0);
expect($el.find('.js-table-body').length).toEqual(1);
expect($el.find('.js-table-body .video-row').length).toEqual(0);
});
it('should render a non-empty collection', function() {
var $el = render(5);
expect($el.find('.js-table-body').length).toEqual(1);
expect($el.find('.js-table-body .video-row').length).toEqual(5);
});
it('removes video upon click on Remove button', function() {
// Remove a video from list and verify that correct video is removed
verifyVideosInfo(this, true);
});
it('does nothing upon click on Cancel button', function() {
// Verify that nothing changes when we click on Cancel button on confirmation popup
verifyVideosInfo(this, false);
});
});
}
);

View File

@@ -1,95 +0,0 @@
define(
['jquery', 'underscore', 'backbone', 'js/views/previous_video_upload', 'common/js/spec_helpers/template_helpers',
'common/js/spec_helpers/view_helpers'],
function($, _, Backbone, PreviousVideoUploadView, TemplateHelpers, ViewHelpers) {
'use strict';
describe('PreviousVideoUploadView', function() {
var render = function(modelData) {
var defaultData = {
client_video_id: 'foo.mp4',
duration: 42,
created: '2014-11-25T23:13:05',
edx_video_id: 'dummy_id',
status: 'uploading',
transcripts: []
},
view = new PreviousVideoUploadView({
model: new Backbone.Model($.extend({}, defaultData, modelData)),
videoHandlerUrl: '/videos/course-v1:org.0+course_0+Run_0',
transcriptAvailableLanguages: [],
videoSupportedFileFormats: [],
videoTranscriptSettings: {},
videoImageSettings: {}
});
return view.render().$el;
};
beforeEach(function() {
setFixtures('<div id="page-prompt"></div><div id="page-notification"></div>');
TemplateHelpers.installTemplate('previous-video-upload', false);
});
it('should render video name correctly', function() {
var testName = 'test name';
var $el = render({client_video_id: testName});
expect($el.find('.name-col').text()).toEqual(testName);
});
it('should render created timestamp correctly', function() {
var fakeDate = 'fake formatted date';
spyOn(Date.prototype, 'toLocaleString').and.callFake(
function(locales, options) {
expect(locales).toEqual([]);
expect(options.timeZone).toEqual('UTC');
expect(options.timeZoneName).toEqual('short');
return fakeDate;
}
);
var $el = render({});
expect($el.find('.date-col').text()).toEqual(fakeDate);
});
it('should render video id correctly', function() {
var testId = 'test_id';
var $el = render({edx_video_id: testId});
expect($el.find('.video-id-col').text()).toEqual(testId);
});
it('should render status correctly', function() {
var testStatus = 'Test Status';
var $el = render({status: testStatus});
expect($el.find('.video-status').text()).toEqual(testStatus);
});
it('should render remove button correctly', function() {
var $el = render(),
removeButton = $el.find('.actions-list .action-remove a.remove-video-button');
expect(removeButton.data('tooltip')).toEqual('Remove this video');
expect(removeButton.find('.sr').text()).toEqual('Remove foo.mp4 video');
});
it('shows a confirmation popup when the remove button is clicked', function() {
var $el = render();
$el.find('a.remove-video-button').click();
expect($('.prompt.warning .title').text()).toEqual('Are you sure you want to remove this video from the list?'); // eslint-disable-line max-len
expect(
$('.prompt.warning .message').text()
).toEqual(
'Removing a video from this list does not affect course content. Any content that uses a previously uploaded video ID continues to display in the course.' // eslint-disable-line max-len
);
expect($('.prompt.warning .action-primary').text()).toEqual('Remove');
expect($('.prompt.warning .action-secondary').text()).toEqual('Cancel');
});
it('shows a notification when the remove button is clicked', function() {
var notificationSpy = ViewHelpers.createNotificationSpy(),
$el = render();
$el.find('a.remove-video-button').click();
$('.action-primary').click();
ViewHelpers.verifyNotificationShowing(notificationSpy, /Removing/);
});
});
}
);

View File

@@ -1,342 +0,0 @@
define(
['jquery', 'underscore', 'backbone', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
'js/views/video_thumbnail', 'js/views/previous_video_upload_list', 'common/js/spec_helpers/template_helpers'],
function($, _, Backbone, AjaxHelpers, VideoThumbnailView, PreviousVideoUploadListView, TemplateHelpers) {
'use strict';
describe('VideoThumbnailView', function() {
var IMAGE_UPLOAD_URL = '/videos/upload/image',
UPLOADED_IMAGE_URL = 'images/upload_success.jpg',
VIDEO_IMAGE_MAX_BYTES = 2 * 1024 * 1024,
VIDEO_IMAGE_MIN_BYTES = 2 * 1024,
VIDEO_IMAGE_MAX_WIDTH = 1280,
VIDEO_IMAGE_MAX_HEIGHT = 720,
VIDEO_IMAGE_SUPPORTED_FILE_FORMATS = {
'.bmp': 'image/bmp',
'.bmp2': 'image/x-ms-bmp', // PIL gives x-ms-bmp format
'.gif': 'image/gif',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.png': 'image/png'
},
videoListView,
videoThumbnailView,
$videoListEl,
$videoThumbnailEl,
createVideoListView,
createFakeImageFile,
verifyStateInfo;
/**
* Creates a list of video records.
*
* @param {Boolean} videoImageUploadEnabled Boolean indicating if the feature is enabled.
* @param {Object} modelData Model data for video records.
* @param {Integer} numVideos Number of video elements to create.
* @param {Integer} videoViewIndex Index of video on which videoThumbnailView would be based.
*/
createVideoListView = function(videoImageUploadEnabled, modelData, numVideos, videoViewIndex) {
var modelData = modelData || {}, // eslint-disable-line no-redeclare
numVideos = numVideos || 1, // eslint-disable-line no-redeclare
videoViewIndex = videoViewIndex || 0, // eslint-disable-line no-redeclare,
defaultData = {
client_video_id: 'foo.mp4',
duration: 42,
created: '2014-11-25T23:13:05',
edx_video_id: 'dummy_id',
status: 'uploading',
transcripts: []
},
collection = new Backbone.Collection(_.map(_.range(numVideos), function(num, index) {
return new Backbone.Model(
_.extend({}, defaultData, {edx_video_id: 'dummy_id_' + index}, modelData)
);
}));
videoListView = new PreviousVideoUploadListView({
collection: collection,
videoHandlerUrl: '/videos/course-v1:org.0+course_0+Run_0',
videoImageUploadURL: IMAGE_UPLOAD_URL,
videoImageSettings: {
max_size: VIDEO_IMAGE_MAX_BYTES,
min_size: VIDEO_IMAGE_MIN_BYTES,
max_width: VIDEO_IMAGE_MAX_WIDTH,
max_height: VIDEO_IMAGE_MAX_HEIGHT,
supported_file_formats: VIDEO_IMAGE_SUPPORTED_FILE_FORMATS,
video_image_upload_enabled: videoImageUploadEnabled
},
transcriptAvailableLanguages: [],
videoSupportedFileFormats: [],
videoTranscriptSettings: {}
});
$videoListEl = videoListView.render().$el;
if (videoImageUploadEnabled) {
videoThumbnailView = videoListView.itemViews[videoViewIndex].videoThumbnailView;
$videoThumbnailEl = videoThumbnailView.render().$el;
}
return videoListView;
};
createFakeImageFile = function(size, type) {
var size = size || VIDEO_IMAGE_MIN_BYTES, // eslint-disable-line no-redeclare
type = type || 'image/jpeg'; // eslint-disable-line no-redeclare
return new Blob([Array(size + 1).join('i')], {type: type});
};
verifyStateInfo = function($thumbnail, state, onHover, additionalSRText) {
var beforeIcon,
beforeText;
// Verify hover message, save the text before hover to verify later
if (onHover) {
beforeIcon = $thumbnail.find('.action-icon').html().trim();
beforeText = $thumbnail.find('.action-text').html().trim();
$thumbnail.trigger('mouseover');
}
if (additionalSRText) {
expect(
$thumbnail.find('.thumbnail-action .action-text-sr').text().trim()
).toEqual(additionalSRText);
}
if (state !== 'error') {
expect($thumbnail.find('.action-icon').html().trim()).toEqual(
videoThumbnailView.actionsInfo[state].icon
);
}
expect($thumbnail.find('.action-text').html().trim()).toEqual(
videoThumbnailView.actionsInfo[state].text
);
// Verify if messages are restored after focus moved away
if (onHover) {
$thumbnail.trigger('mouseout');
expect($thumbnail.find('.action-icon').html().trim()).toEqual(beforeIcon);
expect($thumbnail.find('.action-text').html().trim()).toEqual(beforeText);
}
};
beforeEach(function() {
setFixtures('<div id="page-prompt"></div><div id="page-notification"></div>');
TemplateHelpers.installTemplate('video-thumbnail');
TemplateHelpers.installTemplate('previous-video-upload-list');
createVideoListView(true);
});
it('Verifies that the ThumbnailView is not initialized on disabling the feature', function() {
createVideoListView(false);
expect(videoListView.itemViews[0].videoThumbnailView).toEqual(undefined);
});
it('renders as expected', function() {
expect($videoThumbnailEl.find('.thumbnail-wrapper')).toExist();
expect($videoThumbnailEl.find('.upload-image-input')).toExist();
});
it('does not show duration if not available', function() {
createVideoListView(true, {duration: 0});
expect($videoThumbnailEl.find('.thumbnail-wrapper .video-duration')).not.toExist();
});
it('shows the duration if available', function() {
var $duration = $videoThumbnailEl.find('.thumbnail-wrapper .video-duration');
expect($duration).toExist();
expect($duration.find('.duration-text-machine').text().trim()).toEqual('0:42');
expect($duration.find('.duration-text-human').text().trim()).toEqual('Video duration is 42 seconds');
});
it('calculates duration correctly', function() {
var durations = [
{duration: -1},
{duration: 0},
{duration: 0.75, machine: '0:00', humanize: ''},
{duration: 5, machine: '0:05', humanize: 'Video duration is 5 seconds'},
{duration: 103, machine: '1:43', humanize: 'Video duration is 1 minute and 43 seconds'},
{duration: 120, machine: '2:00', humanize: 'Video duration is 2 minutes'},
{duration: 500, machine: '8:20', humanize: 'Video duration is 8 minutes and 20 seconds'},
{duration: 7425, machine: '123:45', humanize: 'Video duration is 123 minutes and 45 seconds'}
],
expectedDuration;
durations.forEach(function(item) {
expectedDuration = videoThumbnailView.getDuration(item.duration);
if (item.duration <= 0) {
expect(expectedDuration).toEqual(null);
} else {
expect(expectedDuration.machine).toEqual(item.machine);
expect(expectedDuration.humanize).toEqual(item.humanize);
}
});
});
it('can upload image', function() {
var videoViewIndex = 0,
$thumbnail = $videoThumbnailEl.find('.thumbnail-wrapper'),
requests = AjaxHelpers.requests(this),
additionalSRText = videoThumbnailView.getSRText();
videoThumbnailView.chooseFile();
verifyStateInfo($thumbnail, 'upload');
verifyStateInfo($thumbnail, 'requirements', true, additionalSRText);
// Add image to upload queue and send POST request to upload image
$videoThumbnailEl.find('.upload-image-input').fileupload('add', {files: [createFakeImageFile()]});
verifyStateInfo($thumbnail, 'progress');
// Verify if POST request received for image upload
AjaxHelpers.expectRequest(
requests,
'POST',
IMAGE_UPLOAD_URL + '/dummy_id_' + videoViewIndex,
new FormData()
);
// Send successful upload response
AjaxHelpers.respondWithJson(requests, {image_url: UPLOADED_IMAGE_URL});
verifyStateInfo($thumbnail, 'edit', true);
// Verify uploaded image src
expect($thumbnail.find('img').attr('src')).toEqual(UPLOADED_IMAGE_URL);
});
it('shows error state correctly', function() {
var $thumbnail = $videoThumbnailEl.find('.thumbnail-wrapper'),
requests = AjaxHelpers.requests(this);
videoThumbnailView.chooseFile();
// Add image to upload queue and send POST request to upload image
$videoThumbnailEl.find('.upload-image-input').fileupload('add', {files: [createFakeImageFile()]});
AjaxHelpers.respondWithError(requests, 400);
verifyStateInfo($thumbnail, 'edit');
});
it('calls readMessage with correct message', function() {
var errorMessage = 'Image upload failed. This image file type is not supported. Supported file '
+ 'types are ' + videoThumbnailView.getVideoImageSupportedFileFormats().humanize + '.',
successData = {
files: [createFakeImageFile()],
submit: function() {}
},
failureData = {
jqXHR: {
responseText: JSON.stringify({
error: errorMessage
})
}
};
spyOn(videoThumbnailView, 'readMessages');
videoThumbnailView.imageSelected({}, successData);
expect(videoThumbnailView.readMessages).toHaveBeenCalledWith(['Video image upload started']);
videoThumbnailView.imageUploadSucceeded({}, {result: {image_url: UPLOADED_IMAGE_URL}});
expect(videoThumbnailView.readMessages).toHaveBeenCalledWith(['Video image upload completed']);
videoThumbnailView.imageUploadFailed({}, failureData);
expect(videoThumbnailView.readMessages).toHaveBeenCalledWith(
['Could not upload the video image file', errorMessage]
);
});
it('should show error message in case of server error', function() {
var requests = AjaxHelpers.requests(this);
videoThumbnailView.chooseFile();
// Add image to upload queue and send POST request to upload image
$videoThumbnailEl.find('.upload-image-input').fileupload('add', {files: [createFakeImageFile()]});
AjaxHelpers.respondWithError(requests);
// Verify error message is present
expect($videoListEl.find('.thumbnail-error-wrapper')).toExist();
});
it('should show error message when file is smaller than minimum size', function() {
videoThumbnailView.chooseFile();
// Add image to upload queue and send POST request to upload image
$videoThumbnailEl.find('.upload-image-input')
.fileupload('add', {files: [createFakeImageFile(VIDEO_IMAGE_MIN_BYTES - 10)]});
// Verify error message
expect($videoListEl.find('.thumbnail-error-wrapper').find('.action-text').html()
.trim()).toEqual(
'Image upload failed. The selected image must be larger than '
+ videoThumbnailView.getVideoImageMinSize().humanize + '.'
);
});
it('should show error message when file is larger than maximum size', function() {
videoThumbnailView.chooseFile();
// Add image to upload queue and send POST request to upload image
$videoThumbnailEl.find('.upload-image-input')
.fileupload('add', {files: [createFakeImageFile(VIDEO_IMAGE_MAX_BYTES + 10)]});
// Verify error message
expect($videoListEl.find('.thumbnail-error-wrapper').find('.action-text').html()
.trim()).toEqual(
'Image upload failed. The selected image must be smaller than '
+ videoThumbnailView.getVideoImageMaxSize().humanize + '.'
);
});
it('should not show error message when file size is equals to minimum file size', function() {
videoThumbnailView.chooseFile();
// Add image to upload queue and send POST request to upload image
$videoThumbnailEl.find('.upload-image-input')
.fileupload('add', {files: [createFakeImageFile(VIDEO_IMAGE_MIN_BYTES)]});
// Verify error not present.
expect($videoListEl.find('.thumbnail-error-wrapper')).not.toExist();
});
it('should not show error message when file size is equals to maximum file size', function() {
videoThumbnailView.chooseFile();
// Add image to upload queue and send POST request to upload image
$videoThumbnailEl.find('.upload-image-input')
.fileupload('add', {files: [createFakeImageFile(VIDEO_IMAGE_MAX_BYTES)]});
// Verify error not present.
expect($videoListEl.find('.thumbnail-error-wrapper')).not.toExist();
});
it('should show error message when file has unsupported content type', function() {
videoThumbnailView.chooseFile();
// Add image to upload queue and send POST request to upload image
$videoThumbnailEl.find('.upload-image-input')
.fileupload('add', {files: [createFakeImageFile(VIDEO_IMAGE_MIN_BYTES, 'mov/mp4')]});
// Verify error message
expect($videoListEl.find('.thumbnail-error-wrapper').find('.action-text').html()
.trim()).toEqual(
'Image upload failed. This image file type is not supported. Supported file types are '
+ videoThumbnailView.getVideoImageSupportedFileFormats().humanize + '.'
);
});
it('should not show error message when file has supported content type', function() {
videoThumbnailView.chooseFile();
// Add image to upload queue and send POST request to upload image
$videoThumbnailEl.find('.upload-image-input')
.fileupload('add', {files: [createFakeImageFile(VIDEO_IMAGE_MIN_BYTES)]});
// Verify error message is not present
expect($videoListEl.find('.thumbnail-error-wrapper')).not.toExist();
});
});
}
);

View File

@@ -1,434 +0,0 @@
define(
['jquery', 'underscore', 'backbone', 'js/views/video_transcripts', 'js/views/previous_video_upload_list',
'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/js/spec_helpers/template_helpers'],
function($, _, Backbone, VideoTranscriptsView, PreviousVideoUploadListView, AjaxHelpers, TemplateHelpers) {
'use strict';
describe('VideoTranscriptsView', function() {
var videoTranscriptsView,
renderView,
verifyTranscriptStateInfo,
verifyMessage,
verifyDetailedErrorMessage,
createFakeTranscriptFile,
transcripts = ['en', 'es', 'ur'],
edxVideoID = 'test-edx-video-id',
clientVideoID = 'Video client title name.mp4',
transcriptAvailableLanguages = [
{
language_code: 'en',
language_text: 'English'
},
{
language_code: 'es',
language_text: 'Spanish'
},
{
language_code: 'ar',
language_text: 'Chinese'
},
{
language_code: 'ur',
language_text: 'Urdu'
}
],
TRANSCRIPT_DOWNLOAD_FILE_FORMAT = 'srt',
TRANSCRIPT_DOWNLOAD_URL = 'abc.com/transcript_download/course_id',
TRANSCRIPT_UPLOAD_URL = 'abc.com/transcript_upload/course_id',
TRANSCRIPT_DELETE_URL = 'abc.com/transcript_delete/course_id',
videoSupportedFileFormats = ['.mov', '.mp4'],
videoTranscriptSettings = {
trancript_download_file_format: TRANSCRIPT_DOWNLOAD_FILE_FORMAT,
transcript_download_handler_url: TRANSCRIPT_DOWNLOAD_URL,
transcript_upload_handler_url: TRANSCRIPT_UPLOAD_URL,
transcript_delete_handler_url: TRANSCRIPT_DELETE_URL
},
videoListView;
verifyTranscriptStateInfo = function($transcriptEl, transcriptLanguage) {
var $transcriptActionsEl = $transcriptEl.find('.transcript-actions'),
downloadTranscriptActionEl = $transcriptActionsEl.find('.download-transcript-button'),
uploadTranscriptActionEl = $transcriptActionsEl.find('.upload-transcript-button');
// Verify transcript data attributes
expect($transcriptEl.data('edx-video-id')).toEqual(edxVideoID);
expect($transcriptEl.data('language-code')).toEqual(transcriptLanguage);
// Verify transcript language dropdown has correct value set.
expect($transcriptEl.find('.transcript-language-menu').val(), transcriptLanguage);
// Verify transcript actions
expect(downloadTranscriptActionEl.html().trim(), 'Download');
expect(
downloadTranscriptActionEl.attr('href'),
TRANSCRIPT_DOWNLOAD_URL + '?edx_video_id=' + edxVideoID + '&language_code=' + transcriptLanguage
);
expect(uploadTranscriptActionEl.html().trim(), 'Replace');
};
verifyMessage = function($transcriptEl, status) {
var $transcriptStatusEl = $transcriptEl.find('.transcript-upload-status-container'),
statusData = videoTranscriptsView.transcriptUploadStatuses[status];
expect($transcriptStatusEl.hasClass(statusData.statusClass)).toEqual(true);
expect($transcriptStatusEl.find('span.fa').hasClass(statusData.iconClasses)).toEqual(true);
expect(
$transcriptStatusEl.find('.more-details-action').hasClass('hidden')
).toEqual(statusData.hiddenClass === 'hidden');
expect(
$transcriptStatusEl.find('.transcript-detail-status').html().trim()
).toEqual(statusData.shortMessage);
};
verifyDetailedErrorMessage = function($transcriptEl, expectedTitle, expectedMessage) {
$transcriptEl.find('.more-details-action').click();
expect($('#prompt-warning-title').text().trim()).toEqual(expectedTitle);
expect($('#prompt-warning-description').text().trim()).toEqual(expectedMessage);
};
createFakeTranscriptFile = function(transcriptFileName) {
var transcriptFileName = transcriptFileName || 'test-transcript.srt', // eslint-disable-line no-redeclare, max-len
size = 100,
type = '';
return new File([new Blob([Array(size).join('i')], {type: type})], transcriptFileName);
};
renderView = function(availableTranscripts) {
var videoViewIndex = 0,
videoData = {
client_video_id: clientVideoID,
edx_video_id: edxVideoID,
created: '2014-11-25T23:13:05',
transcripts: availableTranscripts
},
videoCollection = new Backbone.Collection([new Backbone.Model(videoData)]);
videoListView = new PreviousVideoUploadListView({
collection: videoCollection,
videoImageSettings: {},
videoTranscriptSettings: videoTranscriptSettings,
transcriptAvailableLanguages: transcriptAvailableLanguages,
videoSupportedFileFormats: videoSupportedFileFormats
});
videoListView.setElement($('.wrapper-assets'));
videoListView.render();
videoTranscriptsView = videoListView.itemViews[videoViewIndex].videoTranscriptsView;
};
beforeEach(function() {
setFixtures(
'<div id="page-prompt"></div>'
+ '<div id="page-notification"></div>'
+ '<section class="wrapper-assets"></section>'
);
TemplateHelpers.installTemplate('previous-video-upload-list');
renderView(transcripts);
});
it('renders as expected', function() {
// Verify transcript container is present.
expect(videoListView.$el.find('.video-transcripts-header')).toExist();
// Veirfy transcript column header is present.
expect(videoListView.$el.find('.js-table-head .video-head-col.transcripts-col')).toExist();
// Verify transcript data column is present.
expect(videoListView.$el.find('.js-table-body .transcripts-col')).toExist();
// Verify view has initiallized.
expect(_.isUndefined(videoTranscriptsView)).toEqual(false);
});
it('does not show list of transcripts initially', function() {
expect(
videoTranscriptsView.$el.find('.video-transcripts-wrapper').hasClass('hidden')
).toEqual(true);
expect(videoTranscriptsView.$el.find('.toggle-show-transcripts-button-text').html().trim()).toEqual(
'Show transcripts (' + transcripts.length + ')'
);
});
it('shows list of transcripts when clicked on show transcript button', function() {
// Verify transcript container is hidden
expect(
videoTranscriptsView.$el.find('.video-transcripts-wrapper').hasClass('hidden')
).toEqual(true);
// Verify initial button text
expect(videoTranscriptsView.$el.find('.toggle-show-transcripts-button-text').html().trim()).toEqual(
'Show transcripts (' + transcripts.length + ')'
);
videoTranscriptsView.$el.find('.toggle-show-transcripts-button').click();
// Verify transcript container is not hidden
expect(
videoTranscriptsView.$el.find('.video-transcripts-wrapper').hasClass('hidden')
).toEqual(false);
// Verify button text is changed.
expect(videoTranscriptsView.$el.find('.toggle-show-transcripts-button-text').html().trim()).toEqual(
'Hide transcripts (' + transcripts.length + ')'
);
});
it('hides list of transcripts when clicked on hide transcripts button', function() {
// Click to show transcripts first.
videoTranscriptsView.$el.find('.toggle-show-transcripts-button').click();
// Verify button text.
expect(videoTranscriptsView.$el.find('.toggle-show-transcripts-button-text').html().trim()).toEqual(
'Hide transcripts (' + transcripts.length + ')'
);
// Verify transcript container is not hidden
expect(
videoTranscriptsView.$el.find('.video-transcripts-wrapper').hasClass('hidden')
).toEqual(false);
videoTranscriptsView.$el.find('.toggle-show-transcripts-button').click();
// Verify button text is changed.
expect(videoTranscriptsView.$el.find('.toggle-show-transcripts-button-text').html().trim()).toEqual(
'Show transcripts (' + transcripts.length + ')'
);
// Verify transcript container is hidden
expect(
videoTranscriptsView.$el.find('.video-transcripts-wrapper').hasClass('hidden')
).toEqual(true);
});
it('renders appropriate text when no transcript is available', function() {
// Render view with no transcripts
renderView({});
// Verify appropriate text is shown
expect(
videoTranscriptsView.$el.find('.transcripts-empty-text').html()
).toEqual('No transcript uploaded.');
});
it('renders correct transcript attributes', function() {
var $transcriptEl;
// Show transcripts
videoTranscriptsView.$el.find('.toggle-show-transcripts-button').click();
expect(videoTranscriptsView.$el.find('.video-transcript-content').length).toEqual(
transcripts.length
);
_.each(transcripts, function(languageCode) {
$transcriptEl = videoTranscriptsView.$el.find('.video-transcript-content[data-language-code="' + languageCode + '"]'); // eslint-disable-line max-len
// Verify correct transcript title is set.
expect($transcriptEl.find('.transcript-title').html()).toEqual(
'Video client title n_' + languageCode + '.' + TRANSCRIPT_DOWNLOAD_FILE_FORMAT
);
// Verify transcript is rendered with correct info.
verifyTranscriptStateInfo($transcriptEl, languageCode);
});
});
it('can upload transcript', function() {
var languageCode = 'en',
newLanguageCode = 'ar',
requests = AjaxHelpers.requests(this),
$transcriptEl = videoTranscriptsView.$el.find('.video-transcript-content[data-language-code="' + languageCode + '"]'); // eslint-disable-line max-len
// Verify correct transcript title is set.
expect($transcriptEl.find('.transcript-title').html()).toEqual(
'Video client title n_' + languageCode + '.' + TRANSCRIPT_DOWNLOAD_FILE_FORMAT
);
// Select a language
$transcriptEl.find('.transcript-language-menu').val(newLanguageCode);
$transcriptEl.find('.upload-transcript-button').click();
// Add transcript to upload queue and send POST request to upload transcript.
$transcriptEl.find('.upload-transcript-input').fileupload('add', {files: [createFakeTranscriptFile()]});
// Verify if POST request received for transcript upload
AjaxHelpers.expectRequest(
requests,
'POST',
TRANSCRIPT_UPLOAD_URL
);
// Send successful upload response
AjaxHelpers.respondWithJson(requests, {});
// Verify correct transcript title is set.
expect($transcriptEl.find('.transcript-title').html()).toEqual(
'Video client title n_' + newLanguageCode + '.' + TRANSCRIPT_DOWNLOAD_FILE_FORMAT
);
verifyMessage($transcriptEl, 'uploaded');
// Verify transcript is rendered with correct info.
verifyTranscriptStateInfo($transcriptEl, newLanguageCode);
});
it('can delete transcript', function() {
var languageCode = 'en',
requests = AjaxHelpers.requests(this),
$transcriptEl = videoTranscriptsView.$el.find('.video-transcript-content[data-language-code="' + languageCode + '"]'); // eslint-disable-line max-len
// Verify correct transcript title is set.
expect($transcriptEl.find('.transcript-title').html()).toEqual(
'Video client title n_' + languageCode + '.' + TRANSCRIPT_DOWNLOAD_FILE_FORMAT
);
$transcriptEl.find('.delete-transcript-button').click();
// Click remove button on prompt.
$('#page-prompt .action-primary').click();
// Verify if DELETE request received for transcript delete
AjaxHelpers.expectRequest(
requests,
'DELETE',
TRANSCRIPT_DELETE_URL + '/' + edxVideoID + '/' + languageCode
);
// Send successful delete response
AjaxHelpers.respondWithJson(requests, {});
// Verify English transcript is not present.
expect(videoTranscriptsView.$el.find(
'.video-transcript-content[data-language-code="' + languageCode + '"]'
)).not.toExist();
// Verify transcripts view is rendered with transcript deleted for English.
expect(videoTranscriptsView.$el.find('.toggle-show-transcripts-button-text').html().trim()).toEqual(
'Show transcripts (2)'
);
});
it('should show error message when deleting a transcript in case of server error', function() {
var languageCode = 'en',
requests = AjaxHelpers.requests(this),
$transcriptEl = videoTranscriptsView.$el.find('.video-transcript-content[data-language-code="' + languageCode + '"]'); // eslint-disable-line max-len
// Verify correct transcript title is set.
expect($transcriptEl.find('.transcript-title').html()).toEqual(
'Video client title n_' + languageCode + '.' + TRANSCRIPT_DOWNLOAD_FILE_FORMAT
);
$transcriptEl.find('.delete-transcript-button').click();
// Verify prompt title and description.
expect($('#page-prompt #prompt-warning-title').html().trim()).toEqual(
'Are you sure you want to remove this transcript?'
);
expect($('#page-prompt #prompt-warning-description').html().trim()).toEqual(
'If you remove this transcript, the transcript will not be available for any components that use this video.' // eslint-disable-line max-len
);
// Click remove button on prompt.
$('#page-prompt .action-primary').click();
// Verify if DELETE request received for transcript delete.
AjaxHelpers.expectRequest(
requests,
'DELETE',
TRANSCRIPT_DELETE_URL + '/' + edxVideoID + '/' + languageCode
);
AjaxHelpers.respondWithError(requests, 500);
// Verify prompt message is shown.
expect($('#page-notification #notification-error-title').html()).toEqual(
"Studio's having trouble saving your work"
);
// Verify English transcript container is not removed.
expect(videoTranscriptsView.$el.find(
'.video-transcript-content[data-language-code="' + languageCode + '"]'
)).toExist();
// Verify transcripts count is correct.
expect(videoTranscriptsView.$el.find('.toggle-show-transcripts-button-text').html().trim()).toEqual(
'Show transcripts (3)'
);
});
it('shows error state correctly', function() {
var languageCode = 'en',
requests = AjaxHelpers.requests(this),
errorMessage = 'Transcript failed error message',
$transcriptEl = videoTranscriptsView.$el.find('.video-transcript-content[data-language-code="' + languageCode + '"]'); // eslint-disable-line max-len
$transcriptEl.find('.upload-transcript-button').click();
// Add transcript to upload queue and send POST request to upload transcript.
$transcriptEl.find('.upload-transcript-input').fileupload('add', {files: [createFakeTranscriptFile()]});
// Server response with bad request.
AjaxHelpers.respondWithError(requests, 400, {error: errorMessage});
verifyMessage($transcriptEl, 'failed');
// verify detailed error message
verifyDetailedErrorMessage(
$transcriptEl,
videoTranscriptsView.defaultFailureTitle,
errorMessage
);
// Verify transcript is rendered with correct info.
verifyTranscriptStateInfo($transcriptEl, languageCode);
});
it('should show error message in case of server error', function() {
var languageCode = 'en',
requests = AjaxHelpers.requests(this),
$transcriptEl = videoTranscriptsView.$el.find('.video-transcript-content[data-language-code="' + languageCode + '"]'); // eslint-disable-line max-len
$transcriptEl.find('.upload-transcript-button').click();
// Add transcript to upload queue and send POST request to upload transcript.
$transcriptEl.find('.upload-transcript-input').fileupload('add', {files: [createFakeTranscriptFile()]});
AjaxHelpers.respondWithError(requests, 500);
verifyMessage($transcriptEl, 'failed');
// verify detailed error message
verifyDetailedErrorMessage(
$transcriptEl,
videoTranscriptsView.defaultFailureTitle,
videoTranscriptsView.defaultFailureMessage
);
// Verify transcript is rendered with correct info.
verifyTranscriptStateInfo($transcriptEl, languageCode);
});
it('should show error message in case of unsupported transcript file format', function() {
var languageCode = 'en',
transcriptFileName = 'unsupported-transcript-file-format.txt',
errorMessage = 'This file type is not supported. Supported file type is ' + TRANSCRIPT_DOWNLOAD_FILE_FORMAT + '.', // eslint-disable-line max-len
$transcriptEl = videoTranscriptsView.$el.find('.video-transcript-content[data-language-code="' + languageCode + '"]'); // eslint-disable-line max-len
$transcriptEl.find('.upload-transcript-button').click();
// Add transcript to upload queue and send POST request to upload transcript.
$transcriptEl.find('.upload-transcript-input').fileupload('add', {
files: [createFakeTranscriptFile(transcriptFileName)]
});
verifyMessage($transcriptEl, 'validationFailed');
// verify detailed error message
verifyDetailedErrorMessage(
$transcriptEl,
videoTranscriptsView.defaultFailureTitle,
errorMessage
);
// Verify transcript is rendered with correct info.
verifyTranscriptStateInfo($transcriptEl, languageCode);
});
});
}
);

View File

@@ -1,58 +0,0 @@
define(
['underscore', 'js/models/active_video_upload', 'js/views/baseview', 'common/js/components/views/feedback_prompt',
'edx-ui-toolkit/js/utils/html-utils'],
function(_, ActiveVideoUpload, BaseView, PromptView, HtmlUtils) {
'use strict';
var STATUS_CLASSES = [
{status: ActiveVideoUpload.STATUS_QUEUED, cls: 'queued'},
{status: ActiveVideoUpload.STATUS_COMPLETED, cls: 'success'},
{status: ActiveVideoUpload.STATUS_FAILED, cls: 'error'}
];
var ActiveVideoUploadView = BaseView.extend({
tagName: 'li',
className: 'active-video-upload',
events: {
'click a.more-details-action': 'showUploadFailureMessage'
},
initialize: function() {
this.template = this.loadTemplate('active-video-upload');
this.listenTo(this.model, 'change', this.render);
},
render: function() {
var $el = this.$el,
status;
$el.html(HtmlUtils.HTML(this.template(this.model.attributes)).toString());
status = this.model.get('status');
_.each(
STATUS_CLASSES,
function(statusClass) {
$el.toggleClass(statusClass.cls, status == statusClass.status);
}
);
return this;
},
showUploadFailureMessage: function() {
return new PromptView.Warning({
title: gettext('Your file could not be uploaded'),
message: this.model.get('failureMessage'),
actions: {
primary: {
text: gettext('Close'),
click: function(prompt) {
return prompt.hide();
}
}
}
}).show();
}
});
return ActiveVideoUploadView;
}
);

View File

@@ -1,453 +0,0 @@
define([
'jquery',
'underscore',
'backbone',
'js/models/active_video_upload',
'js/views/baseview',
'js/views/active_video_upload',
'js/views/course_video_settings',
'edx-ui-toolkit/js/utils/html-utils',
'edx-ui-toolkit/js/utils/string-utils',
'text!templates/active-video-upload-list.underscore',
'jquery.fileupload'
],
function($, _, Backbone, ActiveVideoUpload, BaseView, ActiveVideoUploadView, CourseVideoSettingsView,
HtmlUtils, StringUtils, activeVideoUploadListTemplate) {
'use strict';
var ActiveVideoUploadListView,
CONVERSION_FACTOR_GBS_TO_BYTES = 1000 * 1000 * 1000;
ActiveVideoUploadListView = BaseView.extend({
tagName: 'div',
events: {
'click .file-drop-area': 'chooseFile',
'dragleave .file-drop-area': 'dragleave',
'drop .file-drop-area': 'dragleave'
},
uploadHeader: gettext('Upload Videos'),
uploadText: HtmlUtils.interpolateHtml(
gettext('Drag and drop or {spanStart}browse your computer{spanEnd}.'),
{
spanStart: HtmlUtils.HTML('<span class="upload-text-link">'),
spanEnd: HtmlUtils.HTML('</span>')
}
),
defaultFailureMessage: gettext('This may be happening because of an error with our server or your internet connection. Try refreshing the page or making sure you are online.'), // eslint-disable-line max-len
initialize: function(options) {
this.template = HtmlUtils.template(activeVideoUploadListTemplate);
this.collection = new Backbone.Collection();
this.itemViews = [];
this.listenTo(this.collection, 'add', this.addUpload);
this.concurrentUploadLimit = options.concurrentUploadLimit || 0;
this.postUrl = options.postUrl;
this.activeTranscriptPreferences = options.activeTranscriptPreferences;
this.transcriptOrganizationCredentials = options.transcriptOrganizationCredentials;
this.videoTranscriptSettings = options.videoTranscriptSettings;
this.isVideoTranscriptEnabled = options.isVideoTranscriptEnabled;
this.videoSupportedFileFormats = options.videoSupportedFileFormats;
this.videoUploadMaxFileSizeInGB = options.videoUploadMaxFileSizeInGB;
this.onFileUploadDone = options.onFileUploadDone;
if (options.courseVideoSettingsButton) {
options.courseVideoSettingsButton.click(this.showCourseVideoSettingsView.bind(this));
}
this.maxSizeText = StringUtils.interpolate(
gettext('Maximum file size: {maxFileSize} GB'),
{
maxFileSize: this.videoUploadMaxFileSizeInGB
}
);
this.supportedVideosText = edx.StringUtils.interpolate(
gettext('Supported file types: {supportedVideoTypes}'),
{
supportedVideoTypes: this.videoSupportedFileFormats.join(', ')
}
);
if (this.isVideoTranscriptEnabled) {
this.listenTo(
Backbone,
'coursevideosettings:syncActiveTranscriptPreferences',
this.syncActiveTranscriptPreferences
);
this.listenTo(
Backbone,
'coursevideosettings:destroyCourseVideoSettingsView',
this.destroyCourseVideoSettingsView
);
}
},
syncActiveTranscriptPreferences: function(activeTranscriptPreferences) {
this.activeTranscriptPreferences = activeTranscriptPreferences;
},
showCourseVideoSettingsView: function(event) {
if (this.isVideoTranscriptEnabled) {
this.courseVideoSettingsView = new CourseVideoSettingsView({
activeTranscriptPreferences: this.activeTranscriptPreferences,
transcriptOrganizationCredentials: this.transcriptOrganizationCredentials,
videoTranscriptSettings: this.videoTranscriptSettings
});
this.courseVideoSettingsView.render();
event.stopPropagation();
}
},
destroyCourseVideoSettingsView: function() {
this.courseVideoSettingsView = null;
},
render: function() {
var preventDefault;
HtmlUtils.setHtml(
this.$el,
this.template({
uploadHeader: this.uploadHeader,
uploadText: this.uploadText,
maxSizeText: this.maxSizeText,
supportedVideosText: this.supportedVideosText
})
);
_.each(this.itemViews, this.renderUploadView.bind(this));
this.$uploadForm = this.$('.file-upload-form');
this.$dropZone = this.$uploadForm.find('.file-drop-area');
this.$uploadForm.fileupload({
type: 'PUT',
singleFileUploads: false,
limitConcurrentUploads: this.concurrentUploadLimit,
dropZone: this.$dropZone,
dragover: this.dragover.bind(this),
add: this.fileUploadAdd.bind(this),
send: this.fileUploadSend.bind(this),
progress: this.fileUploadProgress.bind(this),
done: this.fileUploadDone.bind(this),
fail: this.fileUploadFail.bind(this)
});
// Disable default drag and drop behavior for the window (which
// is to load the file in place)
preventDefault = function(event) {
event.preventDefault();
};
$(window).on('dragover', preventDefault);
$(window).on('drop', preventDefault);
$(window).on('beforeunload', this.onBeforeUnload.bind(this));
$(window).on('unload', this.onUnload.bind(this));
return this;
},
onBeforeUnload: function() {
// Are there are uploads queued or in progress?
var uploading = this.collection.filter(function(model) {
var isUploading = model.uploading();
if (isUploading) {
model.set('uploading', true);
} else {
model.set('uploading', false);
}
return isUploading;
});
// If so, show a warning message.
if (uploading.length) {
return gettext('Your video uploads are not complete.');
}
},
onUnload: function() {
var statusUpdates = [];
this.collection.each(function(model) {
if (model.get('uploading')) {
statusUpdates.push(
{
edxVideoId: model.get('videoId'),
status: 'upload_cancelled',
message: 'User cancelled video upload'
}
);
}
});
if (statusUpdates.length > 0) {
this.sendStatusUpdate(statusUpdates);
}
},
addUpload: function(model) {
var itemView = new ActiveVideoUploadView({model: model});
this.itemViews.push(itemView);
this.renderUploadView(itemView);
},
renderUploadView: function(view) {
this.$('.active-video-upload-list').append(view.render().$el);
},
chooseFile: function(event) {
event.preventDefault();
this.$uploadForm.find('.js-file-input').click();
},
dragover: function(event) {
event.preventDefault();
this.$dropZone.addClass('is-dragged');
},
dragleave: function(event) {
event.preventDefault();
this.$dropZone.removeClass('is-dragged');
},
// Each file is ultimately sent to a separate URL, but we want to make a
// single API call to get the URLs for all videos that the user wants to
// upload at one time. The file upload plugin only allows for this one
// callback, so this makes the API call and then breaks apart the
// individual file uploads, using the extra `redirected` field to
// indicate that the correct upload url has already been retrieved
fileUploadAdd: function(event, uploadData) {
var view = this,
model,
errors,
errorMsg;
if (uploadData.redirected) {
model = new ActiveVideoUpload({
fileName: uploadData.files[0].name,
videoId: uploadData.videoId
});
this.collection.add(model);
uploadData.cid = model.cid; // eslint-disable-line no-param-reassign
uploadData.submit();
} else {
// Validate file and remove the files with errors
errors = view.validateFile(uploadData);
_.each(errors, function(error) {
view.addUploadFailureView(error.fileName, error.message);
uploadData.files.splice(
_.findIndex(uploadData.files, function(file) { return file.name === error.fileName; }), 1
);
});
_.each(
uploadData.files,
function(file) {
$.ajax({
url: view.postUrl,
contentType: 'application/json',
data: JSON.stringify({
files: [{file_name: file.name, content_type: file.type}]
}),
dataType: 'json',
type: 'POST',
global: false // Do not trigger global AJAX error handler
}).done(function(responseData) {
_.each(
responseData.files,
function(file) { // eslint-disable-line no-shadow
view.$uploadForm.fileupload('add', {
files: _.filter(uploadData.files, function(fileObj) {
return file.file_name === fileObj.name;
}),
url: file.upload_url,
videoId: file.edx_video_id,
multipart: false,
global: false, // Do not trigger global AJAX error handler
redirected: true
});
}
);
}).fail(function(response) {
try {
errorMsg = JSON.parse(response.responseText).error;
} catch (error) {
errorMsg = view.defaultFailureMessage;
}
view.addUploadFailureView(file.name, errorMsg);
});
}
);
}
},
setStatus: function(cid, status, failureMessage) {
this.collection.get(cid).set({status: status, failureMessage: failureMessage || null});
},
// progress should be a number between 0 and 1 (inclusive)
setProgress: function(cid, progress) {
this.collection.get(cid).set('progress', progress);
},
fileUploadSend: function(event, data) {
this.setStatus(data.cid, ActiveVideoUpload.STATUS_UPLOADING);
},
fileUploadProgress: function(event, data) {
this.setProgress(data.cid, data.loaded / data.total);
},
fileUploadDone: function(event, data) {
var model = this.collection.get(data.cid),
self = this;
this.readMessages([
StringUtils.interpolate(
gettext('Upload completed for video {fileName}'),
{fileName: model.get('fileName')}
)
]);
this.sendStatusUpdate([
{
edxVideoId: model.get('videoId'),
status: 'upload_completed',
message: 'Uploaded completed'
}
]).done(function() {
self.setStatus(data.cid, ActiveVideoUpload.STATUS_COMPLETED);
self.setProgress(data.cid, 1);
if (self.onFileUploadDone) {
self.onFileUploadDone(self.collection);
self.clearSuccessful();
}
});
},
fileUploadFail: function(event, data) {
var responseText = data.jqXHR.responseText,
message = this.defaultFailureMessage,
status = 'upload_failed',
model = this.collection.get(data.cid);
if (responseText && data.jqXHR.getResponseHeader('content-type') === 'application/xml') {
message = $(responseText).find('Message').text();
status = 's3_upload_failed';
}
this.readMessages([
StringUtils.interpolate(
gettext('Upload failed for video {fileName}'),
{fileName: model.get('fileName')}
)
]);
this.sendStatusUpdate([
{
edxVideoId: model.get('videoId'),
status: status,
message: message
}
]);
this.setStatus(data.cid, ActiveVideoUpload.STATUS_FAILED, message);
},
addUploadFailureView: function(fileName, failureMessage) {
var model = new ActiveVideoUpload({
fileName: fileName,
status: ActiveVideoUpload.STATUS_FAILED,
failureMessage: failureMessage
});
this.collection.add(model);
this.readMessages([
StringUtils.interpolate(
gettext('Upload failed for video {fileName}'),
{fileName: model.get('fileName')}
)
]);
},
getMaxFileSizeInBytes: function() {
return this.videoUploadMaxFileSizeInGB * CONVERSION_FACTOR_GBS_TO_BYTES;
},
readMessages: function(messages) {
if ($(window).prop('SR') !== undefined) {
$(window).prop('SR').readTexts(messages);
}
},
validateFile: function(data) {
var self = this,
error = null,
errors = [],
fileName,
fileType;
$.each(data.files, function(index, file) { // eslint-disable-line consistent-return
fileName = file.name;
fileType = fileName.substr(fileName.lastIndexOf('.'));
// validate file type
if (!_.contains(self.videoSupportedFileFormats, fileType)) {
error = gettext(
'{filename} is not in a supported file format. '
+ 'Supported file formats are {supportedFileFormats}.'
)
.replace('{filename}', fileName)
.replace('{supportedFileFormats}', self.videoSupportedFileFormats.join(' and '));
} else if (file.size > self.getMaxFileSizeInBytes()) {
error = gettext(
'{filename} exceeds maximum size of {maxFileSizeInGB} GB.'
)
.replace('{filename}', fileName)
.replace('{maxFileSizeInGB}', self.videoUploadMaxFileSizeInGB);
}
if (error) {
errors.push({
fileName: fileName,
message: error
});
error = null;
}
});
return errors;
},
removeViewAt: function(index) {
this.itemViews.splice(index);
this.$('.active-video-upload-list li').eq(index).remove();
},
// Removes the upload progress view for files that have been
// uploaded successfully. Also removes the corresponding models
// from `collection`, keeping both in sync.
clearSuccessful: function() {
var idx,
completedIndexes = [],
completedModels = [],
completedMessages = [];
this.collection.each(function(model, index) {
if (model.get('status') === ActiveVideoUpload.STATUS_COMPLETED) {
completedModels.push(model);
completedIndexes.push(index - completedIndexes.length);
completedMessages.push(model.get('fileName')
+ gettext(': video upload complete.'));
}
});
for (idx = 0; idx < completedIndexes.length; idx++) {
this.removeViewAt(completedIndexes[idx]);
this.collection.remove(completedModels[idx]);
}
// Alert screen readers that the uploads were successful
if (completedMessages.length) {
completedMessages.push(gettext('Previous Uploads table has been updated.'));
this.readMessages(completedMessages);
}
},
sendStatusUpdate: function(statusUpdates) {
return $.ajax({
url: this.postUrl,
contentType: 'application/json',
data: JSON.stringify(statusUpdates),
dataType: 'json',
type: 'POST'
});
}
});
return ActiveVideoUploadListView;
}
);

View File

@@ -1,872 +0,0 @@
/**
* CourseVideoSettingsView shows a sidebar containing course wide video settings which we show on Video Uploads page.
*/
define([
'jquery', 'backbone', 'underscore', 'gettext', 'moment',
'common/js/components/utils/view_utils',
'edx-ui-toolkit/js/utils/html-utils',
'edx-ui-toolkit/js/utils/string-utils',
'text!templates/course-video-settings.underscore',
'text!templates/course-video-transcript-preferences.underscore',
'text!templates/course-video-transcript-provider-empty.underscore',
'text!templates/course-video-transcript-provider-selected.underscore',
'text!templates/transcript-organization-credentials.underscore',
'text!templates/course-video-settings-update-settings-footer.underscore',
'text!templates/course-video-settings-update-org-credentials-footer.underscore'
],
function($, Backbone, _, gettext, moment, ViewUtils, HtmlUtils, StringUtils, TranscriptSettingsTemplate,
TranscriptPreferencesTemplate, TranscriptProviderEmptyStateTemplate, TranscriptProviderSelectedStateTemplate,
OrganizationCredentialsTemplate, UpdateSettingsFooterTemplate, OrganizationCredentialsFooterTemplate) {
'use strict';
var CourseVideoSettingsView,
CIELO24 = 'Cielo24',
THREE_PLAY_MEDIA = '3PlayMedia',
INTERNAL_SERVER_ERROR_MESSAGE = gettext('An error has occurred. Wait a few minutes, and then try again.');
CourseVideoSettingsView = Backbone.View.extend({
el: 'div.video-transcript-settings-wrapper',
events: {
'change .transcript-provider-group input': 'providerSelected',
'change #transcript-turnaround': 'turnaroundSelected',
'change #transcript-fidelity': 'fidelitySelected',
'change #video-source-language': 'videoSourceLanguageSelected',
'click .action-add-language': 'languageSelected',
'click .action-remove-language': 'languageRemoved',
'click .action-change-provider': 'renderOrganizationCredentials',
'click .action-update-org-credentials': 'updateOrganizationCredentials',
'click .action-update-course-video-settings': 'updateCourseVideoSettings',
'click .action-cancel-course-video-settings': 'discardChanges',
'click .action-close-course-video-settings': 'closeCourseVideoSettings'
},
initialize: function(options) {
var videoTranscriptSettings = options.videoTranscriptSettings;
this.activeTranscriptionPlan = options.activeTranscriptPreferences;
this.transcriptOrganizationCredentials = _.extend({}, options.transcriptOrganizationCredentials);
this.availableTranscriptionPlans = videoTranscriptSettings.transcription_plans;
this.transcriptHandlerUrl = videoTranscriptSettings.transcript_preferences_handler_url;
this.transcriptCredentialsHandlerUrl = videoTranscriptSettings.transcript_credentials_handler_url;
this.template = HtmlUtils.template(TranscriptSettingsTemplate);
this.transcriptPreferencesTemplate = HtmlUtils.template(TranscriptPreferencesTemplate);
this.organizationCredentialsTemplate = HtmlUtils.template(OrganizationCredentialsTemplate);
this.organizationCredentialsFooterTemplate = HtmlUtils.template(OrganizationCredentialsFooterTemplate);
this.updateSettingsFooterTemplate = HtmlUtils.template(UpdateSettingsFooterTemplate);
this.transcriptProviderEmptyStateTemplate = HtmlUtils.template(TranscriptProviderEmptyStateTemplate);
this.transcriptProviderSelectedStateTemplate = HtmlUtils.template(TranscriptProviderSelectedStateTemplate);
this.setActiveTranscriptPlanData();
this.selectedLanguages = [];
},
registerCloseClickHandler: function() {
var self = this;
// Preventing any parent handlers from being notified of the event. This is to stop from firing the document
// level click handler to execute on course video settings pane click.
self.$el.click(function(event) {
event.stopPropagation();
});
// Click anywhere outside the course video settings pane would close the pane.
$(document).click(function(event) {
// If the target of the click isn't the container nor a descendant of the contain
if (!self.$el.is(event.target) && self.$el.has(event.target).length === 0) {
self.closeCourseVideoSettings();
}
});
},
resetPlanData: function() {
this.selectedProvider = '';
this.selectedTurnaroundPlan = '';
this.selectedFidelityPlan = '';
this.activeLanguages = [];
this.selectedVideoSourceLanguage = '';
this.selectedLanguages = [];
},
setActiveTranscriptPlanData: function() {
if (this.activeTranscriptionPlan) {
this.selectedProvider = this.activeTranscriptionPlan.provider;
this.selectedFidelityPlan = this.activeTranscriptionPlan.cielo24_fidelity;
this.selectedTurnaroundPlan = this.selectedProvider === CIELO24
? this.activeTranscriptionPlan.cielo24_turnaround
: this.activeTranscriptionPlan.three_play_turnaround;
this.activeLanguages = this.activeTranscriptionPlan.preferred_languages;
this.selectedVideoSourceLanguage = this.activeTranscriptionPlan.video_source_language;
} else {
this.resetPlanData();
}
},
getTurnaroundPlan: function() {
var turnaroundPlan = null;
if (this.selectedProvider) {
turnaroundPlan = this.availableTranscriptionPlans[this.selectedProvider].turnaround;
}
return turnaroundPlan;
},
getFidelityPlan: function() {
var fidelityPlan = null;
if (this.selectedProvider === CIELO24) {
fidelityPlan = this.availableTranscriptionPlans[this.selectedProvider].fidelity;
}
return fidelityPlan;
},
getTargetLanguages: function() {
var availableLanguages,
selectedPlan = this.selectedProvider ? this.availableTranscriptionPlans[this.selectedProvider] : null;
if (selectedPlan) {
if (this.selectedProvider === CIELO24 && this.selectedFidelityPlan) {
availableLanguages = selectedPlan.fidelity[this.selectedFidelityPlan].languages;
// If fidelity is mechanical then target language would be same as source language.
if (this.selectedFidelityPlan === 'MECHANICAL' && this.selectedVideoSourceLanguage) {
availableLanguages = _.pick(
availableLanguages,
this.selectedVideoSourceLanguage
);
}
} else if (this.selectedProvider === THREE_PLAY_MEDIA) {
availableLanguages = selectedPlan.languages;
}
}
return availableLanguages;
},
getSourceLanguages: function() {
var sourceLanguages = [];
if (this.selectedProvider === THREE_PLAY_MEDIA) {
sourceLanguages = this.availableTranscriptionPlans[this.selectedProvider].translations;
} else {
sourceLanguages = this.getTargetLanguages();
}
return sourceLanguages;
},
fidelitySelected: function(event) {
var $fidelityContainer = this.$el.find('.transcript-fidelity-wrapper');
this.selectedFidelityPlan = event.target.value;
// Remove any error if present already.
this.clearPreferenceErrorState($fidelityContainer);
// Clear active and selected languages.
// eslint-disable-next-line no-multi-assign
this.selectedLanguages = this.activeLanguages = [];
// Also clear selected language.
this.selectedVideoSourceLanguage = '';
this.renderSourceLanguages();
this.renderTargetLanguages();
},
videoSourceLanguageSelected: function(event) {
var $videoSourceLanguageContainer = this.$el.find('.video-source-language-wrapper');
this.selectedVideoSourceLanguage = event.target.value;
// Remove any error if present already.
this.clearPreferenceErrorState($videoSourceLanguageContainer);
// Clear active and selected languages.
// eslint-disable-next-line no-multi-assign
this.selectedLanguages = this.activeLanguages = [];
this.renderTargetLanguages();
},
turnaroundSelected: function(event) {
var $turnaroundContainer = this.$el.find('.transcript-turnaround-wrapper');
this.selectedTurnaroundPlan = event.target.value;
// Remove any error if present already.
this.clearPreferenceErrorState($turnaroundContainer);
},
providerSelected: function(event) {
this.resetPlanData();
this.selectedProvider = event.target.value;
// Re-render view
this.reRenderView();
},
reRenderView: function() {
var $courseVideoSettingsContentEl = this.$el.find('.course-video-settings-content'),
dateModified = this.activeTranscriptionPlan
? moment.utc(this.activeTranscriptionPlan.modified).format('ll') : '';
if (!this.selectedProvider) {
// Hide organization credentials and transcript preferences views
$courseVideoSettingsContentEl.hide();
// Render footer
HtmlUtils.setHtml(
this.$el.find('.course-video-settings-footer'),
this.updateSettingsFooterTemplate({
dateModified: dateModified
})
);
return;
}
$courseVideoSettingsContentEl.show();
// If org provider specific credentials are present
if (this.transcriptOrganizationCredentials[this.selectedProvider]) {
this.renderTranscriptPreferences();
} else {
this.renderOrganizationCredentials();
}
},
languageSelected: function(event) {
var $parentEl = $(event.target.parentElement).parent(),
$languagesEl = this.$el.find('.transcript-languages-wrapper'),
selectedLanguage = $parentEl.find('select').val();
// Remove any error if present already.
this.clearPreferenceErrorState($languagesEl);
// Only add if not in the list already.
if (selectedLanguage && _.indexOf(this.selectedLanguages, selectedLanguage) === -1) {
this.selectedLanguages.push(selectedLanguage);
this.addLanguage(selectedLanguage);
// Populate language menu with latest data.
this.populateLanguageMenu();
} else {
this.addErrorState($languagesEl);
}
},
languageRemoved: function(event) {
var selectedLanguage = $(event.target).data('language-code');
$(event.target.parentElement).parent().remove();
// Remove language from selected languages.
// eslint-disable-next-line no-multi-assign
this.selectedLanguages = this.activeLanguages = _.without(this.selectedLanguages, selectedLanguage);
// Populate menu again to reflect latest changes.
this.populateLanguageMenu();
},
renderProviders: function(state) {
var $transcriptProviderWrapperEl = this.$el.find('.transcript-provider-wrapper');
if (!state) {
state = this.selectedProvider ? 'selected' : 'empty'; // eslint-disable-line no-param-reassign
}
// If no transcription plans are sentm return.
if (!this.availableTranscriptionPlans) {
return;
}
if (state === 'empty') {
HtmlUtils.setHtml(
$transcriptProviderWrapperEl,
this.transcriptProviderEmptyStateTemplate({
providers: [
{
key: 'none',
value: '',
name: gettext('None'),
checked: this.selectedProvider === '' ? 'checked' : ''
},
{
key: CIELO24,
value: CIELO24,
name: this.availableTranscriptionPlans[CIELO24].display_name,
checked: this.selectedProvider === CIELO24 ? 'checked' : ''
},
{
key: THREE_PLAY_MEDIA,
value: THREE_PLAY_MEDIA,
name: this.availableTranscriptionPlans[THREE_PLAY_MEDIA].display_name,
checked: this.selectedProvider === THREE_PLAY_MEDIA ? 'checked' : ''
}
]
})
);
} else {
HtmlUtils.setHtml(
$transcriptProviderWrapperEl,
this.transcriptProviderSelectedStateTemplate({
selectedProvider: this.availableTranscriptionPlans[this.selectedProvider].display_name
})
);
this.renderTranscriptPreferences();
}
},
renderTurnaround: function() {
var self = this,
turnaroundPlan = self.getTurnaroundPlan(),
$turnaroundContainer = self.$el.find('.transcript-turnaround-wrapper'),
$turnaround = self.$el.find('#transcript-turnaround');
// Clear error state if present any.
this.clearPreferenceErrorState($turnaroundContainer);
if (turnaroundPlan) {
HtmlUtils.setHtml(
$turnaround,
HtmlUtils.HTML(new Option(gettext('Select turnaround'), ''))
);
_.each(turnaroundPlan, function(value, key) {
var option = new Option(value, key);
if (self.selectedTurnaroundPlan === key) {
option.selected = true;
}
HtmlUtils.append($turnaround, HtmlUtils.HTML(option));
});
$turnaroundContainer.show();
} else {
$turnaroundContainer.hide();
}
},
renderFidelity: function() {
var self = this,
fidelityPlan = self.getFidelityPlan(),
$fidelityContainer = self.$el.find('.transcript-fidelity-wrapper'),
$fidelity = self.$el.find('#transcript-fidelity');
// Clear error state if present any.
this.clearPreferenceErrorState($fidelityContainer);
// Fidelity dropdown
if (fidelityPlan) {
HtmlUtils.setHtml(
$fidelity,
HtmlUtils.HTML(new Option(gettext('Select fidelity'), ''))
);
_.each(fidelityPlan, function(fidelityObject, key) {
var option = new Option(fidelityObject.display_name, key);
if (self.selectedFidelityPlan === key) {
option.selected = true;
}
HtmlUtils.append($fidelity, HtmlUtils.HTML(option));
});
$fidelityContainer.show();
} else {
$fidelityContainer.hide();
}
},
renderTargetLanguages: function() {
var self = this,
// Merge active and selected languages, this handles the case when active languages are present and
// user also has selected some languages but not saved, user changes organization credentials,
// both active and selected languages should be rendered.
selectedLanguages = _.union(self.activeLanguages, self.selectedLanguages),
$languagesPreferenceContainer = self.$el.find('.transcript-languages-wrapper'),
$languagesContainer = self.$el.find('.languages-container');
// Clear error state if present any.
self.clearPreferenceErrorState($languagesPreferenceContainer);
$languagesContainer.empty();
// Show language container if source language is selected.
if (self.selectedVideoSourceLanguage) {
_.each(selectedLanguages, function(language) {
// Only add if not in the list already.
if (_.indexOf(self.selectedLanguages, language) === -1) {
self.selectedLanguages.push(language);
}
// Show active/ add language language container
self.addLanguage(language);
});
$languagesPreferenceContainer.show();
self.populateLanguageMenu();
} else {
$languagesPreferenceContainer.hide();
}
},
renderSourceLanguages: function() {
var self = this,
availableLanguages = self.getTargetLanguages(),
availableTranslations = self.getSourceLanguages(),
$videoSourceLanguageContainer = self.$el.find('.video-source-language-wrapper'),
$languageMenuEl = self.$el.find('.video-source-language'),
selectOptionEl = new Option(gettext('Select language'), '');
// Clear error state if present any.
self.clearPreferenceErrorState($videoSourceLanguageContainer);
if (!_.isEmpty(availableTranslations)) {
$videoSourceLanguageContainer.show();
// We need to set id due to a11y aria-labelledby
selectOptionEl.id = 'video-source-language-none';
HtmlUtils.setHtml(
$languageMenuEl,
HtmlUtils.HTML(selectOptionEl)
);
_.each(availableTranslations, function(translatableLanguage, key) {
var option = new Option(availableLanguages[key], key);
if (self.selectedVideoSourceLanguage === key) {
option.selected = true;
}
HtmlUtils.append(
$languageMenuEl,
HtmlUtils.HTML(option)
);
});
} else {
$videoSourceLanguageContainer.hide();
}
},
populateLanguageMenu: function() {
var availableLanguages = this.getTargetLanguages(),
availableTranslations = this.availableTranscriptionPlans[THREE_PLAY_MEDIA].translations,
$languageMenuEl = this.$el.find('.transcript-language-menu'),
$languageMenuContainerEl = this.$el.find('.transcript-language-menu-container'),
selectOptionEl = new Option(gettext('Select language'), '');
if (this.selectedProvider === THREE_PLAY_MEDIA) {
// Pick out only those languages from plan laguages that also come from video source language.
availableLanguages = _.pick(
availableLanguages,
availableTranslations[this.selectedVideoSourceLanguage]
);
}
// Omit out selected languages from selecting again.
availableLanguages = _.omit(availableLanguages, this.selectedLanguages);
// If no available language is left, then don't even show add language box.
if (_.keys(availableLanguages).length) {
$languageMenuContainerEl.show();
// We need to set id due to a11y aria-labelledby
selectOptionEl.id = 'transcript-language-none';
HtmlUtils.setHtml(
$languageMenuEl,
HtmlUtils.HTML(selectOptionEl)
);
_.each(availableLanguages, function(value, key) {
HtmlUtils.append(
$languageMenuEl,
HtmlUtils.HTML(new Option(value, key))
);
});
} else {
$languageMenuContainerEl.hide();
}
},
addLanguage: function(language) {
var $languagesContainer = this.$el.find('.languages-container');
HtmlUtils.append(
$languagesContainer,
HtmlUtils.joinHtml(
HtmlUtils.HTML('<div class="transcript-language-container">'),
HtmlUtils.interpolateHtml(
HtmlUtils.HTML('<span>{languageDisplayName}</span>'),
{
languageDisplayName: this.getTargetLanguages()[language]
}
),
HtmlUtils.interpolateHtml(
HtmlUtils.HTML('<div class="remove-language-action"><button class="button-link action-remove-language" data-language-code="{languageCode}">{text}<span class="sr">{srText}</span></button></div>'), // eslint-disable-line max-len
{
languageCode: language,
text: gettext('Remove'),
srText: gettext('Press Remove to remove language')
}
),
HtmlUtils.HTML('</div>')
)
);
},
updateSuccessResponseStatus: function(data, successMessage) {
var dateModified = data ? moment.utc(data.modified).format('ll') : '';
successMessage = successMessage ? successMessage : gettext('Settings updated'); // eslint-disable-line no-param-reassign, no-unneeded-ternary, max-len
// Update last modified date
if (dateModified) {
HtmlUtils.setHtml(
this.$el.find('.last-updated-text'),
HtmlUtils.interpolateHtml(
HtmlUtils.HTML('{lastUpdateText} {dateModified}'),
{
lastUpdateText: gettext('Last updated'),
dateModified: dateModified
}
)
);
}
// Now re-render providers state.
this.renderProviders();
this.renderResponseStatus(successMessage, 'success');
// Sync ActiveUploadListView with latest active plan.
this.activeTranscriptionPlan = data;
Backbone.trigger('coursevideosettings:syncActiveTranscriptPreferences', this.activeTranscriptionPlan);
},
updateFailResponseStatus: function(data) {
var errorMessage;
// Enclose inside try-catch so that if we get erroneous data, we could still
// show some error to user
try {
errorMessage = $.parseJSON(data).error;
} catch (e) {} // eslint-disable-line no-empty
this.renderResponseStatus(errorMessage || INTERNAL_SERVER_ERROR_MESSAGE, 'error');
},
renderResponseStatus: function(responseText, type) {
var addClass = type === 'error' ? 'error' : 'success',
removeClass = type === 'error' ? 'success' : 'error',
iconClass = type === 'error' ? 'fa-info-circle' : 'fa-check-circle',
$messageWrapperEl = this.$el.find('.course-video-settings-message-wrapper');
$messageWrapperEl.removeClass(removeClass);
$messageWrapperEl.addClass(addClass);
HtmlUtils.setHtml(
$messageWrapperEl,
HtmlUtils.interpolateHtml(
HtmlUtils.HTML('<div class="course-video-settings-message"><span class="icon fa {iconClass}" aria-hidden="true"></span><span>{text}</span></div>'), // eslint-disable-line max-len
{
text: responseText,
iconClass: iconClass
}
)
);
},
clearResponseStatus: function() {
// Remove parent level state.
var $messageWrapperEl = this.$el.find('.course-video-settings-message-wrapper');
$messageWrapperEl.empty();
$messageWrapperEl.removeClass('error');
$messageWrapperEl.removeClass('success');
},
clearPreferenceErrorState: function($PreferenceContainer) {
$PreferenceContainer.removeClass('error');
$PreferenceContainer.find('.error-icon').empty();
$PreferenceContainer.find('.error-info').empty();
// Also clear response status if present already
this.clearResponseStatus();
},
addErrorState: function($PreferenceContainer) {
var requiredText = gettext('Required'),
infoIconHtml = HtmlUtils.HTML('<span class="icon fa fa-info-circle" aria-hidden="true"></span>');
$PreferenceContainer.addClass('error');
HtmlUtils.setHtml(
$PreferenceContainer.find('.error-icon'),
infoIconHtml
);
HtmlUtils.setHtml(
$PreferenceContainer.find('.error-info'),
requiredText
);
},
validateCourseVideoSettings: function() {
var isValid = true,
$turnaroundEl = this.$el.find('.transcript-turnaround-wrapper'),
$fidelityEl = this.$el.find('.transcript-fidelity-wrapper'),
$languagesEl = this.$el.find('.transcript-languages-wrapper'),
$videoSourcelanguageEl = this.$el.find('.video-source-language-wrapper');
// Explicit None selected case.
if (this.selectedProvider === '') {
return true;
}
if (!this.selectedTurnaroundPlan) {
isValid = false;
this.addErrorState($turnaroundEl);
} else {
this.clearPreferenceErrorState($turnaroundEl);
}
if (this.selectedProvider === CIELO24 && !this.selectedFidelityPlan) {
isValid = false;
this.addErrorState($fidelityEl);
} else {
this.clearPreferenceErrorState($fidelityEl);
}
if (this.selectedProvider === THREE_PLAY_MEDIA && !this.selectedVideoSourceLanguage) {
isValid = false;
this.addErrorState($videoSourcelanguageEl);
} else {
this.clearPreferenceErrorState($videoSourcelanguageEl);
}
if (this.selectedLanguages.length === 0) {
isValid = false;
this.addErrorState($languagesEl);
} else {
this.clearPreferenceErrorState($languagesEl);
}
return isValid;
},
validateOrganizationCredentials: function() {
var $OrganizationApiSecretWrapperEl,
$OrganizationUsernameWrapperEl,
isValid = true,
$OrganizationApiKeyWrapperEl = this.$el.find('.' + this.selectedProvider + '-api-key-wrapper');
// Explicit None selected case.
if (this.selectedProvider === '') {
return false;
}
if ($OrganizationApiKeyWrapperEl.find('input').val() === '') {
isValid = false;
this.addErrorState($OrganizationApiKeyWrapperEl);
} else {
this.clearPreferenceErrorState($OrganizationApiKeyWrapperEl);
}
if (this.selectedProvider === THREE_PLAY_MEDIA) {
$OrganizationApiSecretWrapperEl = this.$el.find('.' + this.selectedProvider + '-api-secret-wrapper');
if ($OrganizationApiSecretWrapperEl.find('input').val() === '') {
isValid = false;
this.addErrorState($OrganizationApiSecretWrapperEl);
} else {
this.clearPreferenceErrorState($OrganizationApiSecretWrapperEl);
}
} else {
$OrganizationUsernameWrapperEl = this.$el.find('.' + this.selectedProvider + '-username-wrapper');
if ($OrganizationUsernameWrapperEl.find('input').val() === '') {
isValid = false;
this.addErrorState($OrganizationUsernameWrapperEl);
} else {
this.clearPreferenceErrorState($OrganizationUsernameWrapperEl);
}
}
return isValid;
},
saveTranscriptPreferences: function() {
var self = this,
responseTranscriptPreferences;
// First clear response status if present already
this.clearResponseStatus();
if (self.selectedProvider) {
$.postJSON(self.transcriptHandlerUrl, {
provider: self.selectedProvider,
cielo24_fidelity: self.selectedFidelityPlan,
cielo24_turnaround: self.selectedProvider === CIELO24 ? self.selectedTurnaroundPlan : '',
three_play_turnaround: self.selectedProvider === THREE_PLAY_MEDIA ? self.selectedTurnaroundPlan : '', // eslint-disable-line max-len
preferred_languages: self.selectedLanguages,
video_source_language: self.selectedVideoSourceLanguage,
global: false // Do not trigger global AJAX error handler
}, function(data) {
responseTranscriptPreferences = data ? data.transcript_preferences : null;
self.updateSuccessResponseStatus(responseTranscriptPreferences);
}).fail(function(jqXHR) {
self.updateFailResponseStatus(jqXHR.responseText);
});
} else {
$.ajax({
type: 'DELETE',
url: self.transcriptHandlerUrl
}).done(function() {
responseTranscriptPreferences = null;
self.updateSuccessResponseStatus(
responseTranscriptPreferences,
gettext('Automatic transcripts are disabled.')
);
}).fail(function(jqXHR) {
self.updateFailResponseStatus(jqXHR.responseText);
});
}
},
saveOrganizationCredentials: function() {
var self = this,
username,
apiSecret,
apiKey = this.$el.find('.' + this.selectedProvider + '-api-key').val();
// First clear response status if present already
this.clearResponseStatus();
if (this.selectedProvider === THREE_PLAY_MEDIA) {
apiSecret = this.$el.find('.' + this.selectedProvider + '-api-secret').val();
} else {
username = this.$el.find('.' + this.selectedProvider + '-username').val();
}
$.postJSON(self.transcriptCredentialsHandlerUrl, {
provider: self.selectedProvider,
api_key: apiKey,
api_secret_key: apiSecret,
username: username,
global: false // Do not trigger global AJAX error handler
}, function() {
self.$el.find('.organization-credentials-wrapper').hide();
// Update org credentials for selected provider
self.transcriptOrganizationCredentials[self.selectedProvider] = true;
self.updateSuccessResponseStatus(
self.activeTranscriptionPlan,
gettext('{selectedProvider} credentials saved').replace(
'{selectedProvider}',
self.availableTranscriptionPlans[self.selectedProvider].display_name
)
);
}).fail(function(jqXHR) {
self.updateFailResponseStatus(jqXHR.responseText);
});
},
updateOrganizationCredentials: function() {
if (this.validateOrganizationCredentials()) {
this.saveOrganizationCredentials();
}
},
updateCourseVideoSettings: function() {
var $messageWrapperEl = this.$el.find('.course-video-settings-message-wrapper');
if (this.validateCourseVideoSettings()) {
this.saveTranscriptPreferences();
} else {
$messageWrapperEl.empty();
}
},
discardChanges: function() {
this.setActiveTranscriptPlanData();
// Re-render views
this.renderProviders();
this.reRenderView();
},
renderOrganizationCredentials: function() {
var $courseVideoSettingsContentEl = this.$el.find('.course-video-settings-content');
// Render empty state providers view.
this.renderProviders('empty');
HtmlUtils.setHtml(
$courseVideoSettingsContentEl,
this.organizationCredentialsTemplate({
selectedProvider: {
key: this.selectedProvider,
name: this.availableTranscriptionPlans[this.selectedProvider].display_name
},
organizationCredentialsExists: this.transcriptOrganizationCredentials[this.selectedProvider],
CIELO24: CIELO24,
THREE_PLAY_MEDIA: THREE_PLAY_MEDIA
})
);
// Render footer
HtmlUtils.setHtml(
this.$el.find('.course-video-settings-footer'),
this.organizationCredentialsFooterTemplate({})
);
},
renderTranscriptPreferences: function() {
var $courseVideoSettingsContentEl = this.$el.find('.course-video-settings-content'),
dateModified = this.activeTranscriptionPlan
? moment.utc(this.activeTranscriptionPlan.modified).format('ll') : '';
HtmlUtils.setHtml(
$courseVideoSettingsContentEl,
this.transcriptPreferencesTemplate({
selectedProvider: this.selectedProvider,
THREE_PLAY_MEDIA: THREE_PLAY_MEDIA
})
);
// Render transcript preferences.
this.renderTurnaround();
this.renderFidelity();
this.renderSourceLanguages();
this.renderTargetLanguages();
// Render footer
HtmlUtils.setHtml(
this.$el.find('.course-video-settings-footer'),
this.updateSettingsFooterTemplate({
dateModified: dateModified
})
);
},
render: function() {
var dateModified = this.activeTranscriptionPlan
? moment.utc(this.activeTranscriptionPlan.modified).format('ll') : '';
HtmlUtils.setHtml(this.$el, this.template({}));
// Render footer
HtmlUtils.setHtml(
this.$el.find('.course-video-settings-footer'),
this.updateSettingsFooterTemplate({
dateModified: dateModified
})
);
this.renderProviders();
this.registerCloseClickHandler();
this.setFixedCourseVideoSettingsPane();
return this;
},
setFixedCourseVideoSettingsPane: function() {
var $courseVideoSettingsButton = $('.course-video-settings-button'),
$courseVideoSettingsContainer = this.$el.find('.course-video-settings-container'),
initialPositionTop = $courseVideoSettingsContainer.offset().top,
// Button right position = width of window - button left position - button width - paddings - border.
$courseVideoSettingsButtonRight = $(window).width()
- $courseVideoSettingsButton.offset().left
- $courseVideoSettingsButton.width()
- $courseVideoSettingsButton.css('padding-left').replace('px', '')
- $courseVideoSettingsButton.css('padding-right').replace('px', '')
- $courseVideoSettingsButton.css('border-width').replace('px', '') - 5; // Extra pixles for slack;
// Set to windows total height
$courseVideoSettingsContainer.css('height', $(window).height());
// Start settings pane adjascent to 'course video settings' button.
$courseVideoSettingsContainer.css('right', $courseVideoSettingsButtonRight);
// Make sticky when scroll reaches top.
$(window).scroll(function() {
if ($(window).scrollTop() >= initialPositionTop) {
$courseVideoSettingsContainer.addClass('fixed-container');
} else {
$courseVideoSettingsContainer.removeClass('fixed-container');
}
});
},
closeCourseVideoSettings: function() {
// TODO: Slide out when closing settings pane. See EDUCATOR-1477
// Trigger destroy transcript event.
Backbone.trigger('coursevideosettings:destroyCourseVideoSettingsView');
// Unbind any events associated
this.undelegateEvents();
this.stopListening();
// Empty this.$el content from DOM
this.$el.empty();
// Reset everything.
this.resetPlanData();
}
});
return CourseVideoSettingsView;
});

View File

@@ -1,98 +0,0 @@
define(
['underscore', 'gettext', 'js/utils/date_utils', 'js/views/baseview', 'common/js/components/views/feedback_prompt',
'common/js/components/views/feedback_notification', 'js/views/video_thumbnail', 'js/views/video_transcripts',
'js/views/video_status', 'common/js/components/utils/view_utils', 'edx-ui-toolkit/js/utils/html-utils',
'text!templates/previous-video-upload.underscore'],
function(_, gettext, DateUtils, BaseView, PromptView, NotificationView, VideoThumbnailView, VideoTranscriptsView,
VideoStatusView, ViewUtils, HtmlUtils, previousVideoUploadTemplate) {
'use strict';
var PreviousVideoUploadView = BaseView.extend({
tagName: 'div',
className: 'video-row',
events: {
'click .remove-video-button.action-button': 'removeVideo'
},
initialize: function(options) {
this.template = HtmlUtils.template(previousVideoUploadTemplate);
this.videoHandlerUrl = options.videoHandlerUrl;
this.videoImageUploadEnabled = options.videoImageSettings.video_image_upload_enabled;
if (this.videoImageUploadEnabled) {
this.videoThumbnailView = new VideoThumbnailView({
model: this.model,
imageUploadURL: options.videoImageUploadURL,
defaultVideoImageURL: options.defaultVideoImageURL,
videoImageSettings: options.videoImageSettings
});
}
this.videoTranscriptsView = new VideoTranscriptsView({
transcripts: this.model.get('transcripts'),
edxVideoID: this.model.get('edx_video_id'),
clientVideoID: this.model.get('client_video_id'),
transcriptionStatus: this.model.get('transcription_status'),
errorDescription: this.model.get('error_description'),
transcriptAvailableLanguages: options.transcriptAvailableLanguages,
videoSupportedFileFormats: options.videoSupportedFileFormats,
videoTranscriptSettings: options.videoTranscriptSettings
});
this.VideoStatusView = new VideoStatusView({
status: this.model.get('status'),
showError: !this.model.get('transcription_status'),
errorDescription: this.model.get('error_description')
});
},
render: function() {
var renderedAttributes = {
videoImageUploadEnabled: this.videoImageUploadEnabled,
created: DateUtils.renderDate(this.model.get('created')),
status: this.model.get('status')
};
HtmlUtils.setHtml(
this.$el,
this.template(
_.extend({}, this.model.attributes, renderedAttributes)
)
);
if (this.videoImageUploadEnabled) {
this.videoThumbnailView.setElement(this.$('.thumbnail-col')).render();
}
this.videoTranscriptsView.setElement(this.$('.transcripts-col')).render();
this.VideoStatusView.setElement(this.$('.status-col')).render();
return this;
},
removeVideo: function(event) {
var videoView = this;
event.preventDefault();
ViewUtils.confirmThenRunOperation(
gettext('Are you sure you want to remove this video from the list?'),
gettext('Removing a video from this list does not affect course content. Any content that uses a previously uploaded video ID continues to display in the course.'), // eslint-disable-line max-len
gettext('Remove'),
function() {
ViewUtils.runOperationShowingMessage(
gettext('Removing'),
function() {
return $.ajax({
url: videoView.videoHandlerUrl + '/' + videoView.model.get('edx_video_id'),
type: 'DELETE'
}).done(function() {
videoView.remove();
});
}
);
}
);
}
});
return PreviousVideoUploadView;
}
);

View File

@@ -1,51 +0,0 @@
define(
['jquery', 'underscore', 'backbone', 'js/views/baseview', 'edx-ui-toolkit/js/utils/html-utils',
'js/views/previous_video_upload', 'text!templates/previous-video-upload-list.underscore'],
function($, _, Backbone, BaseView, HtmlUtils, PreviousVideoUploadView, previousVideoUploadListTemplate) {
'use strict';
var PreviousVideoUploadListView = BaseView.extend({
tagName: 'section',
className: 'wrapper-assets',
initialize: function(options) {
this.template = HtmlUtils.template(previousVideoUploadListTemplate);
this.encodingsDownloadUrl = options.encodingsDownloadUrl;
this.videoImageUploadEnabled = options.videoImageSettings.video_image_upload_enabled;
this.itemViews = this.collection.map(function(model) {
return new PreviousVideoUploadView({
videoImageUploadURL: options.videoImageUploadURL,
defaultVideoImageURL: options.defaultVideoImageURL,
videoHandlerUrl: options.videoHandlerUrl,
videoImageSettings: options.videoImageSettings,
videoTranscriptSettings: options.videoTranscriptSettings,
model: model,
transcriptAvailableLanguages: options.transcriptAvailableLanguages,
videoSupportedFileFormats: options.videoSupportedFileFormats
});
});
},
render: function() {
var $el = this.$el,
$tabBody;
HtmlUtils.setHtml(
this.$el,
this.template({
encodingsDownloadUrl: this.encodingsDownloadUrl,
videoImageUploadEnabled: this.videoImageUploadEnabled
})
);
$tabBody = $el.find('.js-table-body');
_.each(this.itemViews, function(view) {
$tabBody.append(view.render().$el);
});
return this;
}
});
return PreviousVideoUploadListView;
}
);

View File

@@ -1,37 +0,0 @@
define(
[
'js/views/baseview', 'edx-ui-toolkit/js/utils/html-utils', 'text!templates/video-status.underscore'
],
function(BaseView, HtmlUtils, videoStatusTemplate) {
'use strict';
var VideoStatusView = BaseView.extend({
tagName: 'div',
initialize: function(options) {
this.status = options.status;
this.showError = options.showError;
this.errorDescription = options.errorDescription;
this.template = HtmlUtils.template(videoStatusTemplate);
},
/*
Renders status view.
*/
render: function() {
HtmlUtils.setHtml(
this.$el,
this.template({
status: this.status,
show_error: this.showError,
error_description: this.errorDescription
})
);
return this;
}
});
return VideoStatusView;
}
);

View File

@@ -1,387 +0,0 @@
define(
['underscore', 'gettext', 'moment', 'js/utils/date_utils', 'js/views/baseview',
'common/js/components/utils/view_utils', 'edx-ui-toolkit/js/utils/html-utils',
'edx-ui-toolkit/js/utils/string-utils', 'text!templates/video-thumbnail.underscore',
'text!templates/video-thumbnail-error.underscore'],
function(_, gettext, moment, DateUtils, BaseView, ViewUtils, HtmlUtils, StringUtils, VideoThumbnailTemplate,
VideoThumbnailErrorTemplate) {
'use strict';
var VideoThumbnailView = BaseView.extend({
events: {
'click .thumbnail-wrapper': 'chooseFile',
'mouseover .thumbnail-wrapper': 'showHoverState',
'mouseout .thumbnail-wrapper': 'hideHoverState',
'focus .thumbnail-wrapper': 'showHoverState',
'blur .thumbnail-wrapper': 'hideHoverState'
},
initialize: function(options) {
this.template = HtmlUtils.template(VideoThumbnailTemplate);
this.thumbnailErrorTemplate = HtmlUtils.template(VideoThumbnailErrorTemplate);
this.imageUploadURL = options.imageUploadURL;
this.defaultVideoImageURL = options.defaultVideoImageURL;
this.action = this.model.get('course_video_image_url') ? 'edit' : 'upload';
this.videoImageSettings = options.videoImageSettings;
this.actionsInfo = {
upload: {
name: 'upload',
icon: '',
text: gettext('Add Thumbnail')
},
edit: {
name: 'edit',
actionText: gettext('Edit Thumbnail'),
icon: '<span class="icon fa fa-pencil" aria-hidden="true"></span>',
text: HtmlUtils.interpolateHtml(
// Translators: This is a 2 part text which tells the image requirements.
gettext('{InstructionsSpanStart}{videoImageResoultion}{lineBreak} {videoImageSupportedFileFormats}{spanEnd}'), // eslint-disable-line max-len
{
videoImageResoultion: this.getVideoImageResolution(),
videoImageSupportedFileFormats: this.getVideoImageSupportedFileFormats().humanize,
lineBreak: HtmlUtils.HTML('<br>'),
InstructionsSpanStart: HtmlUtils.HTML('<span class="requirements-instructions">'),
spanEnd: HtmlUtils.HTML('</span>')
}
).toString()
},
error: {
name: 'error',
icon: '',
text: gettext('Image upload failed')
},
progress: {
name: 'progress-action',
icon: '<span class="icon fa fa-spinner fa-pulse fa-spin" aria-hidden="true"></span>',
text: gettext('Uploading')
},
requirements: {
name: 'requirements',
icon: '',
text: HtmlUtils.interpolateHtml(
// Translators: This is a 3 part text which tells the image requirements.
gettext('{ReqTextSpanStart}Requirements{spanEnd}{lineBreak}{InstructionsSpanStart}{videoImageResoultion}{lineBreak} {videoImageSupportedFileFormats}{spanEnd}'), // eslint-disable-line max-len
{
videoImageResoultion: this.getVideoImageResolution(),
videoImageSupportedFileFormats: this.getVideoImageSupportedFileFormats().humanize,
lineBreak: HtmlUtils.HTML('<br>'),
ReqTextSpanStart: HtmlUtils.HTML('<span class="requirements-text">'),
InstructionsSpanStart: HtmlUtils.HTML('<span class="requirements-instructions">'),
spanEnd: HtmlUtils.HTML('</span>')
}
).toString()
}
};
_.bindAll(
this, 'render', 'chooseFile', 'imageSelected', 'imageUploadSucceeded', 'imageUploadFailed',
'showHoverState', 'hideHoverState'
);
},
render: function() {
HtmlUtils.setHtml(
this.$el,
this.template({
action: this.action,
imageAltText: this.getImageAltText(),
videoId: this.model.get('edx_video_id'),
actionInfo: this.actionsInfo[this.action],
thumbnailURL: this.model.get('course_video_image_url') || this.defaultVideoImageURL,
duration: this.getDuration(this.model.get('duration')),
videoImageSupportedFileFormats: this.getVideoImageSupportedFileFormats(),
videoImageMaxSize: this.getVideoImageMaxSize(),
videoImageResolution: this.getVideoImageResolution()
})
);
this.hideHoverState();
return this;
},
getVideoImageSupportedFileFormats: function() {
var supportedFormats = _.reject(_.keys(this.videoImageSettings.supported_file_formats), function(item) {
// Don't show redundant extensions to end user.
return item === '.bmp2' || item === '.jpeg';
}).sort();
return {
humanize: supportedFormats.slice(0, -1).join(', ') + ' or ' + supportedFormats.slice(-1),
machine: _.values(this.videoImageSettings.supported_file_formats)
};
},
getVideoImageMaxSize: function() {
return {
humanize: this.videoImageSettings.max_size / (1024 * 1024) + ' MB',
machine: this.videoImageSettings.max_size
};
},
getVideoImageMinSize: function() {
return {
humanize: this.videoImageSettings.min_size / 1024 + ' KB',
machine: this.videoImageSettings.min_size
};
},
getVideoImageResolution: function() {
return StringUtils.interpolate(
// Translators: message will be like 1280x720 pixels
gettext('{maxWidth}x{maxHeight} pixels'),
{maxWidth: this.videoImageSettings.max_width, maxHeight: this.videoImageSettings.max_height}
);
},
getImageAltText: function() {
return StringUtils.interpolate(
// Translators: message will be like Thumbnail for Arrow.mp4
gettext('Thumbnail for {videoName}'),
{videoName: this.model.get('client_video_id')}
);
},
getSRText: function() {
return StringUtils.interpolate(
// Translators: message will be like Add Thumbnail - Arrow.mp4
gettext('Add Thumbnail - {videoName}'),
{videoName: this.model.get('client_video_id')}
);
},
getDuration: function(durationSeconds) {
if (durationSeconds <= 0) {
return null;
}
return {
humanize: this.getDurationTextHuman(durationSeconds),
machine: this.getDurationTextMachine(durationSeconds)
};
},
getDurationTextHuman: function(durationSeconds) {
var humanize = this.getHumanizeDuration(durationSeconds);
// This case is specifically to handle values between 0 and 1 seconds excluding upper bound
if (humanize.length === 0) {
return '';
}
return StringUtils.interpolate(
// Translators: humanizeDuration will be like 10 minutes, an hour and 20 minutes etc
gettext('Video duration is {humanizeDuration}'),
{
humanizeDuration: humanize
}
);
},
getHumanizeDuration: function(durationSeconds) {
var minutes,
seconds,
minutesText = null,
secondsText = null;
minutes = Math.trunc(moment.duration(durationSeconds, 'seconds').asMinutes());
seconds = moment.duration(durationSeconds, 'seconds').seconds();
if (minutes) {
minutesText = minutes > 1 ? gettext('minutes') : gettext('minute');
minutesText = StringUtils.interpolate(
// Translators: message will be like 15 minutes, 1 minute
gettext('{minutes} {unit}'),
{minutes: minutes, unit: minutesText}
);
}
if (seconds) {
secondsText = seconds > 1 ? gettext('seconds') : gettext('second');
secondsText = StringUtils.interpolate(
// Translators: message will be like 20 seconds, 1 second
gettext('{seconds} {unit}'),
{seconds: seconds, unit: secondsText}
);
}
// Translators: `and` will be used to combine both miuntes and seconds like `13 minutes and 45 seconds`
return _.filter([minutesText, secondsText]).join(gettext(' and '));
},
getDurationTextMachine: function(durationSeconds) {
var minutes = Math.floor(durationSeconds / 60),
seconds = Math.floor(durationSeconds - minutes * 60);
return minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
},
chooseFile: function() {
this.$('.upload-image-input').fileupload({
url: this.imageUploadURL + '/' + encodeURIComponent(this.model.get('edx_video_id')),
add: this.imageSelected,
done: this.imageUploadSucceeded,
fail: this.imageUploadFailed
});
},
imageSelected: function(event, data) {
var errorMessage;
// If an error is already present above the video element, remove it.
this.clearErrorMessage(this.model.get('edx_video_id'));
errorMessage = this.validateImageFile(data.files[0]);
if (!errorMessage) {
// Do not trigger global AJAX error handler
data.global = false; // eslint-disable-line no-param-reassign
this.readMessages([gettext('Video image upload started')]);
this.showUploadInProgressMessage();
data.submit();
} else {
this.showErrorMessage(errorMessage);
}
},
imageUploadSucceeded: function(event, data) {
this.action = 'edit';
this.setActionInfo(this.action, false);
this.$('img').attr('src', data.result.image_url);
this.readMessages([gettext('Video image upload completed')]);
},
imageUploadFailed: function(event, data) {
var errorText = JSON.parse(data.jqXHR.responseText).error;
this.showErrorMessage(errorText);
},
showUploadInProgressMessage: function() {
this.action = 'progress';
this.setActionInfo(this.action, true);
},
showHoverState: function() {
if (this.action === 'upload') {
this.setActionInfo('requirements', true, this.getSRText());
} else if (this.action === 'edit') {
this.setActionInfo(this.action, true);
}
this.$('.thumbnail-wrapper').addClass('focused');
},
hideHoverState: function() {
if (this.action === 'upload') {
this.setActionInfo(this.action, true);
} else if (this.action === 'edit') {
this.setActionInfo(this.action, false);
}
this.$('.thumbnail-wrapper').removeClass('focused');
},
setActionInfo: function(action, showText, additionalSRText) {
var hasError = this.$('.thumbnail-wrapper').hasClass('error');
this.$('.thumbnail-action').toggle(showText);
HtmlUtils.setHtml(
this.$('.thumbnail-action .action-icon'),
HtmlUtils.HTML(this.actionsInfo[action].icon)
);
HtmlUtils.setHtml(
this.$('.thumbnail-action .action-text'),
HtmlUtils.HTML(this.actionsInfo[action].text)
);
this.$('.thumbnail-action .action-text-sr').text(additionalSRText || '');
this.$('.thumbnail-wrapper').attr('class', 'thumbnail-wrapper {action}'.replace('{action}', action));
this.$('.thumbnail-action .action-icon')
.attr('class', 'action-icon {action}'.replace('{action}', action));
// Add error class if it was already present.
if (hasError) {
this.$('.thumbnail-wrapper').addClass('error');
}
// Don't show edit-container layout on progress action.
if (action === 'progress') {
this.$('.thumbnail-action .edit-container').toggle(false);
} else if (action === 'edit') {
this.$('.thumbnail-action .edit-container').toggle(true);
HtmlUtils.setHtml(
this.$('.thumbnail-action .edit-container .action-icon'),
HtmlUtils.HTML(this.actionsInfo[action].icon)
);
HtmlUtils.setHtml(
this.$('.thumbnail-action .edit-container .edit-action-text'),
HtmlUtils.HTML(this.actionsInfo[action].actionText)
);
}
},
validateImageFile: function(imageFile) {
var errorMessage = '';
if (!_.contains(this.getVideoImageSupportedFileFormats().machine, imageFile.type)) {
errorMessage = StringUtils.interpolate(
// Translators: supportedFileFormats will be like .bmp, gif, .jpg or .png.
gettext(
'This image file type is not supported. Supported file types are {supportedFileFormats}.'
),
{
supportedFileFormats: this.getVideoImageSupportedFileFormats().humanize
}
);
} else if (imageFile.size > this.getVideoImageMaxSize().machine) {
errorMessage = StringUtils.interpolate(
// Translators: maxFileSizeInMB will be like 2 MB.
gettext('The selected image must be smaller than {maxFileSizeInMB}.'),
{
maxFileSizeInMB: this.getVideoImageMaxSize().humanize
}
);
} else if (imageFile.size < this.getVideoImageMinSize().machine) {
errorMessage = StringUtils.interpolate(
// Translators: minFileSizeInKB will be like 2 KB.
gettext('The selected image must be larger than {minFileSizeInKB}.'),
{
minFileSizeInKB: this.getVideoImageMinSize().humanize
}
);
}
return errorMessage;
},
clearErrorMessage: function(videoId) {
var $thumbnailWrapperEl = $('.thumbnail-error-wrapper[data-video-id="' + videoId + '"]');
if ($thumbnailWrapperEl.length) {
$thumbnailWrapperEl.remove();
}
// Remove error class from thumbnail wrapper as well.
$('.thumbnail-wrapper').removeClass('error');
},
showErrorMessage: function(errorText) {
var videoId = this.model.get('edx_video_id'),
$parentRowEl = $(this.$el.parent());
// If image url is not this.defaultVideoImageURL then it means image is uploaded
// so we should treat it as edit action otherwise default upload action.
this.action = this.$('.thumbnail-wrapper img').attr('src') !== this.defaultVideoImageURL
? 'edit' : 'upload';
this.setActionInfo(this.action, true);
this.readMessages([gettext('Could not upload the video image file'), errorText]);
errorText = gettext('Image upload failed. ') + errorText; // eslint-disable-line no-param-reassign
// Add error wrapper html to current video element row.
$parentRowEl.before( // xss-lint: disable=javascript-jquery-insertion
HtmlUtils.ensureHtml(
this.thumbnailErrorTemplate({videoId: videoId, errorText: errorText})
).toString()
);
this.$el.find('.thumbnail-wrapper').addClass('error');
},
readMessages: function(messages) {
if ($(window).prop('SR') !== undefined) {
$(window).prop('SR').readTexts(messages);
}
}
});
return VideoThumbnailView;
}
);

View File

@@ -1,364 +0,0 @@
define(
['underscore', 'gettext', 'js/views/baseview', 'common/js/components/views/feedback_prompt',
'edx-ui-toolkit/js/utils/html-utils', 'edx-ui-toolkit/js/utils/string-utils',
'common/js/components/utils/view_utils', 'text!templates/video-transcripts.underscore',
'text!templates/video-transcript-upload-status.underscore'],
function(_, gettext, BaseView, PromptView, HtmlUtils, StringUtils, ViewUtils, videoTranscriptsTemplate,
videoTranscriptUploadStatusTemplate) {
'use strict';
var VideoTranscriptsView = BaseView.extend({
tagName: 'div',
events: {
'click .toggle-show-transcripts-button': 'toggleShowTranscripts',
'click .upload-transcript-button': 'chooseFile',
'click .delete-transcript-button': 'deleteTranscript',
'click .more-details-action': 'showUploadFailureMessage'
},
initialize: function(options) {
this.isCollapsed = true;
this.transcripts = options.transcripts;
this.edxVideoID = options.edxVideoID;
this.clientVideoID = options.clientVideoID;
this.transcriptionStatus = options.transcriptionStatus;
this.errorDescription = options.errorDescription;
this.transcriptAvailableLanguages = options.transcriptAvailableLanguages;
this.videoSupportedFileFormats = options.videoSupportedFileFormats;
this.videoTranscriptSettings = options.videoTranscriptSettings;
this.template = HtmlUtils.template(videoTranscriptsTemplate);
this.transcriptUploadStatusTemplate = HtmlUtils.template(videoTranscriptUploadStatusTemplate);
this.defaultFailureTitle = gettext('The file could not be uploaded.');
this.defaultFailureMessage = gettext('This may be happening because of an error with our server or your internet connection. Try refreshing the page or making sure you are online.'); // eslint-disable-line max-len
this.transcriptUploadStatuses = {
uploaded: {
statusClass: 'success',
iconClasses: 'fa-check',
shortMessage: 'Transcript uploaded.',
hiddenClass: 'hidden'
},
uploading: {
statusClass: '',
iconClasses: 'fa-spinner fa-pulse',
shortMessage: 'Uploading transcript.',
hiddenClass: 'hidden'
},
failed: {
statusClass: 'error',
iconClasses: 'fa-warning',
shortMessage: 'Upload failed.',
hiddenClass: ''
},
validationFailed: {
statusClass: 'error',
iconClasses: 'fa-warning',
shortMessage: 'Validation failed.',
hiddenClass: ''
}
};
// This is needed to attach transcript methods to this object while uploading.
_.bindAll(
this, 'render', 'chooseFile', 'transcriptSelected', 'transcriptUploadSucceeded',
'transcriptUploadFailed'
);
},
/*
Returns transcript title.
*/
getTranscriptClientTitle: function() {
var clientTitle = this.clientVideoID;
// Remove video file extension for transcript title.
_.each(this.videoSupportedFileFormats, function(videoFormat) {
clientTitle = clientTitle.replace(videoFormat, '');
});
return clientTitle.substring(0, 20);
},
/*
Returns transcript download link.
*/
getTranscriptDownloadLink: function(edxVideoID, transcriptLanguageCode, transcriptDownloadHandlerUrl) {
return StringUtils.interpolate(
'{transcriptDownloadHandlerUrl}?edx_video_id={edxVideoID}&language_code={transcriptLanguageCode}',
{
transcriptDownloadHandlerUrl: transcriptDownloadHandlerUrl,
edxVideoID: edxVideoID,
transcriptLanguageCode: transcriptLanguageCode
}
);
},
/*
Returns transcript delete handler url.
*/
getTranscriptDeleteUrl: function(edxVideoID, transcriptLanguageCode, transcriptDeleteHandlerUrl) {
return StringUtils.interpolate(
'{transcriptDeleteHandlerUrl}/{edxVideoID}/{transcriptLanguageCode}',
{
transcriptDeleteHandlerUrl: transcriptDeleteHandlerUrl,
edxVideoID: edxVideoID,
transcriptLanguageCode: transcriptLanguageCode
}
);
},
/*
Toggles Show/Hide transcript button and transcripts container.
*/
toggleShowTranscripts: function() {
var $transcriptsWrapperEl = this.$el.find('.video-transcripts-wrapper');
if ($transcriptsWrapperEl.hasClass('hidden')) {
this.showTranscripts();
this.isCollapsed = false;
} else {
this.hideTranscripts();
this.isCollapsed = true;
}
},
showTranscripts: function() {
// Show transcript wrapper
this.$el.find('.video-transcripts-wrapper').removeClass('hidden');
// Update button text.
HtmlUtils.setHtml(
this.$el.find('.toggle-show-transcripts-button-text'),
StringUtils.interpolate(
gettext('Hide transcripts ({transcriptCount})'),
{
transcriptCount: this.transcripts.length
}
)
);
this.$el.find('.toggle-show-transcripts-icon')
.removeClass('fa-caret-right')
.addClass('fa-caret-down');
},
hideTranscripts: function() {
// Hide transcript wrapper
this.$el.find('.video-transcripts-wrapper').addClass('hidden');
// Update button text.
HtmlUtils.setHtml(
this.$el.find('.toggle-show-transcripts-button-text'),
StringUtils.interpolate(
gettext('Show transcripts ({transcriptCount})'),
{
transcriptCount: this.transcripts.length
}
)
);
this.$el.find('.toggle-show-transcripts-icon')
.removeClass('fa-caret-down')
.addClass('fa-caret-right');
},
validateTranscriptUpload: function(file) {
var errorMessage = '',
fileName = file.name,
fileType = fileName.substr(fileName.lastIndexOf('.') + 1);
if (fileType !== this.videoTranscriptSettings.trancript_download_file_format) {
errorMessage = gettext(
'This file type is not supported. Supported file type is {supportedFileFormat}.'
)
.replace('{supportedFileFormat}', this.videoTranscriptSettings.trancript_download_file_format);
}
return errorMessage;
},
chooseFile: function(event) {
var $transcriptContainer = $(event.target).parents('.video-transcript-content'),
$transcriptUploadEl = $transcriptContainer.find('.upload-transcript-input');
$transcriptUploadEl.fileupload({
url: this.videoTranscriptSettings.transcript_upload_handler_url,
add: this.transcriptSelected,
done: this.transcriptUploadSucceeded,
fail: this.transcriptUploadFailed,
formData: {
edx_video_id: this.edxVideoID,
language_code: $transcriptContainer.attr('data-language-code'),
new_language_code: $transcriptContainer.find('.transcript-language-menu').val()
}
});
$transcriptUploadEl.click();
},
transcriptSelected: function(event, data) {
var errorMessage,
$transcriptContainer = $(event.target).parents('.video-transcript-content');
errorMessage = this.validateTranscriptUpload(data.files[0]);
if (!errorMessage) {
// Do not trigger global AJAX error handler
data.global = false; // eslint-disable-line no-param-reassign
data.submit();
this.renderMessage($transcriptContainer, 'uploading');
} else {
// Reset transcript language back to original.
$transcriptContainer.find('.transcript-language-menu').val($transcriptContainer.attr('data-language-code')); // eslint-disable-line max-len
this.renderMessage($transcriptContainer, 'validationFailed', errorMessage);
}
},
transcriptUploadSucceeded: function(event, data) {
var languageCode = data.formData.language_code,
newLanguageCode = data.formData.new_language_code,
$transcriptContainer = this.$el.find('.video-transcript-content[data-language-code="' + languageCode + '"]'); // eslint-disable-line max-len
$transcriptContainer.attr('data-language-code', newLanguageCode);
$transcriptContainer.find('.download-transcript-button').attr(
'href',
this.getTranscriptDownloadLink(
this.edxVideoID,
newLanguageCode,
this.videoTranscriptSettings.transcript_download_handler_url
)
);
HtmlUtils.setHtml(
$transcriptContainer.find('.transcript-title'),
StringUtils.interpolate(gettext('{transcriptClientTitle}_{transcriptLanguageCode}.{fileExtension}'),
{
transcriptClientTitle: this.getTranscriptClientTitle(),
transcriptLanguageCode: newLanguageCode,
fileExtension: this.videoTranscriptSettings.trancript_download_file_format
}
)
);
this.renderMessage($transcriptContainer, 'uploaded');
},
transcriptUploadFailed: function(event, data) {
var errorMessage,
languageCode = data.formData.language_code,
$transcriptContainer = this.$el.find('.video-transcript-content[data-language-code="' + languageCode + '"]'); // eslint-disable-line max-len
try {
errorMessage = JSON.parse(data.jqXHR.responseText).error;
errorMessage = errorMessage || this.defaultFailureMessage;
} catch (error) {
errorMessage = this.defaultFailureMessage;
}
// Reset transcript language back to original.
$transcriptContainer.find('.transcript-language-menu').val(languageCode);
this.renderMessage($transcriptContainer, 'failed', errorMessage);
},
deleteTranscript: function(event) {
var self = this,
$transcriptEl = $(event.target).parents('.video-transcript-content'),
languageCode = $transcriptEl.attr('data-language-code'),
transcriptDeleteUrl = self.getTranscriptDeleteUrl(
self.edxVideoID,
languageCode,
self.videoTranscriptSettings.transcript_delete_handler_url
);
ViewUtils.confirmThenRunOperation(
gettext('Are you sure you want to remove this transcript?'),
gettext('If you remove this transcript, the transcript will not be available for any components that use this video.'), // eslint-disable-line max-len
gettext('Remove'),
function() {
ViewUtils.runOperationShowingMessage(
gettext('Removing'),
function() {
return $.ajax({
url: transcriptDeleteUrl,
type: 'DELETE'
}).done(function() {
// Update transcripts.
self.transcripts = _.without(self.transcripts, languageCode);
// re-render transcripts.
self.render();
});
}
);
}
);
},
clearMessage: function() {
var $transcriptStatusesEl = this.$el.find('.transcript-upload-status-container');
// Clear all message containers
HtmlUtils.setHtml($transcriptStatusesEl, '');
$transcriptStatusesEl.removeClass('success error');
},
renderMessage: function($transcriptContainer, status, errorMessage) {
var statusData = this.transcriptUploadStatuses[status],
$transcriptStatusEl = $transcriptContainer.find('.transcript-upload-status-container');
// If a messge is already present above the video transcript element, remove it.
this.clearMessage();
HtmlUtils.setHtml(
$transcriptStatusEl,
this.transcriptUploadStatusTemplate({
status: statusData.statusClass,
iconClasses: statusData.iconClasses,
shortMessage: gettext(statusData.shortMessage),
errorMessage: errorMessage || '',
hiddenClass: statusData.hiddenClass
})
);
$transcriptStatusEl.addClass(statusData.statusClass);
},
showUploadFailureMessage: function(event) {
var errorMessage = $(event.target).data('error-message');
return new PromptView.Warning({
title: this.defaultFailureTitle,
message: errorMessage,
actions: {
primary: {
text: gettext('Close'),
click: function(prompt) {
return prompt.hide();
}
}
}
}).show();
},
/*
Renders transcripts view.
*/
render: function() {
HtmlUtils.setHtml(
this.$el,
this.template({
transcripts: this.transcripts,
error_description: this.errorDescription,
transcription_status: this.transcriptionStatus,
transcriptAvailableLanguages: this.transcriptAvailableLanguages,
edxVideoID: this.edxVideoID,
transcriptClientTitle: this.getTranscriptClientTitle(),
transcriptFileFormat: this.videoTranscriptSettings.trancript_download_file_format,
getTranscriptDownloadLink: this.getTranscriptDownloadLink,
transcriptDownloadHandlerUrl: this.videoTranscriptSettings.transcript_download_handler_url
})
);
if (this.isCollapsed) {
this.hideTranscripts();
} else {
this.showTranscripts();
}
return this;
}
});
return VideoTranscriptsView;
}
);

View File

@@ -74,7 +74,6 @@
@import 'views/users';
@import 'views/export-git';
@import 'views/group-configuration';
@import 'views/video-upload';
@import 'views/certificates';
// +Base - Contexts

View File

@@ -1,678 +0,0 @@
.view-video-uploads {
.content-primary,
.content-supplementary {
box-sizing: border-box;
}
.nav-actions {
.fa-cloud-upload {
@extend %t-copy;
vertical-align: bottom;
@include margin-right($baseline/45);
}
}
.fixed-container {
position: fixed !important;
top: 0 !important;
}
.button-link {
background: none;
border: none;
padding: 0;
color: $ui-link-color;
cursor: pointer;
}
.message-error {
color: $red;
}
.video-transcripts-wrapper {
display: block;
.button-link {
color: $ui-link-color !important;
}
}
.hidden {
display: none;
}
.video-transcript-content {
margin-top: ($baseline/2);
.transcript-upload-status-container {
.video-transcript-detail-status,
.more-details-action {
@include font-size(12);
@include line-height(12);
@include margin-left($baseline/4);
}
}
.transcript-upload-status-container.error {
color: $color-error;
}
.transcript-upload-status-container.success {
color: $color-ready;
}
.transcript-language-menu {
display: block;
width: 200px;
}
}
.course-video-settings-container {
position: absolute;
overflow: scroll;
top: 0;
right: -100%;
z-index: 1000;
width: 352px;
transition: all 0.3s ease;
background-color: $white;
-webkit-box-shadow: -3px 0 3px 0 rgba(153, 153, 153, 0.3);
-moz-box-shadow: -3px 0 3px 0 rgba(153, 153, 153, 0.3);
box-shadow: -3px 0 3px 0 rgba(153, 153, 153, 0.3);
.action-close-wrapper {
.action-close-course-video-settings {
width: 100%;
padding: ($baseline/2) ($baseline*0.8);
background-color: $gray-u1;
border: transparent;
height: ($baseline*2.4);
color: $text-dark-black-blue;
@include font-size(16);
@include text-align(left);
}
}
.course-video-settings-wrapper {
margin-top: ($baseline*1.6);
padding: ($baseline) ($baseline*0.8);
.course-video-settings-title {
color: $black-t4;
margin: ($baseline*1.6) 0 ($baseline*0.8) 0;
font-weight: font-weight(semi-bold);
@include font-size(24);
}
.course-video-settings-message {
padding: ($baseline/2);
margin-bottom: ($baseline*0.8);
max-height: ($baseline*2.4);
color: $black;
@include font-size(16);
.icon {
@include margin-right($baseline/4);
}
}
.course-video-settings-message-wrapper.success .course-video-settings-message {
background-color: $state-success-bg;
border: solid 1px $state-success-border;
}
.course-video-settings-message-wrapper.error .course-video-settings-message {
background-color: $state-danger-bg;
border: solid 1px $state-danger-border;
}
.organization-credentials-content {
margin-top: ($baseline*1.6);
.org-credentials-wrapper input {
width: 65%;
margin-top: ($baseline*0.8);
display: inline-block;
}
}
.transcript-preferance-wrapper {
margin-top: ($baseline*1.6);
.icon.fa-info-circle {
@include margin-left($baseline*0.75);
}
}
.transcript-preferance-wrapper.error .transcript-preferance-label {
color: $color-error;
}
.error-info,
.error-icon .fa-info-circle {
color: $color-error;
}
.error-info {
@include font-size(16);
@include margin-left($baseline/2);
}
.transcript-preferance-label {
@include font-size(15);
color: $black-t4;
font-weight: font-weight(semi-bold);
display: block;
}
.transcript-provider-group,
.transcript-turnaround,
.transcript-fidelity,
.video-source-language,
.selected-transcript-provider {
margin-top: ($baseline*0.8);
}
.selected-transcript-provider {
.action-change-provider {
@include margin-left($baseline/2);
}
}
.transcript-provider-group {
input[type=radio] {
margin: 0 ($baseline/2);
}
label {
font-weight: normal;
color: $black-t4;
@include font-size(15);
}
}
.transcript-turnaround-wrapper,
.transcript-fidelity-wrapper,
.video-source-language-wrapper,
.transcript-languages-wrapper {
display: none;
}
.transcript-languages-wrapper .transcript-preferance-label {
display: inline-block;
}
.transcript-languages-container .languages-container {
margin-top: ($baseline*0.8);
.transcript-language-container {
padding: ($baseline/4);
background-color: $gray-l6;
border-top: solid 1px $gray-l4;
border-bottom: solid 1px $gray-l4;
.remove-language-action {
display: inline-block;
@include float(right);
}
}
}
.transcript-language-menu-container {
margin-top: ($baseline*0.8);
.add-language-action {
display: inline-block;
.action-add-language {
@include margin-left($baseline/4);
}
.error-info {
display: inline-block;
}
}
}
.transcript-language-menu,
.video-source-language {
width: 60%;
}
}
.transcription-account-details {
margin-top: ($baseline*0.8);
span {
@include font-size(15);
}
}
.transcription-account-details.warning {
background-color: $state-warning-bg;
padding: ($baseline/2);
}
.action-cancel-course-video-settings {
@include margin-right($baseline/2);
}
.course-video-settings-footer {
margin-top: ($baseline*1.6);
.last-updated-text {
@include font-size(12);
display: block;
margin-top: ($baseline/2);
}
}
.button {
@extend %btn-primary-blue;
@extend %sizing;
.action-button-text {
display: inline-block;
vertical-align: baseline;
}
.icon {
display: inline-block;
vertical-align: baseline;
}
}
}
.file-upload-form {
@include clearfix();
width: 100%;
.file-drop-area {
border: 2px dashed $gray-l3;
border-radius: ($baseline/5);
padding: ($baseline*1.25);
background: $white;
@include text-align(center);
cursor: pointer;
&:hover,
&.is-dragged {
background: $blue-l5;
border-style: solid;
border-color: $blue-l4;
}
&:hover .upload-text-link {
text-decoration: underline;
}
.fa-cloud-upload {
font-size: 7em;
vertical-align: top;
@include margin-right(0.1em);
}
.text-container {
display: inline-block;
@include text-align(left);
.upload-text-link {
color: $ui-link-color;
}
.video-uploads-header {
font-size: 1.5em;
margin-bottom: 0.25em;
font-weight: 600;
}
.video-max-file-size-text {
margin-top: ($baseline/2);
}
}
}
}
.active-video-upload-container {
margin-bottom: ($baseline*2);
.active-video-upload-list {
@extend %cont-no-list;
.active-video-upload {
display: inline-block;
min-height: ($baseline*4);
width: (flex-grid(4) - 1.85);
margin: (flex-gutter() - 1.85);
border: 1px solid $gray-l3;
border-radius: ($baseline/5);
padding: ($baseline/2);
vertical-align: top;
.video-detail-name {
@extend %cont-truncated;
@extend %t-strong;
margin-bottom: ($baseline/2);
font-size: 90%;
}
.video-detail-status,
.more-details-action {
@include font-size(12);
@include line-height(12);
}
.more-details-action,
.upload-failure {
display: none;
}
.video-detail-progress {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
margin-bottom: ($baseline/2);
border: none;
width: 100%;
height: ($baseline/4);
}
.video-detail-progress::-webkit-progress-bar {
background-color: $white;
}
// Sadly, these rules must be separate or both Chrome and Firefox break
.video-detail-progress::-webkit-progress-value {
background-color: $color-ready;
}
.video-detail-progress::-moz-progress-bar {
background-color: $color-ready;
}
&:hover {
@include transition(all $tmg-f3);
background: $white;
}
&.queued {
.video-detail-progress {
visibility: hidden;
}
}
&.error {
.video-upload-status {
color: $color-error;
}
// Sadly, these rules must be separate or both Chrome and Firefox break
.video-detail-progress::-webkit-progress-value {
background-color: $color-error;
}
.video-detail-progress::-moz-progress-bar {
background-color: $color-error;
}
.more-details-action,
.upload-failure {
display: inline-block;
color: $color-error;
}
.more-details-action {
margin-top: ($baseline/5);
float: right;
}
}
&.success {
.video-upload-status {
color: $color-ready;
}
}
}
}
}
.button {
@extend %ui-btn-non;
}
.assets-library {
.js-table-body .video-id-col {
word-break: break-all;
}
.assets-title {
display: inline-block;
width: flex-grid(5, 9);
@include margin-right(flex-gutter());
}
.wrapper-encodings-download {
display: inline-block;
width: flex-grid(4, 9);
text-align: right;
}
.actions-list {
@extend %actions-list;
}
}
.video-table {
.video-row {
display: table;
table-layout: fixed;
width: 100%;
.video-col {
display: table-cell;
}
.name-col {
width: 23%;
}
.transcripts-col {
width: 17%;
}
.thumbnail-col,
.video-id-col {
width: 15%;
}
.date-col,
.status-col {
width: 15%;
}
.actions-col {
width: 5%;
}
.video-head-col.thumbnail-col {
width: 17% !important;
}
}
}
.thumbnail-error-wrapper {
display: table-row;
white-space: nowrap;
color: $red;
.icon {
margin: ($baseline*0.75) ($baseline/4) 0 ($baseline/2);
}
}
$thumbnail-width: ($baseline*7.5);
$thumbnail-height: ($baseline*5);
.thumbnail-wrapper {
position: relative;
max-width: $thumbnail-width;
max-height: $thumbnail-height;
img {
width: $thumbnail-width;
height: $thumbnail-height;
}
* {
cursor: pointer;
}
&.upload,
&.requirements {
border: 1px dashed $gray-l3;
}
&.requirements {
.requirements-text {
font-weight: 600;
}
.requirements-instructions {
font-size: 15px;
font-family: $font-family-sans-serif;
text-align: left;
color: $gray-d2;
line-height: 1.5;
}
.video-duration {
opacity: 0;
}
}
&.edit {
background: black;
&:hover,
&:focus,
&.focused {
img,
.video-duration {
@include transition(all 0.3s linear);
opacity: 0.1;
}
}
}
&.progress {
background: white;
img {
@include transition(all 0.5s linear);
opacity: 0.15;
}
.action-icon {
display: block;
}
}
&.upload .thumbnail-action {
color: $blue;
}
&.progress .thumbnail-action {
.action-icon {
@include font-size(20);
}
}
&.edit {
background-color: #4e4e4e;
}
&.edit .thumbnail-action .action-icon.edit {
display: none;
}
&.edit .thumbnail-action .edit-container {
background-color: $white;
padding: ($baseline/4);
border-radius: ($baseline/5);
margin-top: ($baseline/2);
display: none;
}
&.edit .action-text {
color: $white;
}
.thumbnail-action {
@include font-size(14);
}
.thumbnail-overlay > :not(.upload-image-input) {
position: absolute;
text-align: center;
top: 50%;
left: 5px;
right: 5px;
@include transform(translateY(-50%));
z-index: 1;
}
.upload-image-input {
position: absolute;
top: 0;
left: 0;
right: 0;
opacity: 0;
z-index: 6;
width: $thumbnail-width;
height: $thumbnail-height;
}
.video-duration {
position: absolute;
text-align: center;
bottom: 1px;
@include right(1px);
width: auto;
min-width: 25%;
color: white;
padding: ($baseline/10) ($baseline/5);
background-color: black;
}
&.focused {
box-shadow: 0 0 ($baseline/5) 1px $blue;
}
&.error {
box-shadow: 0 0 ($baseline/5) 1px $red;
}
}
}

View File

@@ -1,16 +0,0 @@
<form class="file-upload-form">
<div class="file-drop-area">
<span class="icon fa fa-cloud-upload" aria-hidden="true"></span>
<div class="text-container">
<div class="video-uploads-header"><%- uploadHeader %></div>
<div class="video-upload-text"><%= uploadText %></div> <% // xss-lint: disable=underscore-not-escaped %>
<div class="video-max-file-size-text"><%- maxSizeText %></div>
<div class="video-allowed-extensions-text"><%- supportedVideosText %></div>
</div>
</div>
<input type="file" class="sr js-file-input" name="file" multiple>
</form>
<section class="active-video-upload-container">
<h3 class="sr"><%- gettext("Active Uploads") %></h3>
<ul class="active-video-upload-list"></ul>
</section>

View File

@@ -1,12 +0,0 @@
<h4 class="video-detail-name"><%- fileName %></h4>
<progress class="video-detail-progress" value="<%- progress %>"></progress>
<div class="video-upload-status">
<span class="icon alert-icon fa fa-warning upload-failure" aria-hidden="true"></span>
<span class="video-detail-status"><%- gettext(status) %></span>
<% if (failureMessage) { %>
<a href="#" class="more-details-action">
<%- gettext("Read More") %>
<span class="sr"><%- gettext("details about the failure") %></span>
</a>
<% } %>
</div>

View File

@@ -1,8 +0,0 @@
<button class='button-link action-cancel-course-video-settings' aria-describedby='cancel-button-text'>
<%- gettext('Discard Changes') %>
<span id='cancel-button-text' class='sr'><%-gettext('Press discard changes to discard your changes.') %></span>
</button>
<button class='button action-update-org-credentials' aria-describedby='update-org-credentials-button-text'>
<%- gettext('Update Settings') %>
<span id='update-org-credentials-button-text' class='sr'><%-gettext('Press update settings to update the information for your organization.') %></span>
</button>

View File

@@ -1,13 +0,0 @@
<button class='button-link action-cancel-course-video-settings' aria-describedby='cancel-button-text'>
<%- gettext('Discard Changes') %>
<span id='cancel-button-text' class='sr'><%-gettext('Press discard changes to discard changes.') %></span>
</button>
<button class="button action-update-course-video-settings" aria-describedby='update-button-text'>
<%- gettext('Update Settings') %>
<span id='update-button-text' class='sr'><%-gettext('Press update settings to update course video settings') %></span>
</button>
<span class='last-updated-text'>
<%if (dateModified) { %>
<%- gettext('Last updated')%> <%- dateModified %>
<% } %>
</span>

View File

@@ -1,18 +0,0 @@
<div class='course-video-settings-container'>
<div class="course-video-settings-header">
<div class="action-close-wrapper">
<button class="action-close-course-video-settings">
<span class="icon fa fa-times" aria-hidden="true"></span>
<%-gettext('Close') %>
<span class='sr'><%-gettext('Press close to hide course video settings') %></span>
</button>
</div>
</div>
<div class='course-video-settings-wrapper'>
<div class='course-video-settings-message-wrapper'></div>
<span class="course-video-settings-title"><%- gettext('Course Video Settings') %></span>
<div class='transcript-preferance-wrapper transcript-provider-wrapper'></div>
<div class='course-video-settings-content'></div>
<div class='course-video-settings-footer'></div>
</div>
</div>

View File

@@ -1,31 +0,0 @@
<div class='course-video-transcript-preferances-wrapper'>
<div class='transcript-preferance-wrapper transcript-turnaround-wrapper'>
<label class='transcript-preferance-label' for='transcript-turnaround'><%- gettext('Transcript Turnaround') %><span class='error-icon' aria-hidden="true"></span></label>
<select id='transcript-turnaround' class='transcript-turnaround'></select>
<span class='error-info' aria-hidden="true"></span>
</div>
<div class='transcript-preferance-wrapper transcript-fidelity-wrapper'>
<label class='transcript-preferance-label' for='transcript-fidelity'><%- gettext('Transcript Fidelity') %><span class='error-icon' aria-hidden="true"></span></label>
<select id='transcript-fidelity' class='transcript-fidelity'></select>
<span class='error-info' aria-hidden="true"></span>
</div>
<div class='transcript-preferance-wrapper video-source-language-wrapper'>
<label class='transcript-preferance-label' for='video-source-language'><%- gettext('Video Source Language') %><span class='error-icon' aria-hidden="true"></span></label>
<select id='video-source-language' class='video-source-language' aria-labelledby="video-source-language-none"></select>
<span class='error-info' aria-hidden="true"></span>
</div>
<div class='transcript-preferance-wrapper transcript-languages-wrapper'>
<span class='transcript-preferance-label'><%- gettext('Transcript Languages') %></span>
<span class='error-icon' aria-hidden="true"></span>
<div class='transcript-languages-container'>
<div class='languages-container'></div>
<div class="transcript-language-menu-container">
<select class="transcript-language-menu" id="transcript-language" aria-labelledby="transcript-language-none"></select>
<div class="add-language-action">
<button class="button-link action-add-language"><%- gettext('Add') %><span class="sr"><%- gettext('Press Add to language') %></span></button>
</div>
<span class="error-info" aria-hidden="true"></span>
</div>
</div>
</div>
</div>

View File

@@ -1,7 +0,0 @@
<label class='transcript-preferance-label' for='transcript-provider'><%- gettext('Automated Transcripts') %></label>
<div class="transcript-provider-group" id="transcript-provider">
<% for (var i = 0; i < providers.length; i++) { %>
<input type='radio' id='transcript-provider-<%- providers[i].key %>' name='transcript-provider' value='<%- providers[i].value %>' <%- providers[i].checked %>>
<label for='transcript-provider-<%- providers[i].key %>'><%- providers[i].name %></label>
<% } %>
</div>

View File

@@ -1,8 +0,0 @@
<label class='transcript-preferance-label' for='transcript-provider'><%- gettext('Transcript Provider') %></label>
<div class='selected-transcript-provider'>
<span class='title'><%- selectedProvider %></span>
<button class='button-link action-change-provider' aria-describedby='change-provider-button-text'>
<%- gettext('Change') %>
<span id='change-provider-button-text' class='sr'><%-gettext('Press change to change selected transcript provider.') %></span>
</button>
</div>

View File

@@ -1,24 +0,0 @@
<div class="assets-library">
<h3 class="assets-title"><%- gettext("Previous Uploads") %></h3>
<div class="wrapper-encodings-download">
<a href="<%- encodingsDownloadUrl %>">
<%- gettext("Download available encodings (.csv)") %>
</a>
</div>
<div class="assets-table video-table">
<div class="js-table-head">
<div class="video-row">
<% if (videoImageUploadEnabled) { %>
<div class="video-head-col video-col thumbnail-col"><%- gettext("Thumbnail") %></div>
<% } %>
<div class="video-head-col video-col name-col"><%- gettext("Name") %></div>
<div class="video-head-col video-col date-col"><%- gettext("Date Added") %></div>
<div class="video-head-col video-col video-id-col"><%- gettext("Video ID") %></div>
<div class="video-head-col video-col transcripts-col"><%- gettext("Transcripts") %></div>
<div class="video-head-col video-col status-col"><%- gettext("Video Status") %></div>
<div class="video-head-col video-col actions-col"><%- gettext("Action") %></div>
</div>
</div>
<div class="js-table-body"></div>
</div>
</div>

View File

@@ -1,20 +0,0 @@
<div class="video-row-container">
<% if (videoImageUploadEnabled) { %>
<div class="video-col thumbnail-col"></div>
<% } %>
<div class="video-col name-col"><%- client_video_id %></div>
<div class="video-col date-col"><%- created %></div>
<div class="video-col video-id-col"><%- edx_video_id %></div>
<div class="video-col transcripts-col"></div>
<div class="video-col status-col"></div>
<div class="video-col actions-col">
<ul class="actions-list">
<li class="action-item action-remove">
<a href="#" data-tooltip="<%- gettext('Remove this video') %>" class="remove-video-button action-button">
<span class="icon fa fa-times-circle" aria-hidden="true"></span>
<span class="sr"><%- StringUtils.interpolate(gettext("Remove {video_name} video"), {video_name: client_video_id}) %></span>
</a>
</li>
</ul>
</div>
</div>

View File

@@ -1,5 +0,0 @@
<span class='video-status'><%- status %></span>
<% if (show_error && error_description) { %>
</br>
<span class='message-error'><%- error_description %></span>
<% }%>

View File

@@ -1,4 +0,0 @@
<div class="thumbnail-error-wrapper thumbnail-error" data-video-id="<%- videoId %>">
<span class="icon fa fa-exclamation-triangle" aria-hidden="true"></span>
<span class="action-text"><%- errorText %></span>
</div>

View File

@@ -1,28 +0,0 @@
<div class="thumbnail-wrapper <%- action === 'upload' ? 'upload' : '' %>" tabindex="-1">
<img src="<%- thumbnailURL %>" alt="<%- imageAltText %>">
<div class="thumbnail-overlay">
<input id="thumb-<%- videoId %>" class="upload-image-input" type="file" name="file" accept=".bmp, .jpg, .jpeg, .png, .gif"/>
<label for="thumb-<%- videoId %>" class="thumbnail-action">
<span class="main-icon action-icon <%- actionInfo.name %>" aria-hidden="true"><%- actionInfo.icon %></span>
<span class="action-text-sr sr"></span>
<span class="action-text"><%- actionInfo.text %></span>
<div class="edit-container">
<span class="action-icon" aria-hidden="true"><%- actionInfo.icon %></span>
<span class="edit-action-text"><%- actionInfo.actionText %></span>
</div>
</label>
<span class="requirements-text-sr sr">
<%- edx.StringUtils.interpolate(
gettext("Recommended image resolution is {imageResolution}, maximum image file size should be {maxFileSize} and format must be one of {supportedImageFormats}."),
{imageResolution: videoImageResolution, maxFileSize: videoImageMaxSize.humanize, supportedImageFormats: videoImageSupportedFileFormats.humanize}
) %>
</span>
</div>
<% if(duration) { %>
<div class="video-duration">
<span class="duration-text-human sr"><%- duration.humanize %></span>
<span class="duration-text-machine" aria-hidden="true"><%- duration.machine %></span>
</div>
<% } %>
</div>

View File

@@ -1,5 +0,0 @@
<span class="fa <%- iconClasses %>" aria-hidden="true"></span>
<span class='transcript-detail-status'><%- shortMessage %></span>
<button class='button-link more-details-action <%- hiddenClass %>' data-error-message='<%- errorMessage %>'>
<%- gettext('Read more') %>
</button>

View File

@@ -1,52 +0,0 @@
<div class='video-transcripts-header'>
<% if (transcription_status) { %>
<span class='transcripts-empty-text'><%- transcription_status %></span>
<% if (error_description) { %>
</br>
<span class='message-error'><%- error_description %></span>
<% }%>
<% } else if (!transcripts.length){ %>
<span class='transcripts-empty-text'><%- gettext('No transcript uploaded.') %></span>
<% }%>
</br>
<% if (transcripts.length) { %>
<button class="button-link toggle-show-transcripts-button">
<strong>
<span class="icon fa fa-caret-right toggle-show-transcripts-icon" aria-hidden="true"></span>
<span class="toggle-show-transcripts-button-text">
<%- StringUtils.interpolate(gettext('Show transcripts ({totalTranscripts})'), {totalTranscripts: transcripts.length})%>
</span>
</strong>
</button>
<% }%>
<div class='video-transcripts-wrapper'>
<% _.each(transcripts, function(transcriptLanguageCode){ %>
<% selectedLanguageCodes = _.keys(_.omit(transcripts, transcriptLanguageCode)); %>
<div class='video-transcript-content' data-edx-video-id="<%- edxVideoID %>" data-language-code="<%- transcriptLanguageCode %>">
<div class='transcript-upload-status-container'></div>
<strong class='transcript-title'><%- StringUtils.interpolate(gettext('{transcriptClientTitle}_{transcriptLanguageCode}.{fileExtension}'), {transcriptClientTitle: transcriptClientTitle, transcriptLanguageCode: transcriptLanguageCode, fileExtension: transcriptFileFormat}) %></strong>
<select class='transcript-language-menu'>
<option value=''>Select Language</option>
<% _.each(transcriptAvailableLanguages, function(availableLanguage){ %>
<% if (!_.contains(selectedLanguageCodes, availableLanguage.language_code)) { %>
<option value='<%- availableLanguage.language_code %>' <%- transcriptLanguageCode === availableLanguage.language_code ? 'selected': '' %>><%- availableLanguage.language_text %></option>
<% } %>
<% }) %>
</select>
<input class="upload-transcript-input hidden" type="file" name="file" accept=".<%- transcriptFileFormat %>"/>
<div class='transcript-actions'>
<a
class="button-link download-transcript-button"
href="<%- getTranscriptDownloadLink(edxVideoID, transcriptLanguageCode, transcriptDownloadHandlerUrl) %>"
>
<%- gettext('Download') %>
</a>
<span class='transcript-actions-separator'> | </span>
<button class="button-link upload-transcript-button"><%- gettext('Replace') %></button>
<span class='transcript-actions-separator'> | </span>
<button class="button-link delete-transcript-button"><%- gettext('Delete') %></button>
</div>
</div>
<% }) %>
</div>
</div>

View File

@@ -1,82 +0,0 @@
<%page expression_filter="h"/>
<%inherit file="base.html" />
<%def name="online_help_token()"><% return "video" %></%def>
<%!
import json
from django.core.serializers.json import DjangoJSONEncoder
from django.utils.translation import gettext as _
from openedx.core.djangolib.js_utils import (
dump_js_escaped_json, js_escaped_string
)
from openedx.core.djangolib.markup import HTML, Text
%>
<%block name="title">${_("Video Uploads")}</%block>
<%block name="bodyclass">is-signedin course view-video-uploads</%block>
<%namespace name='static' file='static_content.html'/>
<%block name="header_extras">
% for template_name in ["active-video-upload", "previous-video-upload-list"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="js/${template_name}.underscore" />
</script>
% endfor
</%block>
<%block name="requirejs">
require(["js/factories/videos_index"], function (VideosIndexFactory) {
"use strict";
var $contentWrapper = $(".content-primary");
VideosIndexFactory(
$contentWrapper,
"${image_upload_url | n, js_escaped_string}",
"${video_handler_url | n, js_escaped_string}",
"${encodings_download_url | n, js_escaped_string}",
"${default_video_image_url | n, js_escaped_string}",
${concurrent_upload_limit | n, dump_js_escaped_json},
$(".nav-actions .course-video-settings-button"),
$contentWrapper.data("previous-uploads"),
${video_supported_file_formats | n, dump_js_escaped_json},
${video_upload_max_file_size | n, dump_js_escaped_json},
${active_transcript_preferences | n, dump_js_escaped_json},
${transcript_credentials | n, dump_js_escaped_json},
${video_transcript_settings | n, dump_js_escaped_json},
${is_video_transcript_enabled | n, dump_js_escaped_json},
${video_image_settings | n, dump_js_escaped_json},
${transcript_available_languages | n, dump_js_escaped_json}
);
});
</%block>
<%block name="content">
<div class="wrapper-mast wrapper">
<div class="video-transcript-settings-wrapper"></div>
<header class="mast has-actions has-subtitle">
<h1 class="page-header">
<small class="subtitle">${_("Content")}</small>
<span class="sr">&gt; </span>${_("Video Uploads")}
</h1>
% if is_video_transcript_enabled :
<nav class="nav-actions" aria-label="${_('Page Actions')}">
<h3 class="sr">${_("Page Actions")}</h3>
<div class="nav-item">
<button class="button course-video-settings-button"><span class="icon fa fa-cog" aria-hidden="true"></span> ${_("Course Video Settings")}</button>
</div>
</nav>
% endif
</header>
</div>
<div class="wrapper-content wrapper">
<section class="content">
<article class="content-primary" role="main" data-previous-uploads="${json.dumps(previous_uploads, cls=DjangoJSONEncoder)}"></article>
</section>
</div>
% if pagination_context:
<%include file="videos_index_pagination.html"/>
% endif
</%block>

View File

@@ -1,40 +0,0 @@
<%page expression_filter="h"/>
<%!
from django.utils.translation import gettext as _
from openedx.core.djangolib.js_utils import dump_js_escaped_json
%>
<link href="//cdnjs.cloudflare.com/ajax/libs/simplePagination.js/1.6/simplePagination.min.css" rel="stylesheet">
<script src="//cdnjs.cloudflare.com/ajax/libs/simplePagination.js/1.6/jquery.simplePagination.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jinplace/1.2.1/jinplace.min.js"></script>
<nav style="text-align: center">
<br>
<div id="video_upload_pagination" style="display: inline-block"></div>
<br>
<span id="videos_per_page"
data-ok-button="${_('Submit')}"
data-cancel-button="${_('Cancel')}"
data-data="${pagination_context['items_on_one_page']}"
data-placeholder="${_('Changing..')}"
data-activator="#edit-activator">
${_('Videos per page:')} ${pagination_context['items_on_one_page']}
</span>
<button id="edit-activator" class="btn-default edit-button action-button">
<span class="icon fa fa-pencil" aria-hidden="true"></span>
<span class="action-button-text">${_("Change")}</span>
</button>
</nav>
<script>
$(function() {
$('#video_upload_pagination').pagination({
pages: "${pagination_context['total_pages']| n, dump_js_escaped_json}",
currentPage:"${pagination_context['current_page']| n, dump_js_escaped_json}",
cssStyle: 'light-theme',
hrefTextPrefix:"?page=",
});
$('#videos_per_page').jinplace({
}).on('jinplace:done',
function() {
window.location = window.location.pathname;
});
});
</script>

View File

@@ -46,7 +46,6 @@
certificates_url = reverse('certificates_list_handler', kwargs={'course_key_string': str(course_key)})
checklists_url = reverse('checklists_handler', kwargs={'course_key_string': str(course_key)})
pages_and_resources_mfe_enabled = ENABLE_PAGES_AND_RESOURCES_MICROFRONTEND.is_enabled(context_course.id)
video_upload_mfe_enabled = toggles.use_new_video_uploads_page(context_course.id)
schedule_details_mfe_enabled = toggles.use_new_schedule_details_page(context_course.id)
grading_mfe_enabled = toggles.use_new_grading_page(context_course.id)
course_team_mfe_enabled = toggles.use_new_course_team_page(context_course.id)
@@ -103,12 +102,7 @@
<a href="${textbooks_url}">${_("Textbooks")}</a>
</li>
% endif
% if context_course.video_pipeline_configured and not video_upload_mfe_enabled:
<li class="nav-item nav-course-courseware-videos">
<a href="${videos_url}">${_("Video Uploads")}</a>
</li>
% endif
% if context_course.video_pipeline_configured and video_upload_mfe_enabled:
% if context_course.video_pipeline_configured:
<li class="nav-item nav-course-courseware-videos">
<a href="${get_video_uploads_url(course_key)}">${_("Videos")}</a>
</li>

View File

@@ -24,19 +24,13 @@ module.exports = {
path.resolve(__dirname, '../cms/static/js/certificates/views/certificate_preview.js'),
path.resolve(__dirname, '../cms/static/js/certificates/views/signatory_details.js'),
path.resolve(__dirname, '../cms/static/js/certificates/views/signatory_editor.js'),
path.resolve(__dirname, '../cms/static/js/views/active_video_upload_list.js'),
path.resolve(__dirname, '../cms/static/js/views/assets.js'),
path.resolve(__dirname, '../cms/static/js/views/course_video_settings.js'),
path.resolve(__dirname, '../cms/static/js/views/experiment_group_edit.js'),
path.resolve(__dirname, '../cms/static/js/views/license.js'),
path.resolve(__dirname, '../cms/static/js/views/modals/move_xblock_modal.js'),
path.resolve(__dirname, '../cms/static/js/views/move_xblock_breadcrumb.js'),
path.resolve(__dirname, '../cms/static/js/views/move_xblock_list.js'),
path.resolve(__dirname, '../cms/static/js/views/paging_header.js'),
path.resolve(__dirname, '../cms/static/js/views/previous_video_upload_list.js'),
path.resolve(__dirname, '../cms/static/js/views/previous_video_upload.js'),
path.resolve(__dirname, '../cms/static/js/views/video_thumbnail.js'),
path.resolve(__dirname, '../cms/static/js/views/video_transcripts.js'),
path.resolve(__dirname, '../common/static/common/js/components/views/feedback.js'),
path.resolve(__dirname, '../common/static/common/js/components/views/paginated_view.js'),
path.resolve(__dirname, '../common/static/common/js/components/views/paging_footer.js'),