Commit dd8d8fc5 by Sarina Canelake

Merge pull request #3831 from lduarte1991/lduarte-harvardx-image

Installing Image Annotation Tool for Upcoming June 2014 Course
parents 94f050c4 e4e4b32c
...@@ -147,3 +147,4 @@ David Bodor <david.gabor.bodor@gmail.com> ...@@ -147,3 +147,4 @@ David Bodor <david.gabor.bodor@gmail.com>
Sébastien Hinderer <Sebastien.Hinderer@inria.fr> Sébastien Hinderer <Sebastien.Hinderer@inria.fr>
Kristin Stephens <ksteph@cs.berkeley.edu> Kristin Stephens <ksteph@cs.berkeley.edu>
Ben Patterson <bpatterson@edx.org> Ben Patterson <bpatterson@edx.org>
Luis Duarte <lduarte1991@gmail.com>
...@@ -754,3 +754,5 @@ LMS: Option to email students when enroll/un-enroll them. ...@@ -754,3 +754,5 @@ LMS: Option to email students when enroll/un-enroll them.
Blades: Added WAI-ARIA markup to the video player controls. These are now fully Blades: Added WAI-ARIA markup to the video player controls. These are now fully
accessible by screen readers. accessible by screen readers.
Common: Added advanced_module for annotating images to go with the ones for text and videos.
...@@ -139,7 +139,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -139,7 +139,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
# response HTML # response HTML
self.check_components_on_page( self.check_components_on_page(
ADVANCED_COMPONENT_TYPES, 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'], 'Open Response Assessment', 'Peer Grading Interface', 'openassessment'],
) )
......
...@@ -54,6 +54,7 @@ else: ...@@ -54,6 +54,7 @@ else:
'annotatable', 'annotatable',
'textannotation', # module for annotating text (with annotation table) 'textannotation', # module for annotating text (with annotation table)
'videoannotation', # module for annotating video (with annotation table) 'videoannotation', # module for annotating video (with annotation table)
'imageannotation', # module for annotating image (with annotation table)
'word_cloud', 'word_cloud',
'graphical_slider_tool', 'graphical_slider_tool',
'lti', 'lti',
......
...@@ -36,6 +36,7 @@ XMODULES = [ ...@@ -36,6 +36,7 @@ XMODULES = [
"annotatable = xmodule.annotatable_module:AnnotatableDescriptor", "annotatable = xmodule.annotatable_module:AnnotatableDescriptor",
"textannotation = xmodule.textannotation_module:TextAnnotationDescriptor", "textannotation = xmodule.textannotation_module:TextAnnotationDescriptor",
"videoannotation = xmodule.videoannotation_module:VideoAnnotationDescriptor", "videoannotation = xmodule.videoannotation_module:VideoAnnotationDescriptor",
"imageannotation = xmodule.imageannotation_module:ImageAnnotationDescriptor",
"foldit = xmodule.foldit_module:FolditDescriptor", "foldit = xmodule.foldit_module:FolditDescriptor",
"word_cloud = xmodule.word_cloud_module:WordCloudDescriptor", "word_cloud = xmodule.word_cloud_module:WordCloudDescriptor",
"hidden = xmodule.hidden_module:HiddenDescriptor", "hidden = xmodule.hidden_module:HiddenDescriptor",
......
"""
Annotations Tool Mixin
This file contains global variables and functions used in the various Annotation Tools.
"""
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('.', '')
class MLStripper(HTMLParser):
"helper function for html_to_text below"
def __init__(self):
HTMLParser.__init__(self)
self.reset()
self.fed = []
def handle_data(self, data):
"""takes the data in separate chunks"""
self.fed.append(data)
def handle_entityref(self, name):
"""appends the reference to the body"""
self.fed.append('&%s;' % name)
def get_data(self):
"""joins together the seperate chunks into one cohesive string"""
return ''.join(self.fed)
def html_to_text(html):
"strips the html tags off of the text to return plaintext"
htmlstripper = MLStripper()
htmlstripper.feed(html)
return htmlstripper.get_data()
...@@ -25,7 +25,7 @@ def retrieve_token(userid, secret): ...@@ -25,7 +25,7 @@ def retrieve_token(userid, secret):
delta = dtnow - dtutcnow delta = dtnow - dtutcnow
newhour, newmin = divmod((delta.days * 24 * 60 * 60 + delta.seconds + 30) // 60, 60) newhour, newmin = divmod((delta.days * 24 * 60 * 60 + delta.seconds + 30) // 60, 60)
newtime = "%s%+02d:%02d" % (dtnow.isoformat(), newhour, newmin) 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 # 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 # federated system in the annotation backend server
custom_data = {"issuedAt": newtime, "consumerKey": secret, "userId": userid, "ttl": 86400} custom_data = {"issuedAt": newtime, "consumerKey": secret, "userId": userid, "ttl": 86400}
newtoken = create_token(secret, custom_data) newtoken = create_token(secret, custom_data)
......
"""
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
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: [{"profile": "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level2", "scale_factors": [1, 2, 4, 8, 16, 32, 64], "tile_height": 1024, "height": 3466, "width": 113793, "tile_width": 1024, "qualities": ["native", "bitonal", "grey", "color"], "formats": ["jpg", "png", "gif"], "@context": "http://library.stanford.edu/iiif/image-api/1.1/context.json", "@id": "http://54.187.32.48/loris/suzhou_orig.jp2"}],
</json>
</annotatable>
"""))
display_name = String(
display_name="Display Name",
help="Display name for this module",
scope=Scope.settings,
default='Image Annotation',
)
instructor_tags = String(
display_name="Tags for Assignments",
help="Add tags that automatically highlight in a certain color using the comma-separated form, i.e. imagery:red,parallelism:blue",
scope=Scope.settings,
default='professor:green,teachingAssistant:blue',
)
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': [
resource_string(__name__, 'js/src/collapsible.js'),
]
}
css = {'scss': [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),
'tag': self.instructor_tags,
'openseadragonjson': self.openseadragonjson,
}
return self.system.render_template('imageannotation.html', context)
class ImageAnnotationDescriptor(AnnotatableFields, RawDescriptor): # pylint: disable=abstract-method
''' 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
"""
This test will run for annotator_mixin.py
"""
import unittest
from lxml import etree
from xmodule.annotator_mixin import get_instructions, get_extension, html_to_text
class HelperFunctionTest(unittest.TestCase):
"""
Tests to ensure that the following helper functions work for the annotation tool
"""
sample_xml = '''
<annotatable>
<instructions><p>Helper Test Instructions.</p></instructions>
</annotatable>
'''
sample_sourceurl = "http://video-js.zencoder.com/oceans-clip.mp4"
sample_youtubeurl = "http://www.youtube.com/watch?v=yxLIu-scR9Y"
sample_html = '<p><b>Testing here</b> and not bolded here</p>'
def test_get_instructions(self):
"""
Function takes in an input of a specific xml string with surrounding instructions
tags and returns a valid html string.
"""
xmltree = etree.fromstring(self.sample_xml)
expected_xml = u"<div><p>Helper Test Instructions.</p></div>"
actual_xml = get_instructions(xmltree)
self.assertIsNotNone(actual_xml)
self.assertEqual(expected_xml.strip(), actual_xml.strip())
xmltree = etree.fromstring('<annotatable>foo</annotatable>')
actual = get_instructions(xmltree)
self.assertIsNone(actual)
def test_get_extension(self):
"""
Tests whether given a url if the video will return a youtube source or extension
"""
expectedyoutube = 'video/youtube'
expectednotyoutube = 'video/mp4'
result1 = get_extension(self.sample_sourceurl)
result2 = get_extension(self.sample_youtubeurl)
self.assertEqual(expectedyoutube, result2)
self.assertEqual(expectednotyoutube, result1)
def test_html_to_text(self):
expectedtext = "Testing here and not bolded here"
result = html_to_text(self.sample_html)
self.assertEqual(expectedtext, result)
...@@ -12,9 +12,12 @@ class TokenRetriever(unittest.TestCase): ...@@ -12,9 +12,12 @@ class TokenRetriever(unittest.TestCase):
""" """
def test_token(self): def test_token(self):
""" """
Test for the token generator. Give an a random username and secret token, it should create the properly encoded string of text. Test for the token generator. Give an a random username and secret token,
it should create the properly encoded string of text.
""" """
expected = "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJpc3N1ZWRBdCI6ICIyMDE0LTAyLTI3VDE3OjAwOjQyLjQwNjQ0MSswOjAwIiwgImNvbnN1bWVyS2V5IjogImZha2Vfc2VjcmV0IiwgInVzZXJJZCI6ICJ1c2VybmFtZSIsICJ0dGwiOiA4NjQwMH0.Dx1PoF-7mqBOOSGDMZ9R_s3oaaLRPnn6CJgGGF2A5CQ" expected = "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJpc3N1ZWRBdCI6ICIyMDE0LTAyLTI3VDE3OjAwOjQyLjQwNjQ0MSswOjAwIiwgImNvbnN1bWVyS2V5IjogImZha2Vfc2VjcmV0IiwgInVzZXJJZCI6ICJ1c2VybmFtZSIsICJ0dGwiOiA4NjQwMH0.Dx1PoF-7mqBOOSGDMZ9R_s3oaaLRPnn6CJgGGF2A5CQ"
response = retrieve_token("username", "fake_secret") response = retrieve_token("username", "fake_secret")
# because the middle hashes are dependent on time, conly the header and footer are checked for secret key
self.assertEqual(expected.split('.')[0], response.split('.')[0]) self.assertEqual(expected.split('.')[0], response.split('.')[0])
self.assertNotEqual(expected.split('.')[2], response.split('.')[2]) self.assertNotEqual(expected.split('.')[2], response.split('.')[2])
\ No newline at end of file
# -*- coding: utf-8 -*-
"""Test for Image Annotation Xmodule functional logic."""
import unittest
from mock import Mock
from lxml import etree
from xblock.field_data import DictFieldData
from xblock.fields import ScopeIds
from xmodule.imageannotation_module import ImageAnnotationModule
from . import get_test_system
class ImageAnnotationModuleTestCase(unittest.TestCase):
''' Image Annotation Module Test Case '''
sample_xml = '''
<annotatable>
<instructions><p>Image Test Instructions.</p></instructions>
<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>
'''
def setUp(self):
"""
Makes sure that the Module is declared and mocked with the sample xml above.
"""
self.mod = ImageAnnotationModule(
Mock(),
get_test_system(),
DictFieldData({'data': self.sample_xml}),
ScopeIds(None, None, None, None)
)
def test_extract_instructions(self):
"""
Tests to make sure that the instructions are correctly pulled from the sample xml above.
It also makes sure that if no instructions exist, that it does in fact return nothing.
"""
xmltree = etree.fromstring(self.sample_xml)
expected_xml = u"<div><p>Image Test Instructions.</p></div>"
actual_xml = self.mod._extract_instructions(xmltree) # pylint: disable=protected-access
self.assertIsNotNone(actual_xml)
self.assertEqual(expected_xml.strip(), actual_xml.strip())
xmltree = etree.fromstring('<annotatable>foo</annotatable>')
actual = self.mod._extract_instructions(xmltree) # pylint: disable=protected-access
self.assertIsNone(actual)
def test_get_html(self):
"""
Tests the function that passes in all the information in the context that will be used in templates/textannotation.html
"""
context = self.mod.get_html()
for key in ['display_name', 'instructions_html', 'annotation_storage', 'token', 'tag', 'openseadragonjson']:
self.assertIn(key, context)
...@@ -66,6 +66,6 @@ class VideoAnnotationModuleTestCase(unittest.TestCase): ...@@ -66,6 +66,6 @@ class VideoAnnotationModuleTestCase(unittest.TestCase):
""" """
Tests to make sure variables passed in truly exist within the html once it is all rendered. Tests to make sure variables passed in truly exist within the html once it is all rendered.
""" """
context = self.mod.get_html() # pylint: disable=W0212 context = self.mod.get_html()
for key in ['display_name', 'instructions_html', 'sourceUrl', 'typeSource', 'poster', 'annotation_storage']: for key in ['display_name', 'instructions_html', 'sourceUrl', 'typeSource', 'poster', 'annotation_storage']:
self.assertIn(key, context) self.assertIn(key, context)
...@@ -6,6 +6,7 @@ from pkg_resources import resource_string ...@@ -6,6 +6,7 @@ from pkg_resources import resource_string
from xmodule.x_module import XModule from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor from xmodule.raw_module import RawDescriptor
from xblock.core import Scope, String from xblock.core import Scope, String
from xmodule.annotator_mixin import get_instructions
from xmodule.annotator_token import retrieve_token from xmodule.annotator_token import retrieve_token
import textwrap import textwrap
...@@ -70,12 +71,7 @@ class TextAnnotationModule(AnnotatableFields, XModule): ...@@ -70,12 +71,7 @@ class TextAnnotationModule(AnnotatableFields, XModule):
def _extract_instructions(self, xmltree): def _extract_instructions(self, xmltree):
""" Removes <instructions> from the xmltree and returns them as a string, otherwise None. """ """ Removes <instructions> from the xmltree and returns them as a string, otherwise None. """
instructions = xmltree.find('instructions') return get_instructions(xmltree)
if instructions is not None:
instructions.tag = 'div'
xmltree.remove(instructions)
return etree.tostring(instructions, encoding='unicode')
return None
def get_html(self): def get_html(self):
""" Renders parameters to template. """ """ Renders parameters to template. """
......
...@@ -7,6 +7,7 @@ from pkg_resources import resource_string ...@@ -7,6 +7,7 @@ from pkg_resources import resource_string
from xmodule.x_module import XModule from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor from xmodule.raw_module import RawDescriptor
from xblock.core import Scope, String from xblock.core import Scope, String
from xmodule.annotator_mixin import get_instructions, get_extension
from xmodule.annotator_token import retrieve_token from xmodule.annotator_token import retrieve_token
import textwrap import textwrap
...@@ -65,24 +66,11 @@ class VideoAnnotationModule(AnnotatableFields, XModule): ...@@ -65,24 +66,11 @@ class VideoAnnotationModule(AnnotatableFields, XModule):
def _extract_instructions(self, xmltree): def _extract_instructions(self, xmltree):
""" Removes <instructions> from the xmltree and returns them as a string, otherwise None. """ """ Removes <instructions> from the xmltree and returns them as a string, otherwise None. """
instructions = xmltree.find('instructions') return get_instructions(xmltree)
if instructions is not None:
instructions.tag = 'div' def _get_extension(self, src_url):
xmltree.remove(instructions)
return etree.tostring(instructions, encoding='unicode')
return None
def _get_extension(self, srcurl):
''' get the extension of a given url ''' ''' get the extension of a given url '''
if 'youtu' in srcurl: return get_extension(src_url)
return 'video/youtube'
else:
spliturl = srcurl.split(".")
extensionplus1 = spliturl[len(spliturl) - 1]
spliturl = extensionplus1.split("?")
extensionplus2 = spliturl[0]
spliturl = extensionplus2.split("#")
return 'video/' + spliturl[0]
def get_html(self): def get_html(self):
""" Renders parameters to template. """ """ Renders parameters to template. """
......
...@@ -16,10 +16,22 @@ ...@@ -16,10 +16,22 @@
font-style: italic; font-style: italic;
} }
.annotator-wrapper .mce-container { .mce-container-body {
z-index:3000000000!important; /*To fix full-screen problems*/ min-width: 400px;
}
.iframe[id="annotator-field"] {
width: inherit;
min-width: 400px;
} }
.mce-floatpanel {
z-index: 700000000!important;
}
div.mce-tinymce.mce-container.mce-panel {
min-width:400px;
}
/* Some change in the design of Annotator */ /* Some change in the design of Annotator */
.annotator-editor .annotator-widget{ .annotator-editor .annotator-widget{
...@@ -30,4 +42,4 @@ ...@@ -30,4 +42,4 @@
.mce-ico.mce-i-rubric{ .mce-ico.mce-i-rubric{
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QkBBB07nraNoQAAAhZJREFUKM+NkstrE1EUxr+5c08ykztpJtVoazHBF8FgQQzonyBKEZS6FrQKLl0EXBRT0ZULJSs3oii4TyHgu90IlTaL6qouWlv7Ck1N0BSnmZk714WbPHz07M4534+Pw3eAHdTY8A9+Nd9/bshU1DpnO4HXjh2ZY2J9/OSTxHTrnP8PvJYf+BDQ6qEDaQBB43jrTusUFy4oPjsYWYzF+VS91nxLYfdhKgONaQT3W/KMxr1XY5e+qj86f8zsKYYsZ6AvjWFzA8ORHkAnwN8So7evzL/8pzMAXL/Hq8mMv1up371T7Z+/c3n9cKeuDS6Xy6dN07zLuZ56Onk2Ed2/ANJsnE/PQMpgyffle+kYzwazB1+3waVS6X48Hr9BRPB9H57nYXplFKeSt8D1Hriug9XKF0x+Lmw+ys8m2m42DOOn4zhQSsGyLOi6jqONm9isbmFVFlDbaGKx8QaB1rvdlbNhGLAsC0IIGIYBIQSy2ROQ0oOp7wOPraHXEugRvDtnzjmi0SiICEIIEBGklAB9B6cmbG0AUnrY5m73h+m6DsYYTNMEYwxEBMY0hGNVhHkcZigBO9qHlDHS7cwYg23bAIBQKAQigud7IH0XwtxDoHwEIQ9SLKx0wa7rPiaivYyxESklXNeFBg0mjyNQTQSuATMSm6ipuYt//eVcLhdeXl5+UKlUlur1upqamVAv3j3/VCyOD3VqfwF6uLp3q+vMcgAAAABJRU5ErkJggg=='); background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QkBBB07nraNoQAAAhZJREFUKM+NkstrE1EUxr+5c08ykztpJtVoazHBF8FgQQzonyBKEZS6FrQKLl0EXBRT0ZULJSs3oii4TyHgu90IlTaL6qouWlv7Ck1N0BSnmZk714WbPHz07M4534+Pw3eAHdTY8A9+Nd9/bshU1DpnO4HXjh2ZY2J9/OSTxHTrnP8PvJYf+BDQ6qEDaQBB43jrTusUFy4oPjsYWYzF+VS91nxLYfdhKgONaQT3W/KMxr1XY5e+qj86f8zsKYYsZ6AvjWFzA8ORHkAnwN8So7evzL/8pzMAXL/Hq8mMv1up371T7Z+/c3n9cKeuDS6Xy6dN07zLuZ56Onk2Ed2/ANJsnE/PQMpgyffle+kYzwazB1+3waVS6X48Hr9BRPB9H57nYXplFKeSt8D1Hriug9XKF0x+Lmw+ys8m2m42DOOn4zhQSsGyLOi6jqONm9isbmFVFlDbaGKx8QaB1rvdlbNhGLAsC0IIGIYBIQSy2ROQ0oOp7wOPraHXEugRvDtnzjmi0SiICEIIEBGklAB9B6cmbG0AUnrY5m73h+m6DsYYTNMEYwxEBMY0hGNVhHkcZigBO9qHlDHS7cwYg23bAIBQKAQigud7IH0XwtxDoHwEIQ9SLKx0wa7rPiaivYyxESklXNeFBg0mjyNQTQSuATMSm6ipuYt//eVcLhdeXl5+UKlUlur1upqamVAv3j3/VCyOD3VqfwF6uLp3q+vMcgAAAABJRU5ErkJggg==');
background-repeat: no-repeat; background-repeat: no-repeat;
} }
\ No newline at end of file
...@@ -225,7 +225,14 @@ ...@@ -225,7 +225,14 @@
margin-top: 15px; margin-top: 15px;
} }
#mainCatch .playMediaButton { #mainCatch .zoomToImageBounds {
width: 20em;
text-align:center;
margin-top: 15px;
margin-bottom: 5px;
}
#mainCatch .playMediaButton, #mainCatch .zoomToImageBounds {
border-top: 2px solid #d3bd89; border-top: 2px solid #d3bd89;
border-bottom: 2px solid #b3ad69; border-bottom: 2px solid #b3ad69;
border-right: 2px solid #b3ad69; border-right: 2px solid #b3ad69;
...@@ -242,7 +249,7 @@ ...@@ -242,7 +249,7 @@
border-radius: 24px; border-radius: 24px;
color: #000000; color: #000000;
} }
#mainCatch .playMediaButton:hover { #mainCatch .playMediaButton:hover, #mainCatch .zoomToImageBounds:hover {
border-top: 2px solid #d3bd89; border-top: 2px solid #d3bd89;
border-bottom: 2px solid #b3ad69; border-bottom: 2px solid #b3ad69;
border-right: 2px solid #b3ad69; border-right: 2px solid #b3ad69;
...@@ -250,7 +257,7 @@ ...@@ -250,7 +257,7 @@
background: #f5c105; background: #f5c105;
color: #080708; color: #080708;
} }
#mainCatch .playMediaButton:active { #mainCatch .playMediaButton:active, #mainCatch .zoomToImageBounds:active {
border-top: 2px solid #d3bd89; border-top: 2px solid #d3bd89;
border-bottom: 2px solid #b3ad69; border-bottom: 2px solid #b3ad69;
border-right: 2px solid #b3ad69; border-right: 2px solid #b3ad69;
......
...@@ -101,4 +101,4 @@ Annotator.Plugin.Flagging = (function(_super) { ...@@ -101,4 +101,4 @@ Annotator.Plugin.Flagging = (function(_super) {
return Flagging; return Flagging;
})(Annotator.Plugin); })(Annotator.Plugin);
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -82,6 +82,7 @@ Annotator.Plugin.Reply = (function(_super) { ...@@ -82,6 +82,7 @@ Annotator.Plugin.Reply = (function(_super) {
var string; var string;
return self; return self;
}); });
field.remove();
this.annotation = annotation; this.annotation = annotation;
//Create the actions for the buttons //Create the actions for the buttons
return ret; return ret;
......
...@@ -38,7 +38,7 @@ Annotator.Plugin.RichText = (function(_super) { ...@@ -38,7 +38,7 @@ Annotator.Plugin.RichText = (function(_super) {
} }
}, },
codemirror: { codemirror: {
path: "static/js/vendor" path: "/static/js/vendor"
}, },
plugins: "image link codemirror", plugins: "image link codemirror",
menubar: false, menubar: false,
...@@ -78,7 +78,6 @@ Annotator.Plugin.RichText = (function(_super) { ...@@ -78,7 +78,6 @@ Annotator.Plugin.RichText = (function(_super) {
annotator.subscribe("annotationEditorShown", function(){ annotator.subscribe("annotationEditorShown", function(){
$(annotator.editor.element).find('.mce-tinymce')[0].style.display='block'; $(annotator.editor.element).find('.mce-tinymce')[0].style.display='block';
$(annotator.editor.element).find('.mce-container').css('z-index',3000000000);
annotator.editor.checkOrientation(); annotator.editor.checkOrientation();
}); });
annotator.subscribe("annotationEditorHidden", function(){ annotator.subscribe("annotationEditorHidden", function(){
...@@ -91,9 +90,6 @@ Annotator.Plugin.RichText = (function(_super) { ...@@ -91,9 +90,6 @@ Annotator.Plugin.RichText = (function(_super) {
//set the modification in the textarea of annotator //set the modification in the textarea of annotator
$(editor.element).find('textarea')[0].value = tinymce.activeEditor.getContent(); $(editor.element).find('textarea')[0].value = tinymce.activeEditor.getContent();
}); });
ed.on('Init', function(ed){
$('.mce-container').css('z-index','3090000000000000000');
});
//New button to add Rubrics of the url https://gteavirtual.org/rubric //New button to add Rubrics of the url https://gteavirtual.org/rubric
ed.addButton('rubric', { ed.addButton('rubric', {
icon: 'rubric', icon: 'rubric',
......
...@@ -845,6 +845,8 @@ main_vendor_js = [ ...@@ -845,6 +845,8 @@ main_vendor_js = [
'js/vendor/ova/tags-annotator.js', 'js/vendor/ova/tags-annotator.js',
'js/vendor/ova/flagging-annotator.js', 'js/vendor/ova/flagging-annotator.js',
'js/vendor/ova/jquery-Watch.js', 'js/vendor/ova/jquery-Watch.js',
'js/vendor/ova/openseadragon.js',
'js/vendor/ova/OpenSeaDragonAnnotation.js',
'js/vendor/ova/ova.js', 'js/vendor/ova/ova.js',
'js/vendor/ova/catch/js/catch.js', 'js/vendor/ova/catch/js/catch.js',
'js/vendor/ova/catch/js/handlebars-1.1.2.js', 'js/vendor/ova/catch/js/handlebars-1.1.2.js',
......
<%! from django.utils.translation import ugettext as _ %>
<style type="text/css">
.openseadragon1{
width: 100%;
height: 600px;
cursor: all-scroll;
}
</style>
<div class="annotatable-wrapper">
<div class="annotatable-header">
% if display_name is not UNDEFINED and display_name is not None:
<div class="annotatable-title">${display_name}</div>
% endif
</div>
% if instructions_html is not UNDEFINED and instructions_html is not None:
<div class="annotatable-section shaded">
<div class="annotatable-section-title">
${_('Instructions')}
<a class="annotatable-toggle annotatable-toggle-instructions expanded" href="javascript:void(0)">${_('Collapse Instructions')}</a>
</div>
<div class="annotatable-section-body annotatable-instructions">
${instructions_html}
</div>
</div>
% endif
<div class="annotatable-section">
<div class="annotatable-content">
<div id="imageHolder" class="openseadragon1">
<%namespace name='static' file='/static_content.html'/>
${static.css(group='style-vendor-tinymce-content', raw=True)}
${static.css(group='style-vendor-tinymce-skin', raw=True)}
<script type="text/javascript" src="${static.url('js/vendor/tinymce/js/tinymce/tinymce.full.min.js', raw=True)}" />
<script type="text/javascript" src="${static.url('js/vendor/tinymce/js/tinymce/jquery.tinymce.min.js', raw=True)}" />
</div>
<div id="catchDIV">
## Translators: Notes below refer to annotations. They wil later be put under a "Notes" section.
<div class="annotationListContainer">${_('You do not have any notes.')}</div>
</div>
</div>
</div>
</div>
<script>
function onClickHideInstructions(){
//Reset function if there is more than one event handler
$(this).off();
$(this).on('click',onClickHideInstructions);
var hide = $(this).html()=='Collapse Instructions'?true:false,
cls, txt,slideMethod;
txt = (hide ? 'Expand' : 'Collapse') + ' Instructions';
cls = (hide ? ['expanded', 'collapsed'] : ['collapsed', 'expanded']);
slideMethod = (hide ? 'slideUp' : 'slideDown');
$(this).text(txt).removeClass(cls[0]).addClass(cls[1]);
$(this).parents('.annotatable-section:first').find('.annotatable-instructions')[slideMethod]();
}
$('.annotatable-toggle-instructions').on('click', onClickHideInstructions);
//Grab uri of the course
var parts = window.location.href.split("/"),
uri = '',
courseid;
for (var index = 0; index <= 9; index += 1) uri += parts[index]+"/"; //Get the unit url
courseid = parts[4] + "/" + parts[5] + "/" + parts[6];
//Change uri in cms
var lms_location = $('.sidebar .preview-button').attr('href');
if (typeof lms_location!='undefined'){
courseid = parts[4].split(".").join("/");
uri = window.location.protocol;
for (var index = 0; index <= 9; index += 1) uri += lms_location.split("/")[index]+"/"; //Get the unit url
}
var unit_id = $('#sequence-list').find('.active').attr("data-element");
uri += unit_id;
var pagination = 100,
is_staff = !('${user.is_staff}'=='False'),
options = {
optionsAnnotator: {
permissions:{
user: {
id:"${user.email}",
name:"${user.username}"
},
userString: function (user) {
if (user && user.name)
return user.name;
return user;
},
userId: function (user) {
if (user && user.id)
return user.id;
return user;
},
permissions: {
'read': [],
'update': ["${user.email}"],
'delete': ["${user.email}"],
'admin': ["${user.email}"]
},
showViewPermissionsCheckbox: true,
showEditPermissionsCheckbox: false,
userAuthorize: function(action, annotation, user) {
var token, tokens, _i, _len;
if (annotation.permissions) {
tokens = annotation.permissions[action] || [];
if (is_staff){
return true;
}
if (tokens.length === 0) {
return true;
}
for (_i = 0, _len = tokens.length; _i < _len; _i++) {
token = tokens[_i];
if (this.userId(user) === token) {
return true;
}
}
return false;
} else if (annotation.user) {
if (user) {
return this.userId(user) === this.userId(annotation.user);
} else {
return false;
}
}
return true;
},
},
auth: {
token: "${token}"
},
store: {
// The endpoint of the store on your server.
prefix: "${annotation_storage}",
annotationData: {
uri: uri,
},
urls: {
// These are the default URLs.
create: '/create',
read: '/read/:id',
update: '/update/:id',
destroy: '/delete/:id',
search: '/search'
},
loadFromSearch:{
limit:pagination,
offset:0,
uri:uri,
media:'image',
userid:'${user.email}',
}
},
highlightTags:{
tag: "${tag}",
},
richText: {
tinymce:{
selector: "li.annotator-item textarea",
plugins: "image codemirror",
menubar: false,
toolbar_items_size: 'small',
extended_valid_elements : "iframe[src|frameborder|style|scrolling|class|width|height|name|align|id]",
toolbar: "insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | image rubric | code ",
}
},
},
optionsOpenSeadragon:{
id: "imageHolder",
prefixUrl: "${settings.STATIC_URL}" + "js/vendor/ova/images/",
${openseadragonjson}
},
optionsOSDA:{},
};
tinymce.baseURL = "${settings.STATIC_URL}" + "js/vendor/tinymce/js/tinymce";
var imgURLRoot = "${settings.STATIC_URL}" + "js/vendor/ova/catch/img/";
if (typeof Annotator != 'undefined'){
//remove old instances
if (Annotator._instances.length !== 0) {
$('#imageHolder').annotator("destroy");
}
delete osda;
//Load the plugin Image/Text Annotation
var osda = new OpenSeadragonAnnotation($('#imageHolder'),options);
//Catch
var annotator = osda.annotator,
catchOptions = {
media:'image',
externalLink:false,
imageUrlRoot:imgURLRoot,
showMediaSelector: false,
showPublicPrivate: true,
userId:'${user.email}',
pagination:pagination,//Number of Annotations per load in the pagination,
flags:is_staff
},
Catch = new CatchAnnotation($('#catchDIV'),catchOptions);
}
</script>
\ No newline at end of file
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