From 0ca9bfa7e4e142f9927465cfd00c90cb0628feae Mon Sep 17 00:00:00 2001 From: lduarte1991 Date: Mon, 21 Jul 2014 13:11:28 -0400 Subject: [PATCH] Annotation Tools: Added Grouping Plug-In - adding comments - spacing issues and indentation --- .../css/vendor/ova/grouping-annotator.css | 49 ++++ common/static/js/vendor/ova/catch/js/catch.js | 208 +++++++------- .../js/vendor/ova/grouping-annotator.js | 269 ++++++++++++++++++ common/static/js/vendor/ova/tags-annotator.js | 11 + lms/envs/common.py | 2 + lms/templates/textannotation.html | 5 +- 6 files changed, 446 insertions(+), 98 deletions(-) create mode 100644 common/static/css/vendor/ova/grouping-annotator.css create mode 100644 common/static/js/vendor/ova/grouping-annotator.js diff --git a/common/static/css/vendor/ova/grouping-annotator.css b/common/static/css/vendor/ova/grouping-annotator.css new file mode 100644 index 0000000000..552157b574 --- /dev/null +++ b/common/static/css/vendor/ova/grouping-annotator.css @@ -0,0 +1,49 @@ +.groupButton { + background-color: rgba(255, 255, 10, 0.3); + position: absolute; + width: 30px; + text-align: center; + right: -17px; + cursor: pointer; + font-size: 13px; + padding: 2px; + border: 1px solid black; + border-bottom-left-radius: 5px; + border-top-left-radius: 5px; + transition: 0.5s; +} + +.groupButton:hover { + width: 60px; + transition: 0.5s; +} + +.onOffGroupButton, .onOffGroupButton.buttonOn { + border: 1px solid blue; + background-color: rgba(255, 255, 10, 0.3); + padding: 5px; + cursor: pointer; + border-radius: 5px; + color: black; + position: relative; + margin-left: auto; + margin-bottom: 10px; + width: 220px; + font-weight: bold; + text-align: center; +} + +.onOffGroupButton.buttonOff { + border: 1px solid black; + background-color: rgba(8, 8, 8, 0.3); + padding: 5px; + cursor: pointer; + border-radius: 5px; + color: black; + position: relative; + margin-left: auto; + margin-bottom: 10px; + width: 220px; + font-weight: bold; + text-align: center; +} diff --git a/common/static/js/vendor/ova/catch/js/catch.js b/common/static/js/vendor/ova/catch/js/catch.js index f56cc21282..fdf69c6465 100644 --- a/common/static/js/vendor/ova/catch/js/catch.js +++ b/common/static/js/vendor/ova/catch/js/catch.js @@ -17,7 +17,7 @@ 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 +// 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 : {}; @@ -284,7 +284,7 @@ CatchAnnotation = function (element, options) { var defaultOptions = { media: 'text', userId: '', // this is an integer and its value is the userId to see user annotations - externalLink: false,//This is true if you want to open the link in a new URL. However, it is false if you want to open the url in the same page + externalLink: false, // This is true if you want to open the link in a new URL. However, it is false if you want to open the url in the same page showMediaSelector: true, // whether show the selector of Media Annotations or not showPublicPrivate: true, // Whether show Public or Private Annotation Selector pagination: 50, // Number of Annotations per load in the pagination @@ -353,7 +353,7 @@ CatchAnnotation.prototype = { // under the instructor's email. Calling changeUserId will update this.options.userId // and most importantly refresh not only the highlights (from Annotator) // but also the table below from the annotations database server (called Catch). - if(this.options.default_tab.toLowerCase() == 'instructor'){ + if(this.options.default_tab.toLowerCase() === 'instructor'){ this.changeUserId(this.options.instructor_email); } }, @@ -372,9 +372,9 @@ CatchAnnotation.prototype = { var self = this; var newInstance = newInstance || false; annotations.forEach(function(annotation) { - var isMedia = annotation.media==self.options.media; - var isUser = (typeof self.options.userId!='undefined' && self.options.userId!='' && self.options.userId!=null)? - self.options.userId == annotation.user.id:true; + var isMedia = annotation.media === self.options.media; + var isUser = (typeof self.options.userId !== 'undefined' && self.options.userId !== '' && self.options.userId !== null)? + self.options.userId === annotation.user.id:true; var isInList = newInstance?false:self._isInList(annotation); if (isMedia && isUser && !isInList){ var item = jQuery.extend(true, {}, annotation); @@ -382,8 +382,8 @@ CatchAnnotation.prototype = { // Authorized var permissions = self.annotator.plugins.Permissions; - var authorized = permissions.options.userAuthorize('delete', annotation,permissions.user); - var updateAuthorized = permissions.options.userAuthorize('update', annotation,permissions.user); + var authorized = permissions.options.userAuthorize('delete', annotation, permissions.user); + var updateAuthorized = permissions.options.userAuthorize('update', annotation, permissions.user); item.authToDeleteButton = authorized; item.authToEditButton = updateAuthorized; @@ -426,13 +426,13 @@ CatchAnnotation.prototype = { var SelButtons = el.find('.annotationList li').removeClass('active'); // reset for (var index=0;index"; - });//Change to < and > tags - item.plainText = item.plainText.replace(/<\/?[^>]+(>|$)/g, "").replace(' ',''); // remove all the html tags + return (p1 === "lt")? "<" : ">"; + }); // Change to < and > tags + item.plainText = item.plainText.replace(/<\/?[^>]+(>|$)/g, "").replace(' ', ''); // remove all the html tags item.mediatypeforgrid = {}; item.mediatypeforgrid[item.media] = true; @@ -751,10 +755,10 @@ CatchAnnotation.prototype = { }; // Flags - if(!this.options.flags && typeof item.tags != 'undefined' && item.tags.length > 0){ + if(!this.options.flags && typeof item.tags !== 'undefined' && item.tags.length > 0){ for(var len=item.tags.length, index = len-1; index >= 0; --index){ var currTag = item.tags[index]; - if(currTag.indexOf("flagged-") != -1){ + if(currTag.indexOf("flagged-") !== -1){ item.tags.splice(index); } @@ -776,16 +780,16 @@ CatchAnnotation.prototype = { uri = shareControl.parent().find('.uri').html(); // remove the last share container shareControl.find('.share-container-annotator').remove(); - shareControl.append(annotator.plugins.Share.buildHTMLShareButton("",idAnnotation)); + shareControl.append(annotator.plugins.Share.buildHTMLShareButton("", idAnnotation)); // Set actions button - annotator.plugins.Share.buttonsActions(shareControl[0],1,uri); + annotator.plugins.Share.buttonsActions(shareControl[0], 1, uri); } else { $(evt.currentTarget).closest(".annotationItem").removeClass("open").addClass("closed"); } }, _closeAnnotationItem: function(evt) { - var existEvent = typeof evt.target!='undefined' && typeof evt.target.localName!='undefined'; - if(existEvent && evt.target.parentNode.className!='geolocationIcon'){ + var existEvent = typeof evt.target !== 'undefined' && typeof evt.target.localName !== 'undefined'; + if(existEvent && evt.target.parentNode.className !== 'geolocationIcon'){ this._openAnnotationItem(evt); } }, @@ -803,46 +807,46 @@ CatchAnnotation.prototype = { uri += (uri.indexOf('?') >= 0)?'&ovaId='+id:'?ovaId='+id; location.href = uri; }else{ - var isContainer = typeof this.annotator.an!='undefined' && typeof this.annotator.an[container]!='undefined', + var isContainer = typeof this.annotator.an !== 'undefined' && typeof this.annotator.an[container] !== 'undefined', ovaInstance = isContainer? this.annotator.an[container]:null; - if(ovaInstance!=null){ + if(ovaInstance !== null){ var allannotations = this.annotator.plugins['Store'].annotations, ovaId = id, player = ovaInstance.player; for (var item in allannotations) { var an = allannotations[item]; - if (typeof an.id!='undefined' && an.id == ovaId){//this is the annotation - if(this._isVideoJS(an)){//It is a video - if (player.id_ == an.target.container && player.tech.options_.source.src == an.target.src){ + if (typeof an.id !== 'undefined' && an.id === ovaId) { //this is the annotation + if (this._isVideoJS(an)) { //It is a video + if (player.id_ === an.target.container && player.tech.options_.source.src === an.target.src){ var anFound = an; var playFunction = function(){ // Fix problem with youtube videos in the first play. The plugin don't have this trigger - if (player.techName == 'Youtube'){ + if (player.techName === 'Youtube'){ var startAPI = function(){ ovaInstance.showAnnotation(anFound); } if (ovaInstance.loaded) startAPI(); else - player.one('loadedRangeSlider', startAPI);//show Annotations once the RangeSlider is loaded + player.one('loadedRangeSlider', startAPI); // show Annotations once the RangeSlider is loaded }else{ ovaInstance.showAnnotation(anFound); } - $('html,body').animate({ + $('html, body').animate({ scrollTop: $("#"+player.id_).offset().top}, 'slow'); }; if (player.paused()) { player.play(); - player.one('playing',playFunction); + player.one('playing', playFunction); }else{ playFunction(); } - return false;//this will stop the code to not set a new player.one. + return false; // this will stop the code to not set a new player.one. } } } @@ -866,16 +870,16 @@ CatchAnnotation.prototype = { var an = allannotations[item]; // Makes sure that all images are set to transparent in case one was // previously selected. - an.highlights[0].style.background = "rgba(0,0,0,0)"; - if (typeof an.id!='undefined' && an.id == osdaId){//this is the annotation + an.highlights[0].style.background = "rgba(0, 0, 0, 0)"; + 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); - $('html,body').animate({scrollTop: $("#"+an.target.container).offset().top}, + $('html, body').animate({scrollTop: $("#"+an.target.container).offset().top}, 'slow'); // signifies a selected annotation once OSD has zoomed in on the // appropriate area, it turns the background a bit yellow - an.highlights[0].style.background = "rgba(255,255,10,0.2)"; + an.highlights[0].style.background = "rgba(255, 255, 10, 0.2)"; } } }, @@ -883,7 +887,7 @@ CatchAnnotation.prototype = { var quote = $(evt.target).hasClass('quote')?$(evt.target):$(evt.target).parents('.quote:first'); var id = quote.find('.idAnnotation').html(); var uri = quote.find('.uri').html(); - if (typeof id=='undefined' || id==''){ + if (typeof id === 'undefined' || id === ''){ this.refreshCatch(); this.checkTotAnnotations(); id = quote.find('.idAnnotation').html(); @@ -897,20 +901,20 @@ CatchAnnotation.prototype = { var ovaId = id; for (var item in allannotations) { var an = allannotations[item]; - if (typeof an.id!='undefined' && an.id == ovaId){//this is the annotation + if (typeof an.id !== 'undefined' && an.id === ovaId){ // this is the annotation if(!this._isVideoJS(an)){ - var hasRanges = typeof an.ranges!='undefined' && typeof an.ranges[0] !='undefined', + var hasRanges = typeof an.ranges !== 'undefined' && typeof an.ranges[0] !== 'undefined', startOffset = hasRanges?an.ranges[0].startOffset:'', endOffset = hasRanges?an.ranges[0].endOffset:''; - if(typeof startOffset!='undefined' && typeof endOffset!='undefined'){ + if(typeof startOffset !== 'undefined' && typeof endOffset !== 'undefined'){ $(an.highlights).parent().find('.annotator-hl').removeClass('api'); // change the color $(an.highlights).addClass('api'); // animate to the annotation - $('html,body').animate({ + $('html, body').animate({ scrollTop: $(an.highlights[0]).offset().top}, 'slow'); } @@ -934,9 +938,9 @@ CatchAnnotation.prototype = { uri:loadFromSearchURI, }; var onSuccess=function(data){ - if (data == null) data = {}; + if (data === null) data = {}; annotations = data.rows || []; - var _i,_len; + var _i, _len; for (_i = 0, _len = annotations.length; _i < _len; _i++) { self._formatCatch(annotations[_i]); @@ -945,16 +949,16 @@ CatchAnnotation.prototype = { annotations: annotations })); var replyItems = $('.replies .replyItem'); - if(typeof replyItems != 'undefined' && replyItems.length > 0){ + if(typeof replyItems !== 'undefined' && replyItems.length > 0){ annotations.forEach(function(ann){ replyItems.each(function(item){ var id = $(replyItems[item]).attr('annotationid'); - if(id == ann.id){ + if(id === ann.id){ var perm = self.annotator.plugins.Permissions; - if(!perm.options.userAuthorize('delete',ann,perm.user)){ + if(!perm.options.userAuthorize('delete', ann, perm.user)){ $(replyItems[item]).find('.deleteReply').remove(); }else{ - $(replyItems[item]).data('annotation',ann); + $(replyItems[item]).data('annotation', ann); } } @@ -974,7 +978,7 @@ CatchAnnotation.prototype = { _onControlRepliesClick: function(evt){ var action = $(evt.target)[0].className; - if(action=='newReply'){ + if(action === 'newReply'){ var item = $(evt.target).parents('.annotationItem:first'); var id = item.attr('annotationId'); // Pre-show Adder @@ -1001,17 +1005,17 @@ CatchAnnotation.prototype = { // Set vertical editor this.annotator.editor.resetOrientation(); this.annotator.editor.invertY(); - this.annotator.editor.element.find('.annotator-widget').css('min-width',replyElem.css('width')); + this.annotator.editor.element.find('.annotator-widget').css('min-width', replyElem.css('width')); // set parent var parentValue = $(this.annotator.editor.element).find(".reply-item span.parent-annotation"); parentValue.html(id); var self = this; - }else if(action=='hideReplies'){ + }else if(action === 'hideReplies'){ var oldAction = $(evt.target).html(); - if (oldAction=='Show Replies'){ + if (oldAction === 'Show Replies'){ $(evt.target).html('Hide Replies'); }else{ $(evt.target).html('Show Replies'); @@ -1022,7 +1026,7 @@ CatchAnnotation.prototype = { // search this._refreshReplies(evt); - }else if(action=='deleteAnnotation'){ + }else if(action === 'deleteAnnotation'){ if(confirm("Would you like to delete the annotation?")){ var annotator = this.annotator; var item = $(evt.target).parents('.annotationItem:first'); @@ -1032,14 +1036,14 @@ CatchAnnotation.prototype = { var permissions = annotator.plugins.Permissions; var annotation; annotations.forEach(function(ann){ - if(ann.id == id) + if(ann.id === id) annotation = ann; }); - var authorized = permissions.options.userAuthorize('delete', annotation,permissions.user); + var authorized = permissions.options.userAuthorize('delete', annotation, permissions.user); if(authorized) annotator.deleteAnnotation(annotation); } - }else if(action=='editAnnotation'){ + }else if(action === 'editAnnotation'){ var annotator = this.annotator; var item = $(evt.target).parents('.annotationItem:first'); @@ -1049,10 +1053,10 @@ CatchAnnotation.prototype = { var permissions = annotator.plugins.Permissions; var annotation; annotations.forEach(function(ann){ - if(ann.id == id) + if(ann.id === id) annotation = ann; }); - var authorized = permissions.options.userAuthorize('update', annotation,permissions.user); + var authorized = permissions.options.userAuthorize('update', annotation, permissions.user); if(authorized){ // Get elements var wrapper = $('.annotator-wrapper'); @@ -1083,18 +1087,18 @@ CatchAnnotation.prototype = { }, _onShareControlsClick: function(evt) { var action = $(evt.target)[0].className; - if(action=='privacy_button'){ + if(action === 'privacy_button'){ - }else if(action=='groups_button'){ + }else if(action === 'groups_button'){ alert("Coming soon..."); - }else if(action=='reply_button'){ + }else if(action === 'reply_button'){ var item = $(evt.target).parents('.annotationItem:first'), id = item.attr('annotationId'); // New annotation var an = this.annotator.setupAnnotation(this.annotator.createAnnotation()); an.text="010"; an.parent = id; - }else if(action=='share_button'){ + }else if(action === 'share_button'){ } @@ -1116,6 +1120,16 @@ CatchAnnotation.prototype = { break; } this.current_tab = action.html(); + + // checks to make sure that Grouping is redone when switching tags in text annotations + if (this.options.media === 'text') { + if (this.current_tab ==='public') { + this.annotator.plugins.Grouping.useGrouping = 0; + } else { + this.annotator.plugins.Grouping.useGrouping = 1; + } + this.annotator.publish("changedTabsInCatch"); + } // Change userid and refresh this.changeUserId(userId); }, @@ -1133,12 +1147,12 @@ CatchAnnotation.prototype = { _onMoreButtonClick: function(evt){ this.clean = false; var moreBut = this.element.find('.annotationListButtons .moreButtonCatch'); - var isLoading = moreBut.html()=='More'?false:true; + var isLoading = moreBut.html() === 'More'?false:true; if(!isLoading) this.loadAnnotations(); }, - _refresh:function(searchtype,searchInput){ + _refresh:function(searchtype, searchInput){ var searchtype = searchtype || ""; var searchInput = searchInput || ""; this.clean = true; @@ -1156,9 +1170,9 @@ CatchAnnotation.prototype = { loadFromSearch.tag = ""; loadFromSearch.text = ""; - if (searchtype == "Users"){ + if (searchtype === "Users"){ loadFromSearch.username = searchInput; - } else if(searchtype == "Tags"){ + } else if(searchtype === "Tags"){ loadFromSearch.tag = searchInput; } else{ loadFromSearch.text = searchInput; @@ -1169,11 +1183,11 @@ CatchAnnotation.prototype = { _onSearchButtonClick: function(evt){ var searchtype = this.element.find('.searchbox .dropdown-list').val(); var searchInput = this.element.find('.searchbox input').val(); - this._refresh(searchtype,searchInput); + this._refresh(searchtype, searchInput); }, _onClearSearchButtonClick: function(evt){ - this._refresh('',''); + this._refresh('', ''); }, _clearAnnotator: function(){ var annotator = this.annotator; @@ -1182,11 +1196,11 @@ CatchAnnotation.prototype = { annotations.forEach(function(ann){ var child, h, _i, _len, _ref; - if (ann.highlights != null) { + if (ann.highlights !== null) { _ref = ann.highlights; for (_i = 0, _len = _ref.length; _i < _len; _i++) { h = _ref[_i]; - if (!(h.parentNode != null)) { + if (!(h.parentNode !== null)) { continue; } child = h.childNodes[0]; @@ -1202,7 +1216,7 @@ CatchAnnotation.prototype = { var id = item.attr('annotationid'); var permissions = annotator.plugins.Permissions; var annotation = item.data('annotation'); - var authorized = permissions.options.userAuthorize('delete', annotation,permissions.user); + var authorized = permissions.options.userAuthorize('delete', annotation, permissions.user); if(authorized){ if(confirm('Would you like to delete this reply?')){ annotator.plugins['Store']._apiRequest('destroy', annotation, function(){}); diff --git a/common/static/js/vendor/ova/grouping-annotator.js b/common/static/js/vendor/ova/grouping-annotator.js new file mode 100644 index 0000000000..a17fb63ffb --- /dev/null +++ b/common/static/js/vendor/ova/grouping-annotator.js @@ -0,0 +1,269 @@ +var _ref; +var __bind = function(fn, me) { + return function() { + return fn.apply(me, arguments); + }; +}; +var __hasProp = {}.hasOwnProperty; +var __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.Grouping = (function(_super) { + __extends(Grouping, _super); + + // this plugin will have a threshold option (-1 = plugin should be removed) + Grouping.prototype.options = null; + + // sets up the grouping structure for the plug-in + function Grouping(element, options) { + this.pluginInit = __bind(this.pluginInit, this); + this.reloadAnnotations = __bind(this.reloadAnnotations, this); + this.groupAndColor = __bind(this.groupAndColor, this); + this.clearGrouping = __bind(this.clearGrouping, this); + this.getPos = __bind(this.getPos, this); + this.groupingButtonPressed = __bind(this.groupingButtonPressed, this); + this.options = options; + _ref = Grouping.__super__.constructor.apply(this, arguments); + return _ref; + } + + // instantiation of variables to be passed around below + Grouping.prototype.unfilteredAnnotations = null; + Grouping.prototype.groupedAnnotations = null; + Grouping.prototype.groupthreshold = 0; + Grouping.prototype.useGrouping = 1; + + /** + * Gets the current position relative to the annotation wrapper + * @param {HTMLElement} el Element (assumed to be within annotator-wrapper) being measured. + * @return {Object} Position of element passed in using x, y coordinates + */ + Grouping.prototype.getPos = function(el) { + // gets the offset of the element and wrapper + var off = $(el).offset(); + var wrapperOff = $($('.annotator-wrapper')[0]).offset(); + + // do height calculations from the wrapper + return {x:off.left, y:off.top-wrapperOff.top}; + } + + /** + * Initializes the plugin and its attributes. + */ + Grouping.prototype.pluginInit = function() { + // Check that annotator is working + if (!Annotator.supported()) { + console.log("Annotator is not supported"); + return; + } + + // makes sure that every time a change is made to annotations, the grouping is redone + this.annotator.subscribe('annotationsLoaded', this.reloadAnnotations); + this.annotator.subscribe('annotationUploaded', this.reloadAnnotations); + this.annotator.subscribe('annotationDeleted', this.reloadAnnotations); + this.annotator.subscribe('annotationCreated', this.reloadAnnotations); + this.annotator.subscribe('changedTabsInCatch', this.groupingButtonPressed); + + // sets up the button that toggles the grouping on or off + var newdiv = document.createElement('div'); + var className = 'onOffGroupButton'; + newdiv.setAttribute('class', className); + + // if the item is in public then it should default to grouping being on + if (options.optionsOVA.default_tab.toLowerCase() === 'public') { + newdiv.innerHTML = "Annotation Grouping: ON"; + this.useGrouping = 1; + // we wait for HighlightTags to complete before reloading annotations + this.annotator.subscribe('colorizeCompleted', this.reloadAnnotations); + } else { + newdiv.innerHTML = "Annotation Grouping: OFF"; + $(newdiv).addClass('buttonOff'); + this.useGrouping = 0; + } + $($('.annotator-wrapper')[0]).prepend(newdiv); + $(newdiv).click(this.groupingButtonPressed); + + // makes sure that if user resizes window, the annotations are regrouped + var self = this; + $(window).resize(function() { + self.reloadAnnotations();//resize just happened, pixels changed + }); + }; + + /** + * Helper function that removes all of the side buttons and sets background to yellow + */ + Grouping.prototype.clearGrouping = function() { + $('.groupButton').remove(); + $.each(this.unfilteredAnnotations, function(val) { + if (val.highlights !== undefined){ + $.each(val.highlights, function(high){ + $(high).css("background-color", "inherit"); + }); + } + }); + } + + /** + * Helper function that goes through and groups together annotations on the same line + */ + Grouping.prototype.groupAndColor = function() { + annotations = this.unfilteredAnnotations; + lineAnnDict = {}; + var self = this; + + // for each annotation, if they have highlights, get the positions and add them + // to a dictionary based on its initial line location + annotations.forEach(function(annot) { + if (annot.highlights !== undefined) { + var loc = Math.round(self.getPos(annot.highlights[0]).y); + if (lineAnnDict[loc] === undefined) { + lineAnnDict[loc] = [annot]; + return; + } else { + lineAnnDict[loc].push(annot); + return; + } + } + }); + this.groupedAnnotations = null; + this.groupedAnnotations = lineAnnDict; + + // Then it goes through and sets the color based on the threshold set + var self = this; + $.each(lineAnnDict, function(key, val) { + if (val.length > self.groupthreshold) { + val.forEach(function(anno){ + $.each(anno.highlights, function(key, anno) { + $(anno).css("background-color", "inherit"); + }); + }); + } else { + val.forEach(function(anno) { + $.each(anno.highlights, function(key, anno) { + $(anno).css("background-color", "rgba(255, 255, 10, .3)"); + }); + }); + } + }); + } + + /** + * Helper function that clears old groupings, regroups, and adds the side buttons. + */ + Grouping.prototype.reloadAnnotations = function() { + var annotations = this.annotator.plugins['Store'].annotations; + // clear the sidebuttons + this.unfilteredAnnotations = annotations; + this.clearGrouping(); + if (this.useGrouping === 0) { + return; + } + this.groupAndColor(); + var self = this; + + // The following creates a sidebutton that is based on line location. it will + // contain a number referring to the number of hidden annotations + $.each(this.groupedAnnotations, function(key, val) { + if (val.length > self.groupthreshold) { + var newdiv = document.createElement('div'); + var className = 'groupButton'; + newdiv.setAttribute('class', className); + $(newdiv).css('top', "" + key + "px"); + newdiv.innerHTML = val.length; + $(newdiv).attr('data-selected', '0'); + $('.annotator-wrapper')[0].appendChild(newdiv); + $(newdiv).click(function(evt){ + if($(evt.srcElement).attr("data-selected") === '0') { + annotations.forEach(function(annot){ + $.each(annot.highlights, function(key, ann) { + $(ann).css("background-color", "inherit"); + }); + }); + self.groupedAnnotations[$(evt.srcElement).css("top").replace("px", "")].forEach(function(item) { + $.each(item.highlights, function(key, ann) { + $(ann).css("background-color", "rgba(255, 255, 10, 0.3)"); + }); + }); + $(evt.srcElement).attr("data-selected", '1'); + } else { + annotations.forEach(function(item) { + $(item).css("background-color", "inherit"); + }); + self.groupAndColor(); + $(evt.srcElement).attr("data-selected", '0'); + } + }); + } + }); + var self = this; + var old = self.unfilteredAnnotations.length; + setTimeout(function() { + if (old !== self.unfilteredAnnotations.length) { + self.reloadAnnotations(); + } + }, 500); + return; + }; + + /** + * Function activated to turn grouping on or off + */ + Grouping.prototype.groupingButtonPressed = function() { + if(this.useGrouping === 1) { + + // grouping is cleared + this.useGrouping = 0; + this.clearGrouping(); + + // remove the grouping functions from being activated by events + this.annotator.unsubscribe('annotationsLoaded', this.reloadAnnotations); + this.annotator.unsubscribe('annotationUploaded', this.reloadAnnotations); + this.annotator.unsubscribe('annotationDeleted', this.reloadAnnotations); + this.annotator.unsubscribe('annotationCreated', this.reloadAnnotations); + + // redraw button to turn grouping on/off + $(".onOffGroupButton").html("Annotation Grouping: OFF"); + $(".onOffGroupButton").addClass("buttonOff"); + this.annotator.plugins.Store.annotations.forEach(function(annot) { + $.each(annot.highlights, function(key, ann) { + $(ann).css("background-color", ""); + }); + }); + + // deals with the HighlightTags Plug-In + this.annotator.publish('externalCallToHighlightTags'); + this.annotator.unsubscribe('colorizeCompleted', this.reloadAnnotations); + } else { + + // runs reload/regroup annotations + this.useGrouping = 1; + this.reloadAnnotations(); + + // subscribe again to the events triggered by annotations + this.annotator.subscribe('annotationsLoaded', this.reloadAnnotations); + this.annotator.subscribe('annotationUploaded', this.reloadAnnotations); + this.annotator.subscribe('annotationDeleted', this.reloadAnnotations); + this.annotator.subscribe('annotationCreated', this.reloadAnnotations); + + // redraw button to turn grouping on/off + $(".onOffGroupButton").html("Annotation Grouping: ON"); + $(".onOffGroupButton").removeClass("buttonOff"); + this.annotator.subscribe('colorizeCompleted', this.reloadAnnotations); + } + } + + return Grouping; + +})(Annotator.Plugin); \ No newline at end of file diff --git a/common/static/js/vendor/ova/tags-annotator.js b/common/static/js/vendor/ova/tags-annotator.js index 80d60a660a..d418f65a92 100644 --- a/common/static/js/vendor/ova/tags-annotator.js +++ b/common/static/js/vendor/ova/tags-annotator.js @@ -902,6 +902,7 @@ Annotator.Plugin.HighlightTags = (function(_super) { this.updateViewer = __bind(this.updateViewer, this); this.colorize = __bind(this.colorize, this); this.updateField = __bind(this.updateField, this); + this.externalCall = __bind(this.externalCall, this); this.options = options; _ref = HighlightTags.__super__.constructor.apply(this, arguments); @@ -950,6 +951,7 @@ Annotator.Plugin.HighlightTags = (function(_super) { this.annotator.subscribe('annotationUpdated', this.colorize); this.annotator.subscribe('flaggedAnnotation', this.updateViewer); this.annotator.subscribe('annotationCreated', this.colorize); + this.annotator.subscribe('externalCallToHighlightTags', this.externalCall); }; @@ -1054,6 +1056,8 @@ Annotator.Plugin.HighlightTags = (function(_super) { $(annotations[annNum]).css("background",""); } } + + this.annotator.publish('colorizeCompleted'); } HighlightTags.prototype.updateField = function(field, annotation){ @@ -1130,6 +1134,13 @@ Annotator.Plugin.HighlightTags = (function(_super) { this.annotator.publish("finishedDrawingTags"); } + //The following will call the colorize function during an external call and then return + //an event signaling completion. + HighlightTags.prototype.externalCall = function(){ + this.colorize(); + this.annotator.publish('finishedExternalCallToHighlightTags'); + } + return HighlightTags; })(Annotator.Plugin); diff --git a/lms/envs/common.py b/lms/envs/common.py index 8ee359e27a..45df9dcfa8 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -908,6 +908,7 @@ main_vendor_js = [ 'js/vendor/ova/tags-annotator.js', 'js/vendor/ova/flagging-annotator.js', 'js/vendor/ova/diacritic-annotator.js', + 'js/vendor/ova/grouping-annotator.js', 'js/vendor/ova/jquery-Watch.js', 'js/vendor/ova/openseadragon.js', 'js/vendor/ova/OpenSeaDragonAnnotation.js', @@ -939,6 +940,7 @@ PIPELINE_CSS = { 'css/vendor/ova/tags-annotator.css', 'css/vendor/ova/flagging-annotator.css', 'css/vendor/ova/diacritic-annotator.css', + 'css/vendor/ova/grouping-annotator.css', 'css/vendor/ova/ova.css', 'js/vendor/ova/catch/css/main.css' ], diff --git a/lms/templates/textannotation.html b/lms/templates/textannotation.html index 19a261f7fa..0728226501 100644 --- a/lms/templates/textannotation.html +++ b/lms/templates/textannotation.html @@ -154,7 +154,7 @@ ${static.css(group='style-vendor-tinymce-skin', raw=True)} }, optionsVideoJS: {techOrder: ["html5","flash","youtube"]}, optionsRS: {}, - optionsOVA: {posBigNew:'none'}, + optionsOVA: {posBigNew:'none', default_tab:"${default_tab}"}, optionsRichText: { tinymce:{ selector: "li.annotator-item textarea", @@ -177,6 +177,9 @@ ${static.css(group='style-vendor-tinymce-skin', raw=True)} //Load the plugin Video/Text Annotation var ova = new OpenVideoAnnotation.Annotator($('#textHolder'),options); + if (typeof Annotator.Plugin["Grouping"] === 'function') + ova.annotator.addPlugin("Grouping"); + var userId = ('${default_tab}'.toLowerCase() === 'instructor') ? '${instructor_email}': '${user.email}';