Commit 15871785 by clrux

Merge pull request #9974 from edx/clrux/ac-187

Video player (1 of 2): refreshing the UI
parents 685572cb 78c6a38c
......@@ -260,7 +260,7 @@
state.videoSpeedControl.setSpeed(1.0);
spyOn(state.videoPlayer, 'onSpeedChange').andCallThrough();
$('li[data-speed="0.75"] a').click();
$('li[data-speed="0.75"] .speed-link').click();
});
it('trigger speedChange event', function () {
......@@ -274,7 +274,7 @@
xdescribe('onSpeedChange', function () {
beforeEach(function () {
state = jasmine.initializePlayer();
$('li[data-speed="1.0"] a').addClass('active');
$('li[data-speed="1.0"] .speed-link').addClass('active');
state.videoSpeedControl.setSpeed(0.75);
});
......
......@@ -5,16 +5,16 @@
closeSubmenuKeyboard, menu, menuItems, menuSubmenuItem, submenu, submenuItems, overlay, playButton;
openMenu = function () {
var container = $('div.video');
var container = $('.video');
jasmine.Clock.useMock();
container.find('video').trigger('contextmenu');
menu = container.children('ol.contextmenu');
menuItems = menu.children('li.menu-item').not('.submenu-item');
menuSubmenuItem = menu.children('li.menu-item.submenu-item');
submenu = menuSubmenuItem.children('ol.submenu');
submenuItems = submenu.children('li.menu-item');
overlay = container.children('div.overlay');
playButton = $('a.video_control.play');
menu = container.children('.contextmenu');
menuItems = menu.children('.menu-item').not('.submenu-item');
menuSubmenuItem = menu.children('.menu-item.submenu-item');
submenu = menuSubmenuItem.children('.submenu');
submenuItems = submenu.children('.menu-item');
overlay = container.children('.overlay');
playButton = $('.video_control.play');
};
keyPressEvent = function(key) {
......
......@@ -30,8 +30,6 @@
var fullScreenControl = $('.add-fullscreen');
expect(fullScreenControl).toHaveAttrs({
'role': 'button',
'title': 'Fill browser',
'aria-disabled': 'false'
});
});
......@@ -53,14 +51,10 @@
var fullScreenControl = $('.add-fullscreen');
fullScreenControl.click();
expect(fullScreenControl).toHaveAttrs({
'role': 'button',
'title': 'Exit full browser',
'aria-disabled': 'false'
});
fullScreenControl.click();
expect(fullScreenControl).toHaveAttrs({
'role': 'button',
'title': 'Fill browser',
'aria-disabled': 'false'
});
});
......
......@@ -25,8 +25,6 @@
it('add ARIA attributes to play control', function () {
expect($('.video_control.play')).toHaveAttrs({
'role': 'button',
'title': 'Play',
'aria-disabled': 'false'
});
});
......@@ -34,8 +32,6 @@
it('can update ARIA state on play', function () {
state.el.trigger('play');
expect($('.video_control.pause')).toHaveAttrs({
'role': 'button',
'title': 'Pause',
'aria-disabled': 'false'
});
});
......@@ -44,8 +40,6 @@
state.el.trigger('play');
state.el.trigger('ended');
expect($('.video_control.play')).toHaveAttrs({
'role': 'button',
'title': 'Play',
'aria-disabled': 'false'
});
});
......
......@@ -27,8 +27,6 @@
it('add ARIA attributes to play control', function () {
expect($('.video_control.play')).toHaveAttrs({
'role': 'button',
'title': 'Play',
'aria-disabled': 'false'
});
});
......
......@@ -745,11 +745,6 @@ function (VideoPlayer) {
$('.add-fullscreen').click();
});
it('replace the full screen button tooltip', function () {
expect($('.add-fullscreen'))
.toHaveAttr('title', 'Exit full browser');
});
it('add the video-fullscreen class', function () {
expect(state.el).toHaveClass('video-fullscreen');
});
......@@ -773,11 +768,6 @@ function (VideoPlayer) {
$('.add-fullscreen').click();
});
it('replace the full screen button tooltip', function () {
expect($('.add-fullscreen'))
.toHaveAttr('title', 'Fill browser');
});
it('remove the video-fullscreen class', function () {
expect(state.el).not.toHaveClass('video-fullscreen');
});
......
......@@ -33,8 +33,6 @@
it('add ARIA attributes to quality control', function () {
expect(qualityControl.el).toHaveAttrs({
'role': 'button',
'title': 'HD off',
'aria-disabled': 'false'
});
});
......@@ -117,7 +115,7 @@
it('does not contain the quality control', function () {
state = jasmine.initializePlayer();
expect(state.el.find('a.quality-control').length).toBe(0);
expect(state.el.find('.quality-control').length).toBe(0);
});
});
});
......
......@@ -33,8 +33,6 @@
it('add ARIA attributes to play control', function () {
state.el.trigger('play');
expect($('.skip-control')).toHaveAttrs({
'role': 'button',
'title': 'Do not show again',
'aria-disabled': 'false'
});
});
......
(function (undefined) {
'use strict';
describe('VideoSpeedControl', function () {
var state, oldOTBD;
......@@ -38,21 +39,11 @@
expect($(link)).toHaveData(
'speed', state.speeds[index]
);
expect($(link).find('a').text()).toBe(
expect($(link).find('.speed-option').text()).toBe(
state.speeds[index] + 'x'
);
});
});
it('add ARIA attributes to speed control', function () {
var speedControl = $('div.speeds>a');
expect(speedControl).toHaveAttrs({
'role': 'button',
'title': 'Speeds',
'aria-disabled': 'false'
});
});
});
describe('when running on touch based device', function () {
......@@ -61,33 +52,17 @@
window.onTouchBasedDevice.andReturn([device]);
state = jasmine.initializePlayer();
expect(state.el.find('div.speeds')).not.toExist();
expect(state.el.find('.speeds')).not.toExist();
});
});
});
describe('when running on non-touch based device', function () {
var speedControl, speedEntries, speedButton,
var speedControl, speedEntries, speedButton, speedsContainer,
KEY = $.ui.keyCode,
keyPressEvent = function(key) {
return $.Event('keydown', {keyCode: key});
},
// Get previous element in array or cyles back to the last
// if it is the first.
previousSpeed = function(index) {
return speedEntries.eq(index < 1 ?
speedEntries.length - 1 :
index - 1);
},
// Get next element in array or cyles back to the first if
// it is the last.
nextSpeed = function(index) {
return speedEntries.eq(index >= speedEntries.length-1 ?
0 :
index + 1);
};
beforeEach(function () {
......@@ -95,7 +70,7 @@
speedControl = $('.speeds');
speedButton = $('.speed-button');
speedsContainer = $('.video-speeds');
speedEntries = speedsContainer.find('a');
speedEntries = speedsContainer.find('.speed-option');
});
it('open/close the speed menu on mouseenter/mouseleave',
......@@ -114,11 +89,6 @@
expect(speedControl).toHaveClass('is-opened');
});
it('close the speed menu on click', function () {
speedControl.mouseenter().click();
expect(speedControl).not.toHaveClass('is-opened');
});
it('close the speed menu on outside click', function () {
speedControl.trigger(keyPressEvent(KEY.ENTER));
$(window).click();
......@@ -150,8 +120,7 @@
it('UP and DOWN keydown function as expected on speed entries',
function () {
var lastEntry = speedEntries.length-1,
speed_0_75 = speedEntries.filter(':contains("0.75x")'),
var speed_0_75 = speedEntries.filter(':contains("0.75x")'),
speed_1_0 = speedEntries.filter(':contains("1.0x")');
// First open menu
......@@ -226,7 +195,7 @@
it('trigger speedChange event', function () {
spyOnEvent(state.el, 'speedchange');
$('li[data-speed="0.75"] a').click();
$('li[data-speed="0.75"] .speed-option').click();
expect('speedchange').toHaveBeenTriggeredOn(state.el);
expect(state.videoSpeedControl.currentSpeed).toEqual('0.75');
});
......
......@@ -3,6 +3,12 @@
describe('VideoVolumeControl', function () {
var state, oldOTBD, volumeControl;
var KEY = $.ui.keyCode,
keyPressEvent = function(key) {
return $.Event('keydown', { keyCode: key });
};
beforeEach(function () {
oldOTBD = window.onTouchBasedDevice;
window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice')
......@@ -56,24 +62,20 @@ describe('VideoVolumeControl', function () {
var liveRegion = $('.video-live-region');
expect(liveRegion).toHaveAttrs({
'role': 'status',
'aria-live': 'polite',
'aria-atomic': 'false'
'aria-live': 'polite'
});
});
it('add ARIA attributes to volume control', function () {
var button = $('.volume > a');
var button = $('.volume .control');
expect(button).toHaveAttrs({
'role': 'button',
'title': 'Volume',
'aria-disabled': 'false'
});
});
it('bind the volume control', function () {
var button = $('.volume > a');
var button = $('.volume .control');
expect(button).toHandle('keydown');
expect(button).toHandle('mousedown');
......@@ -185,16 +187,19 @@ describe('VideoVolumeControl', function () {
});
describe('increaseVolume', function () {
beforeEach(function () {
state = jasmine.initializePlayer();
volumeControl = state.videoVolumeControl;
});
it('volume is increased correctly', function () {
var button = $('.volume .control');
volumeControl.volume = 60;
state.el.trigger(jQuery.Event("keydown", {
keyCode: $.ui.keyCode.UP
}));
// adjust the volume
button.focus();
button.trigger(keyPressEvent(KEY.UP));
expect(volumeControl.volume).toEqual(80);
});
......@@ -206,16 +211,19 @@ describe('VideoVolumeControl', function () {
});
describe('decreaseVolume', function () {
beforeEach(function () {
state = jasmine.initializePlayer();
volumeControl = state.videoVolumeControl;
});
it('volume is decreased correctly', function () {
var button = $('.volume .control');
volumeControl.volume = 60;
state.el.trigger(jQuery.Event("keydown", {
keyCode: $.ui.keyCode.DOWN
}));
// adjust the volume
button.focus();
button.trigger(keyPressEvent(KEY.DOWN));
expect(volumeControl.volume).toEqual(40);
});
......@@ -274,21 +282,21 @@ describe('VideoVolumeControl', function () {
it('nothing happens if ALT+keyUp are pushed down', function () {
assertVolumeIsNotChanged({
keyCode: $.ui.keyCode.UP,
keyCode: KEY.UP,
altKey: true
});
});
it('nothing happens if SHIFT+keyUp are pushed down', function () {
assertVolumeIsNotChanged({
keyCode: $.ui.keyCode.UP,
keyCode: KEY.UP,
shiftKey: true
});
});
it('nothing happens if SHIFT+keyDown are pushed down', function () {
assertVolumeIsNotChanged({
keyCode: $.ui.keyCode.DOWN,
keyCode: KEY.DOWN,
shiftKey: true
});
});
......@@ -302,8 +310,8 @@ describe('VideoVolumeControl', function () {
it('nothing happens if ALT+ENTER are pushed down', function () {
var isMuted = volumeControl.getMuteStatus();
$('.volume > a').trigger(jQuery.Event("keydown", {
keyCode: $.ui.keyCode.ENTER,
$('.volume .control').trigger(jQuery.Event("keydown", {
keyCode: KEY.ENTER,
altKey: true
}));
expect(volumeControl.getMuteStatus()).toEqual(isMuted);
......
......@@ -2,10 +2,14 @@
'use strict';
define('video/04_video_full_screen.js', [], function () {
var template = [
'<a href="#" class="add-fullscreen" title="',
gettext('Fill browser'), '" role="button" aria-disabled="false">',
gettext('Fill browser'),
'</a>'
'<button class="control add-fullscreen" aria-disabled="false">',
'<span class="icon-fallback-img">',
'<span class="icon fa fa-arrows-alt" aria-hidden="true"></span>',
'<span class="sr control-text">',
gettext('Fill browser'),
'</span>',
'</span>',
'</button>'
].join('');
// VideoControl() function - what this module "exports".
......@@ -133,8 +137,12 @@ define('video/04_video_full_screen.js', [], function () {
fullScreenClassNameEl.removeClass('video-fullscreen');
$(window).scrollTop(this.scrollPos);
this.videoFullScreen.fullScreenEl
.attr('title', gettext('Fill browser'))
.text(gettext('Fill browser'));
.find('.icon')
.removeClass('fa-compress')
.addClass('fa-arrows-alt')
.find('.control-text')
.text(gettext('Fill browser'));
this.el.trigger('fullscreen', [this.isFullScreen]);
}
......@@ -146,8 +154,12 @@ define('video/04_video_full_screen.js', [], function () {
this.videoFullScreen.fullScreenState = this.isFullScreen = true;
fullScreenClassNameEl.addClass('video-fullscreen');
this.videoFullScreen.fullScreenEl
.attr('title', gettext('Exit full browser'))
.text(gettext('Exit full browser'));
.find('.icon')
.removeClass('fa-arrows-alt')
.addClass('fa-compress')
.find('.control-text')
.text(gettext('Exit full browser'));
this.el.trigger('fullscreen', [this.isFullScreen]);
}
......
(function (requirejs, require, define) {
// VideoQualityControl module.
'use strict';
define(
'video/05_video_quality_control.js',
[],
function () {
var template = [
'<a href="#" class="quality-control is-hidden" title="',
gettext('HD off'), '" role="button" aria-disabled="false">',
gettext('HD off'),
'</a>'
'<button class="control quality-control is-hidden" aria-disabled="false">',
'<span class="icon-fallback-img">',
'<span class="icon icon-hd" aria-hidden="true">HD</span>', // "HD" is treated as a proper noun
// Translator note:
// HD stands for high definition
'<span class="sr text-translation">',
gettext('High Definition'),
'</span>&nbsp;',
'<span class="text control-text">',
// Translator note:
// Values are 'off' or 'on' depending on the state of the HD control
gettext('off'),
'</span>',
'</span>',
'</button>'
].join('');
// VideoQualityControl() function - what this module "exports".
......@@ -134,17 +146,17 @@ function () {
var controlStateStr;
this.videoQualityControl.quality = value;
if (_.contains(this.config.availableHDQualities, value)) {
controlStateStr = gettext('HD on');
controlStateStr = gettext('on');
this.videoQualityControl.el
.addClass('active')
.attr('title', controlStateStr)
.text(controlStateStr);
.find('.control-text')
.text(controlStateStr);
} else {
controlStateStr = gettext('HD off');
controlStateStr = gettext('off');
this.videoQualityControl.el
.removeClass('active')
.attr('title', controlStateStr)
.text(controlStateStr);
.find('.control-text')
.text(controlStateStr);
}
}
......
......@@ -38,13 +38,25 @@ function() {
step: 20,
template: [
'<div class="volume">',
'<a href="#" role="button" aria-disabled="false" title="',
gettext('Volume'), '" aria-label="',
gettext('Click on this button to mute or unmute this video or press UP or DOWN buttons to increase or decrease volume level.'),
'"></a>',
'<div role="presentation" class="volume-slider-container">',
'<div class="volume-slider"></div>',
'<div class="volume" role="application">',
'<button class="control" aria-disabled="false" aria-label="',
gettext('Volume: Click on this button to mute or unmute this video or press UP or ' +
'DOWN buttons to increase or decrease volume level.'),
'" aria-expanded="false">',
'<span class="icon-fallback-img">',
'<span class="icon fa fa-volume-up" aria-hidden="true"></span>',
'<span class="sr control-text">',
gettext('Volume'),
'</span>',
'</span>',
'</button>',
'<div class="volume-slider-container" aria-hidden="true">',
'<div class="volume-slider" ',
'role="slider"',
'aria-orientation="vertical" ',
'aria-valuemin="0" ',
'aria-valuemax="100" ',
'aria-valuenow=""></div>',
'</div>',
'</div>'
].join(''),
......@@ -89,7 +101,7 @@ function() {
// Youtube iframe react on key buttons and has his own handlers.
// So, we disallow focusing on iframe.
this.state.el.find('iframe').attr('tabindex', -1);
this.button = this.el.children('a');
this.button = this.el.children('.control');
this.cookie = new CookieManager(this.min, this.max);
this.a11y = new Accessibility(
this.button, this.min, this.max, this.i18n
......@@ -128,18 +140,17 @@ function() {
/** Bind any necessary function callbacks to DOM events. */
bindHandlers: function() {
this.state.el.on({
'keydown': this.keyDownHandler,
'play.volume': _.once(this.updateVolumeSilently),
'volumechange': this.onVolumeChangeHandler
});
this.el.on({
this.state.el.find('.volume').on({
'mouseenter': this.openMenu,
'mouseleave': this.closeMenu
});
this.button.on({
'keydown': this.keyDownHandler,
'click': false,
'mousedown': this.toggleMuteHandler,
'keydown': this.keyDownButtonHandler,
'focus': this.openMenu,
'blur': this.closeMenu
});
......@@ -194,6 +205,8 @@ function() {
var volume = Math.min(this.getVolume() + this.step, this.max);
this.setVolume(volume, false, false);
this.el.find('.volume-slider')
.attr('aria-valuenow', volume);
},
/** Decreases current volume level using previously defined step. */
......@@ -201,11 +214,15 @@ function() {
var volume = Math.max(this.getVolume() - this.step, this.min);
this.setVolume(volume, false, false);
this.el.find('.volume-slider')
.attr('aria-valuenow', volume);
},
/** Updates volume slider view. */
updateSliderView: function (volume) {
this.volumeSlider.slider('value', volume);
this.el.find('.volume-slider')
.attr('aria-valuenow', volume);
},
/**
......@@ -223,6 +240,8 @@ function() {
volume = muteStatus ? 0 : this.storedVolume;
this.setVolume(volume, false, false);
this.el.find('.volume-slider')
.attr('aria-valuenow', volume);
},
/**
......@@ -241,6 +260,18 @@ function() {
var action = isMuted ? 'addClass' : 'removeClass';
this.el[action]('is-muted');
if (isMuted) {
this.el
.find('.control .icon')
.removeClass('fa-volume-up')
.addClass('fa-volume-off');
} else {
this.el
.find('.control .icon')
.removeClass('fa-volume-off')
.addClass('fa-volume-up');
}
},
/** Toggles the state of the volume button. */
......@@ -266,11 +297,13 @@ function() {
/** Opens volume menu. */
openMenu: function() {
this.el.addClass('is-opened');
this.button.attr('aria-expanded', 'true');
},
/** Closes speed menu. */
closeMenu: function() {
this.el.removeClass('is-opened');
this.button.attr('aria-expanded', 'false');
},
/**
......@@ -310,6 +343,17 @@ function() {
this.decreaseVolume();
return false;
case KEY.SPACE:
case KEY.ENTER:
// Shift + Enter keyboard shortcut might be used by
// screen readers. In this case, do nothing.
if (event.shiftKey) {
return true;
}
this.toggleMute();
return false;
}
return true;
......@@ -333,7 +377,6 @@ function() {
case KEY.ENTER:
case KEY.SPACE:
this.toggleMute();
return false;
}
......@@ -347,6 +390,8 @@ function() {
*/
onSlideHandler: function(event, ui) {
this.setVolume(ui.value, false, true);
this.el.find('.volume-slider')
.attr('aria-valuenow', ui.volume);
},
/**
......@@ -395,10 +440,8 @@ function() {
initialize: function() {
this.liveRegion = $('<div />', {
'class': 'sr video-live-region',
'role': 'status',
'aria-hidden': 'false',
'aria-live': 'polite',
'aria-atomic': 'false'
'aria-live': 'polite'
});
this.button.after(this.liveRegion);
......@@ -413,6 +456,9 @@ function() {
this.getVolumeDescription(volume),
this.i18n['Volume'] + '.'
].join(' '));
$(this.button).parent().find('.volume-slider')
.attr('aria-valuenow', volume);
},
/**
......
(function (requirejs, require, define) {
"use strict";
define(
'video/08_video_speed_control.js',
['video/00_iterator.js'],
function (Iterator) {
"use strict";
/**
* Video speed control module.
* @exports video/08_video_speed_control.js
......@@ -29,13 +29,23 @@ function (Iterator) {
SpeedControl.prototype = {
template: [
'<div class="speeds menu-container">',
'<a class="speed-button" href="#" title="',
gettext('Speeds'), '" role="button" aria-disabled="false">',
'<span class="label">', gettext('Speed'), '</span>',
'<div class="speeds menu-container" role="application">',
'<button class="control speed-button" aria-label="',
/* jshint maxlen:200 */
gettext('Speed: Press UP to enter the speed menu then use the UP and DOWN arrow keys to navigate the different speeds, then press ENTER to change to the selected speed.'),
'" aria-disabled="false" aria-expanded="false">',
'<span class="icon-fallback-img">',
'<span class="icon fa fa-caret-right" aria-hidden="true"></span>',
'<span class="sr control-text">',
gettext('Speed'),
'</span>',
'</span>',
'<span class="label" aria-hidden="true">',
gettext('Speed'),
'</span>',
'<span class="value"></span>',
'</a>',
'<ol class="video-speeds menu" role="menu"></ol>',
'</button>',
'<ol class="video-speeds menu"></ol>',
'</div>'
].join(''),
......@@ -88,16 +98,16 @@ function (Iterator) {
reversedSpeeds = speeds.concat().reverse(),
speedsList = $.map(reversedSpeeds, function (speed) {
return [
'<li data-speed="', speed, '" role="presentation">',
'<a class="speed-link" href="#" role="menuitem" tabindex="-1">',
'<li data-speed="', speed, '">',
'<button class="control speed-option" tabindex="-1">',
speed, 'x',
'</a>',
'</button>',
'</li>'
].join('');
});
speedsContainer.html(speedsList.join(''));
this.speedLinks = new Iterator(speedsContainer.find('.speed-link'));
this.speedLinks = new Iterator(speedsContainer.find('.speed-option'));
this.state.el.find('.secondary-controls').prepend(this.el);
},
......@@ -110,7 +120,7 @@ function (Iterator) {
this.el.on({
'mouseenter': this.mouseEnterHandler,
'mouseleave': this.mouseLeaveHandler,
'click': this.clickMenuHandler,
'click': this.openMenu,
'keydown': this.keyDownMenuHandler
});
......@@ -119,7 +129,7 @@ function (Iterator) {
this.speedsContainer.on({
click: this.clickLinkHandler,
keydown: this.keyDownLinkHandler
}, 'a.speed-link');
}, '.speed-option');
this.state.el.on({
'speed:set': this.onSetSpeed,
......@@ -169,7 +179,9 @@ function (Iterator) {
}
this.el.addClass('is-opened');
this.speedButton.attr('tabindex', -1);
this.speedButton
.attr('tabindex', -1)
.attr('aria-expanded', 'true');
},
/**
......@@ -183,7 +195,9 @@ function (Iterator) {
}
this.el.removeClass('is-opened');
this.speedButton.attr('tabindex', 0);
this.speedButton
.attr('tabindex', 0)
.attr('aria-expanded', 'false');
},
/**
......@@ -216,7 +230,7 @@ function (Iterator) {
* Click event handler for the menu.
* @param {jquery Event} event
*/
clickMenuHandler: function (event) {
clickMenuHandler: function () {
this.closeMenu();
return false;
......@@ -239,7 +253,7 @@ function (Iterator) {
* Mouseenter event handler for the menu.
* @param {jquery Event} event
*/
mouseEnterHandler: function (event) {
mouseEnterHandler: function () {
this.openMenu();
return false;
......@@ -249,7 +263,7 @@ function (Iterator) {
* Mouseleave event handler for the menu.
* @param {jquery Event} event
*/
mouseLeaveHandler: function (event) {
mouseLeaveHandler: function () {
// Only close the menu is no speed entry has focus.
if (!this.speedLinks.list.is(':focus')) {
this.closeMenu();
......
......@@ -25,10 +25,14 @@ define('video/09_play_pause_control.js', [], function() {
PlayPauseControl.prototype = {
template: [
'<a class="video_control play" href="#" title="',
gettext('Play'), '" role="button" aria-disabled="false">',
gettext('Play'),
'</a>'
'<button class="control video_control play" aria-disabled="false">',
'<span class="icon-fallback-img">',
'<span class="icon fa fa-play" aria-hidden="true"></span>',
'<span class="sr control-text">',
gettext('Play'),
'</span>',
'</span>',
'</button>'
].join(''),
destroy: function () {
......@@ -71,14 +75,28 @@ define('video/09_play_pause_control.js', [], function() {
play: function () {
this.el
.attr('title', this.i18n['Pause']).text(this.i18n['Pause'])
.removeClass('play').addClass('pause');
.addClass('pause')
.removeClass('play')
.find('.icon')
.removeClass('fa-play')
.addClass('fa-pause');
this.el
.find('.control-text')
.text(gettext('Pause'));
},
pause: function () {
this.el
.attr('title', this.i18n['Play']).text(this.i18n['Play'])
.removeClass('pause').addClass('play');
.removeClass('pause')
.addClass('play')
.find('.icon')
.removeClass('fa-pause')
.addClass('fa-play');
this.el
.find('.control-text')
.text(gettext('Play'));
}
};
......
......@@ -25,10 +25,14 @@ define('video/09_play_skip_control.js', [], function() {
PlaySkipControl.prototype = {
template: [
'<a class="video_control play play-skip-control" href="#" title="',
gettext('Play'), '" role="button" aria-disabled="false">',
gettext('Play'),
'</a>'
'<button class="control video_control play play-skip-control" aria-disabled="false">',
'<span class="icon-fallback-img">',
'<span class="icon icon-play" aria-hidden="true"></span>',
'<span class="text control-text">',
gettext('Play'),
'</span>',
'</span>',
'</button>'
].join(''),
destroy: function () {
......@@ -72,8 +76,13 @@ define('video/09_play_skip_control.js', [], function() {
play: function () {
this.el
.attr('title', gettext('Skip')).text(gettext('Skip'))
.removeClass('play').addClass('skip');
.removeClass('play')
.addClass('skip')
.find('.icon')
.removeClass('icon-play')
.addClass('icon-step-forward')
.find('.control-text')
.text(gettext('Skip'));
// Disable possibility to pause the video.
this.state.el.find('video').off('click');
}
......
......@@ -28,10 +28,14 @@ function() {
SkipControl.prototype = {
template: [
'<a class="video_control skip skip-control" href="#" title="',
gettext('Do not show again'), '" role="button" aria-disabled="false">',
gettext('Do not show again'),
'</a>'
'<button class="control video_control skip skip-control" aria-disabled="false">',
'<span class="icon-fallback-img">',
'<span class="icon fa fa-step-forward" aria-hidden="true"></span>',
'<span class="text control-text">',
gettext('Do not show again'),
'</span>',
'</span>',
'</button>'
].join(''),
destroy: function () {
......@@ -51,7 +55,7 @@ function() {
* initial configuration.
*/
render: function() {
this.state.el.find('.vcr a').after(this.el);
this.state.el.find('.vcr .control').after(this.el);
},
/** Bind any necessary function callbacks to DOM events. */
......
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12" height="14" viewBox="0 0 12 14">
<path fill="#f2f2f2" d="M10.023 4.227l-2.773 2.773 2.773 2.773 1.125-1.125q0.227-0.242 0.547-0.109 0.305 0.133 0.305 0.461v3.5q0 0.203-0.148 0.352t-0.352 0.148h-3.5q-0.328 0-0.461-0.312-0.133-0.305 0.109-0.539l1.125-1.125-2.773-2.773-2.773 2.773 1.125 1.125q0.242 0.234 0.109 0.539-0.133 0.312-0.461 0.312h-3.5q-0.203 0-0.352-0.148t-0.148-0.352v-3.5q0-0.328 0.312-0.461 0.305-0.133 0.539 0.109l1.125 1.125 2.773-2.773-2.773-2.773-1.125 1.125q-0.148 0.148-0.352 0.148-0.094 0-0.187-0.039-0.312-0.133-0.312-0.461v-3.5q0-0.203 0.148-0.352t0.352-0.148h3.5q0.328 0 0.461 0.312 0.133 0.305-0.109 0.539l-1.125 1.125 2.773 2.773 2.773-2.773-1.125-1.125q-0.242-0.234-0.109-0.539 0.133-0.312 0.461-0.312h3.5q0.203 0 0.352 0.148t0.148 0.352v3.5q0 0.328-0.305 0.461-0.102 0.039-0.195 0.039-0.203 0-0.352-0.148z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="5" height="14" viewBox="0 0 5 14">
<path fill="#ffffff" d="M5 3.5v7q0 0.203-0.148 0.352t-0.352 0.148-0.352-0.148l-3.5-3.5q-0.148-0.148-0.148-0.352t0.148-0.352l3.5-3.5q0.148-0.148 0.352-0.148t0.352 0.148 0.148 0.352z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="5" height="14" viewBox="0 0 5 14">
<path fill="#f2f2f2" d="M4.5 7q0 0.203-0.148 0.352l-3.5 3.5q-0.148 0.148-0.352 0.148t-0.352-0.148-0.148-0.352v-7q0-0.203 0.148-0.352t0.352-0.148 0.352 0.148l3.5 3.5q0.148 0.148 0.148 0.352z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="8" height="14" viewBox="0 0 8 14">
<path fill="#f2f2f2" d="M8 9.5q0 0.203-0.148 0.352t-0.352 0.148h-7q-0.203 0-0.352-0.148t-0.148-0.352 0.148-0.352l3.5-3.5q0.148-0.148 0.352-0.148t0.352 0.148l3.5 3.5q0.148 0.148 0.148 0.352z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="14" viewBox="0 0 16 14">
<path fill="#f2f2f2" d="M6.133 7.875h1.617q-0.109 1.234-0.77 1.941t-1.676 0.707q-1.266 0-1.988-0.906t-0.723-2.469q0-1.516 0.727-2.434t1.82-0.918q1.156 0 1.812 0.68t0.758 1.93h-1.586q-0.039-0.5-0.277-0.773t-0.637-0.273q-0.445 0-0.691 0.473t-0.246 1.387q0 0.375 0.039 0.656t0.141 0.543 0.312 0.402 0.516 0.141q0.742 0 0.852-1.086zM11.695 7.875h1.609q-0.109 1.234-0.766 1.941t-1.672 0.707q-1.266 0-1.988-0.906t-0.723-2.469q0-1.516 0.727-2.434t1.82-0.918q1.156 0 1.812 0.68t0.758 1.93h-1.594q-0.031-0.5-0.273-0.773t-0.633-0.273q-0.445 0-0.691 0.473t-0.246 1.387q0 0.375 0.039 0.656t0.141 0.543 0.309 0.402 0.512 0.141q0.383 0 0.598-0.297t0.262-0.789zM14.5 6.945q0-1.617-0.121-2.398t-0.473-1.258q-0.047-0.062-0.105-0.109t-0.168-0.117-0.125-0.086q-0.672-0.492-5.445-0.492-4.883 0-5.547 0.492-0.039 0.031-0.137 0.090t-0.164 0.109-0.113 0.113q-0.352 0.469-0.469 1.246t-0.117 2.41q0 1.625 0.117 2.402t0.469 1.254q0.047 0.062 0.117 0.117t0.16 0.109 0.137 0.094q0.344 0.258 1.871 0.383t3.676 0.125q4.766 0 5.445-0.508 0.039-0.031 0.133-0.086t0.16-0.109 0.105-0.125q0.359-0.469 0.477-1.242t0.117-2.414zM16 1v12h-16v-12h16z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12" height="14" viewBox="0 0 12 14">
<path fill="#f2f2f2" d="M6 7.5v3.5q0 0.203-0.148 0.352t-0.352 0.148-0.352-0.148l-1.125-1.125-2.594 2.594q-0.078 0.078-0.18 0.078t-0.18-0.078l-0.891-0.891q-0.078-0.078-0.078-0.18t0.078-0.18l2.594-2.594-1.125-1.125q-0.148-0.148-0.148-0.352t0.148-0.352 0.352-0.148h3.5q0.203 0 0.352 0.148t0.148 0.352zM11.898 2.25q0 0.102-0.078 0.18l-2.594 2.594 1.125 1.125q0.148 0.148 0.148 0.352t-0.148 0.352-0.352 0.148h-3.5q-0.203 0-0.352-0.148t-0.148-0.352v-3.5q0-0.203 0.148-0.352t0.352-0.148 0.352 0.148l1.125 1.125 2.594-2.594q0.078-0.078 0.18-0.078t0.18 0.078l0.891 0.891q0.078 0.078 0.078 0.18z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14" height="14" viewBox="0 0 14 14">
<path fill="#f2f2f2" d="M3 9.25v0.5q0 0.102-0.074 0.176t-0.176 0.074h-0.5q-0.102 0-0.176-0.074t-0.074-0.176v-0.5q0-0.102 0.074-0.176t0.176-0.074h0.5q0.102 0 0.176 0.074t0.074 0.176zM3 7.25v0.5q0 0.102-0.074 0.176t-0.176 0.074h-0.5q-0.102 0-0.176-0.074t-0.074-0.176v-0.5q0-0.102 0.074-0.176t0.176-0.074h0.5q0.102 0 0.176 0.074t0.074 0.176zM3 5.25v0.5q0 0.102-0.074 0.176t-0.176 0.074h-0.5q-0.102 0-0.176-0.074t-0.074-0.176v-0.5q0-0.102 0.074-0.176t0.176-0.074h0.5q0.102 0 0.176 0.074t0.074 0.176zM12 9.25v0.5q0 0.102-0.074 0.176t-0.176 0.074h-7.5q-0.102 0-0.176-0.074t-0.074-0.176v-0.5q0-0.102 0.074-0.176t0.176-0.074h7.5q0.102 0 0.176 0.074t0.074 0.176zM12 7.25v0.5q0 0.102-0.074 0.176t-0.176 0.074h-7.5q-0.102 0-0.176-0.074t-0.074-0.176v-0.5q0-0.102 0.074-0.176t0.176-0.074h7.5q0.102 0 0.176 0.074t0.074 0.176zM12 5.25v0.5q0 0.102-0.074 0.176t-0.176 0.074h-7.5q-0.102 0-0.176-0.074t-0.074-0.176v-0.5q0-0.102 0.074-0.176t0.176-0.074h7.5q0.102 0 0.176 0.074t0.074 0.176zM13 10.75v-6.5q0-0.102-0.074-0.176t-0.176-0.074h-11.5q-0.102 0-0.176 0.074t-0.074 0.176v6.5q0 0.102 0.074 0.176t0.176 0.074h11.5q0.102 0 0.176-0.074t0.074-0.176zM14 2.25v8.5q0 0.516-0.367 0.883t-0.883 0.367h-11.5q-0.516 0-0.883-0.367t-0.367-0.883v-8.5q0-0.516 0.367-0.883t0.883-0.367h11.5q0.516 0 0.883 0.367t0.367 0.883z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12" height="14" viewBox="0 0 12 14">
<path fill="#f2f2f2" d="M12 1.5v11q0 0.203-0.148 0.352t-0.352 0.148h-4q-0.203 0-0.352-0.148t-0.148-0.352v-11q0-0.203 0.148-0.352t0.352-0.148h4q0.203 0 0.352 0.148t0.148 0.352zM5 1.5v11q0 0.203-0.148 0.352t-0.352 0.148h-4q-0.203 0-0.352-0.148t-0.148-0.352v-11q0-0.203 0.148-0.352t0.352-0.148h4q0.203 0 0.352 0.148t0.148 0.352z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="11" height="14" viewBox="0 0 11 14">
<path fill="#f2f2f2" d="M10.812 7.242l-10.375 5.766q-0.18 0.102-0.309 0.023t-0.129-0.281v-11.5q0-0.203 0.129-0.281t0.309 0.023l10.375 5.766q0.18 0.102 0.18 0.242t-0.18 0.242z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="13" height="14" viewBox="0 0 13 14">
<path fill="#f2f2f2" d="M6 7.5v3q0 0.625-0.438 1.062t-1.062 0.438h-3q-0.625 0-1.062-0.438t-0.438-1.062v-5.5q0-0.813 0.316-1.551t0.855-1.277 1.277-0.855 1.551-0.316h0.5q0.203 0 0.352 0.148t0.148 0.352v1q0 0.203-0.148 0.352t-0.352 0.148h-0.5q-0.828 0-1.414 0.586t-0.586 1.414v0.25q0 0.312 0.219 0.531t0.531 0.219h1.75q0.625 0 1.062 0.438t0.438 1.062zM13 7.5v3q0 0.625-0.438 1.062t-1.062 0.438h-3q-0.625 0-1.062-0.438t-0.438-1.062v-5.5q0-0.813 0.316-1.551t0.855-1.277 1.277-0.855 1.551-0.316h0.5q0.203 0 0.352 0.148t0.148 0.352v1q0 0.203-0.148 0.352t-0.352 0.148h-0.5q-0.828 0-1.414 0.586t-0.586 1.414v0.25q0 0.312 0.219 0.531t0.531 0.219h1.75q0.625 0 1.062 0.438t0.438 1.062z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="8" height="14" viewBox="0 0 8 14">
<path fill="#f2f2f2" d="M0.352 12.898q-0.148 0.148-0.25 0.102t-0.102-0.25v-11.5q0-0.203 0.102-0.25t0.25 0.102l5.547 5.547q0.062 0.062 0.102 0.148v-5.297q0-0.203 0.148-0.352t0.352-0.148h1q0.203 0 0.352 0.148t0.148 0.352v11q0 0.203-0.148 0.352t-0.352 0.148h-1q-0.203 0-0.352-0.148t-0.148-0.352v-5.297q-0.039 0.078-0.102 0.148z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="9" height="14" viewBox="0 0 9 14">
<path fill="#f2f2f2" d="M6 2.75v8.5q0 0.203-0.148 0.352t-0.352 0.148-0.352-0.148l-2.602-2.602h-2.047q-0.203 0-0.352-0.148t-0.148-0.352v-3q0-0.203 0.148-0.352t0.352-0.148h2.047l2.602-2.602q0.148-0.148 0.352-0.148t0.352 0.148 0.148 0.352zM9 7q0 0.594-0.332 1.105t-0.879 0.73q-0.078 0.039-0.195 0.039-0.203 0-0.352-0.145t-0.148-0.355q0-0.164 0.094-0.277t0.227-0.195 0.266-0.18 0.227-0.277 0.094-0.445-0.094-0.445-0.227-0.277-0.266-0.18-0.227-0.195-0.094-0.277q0-0.211 0.148-0.355t0.352-0.145q0.117 0 0.195 0.039 0.547 0.211 0.879 0.727t0.332 1.109z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="6" height="14" viewBox="0 0 6 14">
<path fill="#f2f2f2" d="M6 2.75v8.5q0 0.203-0.148 0.352t-0.352 0.148-0.352-0.148l-2.602-2.602h-2.047q-0.203 0-0.352-0.148t-0.148-0.352v-3q0-0.203 0.148-0.352t0.352-0.148h2.047l2.602-2.602q0.148-0.148 0.352-0.148t0.352 0.148 0.148 0.352z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="13" height="14" viewBox="0 0 13 14">
<path fill="#f2f2f2" d="M6 2.75v8.5q0 0.203-0.148 0.352t-0.352 0.148-0.352-0.148l-2.602-2.602h-2.047q-0.203 0-0.352-0.148t-0.148-0.352v-3q0-0.203 0.148-0.352t0.352-0.148h2.047l2.602-2.602q0.148-0.148 0.352-0.148t0.352 0.148 0.148 0.352zM9 7q0 0.594-0.332 1.105t-0.879 0.73q-0.078 0.039-0.195 0.039-0.203 0-0.352-0.145t-0.148-0.355q0-0.164 0.094-0.277t0.227-0.195 0.266-0.18 0.227-0.277 0.094-0.445-0.094-0.445-0.227-0.277-0.266-0.18-0.227-0.195-0.094-0.277q0-0.211 0.148-0.355t0.352-0.145q0.117 0 0.195 0.039 0.547 0.211 0.879 0.727t0.332 1.109zM11 7q0 1.195-0.664 2.207t-1.758 1.473q-0.102 0.039-0.195 0.039-0.211 0-0.359-0.148t-0.148-0.352q0-0.305 0.305-0.461 0.438-0.227 0.594-0.344 0.578-0.422 0.902-1.059t0.324-1.355-0.324-1.355-0.902-1.059q-0.156-0.117-0.594-0.344-0.305-0.156-0.305-0.461 0-0.203 0.148-0.352t0.352-0.148q0.102 0 0.203 0.039 1.094 0.461 1.758 1.473t0.664 2.207zM13 7q0 1.797-0.992 3.301t-2.641 2.215q-0.102 0.039-0.203 0.039-0.203 0-0.352-0.148t-0.148-0.352q0-0.281 0.305-0.461 0.055-0.031 0.176-0.082t0.176-0.082q0.359-0.195 0.641-0.398 0.961-0.711 1.5-1.773t0.539-2.258-0.539-2.258-1.5-1.773q-0.281-0.203-0.641-0.398-0.055-0.031-0.176-0.082t-0.176-0.082q-0.305-0.18-0.305-0.461 0-0.203 0.148-0.352t0.352-0.148q0.102 0 0.203 0.039 1.648 0.711 2.641 2.215t0.992 3.301z"></path>
</svg>
/*! afontgarde - v0.1.6 - 2015-03-13
* https://github.com/filamentgroup/a-font-garde
* Copyright (c) 2015 Filament Group c/o Zach Leatherman
* MIT License */
.icon-fallback-text .icon {
display: none;
}
/*
ADDED BY afontgarde.js:
Note: sure .FONT_NAME comes first for adjoining classes bug in IE7.
.FONT_NAME.supports-generatedcontent .icon-fallback-text .icon {
display: inline-block;
}*/
.icon-fallback-img .text,
.icon-fallback-glyph .text/*,
ADDED BY afontgarde.js:
Note: sure .FONT_NAME comes first for adjoining classes bug in IE7.
.FONT_NAME.supports-generatedcontent .icon-fallback-text .text*/ {
/* visually hide but accessible (h5bp.com) */
clip: rect(0 0 0 0);
overflow: hidden;
position: absolute;
height: 1px;
width: 1px;
}
/* Careful, don’t use adjoining classes here (IE7) */
.supports-no-generatedcontent .icon-fallback-glyph .text,
.supports-no-generatedcontent .icon-fallback-img .text {
clip: auto;
overflow: visible;
position: static;
height: auto;
width: auto;
}
/*
ADDED BY afontgarde.js:
.FONT_NAME .icon-fallback-glyph .icon:before {
// inherit for font-size, line-height was not working on IE8
font-size: 1em;
font-size: inherit;
line-height: 1;
line-height: inherit;
}*/
.icon-fallback-img .icon {
display: inline-block;
}
// Necessary for xblocks and xmodules, but works across the board
html:not('.afontgarde') .icon-fallback-img .icon:before {
content: "";
}
/* The img fallback version is not as reliable since it does not check to make sure the fontloaded font has loaded. If we did add the .fontloaded class, it would unnecessarily request the fallback image. */
.fontawesome .icon-fallback-img .icon {
background-image: none;
}
\ No newline at end of file
/*! afontgarde - v0.1.6 - 2015-03-13
* https://github.com/filamentgroup/a-font-garde
* Copyright (c) 2015 Filament Group c/o Zach Leatherman
* MIT License */
/*! fontfaceonload - v0.1.6 - 2015-03-13
* https://github.com/zachleat/fontfaceonload
* Copyright (c) 2015 Zach Leatherman (@zachleat)
* MIT License */
;(function( win, doc ) {
"use strict";
var TEST_STRING = 'AxmTYklsjo190QW',
SANS_SERIF_FONTS = 'sans-serif',
SERIF_FONTS = 'serif',
// lighter and bolder not supported
weightLookup = {
normal: '400',
bold: '700'
},
defaultOptions = {
tolerance: 2, // px
delay: 100,
glyphs: '',
success: function() {},
error: function() {},
timeout: 5000,
weight: '400', // normal
style: 'normal'
},
// See https://github.com/typekit/webfontloader/blob/master/src/core/fontruler.js#L41
style = [
'display:block',
'position:absolute',
'top:-999px',
'left:-999px',
'font-size:48px',
'width:auto',
'height:auto',
'line-height:normal',
'margin:0',
'padding:0',
'font-variant:normal',
'white-space:nowrap'
],
html = '<div style="%s">' + TEST_STRING + '</div>';
var FontFaceOnloadInstance = function() {
this.fontFamily = '';
this.appended = false;
this.serif = undefined;
this.sansSerif = undefined;
this.parent = undefined;
this.options = {};
};
FontFaceOnloadInstance.prototype.getMeasurements = function () {
return {
sansSerif: {
width: this.sansSerif.offsetWidth,
height: this.sansSerif.offsetHeight
},
serif: {
width: this.serif.offsetWidth,
height: this.serif.offsetHeight
}
};
};
FontFaceOnloadInstance.prototype.load = function () {
var startTime = new Date(),
that = this,
serif = that.serif,
sansSerif = that.sansSerif,
parent = that.parent,
appended = that.appended,
dimensions,
options = this.options,
ref = options.reference;
function getStyle( family ) {
return style
.concat( [ 'font-weight:' + options.weight, 'font-style:' + options.style ] )
.concat( "font-family:" + family )
.join( ";" );
}
var sansSerifHtml = html.replace( /\%s/, getStyle( SANS_SERIF_FONTS ) ),
serifHtml = html.replace( /\%s/, getStyle( SERIF_FONTS ) );
if( !parent ) {
parent = that.parent = doc.createElement( "div" );
}
parent.innerHTML = sansSerifHtml + serifHtml;
sansSerif = that.sansSerif = parent.firstChild;
serif = that.serif = sansSerif.nextSibling;
if( options.glyphs ) {
sansSerif.innerHTML += options.glyphs;
serif.innerHTML += options.glyphs;
}
function hasNewDimensions( dims, el, tolerance ) {
return Math.abs( dims.width - el.offsetWidth ) > tolerance ||
Math.abs( dims.height - el.offsetHeight ) > tolerance;
}
function isTimeout() {
return ( new Date() ).getTime() - startTime.getTime() > options.timeout;
}
(function checkDimensions() {
if( !ref ) {
ref = doc.body;
}
if( !appended && ref ) {
ref.appendChild( parent );
appended = that.appended = true;
dimensions = that.getMeasurements();
// Make sure we set the new font-family after we take our initial dimensions:
// handles the case where FontFaceOnload is called after the font has already
// loaded.
sansSerif.style.fontFamily = that.fontFamily + ', ' + SANS_SERIF_FONTS;
serif.style.fontFamily = that.fontFamily + ', ' + SERIF_FONTS;
}
if( appended && dimensions &&
( hasNewDimensions( dimensions.sansSerif, sansSerif, options.tolerance ) ||
hasNewDimensions( dimensions.serif, serif, options.tolerance ) ) ) {
options.success();
} else if( isTimeout() ) {
options.error();
} else {
if( !appended && "requestAnimationFrame" in window ) {
win.requestAnimationFrame( checkDimensions );
} else {
win.setTimeout( checkDimensions, options.delay );
}
}
})();
}; // end load()
FontFaceOnloadInstance.prototype.checkFontFaces = function( timeout ) {
var _t = this;
doc.fonts.forEach(function( font ) {
if( font.family.toLowerCase() === _t.fontFamily.toLowerCase() &&
( weightLookup[ font.weight ] || font.weight ) === ''+_t.options.weight &&
font.style === _t.options.style ) {
font.load().then(function() {
_t.options.success();
win.clearTimeout( timeout );
});
}
});
};
FontFaceOnloadInstance.prototype.init = function( fontFamily, options ) {
var timeout;
for( var j in defaultOptions ) {
if( !options.hasOwnProperty( j ) ) {
options[ j ] = defaultOptions[ j ];
}
}
this.options = options;
this.fontFamily = fontFamily;
// For some reason this was failing on afontgarde + icon fonts.
if( !options.glyphs && "fonts" in doc ) {
if( options.timeout ) {
timeout = win.setTimeout(function() {
options.error();
}, options.timeout );
}
this.checkFontFaces( timeout );
} else {
this.load();
}
};
var FontFaceOnload = function( fontFamily, options ) {
var instance = new FontFaceOnloadInstance();
instance.init(fontFamily, options);
return instance;
};
// intentional global
win.FontFaceOnload = FontFaceOnload;
})( this, this.document );
/*
* A Font Garde
*/
;(function( w ) {
var doc = w.document,
ref,
css = ['.FONT_NAME.supports-generatedcontent .icon-fallback-text .icon { display: inline-block; }',
'.FONT_NAME.supports-generatedcontent .icon-fallback-text .text { clip: rect(0 0 0 0); overflow: hidden; position: absolute; height: 1px; width: 1px; }',
'.FONT_NAME .icon-fallback-glyph .icon:before { font-size: 1em; font-size: inherit; line-height: 1; line-height: inherit; }'];
function addEvent( type, callback ) {
if( 'addEventListener' in w ) {
return w.addEventListener( type, callback, false );
} else if( 'attachEvent' in w ) {
return w.attachEvent( 'on' + type, callback );
}
}
// options can be a string of glyphs or an options object to pass into FontFaceOnload
AFontGarde = function( fontFamily, options ) {
var fontFamilyClassName = fontFamily.toLowerCase().replace( /\s/g, '' ),
executed = false;
function init() {
if( executed ) {
return;
}
executed = true;
if( typeof FontFaceOnload === 'undefined' ) {
throw 'FontFaceOnload is a prerequisite.';
}
if( !ref ) {
ref = doc.getElementsByTagName( 'script' )[ 0 ];
}
var style = doc.createElement( 'style' ),
cssContent = css.join( '\n' ).replace( /FONT_NAME/gi, fontFamilyClassName );
style.setAttribute( 'type', 'text/css' );
if( style.styleSheet ) {
style.styleSheet.cssText = cssContent;
} else {
style.appendChild( doc.createTextNode( cssContent ) );
}
ref.parentNode.insertBefore( style, ref );
var opts = {
timeout: 5000,
success: function() {
// If you’re using more than one icon font, change this classname (and in a-font-garde.css)
doc.documentElement.className += ' ' + fontFamilyClassName;
if( options && options.success ) {
options.success();
}
}
};
// These characters are a few of the glyphs from the font above */
if( typeof options === "string" ) {
opts.glyphs = options;
} else {
for( var j in options ) {
if( options.hasOwnProperty( j ) && j !== "success" ) {
opts[ j ] = options[ j ];
}
}
}
FontFaceOnload( fontFamily, opts );
}
// MIT credit: filamentgroup/shoestring
addEvent( "DOMContentLoaded", init );
addEvent( "readystatechange", init );
addEvent( "load", init );
if( doc.readyState === "complete" ){
init();
}
};
})( this );
\ No newline at end of file
AFontGarde('FontAwesome', {
glyphs: '&#61515;'
});
\ No newline at end of file
/* Modernizr 2.7.1 (Custom Build) | MIT & BSD
* Build: http://modernizr.com/download/#-fontface-generatedcontent-cssclasses-teststyles-cssclassprefix:supports!
*/
;window.Modernizr=function(a,b,c){function w(a){j.cssText=a}function x(a,b){return w(prefixes.join(a+";")+(b||""))}function y(a,b){return typeof a===b}function z(a,b){return!!~(""+a).indexOf(b)}function A(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:y(f,"function")?f.bind(d||b):f}return!1}var d="2.7.1",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l=":)",m={}.toString,n={},o={},p={},q=[],r=q.slice,s,t=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["&#173;",'<style id="s',h,'">',a,"</style>"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},u={}.hasOwnProperty,v;!y(u,"undefined")&&!y(u.call,"undefined")?v=function(a,b){return u.call(a,b)}:v=function(a,b){return b in a&&y(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=r.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(r.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(r.call(arguments)))};return e}),n.fontface=function(){var a;return t('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&g.indexOf(d.split(" ")[0])===0}),a},n.generatedcontent=function(){var a;return t(["#",h,"{font:0/0 a}#",h,':after{content:"',l,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a};for(var B in n)v(n,B)&&(s=B.toLowerCase(),e[s]=n[B](),q.push((e[s]?"":"no-")+s));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)v(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" supports-"+(b?"":"no-")+a),e[a]=b}return e},w(""),i=k=null,e._version=d,e.testStyles=t,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" supports-js supports-"+q.join(" supports-"):""),e}(this,this.document);
\ No newline at end of file
......@@ -14,7 +14,8 @@ import logging
log = logging.getLogger('VideoPage')
VIDEO_BUTTONS = {
'CC': '.hide-subtitles',
'transcript': '.lang',
'transcript_button': '.toggle-transcript',
'volume': '.volume',
'play': '.video_control.play',
'pause': '.video_control.pause',
......@@ -32,12 +33,12 @@ CSS_CLASS_NAMES = {
'captions': '.subtitles',
'captions_text': '.subtitles > li',
'error_message': '.video .video-player h3',
'video_container': 'div.video',
'video_container': '.video',
'video_sources': '.video-player video source',
'video_spinner': '.video-wrapper .spinner',
'video_xmodule': '.xmodule_VideoModule',
'video_init': '.is-initialized',
'video_time': 'div.vidtime',
'video_time': '.vidtime',
'video_display_name': '.vert h2',
'captions_lang_list': '.langs-list li',
'video_speed': '.speeds .value',
......@@ -45,8 +46,8 @@ CSS_CLASS_NAMES = {
}
VIDEO_MODES = {
'html5': 'div.video video',
'youtube': 'div.video iframe'
'html5': '.video video',
'youtube': '.video iframe'
}
VIDEO_MENUS = {
......@@ -99,7 +100,7 @@ class VideoPage(PageObject):
video_player_buttons.append('play')
for button in video_player_buttons:
self.wait_for_element_visibility(VIDEO_BUTTONS[button], '{} button is visible'.format(button.title()))
self.wait_for_element_visibility(VIDEO_BUTTONS[button], '{} button is visible'.format(button))
def _is_finished_loading():
"""
......@@ -126,7 +127,7 @@ class VideoPage(PageObject):
video_player_buttons = ['do_not_show_again', 'skip_bumper', 'volume']
for button in video_player_buttons:
self.wait_for_element_visibility(VIDEO_BUTTONS[button], '{} button is visible'.format(button.title()))
self.wait_for_element_visibility(VIDEO_BUTTONS[button], '{} button is visible'.format(button))
@property
def is_poster_shown(self):
......@@ -316,13 +317,13 @@ class VideoPage(PageObject):
states = {True: 'Shown', False: 'Hidden'}
state = states[captions_new_state]
# Make sure that the CC button is there
EmptyPromise(lambda: self.is_button_shown('CC'),
"CC button is shown").fulfill()
# Make sure that the transcript button is there
EmptyPromise(lambda: self.is_button_shown('transcript_button'),
"transcript button is shown").fulfill()
# toggle captions visibility state if needed
if self.is_captions_visible() != captions_new_state:
self.click_player_button('CC')
self.click_player_button('transcript_button')
# Verify that captions state is toggled/changed
EmptyPromise(lambda: self.is_captions_visible() == captions_new_state,
......@@ -371,7 +372,7 @@ class VideoPage(PageObject):
hover = ActionChains(self.browser).move_to_element(element_to_hover_over)
hover.perform()
speed_selector = self.get_element_selector('li[data-speed="{speed}"] a'.format(speed=speed))
speed_selector = self.get_element_selector('li[data-speed="{speed}"] .control'.format(speed=speed))
self.q(css=speed_selector).first.click()
def verify_speed_changed(self, expected_speed):
......@@ -548,8 +549,8 @@ class VideoPage(PageObject):
"""
self.wait_for_ajax()
# mouse over to CC button
cc_button_selector = self.get_element_selector(VIDEO_BUTTONS["CC"])
# mouse over to transcript button
cc_button_selector = self.get_element_selector(VIDEO_BUTTONS["transcript"])
element_to_hover_over = self.q(css=cc_button_selector).results[0]
ActionChains(self.browser).move_to_element(element_to_hover_over).perform()
......
......@@ -267,11 +267,11 @@ class CMSVideoTest(CMSVideoBaseTest):
"""
self._create_course_unit(subtitles=True)
self.video.click_player_button('CC')
self.video.click_player_button('transcript_button')
self.assertFalse(self.video.is_captions_visible())
self.video.click_player_button('CC')
self.video.click_player_button('transcript_button')
self.assertTrue(self.video.is_captions_visible())
......
......@@ -254,7 +254,7 @@ class YouTubeVideoTest(VideoBaseTest):
Then the "CC" button is hidden
"""
self.navigate_to_video()
self.assertFalse(self.video.is_button_shown('CC'))
self.assertFalse(self.video.is_button_shown('transcript_button'))
def test_fullscreen_video_alignment_with_transcript_hidden(self):
"""
......@@ -351,8 +351,8 @@ class YouTubeVideoTest(VideoBaseTest):
# check if video aligned correctly with enabled transcript
self.assertTrue(self.video.is_aligned(True))
# click video button "CC"
self.video.click_player_button('CC')
# click video button "transcript"
self.video.click_player_button('transcript_button')
# check if video aligned correctly without enabled transcript
self.assertTrue(self.video.is_aligned(False))
......@@ -459,7 +459,7 @@ class YouTubeVideoTest(VideoBaseTest):
self.assertTrue(self.video.is_video_rendered('html5'))
# check if caption button is visible
self.assertTrue(self.video.is_button_shown('CC'))
self.assertTrue(self.video.is_button_shown('transcript_button'))
self._verify_caption_text('Welcome to edX.')
def test_download_transcript_button_works_correctly(self):
......
......@@ -1273,6 +1273,9 @@ main_vendor_js = base_vendor_js + [
'js/vendor/jquery-ui.min.js',
'js/vendor/jquery.qtip.min.js',
'js/vendor/jquery.ba-bbq.min.js',
'js/vendor/afontgarde/modernizr.fontface-generatedcontent.js',
'js/vendor/afontgarde/afontgarde.js',
'js/vendor/afontgarde/edx-icons.js',
]
# Common files used by both RequireJS code and non-RequireJS code
......@@ -1376,6 +1379,7 @@ credit_web_view_js = [
PIPELINE_CSS = {
'style-vendor': {
'source_filenames': [
'js/vendor/afontgarde/afontgarde.css',
'css/vendor/font-awesome.css',
'css/vendor/jquery.qtip.min.css',
'css/vendor/responsive-carousel/responsive-carousel.css',
......
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