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:
committed by
Kyle McCormick
parent
0a9f7898c6
commit
7c9f468d56
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -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/);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
@@ -74,7 +74,6 @@
|
||||
@import 'views/users';
|
||||
@import 'views/export-git';
|
||||
@import 'views/group-configuration';
|
||||
@import 'views/video-upload';
|
||||
@import 'views/certificates';
|
||||
|
||||
// +Base - Contexts
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1,5 +0,0 @@
|
||||
<span class='video-status'><%- status %></span>
|
||||
<% if (show_error && error_description) { %>
|
||||
</br>
|
||||
<span class='message-error'><%- error_description %></span>
|
||||
<% }%>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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">> </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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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'),
|
||||
|
||||
Reference in New Issue
Block a user