Commit ec399645 by Mushtaq Ali Committed by muzaffaryousaf

Course video settings UI - EDUCATOR-1063

parent 9009669c
......@@ -982,7 +982,9 @@ class TranscriptPreferencesTestCase(VideoUploadTestBase, CourseTestCase):
@override_settings(AWS_ACCESS_KEY_ID='test_key_id', AWS_SECRET_ACCESS_KEY='test_secret')
@patch('boto.s3.key.Key')
@patch('boto.s3.connection.S3Connection')
def test_transcript_preferences_metadata(self, transcript_preferences, mock_conn, mock_key):
@patch('contentstore.views.videos.get_transcript_preferences')
def test_transcript_preferences_metadata(self, transcript_preferences, mock_transcript_preferences,
mock_conn, mock_key):
"""
Tests that transcript preference metadata is only set if it is transcript
preferences are present in request data.
......@@ -990,8 +992,7 @@ class TranscriptPreferencesTestCase(VideoUploadTestBase, CourseTestCase):
file_name = 'test-video.mp4'
request_data = {'files': [{'file_name': file_name, 'content_type': 'video/mp4'}]}
if transcript_preferences:
request_data.update({'transcript_preferences': transcript_preferences})
mock_transcript_preferences.return_value = transcript_preferences
bucket = Mock()
mock_conn.return_value = Mock(get_bucket=Mock(return_value=bucket))
......@@ -1009,10 +1010,12 @@ class TranscriptPreferencesTestCase(VideoUploadTestBase, CourseTestCase):
# Ensure `transcript_preferences` was set up in Key correctly if sent through request.
if transcript_preferences:
mock_key_instance.set_metadata.assert_any_call('transcript_preferences', transcript_preferences)
mock_key_instance.set_metadata.assert_any_call('transcript_preferences', json.dumps(transcript_preferences))
else:
with self.assertRaises(AssertionError):
mock_key_instance.set_metadata.assert_any_call('transcript_preferences', transcript_preferences)
mock_key_instance.set_metadata.assert_any_call(
'transcript_preferences', json.dumps(transcript_preferences)
)
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_VIDEO_UPLOAD_PIPELINE": True})
......
......@@ -4,6 +4,7 @@ Views related to the video upload feature
from contextlib import closing
import csv
import json
import logging
from datetime import datetime, timedelta
from uuid import uuid4
......@@ -340,6 +341,9 @@ def transcript_preferences_handler(request, course_key_string):
data = request.json
provider = data.get('provider', '')
# TODO: if provider == '': delete course preferences
# i.e call delete api end point like delete_transcript_preferences(course_key_string)
error, preferences = validate_transcript_preferences(
provider=provider,
cielo24_fidelity=data.get('cielo24_fidelity', ''),
......@@ -567,7 +571,7 @@ def videos_index_html(course):
}
context.update({
'third_party_transcript_settings': {
'video_transcript_settings': {
'transcript_preferences_handler_url': reverse_course_url(
'transcript_preferences_handler',
unicode(course.id)
......@@ -658,9 +662,9 @@ def videos_post(course, request):
('course_key', unicode(course.id)),
]
transcript_preferences = data.get('transcript_preferences', None)
transcript_preferences = get_transcript_preferences(unicode(course.id))
if transcript_preferences is not None:
metadata_list.append(('transcript_preferences', transcript_preferences))
metadata_list.append(('transcript_preferences', json.dumps(transcript_preferences)))
for metadata_name, value in metadata_list:
key.set_metadata(metadata_name, value)
......
......@@ -64,7 +64,8 @@
contentType: 'application/json; charset=utf-8',
dataType: 'json',
data: JSON.stringify(data),
success: callback
success: callback,
global: data ? data.global : true // Trigger global AJAX error handler or not
});
};
$.postJSON = function(url, data, callback) { // eslint-disable-line no-param-reassign
......
......@@ -258,6 +258,7 @@
'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/previous_video_upload_list_spec',
'js/spec/views/assets_spec',
'js/spec/views/baseview_spec',
......
......@@ -10,19 +10,23 @@ define([
encodingsDownloadUrl,
defaultVideoImageURL,
concurrentUploadLimit,
uploadButton,
courseVideoSettingsButton,
previousUploads,
videoSupportedFileFormats,
videoUploadMaxFileSizeInGB,
activeTranscriptPreferences,
videoTranscriptSettings,
videoImageSettings
) {
var activeView = new ActiveVideoUploadListView({
postUrl: videoHandlerUrl,
concurrentUploadLimit: concurrentUploadLimit,
uploadButton: uploadButton,
courseVideoSettingsButton: courseVideoSettingsButton,
videoSupportedFileFormats: videoSupportedFileFormats,
videoUploadMaxFileSizeInGB: videoUploadMaxFileSizeInGB,
videoImageSettings: videoImageSettings,
activeTranscriptPreferences: activeTranscriptPreferences,
videoTranscriptSettings: videoTranscriptSettings,
onFileUploadDone: function(activeVideos) {
$.ajax({
url: videoHandlerUrl,
......
......@@ -32,20 +32,29 @@ define(
describe('ActiveVideoUploadListView', function() {
beforeEach(function() {
setFixtures(
'<div id="page-prompt"></div><div id="page-notification"></div><div id="reader-feedback"></div>'
'<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');
this.postUrl = POST_URL;
this.uploadButton = $('<button>');
this.courseVideoSettingsButton = $('.course-video-settings-button');
this.videoSupportedFileFormats = ['.mp4', '.mov'];
this.videoUploadMaxFileSizeInGB = 5;
this.view = new ActiveVideoUploadListView({
concurrentUploadLimit: concurrentUploadLimit,
postUrl: this.postUrl,
uploadButton: this.uploadButton,
courseVideoSettingsButton: this.courseVideoSettingsButton,
videoSupportedFileFormats: this.videoSupportedFileFormats,
videoUploadMaxFileSizeInGB: this.videoUploadMaxFileSizeInGB
videoUploadMaxFileSizeInGB: this.videoUploadMaxFileSizeInGB,
activeTranscriptPreferences: {},
videoTranscriptSettings: {
transcript_preferences_handler_url: '',
transcription_plans: {}
}
});
this.view.render();
jasmine.Ajax.install();
......@@ -55,6 +64,10 @@ define(
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() {
......@@ -71,15 +84,19 @@ define(
});
});
it('should trigger file selection when either the upload button or the drop zone is clicked', function() {
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();
this.uploadButton.click();
expect(clickSpy).toHaveBeenCalled();
});
it('shows course video settings pane when course video settings button is clicked', function() {
expect($('.course-video-settings-container')).not.toExist();
this.courseVideoSettingsButton.click();
expect($('.course-video-settings-container')).toExist();
});
it('should not show a notification message if there are no active video uploads', function() {
......
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,
verifyPreferanceErrorState,
transcriptPreferencesUrl = '/transcript_preferences/course-v1:edX+DemoX+Demo_Course',
activeTranscriptPreferences = {
provider: 'Cielo24',
cielo24_fidelity: 'PROFESSIONAL',
cielo24_turnaround: 'PRIORITY',
three_play_turnaround: '',
preferred_languages: ['fr', 'en'],
modified: '2017-08-27T12:28:17.421260Z'
},
transcriptionPlans = {
'3PlayMedia': {
languages: {
fr: 'French',
en: 'English'
},
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'
},
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'
}
};
renderCourseVideoSettingsView = function(activeTranscriptPreferencesData, transcriptionPlansData) {
courseVideoSettingsView = new CourseVideoSettingsView({
activeTranscriptPreferences: activeTranscriptPreferencesData || null,
videoTranscriptSettings: {
transcript_preferences_handler_url: transcriptPreferencesUrl,
transcription_plans: transcriptionPlansData || 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);
};
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() {
// First detroy old referance to the view.
destroyCourseVideoSettingsView();
// Create view with empty data.
renderCourseVideoSettingsView(null, null);
expect($courseVideoSettingsEl.find('.transcript-provider-group').html()).toEqual('');
expect($courseVideoSettingsEl.find('#transcript-turnaround').html()).toEqual('');
expect($courseVideoSettingsEl.find('#transcript-fidelity').html()).toEqual('');
expect($courseVideoSettingsEl.find('#transcript-language').html()).toEqual('');
});
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.
expect($courseVideoSettingsEl.find('.transcript-provider-group input:checked').val()).toEqual(
activeTranscriptPreferences.provider
);
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.selectedProvider, activeTranscriptPreferences.provider);
expect(courseVideoSettingsView.selectedTurnaroundPlan, activeTranscriptPreferences.cielo24_turnaround);
expect(courseVideoSettingsView.selectedFidelityPlan, activeTranscriptPreferences.cielo24_fidelity);
expect(courseVideoSettingsView.selectedLanguages, activeTranscriptPreferences.preferred_languages);
});
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,
global: false
})
);
// Send successful upload response.
AjaxHelpers.respondWithJson(requests, {
transcript_preferences: activeTranscriptPreferences
});
// Verify that success message is shown.
expect($courseVideoSettingsEl.find('.course-video-settings-message-wrapper.success').html()).toEqual(
'<div class="course-video-settings-message">' +
'<span class="icon fa fa-check-circle" aria-hidden="true"></span>' +
'<span>Settings updated</span>' +
'</div>'
);
});
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,
global: false
})
);
// Send error response.
AjaxHelpers.respondWithError(requests, 400, {
error: 'Error message'
});
// Verify that error message is shown.
expect($courseVideoSettingsEl.find('.course-video-settings-message-wrapper.error').html()).toEqual(
'<div class="course-video-settings-message">' +
'<span class="icon fa fa-info-circle" aria-hidden="true"></span>' +
'<span>Error message</span>' +
'</div>'
);
});
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.
$courseVideoSettingsEl.find('#transcript-turnaround').val('test-value');
$courseVideoSettingsEl.find('#transcript-fidelity').val('test-value');
$courseVideoSettingsEl.find('#transcript-language-menu').val('test-value');
verifyPreferanceErrorState($courseVideoSettingsEl.find('.transcript-turnaround-wrapper'), false);
verifyPreferanceErrorState($courseVideoSettingsEl.find('.transcript-fidelity-wrapper'), false);
verifyPreferanceErrorState($courseVideoSettingsEl.find('.transcript-languages-wrapper'), false);
});
// TODO: Add more tests like clicking on add language, remove and their scenarios and some other tests
// like N/A selected, specific provider selected tests, specific preferance selected tests etc.
});
}
);
......@@ -5,12 +5,13 @@ define([
'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,
function($, _, Backbone, ActiveVideoUpload, BaseView, ActiveVideoUploadView, CourseVideoSettingsView,
HtmlUtils, StringUtils, activeVideoUploadListTemplate) {
'use strict';
var ActiveVideoUploadListView,
......@@ -40,11 +41,13 @@ define([
this.listenTo(this.collection, 'add', this.addUpload);
this.concurrentUploadLimit = options.concurrentUploadLimit || 0;
this.postUrl = options.postUrl;
this.activeTranscriptPreferences = options.activeTranscriptPreferences;
this.videoTranscriptSettings = options.videoTranscriptSettings;
this.videoSupportedFileFormats = options.videoSupportedFileFormats;
this.videoUploadMaxFileSizeInGB = options.videoUploadMaxFileSizeInGB;
this.onFileUploadDone = options.onFileUploadDone;
if (options.uploadButton) {
options.uploadButton.click(this.chooseFile.bind(this));
if (options.courseVideoSettingsButton) {
options.courseVideoSettingsButton.click(this.showCourseVideoSettingsView.bind(this));
}
this.maxSizeText = StringUtils.interpolate(
......@@ -59,6 +62,33 @@ define([
supportedVideoTypes: this.videoSupportedFileFormats.join(', ')
}
);
this.listenTo(
Backbone,
'coursevideosettings:syncActiveTranscriptPreferences',
this.syncActiveTranscriptPreferences
);
this.listenTo(
Backbone,
'coursevideosettings:destroyCourseVideoSettingsView',
this.destroyCourseVideoSettingsView
);
},
syncActiveTranscriptPreferences: function(activeTranscriptPreferences) {
this.activeTranscriptPreferences = activeTranscriptPreferences;
},
showCourseVideoSettingsView: function(event) {
this.courseVideoSettingsView = new CourseVideoSettingsView({
activeTranscriptPreferences: this.activeTranscriptPreferences,
videoTranscriptSettings: this.videoTranscriptSettings
});
this.courseVideoSettingsView.render();
event.stopPropagation();
},
destroyCourseVideoSettingsView: function() {
this.courseVideoSettingsView = null;
},
render: function() {
......@@ -98,7 +128,6 @@ define([
$(window).on('drop', preventDefault);
$(window).on('beforeunload', this.onBeforeUnload.bind(this));
$(window).on('unload', this.onUnload.bind(this));
return this;
},
......
/**
* CourseVideoSettingsView shows a sidebar containing course wide video settings which we show on Video Uploads page.
*/
define([
'jquery', 'backbone', 'underscore', 'gettext', 'moment',
'edx-ui-toolkit/js/utils/html-utils',
'edx-ui-toolkit/js/utils/string-utils',
'text!templates/course-video-settings.underscore'
],
function($, Backbone, _, gettext, moment, HtmlUtils, StringUtils, TranscriptSettingsTemplate) {
'use strict';
var CourseVideoSettingsView,
CIELO24 = 'Cielo24',
THREE_PLAY_MEDIA = '3PlayMedia';
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',
'click .action-add-language': 'languageSelected',
'click .action-remove-language': 'languageRemoved',
'click .action-update-course-video-settings': 'updateCourseVideoSettings',
'click .action-close-course-video-settings': 'closeCourseVideoSettings'
},
initialize: function(options) {
var videoTranscriptSettings = options.videoTranscriptSettings;
this.activeTranscriptionPlan = options.activeTranscriptPreferences;
this.availableTranscriptionPlans = videoTranscriptSettings.transcription_plans;
this.transcriptHandlerUrl = videoTranscriptSettings.transcript_preferences_handler_url;
this.template = HtmlUtils.template(TranscriptSettingsTemplate);
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.availableLanguages = [];
this.activeLanguages = [];
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;
} 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;
},
getPlanLanguages: function() {
var selectedPlan = this.availableTranscriptionPlans[this.selectedProvider];
if (this.selectedProvider === CIELO24) {
return selectedPlan.fidelity[this.selectedFidelityPlan].languages;
}
return selectedPlan.languages;
},
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.
this.selectedLanguages = this.activeLanguages = [];
this.renderLanguages();
},
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;
this.renderPreferences();
},
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.
this.selectedLanguages = _.without(this.selectedLanguages, selectedLanguage);
// Populate menu again to reflect latest changes.
this.populateLanguageMenu();
},
renderProviders: function() {
var self = this,
providerPlan = self.availableTranscriptionPlans,
$providerEl = self.$el.find('.transcript-provider-group');
if (providerPlan) {
HtmlUtils.setHtml(
$providerEl,
HtmlUtils.interpolateHtml(
HtmlUtils.HTML('<input type="radio" id="transcript-provider-none" name="transcript-provider" value="" {checked}/><label for="transcript-provider-none">{text}</label>'), // eslint-disable-line max-len
{
text: gettext('N/A'),
checked: self.selectedProvider === '' ? 'checked' : ''
}
)
);
_.each(providerPlan, function(providerObject, key) {
var checked = self.selectedProvider === key ? 'checked' : '';
HtmlUtils.append(
$providerEl,
HtmlUtils.interpolateHtml(
HtmlUtils.HTML('<input type="radio" id="transcript-provider-{value}" name="transcript-provider" value="{value}" {checked}/><label for="transcript-provider-{value}">{text}'), // eslint-disable-line max-len
{
text: providerObject.display_name,
value: key,
checked: checked
}
)
);
});
}
},
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();
}
},
renderLanguages: function() {
var self = this,
$languagesPreferenceContainer = self.$el.find('.transcript-languages-wrapper'),
$languagesContainer = self.$el.find('.languages-container');
// Clear error state if present any.
this.clearPreferenceErrorState($languagesPreferenceContainer);
$languagesContainer.empty();
// Show language container if provider is 3PlayMedia, else if fidelity is selected.
if (self.selectedProvider === THREE_PLAY_MEDIA || self.selectedFidelityPlan) {
self.availableLanguages = self.getPlanLanguages();
_.each(self.activeLanguages, function(activeLanguage) {
// Only add if not in the list already.
if (_.indexOf(self.selectedLanguages, activeLanguage) === -1) {
self.selectedLanguages.push(activeLanguage);
self.addLanguage(activeLanguage);
}
});
$languagesPreferenceContainer.show();
self.populateLanguageMenu();
} else {
self.availableLanguages = {};
$languagesPreferenceContainer.hide();
}
},
populateLanguageMenu: function() {
var availableLanguages,
$languageMenuEl = this.$el.find('.transcript-language-menu'),
$languageMenuContainerEl = this.$el.find('.transcript-language-menu-container'),
selectOptionEl = new Option(gettext('Select language'), '');
// Omit out selected languages from selecting again.
availableLanguages = _.omit(this.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();
}
},
renderPreferences: function() {
this.renderProviders();
this.renderTurnaround();
this.renderFidelity();
this.renderLanguages();
},
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.availableLanguages[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>')
)
);
},
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');
// 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.selectedLanguages.length === 0) {
isValid = false;
this.addErrorState($languagesEl);
} else {
this.clearPreferenceErrorState($languagesEl);
}
return isValid;
},
saveTranscriptPreferences: function() {
var self = this,
$messageWrapperEl = self.$el.find('.course-video-settings-message-wrapper');
// First clear response status if present already
this.clearResponseStatus();
$.postJSON(this.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 : '',
preferred_languages: self.selectedLanguages,
global: false // Do not trigger global AJAX error handler
}, function(data) {
if (data.transcript_preferences) {
$messageWrapperEl.removeClass('error');
$messageWrapperEl.addClass('success');
HtmlUtils.setHtml(
$messageWrapperEl,
HtmlUtils.interpolateHtml(
HtmlUtils.HTML('<div class="course-video-settings-message"><span class="icon fa fa-check-circle" aria-hidden="true"></span><span>{text}</span></div>'), // eslint-disable-line max-len
{
text: gettext('Settings updated')
}
)
);
self.activeTranscriptionPlan = data.transcript_preferences;
// Sync ActiveUploadListView with latest active plan.
Backbone.trigger(
'coursevideosettings:syncActiveTranscriptPreferences',
self.activeTranscriptionPlan
);
}
}).fail(function(jqXHR) {
var errorMessage;
if (jqXHR.responseText) {
// Enclose inside try-catch so that if we get erroneous data, we could still show some error to user
try {
errorMessage = $.parseJSON(jqXHR.responseText).error;
} catch (e) {} // eslint-disable-line no-empty
$messageWrapperEl.removeClass('success');
$messageWrapperEl.addClass('error');
HtmlUtils.setHtml(
$messageWrapperEl,
HtmlUtils.interpolateHtml(
HtmlUtils.HTML('<div class="course-video-settings-message"><span class="icon fa fa-info-circle" aria-hidden="true"></span><span>{text}</span></div>'), // eslint-disable-line max-len
{
text: errorMessage || gettext('Error saving data')
}
)
);
}
});
},
updateCourseVideoSettings: function() {
var $messageWrapperEl = this.$el.find('.course-video-settings-message-wrapper');
if (this.validateCourseVideoSettings()) {
this.saveTranscriptPreferences();
} else {
$messageWrapperEl.empty();
}
},
render: function() {
var dateModified = this.activeTranscriptionPlan ?
moment.utc(this.activeTranscriptionPlan.modified).format('ll') : '';
HtmlUtils.setHtml(
this.$el,
this.template({
dateModified: dateModified
})
);
this.renderPreferences();
this.registerCloseClickHandler();
this.setFixedCourseVideoSettingsPane();
return this;
},
setFixedCourseVideoSettingsPane: function() {
var windowWidth = $(window).width(),
windowHeight = $(window).height(),
$courseVideoSettingsButton = $('.course-video-settings-button'),
$courseVideoSettingsContainer = this.$el.find('.course-video-settings-container'),
initialPositionTop = $courseVideoSettingsContainer.offset().top,
courseVideoSettingsButtonLeft = $courseVideoSettingsButton.offset().left,
fixedOffsetRight = windowWidth -
courseVideoSettingsButtonLeft - $courseVideoSettingsButton.width() - 25;
// set windows total height
$courseVideoSettingsContainer.css('height', windowHeight);
$courseVideoSettingsContainer.css('right', 20);
// Make sticky when scroll reaches top.
$(window).scroll(function() {
// Remove transition when we start scrolling.
// Why we do this? The settings pane come back and forth when it is switched between
// position:fixed and position:absolute, it's right and top position are then being changed wrt to their
// position layout.
$courseVideoSettingsContainer.css('transition', 'none');
if ($(window).scrollTop() >= initialPositionTop) {
$courseVideoSettingsContainer.addClass('fixed-container');
// TODO: Removes these js calculations and try to do through CSS way.
$courseVideoSettingsContainer.css('right', fixedOffsetRight);
} else {
$courseVideoSettingsContainer.removeClass('fixed-container');
$courseVideoSettingsContainer.css('right', 20);
}
});
},
closeCourseVideoSettings: function() {
// 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;
});
......@@ -83,6 +83,8 @@ $gray-d1: shade($gray, 20%) !default;
$gray-d2: shade($gray, 40%) !default;
$gray-d3: shade($gray, 60%) !default;
$gray-d4: shade($gray, 80%) !default;
$gray-u1: #ECF0F1;
// These define button styles similar to LMS
// The goal here is consistency (until we can overhaul all of this...)
......@@ -302,3 +304,5 @@ $state-warning-border: darken($state-warning-bg, 5%) !default;
$state-danger-text: $black !default;
$state-danger-bg: #f2dede !default;
$state-danger-border: darken($state-danger-bg, 5%) !default;
$text-dark-black-blue: #2C3E50;
......@@ -12,6 +12,172 @@
}
}
.fixed-container {
position: fixed !important;
top: 0 !important;
}
.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 0px 3px 0px rgba(153,153,153,0.3);
-moz-box-shadow: -3px 0px 3px 0px rgba(153,153,153,0.3);
box-shadow: -3px 0px 3px 0px rgba(153,153,153,0.3);
.button-link {
background:none;
border:none;
padding:0;
color: $ui-link-color;
cursor:pointer
}
.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.60);
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;
}
.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);
}
.transcript-preferance-label {
color: $black-t4;
@include font-size(15);
font-weight: font-weight(semi-bold);
}
.transcript-provider-group, .transcript-turnaround, .transcript-fidelity {
margin-top: ($baseline*0.8);
}
.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, .transcript-languages-wrapper {
display: none;
}
.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);
.transcript-language-menu {
width: 65%;
}
.add-language-action {
display: inline-block;
.action-add-language {
@include margin-left($baseline/4);
}
}
}
}
.course-video-settings-footer {
margin-top: ($baseline*1.6);
.last-updated-text {
@include font-size(12);
@include margin-left($baseline/4);
}
}
.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();
......
<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('Transcript Settings') %></span>
<div class='transcript-preferance-wrapper transcript-provider-wrapper'>
<label class='transcript-preferance-label' for='transcript-provider'><%- gettext('Transcript Provider') %><span class='error-icon' aria-hidden="true"></span></label>
<div class='transcript-provider-group' id='transcript-provider'></div>
<span class='error-info' aria-hidden="true"></span>
</div>
<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 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>
<span class="error-info" aria-hidden="true"></span>
</div>
</div>
</div>
</div>
<div class='course-video-settings-footer'>
<button class="button 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>
<%if (dateModified) { %>
<span class='last-updated-text'><%- gettext('Last updated')%> <%- dateModified %></span>
<% } %>
</div>
</div>
</div>
......@@ -34,10 +34,12 @@
"${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 .upload-button"),
$(".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},
${video_transcript_settings | n, dump_js_escaped_json},
${video_image_settings | n, dump_js_escaped_json}
);
});
......@@ -46,6 +48,7 @@
<%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>
......@@ -54,11 +57,9 @@
<nav class="nav-actions" aria-label="${_('Page Actions')}">
<h3 class="sr">${_("Page Actions")}</h3>
<ul>
<li class="nav-item">
<a href="#" class="button upload-button new-button"><span class="icon fa fa-plus" aria-hidden="true"></span> ${_("Upload New File")}</a>
</li>
</ul>
<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>
</header>
</div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment