Commit 2612fc2b by Vik Paruchuri

Merge remote-tracking branch 'origin/master' into feature/vik/oe-ui

parents f929b493 0f9d7230
......@@ -617,8 +617,26 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
}
module_store = modulestore('direct')
draft_store = modulestore('draft')
import_from_xml(module_store, 'common/test/data/', ['toy'])
source_course_id = 'edX/toy/2012_Fall'
dest_course_id = 'MITx/999/2013_Spring'
source_location = CourseDescriptor.id_to_location(source_course_id)
dest_location = CourseDescriptor.id_to_location(dest_course_id)
# get a vertical (and components in it) to put into 'draft'
# this is to assert that draft content is also cloned over
vertical = module_store.get_instance(source_course_id, Location([
source_location.tag, source_location.org, source_location.course, 'vertical', 'vertical_test', None]), depth=1)
draft_store.convert_to_draft(vertical.location)
for child in vertical.get_children():
draft_store.convert_to_draft(child.location)
items = module_store.get_items(Location([source_location.tag, source_location.org, source_location.course, None, None, 'draft']))
self.assertGreater(len(items), 0)
resp = self.client.post(reverse('create_new_course'), course_data)
self.assertEqual(resp.status_code, 200)
data = parse_json(resp)
......@@ -626,22 +644,54 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
content_store = contentstore()
source_location = CourseDescriptor.id_to_location('edX/toy/2012_Fall')
dest_location = CourseDescriptor.id_to_location('MITx/999/2013_Spring')
clone_course(module_store, content_store, source_location, dest_location)
# first assert that all draft content got cloned as well
items = module_store.get_items(Location([source_location.tag, source_location.org, source_location.course, None, None, 'draft']))
self.assertGreater(len(items), 0)
clone_items = module_store.get_items(Location([dest_location.tag, dest_location.org, dest_location.course, None, None, 'draft']))
self.assertGreater(len(clone_items), 0)
self.assertEqual(len(items), len(clone_items))
# now loop through all the units in the course and verify that the clone can render them, which
# means the objects are at least present
items = module_store.get_items(Location(['i4x', 'edX', 'toy', 'poll_question', None]))
items = module_store.get_items(Location([source_location.tag, source_location.org, source_location.course, None, None]))
self.assertGreater(len(items), 0)
clone_items = module_store.get_items(Location(['i4x', 'MITx', '999', 'poll_question', None]))
clone_items = module_store.get_items(Location([dest_location.tag, dest_location.org, dest_location.course, None, None]))
self.assertGreater(len(clone_items), 0)
for descriptor in items:
new_loc = descriptor.location.replace(org='MITx', course='999')
source_item = module_store.get_instance(source_course_id, descriptor.location)
if descriptor.location.category == 'course':
new_loc = descriptor.location.replace(org=dest_location.org, course=dest_location.course, name='2013_Spring')
else:
new_loc = descriptor.location.replace(org=dest_location.org, course=dest_location.course)
print "Checking {0} should now also be at {1}".format(descriptor.location.url(), new_loc.url())
resp = self.client.get(reverse('edit_unit', kwargs={'location': new_loc.url()}))
self.assertEqual(resp.status_code, 200)
lookup_item = module_store.get_item(new_loc)
# we want to assert equality between the objects, but we know the locations
# differ, so just make them equal for testing purposes
source_item.location = new_loc
if hasattr(source_item, 'data') and hasattr(lookup_item, 'data'):
self.assertEqual(source_item.data, lookup_item.data)
# also make sure that metadata was cloned over and filtered with own_metadata, i.e. inherited
# values were not explicitly set
self.assertEqual(own_metadata(source_item), own_metadata(lookup_item))
# check that the children are as expected
self.assertEqual(source_item.has_children, lookup_item.has_children)
if source_item.has_children:
expected_children = []
for child_loc_url in source_item.children:
child_loc = Location(child_loc_url)
child_loc = child_loc._replace(
tag=dest_location.tag,
org=dest_location.org,
course=dest_location.course
)
expected_children.append(child_loc.url())
self.assertEqual(expected_children, lookup_item.children)
def test_illegal_draft_crud_ops(self):
draft_store = modulestore('draft')
......
......@@ -3,6 +3,7 @@ import json
import os
import tarfile
import shutil
import cgi
from tempfile import mkdtemp
from path import path
......@@ -27,7 +28,7 @@ from xmodule.modulestore import Location
from xmodule.contentstore.content import StaticContent
from xmodule.util.date_utils import get_default_time_display
from xmodule.modulestore import InvalidLocationError
from xmodule.exceptions import NotFoundError
from xmodule.exceptions import NotFoundError, SerializationError
from .access import get_location_and_verify_access
from util.json_request import JsonResponse
......@@ -336,16 +337,59 @@ def generate_export_course(request, org, course, name):
the course
"""
location = get_location_and_verify_access(request, org, course, name)
course_module = modulestore().get_instance(location.course_id, location)
loc = Location(location)
export_file = NamedTemporaryFile(prefix=name + '.', suffix=".tar.gz")
root_dir = path(mkdtemp())
# export out to a tempdir
logging.debug('root = {0}'.format(root_dir))
try:
export_to_xml(modulestore('direct'), contentstore(), loc, root_dir, name, modulestore())
except SerializationError, e:
unit = None
failed_item = None
parent = None
try:
failed_item = modulestore().get_instance(course_module.location.course_id, e.location)
parent_locs = modulestore().get_parent_locations(failed_item.location, course_module.location.course_id)
if len(parent_locs) > 0:
parent = modulestore().get_item(parent_locs[0])
if parent.location.category == 'vertical':
unit = parent
except:
# if we have a nested exception, then we'll show the more generic error message
pass
export_to_xml(modulestore('direct'), contentstore(), loc, root_dir, name, modulestore())
return render_to_response('export.html', {
'context_course': course_module,
'successful_import_redirect_url': '',
'in_err': True,
'raw_err_msg': str(e),
'failed_module': failed_item,
'unit': unit,
'edit_unit_url': reverse('edit_unit', kwargs={
'location': parent.location
}) if parent else '',
'course_home_url': reverse('course_index', kwargs={
'org': org,
'course': course,
'name': name
})
})
except Exception, e:
return render_to_response('export.html', {
'context_course': course_module,
'successful_import_redirect_url': '',
'in_err': True,
'unit': None,
'raw_err_msg': str(e),
'course_home_url': reverse('course_index', kwargs={
'org': org,
'course': course,
'name': name
})
})
logging.debug('tar file being generated at {0}'.format(export_file.name))
tar_file = tarfile.open(name=export_file.name, mode='w:gz')
......
......@@ -6,6 +6,62 @@
<%block name="title">${_("Course Export")}</%block>
<%block name="bodyclass">is-signedin course tools export</%block>
<%block name="jsextra">
% if in_err:
<script type='text/javascript'>
$(document).ready(function() {
%if unit:
var dialog = new CMS.Views.Prompt({
title: gettext('There has been an error while exporting.'),
message: gettext("There has been a failure to export to XML at least one component. It is recommended that you go to the edit page and repair the error before attempting another export. Please check that all components on the page are valid and do not display any error messages."),
intent: "error",
actions: {
primary: {
text: gettext('Correct failed component'),
click: function(view) {
view.hide();
document.location = "${edit_unit_url}"
}
},
secondary: {
text: gettext('Return to Export'),
click: function(view) {
view.hide();
}
}
}
});
% else:
var msg = "<p>" + gettext("There has been a failure to export your course to XML. Unfortunately, we do not have specific enough information to assist you in identifying the failed component. It is recommended that you inspect your courseware to identify any components in error and try again.") + "</p><p>" + gettext("The raw error message is:") + "</p>";
msg = msg + "${raw_err_msg}";
var dialog = new CMS.Views.Prompt({
title: gettext('There has been an error with your export.'),
message: msg,
intent: "error",
actions: {
primary: {
text: gettext('Yes, take me to the main course page'),
click: function(view) {
view.hide();
document.location = "${course_home_url}"
}
},
secondary: {
text: gettext('Cancel'),
click: function(view) {
view.hide();
}
}
}
});
%endif
dialog.show();
})
</script>
%endif
</%block>
<%block name="content">
<div class="wrapper-mast wrapper">
<header class="mast has-subtitle">
......@@ -18,6 +74,7 @@
<div class="main-wrapper">
<div class="inner-wrapper">
<article class="export-overview">
<div class="description">
<h2>${_("About Exporting Courses")}</h2>
......
......@@ -133,7 +133,6 @@ div.videoalpha {
line-height: 46px;
padding: 0 lh(.75);
text-indent: -9999px;
@include transition(background-color 0.75s linear 0s, opacity 0.75s linear 0s);
width: 14px;
background: url('../images/vcr.png') 15px 15px no-repeat;
outline: 0;
......@@ -150,7 +149,7 @@ div.videoalpha {
&.play {
background-position: 17px -114px;
&:hover {
&:hover, &:focus {
background-color: #444;
}
}
......@@ -158,7 +157,7 @@ div.videoalpha {
&.pause {
background-position: 16px -50px;
&:hover {
&:hover, &:focus {
background-color: #444;
}
}
......@@ -300,12 +299,15 @@ div.videoalpha {
&.muted {
&>a {
background: url('../images/mute.png') 10px center no-repeat;
background-image: url('../images/mute.png');
}
}
> a {
background: url('../images/volume.png') 10px center no-repeat;
background-image: url('../images/volume.png');
background-position: 10px center;
background-repeat: no-repeat;
border-right: 1px solid #000;
box-shadow: 1px 0 0 #555, inset 1px 0 0 #555;
@include clearfix();
......@@ -382,7 +384,7 @@ div.videoalpha {
@include transition(none);
width: 30px;
&:hover {
&:hover, &:active, &:focus {
background-color: #444;
color: #fff;
text-decoration: none;
......@@ -403,7 +405,7 @@ div.videoalpha {
@include transition(none);
width: 30px;
&:hover {
&:hover, &:focus {
background-color: #444;
color: #fff;
text-decoration: none;
......@@ -419,7 +421,6 @@ div.videoalpha {
a.hide-subtitles {
background: url('../images/cc.png') center no-repeat;
display: block;
float: left;
font-weight: 800;
line-height: 46px; //height of play pause buttons
......@@ -432,7 +433,7 @@ div.videoalpha {
-webkit-font-smoothing: antialiased;
width: 30px;
&:hover {
&:hover, &:focus {
background-color: #444;
color: #fff;
text-decoration: none;
......@@ -442,9 +443,7 @@ div.videoalpha {
opacity: 0.7;
}
background-color: #444;
color: #fff;
text-decoration: none;
color: #797979;
}
}
}
......@@ -513,12 +512,6 @@ div.videoalpha {
z-index: 1;
}
article.video-wrapper section.video-controls div.secondary-controls a.hide-subtitles {
background-color: inherit;
color: #797979;
text-decoration: inherit;
}
article.video-wrapper div.video-player-pre, article.video-wrapper div.video-player-post {
height: 0px;
}
......
......@@ -13,6 +13,7 @@ class ProcessingError(Exception):
'''
pass
class InvalidVersionError(Exception):
"""
Tried to save an item with a location that a store cannot support (e.g., draft version
......@@ -21,3 +22,12 @@ class InvalidVersionError(Exception):
def __init__(self, location):
super(InvalidVersionError, self).__init__()
self.location = location
class SerializationError(Exception):
"""
Thrown when a module cannot be exported to XML
"""
def __init__(self, location, msg):
super(SerializationError, self).__init__(msg)
self.location = location
......@@ -2,8 +2,8 @@
<div id="video_example">
<div id="example">
<div id="video_id" class="video"
data-youtube-id-0-75="slowerSpeedYoutubeId"
data-youtube-id-1-0="normalSpeedYoutubeId"
data-youtube-id-0-75="7tqY6eQzVhE"
data-youtube-id-1-0="cogebirgzzM"
data-show-captions="true"
data-start=""
data-end=""
......
......@@ -4,7 +4,7 @@
<div
id="video_id"
class="videoalpha"
data-streams="0.75:slowerSpeedYoutubeId,1.0:normalSpeedYoutubeId"
data-streams="0.75:7tqY6eQzVhE,1.0:cogebirgzzM"
data-show-captions="true"
data-start=""
data-end=""
......
......@@ -8,7 +8,7 @@
data-start=""
data-end=""
data-caption-asset-path="/static/subs/"
data-sub="test_name_of_the_subtitles"
data-sub="Z5KLxerq05Y"
data-mp4-source="test_files/test.mp4"
data-webm-source="test_files/test.webm"
data-ogg-source="test_files/test.ogv"
......
......@@ -8,7 +8,7 @@
data-start=""
data-end=""
data-caption-asset-path="/static/subs/"
data-sub="test_name_of_the_subtitles"
data-sub="Z5KLxerq05Y"
data-mp4-source="test_files/test.mp4"
data-webm-source="test_files/test.webm"
data-ogg-source="test_files/test.ogv"
......
......@@ -4,7 +4,7 @@
<div
id="video_id"
class="videoalpha"
data-streams="0.75:slowerSpeedYoutubeId,1.0:normalSpeedYoutubeId"
data-streams="0.75:7tqY6eQzVhE,1.0:cogebirgzzM"
data-show-captions="false"
data-start=""
data-end=""
......
......@@ -12,6 +12,9 @@ window.STATUS = window.YT.PlayerState
oldAjaxWithPrefix = window.jQuery.ajaxWithPrefix
window.onTouchBasedDevice = ->
navigator.userAgent.match /iPhone|iPod|iPad/i
jasmine.stubbedCaption =
end: [3120, 6270, 8490, 21620, 24920, 25750, 27900, 34380, 35550, 40250]
start: [1180, 3120, 6270, 14910, 21620, 24920, 25750, 27900, 34380, 35550]
......@@ -36,7 +39,7 @@ jasmine.stubbedCaption =
#
# We will replace it with a function that does:
#
# 1.) Return a hard coded captions object if the file name contains 'test_name_of_the_subtitles'.
# 1.) Return a hard coded captions object if the file name contains 'Z5KLxerq05Y'.
# 2.) Behaves the same a as the origianl in all other cases.
window.jQuery.ajaxWithPrefix = (url, settings) ->
......@@ -46,7 +49,7 @@ window.jQuery.ajaxWithPrefix = (url, settings) ->
success = settings.success
data = settings.data
if url.match(/test_name_of_the_subtitles/g) isnt null or url.match(/slowerSpeedYoutubeId/g) isnt null or url.match(/normalSpeedYoutubeId/g) isnt null
if url.match(/Z5KLxerq05Y/g) isnt null or url.match(/7tqY6eQzVhE/g) isnt null or url.match(/cogebirgzzM/g) isnt null
if window.jQuery.isFunction(success) is true
success jasmine.stubbedCaption
else if window.jQuery.isFunction(data) is true
......@@ -60,11 +63,11 @@ window.WAIT_TIMEOUT = 1000
jasmine.getFixtures().fixturesPath = 'xmodule/js/fixtures'
jasmine.stubbedMetadata =
slowerSpeedYoutubeId:
id: 'slowerSpeedYoutubeId'
'7tqY6eQzVhE':
id: '7tqY6eQzVhE'
duration: 300
normalSpeedYoutubeId:
id: 'normalSpeedYoutubeId'
'cogebirgzzM':
id: 'cogebirgzzM'
duration: 200
bogus:
duration: 100
......@@ -117,7 +120,7 @@ jasmine.stubVideoPlayer = (context, enableParts, createPlayer=true) ->
loadFixtures 'video.html'
jasmine.stubRequests()
YT.Player = undefined
videosDefinition = '0.75:slowerSpeedYoutubeId,1.0:normalSpeedYoutubeId'
videosDefinition = '0.75:7tqY6eQzVhE,1.0:cogebirgzzM'
context.video = new Video '#example', videosDefinition
jasmine.stubYoutubePlayer()
if createPlayer
......@@ -135,7 +138,7 @@ jasmine.stubVideoPlayerAlpha = (context, enableParts, html5=false) ->
YT.Player = undefined
window.OldVideoPlayerAlpha = undefined
jasmine.stubYoutubePlayer()
return new VideoAlpha '#example', '.75:slowerSpeedYoutubeId,1.0:normalSpeedYoutubeId'
return new VideoAlpha '#example', '.75:7tqY6eQzVhE,1.0:cogebirgzzM'
# Stub jQuery.cookie
......
......@@ -19,7 +19,7 @@ describe 'VideoCaption', ->
@caption = @player.caption
it 'set the youtube id', ->
expect(@caption.youtubeId).toEqual 'normalSpeedYoutubeId'
expect(@caption.youtubeId).toEqual 'cogebirgzzM'
it 'create the caption element', ->
expect($('.video')).toContain 'ol.subtitles'
......
......@@ -35,7 +35,7 @@ describe 'VideoPlayer', ->
expect(window.VideoCaption.prototype.initialize).toHaveBeenCalled()
expect(@player.caption).toBeDefined()
expect(@player.caption.el).toBe @player.el
expect(@player.caption.youtubeId).toEqual 'normalSpeedYoutubeId'
expect(@player.caption.youtubeId).toEqual 'cogebirgzzM'
expect(@player.caption.currentSpeed).toEqual '1.0'
expect(@player.caption.captionAssetPath).toEqual '/static/subs/'
......@@ -60,7 +60,7 @@ describe 'VideoPlayer', ->
showinfo: 0
enablejsapi: 1
modestbranding: 1
videoId: 'normalSpeedYoutubeId'
videoId: 'cogebirgzzM'
events:
onReady: @player.onReady
onStateChange: @player.onStateChange
......@@ -290,7 +290,7 @@ describe 'VideoPlayer', ->
@player.onSpeedChange {}, '0.75'
it 'load the video', ->
expect(@player.player.loadVideoById).toHaveBeenCalledWith 'slowerSpeedYoutubeId', '80.000'
expect(@player.player.loadVideoById).toHaveBeenCalledWith '7tqY6eQzVhE', '80.000'
it 'trigger updatePlayTime event', ->
expect(@player.updatePlayTime).toHaveBeenCalledWith '80.000'
......@@ -301,7 +301,7 @@ describe 'VideoPlayer', ->
@player.onSpeedChange {}, '0.75'
it 'cue the video', ->
expect(@player.player.cueVideoById).toHaveBeenCalledWith 'slowerSpeedYoutubeId', '80.000'
expect(@player.player.cueVideoById).toHaveBeenCalledWith '7tqY6eQzVhE', '80.000'
it 'trigger updatePlayTime event', ->
expect(@player.updatePlayTime).toHaveBeenCalledWith '80.000'
......
......@@ -5,14 +5,14 @@ describe 'Video', ->
loadFixtures 'video.html'
jasmine.stubRequests()
@slowerSpeedYoutubeId = 'slowerSpeedYoutubeId'
@normalSpeedYoutubeId = 'normalSpeedYoutubeId'
@['7tqY6eQzVhE'] = '7tqY6eQzVhE'
@['cogebirgzzM'] = 'cogebirgzzM'
metadata =
slowerSpeedYoutubeId:
id: @slowerSpeedYoutubeId
'7tqY6eQzVhE':
id: @['7tqY6eQzVhE']
duration: 300
normalSpeedYoutubeId:
id: @normalSpeedYoutubeId
'cogebirgzzM':
id: @['cogebirgzzM']
duration: 200
afterEach ->
......@@ -38,8 +38,8 @@ describe 'Video', ->
it 'parse the videos', ->
expect(@video.videos).toEqual
'0.75': @slowerSpeedYoutubeId
'1.0': @normalSpeedYoutubeId
'0.75': @['7tqY6eQzVhE']
'1.0': @['cogebirgzzM']
it 'fetch the video metadata', ->
expect(@video.fetchMetadata).toHaveBeenCalled
......@@ -102,12 +102,12 @@ describe 'Video', ->
describe 'with speed', ->
it 'return the video id for given speed', ->
expect(@video.youtubeId('0.75')).toEqual @slowerSpeedYoutubeId
expect(@video.youtubeId('1.0')).toEqual @normalSpeedYoutubeId
expect(@video.youtubeId('0.75')).toEqual @['7tqY6eQzVhE']
expect(@video.youtubeId('1.0')).toEqual @['cogebirgzzM']
describe 'without speed', ->
it 'return the video id for current speed', ->
expect(@video.youtubeId()).toEqual @normalSpeedYoutubeId
expect(@video.youtubeId()).toEqual @cogebirgzzM
describe 'setSpeed', ->
beforeEach ->
......@@ -148,6 +148,6 @@ describe 'Video', ->
it 'call the logger with valid parameters', ->
expect(Logger.log).toHaveBeenCalledWith 'someEvent',
id: 'id'
code: @normalSpeedYoutubeId
code: @cogebirgzzM
currentTime: 25
speed: '1.0'
......@@ -6,9 +6,9 @@
jasmine.stubRequests();
oldOTBD = window.onTouchBasedDevice;
window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice').andReturn(false);
this.videosDefinition = '0.75:slowerSpeedYoutubeId,1.0:normalSpeedYoutubeId';
this.slowerSpeedYoutubeId = 'slowerSpeedYoutubeId';
this.normalSpeedYoutubeId = 'normalSpeedYoutubeId';
this.videosDefinition = '0.75:7tqY6eQzVhE,1.0:cogebirgzzM';
this['7tqY6eQzVhE'] = '7tqY6eQzVhE';
this['cogebirgzzM'] = 'cogebirgzzM';
});
afterEach(function () {
......@@ -45,8 +45,8 @@
it('parse the videos', function () {
expect(this.state.videos).toEqual({
'0.75': this.slowerSpeedYoutubeId,
'1.0': this.normalSpeedYoutubeId
'0.75': this['7tqY6eQzVhE'],
'1.0': this['cogebirgzzM']
});
});
......@@ -91,7 +91,7 @@
});
it('parse the videos if subtitles exist', function () {
var sub = 'test_name_of_the_subtitles';
var sub = 'Z5KLxerq05Y';
expect(state.videos).toEqual({
'0.75': sub,
......@@ -165,14 +165,14 @@
describe('with speed', function () {
it('return the video id for given speed', function () {
expect(state.youtubeId('0.75')).toEqual(this.slowerSpeedYoutubeId);
expect(state.youtubeId('1.0')).toEqual(this.normalSpeedYoutubeId);
expect(state.youtubeId('0.75')).toEqual(this['7tqY6eQzVhE']);
expect(state.youtubeId('1.0')).toEqual(this['cogebirgzzM']);
});
});
describe('without speed', function () {
it('return the video id for current speed', function () {
expect(state.youtubeId()).toEqual(this.normalSpeedYoutubeId);
expect(state.youtubeId()).toEqual(this.cogebirgzzM);
});
});
});
......
Jasmine JavaScript tests status
-------------------------------
As of 22.07.2013, all the tests in this directory pass. To disable each of them, change the top level "describe(" to "xdescribe(".
As of 22.07.2013, all the tests in this directory pass. To enable a test file, change
the top level "xdescribe(" to "describe(".
PS: When you are running the tests in chrome locally, make sure that chrome is started
with the option "--allow-file-access-from-files".
......@@ -130,7 +130,6 @@
describe('mouse movement', function() {
beforeEach(function() {
//initialize();
window.setTimeout.andReturn(100);
spyOn(window, 'clearTimeout');
});
......@@ -221,10 +220,6 @@
});
describe('search', function() {
beforeEach(function() {
//initialize();
});
it('return a correct caption index', function() {
expect(videoCaption.search(0)).toEqual(0);
expect(videoCaption.search(3120)).toEqual(1);
......@@ -277,7 +272,6 @@
describe('pause', function() {
beforeEach(function() {
//initialize();
videoCaption.playing = true;
videoCaption.pause();
});
......@@ -288,10 +282,6 @@
});
describe('updatePlayTime', function() {
/*beforeEach(function() {
initialize();
});*/
describe('when the video speed is 1.0x', function() {
beforeEach(function() {
videoSpeedControl.currentSpeed = '1.0';
......@@ -369,16 +359,21 @@
});
it('when CC button is disabled ', function() {
var realHeight = parseInt($('.subtitles').css('maxHeight'), 10),
videoWrapperHeight = $('.video-wrapper').height(),
controlsHeight = videoControl.el.height(),
progressSliderHeight = videoControl.sliderEl.height(),
shouldBeHeight = videoWrapperHeight - controlsHeight \
- 0.5 * controlsHeight;
var realHeight, videoWrapperHeight, progressSliderHeight,
controlHeight, shouldBeHeight;
state.captionsHidden = true;
videoCaption.setSubtitlesHeight();
expect(realHeight).toBeCloseTo($('.video-wrapper').height(shouldBeHeight, 2));
realHeight = parseInt($('.subtitles').css('maxHeight'), 10);
videoWrapperHeight = $('.video-wrapper').height();
progressSliderHeight = videoControl.sliderEl.height();
controlHeight = videoControl.el.height();
shouldBeHeight = videoWrapperHeight -
0.5 * progressSliderHeight -
controlHeight;
expect(realHeight).toBe(shouldBeHeight);
});
});
......@@ -434,17 +429,6 @@
});
it('scroll to current caption', function() {
// Check for calledWith(parameters) for some reason fails...
//
// var offset = -0.5 * ($('.video-wrapper').height() - $('.subtitles .current:first').height());
//
// expect($.fn.scrollTo).toHaveBeenCalledWith(
// $('.subtitles .current:first', videoCaption.el),
// {
// offset: offset
// }
// );
expect($.fn.scrollTo).toHaveBeenCalled();
});
});
......@@ -454,7 +438,6 @@
describe('seekPlayer', function() {
describe('when the video speed is 1.0x', function() {
beforeEach(function() {
//initialize();
videoSpeedControl.currentSpeed = '1.0';
$('.subtitles li[data-start="14910"]').trigger('click');
});
......
......@@ -34,12 +34,6 @@
});
describe('constructor', function() {
beforeEach(function() {
$.fn.qtip.andCallFake(function() {
$(this).data('qtip', true);
});
});
describe('always', function() {
beforeEach(function() {
initialize();
......@@ -60,7 +54,7 @@
it('create video caption', function() {
expect(videoCaption).toBeDefined();
expect(state.youtubeId()).toEqual('test_name_of_the_subtitles');
expect(state.youtubeId()).toEqual('Z5KLxerq05Y');
expect(state.speed).toEqual('1.0');
expect(state.config.caption_asset_path).toEqual('/static/subs/');
});
......@@ -80,38 +74,6 @@
// All the toHandleWith() expect tests are not necessary for this version of Video Alpha.
// jQuery event system is not used to trigger and invoke methods. This is an artifact from
// previous version of Video Alpha.
//
// xit('bind to video control play event', function() {
// expect($(videoControl)).toHandleWith('play', player.play);
// });
//
// xit('bind to video control pause event', function() {
// expect($(videoControl)).toHandleWith('pause', player.pause);
// });
//
// xit('bind to video caption seek event', function() {
// expect($(videoCaption)).toHandleWith('caption_seek', player.onSeek);
// });
//
// xit('bind to video speed control speedChange event', function() {
// expect($(videoSpeedControl)).toHandleWith('speedChange', player.onSpeedChange);
// });
//
// xit('bind to video progress slider seek event', function() {
// expect($(videoProgressSlider)).toHandleWith('slide_seek', player.onSeek);
// });
//
// xit('bind to video volume control volumeChange event', function() {
// expect($(videoVolumeControl)).toHandleWith('volumeChange', player.onVolumeChange);
// });
//
// xit('bind to key press', function() {
// expect($(document.documentElement)).toHandleWith('keyup', player.bindExitFullScreen);
// });
//
// xit('bind to fullscreen switching button', function() {
// expect($('.add-fullscreen')).toHandleWith('click', player.toggleFullScreen);
// });
});
it('create Youtube player', function() {
......@@ -136,7 +98,7 @@
modestbranding: 1,
html5: 1
},
videoId: 'normalSpeedYoutubeId',
videoId: 'cogebirgzzM',
events: {
onReady: videoPlayer.onReady,
onStateChange: videoPlayer.onStateChange,
......@@ -149,20 +111,6 @@
// We can't test the invocation of HTML5Video because it is not available
// globally. It is defined within the scope of Require JS.
//
// xit('create HTML5 player', function() {
// spyOn(state.HTML5Video, 'Player').andCallThrough();
// initialize();
//
// expect(window.HTML5Video.Player).toHaveBeenCalledWith(this.video.el, {
// playerVars: playerVars,
// videoSources: this.video.html5Sources,
// events: {
// onReady: player.onReady,
// onStateChange: player.onStateChange
// }
// });
// });
describe('when not on a touch based device', function() {
beforeEach(function() {
......@@ -170,10 +118,6 @@
initialize();
});
it('does not add the tooltip to fullscreen button', function() {
expect($('.add-fullscreen')).not.toHaveData('qtip');
});
it('create video volume control', function() {
expect(videoVolumeControl).toBeDefined();
expect(videoVolumeControl.el).toHaveClass('volume');
......@@ -187,10 +131,6 @@
initialize();
});
it('add the tooltip to fullscreen button', function() {
expect($('.add-fullscreen')).toHaveData('qtip');
});
it('controls are in paused state', function() {
expect(videoControl.isPlaying).toBe(false);
});
......@@ -433,11 +373,9 @@
expect(state.setSpeed).toHaveBeenCalledWith('0.75', false);
});
// Not relevant any more.
// Not relevant any more:
//
// it('tell video caption that the speed has changed', function() {
// expect(this.player.caption.currentSpeed).toEqual('0.75');
// });
// expect( "tell video caption that the speed has changed" ) ...
});
describe('when the video is playing', function() {
......@@ -548,8 +486,9 @@
expect(true).toBe(false);
}
// The below test has been replaced by above trickery.
// expect($('.vidtime')).toHaveHtml('1:00 / 1:01');
// The below test has been replaced by above trickery:
//
// expect($('.vidtime')).toHaveHtml('1:00 / 1:01');
});
});
......
......@@ -39,21 +39,6 @@
it('build the seek handle', function() {
expect(videoProgressSlider.handle).toBe('.slider .ui-slider-handle');
expect($.fn.qtip).toHaveBeenCalledWith({
content: "0:00",
position: {
my: 'bottom center',
at: 'top center',
container: videoProgressSlider.handle
},
hide: {
delay: 700
},
style: {
classes: 'ui-tooltip-slider',
widget: true
}
});
});
});
......@@ -69,7 +54,6 @@
// We can't expect $.fn.slider not to have been called,
// because sliders are used in other parts of VideoAlpha.
// expect($.fn.slider).not.toHaveBeenCalled();
});
});
});
......@@ -94,43 +78,6 @@
});
// Currently, the slider is not rebuilt if it does not exist.
//
// describe('when the slider was not already built', function() {
// beforeEach(function() {
// spyOn($.fn, 'slider').andCallThrough();
// videoProgressSlider.slider = null;
// videoPlayer.play();
// });
//
// it('build the slider', function() {
// expect(videoProgressSlider.slider).toBe('.slider');
// expect($.fn.slider).toHaveBeenCalledWith({
// range: 'min',
// change: videoProgressSlider.onChange,
// slide: videoProgressSlider.onSlide,
// stop: videoProgressSlider.onStop
// });
// });
//
// it('build the seek handle', function() {
// expect(videoProgressSlider.handle).toBe('.ui-slider-handle');
// expect($.fn.qtip).toHaveBeenCalledWith({
// content: "0:00",
// position: {
// my: 'bottom center',
// at: 'top center',
// container: videoProgressSlider.handle
// },
// hide: {
// delay: 700
// },
// style: {
// classes: 'ui-tooltip-slider',
// widget: true
// }
// });
// });
// });
});
describe('updatePlayTime', function() {
......@@ -181,10 +128,6 @@
expect(videoProgressSlider.frozen).toBeTruthy();
});
it('update the tooltip', function() {
expect($.fn.qtip).toHaveBeenCalled();
});
it('trigger seek event', function() {
expect(videoPlayer.onSlideSeek).toHaveBeenCalled();
expect(videoPlayer.currentTime).toEqual(20);
......@@ -199,9 +142,6 @@
value: 20
});
});
it('update the tooltip', function() {
expect($.fn.qtip).toHaveBeenCalled();
});
});
describe('onStop', function() {
......@@ -228,18 +168,6 @@
expect(videoProgressSlider.frozen).toBeFalsy();
});
});
describe('updateTooltip', function() {
beforeEach(function() {
initialize();
spyOn($.fn, 'slider').andCallThrough();
videoProgressSlider.updateTooltip(90);
});
it('set the tooltip value', function() {
expect($.fn.qtip).toHaveBeenCalledWith('option', 'content.text', '1:30');
});
});
});
}).call(this);
......@@ -90,19 +90,7 @@
// detect (and do not do anything) if there is a request for a speed that
// is already set.
//
// describe('when new speed is the same', function() {
// beforeEach(function() {
// initialize();
// videoSpeedControl.setSpeed(1.0);
// spyOn(videoPlayer, 'onSpeedChange').andCallThrough();
//
// $('li[data-speed="1.0"] a').click();
// });
//
// it('does not trigger speedChange event', function() {
// expect(videoPlayer.onSpeedChange).not.toHaveBeenCalled();
// });
// });
// describe("when new speed is the same") ...
describe('when new speed is not the same', function() {
beforeEach(function() {
......
......@@ -39,7 +39,6 @@
range: "min",
min: 0,
max: 100,
/* value: 100, */
value: videoVolumeControl.currentVolume,
change: videoVolumeControl.onChange,
slide: videoVolumeControl.onChange
......
......@@ -93,14 +93,7 @@ function (VideoPlayer) {
fadeOutTimeout: 1400,
availableQualities: ['hd720', 'hd1080', 'highres'],
qTipConfig: {
position: {
my: 'top right',
at: 'top center'
}
}
availableQualities: ['hd720', 'hd1080', 'highres']
};
if (!(_parseYouTubeIDs(state))) {
......
......@@ -53,9 +53,6 @@ function () {
if (!onTouchBasedDevice()) {
state.videoControl.pause();
state.videoControl.playPauseEl.qtip(state.config.qTipConfig);
state.videoControl.fullScreenEl.qtip(state.config.qTipConfig);
} else {
state.videoControl.play();
}
......@@ -77,7 +74,8 @@ function () {
$(document).on('keyup', state.videoControl.exitFullScreen);
if (state.videoType === 'html5') {
state.el.on('mousemove', state.videoControl.showControls)
state.el.on('mousemove', state.videoControl.showControls);
state.el.on('keydown', state.videoControl.showControls);
}
}
......
......@@ -43,10 +43,6 @@ function () {
state.videoQualityControl.el.show();
state.videoQualityControl.quality = null;
if (!onTouchBasedDevice()) {
state.videoQualityControl.el.qtip(state.config.qTipConfig);
}
}
// function _bindHandlers(state)
......
......@@ -32,9 +32,7 @@ function () {
// get the 'state' object as a context.
function _makeFunctionsPublic(state) {
state.videoProgressSlider.onSlide = _.bind(onSlide, state);
state.videoProgressSlider.onChange = _.bind(onChange, state);
state.videoProgressSlider.onStop = _.bind(onStop, state);
state.videoProgressSlider.updateTooltip = _.bind(updateTooltip, state);
state.videoProgressSlider.updatePlayTime = _.bind(updatePlayTime, state);
//Added for tests -- JM
state.videoProgressSlider.buildSlider = _.bind(buildSlider, state);
......@@ -56,22 +54,6 @@ function () {
function _buildHandle(state) {
state.videoProgressSlider.handle = state.videoProgressSlider.el.find('.ui-slider-handle');
state.videoProgressSlider.handle.qtip({
content: '' + Time.format(state.videoProgressSlider.slider.slider('value')),
position: {
my: 'bottom center',
at: 'top center',
container: state.videoProgressSlider.handle
},
hide: {
delay: 700
},
style: {
classes: 'ui-tooltip-slider',
widget: true
}
});
}
// ***************************************************************
......@@ -83,7 +65,6 @@ function () {
function buildSlider(state) {
state.videoProgressSlider.slider = state.videoProgressSlider.el.slider({
range: 'min',
change: state.videoProgressSlider.onChange,
slide: state.videoProgressSlider.onSlide,
stop: state.videoProgressSlider.onStop
});
......@@ -91,15 +72,10 @@ function () {
function onSlide(event, ui) {
this.videoProgressSlider.frozen = true;
this.videoProgressSlider.updateTooltip(ui.value);
this.trigger('videoPlayer.onSlideSeek', {'type': 'onSlideSeek', 'time': ui.value});
}
function onChange(event, ui) {
this.videoProgressSlider.updateTooltip(ui.value);
}
function onStop(event, ui) {
var _this = this;
......@@ -112,10 +88,6 @@ function () {
}, 200);
}
function updateTooltip(value) {
this.videoProgressSlider.handle.qtip('option', 'content.text', '' + Time.format(value));
}
//Changed for tests -- JM: Check if it is the cause of Chrome Bug Valera noticed
function updatePlayTime(params) {
if ((this.videoProgressSlider.slider) && (!this.videoProgressSlider.frozen)) {
......
......@@ -61,6 +61,9 @@ function () {
slide: state.videoVolumeControl.onChange
});
// Make sure that we can focus the actual volume slider while Tabing.
state.videoVolumeControl.volumeSliderEl.find('a').attr('tabindex', '0');
state.videoVolumeControl.el.toggleClass('muted', state.videoVolumeControl.currentVolume === 0);
}
......@@ -74,9 +77,21 @@ function () {
$(this).addClass('open');
});
state.videoVolumeControl.buttonEl.on('focus', function() {
$(this).parent().addClass('open');
});
state.videoVolumeControl.el.on('mouseleave', function() {
$(this).removeClass('open');
});
state.videoVolumeControl.buttonEl.on('blur', function() {
state.videoVolumeControl.volumeSliderEl.find('a').focus();
});
state.videoVolumeControl.volumeSliderEl.find('a').on('blur', function () {
state.videoVolumeControl.el.removeClass('open');
});
}
// ***************************************************************
......
......@@ -21,34 +21,41 @@ function () {
// function _makeFunctionsPublic(state)
//
// Functions which will be accessible via 'state' object. When called, these functions will
// get the 'state' object as a context.
// Functions which will be accessible via 'state' object. When called,
// these functions will get the 'state' object as a context.
function _makeFunctionsPublic(state) {
state.videoSpeedControl.changeVideoSpeed = _.bind(changeVideoSpeed, state);
state.videoSpeedControl.changeVideoSpeed = _.bind(
changeVideoSpeed, state
);
state.videoSpeedControl.setSpeed = _.bind(setSpeed, state);
state.videoSpeedControl.reRender = _.bind(reRender, state);
}
// function _renderElements(state)
//
// Create any necessary DOM elements, attach them, and set their initial configuration. Also
// make the created DOM elements available via the 'state' object. Much easier to work this
// way - you don't have to do repeated jQuery element selects.
// Create any necessary DOM elements, attach them, and set their
// initial configuration. Also make the created DOM elements available
// via the 'state' object. Much easier to work this way - you don't
// have to do repeated jQuery element selects.
function _renderElements(state) {
state.videoSpeedControl.speeds = state.speeds;
state.videoSpeedControl.el = state.el.find('div.speeds');
state.videoSpeedControl.videoSpeedsEl = state.videoSpeedControl.el.find('.video_speeds');
state.videoSpeedControl.videoSpeedsEl = state.videoSpeedControl.el
.find('.video_speeds');
state.videoControl.secondaryControlsEl.prepend(state.videoSpeedControl.el);
state.videoControl.secondaryControlsEl.prepend(
state.videoSpeedControl.el
);
$.each(state.videoSpeedControl.speeds, function(index, speed) {
var link = '<a class="speed_link" href="#">' + speed + 'x</a>';
//var link = $('<a href="#">' + speed + 'x</a>');
var link = '<a href="#">' + speed + 'x</a>';
state.videoSpeedControl.videoSpeedsEl.prepend($('<li data-speed="' + speed + '">' + link + '</li>'));
state.videoSpeedControl.videoSpeedsEl
.prepend(
$('<li data-speed="' + speed + '">' + link + '</li>')
);
});
state.videoSpeedControl.setSpeed(state.speed);
......@@ -56,9 +63,11 @@ function () {
// function _bindHandlers(state)
//
// Bind any necessary function callbacks to DOM events (click, mousemove, etc.).
// Bind any necessary function callbacks to DOM events (click,
// mousemove, etc.).
function _bindHandlers(state) {
state.videoSpeedControl.videoSpeedsEl.find('a').on('click', state.videoSpeedControl.changeVideoSpeed);
state.videoSpeedControl.videoSpeedsEl.find('a')
.on('click', state.videoSpeedControl.changeVideoSpeed);
if (onTouchBasedDevice()) {
state.videoSpeedControl.el.on('click', function(event) {
......@@ -77,18 +86,36 @@ function () {
event.preventDefault();
$(this).removeClass('open');
});
state.videoSpeedControl.el.children('a')
.on('focus', function () {
$(this).parent().addClass('open');
})
.on('blur', function () {
state.videoSpeedControl.videoSpeedsEl
.find('a.speed_link:first')
.focus();
});
state.videoSpeedControl.videoSpeedsEl.find('a.speed_link:last')
.on('blur', function () {
state.videoSpeedControl.el.removeClass('open');
});
}
}
// ***************************************************************
// Public functions start here.
// These are available via the 'state' object. Their context ('this' keyword) is the 'state' object.
// The magic private function that makes them available and sets up their context is makeFunctionsPublic().
// These are available via the 'state' object. Their context ('this'
// keyword) is the 'state' object. The magic private function that makes
// them available and sets up their context is makeFunctionsPublic().
// ***************************************************************
function setSpeed(speed) {
this.videoSpeedControl.videoSpeedsEl.find('li').removeClass('active');
this.videoSpeedControl.videoSpeedsEl.find("li[data-speed='" + speed + "']").addClass('active');
this.videoSpeedControl.videoSpeedsEl
.find("li[data-speed='" + speed + "']")
.addClass('active');
this.videoSpeedControl.el.find('p.active').html('' + speed + 'x');
}
......@@ -102,10 +129,15 @@ function () {
this.videoSpeedControl.setSpeed(
// To meet the API expected format.
parseFloat(this.videoSpeedControl.currentSpeed).toFixed(2).replace(/\.00$/, '.0')
parseFloat(this.videoSpeedControl.currentSpeed)
.toFixed(2)
.replace(/\.00$/, '.0')
);
this.trigger('videoPlayer.onSpeedChange', this.videoSpeedControl.currentSpeed);
this.trigger(
'videoPlayer.onSpeedChange',
this.videoSpeedControl.currentSpeed
);
}
}
......@@ -119,7 +151,6 @@ function () {
$.each(this.videoSpeedControl.speeds, function(index, speed) {
var link, listItem;
//link = $('<a href="#">' + speed + 'x</a>');
link = '<a href="#">' + speed + 'x</a>';
listItem = $('<li data-speed="' + speed + '">' + link + '</li>');
......@@ -131,7 +162,11 @@ function () {
_this.videoSpeedControl.videoSpeedsEl.prepend(listItem);
});
this.videoSpeedControl.videoSpeedsEl.find('a').on('click', this.videoSpeedControl.changeVideoSpeed);
this.videoSpeedControl.videoSpeedsEl.find('a')
.on('click', this.videoSpeedControl.changeVideoSpeed);
// TODO: After the control was re-rendered, we should attach 'focus'
// and 'blur' events once more.
}
});
......
......@@ -109,6 +109,7 @@ function () {
if (this.videoType === 'html5') {
this.el.on('mousemove', this.videoCaption.autoShowCaptions);
this.el.on('keydown', this.videoCaption.autoShowCaptions);
// Moving slider on subtitles is not a mouse move,
// but captions and controls should be showed.
......@@ -122,6 +123,10 @@ function () {
this.videoCaption.hideCaptions(this.hide_captions);
if (!this.youtubeId('1.0')) {
return;
}
$.ajaxWithPrefix({
url: _this.videoCaption.captionURL(),
notifyOnError: false,
......
from xmodule.contentstore.content import StaticContent
from xmodule.modulestore import Location
from xmodule.modulestore.mongo import MongoModuleStore
from xmodule.modulestore.inheritance import own_metadata
import logging
def _clone_modules(modulestore, modules, dest_location):
for module in modules:
original_loc = Location(module.location)
if original_loc.category != 'course':
module.location = module.location._replace(
tag=dest_location.tag, org=dest_location.org, course=dest_location.course)
else:
# on the course module we also have to update the module name
module.location = module.location._replace(
tag=dest_location.tag, org=dest_location.org, course=dest_location.course, name=dest_location.name)
print "Cloning module {0} to {1}....".format(original_loc, module.location)
# NOTE: usage of the the internal module.xblock_kvs._data does not include any 'default' values for the fields
modulestore.update_item(module.location, module.xblock_kvs._data)
# repoint children
if module.has_children:
new_children = []
for child_loc_url in module.children:
child_loc = Location(child_loc_url)
child_loc = child_loc._replace(
tag=dest_location.tag,
org=dest_location.org,
course=dest_location.course
)
new_children.append(child_loc.url())
modulestore.update_children(module.location, new_children)
# save metadata
modulestore.update_metadata(module.location, own_metadata(module))
def clone_course(modulestore, contentstore, source_location, dest_location, delete_original=False):
# first check to see if the modulestore is Mongo backed
if not isinstance(modulestore, MongoModuleStore):
......@@ -37,38 +73,10 @@ def clone_course(modulestore, contentstore, source_location, dest_location, dele
# Get all modules under this namespace which is (tag, org, course) tuple
modules = modulestore.get_items([source_location.tag, source_location.org, source_location.course, None, None, None])
_clone_modules(modulestore, modules, dest_location)
for module in modules:
original_loc = Location(module.location)
if original_loc.category != 'course':
module.location = module.location._replace(tag=dest_location.tag, org=dest_location.org,
course=dest_location.course)
else:
# on the course module we also have to update the module name
module.location = module.location._replace(tag=dest_location.tag, org=dest_location.org,
course=dest_location.course, name=dest_location.name)
print "Cloning module {0} to {1}....".format(original_loc, module.location)
modulestore.update_item(module.location, module._model_data._kvs._data)
# repoint children
if module.has_children:
new_children = []
for child_loc_url in module.children:
child_loc = Location(child_loc_url)
child_loc = child_loc._replace(
tag=dest_location.tag,
org=dest_location.org,
course=dest_location.course
)
new_children.append(child_loc.url())
modulestore.update_children(module.location, new_children)
# save metadata
modulestore.update_metadata(module.location, module._model_data._kvs._metadata)
modules = modulestore.get_items([source_location.tag, source_location.org, source_location.course, None, None, 'draft'])
_clone_modules(modulestore, modules, dest_location)
# now iterate through all of the assets and clone them
# first the thumbnails
......
......@@ -4,6 +4,7 @@ from xmodule.xml_module import XmlDescriptor
import logging
import sys
from xblock.core import String, Scope
from exceptions import SerializationError
log = logging.getLogger(__name__)
......@@ -27,11 +28,11 @@ class RawDescriptor(XmlDescriptor, XMLEditingDescriptor):
# re-raise
lines = self.data.split('\n')
line, offset = err.position
msg = ("Unable to create xml for problem {loc}. "
msg = ("Unable to create xml for module {loc}. "
"Context: '{context}'".format(
context=lines[line - 1][offset - 40:offset + 40],
loc=self.location))
raise Exception, msg, sys.exc_info()[2]
raise SerializationError(self.location, msg)
class EmptyDataRawDescriptor(XmlDescriptor, XMLEditingDescriptor):
......
<%! from django.utils.translation import ugettext as _ %>
% if display_name is not UNDEFINED and display_name is not None:
<h2> ${display_name} </h2>
<h2>${display_name}</h2>
% endif
<div
id="video_${id}"
class="videoalpha"
id="video_${id}"
class="videoalpha"
% if not settings.MITX_FEATURES['STUB_VIDEO_FOR_TESTING']:
data-streams="${youtube_streams}"
% endif
% if not settings.MITX_FEATURES['STUB_VIDEO_FOR_TESTING']:
data-streams="${youtube_streams}"
% endif
${'data-sub="{}"'.format(sub) if sub else ''}
${'data-autoplay="{}"'.format(autoplay) if autoplay else ''}
% if not settings.MITX_FEATURES['STUB_VIDEO_FOR_TESTING']:
${'data-mp4-source="{}"'.format(sources.get('mp4')) if sources.get('mp4') else ''}
${'data-webm-source="{}"'.format(sources.get('webm')) if sources.get('webm') else ''}
${'data-ogg-source="{}"'.format(sources.get('ogv')) if sources.get('ogv') else ''}
% endif
${'data-sub="{}"'.format(sub) if sub else ''}
${'data-autoplay="{}"'.format(autoplay) if autoplay else ''}
data-caption-data-dir="${data_dir}"
data-show-captions="${show_captions}"
data-start="${start}"
data-end="${end}"
data-caption-asset-path="${caption_asset_path}"
data-autoplay="${autoplay}"
% if not settings.MITX_FEATURES['STUB_VIDEO_FOR_TESTING']:
${'data-mp4-source="{}"'.format(sources.get('mp4')) if sources.get('mp4') else ''}
${'data-webm-source="{}"'.format(sources.get('webm')) if sources.get('webm') else ''}
${'data-ogg-source="{}"'.format(sources.get('ogv')) if sources.get('ogv') else ''}
% endif
data-caption-data-dir="${data_dir}"
data-show-captions="${show_captions}"
data-start="${start}"
data-end="${end}"
data-caption-asset-path="${caption_asset_path}"
data-autoplay="${autoplay}"
>
<div class="tc-wrapper">
<article class="video-wrapper">
<div class="video-player-pre"></div>
<section class="video-player">
<div id="${id}"></div>
</section>
<div class="video-player-post"></div>
<section class="video-controls">
<div class="slider"></div>
<div>
<ul class="vcr">
<li><a class="video_control" href="#" title="${_('Play')}"></a></li>
<li><div class="vidtime">0:00 / 0:00</div></li>
</ul>
<div class="secondary-controls">
<div class="speeds">
<a href="#">
<h3>${_('Speed')}</h3>
<p class="active"></p>
</a>
<ol class="video_speeds"></ol>
</div>
<div class="volume">
<a href="#"></a>
<div class="volume-slider-container">
<div class="volume-slider"></div>
<div class="tc-wrapper">
<article class="video-wrapper">
<div class="video-player-pre"></div>
<section class="video-player">
<div id="${id}"></div>
</section>
<div class="video-player-post"></div>
<section class="video-controls">
<div class="slider" tabindex="0" title="Video position"></div>
<div>
<ul class="vcr">
<li><a class="video_control" href="#" tabindex="0" title="${_('Play')}"></a></li>
<li><div class="vidtime">0:00 / 0:00</div></li>
</ul>
<div class="secondary-controls">
<div class="speeds">
<a href="#" tabindex="0" title="Speeds">
<h3 tabindex="-1">${_('Speed')}</h3>
<p tabindex="-1" class="active"></p>
</a>
<ol tabindex="-1" class="video_speeds"></ol>
</div>
</div>
<a href="#" class="add-fullscreen" title="${_('Fill browser')}">${_('Fill browser')}</a>
<a href="#" class="quality_control" title="${_('HD')}">${_('HD')}</a>
<div class="volume">
<a href="#" tabindex="0" title="Volume"></a>
<div tabindex="-1" class="volume-slider-container">
<div tabindex="-1" class="volume-slider"></div>
</div>
</div>
<a href="#" class="add-fullscreen" tabindex="0" title="${_('Fill browser')}">${_('Fill browser')}</a>
<a href="#" class="quality_control" tabindex="0" title="${_('HD')}">${_('HD')}</a>
<a href="#" class="hide-subtitles" title="${_('Turn off captions')}">Captions</a>
<a href="#" class="hide-subtitles" title="${_('Turn off captions')}">${_('Captions')}</a>
</div>
</div>
</div>
</section>
</article>
<ol class="subtitles"><li></li></ol>
</section>
</article>
</div>
<ol class="subtitles" tabindex="0" title="Captions"><li></li></ol>
</div>
</div>
% if sources.get('main'):
<div class="video-sources">
<p>${_('Download video <a href="%s">here</a>.') % sources.get('main')}</p>
</div>
<div class="video-sources">
<p>${(_('Download video') + ' <a href="%s">' + _('here') + '</a>.') % sources.get('main')}</p>
</div>
% endif
% if track:
<div class="video-tracks">
<p>${_('Download subtitles <a href="%s">here</a>.') % track}</p>
</div>
<div class="video-tracks">
<p>${(_('Download subtitles') + ' <a href="%s">' + _('here') + '</a>.') % track}</p>
</div>
% endif
......@@ -8,6 +8,6 @@
-e git+https://github.com/eventbrite/zendesk.git@d53fe0e81b623f084e91776bcf6369f8b7b63879#egg=zendesk
# Our libraries:
-e git+https://github.com/edx/XBlock.git@f8c10cb0d16122ba66f7e94a32ec3774c1dee13e#egg=XBlock
-e git+https://github.com/edx/XBlock.git@446668fddc75b78512eef4e9425cbc9a3327606f#egg=XBlock
-e git+https://github.com/edx/codejail.git@0a1b468#egg=codejail
-e git+https://github.com/edx/diff-cover.git@v0.2.0#egg=diff_cover
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