This commit is contained in:
vinhub 2016-04-21 15:02:30 -07:00
Коммит affc72a54a
10 изменённых файлов: 445 добавлений и 0 удалений

75
README.md Normal file
Просмотреть файл

@ -0,0 +1,75 @@
OfficeVideo XBlock
===================
The “OfficeVideo XBlock” allows course content authors to embed videos stored in Microsoft Office 365 Video to the course.
Students can view these videos in their Open edX or edX.org courses.
Installation
------------
To install the OfficeVideo XBlock within your edX Python environment, run the following command:
```bash
$ pip install /path/to/xblock-officevideo/
```
Ensure that you have added the following to /edx/app/edxapp/edx-platform/cms/envs/common.py
- ALLOW_ALL_ADVANCED_COMPONENTS: True
Also ensure that you have restarted edX services after these steps.
Enabling in Studio
------------------
To enable the OfficeVideo XBlock within studio:
1. Navigate to `Settings -> Advanced Settings` from the top nav bar.
2. Add `"officevideo"` to the Advanced Module List, as shown in the screen shot below.
![Advanced Module List](docs/img/officevideo_advanced.jpg)
Usage
-----
Once enabled in Studio, it's easy to use the OfficeVideo XBlock.
Start by navigating to the unit in studio where you want to insert your video. From here choose the `Advanced` component.
![Studio Component List](docs/img/component_list.png)
This will bring up a list of the XBlocks that have been enabled in Studio. If you followed the previous step to enable the OfficeVideo XBlock in Studio you will see an option titled `Office Video`. Click on it to insert the Office Video XBlock into your unit.
![Studio Advanced Component Selection](docs/img/officevideo_button.jpg)
After you've inserted the OfficeVideo XBlock, a default video will be inserted into your unit as shown in the screen shot below.
![Studio Initial OfficeVideo XBlock Insertion](docs/img/xblock_insert.png)
To change the video added to the course using the OfficeVideo XBlock, click on the `Edit` button on the upper-right corner of the XBlock. This will bring up the edit dialog where you can change the display name of the component as well as the video that is being inserted and how you want it to be embedded.
![Edit inserted document](docs/img/officevideo_edit.jpg)
In this dialog, you can
- Update the XBlock title.
- Enter the URL or embed code for the video from its original location (in OfficeVideo)
- Note that the video must be accessible to the intended audience.
- Also, note that you do not need to obtain an embed code for the video yourself. The OfficeVideo XBlock can build it automatically.
After you click save, your OfficeVideo XBlock will have been updated with the new values.
![Updated studio view](docs/img/xblock_insert.png)
At this point simply click on the `Publish` button and the video will be available for students to view it from the LMS.
![Published Office Video XBlock in LMS](docs/img/officevideo_student_view.jpg)
Troubleshooting
---------------
In case the XBlock fails to appear in the Advanced menu or other errors, you may check the following:
- Run `sudo -u edxapp /edx/bin/pip.edxapp list`, to verify that "xblock-officevideo" is installed
- Verify that "XBLOCK_SELECT_FUNCTION = prefer_xmodules" is present in the following config files:
- /edx/app/edxapp/edx-platform/lms/envs/common.py
- /edx/app/edxapp/edx-platform/cms/envs/common.py
- Ensure that you have restarted edX services after installing the XBlock

1
officevideo/__init__.py Normal file
Просмотреть файл

@ -0,0 +1 @@
from .officevideo import OfficeVideoXBlock

153
officevideo/officevideo.py Normal file
Просмотреть файл

@ -0,0 +1,153 @@
import textwrap
import pkg_resources
import urllib2
import mimetypes
from xblock.core import XBlock
from xblock.fragment import Fragment
from xblock.fields import Scope, String
from django.conf import settings
import logging
LOG = logging.getLogger(__name__)
import re
from urlparse import parse_qs, urlsplit, urlunsplit
from urllib import urlencode
"""test url: https://wwedudemo17.sharepoint.com/portals/hub/_layouts/15/PointPublishing.aspx?app=video&p=p&chid=4fe89746-6fd9-4a2b-9a42-ea41c5853a53&vid=70113d75-9a34-494a-972d-dc498c12168f """
"""
<iframe width=640 height=360
src='https://wwedudemo17.sharepoint.com/portals/hub/_layouts/15/VideoEmbedHost.aspx?chId=4fe89746%2D6fd9%2D4a2b%2D9a42%2Dea41c5853a53&amp;vId=70113d75%2D9a34%2D494a%2D972d%2Ddc498c12168f&amp;width=640&amp;height=360&amp;autoPlay=false&amp;showInfo=true' allowfullscreen></iframe>
"""
DEFAULT_VIDEO_URL = ('https://wwedudemo17.sharepoint.com/portals/hub/_layouts/15/VideoEmbedHost.aspx?chId=4fe89746%2D6fd9%2D4a2b%2D9a42%2Dea41c5853a53&amp;vId=70113d75%2D9a34%2D494a%2D972d%2Ddc498c12168f&amp;width=640&amp;height=360&amp;autoPlay=false&amp;showInfo=true')
class OfficeVideoXBlock(XBlock):
EMBED_CODE_TEMPLATE = textwrap.dedent("""
<iframe
src="{}"
frameborder="0"
width="960"
height="569"
allowfullscreen="true"
mozallowfullscreen="true"
webkitallowfullscreen="true">
</iframe>
""")
display_name = String(
display_name="Display Name",
help="This name appears in the horizontal navigation at the top of the page.",
scope=Scope.settings,
default="OfficeVideo",
)
video_url = String(
display_name="Video URL",
help="Navigate to the video in your browser and ensure that it is accessible to your intended audience. Copy its URL or embed code and paste it into this field.",
scope=Scope.settings,
default=DEFAULT_VIDEO_URL
)
output_code = String(
display_name="Output Iframe Embed Code",
help="Copy the embed code into this field.",
scope=Scope.settings,
default=EMBED_CODE_TEMPLATE.format(DEFAULT_VIDEO_URL)
)
message = String(
display_name="video display status message",
help="Message to help students in case of errors.",
scope=Scope.settings,
default="Note: Office Video message."
)
message_display_state = String(
display_name="Whether to display the status message",
help="Determines whether to display the message to help students in case of errors.",
scope=Scope.settings,
default="block"
)
def resource_string(self, path):
"""Handy helper for getting resources from our kit."""
data = pkg_resources.resource_string(__name__, path)
return data.decode("utf8")
def student_view(self, context=None):
"""
The primary view of the OfficeVideoXBlock, shown to students
when viewing courses.
"""
html = self.resource_string("static/html/officevideo.html")
frag = Fragment(html.format(self=self))
frag.add_css(self.resource_string("static/css/officevideo.css"))
frag.add_javascript(self.resource_string("static/js/src/officevideo.js"))
frag.initialize_js('OfficeVideoXBlock')
return frag
def studio_view(self, context=None):
"""
he primary view of the OfficeVideoXBlock, shown to teachers
when viewing courses.
"""
html = self.resource_string("static/html/officevideo_edit.html")
frag = Fragment(html.format(self=self))
frag.add_css(self.resource_string("static/css/officevideo.css"))
frag.add_javascript(self.resource_string("static/js/src/officevideo_edit.js"))
frag.initialize_js('OfficeVideoXBlock')
return frag
@XBlock.json_handler
def studio_submit(self, submissions, suffix=''): # pylint: disable=unused-argument
"""
Change the settings for this XBlock given by the Studio user
"""
if not isinstance(submissions, dict):
LOG.error("submissions object from Studio is not a dict - %r", submissions)
return {
'result': 'error'
}
self.video_url = submissions['video_url']
self.output_code = self.get_officevideo_embed_code(officevideo_url=self.video_url)
self.message = "Note: Office Video message."
self.message_display_state = "block"
return {'result': 'success'}
def get_officevideo_embed_code(self, officevideo_url):
officevideo_url = officevideo_url.strip()
scheme, netloc, path, query_string, fragment = urlsplit(officevideo_url)
query_params = parse_qs(query_string)
# OfficeVideo
odb_regex = 'https?:\/\/((\w|-)+)-my.sharepoint.com\/'
matched = re.match(odb_regex, officevideo_url, re.IGNORECASE)
if matched is not None:
query_params['action'] = ['embedview']
new_query_string = urlencode(query_params, doseq=True)
video_url = urlunsplit((scheme, netloc, path, new_query_string, fragment))
return self.EMBED_CODE_TEMPLATE.format(video_url)
@staticmethod
def workbench_scenarios():
"""A canned scenario for display in the workbench."""
return [
("OfficeVideoXBlock",
"""<vertical_demo>
<officevideo/>
<officevideo/>
</vertical_demo>
"""),
]

Просмотреть файл

@ -0,0 +1,19 @@
This static directory is for files that should be included in your kit as plain
static files.
You can ask the runtime for a URL that will retrieve these files with:
url = self.runtime.local_resource_url(self, "static/js/lib.js")
The default implementation is very strict though, and will not serve files from
the static directory. It will serve files from a directory named "public".
Create a directory alongside this one named "public", and put files there.
Then you can get a url with code like this:
url = self.runtime.local_resource_url(self, "public/js/lib.js")
The sample code includes a function you can use to read the content of files
in the static directory, like this:
frag.add_javascript(self.resource_string("static/js/my_block.js"))

Просмотреть файл

@ -0,0 +1,44 @@
/* CSS for OfficeVideoXBlock */
.officevideo-xblock p {
cursor: pointer;
}
.officevideo-xblock .tip {
float: right;
min-width: 20% !important;
width: 25%;
}
.officevideo-xblock .wrapper iframe{
width: 100%;
min-height: 569px;
display: block;
margin: 0 auto;
border: 1px solid #cccccc;
}
.officevideo-xblock .wrapper iframe.no-width-height{
top: 0;
left: 0;
}
.officevideo-xblock .message {
width: 100%;
margin: 5px 0px 0px 0px;
font-size: 12px;
}
.officevideo-xblock .error-message {
color: red;
display: none;
margin: 0px 0px 10px 0px;
}
.officevideo-xblock .visible {
display: block;
}
.officevideo-xblock .error {
border: 1px solid red !important;
}

Просмотреть файл

@ -0,0 +1,10 @@
<div class="officevideo-xblock" id="officevideo_block_container">
<div class="wrapper">
{self.output_code}
</div>
<div class="message" style="display: {self.message_display_state}">
{self.message}
</div>
</div>

Просмотреть файл

@ -0,0 +1,28 @@
<div class="wrapper-comp-settings is-active editor-with-buttons officevideo-xblock" id="settings-tab">
<ul class="list-input settings-list">
<li class="field comp-setting-entry is-set">
<label class="label setting-label">Title of this component</label>
<input class="input setting-input" id="edit_display_name" name="edit_display_name" value="{self.display_name}" type="text">
</li>
<li class="field comp-setting-entry is-set">
<label class="label setting-label">Video URL or embed code</label>
<input class="input setting-input" id="edit_ms_video_url" name="edit_video_url" value="{self.video_url}" type="text">
<span class="tip setting-help">This should be a URL where users can view this video. If you already have an embed code, that will work too.</span>
</li>
<div class="xblock-actions">
<span class="xblock-editor-error-message">
<span class="error-message">Please enter valid values into the highlighted fields above.</span>
</span>
<ul>
<li class="action-item">
<a href="#" class="save-button action-primary action">Save</a>
</li>
<li class="action-item">
<a href="#" class="button cancel-button">Cancel</a>
</li>
</ul>
</div>
</div>

Просмотреть файл

@ -0,0 +1,8 @@
/* Javascript for OfficeVideoXBlock. */
function OfficeVideoXBlock(runtime, element) {
$(function ($) {
/* Here's where you'd do things on page load. */
});
}

Просмотреть файл

@ -0,0 +1,68 @@
/* Javascript for OfficeVideoXBlock. */
function OfficeVideoXBlock(runtime, element) {
var display_name = $(element).find('input[name=edit_display_name]');
var video_url = $(element).find('input[name=edit_video_url]');
var error_message = $(element).find('.officevideo-xblock .error-message');
$(element).find('.save-button').bind('click', function() {
var display_name_val = display_name.val().trim();
var video_url_val = video_url.val().trim();
clearErrors();
if (!display_name_val) {
display_name.addClass('error');
error_message.addClass('visible');
return;
}
if (!video_url_val || (!isValidUrl(video_url_val) && !isValidEmbedCode(video_url_val))) {
video_url.addClass('error');
error_message.addClass('visible');
return;
}
var data = {
display_name: display_name_val,
video_url: video_url_val
};
var handlerUrl = runtime.handlerUrl(element, 'studio_submit');
$.post(handlerUrl, JSON.stringify(data)).done(function(response) {
window.location.reload(false);
});
});
display_name.bind('keyup', function(){
clearErrors();
});
video_url.bind('keyup', function(){
clearErrors();
});
$('.cancel-button', element).bind('click', function() {
runtime.notify('cancel', {});
});
function clearErrors() {
display_name.removeClass('error');
video_url.removeClass('error');
error_message.removeClass('visible');
}
function isValidUrl(url) {
return /^(https?):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(url);
}
function isValidEmbedCode(code) {
return /<iframe /i.test(code);
}
$(function ($) {
/* Here's where you'd do things on page load. */
});
}

39
setup.py Normal file
Просмотреть файл

@ -0,0 +1,39 @@
"""Setup for Office Video XBlock."""
import os
from setuptools import setup
def package_data(pkg, roots):
"""Generic function to find package_data.
All of the files under each of the `roots` will be declared as package
data for package `pkg`.
"""
data = []
for root in roots:
for dirname, _, files in os.walk(os.path.join(pkg, root)):
for fname in files:
data.append(os.path.relpath(os.path.join(dirname, fname), pkg))
return {pkg: data}
setup(
name='xblock-officevideo',
version='0.6',
description='Office Video XBlock for adding videos from Office 365 Video to courseware',
packages=[
'officevideo',
],
install_requires=[
'XBlock',
],
entry_points={
'xblock.v1': [
'officevideo = officevideo:OfficeVideoXBlock',
]
},
package_data=package_data("officevideo", ["static", "public"]),
)