Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cms/djangoapps/contentstore/tests/test_contentstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ def test_advanced_components_in_edit_unit(self):
# response HTML
self.check_components_on_page(
ADVANCED_COMPONENT_TYPES,
['Word cloud', 'Annotation', 'Text Annotation', 'Video Annotation',
['Word cloud', 'Annotation', 'Text Annotation', 'Video Annotation', 'Image Annotation',
'Open Response Assessment', 'Peer Grading Interface', 'openassessment'],
)

Expand Down
1 change: 1 addition & 0 deletions cms/djangoapps/contentstore/views/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
'annotatable',
'textannotation', # module for annotating text (with annotation table)
'videoannotation', # module for annotating video (with annotation table)
'imageannotation', # module for annotating images (with annotation table)
'word_cloud',
'graphical_slider_tool',
'lti',
Expand Down
7 changes: 7 additions & 0 deletions cms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,13 @@
],
'output_filename': 'css/cms-style-vendor-tinymce-skin.css',
},
'style-annotator' :{
'source_filenames':[
'css/vendor/ova/annotator.css',
'css/vendor/ova/token-input.css'
],
'output_filename': 'css/lms-style-vendor-ova-annotator.css',
},
'style-app': {
'source_filenames': [
'sass/style-app.css',
Expand Down
1 change: 1 addition & 0 deletions common/lib/xmodule/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"annotatable = xmodule.annotatable_module:AnnotatableDescriptor",
"textannotation = xmodule.textannotation_module:TextAnnotationDescriptor",
"videoannotation = xmodule.videoannotation_module:VideoAnnotationDescriptor",
"imageannotation = xmodule.imageannotation_module:ImageAnnotationDescriptor",
"foldit = xmodule.foldit_module:FolditDescriptor",
"word_cloud = xmodule.word_cloud_module:WordCloudDescriptor",
"hidden = xmodule.hidden_module:HiddenDescriptor",
Expand Down
74 changes: 74 additions & 0 deletions common/lib/xmodule/xmodule/annotator_mixin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""
Annotations Tool Mixin
This file contains global variables and functions used in the various Annotation Tools.
"""
from pkg_resources import resource_string
from lxml import etree
from urlparse import urlparse
from os.path import splitext, basename
from HTMLParser import HTMLParser

def get_instructions(xmltree):
""" Removes <instructions> from the xmltree and returns them as a string, otherwise None. """
instructions = xmltree.find('instructions')
if instructions is not None:
instructions.tag = 'div'
xmltree.remove(instructions)
return etree.tostring(instructions, encoding='unicode')
return None

def get_extension(srcurl):
''' get the extension of a given url '''
if 'youtu' in srcurl:
return 'video/youtube'
else:
disassembled = urlparse(srcurl)
file_ext = splitext(basename(disassembled.path))[1]
return 'video/' + file_ext.replace('.', '')

ANNOTATOR_COMMON_JS = [
resource_string(__name__, 'js/src/ova/annotator-full.js'),
resource_string(__name__, 'js/src/ova/annotator-full-firebase-auth.js'),
resource_string(__name__, 'js/src/ova/video.dev.js'),
resource_string(__name__, 'js/src/ova/vjs.youtube.js'),
resource_string(__name__, 'js/src/ova/rangeslider.js'),
resource_string(__name__, 'js/src/ova/share-annotator.js'),
resource_string(__name__, 'js/src/ova/richText-annotator.js'),
resource_string(__name__, 'js/src/ova/reply-annotator.js'),
resource_string(__name__, 'js/src/ova/tags-annotator.js'),
resource_string(__name__, 'js/src/ova/flagging-annotator.js'),
resource_string(__name__, 'js/src/ova/diacritic-annotator.js'),
resource_string(__name__, 'js/src/ova/jquery-Watch.js'),
resource_string(__name__, 'js/src/ova/ova.js'),
resource_string(__name__, 'js/src/ova/openseadragon.js'),
resource_string(__name__, 'js/src/ova/OpenSeaDragonAnnotation.js'),
resource_string(__name__, 'js/src/ova/catch/js/handlebars-1.1.2.js'),
resource_string(__name__, 'js/src/ova/catch/js/catch.js'),
]

ANNOTATOR_COMMON_CSS = [
resource_string(__name__, 'css/ova/edx-annotator.css'),
resource_string(__name__, 'css/ova/rangeslider.css'),
resource_string(__name__, 'css/ova/share-annotator.css'),
resource_string(__name__, 'css/ova/richText-annotator.css'),
resource_string(__name__, 'css/ova/diacritic-annotator.css'),
resource_string(__name__, 'css/ova/flagging-annotator.css'),
resource_string(__name__, 'css/ova/ova.css'),
resource_string(__name__, 'js/src/ova/catch/css/main.css'),
]

class MLStripper(HTMLParser):
def __init__(self):
self.reset()
self.fed = []
def handle_data(self, d):
self.fed.append(d)
def handle_entityref(self, name):
self.fed.append('&%s;' % name)
def get_data(self):
return ''.join(self.fed)

def html_to_text(html):
s = MLStripper()
s.feed(html)
return s.get_data()
7 changes: 6 additions & 1 deletion common/lib/xmodule/xmodule/annotator_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,16 @@ def retrieve_token(userid, secret):
the token was issued. This will be stored with the user along with
the id for identification purposes in the backend.
'''

# the following five lines of code allows you to include the default timezone in the iso format
# for more information: http://stackoverflow.com/questions/3401428/how-to-get-an-isoformat-datetime-string-including-the-default-timezone
dtnow = datetime.datetime.now()
dtutcnow = datetime.datetime.utcnow()
delta = dtnow - dtutcnow
newhour, newmin = divmod((delta.days * 24 * 60 * 60 + delta.seconds + 30) // 60, 60)
newtime = "%s%+02d:%02d" % (dtnow.isoformat(), newhour, newmin)
# uses the issued time (UTC plus timezone), the consumer key and the user's email to maintain a
# federated system in the annotation backend server
custom_data = {"issuedAt": newtime, "consumerKey": secret, "userId": userid, "ttl": 86400}
newtoken = create_token(secret, custom_data)
return newtoken
return newtoken
8 changes: 8 additions & 0 deletions common/lib/xmodule/xmodule/css/ova/diacritic-annotator.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.mark{
width: 10px;
height: 10px;
position: absolute;
background-size: contain;
background-repeat: no-repeat;
background-position: 50% 0%;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
/*This is written to fix some design problems with edX*/
.annotatable-wrapper .annotatable-header .annotatable-title{
padding-top: 20px !important;
}

.annotator-wrapper .annotator-adder button {
opacity:0;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,21 @@
}

.annotator-wrapper .mce-container {
z-index:3000000000!important; /*To fix full-screen problems*/
z-index: 3000000000!important; /*To fix full-screen problems*/
}

.mce-container-body {
min-width: 400px;
}

.iframe[id="annotator-field"] {
width: inherit;
min-width: 400px;
}

div.mce-tinymce.mce-container.mce-panel {
min-width:400px;
}

/* Some change in the design of Annotator */
.annotator-editor .annotator-widget{
Expand Down
116 changes: 116 additions & 0 deletions common/lib/xmodule/xmodule/imageannotation_module.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"""
Module for Image annotations using annotator.
"""
from lxml import etree
from pkg_resources import resource_string

from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor
from xblock.core import Scope, String
from xmodule.annotator_mixin import get_instructions, html_to_text, ANNOTATOR_COMMON_JS, ANNOTATOR_COMMON_CSS
from xmodule.annotator_token import retrieve_token

import textwrap


class AnnotatableFields(object):
""" Fields for `ImageModule` and `ImageDescriptor`. """
data = String(help="XML data for the annotation", scope=Scope.content, default=textwrap.dedent("""\
<annotatable>
<instructions>
<p>
Add the instructions to the assignment here.
</p>
</instructions>
<p>
Lorem ipsum dolor sit amet, at amet animal petentium nec. Id augue nemore postulant mea. Ex eam dicant noluisse expetenda, alia admodum abhorreant qui et. An ceteros expetenda mea, tale natum ipsum quo no, ut pro paulo alienum noluisse.
</p>
<json>
navigatorSizeRatio: 0.25,
wrapHorizontal: false,
showNavigator: true,
navigatorPosition: "BOTTOM_LEFT",
showNavigationControl: true,
tileSources: [{
Image: {
xmlns: "http://schemas.microsoft.com/deepzoom/2009",
Url: "http://static.seadragon.com/content/misc/milwaukee_files/",
TileSize: "254",
Overlap: "1",
Format: "jpg",
ServerFormat: "Default",
Size: {
Width: "15497",
Height: "5378"
}
}
},],
</json>
</annotatable>
"""))
display_name = String(
display_name="Display Name",
help="Display name for this module",
scope=Scope.settings,
default='Image Annotation',
)
annotation_storage_url = String(help="Location of Annotation backend", scope=Scope.settings, default="http://your_annotation_storage.com", display_name="Url for Annotation Storage")
annotation_token_secret = String(help="Secret string for annotation storage", scope=Scope.settings, default="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", display_name="Secret Token String for Annotation")


class ImageAnnotationModule(AnnotatableFields, XModule):
'''Image Annotation Module'''
js = {
'coffee': [
resource_string(__name__, 'js/src/javascript_loader.coffee'),
resource_string(__name__, 'js/src/html/display.coffee'),
resource_string(__name__, 'js/src/annotatable/display.coffee'),
],
'js': ANNOTATOR_COMMON_JS + [
resource_string(__name__, 'js/src/collapsible.js'),
]
}
css = {'scss': ANNOTATOR_COMMON_CSS + [resource_string(__name__, 'css/annotatable/display.scss')]}
icon_class = 'imageannotation'

def __init__(self, *args, **kwargs):
super(ImageAnnotationModule, self).__init__(*args, **kwargs)

xmltree = etree.fromstring(self.data)

self.instructions = self._extract_instructions(xmltree)
self.openseadragonjson = html_to_text(etree.tostring(xmltree.find('json'), encoding='unicode'))
self.user = ""
if self.runtime.get_real_user is not None:
self.user = self.runtime.get_real_user(self.runtime.anonymous_student_id).email

def _extract_instructions(self, xmltree):
""" Removes <instructions> from the xmltree and returns them as a string, otherwise None. """
return get_instructions(xmltree)

def get_html(self):
""" Renders parameters to template. """
context = {
'display_name': self.display_name_with_default,
'instructions_html': self.instructions,
'annotation_storage': self.annotation_storage_url,
'token':retrieve_token(self.user, self.annotation_token_secret),
'openseadragonjson': self.openseadragonjson,
}

return self.system.render_template('imageannotation.html', context)


class ImageAnnotationDescriptor(AnnotatableFields, RawDescriptor):
''' Image annotation descriptor '''
module_class = ImageAnnotationModule
mako_template = "widgets/raw-edit.html"

@property
def non_editable_metadata_fields(self):
non_editable_fields = super(ImageAnnotationDescriptor, self).non_editable_metadata_fields
non_editable_fields.extend([
ImageAnnotationDescriptor.annotation_storage_url,
ImageAnnotationDescriptor.annotation_token_secret,
])
return non_editable_fields
Loading