Skip to content

Commit

Permalink
Download and upload handout file on MongoDB
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexander Marenich authored and sendr committed Dec 7, 2016
1 parent 7a5c6d5 commit b47497e
Show file tree
Hide file tree
Showing 6 changed files with 431 additions and 8 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,5 @@ ENV/
# Bower components
bower_components/

#Pycharm
.idea/
34 changes: 34 additions & 0 deletions video_xblock/static/css/handout.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
.wrapper-comp-settings .file-uploader .xblock-edit-settings .upload-setting,
.wrapper-comp-settings .file-uploader .xblock-edit-settings .download-setting {
width: 47%;
}
.upload-success {
position: absolute;
}

.wrapper-downloads-custom {
margin: 0;
padding: 0;
background-color: #f0f3f5;
}

.video-download-button-custom {
display: inline-block;
vertical-align: top;
margin: 10px;
}

.video-download-button-custom a {
transition: all 0.25s ease-in-out 0s;
font-size: 14px;
line-height: 14px;
float: left;
border-radius: 3px;
background-color: #fff;
padding: 15px;
}

.video-download-button-custom a:hover {
background-color: #1aa1de;
color: #fff;
}
9 changes: 8 additions & 1 deletion video_xblock/static/html/student_view.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{# To avoid interferrence with other video players and JavaScript modules (e.g. RequireJS in Studio) we wrap video player's code inside an iframe. #}
{% load i18n %}
<h3 class="hd hd-2">{{display_name}}</h3>
<iframe
src="{{player_url}}#{{usage_id}}"
Expand All @@ -11,3 +11,10 @@ <h3 class="hd hd-2">{{display_name}}</h3>
height="600px"
>
</iframe>
<ul class="wrapper-downloads-custom">
{% if handout %}
<li class="video-handout video-download-button-custom">
<a href="{{ handout }}" class="download-handout" download="{{ handout_file_name }}">{% trans 'Download Handout' %}</a>
</li>
{% endif %}
</ul>
132 changes: 132 additions & 0 deletions video_xblock/static/html/studio_edit.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
{% load i18n %}
<div class="editor-with-buttons">
<div class="wrapper-comp-settings is-active editor-with-buttons" id="settings-tab">
<ul class="list-input settings-list">
{% for field in fields %}
<li
class="field comp-setting-entry metadata_entry {% if field.is_set %}is-set{% endif %}"
data-field-name="{{field.name}}"
data-default="{% if field.type == 'boolean' %}{{ field.default|yesno:'1,0' }}{% else %}{{ field.default|default_if_none:"" }}{% endif %}"
data-cast="{{field.type}}"
>
<div class='wrapper-comp-setting {% if field.type == "set" %}metadata-list-enum{% endif %} {% if field.type == "file_uploader" %}file-uploader{% endif %}'>
<label class="label setting-label" for="xb-field-edit-{{field.name}}">{{field.display_name}}</label>

{% if field.type == "boolean" %}
<select
class="field-data-control"
id="xb-field-edit-{{field.name}}"
>
<option value="1" {% if field.value %}selected{% endif %}>
True {% if field.default %}&nbsp;&nbsp;&nbsp;&nbsp;(Default){% endif %}
</option>
<option value="0" {% if not field.value %}selected{% endif %}>
False {% if not field.default %}&nbsp;&nbsp;&nbsp;&nbsp;(Default){% endif %}
</option>
</select>
{% elif field.has_values %}
<select
class="field-data-control"
id="xb-field-edit-{{field.name}}"
>
{% for option in field.values %}
<option value="{{option.value}}" {% if field.value == option.value %}selected{% endif %}>
{{option.display_name}} {% if option.value == field.default %}&nbsp;&nbsp;&nbsp;&nbsp;(Default){% endif %}
</option>
{% endfor %}
</select>
{% elif field.type == "string" or field.type == "datepicker" %}
<input
type="text"
class="field-data-control"
id="xb-field-edit-{{field.name}}"
value="{{field.value|default_if_none:""}}"
>
{% elif field.type == "integer" or field.type == "float" %}
<input
type="number"
class="field-data-control"
id="xb-field-edit-{{field.name}}"
{% if field.step %} step="{{field.step}}" {% elif field.type == "integer" %} step=1 {% endif %}
{% if field.max %} max="{{field.max}}" {% endif %}
{% if field.min %} min="{{field.min}}" {% endif %}
value="{{field.value|default_if_none:""}}"
>
{% elif field.type == "text" or field.type == "html" %}
<textarea class="field-data-control" data-field-name="{{field.name}}" id="xb-field-edit-{{field.name}}" rows=10 cols=70>{{field.value}}</textarea>
{% elif field.type == 'set' and field.has_list_values %}
{% comment %}
TODO: If len(list_values) is high, show an alternate editor
with a select box and a growing list of selected choices
{% endcomment %}
<div class="wrapper-list-settings">
<ul class="list-settings list-set">
{% for choice in field.list_values %}
<li class="list-settings-item">
<input
id="xb-field-edit-{{field.name}}-{{forloop.counter}}"
type="checkbox"
value="{{choice.value}}"
style="width:auto;min-width:auto;height:auto;float:left;margin-top:3px;"
{% if choice.value in field.value %}checked="checked"{% endif %}
>
<label for="xb-field-edit-{{field.name}}-{{forloop.counter}}" style="display:block;margin-left:1.1em;">
{{choice.display_name}}
</label>
</li>
{% empty %}
<li>{% trans "None Available" %}</li>
{% endfor %}
</ul>
</div>
{% elif field.type == 'generic' or field.type == 'list' or field.type == 'set' %}
{# Show a textarea so we can edit it as a JSON string #}
<textarea class="field-data-control" data-field-name="{{field.name}}" id="xb-field-edit-{{field.name}}" rows=5 cols=70>{{field.value}}</textarea>

<!-- Raccoongang addons -->
{% elif field.type == 'file_uploader' %}

<div class="wrapper-uploader-actions xblock-edit-settings">
<a href="#" class="upload-action upload-setting" data-change-field-name="{{field.name}}">{% trans "Upload" %}</a>
{% if field.value %}
<a href="/{{ field.value }}" target="_blank" class="download-action download-setting" download="{{ field.file_name}}">{% trans "Download" %}</a>
{% endif %}
</div>
<input type="hidden" class="field-data-control" data-field-name="{{field.name}}" id="metadata-file-uploader-{{ field.name }}" value="{{ field.value }}">
<!-- End of Raccoongang addons -->

{% else %}
Unsupported field type. This setting cannot be edited.
{% endif %}

{% if field.allow_reset %}
<button class="action setting-clear {% if field.is_set %}active{%else%}inactive{% endif %}" type="button" name="setting-clear" value="{% trans "Clear" %}" data-tooltip="{% trans "Clear" %}">
<i class="icon fa fa-undo"></i><span class="sr">{% trans "Clear Value" %}</span>
</button>
{% endif %}
</div>
{% if field.help %}
<span class="tip setting-help"> {{ field.help|safe }} </span>
{% endif %}
</li>
{% endfor %}
</ul>
<!-- Raccoongang addons -->
<form method="POST" action="/assets/{{ courseKey }}/" class="is-hidden" enctype="multipart/form-data">
<input class="input-file-uploader" name="file" type="file" data-change-field-name="">
</form>
<!-- End of Raccoongang addons -->
</div>

<div class="xblock-actions">
<ul>
<li class="action-item">
<a href="#" class="button action-primary save-button">{% trans "Save" %}</a>
</li>

<li class="action-item">
<a href="#" class="button cancel-button">{% trans "Cancel" %}</a>
</li>
</ul>
</div>
</div>
193 changes: 193 additions & 0 deletions video_xblock/static/js/studio_edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
function StudioEditableXBlock(runtime, element) {
"use strict";

var fields = [];
var tinyMceAvailable = (typeof $.fn.tinymce !== 'undefined'); // Studio includes a copy of tinyMCE and its jQuery plugin
var datepickerAvailable = (typeof $.fn.datepicker !== 'undefined'); // Studio includes datepicker jQuery plugin

$(element).find('.field-data-control').each(function() {
var $field = $(this);
var $wrapper = $field.closest('li');
var $resetButton = $wrapper.find('button.setting-clear');
var type = $wrapper.data('cast');
fields.push({
name: $wrapper.data('field-name'),
isSet: function() { return $wrapper.hasClass('is-set'); },
hasEditor: function() { return tinyMceAvailable && $field.tinymce(); },
val: function() {
var val = $field.val();
// Cast values to the appropriate type so that we send nice clean JSON over the wire:
if (type == 'boolean')
return (val == 'true' || val == '1');
if (type == "integer")
return parseInt(val, 10);
if (type == "float")
return parseFloat(val);
if (type == "generic" || type == "list" || type == "set") {
val = val.trim();
if (val === "")
val = null;
else
val = JSON.parse(val); // TODO: handle parse errors
}
return val;
},
removeEditor: function() {
$field.tinymce().remove();
}
});
var fieldChanged = function() {
// Field value has been modified:
$wrapper.addClass('is-set');
$resetButton.removeClass('inactive').addClass('active');
};
$field.bind("change input paste", fieldChanged);
$resetButton.click(function() {
$field.val($wrapper.attr('data-default')); // Use attr instead of data to force treating the default value as a string
$wrapper.removeClass('is-set');
$resetButton.removeClass('active').addClass('inactive');
});
if (type == 'html' && tinyMceAvailable) {
tinyMCE.baseURL = baseUrl + "/js/vendor/tinymce/js/tinymce";
$field.tinymce({
theme: 'modern',
skin: 'studio-tmce4',
height: '200px',
formats: { code: { inline: 'code' } },
codemirror: { path: "" + baseUrl + "/js/vendor" },
convert_urls: false,
plugins: "link codemirror",
menubar: false,
statusbar: false,
toolbar_items_size: 'small',
toolbar: "formatselect | styleselect | bold italic underline forecolor wrapAsCode | bullist numlist outdent indent blockquote | link unlink | code",
resize: "both",
setup : function(ed) {
ed.on('change', fieldChanged);
}
});
}

if (type == 'datepicker' && datepickerAvailable) {
$field.datepicker('destroy');
$field.datepicker({dateFormat: "m/d/yy"});
}
});

$(element).find('.wrapper-list-settings .list-set').each(function() {
var $optionList = $(this);
var $checkboxes = $(this).find('input');
var $wrapper = $optionList.closest('li');
var $resetButton = $wrapper.find('button.setting-clear');

fields.push({
name: $wrapper.data('field-name'),
isSet: function() { return $wrapper.hasClass('is-set'); },
hasEditor: function() { return false; },
val: function() {
var val = [];
$checkboxes.each(function() {
if ($(this).is(':checked')) {
val.push(JSON.parse($(this).val()));
}
});
return val;
}
});
var fieldChanged = function() {
// Field value has been modified:
$wrapper.addClass('is-set');
$resetButton.removeClass('inactive').addClass('active');
};
$checkboxes.bind("change input", fieldChanged);

$resetButton.click(function() {
var defaults = JSON.parse($wrapper.attr('data-default'));
$checkboxes.each(function() {
var val = JSON.parse($(this).val());
$(this).prop('checked', defaults.indexOf(val) > -1);
});
$wrapper.removeClass('is-set');
$resetButton.removeClass('active').addClass('inactive');
});
});

var studio_submit = function(data) {
var handlerUrl = runtime.handlerUrl(element, 'submit_studio_edits');
runtime.notify('save', {state: 'start', message: gettext("Saving")});
$.ajax({
type: "POST",
url: handlerUrl,
data: JSON.stringify(data),
dataType: "json",
global: false, // Disable Studio's error handling that conflicts with studio's notify('save') and notify('cancel') :-/
success: function(response) { runtime.notify('save', {state: 'end'}); }
}).fail(function(jqXHR) {
var message = gettext("This may be happening because of an error with our server or your internet connection. Try refreshing the page or making sure you are online.");
if (jqXHR.responseText) { // Is there a more specific error message we can show?
try {
message = JSON.parse(jqXHR.responseText).error;
if (typeof message === "object" && message.messages) {
// e.g. {"error": {"messages": [{"text": "Unknown user 'bob'!", "type": "error"}, ...]}} etc.
message = $.map(message.messages, function(msg) { return msg.text; }).join(", ");
}
} catch (error) { message = jqXHR.responseText.substr(0, 300); }
}
runtime.notify('error', {title: gettext("Unable to update settings"), message: message});
});
};

$('.save-button', element).bind('click', function(e) {
e.preventDefault();
var values = {};
var notSet = []; // List of field names that should be set to default values
for (var i in fields) {
var field = fields[i];
if (field.isSet()) {
values[field.name] = field.val();
} else {
notSet.push(field.name);
}
// Remove TinyMCE instances to make sure jQuery does not try to access stale instances
// when loading editor for another block:
if (field.hasEditor()) {
field.removeEditor();
}
}
studio_submit({values: values, defaults: notSet});
});

// Raccoongang addons

var $fileUploader = $('.input-file-uploader', element);

$fileUploader.on('change', function(e) {
var fieldName = $fileUploader.data('change-field-name');
$('form', element).ajaxSubmit({
success: function(response, statusText, xhr, form) {
$('input[data-field-name=' + fieldName + ']').val(response['asset']['id']).change();
}
})
});

$('.upload-setting', element).on('click', function(e) {
e.preventDefault();
var fieldName = $(e.currentTarget).data('change-field-name');
$fileUploader.attr('data-change-field-name', fieldName);
$fileUploader.click();
});
// End of Raccoongang addons

$(element).find('.cancel-button').bind('click', function(e) {
// Remove TinyMCE instances to make sure jQuery does not try to access stale instances
// when loading editor for another block:
for (var i in fields) {
var field = fields[i];
if (field.hasEditor()) {
field.removeEditor();
}
}
e.preventDefault();
runtime.notify('cancel', {});
});
}
Loading

0 comments on commit b47497e

Please sign in to comment.