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>
Sébastien Hinderer <Sebastien.Hinderer@inria.fr>
Kristin Stephens <ksteph@cs.berkeley.edu>
Ben Patterson <bpatterson@edx.org>
Luis Duarte <lduarte1991@gmail.com>
......@@ -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
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):
# 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'],
)
......
......@@ -54,6 +54,7 @@ else:
'annotatable',
'textannotation', # module for annotating text (with annotation table)
'videoannotation', # module for annotating video (with annotation table)
'imageannotation', # module for annotating image (with annotation table)
'word_cloud',
'graphical_slider_tool',
'lti',
......
......@@ -36,6 +36,7 @@ XMODULES = [
"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",
......
"""
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):
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
# 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)
......
"""
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):
"""
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"
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.assertNotEqual(expected.split('.')[2], response.split('.')[2])
\ No newline at end of file
self.assertNotEqual(expected.split('.')[2], response.split('.')[2])
# -*- 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):
"""
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']:
self.assertIn(key, context)
......@@ -6,6 +6,7 @@ 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
from xmodule.annotator_token import retrieve_token
import textwrap
......@@ -70,12 +71,7 @@ class TextAnnotationModule(AnnotatableFields, XModule):
def _extract_instructions(self, 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
return get_instructions(xmltree)
def get_html(self):
""" Renders parameters to template. """
......
......@@ -7,6 +7,7 @@ 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, get_extension
from xmodule.annotator_token import retrieve_token
import textwrap
......@@ -65,24 +66,11 @@ class VideoAnnotationModule(AnnotatableFields, XModule):
def _extract_instructions(self, 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(self, srcurl):
return get_instructions(xmltree)
def _get_extension(self, src_url):
''' get the extension of a given url '''
if 'youtu' in srcurl:
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]
return get_extension(src_url)
def get_html(self):
""" Renders parameters to template. """
......
......@@ -16,10 +16,22 @@
font-style: italic;
}
.annotator-wrapper .mce-container {
z-index:3000000000!important; /*To fix full-screen problems*/
.mce-container-body {
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 */
.annotator-editor .annotator-widget{
......@@ -30,4 +42,4 @@
.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-repeat: no-repeat;
}
}
\ No newline at end of file
/*
OpenSeaDragonAnnotation v1.0 (http://)
Copyright (C) 2014 CHS (Harvard University), Daniel Cebrián Robles and Phil Desenne
License: https://github.com/CtrHellenicStudies/OpenSeaDragonAnnotation/blob/master/License.rst
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
(function($) {
$.Viewer.prototype.annotation = function(options) {
//-- wait for plugins --//
var wrapper = jQuery('.annotator-wrapper').parent()[0],
annotator = jQuery.data(wrapper, 'annotator'),
self = this,
isOpenViewer = false;
this.addHandler("open", function() {
isOpenViewer = true;
if (typeof self.annotationInstance!='undefined')
self.annotationInstance.refreshDisplay();
});
annotator
//-- Finished the Annotator DOM
.subscribe("annotationsLoaded", function (annotations){
if (!self.annotationInstance) {
self.annotationInstance = new $._annotation({
viewer: self,
annotator: annotator,
});
annotator.osda = self.annotationInstance;
//Wait until viewer is opened
function refreshDisplay(){
if(!isOpenViewer){
setTimeout(refreshDisplay,200);
}else{
self.annotationInstance.refreshDisplay();
}
}
refreshDisplay();
} else {
self.annotationInstance.refreshDisplay();
}
});
};
// INIT annotation
$._annotation = function(options) {
//options
options = options || {};
if (!options.viewer) {
throw new Error("A viewer must be specified.");
}
//variables
this.viewer = options.viewer;
this.annotator = options.annotator;
this.options = options;
this.isAnnotating = false; //If the user is annotating
this.isDrawing = false; //if the user is drawing something
this.rectPosition = undefined;
//Init
this.init();
};
//-- Methods
$._annotation.prototype = {
init: function(){
var viewer = this.viewer;
//create Buttons
this._createNewButton();
/* canvas Events */
//- Bind canvas functions
var onCanvasMouseDown = this.__bind(this._onCanvasMouseDown,this);
var onCanvasMouseMove = this.__bind(this._onCanvasMouseMove,this);
var onDocumentMouseUp = this.__bind(this._onDocumentMouseUp,this);
//- Add canvas events
$.addEvent(viewer.canvas, "mousedown", onCanvasMouseDown, true);
$.addEvent(viewer.canvas, "mousemove", onCanvasMouseMove, true);
$.addEvent(document, "mouseup", onDocumentMouseUp, true);
//Viewer events
var self = this;
},
newAnnotation:function(){
var annotator = this.annotator;
//This variable is to say the editor that we want create an image annotation
annotator.editor.OpenSeaDragon = this.viewer.id;
annotator.adder.show();
this._setOverShape(annotator.adder);
//Open a new annotator dialog
annotator.onAdderClick();
},
editAnnotation: function(annotation,editor){
//This will be usefull when we are going to edit an annotation.
if (this._isOpenSeaDragon(annotation)){
//this.hideDisplay();
var editor = editor || this.annotator.editor;
//set the editor over the range slider
this._setOverShape(editor.element);
editor.checkOrientation();
//This variable is to say the editor that we want create an image annotation
editor.OpenSeaDragon = this.viewer.id;
}
},
refreshDisplay: function(){
var allannotations = this.annotator.plugins['Store'].annotations;
var annotator = this.annotator;
//Sort by date the Array
this._sortByDate(allannotations);
//remove all the overlays
this.viewer.drawer.clearOverlays();
for (var item in allannotations) {
var an = allannotations[item];
//check if the annotation is an OpenSeaDragon annotation
if (this._isOpenSeaDragon(an))
this.drawRect(an);
annotator.publish('colorizeHighlight', [an]);
};
},
modeAnnotation:function(e){
this._reset();
var viewer = this.viewer;
if (!this.isAnnotating){
jQuery('.openseadragon1').css('cursor', 'crosshair');
jQuery('.openseadragon1').css('border', '2px solid rgb(51,204,102)');
e.eventSource.imgGroup.src = this.resolveUrl( viewer.prefixUrl,"newan_hover.png");
e.eventSource.imgRest.src = this.resolveUrl( viewer.prefixUrl,"newan_hover.png");
e.eventSource.imgHover.src = this.resolveUrl( viewer.prefixUrl,"newan_grouphover.png");
}else{
jQuery('.openseadragon1').css('cursor', 'all-scroll');
jQuery('.openseadragon1').css('border', 'inherit');
e.eventSource.imgGroup.src = this.resolveUrl( viewer.prefixUrl,"newan_grouphover.png");
e.eventSource.imgRest.src = this.resolveUrl( viewer.prefixUrl,"newan_rest.png");
e.eventSource.imgHover.src = this.resolveUrl( viewer.prefixUrl,"newan_hover.png");
}
this.isAnnotating = !this.isAnnotating?true:false;
},
drawRect:function(an){
if (typeof an.rangePosition!='undefined'){
var span = document.createElement('span');
var rectPosition = an.rangePosition;
//Span
span.className = "annotator-hl";
span.style.border = '1px solid rgba(0,0,0,0.5)';
var onAnnotationMouseMove = this.__bind(this._onAnnotationMouseMove,this);
var onAnnotationClick = this.__bind(this._onAnnotationClick,this);
$.addEvent(span, "mousemove", onAnnotationMouseMove, true);
$.addEvent(span, "click", onAnnotationClick, true);
//Set the object in the div
jQuery.data(span, 'annotation', an);
//Add the highlights to the annotation
an.highlights = jQuery(span);
var olRect = new OpenSeadragon.Rect(rectPosition.left, rectPosition.top, rectPosition.width, rectPosition.height);
return this.viewer.drawer.addOverlay({
element: span,
location: olRect,
placement: OpenSeadragon.OverlayPlacement.TOP_LEFT
});
}
return false;
},
//Change object(this.rectPosition)the rectangle Position using div element(this.rect)
setRectPosition:function(){
var left = parseInt(this.rect.style.left);
var top = parseInt(this.rect.style.top);
var width = parseInt(this.rect.style.left)+parseInt(this.rect.style.width);
var height = parseInt(this.rect.style.top)+parseInt(this.rect.style.height);
var startPoint = new $.Point(left,top);
var endPoint = new $.Point(width,height);
this.rectPosition = {left:this._physicalToLogicalXY(startPoint).x,
top:this._physicalToLogicalXY(startPoint).y,
width:this._physicalToLogicalXY(endPoint).x-this._physicalToLogicalXY(startPoint).x,
height:this._physicalToLogicalXY(endPoint).y-this._physicalToLogicalXY(startPoint).y
};
},
/* Handlers */
_onCanvasMouseDown: function(event,seft) {
if (this.isAnnotating){
var viewer = this.viewer;
event.preventDefault();
//reset the display
this._reset();
//set mode drawing
this.isDrawing = true;
//Create rect element
var mouse = $.getMousePosition( event );
var elementPosition = $.getElementPosition(viewer.canvas);
var position = mouse.minus( elementPosition );
viewer.innerTracker.setTracking(false);
this.rect = document.createElement('div');
this.rect.style.background = 'rgba(0,0,0,0.25)';
this.rect.style.border = '1px solid rgba(0,0,0,0.5)';
this.rect.style.position = 'absolute';
this.rect.className = 'DrawingRect';
//set the initial position
this.rect.style.top = position.y+"px";
this.rect.style.left = position.x+"px";
this.rect.style.width = "1px";
this.rect.style.height = "1px";
//save the start Position
this.startPosition = position;
//save rectPosition as initial rectangle parameter to Draw in the canvas
this.setRectPosition();
//append Child to the canvas
viewer.canvas.appendChild(this.rect);
}
},
_onCanvasMouseMove: function(event) {
if (this.isAnnotating && this.isDrawing){
var viewer = this.viewer;
//Calculate the new end position
var mouse = $.getMousePosition( event );
var elementPosition = $.getElementPosition(viewer.canvas);
var endPosition = mouse.minus( elementPosition );
//retrieve start position
var startPosition = this.startPosition;
var newWidth= endPosition.x-startPosition.x;
var newHeight =endPosition.y-startPosition.y;
//Set new position
this.rect.style.width = (newWidth<0) ? (-1*newWidth) +'px' : newWidth +'px';
this.rect.style.left = (newWidth<0) ? (startPosition.x + newWidth) +'px' : startPosition.x +'px';
this.rect.style.height = (newHeight<0) ? (-1*newHeight) +'px' : newHeight +'px';
this.rect.style.top = (newHeight<0) ? (startPosition.y + newHeight) +'px' : startPosition.y +'px';
//Modify the rectPosition with the new this.rect values
this.setRectPosition();
//Show adder and hide editor
this.annotator.editor.element[0].style.display = 'none';
this._setOverShape(this.annotator.adder);
}
},
_onDocumentMouseUp: function() {
if (this.isAnnotating && this.isDrawing){
var viewer = this.viewer;
viewer.innerTracker.setTracking(true);
this.isDrawing = false;
//Set the new position for the rectangle
this.setRectPosition();
//Open Annotator editor
this.newAnnotation();
//Hide adder and show editor
this.annotator.editor.element[0].style.display = 'block';
this._setOverShape(this.annotator.editor.element);
this.annotator.editor.checkOrientation();
}
},
_onAnnotationMouseMove: function(event){
var annotator = this.annotator;
var elem = jQuery(event.target).parents('.annotator-hl').andSelf();
//if there is a opened annotation then show the new annotation mouse over
if (typeof annotator!='undefined' && elem.hasClass("annotator-hl") && !this.isDrawing){
//hide the last open viewer
annotator.viewer.hide();
//get the annotation over the mouse
var annotations = jQuery(event.target.parentNode).find('.annotator-hl').map(function() {
var self = jQuery(this);
var offset = self.offset();
var l = offset.left;
var t = offset.top;
var h = self.height();
var w = self.width();
var x = $.getMousePosition(event).x;
var y = $.getMousePosition(event).y;
var maxx = l + w;
var maxy = t + h;
this.style.background = (y <= maxy && y >= t) && (x <= maxx && x >= l)?
'rgba(12, 150, 0, 0.3)':'rgba(255, 255, 10, 0.3)';
return (y <= maxy && y >= t) && (x <= maxx && x >= l)? jQuery(this).data("annotation") : null;
});
//show the annotation in the viewer
var mousePosition = {
top:$.getMousePosition(event).y,
left:$.getMousePosition(event).x,
};
if (annotations.length>0) annotator.showViewer(jQuery.makeArray(annotations), mousePosition);
}
},
_onAnnotationClick: function(event){
var an = jQuery.data(event.target, 'annotation');
var bounds = typeof an.bounds!='undefined'?an.bounds:{};
var currentBounds = this.viewer.drawer.viewport.getBounds();
if (typeof bounds.x!='undefined') currentBounds.x = bounds.x;
if (typeof bounds.y!='undefined') currentBounds.y = bounds.y;
if (typeof bounds.width!='undefined') currentBounds.width = bounds.width;
if (typeof bounds.height!='undefined') currentBounds.height = bounds.height;
//change the zoom to the saved
this.viewer.drawer.viewport.fitBounds(currentBounds);
},
_onAnnotationMouseOut: function(event){
var annotator = this.annotator;
var elem = jQuery(event.target).parents('.annotator-hl').andSelf();
//if there is a opened annotation then show the new annotation mouse over
if (typeof annotator!='undefined' && elem.hasClass("annotator-hl") && !this.isDrawing){
/*jQuery(event.target.parentNode).find('.annotator-hl').map(function() {
return this.style.background = 'rgba(255, 255, 10, 0.3)';
});*/
}
},
/* Utilities */
_sortByDate: function (annotations,type){
var type = type || 'asc'; //asc => The value [0] will be the most recent date
annotations.sort(function(a,b){
a = new Date(typeof a.updated!='undefined'?createDateFromISO8601(a.updated):'');
b = new Date(typeof b.updated!='undefined'?createDateFromISO8601(b.updated):'');
if (type == 'asc')
return b<a?-1:b>a?1:0;
else
return a<b?-1:a>b?1:0;
});
},
_createNewButton:function(){
var viewer = this.viewer;
var onFocusHandler = $.delegate( this, onFocus );
var onBlurHandler = $.delegate( this, onBlur );
var onModeAnnotationHandler = $.delegate( this, this.modeAnnotation );
/* Buttons */
var viewer = this.viewer;
var self = this;
viewer.modeAnnotation = new $.Button({
element: viewer.modeAnnotation ? $.getElement( viewer.modeAnnotation ) : null,
clickTimeThreshold: viewer.clickTimeThreshold,
clickDistThreshold: viewer.clickDistThreshold,
tooltip: "New Annotation",
srcRest: self.resolveUrl( viewer.prefixUrl,"newan_rest.png"),
srcGroup: self.resolveUrl( viewer.prefixUrl,"newan_grouphover.png"),
srcHover: self.resolveUrl( viewer.prefixUrl,"newan_hover.png"),
srcDown: self.resolveUrl( viewer.prefixUrl,"newan_pressed.png"),
onRelease: onModeAnnotationHandler,
onFocus: onFocusHandler,
onBlur: onBlurHandler
});
//- Wrapper Annotation Menu
viewer.wrapperAnnotation = new $.ButtonGroup({
buttons: [
viewer.modeAnnotation,
],
clickTimeThreshold: viewer.clickTimeThreshold,
clickDistThreshold: viewer.clickDistThreshold
});
/* Set elements to the control menu */
viewer.annotatorControl = viewer.wrapperAnnotation.element;
if( viewer.toolbar ){
viewer.toolbar.addControl(
viewer.annotatorControl,
{anchor: $.ControlAnchor.BOTTOM_RIGHT}
);
}else{
viewer.addControl(
viewer.annotatorControl,
{anchor: $.ControlAnchor.TOP_LEFT}
);
}
},
_reset: function(){
//Find and remove DrawingRect. This is the previous rectangle
this._removeElemsByClass('DrawingRect',this.viewer.canvas);
//Show adder and hide editor
this.annotator.editor.element[0].style.display = 'none';
},
__bind: function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
// Remove all the elements with a given name inside "inElement"
_removeElemsByClass: function(className,inElement){
var className = className || '';
var inElement = inElement || {};
divs = inElement.getElementsByClassName(className);
for(var i = 0; i < divs.length; i++) {
divs[i].remove();
}
},
//Detect if the annotation is an image annotation
_isOpenSeaDragon: function (an){
var annotator = this.annotator;
var rp = an.rangePosition;
var isOpenSeaDragon = (typeof annotator.osda != 'undefined');
var isContainer = (typeof an.target!='undefined' && an.target.container==this.viewer.id );
var isImage = (typeof an.media!='undefined' && an.media=='image');
var isRP = (typeof rp!='undefined');
var isSource = false;
//Save source url
var source = this.viewer.source;
var tilesUrl = typeof source.tilesUrl!='undefined'?source.tilesUrl:'';
var functionUrl = typeof source.getTileUrl!='undefined'?source.getTileUrl:'';
var compareUrl = tilesUrl!=''?tilesUrl:(''+functionUrl).replace(/\s+/g, ' ');
if(isContainer) isSource = (an.target.src == compareUrl);
return (isOpenSeaDragon && isContainer && isImage && isRP && isSource);
},
/* Annotator Utilities */
_setOverShape: function(elem){
//Calculate Point absolute positions
var rectPosition = this.rectPosition || {};
var startPoint = this._logicalToPhysicalXY(new $.Point(rectPosition.left,rectPosition.top));
var endPoint = this._logicalToPhysicalXY(new $.Point(rectPosition.left+rectPosition.width,rectPosition.top+rectPosition.height));
//Calculate Point absolute positions
var wrapper = jQuery('.annotator-wrapper')[0];
var positionAnnotator = $.getElementPosition(wrapper);
var positionCanvas = $.getElementPosition(this.viewer.canvas);
var positionAdder = {};
//Fix with positionCanvas
startPoint = startPoint.plus(positionCanvas);
endPoint = endPoint.plus(positionCanvas);
elem[0].style.display = 'block'; //Show the adder
positionAdder.left = (startPoint.x - positionAnnotator.x) + (endPoint.x - startPoint.x) / 2;
positionAdder.top = (startPoint.y - positionAnnotator.y) + (endPoint.y - startPoint.y) / 2; //It is not necessary fix with - positionAnnotator.y
elem.css(positionAdder);
},
resolveUrl: function( prefix, url ) {
return prefix ? prefix + url : url;
},
/* Canvas Utilities */
// return a point with the values in percentage related to the Image
// point is an object $.Point with the value of the canvas relative coordenates
_physicalToLogicalXY: function(point){
var point = typeof point!='undefined'?point:{};
var boundX = this.viewer.viewport.getBounds(true).x;
var boundY = this.viewer.viewport.getBounds(true).y;
var boundWidth = this.viewer.viewport.getBounds(true).width;
var boundHeight = this.viewer.viewport.getBounds(true).height;
var containerSizeX = this.viewer.viewport.getContainerSize().x;
var containerSizeY = this.viewer.viewport.getContainerSize().y;
var x = typeof point.x!='undefined'?point.x:0;
var y = typeof point.y!='undefined'?point.y:0;
x = boundX + ((x / containerSizeX) * boundWidth);
y = boundY + ((y / containerSizeY) * boundHeight);
return new $.Point(x,y);
},
// return a point with the values in pixels related to the canvas element
// point is an object $.Point with the value of the Image relative percentage
_logicalToPhysicalXY: function(point){
var point = typeof point!='undefined'?point:{};
var boundX = this.viewer.viewport.getBounds(true).x;
var boundY = this.viewer.viewport.getBounds(true).y;
var boundWidth = this.viewer.viewport.getBounds(true).width;
var boundHeight = this.viewer.viewport.getBounds(true).height;
var containerSizeX = this.viewer.viewport.getContainerSize().x;
var containerSizeY = this.viewer.viewport.getContainerSize().y;
var x = typeof point.x!='undefined'?point.x:0;
var y = typeof point.y!='undefined'?point.y:0;
x = (x - boundX) * containerSizeX / boundWidth;
y = (y - boundY) * containerSizeY / boundHeight;
return new $.Point(x,y);
},
}
/* General functions */
//initiates an animation to hide the controls
function beginControlsAutoHide( viewer ) {
if ( !viewer.autoHideControls ) {
return;
}
viewer.controlsShouldFade = true;
viewer.controlsFadeBeginTime =
$.now() +
viewer.controlsFadeDelay;
window.setTimeout( function(){
scheduleControlsFade( viewer );
}, viewer.controlsFadeDelay );
}
//stop the fade animation on the controls and show them
function abortControlsAutoHide( viewer ) {
var i;
viewer.controlsShouldFade = false;
for ( i = viewer.controls.length - 1; i >= 0; i-- ) {
viewer.controls[ i ].setOpacity( 1.0 );
}
}
function onFocus(){
abortControlsAutoHide( this.viewer );
}
function onBlur(){
beginControlsAutoHide( this.viewer );
}
})(OpenSeadragon);
//----------------Plugin for Annotator to setup OpenSeaDragon----------------//
Annotator.Plugin.OpenSeaDragon = (function(_super) {
__extends(OpenSeaDragon, _super);
//constructor
function OpenSeaDragon() {
this.pluginSubmit = __bind(this.pluginSubmit, this);
_ref = OpenSeaDragon.__super__.constructor.apply(this, arguments);
this.__indexOf = [].indexOf;
if(!this.__indexOf){
this.__indexOf = function(item) {
for (var i = 0, l = this.length; i < l; i++) {
if (i in this && this[i] === item)
return i;
}
return -1;
}
}
return _ref;
}
OpenSeaDragon.prototype.field = null;
OpenSeaDragon.prototype.input = null;
OpenSeaDragon.prototype.pluginInit = function() {
//Check that annotator is working
if (!Annotator.supported()) {
return;
}
//-- Editor
this.field = this.annotator.editor.addField({
id: 'osd-input-rangePosition-annotations',
type: 'input', //options (textarea,input,select,checkbox)
submit: this.pluginSubmit,
EditOpenSeaDragonAn: this.EditOpenSeaDragonAn
});
//Modify the element created with annotator to be an invisible span
var select = '<li><span id="osd-input-rangePosition-annotations"></span></li>';
var newfield = Annotator.$(select);
Annotator.$(this.field).replaceWith(newfield);
this.field=newfield[0];
//-- Listener for OpenSeaDragon Plugin
this.initListeners();
return this.input = $(this.field).find(':input');
}
// New JSON for the database
OpenSeaDragon.prototype.pluginSubmit = function(field, annotation) {
//Select the new JSON for the Object to save
if (this.EditOpenSeaDragonAn()){
var annotator = this.annotator;
var osda = annotator.osda;
var position = osda.rectPosition;
var isNew = typeof annotation.media=='undefined';
if (typeof annotation.media == 'undefined') annotation.media = "image"; // - media
annotation.target = annotation.target || {}; // - target
annotation.target.container = osda.viewer.id || ""; // - target.container
//Save source url
var source = osda.viewer.source;
var tilesUrl = typeof source.tilesUrl!='undefined'?source.tilesUrl:'';
var functionUrl = typeof source.getTileUrl!='undefined'?source.getTileUrl:'';
annotation.target.src = tilesUrl!=''?tilesUrl:(''+functionUrl).replace(/\s+/g, ' '); // - target.src (media source)
annotation.target.ext = source.fileFormat || ""; // - target.ext (extension)
annotation.bounds = osda.viewer.drawer.viewport.getBounds() || {}; // - bounds
var finalimagelink = source["@id"].replace("/info.json", "");
var highlightX = Math.round(position.left * source["width"]);
var highlightY = Math.round(position.top * source["width"]);
var highlightWidth = Math.round(position.width * source["width"]);
var highlightHeight = Math.round(position.height * source["width"]);
annotation.target.thumb = finalimagelink + "/" + highlightX + "," + highlightY + "," + highlightWidth + "," + highlightHeight + "/full/0/native." + source["formats"][0];
if(isNew) annotation.rangePosition = position || {}; // - rangePosition
annotation.updated = new Date().toISOString(); // - updated
if (typeof annotation.created == 'undefined')
annotation.created = annotation.updated; // - created
}else{
if (typeof annotation.media == 'undefined')
annotation.media = "text"; // - media
annotation.updated = new Date().toISOString(); // - updated
if (typeof annotation.created == 'undefined')
annotation.created = annotation.updated; // - created
}
return annotation.media;
};
//------ Methods ------//
//Detect if we are creating or editing an OpenSeaDragon annotation
OpenSeaDragon.prototype.EditOpenSeaDragonAn = function (){
var wrapper = $('.annotator-wrapper').parent()[0],
annotator = window.annotator = $.data(wrapper, 'annotator'),
isOpenSeaDragon = (typeof annotator.osda != 'undefined'),
OpenSeaDragon = annotator.editor.OpenSeaDragon;
return (isOpenSeaDragon && typeof OpenSeaDragon!='undefined' && OpenSeaDragon!==-1);
};
//Detect if the annotation is an OpenSeaDragon annotation
OpenSeaDragon.prototype.isOpenSeaDragon = function (an){
var wrapper = $('.annotator-wrapper').parent()[0];
var annotator = window.annotator = $.data(wrapper, 'annotator');
var rp = an.rangePosition;
var isOpenSeaDragon = (typeof annotator.osda != 'undefined');
var isContainer = (typeof an.target!='undefined' && an.target.container==annotator.osda.viewer.id );
var isImage = (typeof an.media!='undefined' && an.media=='image');
var isRP = (typeof rp!='undefined');
var isSource = false;
//Save source url
var source = annotator.osda.viewer.source;
var tilesUrl = typeof source.tilesUrl!='undefined'?source.tilesUrl:'';
var functionUrl = typeof source.getTileUrl!='undefined'?source.getTileUrl:'';
var compareUrl = tilesUrl!=''?tilesUrl:(''+functionUrl).replace(/\s+/g, ' ');
if(isContainer) isSource = (an.target.src == compareUrl);
return (isOpenSeaDragon && isContainer && isImage && isRP && isSource);
};
//Delete OpenSeaDragon Annotation
OpenSeaDragon.prototype._deleteAnnotation = function(an){
//Remove the annotation of the plugin Store
var annotations = this.annotator.plugins['Store'].annotations;
if (annotations.indexOf(an)>-1)
annotations.splice(annotations.indexOf(an), 1);
//Refresh the annotations in the display
this.annotator.osda.refreshDisplay();
};
//--Listeners
OpenSeaDragon.prototype.initListeners = function (){
var wrapper = $('.annotator-wrapper').parent()[0];
var annotator = $.data(wrapper, 'annotator');
var EditOpenSeaDragonAn = this.EditOpenSeaDragonAn;
var isOpenSeaDragon = this.isOpenSeaDragon;
var self = this;
//local functions
//-- Editor
function annotationEditorHidden(editor) {
if (EditOpenSeaDragonAn()){
annotator.osda._reset();
annotator.osda.refreshDisplay(); //Reload the display of annotations
}
annotator.editor.OpenSeaDragon=-1;
annotator.unsubscribe("annotationEditorHidden", annotationEditorHidden);
};
function annotationEditorShown(editor,annotation) {
annotator.osda.editAnnotation(annotation,editor);
annotator.subscribe("annotationEditorHidden", annotationEditorHidden);
};
//-- Annotations
function annotationDeleted(annotation) {
if (isOpenSeaDragon(annotation))
self._deleteAnnotation(annotation);
};
//-- Viewer
function hideViewer(){
jQuery(annotator.osda.viewer.canvas.parentNode).find('.annotator-hl').map(function() {
return this.style.background = 'rgba(255, 255, 10, 0.3)';
});
annotator.viewer.unsubscribe("hide", hideViewer);
};
function annotationViewerShown(viewer,annotations) {
var wrapper = jQuery('.annotator-wrapper').offset();
//Fix with positionCanvas
var startPoint = {x: parseFloat(viewer.element[0].style.left),
y: parseFloat(viewer.element[0].style.top)};
var separation = viewer.element.hasClass(viewer.classes.invert.y)?5:-5,
newpos = {
top: (startPoint.y - wrapper.top)+separation,
left: (startPoint.x - wrapper.left)
};
viewer.element.css(newpos);
//Remove the time to wait until disapear, to be more faster that annotator by default
viewer.element.find('.annotator-controls').removeClass(viewer.classes.showControls);
annotator.viewer.subscribe("hide", hideViewer);
};
//subscribe to Annotator
annotator.subscribe("annotationEditorShown", annotationEditorShown)
.subscribe("annotationDeleted", annotationDeleted)
.subscribe("annotationViewerShown", annotationViewerShown);
}
return OpenSeaDragon;
})(Annotator.Plugin);
//----------------PUBLIC OBJECT TO CONTROL THE ANNOTATIONS----------------//
//The name of the plugin that the user will write in the html
OpenSeadragonAnnotation = ("OpenSeadragonAnnotation" in window) ? OpenSeadragonAnnotation : {};
OpenSeadragonAnnotation = function (element, options) {
//local variables
var $ = jQuery;
var options = options || {};
options.optionsOpenSeadragon = options.optionsOpenSeadragon || {};
options.optionsOSDA = options.optionsOSDA || {};
options.optionsAnnotator = options.optionsAnnotator || {};
//if there isn't store optinos it will create a uri and limit variables for the Back-end of Annotations
if (typeof options.optionsAnnotator.store=='undefined')
options.optionsAnnotator.store = {};
var store = options.optionsAnnotator.store;
if (typeof store.annotationData=='undefined')
store.annotationData = {};
if (typeof store.annotationData.uri=='undefined'){
var uri = location.protocol + '//' + location.host + location.pathname;
store.annotationData.store = {uri:uri};
}
if (typeof store.loadFromSearch=='undefined')
store.loadFromSearch={};
if (typeof store.loadFromSearch.uri=='undefined')
store.loadFromSearch.uri = uri;
if (typeof store.loadFromSearch.limit=='undefined')
store.loadFromSearch.limit = 10000;
//global variables
this.currentUser = null;
//-- Init all the classes --/
//Annotator
this.annotator = $(element).annotator(options.optionsAnnotator.annotator).data('annotator');
//-- Activate all the Annotator plugins --//
if (typeof options.optionsAnnotator.auth!='undefined')
this.annotator.addPlugin('Auth', options.optionsAnnotator.auth);
if (typeof options.optionsAnnotator.permissions!='undefined')
this.annotator.addPlugin("Permissions", options.optionsAnnotator.permissions);
if (typeof options.optionsAnnotator.store!='undefined')
this.annotator.addPlugin("Store", options.optionsAnnotator.store);
if (typeof Annotator.Plugin["Geolocation"] === 'function')
this.annotator.addPlugin("Geolocation",options.optionsAnnotator.geolocation);
if (typeof Annotator.Plugin["Share"] === 'function')
this.annotator.addPlugin("Share",options.optionsAnnotator.share);
if (typeof Annotator.Plugin["RichText"] === 'function')
this.annotator.addPlugin("RichText",options.optionsAnnotator.richText);
if (typeof Annotator.Plugin["Reply"] === 'function')
this.annotator.addPlugin("Reply");
if (typeof Annotator.Plugin["OpenSeaDragon"] === 'function')
this.annotator.addPlugin("OpenSeaDragon");
if (typeof Annotator.Plugin["Flagging"] === 'function')
this.annotator.addPlugin("Flagging");
if (typeof Annotator.Plugin["HighlightTags"] === 'function')
this.annotator.addPlugin("HighlightTags", options.optionsAnnotator.highlightTags);
//- OpenSeaDragon
this.viewer = OpenSeadragon(options.optionsOpenSeadragon);
//- OpenSeaDragon Plugins
this.viewer.annotation(options.optionsOSDA);
//Set annotator.editor.OpenSeaDragon by default
this.annotator.editor.OpenSeaDragon=-1;
function reloadEditor(){
tinymce.EditorManager.execCommand('mceRemoveEditor',true, "annotator-field-0");
tinymce.EditorManager.execCommand('mceAddEditor',true, "annotator-field-0");
}
var self = this;
document.addEventListener("fullscreenchange", function () {
reloadEditor();
}, false);
document.addEventListener("mozfullscreenchange", function () {
reloadEditor();
}, false);
document.addEventListener("webkitfullscreenchange", function () {
reloadEditor();
}, false);
document.addEventListener("msfullscreenchange", function () {
reloadEditor();
}, false);
this.options = options;
return this;
}
\ No newline at end of file
......@@ -225,7 +225,14 @@
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-bottom: 2px solid #b3ad69;
border-right: 2px solid #b3ad69;
......@@ -242,7 +249,7 @@
border-radius: 24px;
color: #000000;
}
#mainCatch .playMediaButton:hover {
#mainCatch .playMediaButton:hover, #mainCatch .zoomToImageBounds:hover {
border-top: 2px solid #d3bd89;
border-bottom: 2px solid #b3ad69;
border-right: 2px solid #b3ad69;
......@@ -250,7 +257,7 @@
background: #f5c105;
color: #080708;
}
#mainCatch .playMediaButton:active {
#mainCatch .playMediaButton:active, #mainCatch .zoomToImageBounds:active {
border-top: 2px solid #d3bd89;
border-bottom: 2px solid #b3ad69;
border-right: 2px solid #b3ad69;
......
/*
Grid Annotation Plugin v1.0
Copyright (C) 2014 Daniel Cebrian Robles and Luis Duarte
License: https://github.com/danielcebrian/share-annotator/blob/master/License.rst
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
//The name of the plugin that the user will write in the html
window.CatchAnnotation = ("CatchAnnotation" in window) ? CatchAnnotation : {};
window.CatchSources = ("CatchSources" in window) ? CatchSources : {};
......@@ -76,13 +95,10 @@ annotationMediaSelector:
'<li class="ui-state-default" media="video">'+
'Video'+
'</li>'+
'li class="ui-state-default" media="image">'+
'Image'+
'</li>'+
'</ul>',
// '<div class="selButtonCatch">Text<span class="action">text</span></div>'+
// '<div class="selButtonCatch">Video<span class="action">video</span></div>',
// '<div class="selButtonCatch">Images<span class="action">image</span></div>'+
// '<div class="selButtonCatch">Audio<span class="action">audio</span></div>'+
// '<div class="selButtonCatch">Maps<span class="action">map</span></div>'+
// '<div class="selButtonCatch">3D studio<span class="action">3d</span></div>',
//Main->ContainerRow
annotationItem:
......@@ -162,7 +178,15 @@ annotationRow:
//Main->ContainerRow->DetailRow
annotationDetail:
'<div class="annotationDetail">'+
'{{#if mediatypeforgrid.text}}'+
'<div class="annotationDetail">'+
'{{/if}}'+
'{{#if mediatypeforgrid.video}}'+
'<div class="annotationDetail videoAnnotationDetail">'+
'{{/if}}'+
'{{#if mediatypeforgrid.image}}'+
'<div class="annotationDetail imageAnnotationDetail">'+
'{{/if}}'+
'<div class="detailHeader">'+
'<span class="closeDetailIcon">'+
'<img src="'+root+'closeIcon.png" alt="Hide Details" />'+
......@@ -181,77 +205,29 @@ annotationDetail:
'{{/if}}'+
'</div>'+
'{{#if mediatypeforgrid.text}}'+
'<div class="quote">'+
'<div style="text-align: center">'+
'<div class="quoteItem">“</div><div class="quoteText">{{{ quote }}}</div><div class="quoteItem">”</div></div>'+
'<span class="idAnnotation" style="display:none">{{{ id }}}</span>'+
'<span class="uri" style="display:none">{{{uri}}}</span>'+
'</div>'+
'<div class="body">'+
'{{{ text }}}'+
'</div>'+
'<div class="controlReplies">'+
'<div class="newReply" style="text-decoration:underline">Reply</div>&nbsp;'+
'<div class="hideReplies" style="text-decoration:underline;display:{{#if hasReplies}}block{{else}}none{{/if}}">Show Replies</div>&nbsp;'+
'{{#if authToEditButton}}'+
'<div class="editAnnotation" style="text-decoration:underline">Edit</div>'+
'{{/if}}'+
'{{#if authToDeleteButton}}'+
'<div class="deleteAnnotation" style="text-decoration:underline">Delete</div>'+
'{{/if}}'+
'</div>'+
'<div class="replies"></div>'+
'{{#if tags}}'+
'<div class="tags">'+
'<h3>Tags:</h3>'+
'{{#each tags}}'+
'<div class="tag">'+
'{{this}}'+
'</div>'+
'{{/each}}'+
'</div>'+
'{{/if}}'+
'<div class="controlPanel">'+
//'<img class="privacy_button" src="'+root+'privacy_icon.png" width="36" height="36" alt="Privacy Settings" title="Privacy Settings">'+
// '<img class="groups_button" src="'+root+'groups_icon.png" width="36" height="36" alt="Groups Access" title="Groups Access">'+
//'<img class="share_button" src="'+root+'share_icon.png" width="36" height="36" alt="Share Annotation" title="Share Annotation"/>'+
'</div>'+
'</div>',
//Main->ContainerRow->DetailRow (Video)
videoAnnotationDetail:
'<div class="annotationDetail videoAnnotationDetail">'+
'<div class="detailHeader">'+
'<span class="closeDetailIcon">'+
'<img src="'+root+'closeIcon.png" alt="Hide Details" />'+
'</span>'+
'On {{ updated }} <!--<a href="index.php?r=user/user/view&id={{{user.id}}}">-->{{{ user.name }}}<!--</a>-->{{#if geolocation}}, wrote from {{/if}}'+
'{{#if geolocation}}'+
'<span class="geolocationIcon">'+
'<img src="'+root+'geolocation_icon.png"width="25" height="25" alt="Location Map" title="Show Location Map" data-dropdown="myLocationMap"/>'+
'<span class="idAnnotation" style="display:none">{{{ id }}}</span>'+
'<span class="latitude" style="display:none">{{{ geolocation.latitude }}}</span>'+
'<span class="longitude" style="display:none">{{{ geolocation.longitude }}}</span>'+
'</span>'+
'<div id="myLocationMap" data-dropdown-content class="f-dropdown content">'+
'<div class="map"></div>'+
'</div>'+
'{{/if}}'+
'</div>'+
'{{#if mediatypeforgrid.video}}'+
'<div class="playMediaButton">'+
'Play segment {{{ rangeTime.start }}} - {{{ rangeTime.end }}}'+
'<span class="idAnnotation" style="display:none">{{{ id }}}</span>'+
'<span class="uri" style="display:none">{{{uri}}}</span>'+
'<span class="container" style="display:none">{{{target.container}}}</span>'+
'</div>'+
'{{/if}}'+
'{{#if mediatypeforgrid.image}}'+
'<div class="zoomToImageBounds">'+
'<img src="{{{ thumbnailLink }}}">'+
'<span class="idAnnotation" style="display:none">{{{ id }}}</span>'+
'<span class="uri" style="display:none">{{{uri}}}</span>'+
'</div>'+
'{{/if}}'+
'<div class="body">'+
'{{{ text }}}'+
'</div>'+
......@@ -282,10 +258,6 @@ videoAnnotationDetail:
'{{/if}}'+
'<div class="controlPanel">'+
//'<img class="privacy_button" src="'+root+'privacy_icon.png" width="36" height="36" alt="Privacy Settings" title="Privacy Settings">'+
// '<img class="groups_button" src="'+root+'groups_icon.png" width="36" height="36" alt="Groups Access" title="Groups Access">'+
// '<img class="reply_button" src="'+root+'groups_icon.png" width="36" height="36" alt="Reply" title="Reply" idAnnotation="{{{ id }}}">'+
//'<img class="share_button" src="'+root+'share_icon.png" width="36" height="36" alt="Share Annotation" title="Share Annotation"/>'+
'</div>'+
'</div>',
};
......@@ -324,8 +296,8 @@ CatchAnnotation = function (element, options) {
$( document ).ready(function() {
self.init();
self.refreshCatch(true);
var moreBut = self.element.find('.annotationListButtons .moreButtonCatch');
moreBut.hide();
var moreBut = self.element.find('.annotationListButtons .moreButtonCatch');
moreBut.hide();
});
return this;
......@@ -343,7 +315,6 @@ CatchAnnotation.prototype = {
"annotationReply",//Main->ContainerRow->Reply
"annotationRow", //Main->ContainerRow->Row
"annotationDetail",//Main->ContainerRow->DetailRow
"videoAnnotationDetail"//Main->ContainerRow->DetailRow (Video)
];
//annotator
var wrapper = $('.annotator-wrapper').parent()[0],
......@@ -400,7 +371,7 @@ CatchAnnotation.prototype = {
evenOrOdd: index % 2 ? "odd" : "even",
openOrClosed: "closed",
annotationRow: self.TEMPLATES.annotationRow(item),
annotationDetail: (mediaType === "video") ? self.TEMPLATES.videoAnnotationDetail(item):self.TEMPLATES.annotationDetail(item),
annotationDetail: self.TEMPLATES.annotationDetail(item),
});
index++;
annotationItems.push(html);
......@@ -441,18 +412,19 @@ CatchAnnotation.prototype = {
el.off();
//Bind functions
var openAnnotationItem = this.__bind(this._openAnnotationItem,this),
closeAnnotationItem = this.__bind(this._closeAnnotationItem,this),
onGeolocationClick = this.__bind(this._onGeolocationClick,this),
onPlaySelectionClick = this.__bind(this._onPlaySelectionClick,this),
onShareControlsClick = this.__bind(this._onShareControlsClick,this),
onSelectionButtonClick = this.__bind(this._onSelectionButtonClick,this),
onPublicPrivateButtonClick = this.__bind(this._onPublicPrivateButtonClick,this),
onQuoteMediaButton = this.__bind(this._onQuoteMediaButton,this),
onControlRepliesClick = this.__bind(this._onControlRepliesClick,this),
onMoreButtonClick = this.__bind(this._onMoreButtonClick,this),
var openAnnotationItem = this.__bind(this._openAnnotationItem, this),
closeAnnotationItem = this.__bind(this._closeAnnotationItem, this),
onGeolocationClick = this.__bind(this._onGeolocationClick, this),
onPlaySelectionClick = this.__bind(this._onPlaySelectionClick, this),
onShareControlsClick = this.__bind(this._onShareControlsClick, this),
onSelectionButtonClick = this.__bind(this._onSelectionButtonClick, this),
onPublicPrivateButtonClick = this.__bind(this._onPublicPrivateButtonClick, this),
onQuoteMediaButton = this.__bind(this._onQuoteMediaButton, this),
onControlRepliesClick = this.__bind(this._onControlRepliesClick, this),
onMoreButtonClick = this.__bind(this._onMoreButtonClick, this),
onSearchButtonClick = this.__bind(this._onSearchButtonClick, this),
onDeleteReplyButtonClick = this.__bind(this._onDeleteReplyButtonClick,this);
onDeleteReplyButtonClick = this.__bind(this._onDeleteReplyButtonClick, this),
onZoomToImageBoundsButtonClick = this.__bind(this._onZoomToImageBoundsButtonClick, this);
//Open Button
el.on("click", ".annotationItem .annotationRow", openAnnotationItem);
......@@ -472,6 +444,12 @@ CatchAnnotation.prototype = {
//PlaySelection button
el.on("click",".annotationItem .annotationDetail .quote", onQuoteMediaButton);
}
//IMAGE
if (this.options.media=='image') {
//PlaySelection button
el.on("click",".annotationItem .annotationDetail .zoomToImageBounds", onZoomToImageBoundsButtonClick);
}
//controlReplies
el.on("click",".annotationItem .controlReplies", onControlRepliesClick);
......@@ -493,16 +471,16 @@ CatchAnnotation.prototype = {
changeMedia: function(media) {
var media = media || 'text';
this.options.media = media;
this._refresh();
this._refresh();
this.refreshCatch(true);
this.checkTotAnnotations();
this.checkTotAnnotations();
},
changeUserId: function(userId) {
var userId = userId || '';
this.options.userId = userId;
this._refresh();
this.refreshCatch(true);
this.checkTotAnnotations();
this.checkTotAnnotations();
},
loadAnnotations: function() {
var annotator = this.annotator,
......@@ -520,9 +498,9 @@ CatchAnnotation.prototype = {
//annotator.plugins['Store'].loadAnnotationsFromSearch(loadFromSearch);
//Make sure to be openned all annotations for this pagination
loadFromSearch.limit = this.options.pagination+loadedAn;
loadFromSearch.offset = 0;
annotator.plugins['Store'].loadAnnotationsFromSearch(loadFromSearch);
loadFromSearch.limit = this.options.pagination+loadedAn;
loadFromSearch.offset = 0;
annotator.plugins['Store'].loadAnnotationsFromSearch(loadFromSearch);
//text loading annotations
var moreBut = this.element.find('.annotationListButtons .moreButtonCatch');
......@@ -568,7 +546,6 @@ CatchAnnotation.prototype = {
var moreBut = this.element.find('.annotationListButtons .moreButtonCatch');
moreBut.html('More');
setTimeout();
},
//
......@@ -598,7 +575,7 @@ CatchAnnotation.prototype = {
setTimeout(function(){
if (new_tot != tot){
self.refreshCatch(true);
self.checkTotAnnotations();
self.checkTotAnnotations();
}else{
attempts++;
ischanged();
......@@ -617,7 +594,7 @@ CatchAnnotation.prototype = {
self.refreshCatch();
if (typeof annotation.parent != 'undefined' && annotation.parent != '0'){
var replies = $("[annotationid="+annotation.parent+"]").find(".controlReplies .hideReplies");
replies.show();
replies.show();
replies.click();
replies.click();
}
......@@ -632,7 +609,7 @@ CatchAnnotation.prototype = {
},
__bind: function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
_compileTemplates: function() {
var self = this;
var self = this;
//Change the html tags to functions
this.TEMPLATENAMES.forEach(function(templateName) {
self.TEMPLATES[templateName] = Handlebars.compile(self.HTMLTEMPLATES[templateName]);
......@@ -678,6 +655,13 @@ CatchAnnotation.prototype = {
});//Change to < and > tags
item.plainText = item.plainText.replace(/<\/?[^>]+(>|$)/g, "").replace('&nbsp;',''); //remove all the html tags
item.mediatypeforgrid = {};
item.mediatypeforgrid[item.media] = true;
if (item.mediatypeforgrid.image) {
item.thumbnailLink = item.target.thumb;
};
//Flags
if(!this.options.flags && typeof item.tags != 'undefined' && item.tags.length > 0){
for(var len=item.tags.length, index = len-1; index >= 0; --index){
......@@ -778,6 +762,26 @@ CatchAnnotation.prototype = {
}
}
},
_onZoomToImageBoundsButtonClick: function(evt){
var zoomToBounds = $(evt.target).hasClass('zoomToImageBounds')?$(evt.target):$(evt.target).parents('.zoomToImageBounds:first'),
osdaId = zoomToBounds.find('.idAnnotation').html(),
uri = zoomToBounds.find('.uri').html();
var allannotations = this.annotator.plugins['Store'].annotations,
osda = this.annotator.osda;
for(var item in allannotations){
var an = allannotations[item];
if (typeof an.id!='undefined' && an.id == osdaId){//this is the annotation
var bounds = new OpenSeadragon.Rect(an.bounds.x, an.bounds.y, an.bounds.width, an.bounds.height);
osda.viewer.viewport.fitBounds(bounds, false);
console.log(an.target.container);
$('html,body').animate({scrollTop: $("#"+an.target.container).offset().top},
'slow');
}
}
},
_onQuoteMediaButton: function(evt){
var quote = $(evt.target).hasClass('quote')?$(evt.target):$(evt.target).parents('.quote:first'),
id = quote.find('.idAnnotation').html(),
......@@ -1095,13 +1099,10 @@ CatchAnnotation.prototype = {
annotation = item.data('annotation');
var authorized = permissions.options.userAuthorize('delete', annotation,permissions.user);
if(authorized){
//annotator.deleteAnnotation(annotation);
if(confirm('Would you like to delete this reply?')){
annotator.plugins['Store']._apiRequest('destroy', annotation, function(){});
item.remove();
}
}
}
}
}
......@@ -101,4 +101,4 @@ Annotator.Plugin.Flagging = (function(_super) {
return Flagging;
})(Annotator.Plugin);
\ No newline at end of file
})(Annotator.Plugin);
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) {
var string;
return self;
});
field.remove();
this.annotation = annotation;
//Create the actions for the buttons
return ret;
......
......@@ -38,7 +38,7 @@ Annotator.Plugin.RichText = (function(_super) {
}
},
codemirror: {
path: "static/js/vendor"
path: "/static/js/vendor"
},
plugins: "image link codemirror",
menubar: false,
......@@ -78,7 +78,6 @@ Annotator.Plugin.RichText = (function(_super) {
annotator.subscribe("annotationEditorShown", function(){
$(annotator.editor.element).find('.mce-tinymce')[0].style.display='block';
$(annotator.editor.element).find('.mce-container').css('z-index',3000000000);
annotator.editor.checkOrientation();
});
annotator.subscribe("annotationEditorHidden", function(){
......@@ -91,9 +90,6 @@ Annotator.Plugin.RichText = (function(_super) {
//set the modification in the textarea of annotator
$(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
ed.addButton('rubric', {
icon: 'rubric',
......
/*
HighlightTags Annotator Plugin v1.0 (https://github.com/lduarte1991/tags-annotator)
Copyright (C) 2014 Luis F Duarte
License: https://github.com/lduarte1991/tags-annotator/blob/master/LICENSE.rst
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/*===============================================================================
===============================================================================
===============================================================================
===============================================================================
==============================================================================*/
/*
* jQuery Plugin: Tokenizing Autocomplete Text Entry
* Version 1.6.0
......@@ -11,7 +35,7 @@
(function ($) {
// Default settings
var DEFAULT_SETTINGS = {
// Search settings
// Search settings
method: "GET",
contentType: "json",
queryParam: "q",
......@@ -20,33 +44,33 @@ var DEFAULT_SETTINGS = {
propertyToSearch: "name",
jsonContainer: null,
// Display settings
// Display settings
hintText: "Type in a search term",
noResultsText: "No results",
noResultsText: "Not Found. Hit ENTER to add a personal tag.",
searchingText: "Searching...",
deleteText: "&times;",
animateDropdown: true,
// Tokenization settings
// Tokenization settings
tokenLimit: null,
tokenDelimiter: ",",
preventDuplicates: false,
// Output settings
// Output settings
tokenValue: "id",
// Prepopulation settings
// Prepopulation settings
prePopulate: null,
processPrePopulate: false,
// Manipulation settings
// Manipulation settings
idPrefix: "token-input-",
// Formatters
// Formatters
resultsFormatter: function(item){ return "<li>" + item[this.propertyToSearch]+ "</li>" },
tokenFormatter: function(item) { return "<li><p>" + item[this.propertyToSearch] + "</p></li>" },
// Callbacks
// Callbacks
onResult: null,
onAdd: null,
onDelete: null,
......@@ -115,8 +139,8 @@ var methods = {
return this;
},
get: function() {
return this.data("tokenInputObject").getTokens();
}
return this.data("tokenInputObject").getTokens();
}
}
// Expose the .tokenInput function to jQuery as a plugin
......@@ -271,7 +295,10 @@ $.TokenList = function (input, url_or_data, settings) {
add_token($(selected_dropdown_item).data("tokeninput"));
hidden_input.change();
return false;
}
} else{
add_token({id:$(this).val(), name:$(this).val()});
hidden_input.change();
}
break;
case KEY.ESCAPE:
......@@ -414,8 +441,8 @@ $.TokenList = function (input, url_or_data, settings) {
}
this.getTokens = function() {
return saved_tokens;
}
return saved_tokens;
}
//
// Private functions
......@@ -866,232 +893,232 @@ var _ref,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
Annotator.Plugin.HighlightTags = (function(_super) {
__extends(HighlightTags, _super);
__extends(HighlightTags, _super);
HighlightTags.prototype.options = null;
function HighlightTags(element,options) {
this.pluginSubmit = __bind(this.pluginSubmit, this);
this.updateViewer = __bind(this.updateViewer, this);
this.colorize = __bind(this.colorize, this);
this.updateField = __bind(this.updateField, this);
function HighlightTags(element,options) {
this.pluginSubmit = __bind(this.pluginSubmit, this);
this.updateViewer = __bind(this.updateViewer, this);
this.colorize = __bind(this.colorize, this);
this.updateField = __bind(this.updateField, this);
this.options = options;
_ref = HighlightTags.__super__.constructor.apply(this, arguments);
return _ref;
}
_ref = HighlightTags.__super__.constructor.apply(this, arguments);
return _ref;
}
//example variables to be used to receive input in the annotator view
HighlightTags.prototype.field = null;
HighlightTags.prototype.input = null;
HighlightTags.prototype.colors = null;
HighlightTags.prototype.isFirstTime = true;
HighlightTags.prototype.field = null;
HighlightTags.prototype.input = null;
HighlightTags.prototype.colors = null;
HighlightTags.prototype.isFirstTime = true;
//this function will initialize the plug in. Create your fields here in the editor and viewer.
HighlightTags.prototype.pluginInit = function() {
console.log("HighlightTags-pluginInit");
//Check that annotator is working
if (!Annotator.supported()) {
return;
}
this.field = this.annotator.editor.addField({
type: 'input',
load: this.updateField,
submit: this.pluginSubmit,
});
var self = this;
var newfield = Annotator.$('<li class="annotator-item">'+ "<div><input placeholder =\"Add tags\" type=\"text\" id=\"tag-input\" name=\"tags\" /></div>"+'</li>');
console.log("HighlightTags-pluginInit");
//Check that annotator is working
if (!Annotator.supported()) {
return;
}
this.field = this.annotator.editor.addField({
type: 'input',
load: this.updateField,
submit: this.pluginSubmit,
});
var self = this;
var newfield = Annotator.$('<li class="annotator-item">'+ "<div><input placeholder =\"Add tags\" type=\"text\" id=\"tag-input\" name=\"tags\" /></div>"+'</li>');
Annotator.$(self.field).replaceWith(newfield);
self.field=newfield[0];
//
//-- Viewer
var newview = this.annotator.viewer.addField({
load: this.updateViewer,
});
this.colors = this.getHighlightTags();
var self = this;
this.annotator.subscribe('annotationsLoaded', function(){setTimeout(function(){self.colorize()},1000)});
this.annotator.subscribe('annotationUpdated', this.colorize);
this.annotator.subscribe('flaggedAnnotation', this.updateViewer);
this.annotator.subscribe('annotationCreated', this.colorize);
};
HighlightTags.prototype.getHighlightTags = function(){
if (typeof this.options.tag != 'undefined') {
var self = this;
var final = {}, prelim = this.options.tag.split(",");
prelim.forEach(function(item){
var temp = item.split(":");
final[temp[0]] = self.getRGB(temp[1]);
});
return final;
}
return {};
}
HighlightTags.prototype.getRGB = function(item){
function getColorValues( color ){
var values = { red:null, green:null, blue:null, alpha:null };
if( typeof color == 'string' ){
/* hex */
if( color.indexOf('#') === 0 ){
color = color.substr(1)
if( color.length == 3 )
values = {
red: parseInt( color[0]+color[0], 16 ),
green: parseInt( color[1]+color[1], 16 ),
blue: parseInt( color[2]+color[2], 16 ),
alpha: .3
}
else
values = {
red: parseInt( color.substr(0,2), 16 ),
green: parseInt( color.substr(2,2), 16 ),
blue: parseInt( color.substr(4,2), 16 ),
alpha: .3
}
/* rgb */
}else if( color.indexOf('rgb(') === 0 ){
var pars = color.indexOf(',');
values = {
red: parseInt(color.substr(4,pars)),
green: parseInt(color.substr(pars+1,color.indexOf(',',pars))),
blue: parseInt(color.substr(color.indexOf(',',pars+1)+1,color.indexOf(')'))),
alpha: .3
}
/* rgba */
}else if( color.indexOf('rgba(') === 0 ){
var pars = color.indexOf(','),
repars = color.indexOf(',',pars+1);
values = {
red: parseInt(color.substr(5,pars)),
green: parseInt(color.substr(pars+1,repars)),
blue: parseInt(color.substr(color.indexOf(',',pars+1)+1,color.indexOf(',',repars))),
alpha: parseFloat(color.substr(color.indexOf(',',repars+1)+1,color.indexOf(')')))
}
/* verbous */
}else{
var stdCol = { acqua:'#0ff', teal:'#008080', blue:'#00f', navy:'#000080',
yellow:'#ff0', olive:'#808000', lime:'#0f0', green:'#008000',
fuchsia:'#f0f', purple:'#800080', red:'#f00', maroon:'#800000',
white:'#fff', gray:'#808080', silver:'#c0c0c0', black:'#000' };
if( stdCol[color]!=undefined )
values = getColorValues(stdCol[color]);
}
}
return values
//-- Viewer
var newview = this.annotator.viewer.addField({
load: this.updateViewer,
});
this.colors = this.getHighlightTags();
var self = this;
this.annotator.subscribe('annotationsLoaded', function(){setTimeout(function(){self.colorize()},1000)});
this.annotator.subscribe('annotationUpdated', this.colorize);
this.annotator.subscribe('flaggedAnnotation', this.updateViewer);
this.annotator.subscribe('annotationCreated', this.colorize);
};
HighlightTags.prototype.getHighlightTags = function(){
if (typeof this.options.tag != 'undefined') {
var self = this;
var final = {}, prelim = this.options.tag.split(",");
prelim.forEach(function(item){
var temp = item.split(":");
final[temp[0]] = self.getRGB(temp[1]);
});
return final;
}
return {};
}
HighlightTags.prototype.getRGB = function(item){
function getColorValues( color ){
var values = { red:null, green:null, blue:null, alpha:null };
if( typeof color == 'string' ){
/* hex */
if( color.indexOf('#') === 0 ){
color = color.substr(1)
if( color.length == 3 )
values = {
red: parseInt( color[0]+color[0], 16 ),
green: parseInt( color[1]+color[1], 16 ),
blue: parseInt( color[2]+color[2], 16 ),
alpha: .3
}
else
values = {
red: parseInt( color.substr(0,2), 16 ),
green: parseInt( color.substr(2,2), 16 ),
blue: parseInt( color.substr(4,2), 16 ),
alpha: .3
}
/* rgb */
}else if( color.indexOf('rgb(') === 0 ){
var pars = color.indexOf(',');
values = {
red: parseInt(color.substr(4,pars)),
green: parseInt(color.substr(pars+1,color.indexOf(',',pars))),
blue: parseInt(color.substr(color.indexOf(',',pars+1)+1,color.indexOf(')'))),
alpha: .3
}
/* rgba */
}else if( color.indexOf('rgba(') === 0 ){
var pars = color.indexOf(','),
repars = color.indexOf(',',pars+1);
values = {
red: parseInt(color.substr(5,pars)),
green: parseInt(color.substr(pars+1,repars)),
blue: parseInt(color.substr(color.indexOf(',',pars+1)+1,color.indexOf(',',repars))),
alpha: parseFloat(color.substr(color.indexOf(',',repars+1)+1,color.indexOf(')')))
}
/* verbous */
}else{
var stdCol = { acqua:'#0ff', teal:'#008080', blue:'#00f', navy:'#000080',
yellow:'#ff0', olive:'#808000', lime:'#0f0', green:'#008000',
fuchsia:'#f0f', purple:'#800080', red:'#f00', maroon:'#800000',
white:'#fff', gray:'#808080', silver:'#c0c0c0', black:'#000' };
if( stdCol[color]!=undefined )
values = getColorValues(stdCol[color]);
}
}
return values
}
return getColorValues(item)
}
HighlightTags.prototype.colorize = function(){
var annotations = Array.prototype.slice.call($(".annotator-hl"));
for (annNum = 0; annNum < annotations.length; ++annNum){
var anns = $.data(annotations[annNum],"annotation");
if (typeof anns.tags != "undefined" && anns.tags.length == 0) {
$(annotations[annNum]).css("background-color","");
}
if (typeof anns.tags != "undefined" && this.colors !== {}) {
for(var index = 0; index < anns.tags.length; ++index){
if (typeof this.colors[anns.tags[index]] != "undefined") {
var finalcolor = this.colors[anns.tags[index]];
$(annotations[annNum]).css("background","rgba("+finalcolor.red+","+finalcolor.green+","+finalcolor.blue+",0.3");
}else{
$(annotations[annNum]).css("background","");
}
}
}else{
$(annotations[annNum]).css("background","");
}
}
}
HighlightTags.prototype.updateField = function(field, annotation){
return getColorValues(item)
}
HighlightTags.prototype.colorize = function(){
var annotations = Array.prototype.slice.call($(".annotator-hl"));
for (annNum = 0; annNum < annotations.length; ++annNum){
var anns = $.data(annotations[annNum],"annotation");
if (typeof anns.tags != "undefined" && anns.tags.length == 0) {
$(annotations[annNum]).css("background-color","");
}
if (typeof anns.tags != "undefined" && this.colors !== {}) {
for(var index = 0; index < anns.tags.length; ++index){
if (typeof this.colors[anns.tags[index]] != "undefined") {
var finalcolor = this.colors[anns.tags[index]];
$(annotations[annNum]).css("background","rgba("+finalcolor.red+","+finalcolor.green+","+finalcolor.blue+",0.3");
}else{
$(annotations[annNum]).css("background","");
}
}
}else{
$(annotations[annNum]).css("background","");
}
}
}
HighlightTags.prototype.updateField = function(field, annotation){
if(this.isFirstTime){
var tags = this.options.tag.split(",");
var tokensavailable = [];
var tokensavailable = [];
tags.forEach (function(tagnames){
lonename = tagnames.split(":");
tokensavailable.push({'id': lonename[0], 'name':lonename[0]});
});
lonename = tagnames.split(":");
tokensavailable.push({'id': lonename[0], 'name':lonename[0]});
});
$("#tag-input").tokenInput(tokensavailable);
this.isFirstTime = false;
}
$('#token-input-tag-input').attr('placeholder','Add tags...');
$('#tag-input').tokenInput('clear');
$('#tag-input').tokenInput('clear');
if (typeof annotation.tags != "undefined") {
for (tagnum = 0; tagnum < annotation.tags.length; tagnum++){
var n = annotation.tags[tagnum];
if (typeof this.annotator.plugins["HighlightTags"] != 'undefined') {
if (annotation.tags[tagnum].indexOf("flagged-")==-1){
$('#tag-input').tokenInput('add',{'id':n,'name':n});
}
} else{
$('#tag-input').tokenInput('add',{'id':n,'name':n});
}
if (typeof this.annotator.plugins["HighlightTags"] != 'undefined') {
if (annotation.tags[tagnum].indexOf("flagged-")==-1){
$('#tag-input').tokenInput('add',{'id':n,'name':n});
}
} else{
$('#tag-input').tokenInput('add',{'id':n,'name':n});
}
}
}
}
}
//The following function is run when a person hits submit.
HighlightTags.prototype.pluginSubmit = function(field, annotation) {
var tokens = Array.prototype.slice.call($(".token-input-input-token").parent().find('.token-input-token'));
var arr = [];
tokens.forEach(function(element){
tag = element.firstChild.firstChild;
arr.push(tag.nodeValue);
});
annotation.tags = arr;
var tokens = Array.prototype.slice.call($(".token-input-input-token").parent().find('.token-input-token'));
var arr = [];
tokens.forEach(function(element){
tag = element.firstChild.firstChild;
arr.push(tag.nodeValue);
});
annotation.tags = arr;
}
//The following allows you to edit the annotation popup when the viewer has already
//hit submit and is just viewing the annotation.
HighlightTags.prototype.updateViewer = function(field, annotation) {
if (typeof annotation.tags != "undefined") {
if (annotation.tags.length == 0) {
$(field).remove();
return;
}
var nonFlagTags = true;
HighlightTags.prototype.updateViewer = function(field, annotation) {
if (typeof annotation.tags != "undefined") {
if (annotation.tags.length == 0) {
$(field).remove();
return;
}
var nonFlagTags = true;
var tokenList = "<ul class=\"token-input-list\">";
for (tagnum = 0; tagnum < annotation.tags.length; ++tagnum){
if (typeof this.annotator.plugins["Flagging"] != 'undefined') {
if (annotation.tags[tagnum].indexOf("flagged-")==-1){
tokenList += "<li class=\"token-input-token\"><p>"+ annotation.tags[tagnum]+"</p></span></li>";
nonFlagTags = false;
}
} else{
tokenList += "<li class=\"token-input-token\"><p>"+ annotation.tags[tagnum]+"</p></span></li>";
}
}
for (tagnum = 0; tagnum < annotation.tags.length; ++tagnum){
if (typeof this.annotator.plugins["Flagging"] != 'undefined') {
if (annotation.tags[tagnum].indexOf("flagged-")==-1){
tokenList += "<li class=\"token-input-token\"><p>"+ annotation.tags[tagnum]+"</p></span></li>";
nonFlagTags = false;
}
} else{
tokenList += "<li class=\"token-input-token\"><p>"+ annotation.tags[tagnum]+"</p></span></li>";
}
}
tokenList += "</ul>";
$(field).append(tokenList);
if (nonFlagTags) {
$(field).remove();
}
} else{
$(field).remove();
}
if (nonFlagTags) {
$(field).remove();
}
} else{
$(field).remove();
}
this.annotator.publish("finishedDrawingTags");
}
......
......@@ -845,6 +845,8 @@ main_vendor_js = [
'js/vendor/ova/tags-annotator.js',
'js/vendor/ova/flagging-annotator.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/catch/js/catch.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