Commit 763f0051 by muhammad-ammar Committed by Mushtaq Ali

video thumbnail ui

parent dca12d65
......@@ -210,6 +210,7 @@ class VideosHandlerTestCase(VideoUploadTestMixin, CourseTestCase):
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
self.assertRegexpMatches(response["Content-Type"], "^text/html(;.*)?$")
self.assertIn(_get_default_video_image_url(), response.content)
# Crude check for presence of data in returned HTML
for video in self.previous_uploads:
self.assertIn(video["edx_video_id"], response.content)
......@@ -586,12 +587,11 @@ class VideoImageTestCase(VideoUploadTestBase, CourseTestCase):
response = json.loads(response.content)
self.assertEqual(response['error'], 'No file provided for video image')
def test_default_video_image(self):
def test_no_video_image(self):
"""
Test default video image.
Test image url is set to None if no video image.
"""
edx_video_id = 'test1'
default_video_image_url = _get_default_video_image_url()
get_videos_url = reverse_course_url('videos_handler', self.course.id)
video_image_upload_url = self.get_url_for_course_key(self.course.id, {'edx_video_id': edx_video_id})
with make_image_file() as image_file:
......@@ -606,7 +606,7 @@ class VideoImageTestCase(VideoUploadTestBase, CourseTestCase):
if response_video['edx_video_id'] == edx_video_id:
self.assertEqual(response_video['course_video_image_url'], val_image_url)
else:
self.assertEqual(response_video['course_video_image_url'], default_video_image_url)
self.assertEqual(response_video['course_video_image_url'], None)
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_VIDEO_UPLOAD_PIPELINE": True})
......
......@@ -336,7 +336,6 @@ def _get_index_videos(course):
Returns the information about each video upload required for the video list
"""
course_id = unicode(course.id)
default_video_image_url = _get_default_video_image_url()
attrs = ['edx_video_id', 'client_video_id', 'created', 'duration', 'status', 'courses']
def _get_values(video):
......@@ -347,8 +346,7 @@ def _get_index_videos(course):
for attr in attrs:
if attr == 'courses':
course = filter(lambda c: course_id in c, video['courses'])
(__, image_url), = course[0].items()
values['course_video_image_url'] = image_url or default_video_image_url
(__, values['course_video_image_url']), = course[0].items()
else:
values[attr] = video[attr]
......@@ -370,6 +368,7 @@ def videos_index_html(course):
'image_upload_url': reverse_course_url('video_images_handler', unicode(course.id)),
'video_handler_url': reverse_course_url('videos_handler', unicode(course.id)),
'encodings_download_url': reverse_course_url('video_encodings_download', unicode(course.id)),
'default_video_image_url': _get_default_video_image_url(),
'previous_uploads': _get_index_videos(course),
'concurrent_upload_limit': settings.VIDEO_UPLOAD_PIPELINE.get('CONCURRENT_UPLOAD_LIMIT', 0),
'video_supported_file_formats': VIDEO_SUPPORTED_FILE_FORMATS.keys(),
......
......@@ -1349,4 +1349,4 @@ PROFILE_IMAGE_SIZES_MAP = {
###################### VIDEO IMAGE STORAGE ######################
VIDEO_IMAGE_DEFAULT_FILENAME = 'default_video_image.png'
VIDEO_IMAGE_DEFAULT_FILENAME = 'images/video-images/default_video_image.png'
......@@ -342,6 +342,6 @@ VIDEO_IMAGE_SETTINGS = dict(
location=MEDIA_ROOT,
base_url=MEDIA_URL,
),
DIRECTORY_PREFIX='videoimage/',
DIRECTORY_PREFIX='video-images/',
)
VIDEO_IMAGE_DEFAULT_FILENAME = 'default_video_image.png'
......@@ -258,6 +258,7 @@
'js/spec/utils/module_spec',
'js/spec/views/active_video_upload_list_spec',
'js/spec/views/previous_video_upload_spec',
'js/spec/views/video_thumbnail_spec',
'js/spec/views/previous_video_upload_list_spec',
'js/spec/views/assets_spec',
'js/spec/views/baseview_spec',
......
......@@ -5,8 +5,10 @@ define([
'use strict';
var VideosIndexFactory = function(
$contentWrapper,
videoImageUploadURL,
videoHandlerUrl,
encodingsDownloadUrl,
defaultVideoImageURL,
concurrentUploadLimit,
uploadButton,
previousUploads,
......@@ -34,6 +36,8 @@ define([
isActive[0].get('status') === ActiveVideoUpload.STATUS_COMPLETE;
}),
updatedView = new PreviousVideoUploadListView({
videoImageUploadURL: videoImageUploadURL,
defaultVideoImageURL: defaultVideoImageURL,
videoHandlerUrl: videoHandlerUrl,
collection: updatedCollection,
encodingsDownloadUrl: encodingsDownloadUrl
......@@ -43,6 +47,8 @@ define([
}
}),
previousView = new PreviousVideoUploadListView({
videoImageUploadURL: videoImageUploadURL,
defaultVideoImageURL: defaultVideoImageURL,
videoHandlerUrl: videoHandlerUrl,
collection: new Backbone.Collection(previousUploads),
encodingsDownloadUrl: encodingsDownloadUrl
......
......@@ -30,24 +30,6 @@ define(
expect($el.find('.name-col').text()).toEqual(testName);
});
_.each(
[
{desc: 'zero as pending', seconds: 0, expected: 'Pending'},
{desc: 'less than one second as zero', seconds: 0.75, expected: '0:00'},
{desc: 'with minutes and without seconds', seconds: 900, expected: '15:00'},
{desc: 'with seconds and without minutes', seconds: 15, expected: '0:15'},
{desc: 'with minutes and seconds', seconds: 915, expected: '15:15'},
{desc: 'with seconds padded', seconds: 5, expected: '0:05'},
{desc: 'longer than an hour as many minutes', seconds: 7425, expected: '123:45'}
],
function(caseInfo) {
it('should render duration ' + caseInfo.desc, function() {
var $el = render({duration: caseInfo.seconds});
expect($el.find('.duration-col').text()).toEqual(caseInfo.expected);
});
}
);
it('should render created timestamp correctly', function() {
var fakeDate = 'fake formatted date';
spyOn(Date.prototype, 'toLocaleString').and.callFake(
......
define(
['jquery', 'underscore', 'backbone', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
'js/views/video_thumbnail', 'common/js/spec_helpers/template_helpers'],
function($, _, Backbone, AjaxHelpers, VideoThumbnailView, TemplateHelpers) {
'use strict';
describe('VideoThumbnailView', function() {
var IMAGE_UPLOAD_URL = '/videos/upload/image',
UPLOADED_IMAGE_URL = 'images/upload_success.jpg',
videoThumbnailView,
createFakeImageFile,
verifyStateInfo,
render = function(modelData) {
var defaultData = {
client_video_id: 'foo.mp4',
duration: 42,
created: '2014-11-25T23:13:05',
edx_video_id: 'dummy_id',
status: 'uploading',
thumbnail_url: null
};
videoThumbnailView = new VideoThumbnailView({
model: new Backbone.Model($.extend({}, defaultData, modelData)),
imageUploadURL: IMAGE_UPLOAD_URL
});
return videoThumbnailView.render().$el;
};
createFakeImageFile = function(size) {
var fileFakeData = 'i63ljc6giwoskyb9x5sw0169bdcmcxr3cdz8boqv0lik971972cmd6yknvcxr5sw0nvc169bdcmcxsdf';
return new Blob(
[fileFakeData.substr(0, size)],
{type: 'image/jpg'}
);
};
verifyStateInfo = function($thumbnail, state, onHover, additionalSRText) {
var beforeIcon,
beforeText;
// Verify hover message, save the text before hover to verify later
if (onHover) {
beforeIcon = $thumbnail.find('.action-icon').html().trim();
beforeText = $thumbnail.find('.action-text').html().trim();
$thumbnail.trigger('mouseover');
}
if (additionalSRText) {
expect(
$thumbnail.find('.thumbnail-action .action-text-sr').text().trim()
).toEqual(additionalSRText);
}
expect($thumbnail.find('.action-icon').html().trim()).toEqual(
videoThumbnailView.actionsInfo[state].icon
);
expect($thumbnail.find('.action-text').html().trim()).toEqual(
videoThumbnailView.actionsInfo[state].text
);
// Verify if messages are restored after focus moved away
if (onHover) {
$thumbnail.trigger('mouseout');
expect($thumbnail.find('.action-icon').html().trim()).toEqual(beforeIcon);
expect($thumbnail.find('.action-text').html().trim()).toEqual(beforeText);
}
};
beforeEach(function() {
setFixtures('<div id="page-prompt"></div><div id="page-notification"></div>');
TemplateHelpers.installTemplate('video-thumbnail');
});
it('renders as expected', function() {
var $el = render({});
expect($el.find('.thumbnail-wrapper')).toExist();
expect($el.find('.upload-image-input')).toExist();
});
it('does not show duration if not available', function() {
var $el = render({duration: 0});
expect($el.find('.thumbnail-wrapper .video-duration')).not.toExist();
});
it('shows the duration if available', function() {
var $el = render({}),
$duration = $el.find('.thumbnail-wrapper .video-duration');
expect($duration).toExist();
expect($duration.find('.duration-text-machine').text().trim()).toEqual('0:42');
expect($duration.find('.duration-text-human').text().trim()).toEqual('Video duration is 42 seconds');
});
it('calculates duration correctly', function() {
var durations = [
{duration: -1},
{duration: 0},
{duration: 0.75, machine: '0:00', humanize: ''},
{duration: 5, machine: '0:05', humanize: 'Video duration is 5 seconds'},
{duration: 103, machine: '1:43', humanize: 'Video duration is 1 minute and 43 seconds'},
{duration: 120, machine: '2:00', humanize: 'Video duration is 2 minutes'},
{duration: 500, machine: '8:20', humanize: 'Video duration is 8 minutes and 20 seconds'},
{duration: 7425, machine: '123:45', humanize: 'Video duration is 123 minutes and 45 seconds'}
],
expectedDuration;
durations.forEach(function(item) {
expectedDuration = videoThumbnailView.getDuration(item.duration);
if (item.duration <= 0) {
expect(expectedDuration).toEqual(null);
} else {
expect(expectedDuration.machine).toEqual(item.machine);
expect(expectedDuration.humanize).toEqual(item.humanize);
}
});
});
it('can upload image', function() {
var $el = render({}),
$thumbnail = $el.find('.thumbnail-wrapper'),
requests = AjaxHelpers.requests(this),
additionalSRText = videoThumbnailView.getSRText();
videoThumbnailView.chooseFile();
verifyStateInfo($thumbnail, 'upload');
verifyStateInfo($thumbnail, 'requirements', true, additionalSRText);
// Add image to upload queue and send POST request to upload image
$el.find('.upload-image-input').fileupload('add', {files: [createFakeImageFile(60)]});
verifyStateInfo($thumbnail, 'progress');
// Verify if POST request received for image upload
AjaxHelpers.expectRequest(requests, 'POST', IMAGE_UPLOAD_URL + '/dummy_id', new FormData());
// Send successful upload response
AjaxHelpers.respondWithJson(requests, {image_url: UPLOADED_IMAGE_URL});
verifyStateInfo($thumbnail, 'edit', true);
// Verify uploaded image src
expect($thumbnail.find('img').attr('src')).toEqual(UPLOADED_IMAGE_URL);
});
it('shows error state correctly', function() {
var $el = render({}),
$thumbnail = $el.find('.thumbnail-wrapper'),
requests = AjaxHelpers.requests(this);
videoThumbnailView.chooseFile();
// Add image to upload queue and send POST request to upload image
$el.find('.upload-image-input').fileupload('add', {files: [createFakeImageFile(60)]});
AjaxHelpers.respondWithError(requests, 400);
verifyStateInfo($thumbnail, 'error');
});
it('should show error notification in case of server error', function() {
var $el = render({}),
requests = AjaxHelpers.requests(this);
videoThumbnailView.chooseFile();
// Add image to upload queue and send POST request to upload image
$el.find('.upload-image-input').fileupload('add', {files: [createFakeImageFile(60)]});
AjaxHelpers.respondWithError(requests);
expect($('#notification-error-title').text().trim()).toEqual(
"Studio's having trouble saving your work"
);
});
it('calls readMessage with correct message', function() {
spyOn(videoThumbnailView, 'readMessages');
videoThumbnailView.imageSelected({}, {submit: function() {}});
expect(videoThumbnailView.readMessages).toHaveBeenCalledWith(['Video image upload started']);
videoThumbnailView.imageUploadSucceeded({}, {result: {image_url: UPLOADED_IMAGE_URL}});
expect(videoThumbnailView.readMessages).toHaveBeenCalledWith(['Video image upload completed']);
videoThumbnailView.imageUploadFailed();
expect(videoThumbnailView.readMessages).toHaveBeenCalledWith(['Video image upload failed']);
});
});
}
);
define(
['underscore', 'gettext', 'js/utils/date_utils', 'js/views/baseview', 'common/js/components/views/feedback_prompt',
'common/js/components/views/feedback_notification', 'common/js/components/utils/view_utils',
'edx-ui-toolkit/js/utils/html-utils', 'text!templates/previous-video-upload.underscore'],
function(_, gettext, DateUtils, BaseView, PromptView, NotificationView, ViewUtils, HtmlUtils,
'common/js/components/views/feedback_notification', 'js/views/video_thumbnail',
'common/js/components/utils/view_utils', 'edx-ui-toolkit/js/utils/html-utils',
'text!templates/previous-video-upload.underscore'],
function(_, gettext, DateUtils, BaseView, PromptView, NotificationView, VideoThumbnailView, ViewUtils, HtmlUtils,
previousVideoUploadTemplate) {
'use strict';
......@@ -16,22 +17,15 @@ define(
initialize: function(options) {
this.template = HtmlUtils.template(previousVideoUploadTemplate);
this.videoHandlerUrl = options.videoHandlerUrl;
},
renderDuration: function(seconds) {
var minutes = Math.floor(seconds / 60);
var seconds = Math.floor(seconds - minutes * 60);
return minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
this.videoThumbnailView = new VideoThumbnailView({
model: this.model,
imageUploadURL: options.videoImageUploadURL,
defaultVideoImageURL: options.defaultVideoImageURL
});
},
render: function() {
var duration = this.model.get('duration');
var renderedAttributes = {
// Translators: This is listed as the duration for a video
// that has not yet reached the point in its processing by
// the servers where its duration is determined.
duration: duration > 0 ? this.renderDuration(duration) : gettext('Pending'),
created: DateUtils.renderDate(this.model.get('created')),
status: this.model.get('status')
};
......@@ -41,6 +35,7 @@ define(
_.extend({}, this.model.attributes, renderedAttributes)
)
);
this.videoThumbnailView.setElement(this.$('.thumbnail-col')).render();
return this;
},
......
......@@ -11,6 +11,8 @@ define(
this.encodingsDownloadUrl = options.encodingsDownloadUrl;
this.itemViews = this.collection.map(function(model) {
return new PreviousVideoUploadView({
videoImageUploadURL: options.videoImageUploadURL,
defaultVideoImageURL: options.defaultVideoImageURL,
videoHandlerUrl: options.videoHandlerUrl,
model: model
});
......
define(
['underscore', 'gettext', 'moment', 'js/utils/date_utils', 'js/views/baseview',
'common/js/components/utils/view_utils', 'edx-ui-toolkit/js/utils/html-utils',
'edx-ui-toolkit/js/utils/string-utils', 'text!templates/video-thumbnail.underscore'],
function(_, gettext, moment, DateUtils, BaseView, ViewUtils, HtmlUtils, StringUtils, VideoThumbnailTemplate) {
'use strict';
var VideoThumbnailView = BaseView.extend({
actionsInfo: {
upload: {
icon: '',
text: gettext('Add Thumbnail')
},
edit: {
icon: '<span class="icon fa fa-pencil" aria-hidden="true"></span>',
text: gettext('Edit Thumbnail')
},
error: {
icon: '<span class="icon fa fa-exclamation-triangle" aria-hidden="true"></span>',
text: gettext('Image upload failed')
},
progress: {
icon: '<span class="icon fa fa-spinner fa-pulse fa-spin" aria-hidden="true"></span>',
text: gettext('Uploading')
},
requirements: {
icon: '',
text: HtmlUtils.interpolateHtml(
// Translators: This is a 3 part text which tells the image requirements.
gettext('Image requirements {lineBreak} 1280px by 720px {lineBreak} .jpg | .png | .gif'),
{
lineBreak: HtmlUtils.HTML('<br>')
}
).toString()
}
},
events: {
'click .thumbnail-wrapper': 'chooseFile',
'mouseover .thumbnail-wrapper': 'showHoverState',
'mouseout .thumbnail-wrapper': 'hideHoverState',
'focus .thumbnail-wrapper': 'showHoverState',
'blur .thumbnail-wrapper': 'hideHoverState'
},
initialize: function(options) {
this.template = HtmlUtils.template(VideoThumbnailTemplate);
this.imageUploadURL = options.imageUploadURL;
this.defaultVideoImageURL = options.defaultVideoImageURL;
this.action = this.model.get('course_video_image_url') ? 'edit' : 'upload';
_.bindAll(
this, 'render', 'chooseFile', 'imageSelected', 'imageUploadSucceeded', 'imageUploadFailed',
'showHoverState', 'hideHoverState'
);
},
render: function() {
HtmlUtils.setHtml(
this.$el,
this.template({
action: this.action,
imageAltText: this.getImageAltText(),
videoId: this.model.get('edx_video_id'),
actionInfo: this.actionsInfo[this.action],
thumbnailURL: this.model.get('course_video_image_url') || this.defaultVideoImageURL,
duration: this.getDuration(this.model.get('duration'))
})
);
this.hideHoverState();
return this;
},
getImageAltText: function() {
return StringUtils.interpolate(
// Translators: message will be like Thumbnail for Arrow.mp4
gettext('Thumbnail for {videoName}'),
{videoName: this.model.get('client_video_id')}
);
},
getSRText: function() {
return StringUtils.interpolate(
// Translators: message will be like Add Thumbnail - Arrow.mp4
gettext('Add Thumbnail - {videoName}'),
{videoName: this.model.get('client_video_id')}
);
},
getDuration: function(durationSeconds) {
if (durationSeconds <= 0) {
return null;
}
return {
humanize: this.getDurationTextHuman(durationSeconds),
machine: this.getDurationTextMachine(durationSeconds)
};
},
getDurationTextHuman: function(durationSeconds) {
var humanize = this.getHumanizeDuration(durationSeconds);
// This case is specifically to handle values between 0 and 1 seconds excluding upper bound
if (humanize.length === 0) {
return '';
}
return StringUtils.interpolate(
// Translators: humanizeDuration will be like 10 minutes, an hour and 20 minutes etc
gettext('Video duration is {humanizeDuration}'),
{
humanizeDuration: humanize
}
);
},
getHumanizeDuration: function(durationSeconds) {
var minutes,
seconds,
minutesText = null,
secondsText = null;
minutes = Math.trunc(moment.duration(durationSeconds, 'seconds').asMinutes());
seconds = moment.duration(durationSeconds, 'seconds').seconds();
if (minutes) {
minutesText = minutes > 1 ? gettext('minutes') : gettext('minute');
minutesText = StringUtils.interpolate(
// Translators: message will be like 15 minutes, 1 minute
gettext('{minutes} {unit}'),
{minutes: minutes, unit: minutesText}
);
}
if (seconds) {
secondsText = seconds > 1 ? gettext('seconds') : gettext('second');
secondsText = StringUtils.interpolate(
// Translators: message will be like 20 seconds, 1 second
gettext('{seconds} {unit}'),
{seconds: seconds, unit: secondsText}
);
}
// Translators: `and` will be used to combine both miuntes and seconds like `13 minutes and 45 seconds`
return _.filter([minutesText, secondsText]).join(gettext(' and '));
},
getDurationTextMachine: function(durationSeconds) {
var minutes = Math.floor(durationSeconds / 60),
seconds = Math.floor(durationSeconds - minutes * 60);
return minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
},
chooseFile: function() {
this.$('.upload-image-input').fileupload({
url: this.imageUploadURL + '/' + encodeURIComponent(this.model.get('edx_video_id')),
add: this.imageSelected,
done: this.imageUploadSucceeded,
fail: this.imageUploadFailed
});
},
imageSelected: function(event, data) {
this.readMessages([gettext('Video image upload started')]);
this.showUploadInProgressMessage();
data.submit();
},
imageUploadSucceeded: function(event, data) {
this.action = 'edit';
this.setActionInfo(this.action, false);
this.$('img').attr('src', data.result.image_url);
this.readMessages([gettext('Video image upload completed')]);
},
imageUploadFailed: function() {
this.action = 'error';
this.setActionInfo(this.action, true);
this.readMessages([gettext('Video image upload failed')]);
},
showUploadInProgressMessage: function() {
this.action = 'progress';
this.setActionInfo(this.action, true);
},
showHoverState: function() {
if (this.action === 'upload') {
this.setActionInfo('requirements', true, this.getSRText());
} else if (this.action === 'edit') {
this.setActionInfo(this.action, true);
}
this.$('.thumbnail-wrapper').addClass('focused');
},
hideHoverState: function() {
if (this.action === 'upload') {
this.setActionInfo(this.action, true);
} else if (this.action === 'edit') {
this.setActionInfo(this.action, false);
}
},
setActionInfo: function(action, showText, additionalSRText) {
this.$('.thumbnail-action').toggle(showText);
HtmlUtils.setHtml(
this.$('.thumbnail-action .action-icon'),
HtmlUtils.HTML(this.actionsInfo[action].icon)
);
this.$('.thumbnail-action .action-text').html(this.actionsInfo[action].text);
this.$('.thumbnail-action .action-text-sr').text(additionalSRText || '');
this.$('.thumbnail-wrapper').attr('class', 'thumbnail-wrapper {action}'.replace('{action}', action));
},
readMessages: function(messages) {
if ($(window).prop('SR') !== undefined) {
$(window).prop('SR').readTexts(messages);
}
}
});
return VideoThumbnailView;
}
);
......@@ -163,4 +163,120 @@
@extend %actions-list;
}
}
$thumbnail-width: ($baseline*7.5);
$thumbnail-height: ($baseline*5);
.thumbnail-wrapper {
position: relative;
max-width: $thumbnail-width;
max-height: $thumbnail-height;
img {
width: $thumbnail-width;
height: $thumbnail-height;
}
* {
cursor: pointer;
}
&.upload,
&.requirements {
border: 1px dashed $gray-l3;
}
&.requirements {
.video-duration {
opacity: 0;
}
}
&.edit {
background: black;
&:hover,
&:focus,
&.focused {
img, .video-duration {
@include transition(all 0.3s linear);
opacity: 0.5;
}
}
}
&.error,
&.progress {
background: white;
img {
@include transition(all 0.5s linear);
opacity: 0.15;
}
.action-icon {
display: block;
}
}
&.error .thumbnail-action {
color: $red;
}
&.upload .thumbnail-action {
color: $blue;
}
&.progress .thumbnail-action {
.action-icon {
@include font-size(20);
}
}
&.edit .thumbnail-action {
background-color: white;
padding: ($baseline/4);
border-radius: ($baseline/5);
}
.thumbnail-action {
@include font-size(14);
}
.thumbnail-overlay > :not(.upload-image-input) {
position: absolute;
text-align: center;
top: 50%;
left: 5px;;
right: 5px;
@include transform(translateY(-50%));
}
.upload-image-input {
position: absolute;
top: 0;
left: 0;
right: 0;
opacity: 0;
z-index: 6;
width: $thumbnail-width;
height: $thumbnail-height;
}
.video-duration {
position: absolute;
text-align: center;
bottom: 1px;
@include right(1px);
width: auto;
min-width: 25%;
color: white;
padding: ($baseline/10) ($baseline/5);
background-color: black;
}
&.focused {
box-shadow: 0 0 ($baseline/5) 1px $blue;
}
}
}
......@@ -8,12 +8,12 @@
<table class="assets-table">
<thead>
<tr>
<th><%- gettext("Name") %></th>
<th><%- gettext("Duration") %></th>
<th><%- gettext("Date Added") %></th>
<th><%- gettext("Video ID") %></th>
<th><%- gettext("Status") %></th>
<th><%- gettext("Action") %></th>
<th scope="col"><%- gettext("Thumbnail") %></th>
<th scope="col"><%- gettext("Name") %></th>
<th scope="col"><%- gettext("Date Added") %></th>
<th scope="col"><%- gettext("Video ID") %></th>
<th scope="col"><%- gettext("Status") %></th>
<th scope="col"><%- gettext("Action") %></th>
</tr>
</thead>
<tbody class="js-table-body"></tbody>
......
<td class="thumbnail-col"></td>
<td class="name-col"><%- client_video_id %></td>
<td class="duration-col"><%- duration %></td>
<td class="date-col"><%- created %></td>
<td class="video-id-col"><%- edx_video_id %></td>
<td class="status-col"><%- status %></td>
......
<div class="thumbnail-wrapper <%- action === 'upload' ? 'upload' : '' %>" tabindex="-1">
<img src="<%- thumbnailURL %>" alt="<%- imageAltText %>">
<div class="thumbnail-overlay">
<input id="thumb-<%- videoId %>" class="upload-image-input" type="file" name="file" accept=".bmp, .jpg, .jpeg, .png, .gif"/>
<label for="thumb-<%- videoId %>" class="thumbnail-action">
<span class="action-icon" aria-hidden="true"><%- actionInfo.icon %></span>
<span class="action-text-sr sr"></span>
<span class="action-text"><%- actionInfo.text %></span>
</label>
<span class="requirements-text-sr sr">
<%- gettext('Recommended image resolution is 1280x720 pixels and format must be one of .jpg, .png or .gif') %>
</span>
</div>
<% if(duration) { %>
<div class="video-duration">
<span class="duration-text-human sr"><%- duration.humanize %></span>
<span class="duration-text-machine" aria-hidden="true"><%- duration.machine %></span>
</div>
<% } %>
</div>
......@@ -29,8 +29,10 @@
var $contentWrapper = $(".content-primary");
VideosIndexFactory(
$contentWrapper,
"${image_upload_url | n, js_escaped_string}",
"${video_handler_url | n, js_escaped_string}",
"${encodings_download_url | n, js_escaped_string}",
"${default_video_image_url | n, js_escaped_string}",
${concurrent_upload_limit | n, dump_js_escaped_json},
$(".nav-actions .upload-button"),
$contentWrapper.data("previous-uploads"),
......
......@@ -2574,7 +2574,7 @@ VIDEO_IMAGE_SETTINGS = dict(
location=MEDIA_ROOT,
base_url=MEDIA_URL,
),
DIRECTORY_PREFIX='videoimage/',
DIRECTORY_PREFIX='video-images/',
)
......
......@@ -7,7 +7,6 @@
<input class="upload-button-input" type="file" name="<%= inputName %>"/>
</label>
<button class="upload-submit" type="button" hidden="true"><%= uploadButtonTitle %></button>
<button class="u-field-remove-button" type="button">
<span class="remove-button-icon" aria-hidden="true"><%= removeButtonIcon %></span>
<span class="remove-button-title" aria-live="polite"><%= removeButtonTitle %></span>
......
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