From 874a322d71bf3b3261648e85fa2c7426eed1c933 Mon Sep 17 00:00:00 2001 From: edX requirements bot Date: Tue, 5 Sep 2023 06:46:18 -0400 Subject: [PATCH] fix: setup.py update using script --- build/lib/imagemodal/__init__.py | 4 + build/lib/imagemodal/mixins/__init__.py | 3 + build/lib/imagemodal/mixins/fragment.py | 90 ++++++++++ build/lib/imagemodal/mixins/scenario.py | 72 ++++++++ build/lib/imagemodal/models.py | 70 ++++++++ .../imagemodal/public/draggabilly.pkgd.min.js | 8 + build/lib/imagemodal/public/view.css | 89 +++++++++ build/lib/imagemodal/public/view.js | 170 ++++++++++++++++++ build/lib/imagemodal/public/view.less | 110 ++++++++++++ .../imagemodal/scenarios/image-modal-many.xml | 14 ++ .../scenarios/image-modal-single.xml | 7 + build/lib/imagemodal/settings.py | 18 ++ build/lib/imagemodal/templates/view.html | 34 ++++ .../translations/en/LC_MESSAGES/django.po | 62 +++++++ .../translations/en/LC_MESSAGES/text.po | 62 +++++++ build/lib/imagemodal/views.py | 46 +++++ build/lib/imagemodal/xblocks.py | 20 +++ setup.py | 41 ++++- 18 files changed, 914 insertions(+), 6 deletions(-) create mode 100644 build/lib/imagemodal/__init__.py create mode 100644 build/lib/imagemodal/mixins/__init__.py create mode 100644 build/lib/imagemodal/mixins/fragment.py create mode 100644 build/lib/imagemodal/mixins/scenario.py create mode 100644 build/lib/imagemodal/models.py create mode 100644 build/lib/imagemodal/public/draggabilly.pkgd.min.js create mode 100644 build/lib/imagemodal/public/view.css create mode 100644 build/lib/imagemodal/public/view.js create mode 100644 build/lib/imagemodal/public/view.less create mode 100644 build/lib/imagemodal/scenarios/image-modal-many.xml create mode 100644 build/lib/imagemodal/scenarios/image-modal-single.xml create mode 100644 build/lib/imagemodal/settings.py create mode 100644 build/lib/imagemodal/templates/view.html create mode 100644 build/lib/imagemodal/translations/en/LC_MESSAGES/django.po create mode 100644 build/lib/imagemodal/translations/en/LC_MESSAGES/text.po create mode 100644 build/lib/imagemodal/views.py create mode 100644 build/lib/imagemodal/xblocks.py diff --git a/build/lib/imagemodal/__init__.py b/build/lib/imagemodal/__init__.py new file mode 100644 index 0000000..7ea114f --- /dev/null +++ b/build/lib/imagemodal/__init__.py @@ -0,0 +1,4 @@ +""" +A fullscreen, zooming image modal XBlock +""" +from .xblocks import ImageModal diff --git a/build/lib/imagemodal/mixins/__init__.py b/build/lib/imagemodal/mixins/__init__.py new file mode 100644 index 0000000..ccb716c --- /dev/null +++ b/build/lib/imagemodal/mixins/__init__.py @@ -0,0 +1,3 @@ +""" +Mixin behavior to XBlocks +""" diff --git a/build/lib/imagemodal/mixins/fragment.py b/build/lib/imagemodal/mixins/fragment.py new file mode 100644 index 0000000..ed5091b --- /dev/null +++ b/build/lib/imagemodal/mixins/fragment.py @@ -0,0 +1,90 @@ +""" +Mixin fragment/html behavior into XBlocks + +Note: We should resume test coverage for all lines in this file once +split into its own library. +""" +from django.template.context import Context +from xblock.core import XBlock +from xblock.fragment import Fragment + + +class XBlockFragmentBuilderMixin: + """ + Create a default XBlock fragment builder + """ + static_css = [ + 'view.css', + ] + static_js = [ + 'view.js', + ] + static_js_init = None + template = 'view.html' + + def provide_context(self, context): # pragma: no cover + """ + Build a context dictionary to render the student view + + This should generally be overriden by child classes. + """ + context = context or {} + context = dict(context) + return context + + @XBlock.supports('multi_device') + def student_view(self, context=None): + """ + Build the fragment for the default student view + """ + template = self.template + context = self.provide_context(context) + static_css = self.static_css or [] + static_js = self.static_js or [] + js_init = self.static_js_init + fragment = self.build_fragment( + template=template, + context=context, + css=static_css, + js=static_js, + js_init=js_init, + ) + return fragment + + def build_fragment( + self, + template='', + context=None, + css=None, + js=None, + js_init=None, + ): + """ + Creates a fragment for display. + """ + context = context or {} + css = css or [] + js = js or [] + rendered_template = '' + if template: # pragma: no cover + template = 'templates/' + template + rendered_template = self.loader.render_django_template( + template, + context=Context(context), + i18n_service=self.runtime.service(self, 'i18n'), + ) + fragment = Fragment(rendered_template) + for item in css: + if item.startswith('/'): + url = item + else: + item = 'public/' + item + url = self.runtime.local_resource_url(self, item) + fragment.add_css_url(url) + for item in js: + item = 'public/' + item + url = self.runtime.local_resource_url(self, item) + fragment.add_javascript_url(url) + if js_init: # pragma: no cover + fragment.initialize_js(js_init) + return fragment diff --git a/build/lib/imagemodal/mixins/scenario.py b/build/lib/imagemodal/mixins/scenario.py new file mode 100644 index 0000000..e08255a --- /dev/null +++ b/build/lib/imagemodal/mixins/scenario.py @@ -0,0 +1,72 @@ +""" +Mixin workbench behavior into XBlocks +""" +from glob import glob +import pkg_resources + + +def _read_file(file_path): + """ + Read in a file's contents + """ + with open(file_path, encoding='utf-8') as file_input: + file_contents = file_input.read() + return file_contents + + +def _parse_title(file_path): + """ + Parse a title from a file name + """ + title = file_path + title = title.split('/')[-1] + title = '.'.join(title.split('.')[:-1]) + title = ' '.join(title.split('-')) + title = ' '.join([ + word.capitalize() + for word in title.split(' ') + ]) + return title + + +def _read_files(files): + """ + Read the contents of a list of files + """ + file_contents = [ + ( + _parse_title(file_path), + _read_file(file_path), + ) + for file_path in files + ] + return file_contents + + +def _find_files(directory): + """ + Find XML files in the directory + """ + pattern = "{directory}/*.xml".format( + directory=directory, + ) + files = glob(pattern) + return files + + +class XBlockWorkbenchMixin: + """ + Provide a default test workbench for the XBlock + """ + + @classmethod + def workbench_scenarios(cls): + """ + Gather scenarios to be displayed in the workbench + """ + module = cls.__module__ + module = module.split('.', maxsplit=1)[0] + directory = pkg_resources.resource_filename(module, 'scenarios') + files = _find_files(directory) + scenarios = _read_files(files) + return scenarios diff --git a/build/lib/imagemodal/models.py b/build/lib/imagemodal/models.py new file mode 100644 index 0000000..9fe6f29 --- /dev/null +++ b/build/lib/imagemodal/models.py @@ -0,0 +1,70 @@ +""" +Handle data access logic for the XBlock +""" +from django.utils.translation import gettext_lazy as _ +from xblock.fields import Scope +from xblock.fields import String + + +class ImageModalModelMixin: + """ + Handle data access for Image Modal XBlock instances + """ + + editable_fields = [ + 'display_name', + 'image_url', + 'thumbnail_url', + 'description', + 'alt_text', + ] + + show_in_read_only_mode = True + + display_name = String( + display_name=_('Display Name'), + default='Image Modal XBlock', + scope=Scope.settings, + help=_("This is the XBlock's display name"), + ) + + image_url = String( + display_name=_('Image URL'), + default=( + 'http://upload.wikimedia.org/' + 'wikipedia/commons/4/48/' + '1853_Kaei_6_Japanese_Map_of_the_World_-_' + 'Geographicus_-_ChikyuBankokuHozu-nakajima-1853.jpg' + ), + scope=Scope.settings, + help=_( + 'This is the location of the full-screen image to be displayed.' + ), + ) + + thumbnail_url = String( + display_name=_('Thumbnail URL'), + default='', + scope=Scope.settings, + help=_( + 'This is the (optional) location of a thumbnail image to be ' + 'displayed before the main image has been enlarged.' + ), + ) + + description = String( + display_name=_('Description'), + default='', + scope=Scope.settings, + help=_('Description text, displayed to screen readers'), + multiline_editor=True, + ) + + alt_text = String( + display_name=_('Alt Text'), + default='', + scope=Scope.settings, + help=_( + 'This field allows you to add alternate or descriptive text that pertains to your image.' + ), + ) diff --git a/build/lib/imagemodal/public/draggabilly.pkgd.min.js b/build/lib/imagemodal/public/draggabilly.pkgd.min.js new file mode 100644 index 0000000..11861d9 --- /dev/null +++ b/build/lib/imagemodal/public/draggabilly.pkgd.min.js @@ -0,0 +1,8 @@ +/*! + * Draggabilly PACKAGED v1.2.4 + * Make that shiz draggable + * http://draggabilly.desandro.com + * MIT license + */ + +!function(t){function e(){}function n(t){function n(e){e.prototype.option||(e.prototype.option=function(e){t.isPlainObject(e)&&(this.options=t.extend(!0,this.options,e))})}function o(e,n){t.fn[e]=function(o){if("string"==typeof o){for(var s=i.call(arguments,1),a=0,p=this.length;p>a;a++){var u=this[a],d=t.data(u,e);if(d)if(t.isFunction(d[o])&&"_"!==o.charAt(0)){var c=d[o].apply(d,s);if(void 0!==c)return c}else r("no such method '"+o+"' for "+e+" instance");else r("cannot call methods on "+e+" prior to initialization; attempted to call '"+o+"'")}return this}return this.each(function(){var i=t.data(this,e);i?(i.option(o),i._init()):(i=new n(this,o),t.data(this,e,i))})}}if(t){var r="undefined"==typeof console?e:function(t){console.error(t)};return t.bridget=function(t,e){n(e),o(t,e)},t.bridget}}var i=Array.prototype.slice;"function"==typeof define&&define.amd?define("jquery-bridget/jquery.bridget",["jquery"],n):n("object"==typeof exports?require("jquery"):t.jQuery)}(window),function(t){function e(t){return new RegExp("(^|\\s+)"+t+"(\\s+|$)")}function n(t,e){var n=i(t,e)?r:o;n(t,e)}var i,o,r;"classList"in document.documentElement?(i=function(t,e){return t.classList.contains(e)},o=function(t,e){t.classList.add(e)},r=function(t,e){t.classList.remove(e)}):(i=function(t,n){return e(n).test(t.className)},o=function(t,e){i(t,e)||(t.className=t.className+" "+e)},r=function(t,n){t.className=t.className.replace(e(n)," ")});var s={hasClass:i,addClass:o,removeClass:r,toggleClass:n,has:i,add:o,remove:r,toggle:n};"function"==typeof define&&define.amd?define("classie/classie",s):"object"==typeof exports?module.exports=s:t.classie=s}(window),function(t){function e(t){if(t){if("string"==typeof i[t])return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e,o=0,r=n.length;r>o;o++)if(e=n[o]+t,"string"==typeof i[e])return e}}var n="Webkit Moz ms Ms O".split(" "),i=document.documentElement.style;"function"==typeof define&&define.amd?define("get-style-property/get-style-property",[],function(){return e}):"object"==typeof exports?module.exports=e:t.getStyleProperty=e}(window),function(t){function e(t){var e=parseFloat(t),n=-1===t.indexOf("%")&&!isNaN(e);return n&&e}function n(){}function i(){for(var t={width:0,height:0,innerWidth:0,innerHeight:0,outerWidth:0,outerHeight:0},e=0,n=s.length;n>e;e++){var i=s[e];t[i]=0}return t}function o(n){function o(){if(!h){h=!0;var i=t.getComputedStyle;if(u=function(){var t=i?function(t){return i(t,null)}:function(t){return t.currentStyle};return function(e){var n=t(e);return n||r("Style returned "+n+". Are you running this code in a hidden iframe on Firefox? See http://bit.ly/getsizebug1"),n}}(),d=n("boxSizing")){var o=document.createElement("div");o.style.width="200px",o.style.padding="1px 2px 3px 4px",o.style.borderStyle="solid",o.style.borderWidth="1px 2px 3px 4px",o.style[d]="border-box";var s=document.body||document.documentElement;s.appendChild(o);var a=u(o);c=200===e(a.width),s.removeChild(o)}}}function a(t){if(o(),"string"==typeof t&&(t=document.querySelector(t)),t&&"object"==typeof t&&t.nodeType){var n=u(t);if("none"===n.display)return i();var r={};r.width=t.offsetWidth,r.height=t.offsetHeight;for(var a=r.isBorderBox=!(!d||!n[d]||"border-box"!==n[d]),h=0,f=s.length;f>h;h++){var l=s[h],g=n[l];g=p(t,g);var v=parseFloat(g);r[l]=isNaN(v)?0:v}var y=r.paddingLeft+r.paddingRight,m=r.paddingTop+r.paddingBottom,E=r.marginLeft+r.marginRight,b=r.marginTop+r.marginBottom,P=r.borderLeftWidth+r.borderRightWidth,x=r.borderTopWidth+r.borderBottomWidth,_=a&&c,w=e(n.width);w!==!1&&(r.width=w+(_?0:y+P));var S=e(n.height);return S!==!1&&(r.height=S+(_?0:m+x)),r.innerWidth=r.width-(y+P),r.innerHeight=r.height-(m+x),r.outerWidth=r.width+E,r.outerHeight=r.height+b,r}}function p(e,n){if(t.getComputedStyle||-1===n.indexOf("%"))return n;var i=e.style,o=i.left,r=e.runtimeStyle,s=r&&r.left;return s&&(r.left=e.currentStyle.left),i.left=n,n=i.pixelLeft,i.left=o,s&&(r.left=s),n}var u,d,c,h=!1;return a}var r="undefined"==typeof console?n:function(t){console.error(t)},s=["paddingLeft","paddingRight","paddingTop","paddingBottom","marginLeft","marginRight","marginTop","marginBottom","borderLeftWidth","borderRightWidth","borderTopWidth","borderBottomWidth"];"function"==typeof define&&define.amd?define("get-size/get-size",["get-style-property/get-style-property"],o):"object"==typeof exports?module.exports=o(require("desandro-get-style-property")):t.getSize=o(t.getStyleProperty)}(window),function(t){function e(e){var n=t.event;return n.target=n.target||n.srcElement||e,n}var n=document.documentElement,i=function(){};n.addEventListener?i=function(t,e,n){t.addEventListener(e,n,!1)}:n.attachEvent&&(i=function(t,n,i){t[n+i]=i.handleEvent?function(){var n=e(t);i.handleEvent.call(i,n)}:function(){var n=e(t);i.call(t,n)},t.attachEvent("on"+n,t[n+i])});var o=function(){};n.removeEventListener?o=function(t,e,n){t.removeEventListener(e,n,!1)}:n.detachEvent&&(o=function(t,e,n){t.detachEvent("on"+e,t[e+n]);try{delete t[e+n]}catch(i){t[e+n]=void 0}});var r={bind:i,unbind:o};"function"==typeof define&&define.amd?define("eventie/eventie",r):"object"==typeof exports?module.exports=r:t.eventie=r}(window),function(){function t(){}function e(t,e){for(var n=t.length;n--;)if(t[n].listener===e)return n;return-1}function n(t){return function(){return this[t].apply(this,arguments)}}var i=t.prototype,o=this,r=o.EventEmitter;i.getListeners=function(t){var e,n,i=this._getEvents();if(t instanceof RegExp){e={};for(n in i)i.hasOwnProperty(n)&&t.test(n)&&(e[n]=i[n])}else e=i[t]||(i[t]=[]);return e},i.flattenListeners=function(t){var e,n=[];for(e=0;ee;e++){var i=t[e];if(i.identifier==this.pointerIdentifier)return i}},o.prototype.onmousedown=function(t){var e=t.button;e&&0!==e&&1!==e||this._pointerDown(t,t)},o.prototype.ontouchstart=function(t){this._pointerDown(t,t.changedTouches[0])},o.prototype.onMSPointerDown=o.prototype.onpointerdown=function(t){this._pointerDown(t,t)},o.prototype._pointerDown=function(t,e){this.isPointerDown||(this.isPointerDown=!0,this.pointerIdentifier=void 0!==e.pointerId?e.pointerId:e.identifier,this.pointerDown(t,e))},o.prototype.pointerDown=function(t,e){this._bindPostStartEvents(t),this.emitEvent("pointerDown",[t,e])};var r={mousedown:["mousemove","mouseup"],touchstart:["touchmove","touchend","touchcancel"],pointerdown:["pointermove","pointerup","pointercancel"],MSPointerDown:["MSPointerMove","MSPointerUp","MSPointerCancel"]};return o.prototype._bindPostStartEvents=function(e){if(e){for(var i=r[e.type],o=e.preventDefault?t:document,s=0,a=i.length;a>s;s++){var p=i[s];n.bind(o,p,this)}this._boundPointerEvents={events:i,node:o}}},o.prototype._unbindPostStartEvents=function(){var t=this._boundPointerEvents;if(t&&t.events){for(var e=0,i=t.events.length;i>e;e++){var o=t.events[e];n.unbind(t.node,o,this)}delete this._boundPointerEvents}},o.prototype.onmousemove=function(t){this._pointerMove(t,t)},o.prototype.onMSPointerMove=o.prototype.onpointermove=function(t){t.pointerId==this.pointerIdentifier&&this._pointerMove(t,t)},o.prototype.ontouchmove=function(t){var e=this.getTouch(t.changedTouches);e&&this._pointerMove(t,e)},o.prototype._pointerMove=function(t,e){this.pointerMove(t,e)},o.prototype.pointerMove=function(t,e){this.emitEvent("pointerMove",[t,e])},o.prototype.onmouseup=function(t){this._pointerUp(t,t)},o.prototype.onMSPointerUp=o.prototype.onpointerup=function(t){t.pointerId==this.pointerIdentifier&&this._pointerUp(t,t)},o.prototype.ontouchend=function(t){var e=this.getTouch(t.changedTouches);e&&this._pointerUp(t,e)},o.prototype._pointerUp=function(t,e){this._pointerDone(),this.pointerUp(t,e)},o.prototype.pointerUp=function(t,e){this.emitEvent("pointerUp",[t,e])},o.prototype._pointerDone=function(){this.isPointerDown=!1,delete this.pointerIdentifier,this._unbindPostStartEvents(),this.pointerDone()},o.prototype.pointerDone=i,o.prototype.onMSPointerCancel=o.prototype.onpointercancel=function(t){t.pointerId==this.pointerIdentifier&&this._pointerCancel(t,t)},o.prototype.ontouchcancel=function(t){var e=this.getTouch(t.changedTouches);e&&this._pointerCancel(t,e)},o.prototype._pointerCancel=function(t,e){this._pointerDone(),this.pointerCancel(t,e)},o.prototype.pointerCancel=function(t,e){this.emitEvent("pointerCancel",[t,e])},o.getPointerPoint=function(t){return{x:void 0!==t.pageX?t.pageX:t.clientX,y:void 0!==t.pageY?t.pageY:t.clientY}},o}),function(t,e){"function"==typeof define&&define.amd?define("unidragger/unidragger",["eventie/eventie","unipointer/unipointer"],function(n,i){return e(t,n,i)}):"object"==typeof exports?module.exports=e(t,require("eventie"),require("unipointer")):t.Unidragger=e(t,t.eventie,t.Unipointer)}(window,function(t,e,n){function i(){}function o(t){t.preventDefault?t.preventDefault():t.returnValue=!1}function r(t){for(;t!=document.body;)if(t=t.parentNode,"A"==t.nodeName)return t}function s(){}function a(){return!1}s.prototype=new n,s.prototype.bindHandles=function(){this._bindHandles(!0)},s.prototype.unbindHandles=function(){this._bindHandles(!1)};var p=t.navigator;s.prototype._bindHandles=function(t){t=void 0===t?!0:!!t;var n;n=p.pointerEnabled?function(e){e.style.touchAction=t?"none":""}:p.msPointerEnabled?function(e){e.style.msTouchAction=t?"none":""}:function(){t&&d(s)};for(var i=t?"bind":"unbind",o=0,r=this.handles.length;r>o;o++){var s=this.handles[o];this._bindStartEvent(s,t),n(s),e[i](s,"click",this)}};var u="attachEvent"in document.documentElement,d=u?function(t){"IMG"==t.nodeName&&(t.ondragstart=a);for(var e=t.querySelectorAll("img"),n=0,i=e.length;i>n;n++){var o=e[n];o.ondragstart=a}}:i,c=s.allowTouchstartNodes={INPUT:!0,A:!0,BUTTON:!0,SELECT:!0};return s.prototype.pointerDown=function(t,e){this._dragPointerDown(t,e);var n=document.activeElement;n&&n.blur&&n.blur(),this._bindPostStartEvents(t),this.emitEvent("pointerDown",[t,e])},s.prototype._dragPointerDown=function(t,e){this.pointerDownPoint=n.getPointerPoint(e);var i=t.target.nodeName,s="touchstart"==t.type&&(c[i]||r(t.target));s||"SELECT"==i||o(t)},s.prototype.pointerMove=function(t,e){var n=this._dragPointerMove(t,e);this.emitEvent("pointerMove",[t,e,n]),this._dragMove(t,e,n)},s.prototype._dragPointerMove=function(t,e){var i=n.getPointerPoint(e),o={x:i.x-this.pointerDownPoint.x,y:i.y-this.pointerDownPoint.y};return!this.isDragging&&this.hasDragStarted(o)&&this._dragStart(t,e),o},s.prototype.hasDragStarted=function(t){return Math.abs(t.x)>3||Math.abs(t.y)>3},s.prototype.pointerUp=function(t,e){this.emitEvent("pointerUp",[t,e]),this._dragPointerUp(t,e)},s.prototype._dragPointerUp=function(t,e){this.isDragging?this._dragEnd(t,e):this._staticClick(t,e)},s.prototype._dragStart=function(t,e){this.isDragging=!0,this.dragStartPoint=s.getPointerPoint(e),this.isPreventingClicks=!0,this.dragStart(t,e)},s.prototype.dragStart=function(t,e){this.emitEvent("dragStart",[t,e])},s.prototype._dragMove=function(t,e,n){this.isDragging&&this.dragMove(t,e,n)},s.prototype.dragMove=function(t,e,n){this.emitEvent("dragMove",[t,e,n])},s.prototype._dragEnd=function(t,e){this.isDragging=!1;var n=this;setTimeout(function(){delete n.isPreventingClicks}),this.dragEnd(t,e)},s.prototype.dragEnd=function(t,e){this.emitEvent("dragEnd",[t,e])},s.prototype.onclick=function(t){this.isPreventingClicks&&o(t)},s.prototype._staticClick=function(t,e){"INPUT"==t.target.nodeName&&"text"==t.target.type&&t.target.focus(),this.staticClick(t,e)},s.prototype.staticClick=function(t,e){this.emitEvent("staticClick",[t,e])},s.getPointerPoint=function(t){return{x:void 0!==t.pageX?t.pageX:t.clientX,y:void 0!==t.pageY?t.pageY:t.clientY}},s.getPointerPoint=n.getPointerPoint,s}),function(t,e){"function"==typeof define&&define.amd?define(["classie/classie","get-style-property/get-style-property","get-size/get-size","unidragger/unidragger"],function(n,i,o,r){return e(t,n,i,o,r)}):"object"==typeof exports?module.exports=e(t,require("desandro-classie"),require("desandro-get-style-property"),require("get-size"),require("unidragger")):t.Draggabilly=e(t,t.classie,t.getStyleProperty,t.getSize,t.Unidragger)}(window,function(t,e,n,i,o){function r(){}function s(t,e){for(var n in e)t[n]=e[n];return t}function a(t,e){this.element="string"==typeof t?d.querySelector(t):t,P&&(this.$element=P(this.element)),this.options=s({},this.constructor.defaults),this.option(e),this._create()}function p(t,e,n){return n=n||"round",e?Math[n](t/e)*e:t}for(var u,d=t.document,c=d.defaultView,h=c&&c.getComputedStyle?function(t){return c.getComputedStyle(t,null)}:function(t){return t.currentStyle},f="object"==typeof HTMLElement?function(t){return t instanceof HTMLElement}:function(t){return t&&"object"==typeof t&&1==t.nodeType&&"string"==typeof t.nodeName},l=0,g="webkit moz ms o".split(" "),v=t.requestAnimationFrame,y=t.cancelAnimationFrame,m=0;m 0 && maskWidth > 0) { + image.parent().css({ + left: -maskLeft, + top: -maskTop, + width: maskWidth, + height: maskHeight, + }); + image.css({ + top: maskTop / 2, + left: maskLeft / 2, + }); + draggie.enable(); + } else { + draggie.enable(); + } + } + + /** + * Close the image modal + * @returns {boolean} False to stop event bubbling + */ + function closeModal() { + body.css('overflow', ''); + curtain.hide(); + body.off('.imagemodal'); + buttonZoom.off('.imagemodal'); + curtain.off('.imagemodal'); + image.off('.imagemodal'); + return false; + } + + /** + * Zoom out from the image + * @returns {undefined} nothing + */ + function zoomOut() { + buttonZoomText.text('Zoom In'); + buttonZoomIcon.removeClass('icon-zoom-out'); + buttonZoomIcon.addClass('icon-zoom-in'); + image.off('.imagemodal'); + // eslint-disable-next-line no-use-before-define + image.on('click.imagemodal_zoomout', openModal); + image.removeClass('zoomed'); + image.parent().css({ + left: 0, + top: 0, + width: '100%', + height: '100%', + }); + image.css({ + left: 0, + top: 0, + }); + if (draggie) { + draggie.disable(); + draggie = null; + } + } + + /** + * Toggle the zoom state in and out + * @returns {boolean} False to stop event bubbling + */ + function toggleZoom() { + var isZoomed = image.hasClass('zoomed'); + if (isZoomed) { + zoomOut(); + } else { + zoomIn(); + } + return false; + } + + /** + * Open the image modal div + * @returns {boolean} False to stop event bubbling + */ + function openModal() { + curtain.show(); + body.css('overflow', 'hidden'); + body.on('keyup.imagemodal', function(event) { + if (event.which === KEY_ESCAPE) { + return closeModal(); + } + if (event.which === KEY_ENTER) { + return toggleZoom(); + } + return true; + }); + buttonZoom.on('click.imagemodal', toggleZoom); + curtain.on('click.imagemodal', closeModal); + image.on('click.imagemodal', toggleZoom); + return false; + } + + closeModal(); + if ($element.attr('data-runtime-class') === 'PreviewRuntime') { + anchor.on('click.imagemodal', preventDefault); + } else { + anchor.on('click.imagemodal', openModal); + buttonFullScreen.on('click.imagemodal', openModal); + } +} diff --git a/build/lib/imagemodal/public/view.less b/build/lib/imagemodal/public/view.less new file mode 100644 index 0000000..a6f5f01 --- /dev/null +++ b/build/lib/imagemodal/public/view.less @@ -0,0 +1,110 @@ +.imagemodal_block { + @dark: #2e2d29; + @light: #fbfbf9; + + P { + cursor: pointer; + } + + IMG { + max-width: 100%; + } + + A, + BUTTON { + cursor: pointer; + } + + BUTTON { + border: 2px solid @dark; + background: @light; + color: @dark; + border-radius: 5px; + cursor: pointer; + opacity: 0.9; + padding: 5px 7px 7px; + font-weight: bold; + font-size: 1em; + + I { + // Override Studio's italic style + font-style: normal; + } + } + + .close { + position: absolute; + top: 10px; + right: 10px; + } + + .zoom { + position: absolute; + bottom: 10px; + right: 10px; + } + + .fullscreen { + position: absolute; + top: 10px; + left: 10px; + } + + .wrapper { + position: relative; + } + + .count { + font-weight: bold; + } + + .curtain { + display: none; + position: fixed; + width: 100%; + height: 100%; + top: 0; + left: 0; + background-color: #000; + background-color: rgba(0, 0, 0, 0.7); + cursor: no-drop; + + /* + Override LMS's sequence-nav (z-index: 99;) + and modal-backdrop (z-index: 1000;) + */ + z-index: 1001; + + .mask { + height: 95%; + width: 95%; + margin: auto; + overflow: hidden; + position: relative; + top: 2.5%; + + .wrapper { + height: 100%; + width: 100%; + top: 0; + left: 0; + position: relative; + + IMG { + display: block; + margin: auto; + max-height: 100%; + max-width: 100%; + position: relative; + cursor: zoom-in; + } + + .zoomed { + max-height: none; + max-width: none; + cursor: move; + } + } + } + } +} diff --git a/build/lib/imagemodal/scenarios/image-modal-many.xml b/build/lib/imagemodal/scenarios/image-modal-many.xml new file mode 100644 index 0000000..fc9e649 --- /dev/null +++ b/build/lib/imagemodal/scenarios/image-modal-many.xml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/build/lib/imagemodal/scenarios/image-modal-single.xml b/build/lib/imagemodal/scenarios/image-modal-single.xml new file mode 100644 index 0000000..93ab147 --- /dev/null +++ b/build/lib/imagemodal/scenarios/image-modal-single.xml @@ -0,0 +1,7 @@ + + + diff --git a/build/lib/imagemodal/settings.py b/build/lib/imagemodal/settings.py new file mode 100644 index 0000000..3295392 --- /dev/null +++ b/build/lib/imagemodal/settings.py @@ -0,0 +1,18 @@ +""" +Stub settings for xblock +""" + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + # 'NAME': 'intentionally-omitted', + }, +} +INSTALLED_APPS = ( + 'imagemodal', +) +LOCALE_PATHS = [ + 'imagemodal/translations', +] +SECRET_KEY = 'SECRET_KEY' +DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' diff --git a/build/lib/imagemodal/templates/view.html b/build/lib/imagemodal/templates/view.html new file mode 100644 index 0000000..a926876 --- /dev/null +++ b/build/lib/imagemodal/templates/view.html @@ -0,0 +1,34 @@ +{% load i18n %} + +
+

{{display_name}}

+
+ + {{alt_text}} + + +
+
+
+
+ {{alt_text}} +
+ + +
+
+

{{description}}

+
diff --git a/build/lib/imagemodal/translations/en/LC_MESSAGES/django.po b/build/lib/imagemodal/translations/en/LC_MESSAGES/django.po new file mode 100644 index 0000000..38ceb7b --- /dev/null +++ b/build/lib/imagemodal/translations/en/LC_MESSAGES/django.po @@ -0,0 +1,62 @@ +# Stanford's Image Modal XBlock. +# Copyright (C) 2019 +# This file is distributed under the same license as the package. +# Steven Burch , 2019. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-03-09 18:45-0600\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Steven Burch \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: imagemodal/imagemodal.py:35 +msgid "Display Name" +msgstr "" + +#: imagemodal/imagemodal.py:38 +msgid "This is the XBlock's display name" +msgstr "" + +#: imagemodal/imagemodal.py:42 +msgid "Image URL" +msgstr "" + +#: imagemodal/imagemodal.py:51 +msgid "This is the location of the full-screen image to be displayed." +msgstr "" + +#: imagemodal/imagemodal.py:56 +msgid "Thumbnail URL" +msgstr "" + +#: imagemodal/imagemodal.py:60 +msgid "" +"This is the (optional) location of a thumbnail image to be displayed before " +"the main image has been enlarged." +msgstr "" + +#: imagemodal/imagemodal.py:66 +msgid "Description" +msgstr "" + +#: imagemodal/imagemodal.py:69 +msgid "Description text, displayed to screen readers" +msgstr "" + +#: imagemodal/imagemodal.py:74 +msgid "Alt Text" +msgstr "" + +#: imagemodal/imagemodal.py:78 +msgid "" +"This field allows you to add alternate or descriptive text that pertains to " +"your image." +msgstr "" diff --git a/build/lib/imagemodal/translations/en/LC_MESSAGES/text.po b/build/lib/imagemodal/translations/en/LC_MESSAGES/text.po new file mode 100644 index 0000000..38ceb7b --- /dev/null +++ b/build/lib/imagemodal/translations/en/LC_MESSAGES/text.po @@ -0,0 +1,62 @@ +# Stanford's Image Modal XBlock. +# Copyright (C) 2019 +# This file is distributed under the same license as the package. +# Steven Burch , 2019. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-03-09 18:45-0600\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Steven Burch \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: imagemodal/imagemodal.py:35 +msgid "Display Name" +msgstr "" + +#: imagemodal/imagemodal.py:38 +msgid "This is the XBlock's display name" +msgstr "" + +#: imagemodal/imagemodal.py:42 +msgid "Image URL" +msgstr "" + +#: imagemodal/imagemodal.py:51 +msgid "This is the location of the full-screen image to be displayed." +msgstr "" + +#: imagemodal/imagemodal.py:56 +msgid "Thumbnail URL" +msgstr "" + +#: imagemodal/imagemodal.py:60 +msgid "" +"This is the (optional) location of a thumbnail image to be displayed before " +"the main image has been enlarged." +msgstr "" + +#: imagemodal/imagemodal.py:66 +msgid "Description" +msgstr "" + +#: imagemodal/imagemodal.py:69 +msgid "Description text, displayed to screen readers" +msgstr "" + +#: imagemodal/imagemodal.py:74 +msgid "Alt Text" +msgstr "" + +#: imagemodal/imagemodal.py:78 +msgid "" +"This field allows you to add alternate or descriptive text that pertains to " +"your image." +msgstr "" diff --git a/build/lib/imagemodal/views.py b/build/lib/imagemodal/views.py new file mode 100644 index 0000000..e952e21 --- /dev/null +++ b/build/lib/imagemodal/views.py @@ -0,0 +1,46 @@ +""" +Handle view logic for the XBlock +""" +from xblockutils.resources import ResourceLoader +from xblockutils.studio_editable import StudioEditableXBlockMixin + +from .mixins.fragment import XBlockFragmentBuilderMixin + + +URL_FONT_AWESOME_CSS = '//netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.css' # nopep8 + + +class ImageModalViewMixin( + XBlockFragmentBuilderMixin, + StudioEditableXBlockMixin, +): + """ + Handle view logic for Image Modal XBlock instances + """ + + loader = ResourceLoader(__name__) + static_css = [ + URL_FONT_AWESOME_CSS, + 'view.css', + ] + static_js = [ + 'draggabilly.pkgd.min.js', + 'view.js', + ] + static_js_init = 'ImageModalView' + + def provide_context(self, context=None): + """ + Build a context dictionary to render the student view + """ + context = context or {} + context = dict(context) + context.update({ + 'display_name': self.display_name, + 'image_url': self.image_url, + 'thumbnail_url': self.thumbnail_url or self.image_url, + 'description': self.description, + 'xblock_id': str(self.scope_ids.usage_id), + 'alt_text': self.alt_text or self.display_name, + }) + return context diff --git a/build/lib/imagemodal/xblocks.py b/build/lib/imagemodal/xblocks.py new file mode 100644 index 0000000..1aecdae --- /dev/null +++ b/build/lib/imagemodal/xblocks.py @@ -0,0 +1,20 @@ +""" +This is the core logic for the XBlock +""" +from xblock.core import XBlock + +from .mixins.scenario import XBlockWorkbenchMixin +from .models import ImageModalModelMixin +from .views import ImageModalViewMixin + + +@XBlock.needs('i18n') +class ImageModal( + ImageModalModelMixin, + ImageModalViewMixin, + XBlockWorkbenchMixin, + XBlock, +): + """ + A fullscreen image modal XBlock. + """ diff --git a/setup.py b/setup.py index 87e7024..3db8053 100644 --- a/setup.py +++ b/setup.py @@ -24,20 +24,48 @@ def load_requirements(*requirements_paths): """ # UPDATED VIA SEMGREP - if you need to remove/modify this method remove this line and add a comment specifying why. + # e.g. {"django": "Django", "confluent-kafka": "confluent_kafka[avro]"} + by_canonical_name = {} + + def check_name_consistent(package): + """ + Raise exception if package is named different ways. + + This ensures that packages are named consistently so we can match + constraints to packages. It also ensures that if we require a package + with extras we don't constrain it without mentioning the extras (since + that too would interfere with matching constraints.) + """ + canonical = package.lower().replace('_', '-').split('[')[0] + seen_spelling = by_canonical_name.get(canonical) + if seen_spelling is None: + by_canonical_name[canonical] = package + elif seen_spelling != package: + raise Exception( + f'Encountered both "{seen_spelling}" and "{package}" in requirements ' + 'and constraints files; please use just one or the other.' + ) + requirements = {} constraint_files = set() - # groups "my-package-name<=x.y.z,..." into ("my-package-name", "<=x.y.z,...") - requirement_line_regex = re.compile(r"([a-zA-Z0-9-_.]+)([<>=][^#\s]+)?") + # groups "pkg<=x.y.z,..." into ("pkg", "<=x.y.z,...") + re_package_name_base_chars = r"a-zA-Z0-9\-_." # chars allowed in base package name + # Two groups: name[maybe,extras], and optionally a constraint + requirement_line_regex = re.compile( + r"([%s]+(?:\[[%s,\s]+\])?)([<>=][^#\s]+)?" + % (re_package_name_base_chars, re_package_name_base_chars) + ) def add_version_constraint_or_raise(current_line, current_requirements, add_if_not_present): regex_match = requirement_line_regex.match(current_line) if regex_match: package = regex_match.group(1) version_constraints = regex_match.group(2) + check_name_consistent(package) existing_version_constraints = current_requirements.get(package, None) - # it's fine to add constraints to an unconstrained package, but raise an error if there are already - # constraints in place + # It's fine to add constraints to an unconstrained package, + # but raise an error if there are already constraints in place. if existing_version_constraints and existing_version_constraints != version_constraints: raise BaseException(f'Multiple constraint definitions found for {package}:' f' "{existing_version_constraints}" and "{version_constraints}".' @@ -46,7 +74,8 @@ def add_version_constraint_or_raise(current_line, current_requirements, add_if_n if add_if_not_present or package in current_requirements: current_requirements[package] = version_constraints - # process .in files and store the path to any constraint files that are pulled in + # Read requirements from .in files and store the path to any + # constraint files that are pulled in. for path in requirements_paths: with open(path) as reqs: for line in reqs: @@ -55,7 +84,7 @@ def add_version_constraint_or_raise(current_line, current_requirements, add_if_n if line and line.startswith('-c') and not line.startswith('-c http'): constraint_files.add(os.path.dirname(path) + '/' + line.split('#')[0].replace('-c', '').strip()) - # process constraint files and add any new constraints found to existing requirements + # process constraint files: add constraints to existing requirements for constraint_file in constraint_files: with open(constraint_file) as reader: for line in reader: