-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add basic In Video Quiz functionality
- Leverages default video and problem components to take advantage of grading and theming - Basic "in-video" learner view where components are displayed inside of video at the set time - Instructor view where problems are displayed as normal problems with a message about when they appear in the video so instructors can still use all the problem data features - Simple Studio view in which the video and problem component IDs can be set, as well as the timemap - use xblock user_service to check for staff access
- Loading branch information
Giulio Gratta
committed
Dec 1, 2016
1 parent
1e8aac2
commit ed49d78
Showing
23 changed files
with
422 additions
and
125 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
.coverage | ||
.tox/ | ||
.tox/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
In Video Quiz XBlock |BS| |CA| | ||
============================== | ||
|
||
This XBlock allows for edX components to be displayed to users inside of videos at specific time points. | ||
|
||
Installation | ||
------------ | ||
|
||
Install the requirements into the python virtual environment of your | ||
``edx-platform`` installation by running the following command from the | ||
root folder: | ||
|
||
.. code:: bash | ||
$ pip install -r requirements.txt | ||
Enabling in Studio | ||
------------------ | ||
|
||
You can enable the In Video Quiz XBlock in Studio through the | ||
advanced settings. | ||
|
||
1. From the main page of a specific course, navigate to | ||
``Settings -> Advanced Settings`` from the top menu. | ||
2. Check for the ``advanced_modules`` policy key, and add | ||
``"invideoquiz"`` to the policy value list. | ||
3. Click the "Save changes" button. | ||
|
||
Package Requirements | ||
-------------------- | ||
|
||
setup.py contains a list of package dependencies which are required for this XBlock package. | ||
This list is what is used to resolve dependencies when an upstream project is consuming | ||
this XBlock package. requirements.txt is used to install the same dependencies when running | ||
the tests for this package. | ||
|
||
License | ||
------- | ||
|
||
The In Video Quiz XBlock is available under the AGPL Version 3.0 License. | ||
|
||
|
||
.. |BS| image:: https://travis-ci.org/Stanford-Online/xblock-in-video-quiz.svg | ||
:target: https://travis-ci.org/Stanford-Online/xblock-in-video-quiz | ||
|
||
.. |CA| image:: https://coveralls.io/repos/Stanford-Online/xblock-in-video-quiz/badge.svg?branch=master&service=github | ||
:target: https://coveralls.io/github/Stanford-Online/xblock-in-video-quiz?branch=master |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,4 @@ | ||
""" | ||
Runtime will load the XBlock class from here. | ||
""" | ||
from .invideoquiz import InVideoQuizXBlock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,71 +1,171 @@ | ||
"""TO-DO: Write a description of what this XBlock is.""" | ||
""" | ||
This XBlock allows for edX components to be displayed to users inside of | ||
videos at specific time points. | ||
""" | ||
|
||
import os | ||
import pkg_resources | ||
|
||
from xblock.core import XBlock | ||
from xblock.fields import Scope, Integer | ||
from xblock.fields import Scope | ||
from xblock.fields import String | ||
from xblock.fragment import Fragment | ||
from xblockutils.studio_editable import StudioEditableXBlockMixin | ||
|
||
from .utils import _ | ||
|
||
class InVideoQuizXBlock(XBlock): | ||
|
||
def get_resource_string(path): | ||
""" | ||
TO-DO: document what your XBlock does. | ||
Retrieve string contents for the file path | ||
""" | ||
path = os.path.join('public', path) | ||
resource_string = pkg_resources.resource_string(__name__, path) | ||
return resource_string.decode('utf8') | ||
|
||
# Fields are defined on the class. You can access them in your code as | ||
# self.<fieldname>. | ||
|
||
# TO-DO: delete count, and define your own fields. | ||
count = Integer( | ||
default=0, scope=Scope.user_state, | ||
help="A simple counter, to show something happening", | ||
class InVideoQuizXBlock(StudioEditableXBlockMixin, XBlock): | ||
# pylint: disable=too-many-ancestors | ||
""" | ||
Display CAPA problems within a video component at a specified time. | ||
""" | ||
|
||
display_name = String( | ||
display_name=_('Display Name'), | ||
default=_('In-Video Quiz XBlock'), | ||
scope=Scope.settings, | ||
) | ||
|
||
def resource_string(self, path): | ||
"""Handy helper for getting resources from our kit.""" | ||
data = pkg_resources.resource_string(__name__, path) | ||
return data.decode("utf8") | ||
video_id = String( | ||
display_name=_('Video ID'), | ||
default='', | ||
scope=Scope.settings, | ||
help=_( | ||
'This is the component ID for the video in which ' | ||
'you want to insert your quiz question.' | ||
), | ||
) | ||
|
||
# TO-DO: change this view to display your data your own way. | ||
def student_view(self, context=None): | ||
timemap = String( | ||
display_name=_('Problem Timemap'), | ||
default='', | ||
scope=Scope.settings, | ||
help=_( | ||
'A simple string field to define problem IDs ' | ||
'and their time maps (in seconds) as JSON. ' | ||
'Example: {"60": "50srvqlii4ru9gonprp35gkcfyd5weju"}' | ||
), | ||
multiline_editor=True, | ||
) | ||
|
||
editable_fields = [ | ||
'video_id', | ||
'timemap', | ||
] | ||
|
||
def student_view(self, context=None): # pylint: disable=unused-argument | ||
""" | ||
The primary view of the InVideoQuizXBlock, shown to students | ||
when viewing courses. | ||
Show to students when viewing courses | ||
""" | ||
html = self.resource_string("static/html/invideoquiz.html") | ||
frag = Fragment(html.format(self=self)) | ||
frag.add_css(self.resource_string("static/css/invideoquiz.css")) | ||
frag.add_javascript(self.resource_string("static/js/src/invideoquiz.js")) | ||
frag.initialize_js('InVideoQuizXBlock') | ||
return frag | ||
|
||
# TO-DO: change this handler to perform your own actions. You may need | ||
# more than one handler, or you may not need any handlers at all. | ||
@XBlock.json_handler | ||
def increment_count(self, data, suffix=''): | ||
fragment = self.build_fragment( | ||
path_html='html/invideoquiz.html', | ||
paths_css=[ | ||
'css/invideoquiz.css', | ||
], | ||
paths_js=[ | ||
'js/src/invideoquiz.js', | ||
], | ||
fragment_js='InVideoQuizXBlock', | ||
context={ | ||
'video_id': self.video_id, | ||
'user_mode': self.user_mode, | ||
}, | ||
) | ||
config = get_resource_string('js/src/config.js') | ||
config = config.format( | ||
video_id=self.video_id, | ||
timemap=self.timemap, | ||
) | ||
fragment.add_javascript(config) | ||
return fragment | ||
|
||
@property | ||
def user_mode(self): | ||
""" | ||
An example handler, which increments the data. | ||
Check user's permission mode for this XBlock. | ||
Returns: | ||
user permission mode | ||
""" | ||
# Just to show data coming in... | ||
assert data['hello'] == 'world' | ||
try: | ||
if self.xmodule_runtime.user_is_staff: | ||
return 'staff' | ||
except AttributeError: | ||
pass | ||
return 'student' | ||
|
||
self.count += 1 | ||
return {"count": self.count} | ||
|
||
# TO-DO: change this to create the scenarios you'd like to see in the | ||
# workbench while developing your XBlock. | ||
@staticmethod | ||
def workbench_scenarios(): | ||
"""A canned scenario for display in the workbench.""" | ||
""" | ||
A canned scenario for display in the workbench. | ||
""" | ||
return [ | ||
("InVideoQuizXBlock", | ||
"""<invideoquiz/> | ||
"""<invideoquiz video_id='###' timemap='{ 10: "###" }' /> | ||
"""), | ||
("Multiple InVideoQuizXBlock", | ||
"""<vertical_demo> | ||
<invideoquiz/> | ||
<invideoquiz/> | ||
<invideoquiz/> | ||
<invideoquiz video_id='###' timemap='{ 10: "###" }' /> | ||
<invideoquiz video_id='###' timemap='{ 10: "###" }' /> | ||
<invideoquiz video_id='###' timemap='{ 10: "###" }' /> | ||
</vertical_demo> | ||
"""), | ||
] | ||
|
||
def get_resource_url(self, path): | ||
""" | ||
Retrieve a public URL for the file path | ||
""" | ||
path = os.path.join('public', path) | ||
resource_url = self.runtime.local_resource_url(self, path) | ||
return resource_url | ||
|
||
def build_fragment( | ||
self, | ||
path_html='', | ||
paths_css=None, | ||
paths_js=None, | ||
urls_css=None, | ||
urls_js=None, | ||
fragment_js=None, | ||
context=None, | ||
): # pylint: disable=too-many-arguments | ||
""" | ||
Assemble the HTML, JS, and CSS for an XBlock fragment | ||
""" | ||
paths_css = paths_css or [] | ||
paths_js = paths_js or [] | ||
urls_css = urls_css or [] | ||
urls_js = urls_js or [] | ||
# If no context is provided, convert self.fields into a dict | ||
context = context or { | ||
key: getattr(self, key) | ||
for key in self.editable_fields | ||
} | ||
html_source = get_resource_string(path_html) | ||
html_source = html_source.format( | ||
**context | ||
) | ||
fragment = Fragment(html_source) | ||
for path in paths_css: | ||
url = self.get_resource_url(path) | ||
fragment.add_css_url(url) | ||
for path in paths_js: | ||
url = self.get_resource_url(path) | ||
fragment.add_javascript_url(url) | ||
for url in urls_css: | ||
fragment.add_css_url(url) | ||
for url in urls_js: | ||
fragment.add_javascript_url(url) | ||
if fragment_js: | ||
fragment.initialize_js(fragment_js) | ||
return fragment |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/* CSS for InVideoQuizXBlock */ | ||
|
||
#seq_content .vert-mod { | ||
position: relative; | ||
} | ||
#seq_content .vert-mod .in-video-alert { | ||
padding: 5px 10px; | ||
background: #eee; | ||
border-radius: 5px; | ||
} | ||
|
||
#seq_content .vert-mod .vert.in-video-problem-wrapper { | ||
padding-bottom: 0; | ||
margin-bottom: 0; | ||
border-bottom: none; | ||
} | ||
#seq_content .vert-mod .vert.in-video-problem-wrapper .in-video-problem { | ||
position: absolute; | ||
top: 50px; | ||
padding: 25px 25px 0 25px; | ||
background: white; | ||
box-sizing: border-box; | ||
width: 100%; | ||
height: 461px; | ||
overflow-y: scroll; | ||
z-index: 99; | ||
} | ||
.video-fullscreen #seq_content .vert-mod .vert.in-video-problem-wrapper .in-video-problem { | ||
position: fixed; | ||
height: auto; | ||
top: 0; | ||
left: 0; | ||
right: 0; | ||
bottom: 53px; | ||
z-index: 10000; | ||
} | ||
.video-controls { | ||
z-index: 100; | ||
} | ||
.video-fullscreen #seq_content .vert-mod .video-controls .slider { | ||
height: 13px; | ||
} | ||
.video-fullscreen #seq_content .vert-mod .video-controls .slider .ui-slider-handle { | ||
height: 13px; | ||
width: 13px; | ||
} | ||
.in-video-continue { | ||
float: right; | ||
height: 40px; | ||
margin-bottom: 25px; | ||
text-transform: uppercase; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<div class="in-video-quiz-block" data-videoid="{video_id}" data-mode='{user_mode}'> | ||
<p>This is an XBlock to display problem components inside of videos.</p> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// Curly braces are all doubled because this file gets called and formatted by python | ||
|
||
var InVideoQuizXBlock = InVideoQuizXBlock || {{}}; | ||
|
||
(function () {{ | ||
InVideoQuizXBlock.config = InVideoQuizXBlock.config || {{}}; | ||
|
||
var videoId = '{video_id}'; | ||
if (videoId) {{ | ||
InVideoQuizXBlock.config[videoId] = {timemap}; | ||
}} | ||
}}()); |
Oops, something went wrong.