From d07c7ae33e30a97825acb0678f2389c575013cd3 Mon Sep 17 00:00:00 2001 From: Huong Nguyen Date: Tue, 8 Dec 2020 16:04:09 +0700 Subject: [PATCH] MDL-70426 qtype_ddmarker: infinite markers keep duplicating --- .../type/ddmarker/amd/build/question.min.js | 2 +- .../ddmarker/amd/build/question.min.js.map | 2 +- question/type/ddmarker/amd/src/question.js | 63 ++++++++++++++----- 3 files changed, 50 insertions(+), 17 deletions(-) diff --git a/question/type/ddmarker/amd/build/question.min.js b/question/type/ddmarker/amd/build/question.min.js index 9b4d45e92e5..aaf8d2e39af 100644 --- a/question/type/ddmarker/amd/build/question.min.js +++ b/question/type/ddmarker/amd/build/question.min.js @@ -1,2 +1,2 @@ -define ("qtype_ddmarker/question",["jquery","core/dragdrop","qtype_ddmarker/shapes","core/key_codes"],function(a,b,c,d){"use strict";function e(a,b,c){var d=this;this.containerId=a;this.visibleDropZones=c;this.shapes=[];this.shapeSVGs=[];this.isPrinting=!1;if(b){this.getRoot().addClass("qtype_ddmarker-readonly")}d.cloneDrags();d.repositionDrags();d.drawDropzones()}e.prototype.drawDropzones=function(){if(0");for(var b=this.getRoot().find("svg.dropzones"),c=0,d=0,e;d"+e.markertext+"");var i=this.getRoot().find("div.ddarea div.markertexts span.markertext"+b);if(i.length){var j=f.getHandlePositions(),k=j.moveHandle.x-i.outerWidth()/2-4,l=j.moveHandle.y-i.outerHeight()/2;i.css("left",k).css("top",l);i.data("originX",i.position().left/h).data("originY",i.position().top/h);this.handleElementScale(i,"center")}}var m=f.makeSvg(a[0]);m.setAttribute("class","dropzone "+d);this.shapes[this.shapes.length]=f;this.shapeSVGs[this.shapeSVGs.length]=m};e.prototype.repositionDrags=function(){var b=this.getRoot(),c=this;b.find("div.draghomes .marker").not(".dragplaceholder").each(function(b,c){a(c).addClass("unneeded")});b.find("input.choices").each(function(a,b){var d=c.getChoiceNoFromElement(b),e=c.getCoords(b);if(e.length){var f=c.getRoot().find(".draghomes span.marker.choice"+d).not(".dragplaceholder");f.remove();for(var g=0,h;g=c.left&&a.x=c.top&&a.y");for(var b=this.getRoot().find("svg.dropzones"),c=0,d=0,e;d"+e.markertext+"");var i=this.getRoot().find("div.ddarea div.markertexts span.markertext"+b);if(i.length){var j=f.getHandlePositions(),k=j.moveHandle.x-i.outerWidth()/2-4,l=j.moveHandle.y-i.outerHeight()/2;i.css("left",k).css("top",l);i.data("originX",i.position().left/h).data("originY",i.position().top/h);this.handleElementScale(i,"center")}}var m=f.makeSvg(a[0]);m.setAttribute("class","dropzone "+d);this.shapes[this.shapes.length]=f;this.shapeSVGs[this.shapeSVGs.length]=m};e.prototype.repositionDrags=function(){var b=this.getRoot(),c=this;b.find("div.draghomes .marker").not(".dragplaceholder").each(function(b,c){a(c).addClass("unneeded")});b.find("input.choices").each(function(a,b){var d=c.getChoiceNoFromElement(b),e=c.getCoords(b);if(e.length){var f=c.getRoot().find(".draghomes span.marker.choice"+d).not(".dragplaceholder");f.remove();for(var g=0,h;g=c.left&&a.x=c.top&&a.y.\n\n/**\n * Question class for drag and drop marker question type, used to support the question and preview pages.\n *\n * @package qtype_ddmarker\n * @subpackage question\n * @copyright 2018 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], function($, dragDrop, Shapes, keys) {\n\n \"use strict\";\n\n /**\n * Object to handle one drag-drop markers question.\n *\n * @param {String} containerId id of the outer div for this question.\n * @param {boolean} readOnly whether the question is being displayed read-only.\n * @param {Object[]} visibleDropZones the geometry of any drop-zones to show.\n * Objects have fields shape, coords and markertext.\n * @constructor\n */\n function DragDropMarkersQuestion(containerId, readOnly, visibleDropZones) {\n var thisQ = this;\n this.containerId = containerId;\n this.visibleDropZones = visibleDropZones;\n this.shapes = [];\n this.shapeSVGs = [];\n this.isPrinting = false;\n if (readOnly) {\n this.getRoot().addClass('qtype_ddmarker-readonly');\n }\n thisQ.cloneDrags();\n thisQ.repositionDrags();\n thisQ.drawDropzones();\n }\n\n /**\n * Draws the svg shapes of any drop zones that should be visible for feedback purposes.\n */\n DragDropMarkersQuestion.prototype.drawDropzones = function() {\n if (this.visibleDropZones.length > 0) {\n var bgImage = this.bgImage();\n\n this.getRoot().find('div.dropzones').html('');\n var svg = this.getRoot().find('svg.dropzones');\n\n var nextColourIndex = 0;\n for (var dropZoneNo = 0; dropZoneNo < this.visibleDropZones.length; dropZoneNo++) {\n var colourClass = 'color' + nextColourIndex;\n nextColourIndex = (nextColourIndex + 1) % 8;\n this.addDropzone(svg, dropZoneNo, colourClass);\n }\n }\n };\n\n /**\n * Adds a dropzone shape with colour, coords and link provided to the array of shapes.\n *\n * @param {jQuery} svg the SVG image to which to add this drop zone.\n * @param {int} dropZoneNo which drop-zone to add.\n * @param {string} colourClass class name\n */\n DragDropMarkersQuestion.prototype.addDropzone = function(svg, dropZoneNo, colourClass) {\n var dropZone = this.visibleDropZones[dropZoneNo],\n shape = Shapes.make(dropZone.shape, ''),\n existingmarkertext,\n bgRatio = this.bgRatio();\n if (!shape.parse(dropZone.coords, bgRatio)) {\n return;\n }\n\n existingmarkertext = this.getRoot().find('div.markertexts span.markertext' + dropZoneNo);\n if (existingmarkertext.length) {\n if (dropZone.markertext !== '') {\n existingmarkertext.html(dropZone.markertext);\n } else {\n existingmarkertext.remove();\n }\n } else if (dropZone.markertext !== '') {\n var classnames = 'markertext markertext' + dropZoneNo;\n this.getRoot().find('div.markertexts').append('' +\n dropZone.markertext + '');\n var markerspan = this.getRoot().find('div.ddarea div.markertexts span.markertext' + dropZoneNo);\n if (markerspan.length) {\n var handles = shape.getHandlePositions();\n var positionLeft = handles.moveHandle.x - (markerspan.outerWidth() / 2) - 4;\n var positionTop = handles.moveHandle.y - (markerspan.outerHeight() / 2);\n markerspan\n .css('left', positionLeft)\n .css('top', positionTop);\n markerspan\n .data('originX', markerspan.position().left / bgRatio)\n .data('originY', markerspan.position().top / bgRatio);\n this.handleElementScale(markerspan, 'center');\n }\n }\n\n var shapeSVG = shape.makeSvg(svg[0]);\n shapeSVG.setAttribute('class', 'dropzone ' + colourClass);\n\n this.shapes[this.shapes.length] = shape;\n this.shapeSVGs[this.shapeSVGs.length] = shapeSVG;\n };\n\n /**\n * Draws the drag items on the page (and drop zones if required).\n * The idea is to re-draw all the drags and drops whenever there is a change\n * like a widow resize or an item dropped in place.\n */\n DragDropMarkersQuestion.prototype.repositionDrags = function() {\n var root = this.getRoot(),\n thisQ = this;\n\n root.find('div.draghomes .marker').not('.dragplaceholder').each(function(key, item) {\n $(item).addClass('unneeded');\n });\n\n root.find('input.choices').each(function(key, input) {\n var choiceNo = thisQ.getChoiceNoFromElement(input),\n coords = thisQ.getCoords(input);\n if (coords.length) {\n var drag = thisQ.getRoot().find('.draghomes' + ' span.marker' + '.choice' + choiceNo).not('.dragplaceholder');\n drag.remove();\n for (var i = 0; i < coords.length; i++) {\n var dragInDrop = drag.clone();\n dragInDrop.data('pagex', coords[i].x).data('pagey', coords[i].y);\n thisQ.sendDragToDrop(dragInDrop, false);\n }\n thisQ.getDragClone(drag).addClass('active');\n thisQ.cloneDragIfNeeded(drag);\n }\n });\n };\n\n /**\n * Determine what drag items need to be shown and\n * return coords of all drag items except any that are currently being dragged\n * based on contents of hidden inputs and whether drags are 'infinite' or how many\n * drags should be shown.\n *\n * @param {jQuery} inputNode\n * @returns {Point[]} coordinates of however many copies of the drag item should be shown.\n */\n DragDropMarkersQuestion.prototype.getCoords = function(inputNode) {\n var coords = [],\n val = $(inputNode).val();\n if (val !== '') {\n var coordsStrings = val.split(';');\n for (var i = 0; i < coordsStrings.length; i++) {\n coords[i] = this.convertToWindowXY(Shapes.Point.parse(coordsStrings[i]));\n }\n }\n return coords;\n };\n\n /**\n * Converts the relative x and y position coordinates into\n * absolute x and y position coordinates.\n *\n * @param {Point} point relative to the background image.\n * @returns {Point} point relative to the page.\n */\n DragDropMarkersQuestion.prototype.convertToWindowXY = function(point) {\n var bgImage = this.bgImage();\n // The +1 seems rather odd, but seems to give the best results in\n // the three main browsers at a range of zoom levels.\n // (Its due to the 1px border around the image, that shifts the\n // image pixels by 1 down and to the left.)\n return point.offset(bgImage.offset().left + 1, bgImage.offset().top + 1);\n };\n\n /**\n * Utility function converting window coordinates to relative to the\n * background image coordinates.\n *\n * @param {Point} point relative to the page.\n * @returns {Point} point relative to the background image.\n */\n DragDropMarkersQuestion.prototype.convertToBgImgXY = function(point) {\n var bgImage = this.bgImage();\n return point.offset(-bgImage.offset().left - 1, -bgImage.offset().top - 1);\n };\n\n /**\n * Is the point within the background image?\n *\n * @param {Point} point relative to the BG image.\n * @return {boolean} true it they are.\n */\n DragDropMarkersQuestion.prototype.coordsInBgImg = function(point) {\n var bgImage = this.bgImage();\n var bgPosition = bgImage.offset();\n\n return point.x >= bgPosition.left && point.x < bgPosition.left + bgImage.width()\n && point.y >= bgPosition.top && point.y < bgPosition.top + bgImage.height();\n };\n\n /**\n * Get the outer div for this question.\n * @returns {jQuery} containing that div.\n */\n DragDropMarkersQuestion.prototype.getRoot = function() {\n return $(document.getElementById(this.containerId));\n };\n\n /**\n * Get the img that is the background image.\n * @returns {jQuery} containing that img.\n */\n DragDropMarkersQuestion.prototype.bgImage = function() {\n return this.getRoot().find('img.dropbackground');\n };\n\n DragDropMarkersQuestion.prototype.handleDragStart = function(e) {\n var thisQ = this,\n dragged = $(e.target).closest('.marker');\n\n var info = dragDrop.prepare(e);\n if (!info.start) {\n return;\n }\n\n dragged.addClass('beingdragged').css('transform', '');\n\n var placed = !dragged.hasClass('unneeded');\n if (!placed) {\n var hiddenDrag = thisQ.getDragClone(dragged);\n if (hiddenDrag.length) {\n hiddenDrag.addClass('active');\n dragged.offset(hiddenDrag.offset());\n }\n }\n\n dragDrop.start(e, dragged, function() {\n void (1);\n }, function(x, y, dragged) {\n thisQ.dragEnd(dragged);\n });\n };\n\n /**\n * Functionality at the end of a drag drop.\n * @param {jQuery} dragged the marker that was dragged.\n */\n DragDropMarkersQuestion.prototype.dragEnd = function(dragged) {\n var placed = false,\n choiceNo = this.getChoiceNoFromElement(dragged),\n bgRatio = this.bgRatio(),\n dragXY;\n\n dragged.data('pagex', dragged.offset().left).data('pagey', dragged.offset().top);\n dragXY = new Shapes.Point(dragged.data('pagex'), dragged.data('pagey'));\n if (this.coordsInBgImg(dragXY)) {\n this.sendDragToDrop(dragged, true);\n placed = true;\n\n // It seems that the dragdrop sometimes leaves the drag\n // one pixel out of position. Put it in exactly the right place.\n var bgImgXY = this.convertToBgImgXY(dragXY);\n bgImgXY = new Shapes.Point(bgImgXY.x / bgRatio, bgImgXY.y / bgRatio);\n dragged.data('originX', bgImgXY.x).data('originY', bgImgXY.y);\n }\n\n if (!placed) {\n this.sendDragHome(dragged);\n this.removeDragIfNeeded(dragged);\n } else {\n this.cloneDragIfNeeded(dragged);\n }\n\n this.saveCoordsForChoice(choiceNo);\n };\n\n /**\n * Save the coordinates for a dropped item in the form field.\n * @param {Number} choiceNo which copy of the choice this was.\n */\n DragDropMarkersQuestion.prototype.saveCoordsForChoice = function(choiceNo) {\n var coords = [],\n items = this.getRoot().find('div.droparea span.marker.choice' + choiceNo),\n thiQ = this,\n bgRatio = this.bgRatio();\n\n if (items.length) {\n items.each(function() {\n var drag = $(this);\n if (!drag.hasClass('beingdragged')) {\n var dragXY = new Shapes.Point(drag.data('pagex'), drag.data('pagey'));\n if (thiQ.coordsInBgImg(dragXY)) {\n var bgImgXY = thiQ.convertToBgImgXY(dragXY);\n bgImgXY = new Shapes.Point(bgImgXY.x / bgRatio, bgImgXY.y / bgRatio);\n coords[coords.length] = bgImgXY;\n }\n }\n });\n }\n\n this.getRoot().find('input.choice' + choiceNo).val(coords.join(';'));\n };\n\n /**\n * Handle key down / press events on markers.\n * @param {KeyboardEvent} e\n */\n DragDropMarkersQuestion.prototype.handleKeyPress = function(e) {\n var drag = $(e.target).closest('.marker'),\n point = new Shapes.Point(drag.offset().left, drag.offset().top),\n choiceNo = this.getChoiceNoFromElement(drag);\n\n switch (e.keyCode) {\n case keys.arrowLeft:\n case 65: // A.\n point.x -= 1;\n break;\n case keys.arrowRight:\n case 68: // D.\n point.x += 1;\n break;\n case keys.arrowDown:\n case 83: // S.\n point.y += 1;\n break;\n case keys.arrowUp:\n case 87: // W.\n point.y -= 1;\n break;\n case keys.space:\n case keys.escape:\n point = null;\n break;\n default:\n return; // Ingore other keys.\n }\n e.preventDefault();\n\n if (point !== null) {\n point = this.constrainToBgImg(point);\n drag.offset({'left': point.x, 'top': point.y});\n drag.data('pagex', drag.offset().left).data('pagey', drag.offset().top);\n var dragXY = this.convertToBgImgXY(new Shapes.Point(drag.data('pagex'), drag.data('pagey')));\n drag.data('originX', dragXY.x / this.bgRatio()).data('originY', dragXY.y / this.bgRatio());\n if (this.coordsInBgImg(new Shapes.Point(drag.offset().left, drag.offset().top))) {\n if (drag.hasClass('unneeded')) {\n this.sendDragToDrop(drag, true);\n var hiddenDrag = this.getDragClone(drag);\n if (hiddenDrag.length) {\n hiddenDrag.addClass('active');\n }\n this.cloneDragIfNeeded(drag);\n }\n }\n } else {\n drag.css('left', '').css('top', '');\n drag.data('pagex', drag.offset().left).data('pagey', drag.offset().top);\n this.sendDragHome(drag);\n this.removeDragIfNeeded(drag);\n }\n drag.focus();\n this.saveCoordsForChoice(choiceNo);\n };\n\n /**\n * Makes sure the dragged item always exists within the background image area.\n *\n * @param {Point} windowxy\n * @returns {Point} coordinates\n */\n DragDropMarkersQuestion.prototype.constrainToBgImg = function(windowxy) {\n var bgImg = this.bgImage(),\n bgImgXY = this.convertToBgImgXY(windowxy);\n bgImgXY.x = Math.max(0, bgImgXY.x);\n bgImgXY.y = Math.max(0, bgImgXY.y);\n bgImgXY.x = Math.min(bgImg.width(), bgImgXY.x);\n bgImgXY.y = Math.min(bgImg.height(), bgImgXY.y);\n return this.convertToWindowXY(bgImgXY);\n };\n\n /**\n * Returns the choice number for a node.\n *\n * @param {Element|jQuery} node\n * @returns {Number}\n */\n DragDropMarkersQuestion.prototype.getChoiceNoFromElement = function(node) {\n return Number(this.getClassnameNumericSuffix(node, 'choice'));\n };\n\n /**\n * Returns the numeric part of a class with the given prefix.\n *\n * @param {Element|jQuery} node\n * @param {String} prefix\n * @returns {Number|null}\n */\n DragDropMarkersQuestion.prototype.getClassnameNumericSuffix = function(node, prefix) {\n var classes = $(node).attr('class');\n if (classes !== undefined && classes !== '') {\n var classesarr = classes.split(' ');\n for (var index = 0; index < classesarr.length; index++) {\n var patt1 = new RegExp('^' + prefix + '([0-9])+$');\n if (patt1.test(classesarr[index])) {\n var patt2 = new RegExp('([0-9])+$');\n var match = patt2.exec(classesarr[index]);\n return Number(match[0]);\n }\n }\n }\n return null;\n };\n\n /**\n * Handle when the window is resized.\n */\n DragDropMarkersQuestion.prototype.handleResize = function() {\n var thisQ = this,\n bgRatio = this.bgRatio();\n if (this.isPrinting) {\n bgRatio = 1;\n }\n\n this.getRoot().find('div.droparea .marker').not('.beingdragged').each(function(key, drag) {\n $(drag)\n .css('left', parseFloat($(drag).data('originX')) * parseFloat(bgRatio))\n .css('top', parseFloat($(drag).data('originY')) * parseFloat(bgRatio));\n thisQ.handleElementScale(drag, 'left top');\n });\n\n this.getRoot().find('div.droparea svg.dropzones')\n .width(this.bgImage().width())\n .height(this.bgImage().height());\n\n for (var dropZoneNo = 0; dropZoneNo < this.visibleDropZones.length; dropZoneNo++) {\n var dropZone = thisQ.visibleDropZones[dropZoneNo];\n var originCoords = dropZone.coords;\n var shape = thisQ.shapes[dropZoneNo];\n var shapeSVG = thisQ.shapeSVGs[dropZoneNo];\n shape.parse(originCoords, bgRatio);\n shape.updateSvg(shapeSVG);\n\n var handles = shape.getHandlePositions();\n var markerSpan = this.getRoot().find('div.ddarea div.markertexts span.markertext' + dropZoneNo);\n markerSpan\n .css('left', handles.moveHandle.x - (markerSpan.outerWidth() / 2) - 4)\n .css('top', handles.moveHandle.y - (markerSpan.outerHeight() / 2));\n thisQ.handleElementScale(markerSpan, 'center');\n }\n };\n\n /**\n * Clone the drag.\n */\n DragDropMarkersQuestion.prototype.cloneDrags = function() {\n var thisQ = this;\n this.getRoot().find('div.draghomes span.marker').each(function(index, draghome) {\n var drag = $(draghome);\n var placeHolder = drag.clone();\n placeHolder.removeClass();\n placeHolder.addClass('marker choice' +\n thisQ.getChoiceNoFromElement(drag) + ' dragno' + thisQ.getDragNo(drag) + ' dragplaceholder');\n drag.before(placeHolder);\n });\n };\n\n /**\n * Get the drag number of a drag.\n *\n * @param {jQuery} drag the drag.\n * @returns {Number} the drag number.\n */\n DragDropMarkersQuestion.prototype.getDragNo = function(drag) {\n return this.getClassnameNumericSuffix(drag, 'dragno');\n };\n\n /**\n * Get drag clone for a given drag.\n *\n * @param {jQuery} drag the drag.\n * @returns {jQuery} the drag's clone.\n */\n DragDropMarkersQuestion.prototype.getDragClone = function(drag) {\n return this.getRoot().find('.draghomes' + ' span.marker' +\n '.choice' + this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag) + '.dragplaceholder');\n };\n\n /**\n * Get the drop area element.\n * @returns {jQuery} droparea element.\n */\n DragDropMarkersQuestion.prototype.dropArea = function() {\n return this.getRoot().find('div.droparea');\n };\n\n /**\n * Animate a drag back to its home.\n *\n * @param {jQuery} drag the item being moved.\n */\n DragDropMarkersQuestion.prototype.sendDragHome = function(drag) {\n drag.removeClass('beingdragged')\n .addClass('unneeded')\n .css('top', '')\n .css('left', '')\n .css('transform', '');\n var placeHolder = this.getDragClone(drag);\n placeHolder.after(drag);\n placeHolder.removeClass('active');\n };\n\n /**\n * Animate a drag item into a given place.\n *\n * @param {jQuery} drag the item to place.\n * @param {boolean} isScaling Scaling or not\n */\n DragDropMarkersQuestion.prototype.sendDragToDrop = function(drag, isScaling) {\n var dropArea = this.dropArea(),\n bgRatio = this.bgRatio();\n drag.removeClass('beingdragged').removeClass('unneeded');\n var dragXY = this.convertToBgImgXY(new Shapes.Point(drag.data('pagex'), drag.data('pagey')));\n if (isScaling) {\n drag.data('originX', dragXY.x / bgRatio).data('originY', dragXY.y / bgRatio);\n drag.css('left', dragXY.x).css('top', dragXY.y);\n } else {\n drag.data('originX', dragXY.x).data('originY', dragXY.y);\n drag.css('left', dragXY.x * bgRatio).css('top', dragXY.y * bgRatio);\n }\n dropArea.append(drag);\n this.handleElementScale(drag, 'left top');\n };\n\n /**\n * Clone the drag at the draghome area if needed.\n *\n * @param {jQuery} drag the item to place.\n */\n DragDropMarkersQuestion.prototype.cloneDragIfNeeded = function(drag) {\n var inputNode = this.getInput(drag),\n noOfDrags = Number(this.getClassnameNumericSuffix(inputNode, 'noofdrags')),\n displayedDragsInDropArea = this.getRoot().find('div.droparea .marker.choice' +\n this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag)).length,\n displayedDragsInDragHomes = this.getRoot().find('div.draghomes .marker.choice' +\n this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag)).not('.dragplaceholder').length;\n\n if (displayedDragsInDropArea < noOfDrags && displayedDragsInDragHomes === 0) {\n var dragclone = drag.clone();\n dragclone.addClass('unneeded')\n .css('top', '')\n .css('left', '')\n .css('transform', '');\n this.getDragClone(drag)\n .removeClass('active')\n .after(dragclone);\n questionManager.addEventHandlersToMarker(dragclone);\n }\n };\n\n /**\n * Remove the clone drag at the draghome area if needed.\n *\n * @param {jQuery} drag the item to place.\n */\n DragDropMarkersQuestion.prototype.removeDragIfNeeded = function(drag) {\n var displayeddrags = this.getRoot().find('div.draghomes .marker.choice' +\n this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag)).not('.dragplaceholder').length;\n if (displayeddrags > 1) {\n this.getRoot().find('div.draghomes .marker.choice' +\n this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag)).not('.dragplaceholder').first().remove();\n }\n };\n\n /**\n * Get the input belong to drag.\n *\n * @param {jQuery} drag the item to place.\n * @returns {jQuery} input element.\n */\n DragDropMarkersQuestion.prototype.getInput = function(drag) {\n var choiceNo = this.getChoiceNoFromElement(drag);\n return this.getRoot().find('input.choices.choice' + choiceNo);\n };\n\n /**\n * Return the background ratio.\n *\n * @returns {number} Background ratio.\n */\n DragDropMarkersQuestion.prototype.bgRatio = function() {\n var bgImg = this.bgImage();\n var bgImgNaturalWidth = bgImg.get(0).naturalWidth;\n var bgImgClientWidth = bgImg.width();\n\n return bgImgClientWidth / bgImgNaturalWidth;\n };\n\n /**\n * Scale the drag if needed.\n *\n * @param {jQuery} element the item to place.\n * @param {String} type scaling type\n */\n DragDropMarkersQuestion.prototype.handleElementScale = function(element, type) {\n var bgRatio = parseFloat(this.bgRatio());\n if (this.isPrinting) {\n bgRatio = 1;\n }\n $(element).css({\n '-webkit-transform': 'scale(' + bgRatio + ')',\n '-moz-transform': 'scale(' + bgRatio + ')',\n '-ms-transform': 'scale(' + bgRatio + ')',\n '-o-transform': 'scale(' + bgRatio + ')',\n 'transform': 'scale(' + bgRatio + ')',\n 'transform-origin': type\n });\n };\n\n /**\n * Singleton that tracks all the DragDropToTextQuestions on this page, and deals\n * with event dispatching.\n *\n * @type {Object}\n */\n var questionManager = {\n\n /**\n * {boolean} ensures that the event handlers are only initialised once per page.\n */\n eventHandlersInitialised: false,\n\n /**\n * {boolean} is printing or not.\n */\n isPrinting: false,\n\n /**\n * {boolean} is keyboard navigation.\n */\n isKeyboardNavigation: false,\n\n /**\n * {Object} all the questions on this page, indexed by containerId (id on the .que div).\n */\n questions: {}, // An object containing all the information about each question on the page.\n\n /**\n * Initialise one question.\n *\n * @param {String} containerId the id of the div.que that contains this question.\n * @param {boolean} readOnly whether the question is read-only.\n * @param {Object[]} visibleDropZones data on any drop zones to draw as part of the feedback.\n */\n init: function(containerId, readOnly, visibleDropZones) {\n questionManager.questions[containerId] =\n new DragDropMarkersQuestion(containerId, readOnly, visibleDropZones);\n if (!questionManager.eventHandlersInitialised) {\n questionManager.setupEventHandlers();\n questionManager.eventHandlersInitialised = true;\n }\n },\n\n /**\n * Set up the event handlers that make this question type work. (Done once per page.)\n */\n setupEventHandlers: function() {\n // We do not use the body event here to prevent the other event on Mobile device, such as scroll event.\n questionManager.addEventHandlersToMarker($('.que.ddmarker:not(.qtype_ddmarker-readonly) div.draghomes .marker'));\n questionManager.addEventHandlersToMarker($('.que.ddmarker:not(.qtype_ddmarker-readonly) div.droparea .marker'));\n $(window).on('resize', function() {\n questionManager.handleWindowResize(false);\n });\n window.addEventListener('beforeprint', function() {\n questionManager.isPrinting = true;\n questionManager.handleWindowResize(questionManager.isPrinting);\n });\n window.addEventListener('afterprint', function() {\n questionManager.isPrinting = false;\n questionManager.handleWindowResize(questionManager.isPrinting);\n });\n setTimeout(function() {\n questionManager.fixLayoutIfThingsMoved();\n }, 100);\n },\n\n /**\n * Binding the event again for newly created element.\n *\n * @param {jQuery} element Element to bind the event\n */\n addEventHandlersToMarker: function(element) {\n element\n .on('mousedown touchstart', questionManager.handleDragStart)\n .on('keydown keypress', questionManager.handleKeyPress)\n .focusin(function(e) {\n questionManager.handleKeyboardFocus(e, true);\n })\n .focusout(function(e) {\n questionManager.handleKeyboardFocus(e, false);\n });\n },\n\n /**\n * Handle mouse down / touch start events on markers.\n * @param {Event} e the DOM event.\n */\n handleDragStart: function(e) {\n e.preventDefault();\n var question = questionManager.getQuestionForEvent(e);\n if (question) {\n question.handleDragStart(e);\n }\n },\n\n /**\n * Handle key down / press events on markers.\n * @param {Event} e\n */\n handleKeyPress: function(e) {\n var question = questionManager.getQuestionForEvent(e);\n if (question) {\n question.handleKeyPress(e);\n }\n },\n\n /**\n * Handle when the window is resized.\n * @param {boolean} isPrinting\n */\n handleWindowResize: function(isPrinting) {\n for (var containerId in questionManager.questions) {\n if (questionManager.questions.hasOwnProperty(containerId)) {\n questionManager.questions[containerId].isPrinting = isPrinting;\n questionManager.questions[containerId].handleResize();\n }\n }\n },\n\n /**\n * Handle focus lost events on markers.\n * @param {Event} e\n * @param {boolean} isNavigating\n */\n handleKeyboardFocus: function(e, isNavigating) {\n questionManager.isKeyboardNavigation = isNavigating;\n },\n\n /**\n * Sometimes, despite our best efforts, things change in a way that cannot\n * be specifically caught (e.g. dock expanding or collapsing in Boost).\n * Therefore, we need to periodically check everything is in the right position.\n */\n fixLayoutIfThingsMoved: function() {\n if (!questionManager.isKeyboardNavigation) {\n this.handleWindowResize(questionManager.isPrinting);\n }\n // We use setTimeout after finishing work, rather than setInterval,\n // in case positioning things is slow. We want 100 ms gap\n // between executions, not what setInterval does.\n setTimeout(function() {\n questionManager.fixLayoutIfThingsMoved(questionManager.isPrinting);\n }, 100);\n },\n\n /**\n * Given an event, work out which question it effects.\n * @param {Event} e the event.\n * @returns {DragDropMarkersQuestion|undefined} The question, or undefined.\n */\n getQuestionForEvent: function(e) {\n var containerId = $(e.currentTarget).closest('.que.ddmarker').attr('id');\n return questionManager.questions[containerId];\n }\n };\n\n /**\n * @alias module:qtype_ddmarker/question\n */\n return {\n /**\n * Initialise one drag-drop markers question.\n *\n * @param {String} containerId id of the outer div for this question.\n * @param {String} bgImgUrl the URL of the background image.\n * @param {boolean} readOnly whether the question is being displayed read-only.\n * @param {String[]} visibleDropZones the geometry of any drop-zones to show.\n */\n init: questionManager.init\n };\n});\n"],"file":"question.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/question.js"],"names":["define","$","dragDrop","Shapes","keys","DragDropMarkersQuestion","containerId","readOnly","visibleDropZones","thisQ","shapes","shapeSVGs","isPrinting","getRoot","addClass","cloneDrags","repositionDrags","drawDropzones","prototype","length","bgImage","find","html","outerWidth","outerHeight","svg","nextColourIndex","dropZoneNo","colourClass","addDropzone","dropZone","shape","make","existingmarkertext","bgRatio","parse","coords","markertext","remove","append","markerspan","handles","getHandlePositions","positionLeft","moveHandle","x","positionTop","y","css","data","position","left","top","handleElementScale","shapeSVG","makeSvg","setAttribute","root","not","each","key","item","input","choiceNo","getChoiceNoFromElement","getCoords","drag","i","dragInDrop","clone","sendDragToDrop","getDragClone","cloneDragIfNeeded","inputNode","val","coordsStrings","split","convertToWindowXY","Point","point","offset","convertToBgImgXY","coordsInBgImg","bgPosition","width","height","document","getElementById","handleDragStart","e","dragged","target","closest","info","prepare","start","placed","hasClass","hiddenDrag","dragEnd","dragXY","bgImgXY","sendDragHome","removeDragIfNeeded","saveCoordsForChoice","items","thiQ","join","handleKeyPress","keyCode","arrowLeft","arrowRight","arrowDown","arrowUp","space","escape","preventDefault","constrainToBgImg","focus","windowxy","bgImg","Math","max","min","node","getClassnameNumericSuffix","prefix","classes","attr","classesarr","index","patt1","RegExp","test","match","exec","handleResize","parseFloat","originCoords","updateSvg","markerSpan","draghome","placeHolder","removeClass","getDragNoClass","before","getDragNo","includeSelector","className","isInfiniteDrag","dropArea","after","isScaling","getInput","noOfDrags","displayedDragsInDropArea","displayedDragsInDragHomes","dragClone","questionManager","addEventHandlersToMarker","dragsInHome","displayedDrags","first","bgImgNaturalWidth","get","naturalWidth","bgImgClientWidth","element","type","eventHandlersInitialised","isKeyboardNavigation","questions","init","setupEventHandlers","window","on","handleWindowResize","addEventListener","setTimeout","fixLayoutIfThingsMoved","focusin","handleKeyboardFocus","focusout","question","getQuestionForEvent","hasOwnProperty","isNavigating","currentTarget"],"mappings":"AAwBAA,OAAM,2BAAC,CAAC,QAAD,CAAW,eAAX,CAA4B,uBAA5B,CAAqD,gBAArD,CAAD,CAAyE,SAASC,CAAT,CAAYC,CAAZ,CAAsBC,CAAtB,CAA8BC,CAA9B,CAAoC,CAE/G,aAWA,QAASC,CAAAA,CAAT,CAAiCC,CAAjC,CAA8CC,CAA9C,CAAwDC,CAAxD,CAA0E,CACtE,GAAIC,CAAAA,CAAK,CAAG,IAAZ,CACA,KAAKH,WAAL,CAAmBA,CAAnB,CACA,KAAKE,gBAAL,CAAwBA,CAAxB,CACA,KAAKE,MAAL,CAAc,EAAd,CACA,KAAKC,SAAL,CAAiB,EAAjB,CACA,KAAKC,UAAL,IACA,GAAIL,CAAJ,CAAc,CACV,KAAKM,OAAL,GAAeC,QAAf,CAAwB,yBAAxB,CACH,CACDL,CAAK,CAACM,UAAN,GACAN,CAAK,CAACO,eAAN,GACAP,CAAK,CAACQ,aAAN,EACH,CAKDZ,CAAuB,CAACa,SAAxB,CAAkCD,aAAlC,CAAkD,UAAW,CACzD,GAAmC,CAA/B,MAAKT,gBAAL,CAAsBW,MAA1B,CAAsC,CAClC,GAAIC,CAAAA,CAAO,CAAG,KAAKA,OAAL,EAAd,CAEA,KAAKP,OAAL,GAAeQ,IAAf,CAAoB,eAApB,EAAqCC,IAArC,CAA0C,yEAC1BF,CAAO,CAACG,UAAR,EAD0B,gBAEzBH,CAAO,CAACI,WAAR,EAFyB,CAED,WAFzC,EAMA,OAHIC,CAAAA,CAAG,CAAG,KAAKZ,OAAL,GAAeQ,IAAf,CAAoB,eAApB,CAGV,CADIK,CAAe,CAAG,CACtB,CAASC,CAAU,CAAG,CAAtB,CACQC,CADR,CAAyBD,CAAU,CAAG,KAAKnB,gBAAL,CAAsBW,MAA5D,CAAoEQ,CAAU,EAA9E,CAAkF,CAC1EC,CAD0E,CAC5D,QAAUF,CADkD,CAE9EA,CAAe,CAAG,CAACA,CAAe,CAAG,CAAnB,EAAwB,CAA1C,CACA,KAAKG,WAAL,CAAiBJ,CAAjB,CAAsBE,CAAtB,CAAkCC,CAAlC,CACH,CACJ,CACJ,CAhBD,CAyBAvB,CAAuB,CAACa,SAAxB,CAAkCW,WAAlC,CAAgD,SAASJ,CAAT,CAAcE,CAAd,CAA0BC,CAA1B,CAAuC,CACnF,GAAIE,CAAAA,CAAQ,CAAG,KAAKtB,gBAAL,CAAsBmB,CAAtB,CAAf,CACII,CAAK,CAAG5B,CAAM,CAAC6B,IAAP,CAAYF,CAAQ,CAACC,KAArB,CAA4B,EAA5B,CADZ,CAEIE,CAFJ,CAGIC,CAAO,CAAG,KAAKA,OAAL,EAHd,CAIA,GAAI,CAACH,CAAK,CAACI,KAAN,CAAYL,CAAQ,CAACM,MAArB,CAA6BF,CAA7B,CAAL,CAA4C,CACxC,MACH,CAEDD,CAAkB,CAAG,KAAKpB,OAAL,GAAeQ,IAAf,CAAoB,kCAAoCM,CAAxD,CAArB,CACA,GAAIM,CAAkB,CAACd,MAAvB,CAA+B,CAC3B,GAA4B,EAAxB,GAAAW,CAAQ,CAACO,UAAb,CAAgC,CAC5BJ,CAAkB,CAACX,IAAnB,CAAwBQ,CAAQ,CAACO,UAAjC,CACH,CAFD,IAEO,CACHJ,CAAkB,CAACK,MAAnB,EACH,CACJ,CAND,IAMO,IAA4B,EAAxB,GAAAR,CAAQ,CAACO,UAAb,CAAgC,CAEnC,KAAKxB,OAAL,GAAeQ,IAAf,CAAoB,iBAApB,EAAuCkB,MAAvC,CAA8C,kBAD7B,wBAA0BZ,CACG,EAA+B,KAA/B,CAC1CG,CAAQ,CAACO,UADiC,CACpB,SAD1B,EAEA,GAAIG,CAAAA,CAAU,CAAG,KAAK3B,OAAL,GAAeQ,IAAf,CAAoB,6CAA+CM,CAAnE,CAAjB,CACA,GAAIa,CAAU,CAACrB,MAAf,CAAuB,IACfsB,CAAAA,CAAO,CAAGV,CAAK,CAACW,kBAAN,EADK,CAEfC,CAAY,CAAGF,CAAO,CAACG,UAAR,CAAmBC,CAAnB,CAAwBL,CAAU,CAACjB,UAAX,GAA0B,CAAlD,CAAuD,CAFvD,CAGfuB,CAAW,CAAGL,CAAO,CAACG,UAAR,CAAmBG,CAAnB,CAAwBP,CAAU,CAAChB,WAAX,GAA2B,CAHlD,CAInBgB,CAAU,CACLQ,GADL,CACS,MADT,CACiBL,CADjB,EAEKK,GAFL,CAES,KAFT,CAEgBF,CAFhB,EAGAN,CAAU,CACLS,IADL,CACU,SADV,CACqBT,CAAU,CAACU,QAAX,GAAsBC,IAAtB,CAA6BjB,CADlD,EAEKe,IAFL,CAEU,SAFV,CAEqBT,CAAU,CAACU,QAAX,GAAsBE,GAAtB,CAA4BlB,CAFjD,EAGA,KAAKmB,kBAAL,CAAwBb,CAAxB,CAAoC,QAApC,CACH,CACJ,CAED,GAAIc,CAAAA,CAAQ,CAAGvB,CAAK,CAACwB,OAAN,CAAc9B,CAAG,CAAC,CAAD,CAAjB,CAAf,CACA6B,CAAQ,CAACE,YAAT,CAAsB,OAAtB,CAA+B,YAAc5B,CAA7C,EAEA,KAAKlB,MAAL,CAAY,KAAKA,MAAL,CAAYS,MAAxB,EAAkCY,CAAlC,CACA,KAAKpB,SAAL,CAAe,KAAKA,SAAL,CAAeQ,MAA9B,EAAwCmC,CAC3C,CAxCD,CA+CAjD,CAAuB,CAACa,SAAxB,CAAkCF,eAAlC,CAAoD,UAAW,CAC3D,GAAIyC,CAAAA,CAAI,CAAG,KAAK5C,OAAL,EAAX,CACIJ,CAAK,CAAG,IADZ,CAGAgD,CAAI,CAACpC,IAAL,CAAU,uBAAV,EAAmCqC,GAAnC,CAAuC,kBAAvC,EAA2DC,IAA3D,CAAgE,SAASC,CAAT,CAAcC,CAAd,CAAoB,CAChF5D,CAAC,CAAC4D,CAAD,CAAD,CAAQ/C,QAAR,CAAiB,UAAjB,CACH,CAFD,EAIA2C,CAAI,CAACpC,IAAL,CAAU,eAAV,EAA2BsC,IAA3B,CAAgC,SAASC,CAAT,CAAcE,CAAd,CAAqB,CACjD,GAAIC,CAAAA,CAAQ,CAAGtD,CAAK,CAACuD,sBAAN,CAA6BF,CAA7B,CAAf,CACI1B,CAAM,CAAG3B,CAAK,CAACwD,SAAN,CAAgBH,CAAhB,CADb,CAEA,GAAI1B,CAAM,CAACjB,MAAX,CAAmB,CACf,GAAI+C,CAAAA,CAAI,CAAGzD,CAAK,CAACI,OAAN,GAAgBQ,IAAhB,CAAqB,gCAA4C0C,CAAjE,EAA2EL,GAA3E,CAA+E,kBAA/E,CAAX,CACAQ,CAAI,CAAC5B,MAAL,GACA,IAAK,GAAI6B,CAAAA,CAAC,CAAG,CAAR,CACGC,CADR,CAAgBD,CAAC,CAAG/B,CAAM,CAACjB,MAA3B,CAAmCgD,CAAC,EAApC,CAAwC,CAChCC,CADgC,CACnBF,CAAI,CAACG,KAAL,EADmB,CAEpCD,CAAU,CAACnB,IAAX,CAAgB,OAAhB,CAAyBb,CAAM,CAAC+B,CAAD,CAAN,CAAUtB,CAAnC,EAAsCI,IAAtC,CAA2C,OAA3C,CAAoDb,CAAM,CAAC+B,CAAD,CAAN,CAAUpB,CAA9D,EACAtC,CAAK,CAAC6D,cAAN,CAAqBF,CAArB,IACH,CACD3D,CAAK,CAAC8D,YAAN,CAAmBL,CAAnB,EAAyBpD,QAAzB,CAAkC,QAAlC,EACAL,CAAK,CAAC+D,iBAAN,CAAwBN,CAAxB,CACH,CACJ,CAdD,CAeH,CAvBD,CAkCA7D,CAAuB,CAACa,SAAxB,CAAkC+C,SAAlC,CAA8C,SAASQ,CAAT,CAAoB,CAC9D,GAAIrC,CAAAA,CAAM,CAAG,EAAb,CACIsC,CAAG,CAAGzE,CAAC,CAACwE,CAAD,CAAD,CAAaC,GAAb,EADV,CAEA,GAAY,EAAR,GAAAA,CAAJ,CAAgB,CAEZ,OADIC,CAAAA,CAAa,CAAGD,CAAG,CAACE,KAAJ,CAAU,GAAV,CACpB,CAAST,CAAC,CAAG,CAAb,CAAgBA,CAAC,CAAGQ,CAAa,CAACxD,MAAlC,CAA0CgD,CAAC,EAA3C,CAA+C,CAC3C/B,CAAM,CAAC+B,CAAD,CAAN,CAAY,KAAKU,iBAAL,CAAuB1E,CAAM,CAAC2E,KAAP,CAAa3C,KAAb,CAAmBwC,CAAa,CAACR,CAAD,CAAhC,CAAvB,CACf,CACJ,CACD,MAAO/B,CAAAA,CACV,CAVD,CAmBA/B,CAAuB,CAACa,SAAxB,CAAkC2D,iBAAlC,CAAsD,SAASE,CAAT,CAAgB,CAClE,GAAI3D,CAAAA,CAAO,CAAG,KAAKA,OAAL,EAAd,CAKA,MAAO2D,CAAAA,CAAK,CAACC,MAAN,CAAa5D,CAAO,CAAC4D,MAAR,GAAiB7B,IAAjB,CAAwB,CAArC,CAAwC/B,CAAO,CAAC4D,MAAR,GAAiB5B,GAAjB,CAAuB,CAA/D,CACV,CAPD,CAgBA/C,CAAuB,CAACa,SAAxB,CAAkC+D,gBAAlC,CAAqD,SAASF,CAAT,CAAgB,CACjE,GAAI3D,CAAAA,CAAO,CAAG,KAAKA,OAAL,EAAd,CACA,MAAO2D,CAAAA,CAAK,CAACC,MAAN,CAAa,CAAC5D,CAAO,CAAC4D,MAAR,GAAiB7B,IAAlB,CAAyB,CAAtC,CAAyC,CAAC/B,CAAO,CAAC4D,MAAR,GAAiB5B,GAAlB,CAAwB,CAAjE,CACV,CAHD,CAWA/C,CAAuB,CAACa,SAAxB,CAAkCgE,aAAlC,CAAkD,SAASH,CAAT,CAAgB,IAC1D3D,CAAAA,CAAO,CAAG,KAAKA,OAAL,EADgD,CAE1D+D,CAAU,CAAG/D,CAAO,CAAC4D,MAAR,EAF6C,CAI9D,MAAOD,CAAAA,CAAK,CAAClC,CAAN,EAAWsC,CAAU,CAAChC,IAAtB,EAA8B4B,CAAK,CAAClC,CAAN,CAAUsC,CAAU,CAAChC,IAAX,CAAkB/B,CAAO,CAACgE,KAAR,EAA1D,EACAL,CAAK,CAAChC,CAAN,EAAWoC,CAAU,CAAC/B,GADtB,EAC6B2B,CAAK,CAAChC,CAAN,CAAUoC,CAAU,CAAC/B,GAAX,CAAiBhC,CAAO,CAACiE,MAAR,EAClE,CAND,CAYAhF,CAAuB,CAACa,SAAxB,CAAkCL,OAAlC,CAA4C,UAAW,CACnD,MAAOZ,CAAAA,CAAC,CAACqF,QAAQ,CAACC,cAAT,CAAwB,KAAKjF,WAA7B,CAAD,CACX,CAFD,CAQAD,CAAuB,CAACa,SAAxB,CAAkCE,OAAlC,CAA4C,UAAW,CACnD,MAAO,MAAKP,OAAL,GAAeQ,IAAf,CAAoB,oBAApB,CACV,CAFD,CAIAhB,CAAuB,CAACa,SAAxB,CAAkCsE,eAAlC,CAAoD,SAASC,CAAT,CAAY,IACxDhF,CAAAA,CAAK,CAAG,IADgD,CAExDiF,CAAO,CAAGzF,CAAC,CAACwF,CAAC,CAACE,MAAH,CAAD,CAAYC,OAAZ,CAAoB,SAApB,CAF8C,CAIxDC,CAAI,CAAG3F,CAAQ,CAAC4F,OAAT,CAAiBL,CAAjB,CAJiD,CAK5D,GAAI,CAACI,CAAI,CAACE,KAAV,CAAiB,CACb,MACH,CAEDL,CAAO,CAAC5E,QAAR,CAAiB,cAAjB,EAAiCkC,GAAjC,CAAqC,WAArC,CAAkD,EAAlD,EAEA,GAAIgD,CAAAA,CAAM,CAAG,CAACN,CAAO,CAACO,QAAR,CAAiB,UAAjB,CAAd,CACA,GAAI,CAACD,CAAL,CAAa,CACT,GAAIE,CAAAA,CAAU,CAAGzF,CAAK,CAAC8D,YAAN,CAAmBmB,CAAnB,CAAjB,CACA,GAAIQ,CAAU,CAAC/E,MAAf,CAAuB,CACnB+E,CAAU,CAACpF,QAAX,CAAoB,QAApB,EACA4E,CAAO,CAACV,MAAR,CAAekB,CAAU,CAAClB,MAAX,EAAf,CACH,CACJ,CAED9E,CAAQ,CAAC6F,KAAT,CAAeN,CAAf,CAAkBC,CAAlB,CAA2B,UAAW,CAErC,CAFD,CAEG,SAAS7C,CAAT,CAAYE,CAAZ,CAAe2C,CAAf,CAAwB,CACvBjF,CAAK,CAAC0F,OAAN,CAAcT,CAAd,CACH,CAJD,CAKH,CAzBD,CA+BArF,CAAuB,CAACa,SAAxB,CAAkCiF,OAAlC,CAA4C,SAAST,CAAT,CAAkB,CAC1D,GAAIM,CAAAA,CAAM,GAAV,CACIjC,CAAQ,CAAG,KAAKC,sBAAL,CAA4B0B,CAA5B,CADf,CAEIxD,CAAO,CAAG,KAAKA,OAAL,EAFd,CAGIkE,CAHJ,CAKAV,CAAO,CAACzC,IAAR,CAAa,OAAb,CAAsByC,CAAO,CAACV,MAAR,GAAiB7B,IAAvC,EAA6CF,IAA7C,CAAkD,OAAlD,CAA2DyC,CAAO,CAACV,MAAR,GAAiB5B,GAA5E,EACAgD,CAAM,CAAG,GAAIjG,CAAAA,CAAM,CAAC2E,KAAX,CAAiBY,CAAO,CAACzC,IAAR,CAAa,OAAb,CAAjB,CAAwCyC,CAAO,CAACzC,IAAR,CAAa,OAAb,CAAxC,CAAT,CACA,GAAI,KAAKiC,aAAL,CAAmBkB,CAAnB,CAAJ,CAAgC,CAC5B,KAAK9B,cAAL,CAAoBoB,CAApB,KACAM,CAAM,GAAN,CAIA,GAAIK,CAAAA,CAAO,CAAG,KAAKpB,gBAAL,CAAsBmB,CAAtB,CAAd,CACAC,CAAO,CAAG,GAAIlG,CAAAA,CAAM,CAAC2E,KAAX,CAAiBuB,CAAO,CAACxD,CAAR,CAAYX,CAA7B,CAAsCmE,CAAO,CAACtD,CAAR,CAAYb,CAAlD,CAAV,CACAwD,CAAO,CAACzC,IAAR,CAAa,SAAb,CAAwBoD,CAAO,CAACxD,CAAhC,EAAmCI,IAAnC,CAAwC,SAAxC,CAAmDoD,CAAO,CAACtD,CAA3D,CACH,CAED,GAAI,CAACiD,CAAL,CAAa,CACT,KAAKM,YAAL,CAAkBZ,CAAlB,EACA,KAAKa,kBAAL,CAAwBb,CAAxB,CACH,CAHD,IAGO,CACH,KAAKlB,iBAAL,CAAuBkB,CAAvB,CACH,CAED,KAAKc,mBAAL,CAAyBzC,CAAzB,CACH,CA3BD,CAiCA1D,CAAuB,CAACa,SAAxB,CAAkCsF,mBAAlC,CAAwD,SAASzC,CAAT,CAAmB,CACvE,GAAI3B,CAAAA,CAAM,CAAG,EAAb,CACIqE,CAAK,CAAG,KAAK5F,OAAL,GAAeQ,IAAf,CAAoB,kCAAoC0C,CAAxD,CADZ,CAEI2C,CAAI,CAAG,IAFX,CAGIxE,CAAO,CAAG,KAAKA,OAAL,EAHd,CAKA,GAAIuE,CAAK,CAACtF,MAAV,CAAkB,CACdsF,CAAK,CAAC9C,IAAN,CAAW,UAAW,CAClB,GAAIO,CAAAA,CAAI,CAAGjE,CAAC,CAAC,IAAD,CAAZ,CACA,GAAI,CAACiE,CAAI,CAAC+B,QAAL,CAAc,cAAd,CAAL,CAAoC,CAChC,GAAIG,CAAAA,CAAM,CAAG,GAAIjG,CAAAA,CAAM,CAAC2E,KAAX,CAAiBZ,CAAI,CAACjB,IAAL,CAAU,OAAV,CAAjB,CAAqCiB,CAAI,CAACjB,IAAL,CAAU,OAAV,CAArC,CAAb,CACA,GAAIyD,CAAI,CAACxB,aAAL,CAAmBkB,CAAnB,CAAJ,CAAgC,CAC5B,GAAIC,CAAAA,CAAO,CAAGK,CAAI,CAACzB,gBAAL,CAAsBmB,CAAtB,CAAd,CACAC,CAAO,CAAG,GAAIlG,CAAAA,CAAM,CAAC2E,KAAX,CAAiBuB,CAAO,CAACxD,CAAR,CAAYX,CAA7B,CAAsCmE,CAAO,CAACtD,CAAR,CAAYb,CAAlD,CAAV,CACAE,CAAM,CAACA,CAAM,CAACjB,MAAR,CAAN,CAAwBkF,CAC3B,CACJ,CACJ,CAVD,CAWH,CAED,KAAKxF,OAAL,GAAeQ,IAAf,CAAoB,eAAiB0C,CAArC,EAA+CW,GAA/C,CAAmDtC,CAAM,CAACuE,IAAP,CAAY,GAAZ,CAAnD,CACH,CArBD,CA2BAtG,CAAuB,CAACa,SAAxB,CAAkC0F,cAAlC,CAAmD,SAASnB,CAAT,CAAY,CAC3D,GAAIvB,CAAAA,CAAI,CAAGjE,CAAC,CAACwF,CAAC,CAACE,MAAH,CAAD,CAAYC,OAAZ,CAAoB,SAApB,CAAX,CACIb,CAAK,CAAG,GAAI5E,CAAAA,CAAM,CAAC2E,KAAX,CAAiBZ,CAAI,CAACc,MAAL,GAAc7B,IAA/B,CAAqCe,CAAI,CAACc,MAAL,GAAc5B,GAAnD,CADZ,CAEIW,CAAQ,CAAG,KAAKC,sBAAL,CAA4BE,CAA5B,CAFf,CAIA,OAAQuB,CAAC,CAACoB,OAAV,EACI,IAAKzG,CAAAA,CAAI,CAAC0G,SAAV,CACA,IAAK,GAAL,CACI/B,CAAK,CAAClC,CAAN,EAAW,CAAX,CACA,MACJ,IAAKzC,CAAAA,CAAI,CAAC2G,UAAV,CACA,IAAK,GAAL,CACIhC,CAAK,CAAClC,CAAN,EAAW,CAAX,CACA,MACJ,IAAKzC,CAAAA,CAAI,CAAC4G,SAAV,CACA,IAAK,GAAL,CACIjC,CAAK,CAAChC,CAAN,EAAW,CAAX,CACA,MACJ,IAAK3C,CAAAA,CAAI,CAAC6G,OAAV,CACA,IAAK,GAAL,CACIlC,CAAK,CAAChC,CAAN,EAAW,CAAX,CACA,MACJ,IAAK3C,CAAAA,CAAI,CAAC8G,KAAV,CACA,IAAK9G,CAAAA,CAAI,CAAC+G,MAAV,CACIpC,CAAK,CAAG,IAAR,CACA,MACJ,QACI,OAtBR,CAwBAU,CAAC,CAAC2B,cAAF,GAEA,GAAc,IAAV,GAAArC,CAAJ,CAAoB,CAChBA,CAAK,CAAG,KAAKsC,gBAAL,CAAsBtC,CAAtB,CAAR,CACAb,CAAI,CAACc,MAAL,CAAY,CAAC,KAAQD,CAAK,CAAClC,CAAf,CAAkB,IAAOkC,CAAK,CAAChC,CAA/B,CAAZ,EACAmB,CAAI,CAACjB,IAAL,CAAU,OAAV,CAAmBiB,CAAI,CAACc,MAAL,GAAc7B,IAAjC,EAAuCF,IAAvC,CAA4C,OAA5C,CAAqDiB,CAAI,CAACc,MAAL,GAAc5B,GAAnE,EACA,GAAIgD,CAAAA,CAAM,CAAG,KAAKnB,gBAAL,CAAsB,GAAI9E,CAAAA,CAAM,CAAC2E,KAAX,CAAiBZ,CAAI,CAACjB,IAAL,CAAU,OAAV,CAAjB,CAAqCiB,CAAI,CAACjB,IAAL,CAAU,OAAV,CAArC,CAAtB,CAAb,CACAiB,CAAI,CAACjB,IAAL,CAAU,SAAV,CAAqBmD,CAAM,CAACvD,CAAP,CAAW,KAAKX,OAAL,EAAhC,EAAgDe,IAAhD,CAAqD,SAArD,CAAgEmD,CAAM,CAACrD,CAAP,CAAW,KAAKb,OAAL,EAA3E,EACA,GAAI,KAAKgD,aAAL,CAAmB,GAAI/E,CAAAA,CAAM,CAAC2E,KAAX,CAAiBZ,CAAI,CAACc,MAAL,GAAc7B,IAA/B,CAAqCe,CAAI,CAACc,MAAL,GAAc5B,GAAnD,CAAnB,CAAJ,CAAiF,CAC7E,GAAIc,CAAI,CAAC+B,QAAL,CAAc,UAAd,CAAJ,CAA+B,CAC3B,KAAK3B,cAAL,CAAoBJ,CAApB,KACA,GAAIgC,CAAAA,CAAU,CAAG,KAAK3B,YAAL,CAAkBL,CAAlB,CAAjB,CACA,GAAIgC,CAAU,CAAC/E,MAAf,CAAuB,CACnB+E,CAAU,CAACpF,QAAX,CAAoB,QAApB,CACH,CACD,KAAK0D,iBAAL,CAAuBN,CAAvB,CACH,CACJ,CACJ,CAhBD,IAgBO,CACHA,CAAI,CAAClB,GAAL,CAAS,MAAT,CAAiB,EAAjB,EAAqBA,GAArB,CAAyB,KAAzB,CAAgC,EAAhC,EACAkB,CAAI,CAACjB,IAAL,CAAU,OAAV,CAAmBiB,CAAI,CAACc,MAAL,GAAc7B,IAAjC,EAAuCF,IAAvC,CAA4C,OAA5C,CAAqDiB,CAAI,CAACc,MAAL,GAAc5B,GAAnE,EACA,KAAKkD,YAAL,CAAkBpC,CAAlB,EACA,KAAKqC,kBAAL,CAAwBrC,CAAxB,CACH,CACDA,CAAI,CAACoD,KAAL,GACA,KAAKd,mBAAL,CAAyBzC,CAAzB,CACH,CAvDD,CA+DA1D,CAAuB,CAACa,SAAxB,CAAkCmG,gBAAlC,CAAqD,SAASE,CAAT,CAAmB,CACpE,GAAIC,CAAAA,CAAK,CAAG,KAAKpG,OAAL,EAAZ,CACIiF,CAAO,CAAG,KAAKpB,gBAAL,CAAsBsC,CAAtB,CADd,CAEAlB,CAAO,CAACxD,CAAR,CAAY4E,IAAI,CAACC,GAAL,CAAS,CAAT,CAAYrB,CAAO,CAACxD,CAApB,CAAZ,CACAwD,CAAO,CAACtD,CAAR,CAAY0E,IAAI,CAACC,GAAL,CAAS,CAAT,CAAYrB,CAAO,CAACtD,CAApB,CAAZ,CACAsD,CAAO,CAACxD,CAAR,CAAY4E,IAAI,CAACE,GAAL,CAASH,CAAK,CAACpC,KAAN,EAAT,CAAwBiB,CAAO,CAACxD,CAAhC,CAAZ,CACAwD,CAAO,CAACtD,CAAR,CAAY0E,IAAI,CAACE,GAAL,CAASH,CAAK,CAACnC,MAAN,EAAT,CAAyBgB,CAAO,CAACtD,CAAjC,CAAZ,CACA,MAAO,MAAK8B,iBAAL,CAAuBwB,CAAvB,CACV,CARD,CAgBAhG,CAAuB,CAACa,SAAxB,CAAkC8C,sBAAlC,CAA2D,SAAS4D,CAAT,CAAe,CACtE,OAAc,KAAKC,yBAAL,CAA+BD,CAA/B,CAAqC,QAArC,CACjB,CAFD,CAWAvH,CAAuB,CAACa,SAAxB,CAAkC2G,yBAAlC,CAA8D,SAASD,CAAT,CAAeE,CAAf,CAAuB,CACjF,GAAIC,CAAAA,CAAO,CAAG9H,CAAC,CAAC2H,CAAD,CAAD,CAAQI,IAAR,CAAa,OAAb,CAAd,CACA,GAAID,CAAO,SAAP,EAAqC,EAAZ,GAAAA,CAA7B,CAA6C,CAEzC,OADIE,CAAAA,CAAU,CAAGF,CAAO,CAACnD,KAAR,CAAc,GAAd,CACjB,CAASsD,CAAK,CAAG,CAAjB,CACQC,CADR,CAAoBD,CAAK,CAAGD,CAAU,CAAC9G,MAAvC,CAA+C+G,CAAK,EAApD,CAAwD,CAChDC,CADgD,CACxC,GAAIC,CAAAA,MAAJ,CAAW,IAAMN,CAAN,CAAe,WAA1B,CADwC,CAEpD,GAAIK,CAAK,CAACE,IAAN,CAAWJ,CAAU,CAACC,CAAD,CAArB,CAAJ,CAAmC,IAE3BI,CAAAA,CAAK,CAAG,YAAMC,IAAN,CAAWN,CAAU,CAACC,CAAD,CAArB,CAFmB,CAG/B,OAAcI,CAAK,CAAC,CAAD,CACtB,CACJ,CACJ,CACD,MAAO,KACV,CAdD,CAmBAjI,CAAuB,CAACa,SAAxB,CAAkCsH,YAAlC,CAAiD,UAAW,CACxD,GAAI/H,CAAAA,CAAK,CAAG,IAAZ,CACIyB,CAAO,CAAG,KAAKA,OAAL,EADd,CAEA,GAAI,KAAKtB,UAAT,CAAqB,CACjBsB,CAAO,CAAG,CACb,CAED,KAAKrB,OAAL,GAAeQ,IAAf,CAAoB,sBAApB,EAA4CqC,GAA5C,CAAgD,eAAhD,EAAiEC,IAAjE,CAAsE,SAASC,CAAT,CAAcM,CAAd,CAAoB,CACtFjE,CAAC,CAACiE,CAAD,CAAD,CACKlB,GADL,CACS,MADT,CACiByF,UAAU,CAACxI,CAAC,CAACiE,CAAD,CAAD,CAAQjB,IAAR,CAAa,SAAb,CAAD,CAAV,CAAsCwF,UAAU,CAACvG,CAAD,CADjE,EAEKc,GAFL,CAES,KAFT,CAEgByF,UAAU,CAACxI,CAAC,CAACiE,CAAD,CAAD,CAAQjB,IAAR,CAAa,SAAb,CAAD,CAAV,CAAsCwF,UAAU,CAACvG,CAAD,CAFhE,EAGAzB,CAAK,CAAC4C,kBAAN,CAAyBa,CAAzB,CAA+B,UAA/B,CACH,CALD,EAOA,KAAKrD,OAAL,GAAeQ,IAAf,CAAoB,4BAApB,EACK+D,KADL,CACW,KAAKhE,OAAL,GAAegE,KAAf,EADX,EAEKC,MAFL,CAEY,KAAKjE,OAAL,GAAeiE,MAAf,EAFZ,EAIA,IAAK,GAAI1D,CAAAA,CAAU,CAAG,CAAtB,CAAyBA,CAAU,CAAG,KAAKnB,gBAAL,CAAsBW,MAA5D,CAAoEQ,CAAU,EAA9E,CAAkF,IAC1EG,CAAAA,CAAQ,CAAGrB,CAAK,CAACD,gBAAN,CAAuBmB,CAAvB,CAD+D,CAE1E+G,CAAY,CAAG5G,CAAQ,CAACM,MAFkD,CAG1EL,CAAK,CAAGtB,CAAK,CAACC,MAAN,CAAaiB,CAAb,CAHkE,CAI1E2B,CAAQ,CAAG7C,CAAK,CAACE,SAAN,CAAgBgB,CAAhB,CAJ+D,CAK9EI,CAAK,CAACI,KAAN,CAAYuG,CAAZ,CAA0BxG,CAA1B,EACAH,CAAK,CAAC4G,SAAN,CAAgBrF,CAAhB,EAN8E,GAQ1Eb,CAAAA,CAAO,CAAGV,CAAK,CAACW,kBAAN,EARgE,CAS1EkG,CAAU,CAAG,KAAK/H,OAAL,GAAeQ,IAAf,CAAoB,6CAA+CM,CAAnE,CAT6D,CAU9EiH,CAAU,CACL5F,GADL,CACS,MADT,CACiBP,CAAO,CAACG,UAAR,CAAmBC,CAAnB,CAAwB+F,CAAU,CAACrH,UAAX,GAA0B,CAAlD,CAAuD,CADxE,EAEKyB,GAFL,CAES,KAFT,CAEgBP,CAAO,CAACG,UAAR,CAAmBG,CAAnB,CAAwB6F,CAAU,CAACpH,WAAX,GAA2B,CAFnE,EAGAf,CAAK,CAAC4C,kBAAN,CAAyBuF,CAAzB,CAAqC,QAArC,CACH,CACJ,CAjCD,CAsCAvI,CAAuB,CAACa,SAAxB,CAAkCH,UAAlC,CAA+C,UAAW,CACtD,GAAIN,CAAAA,CAAK,CAAG,IAAZ,CACA,KAAKI,OAAL,GAAeQ,IAAf,CAAoB,2BAApB,EAAiDsC,IAAjD,CAAsD,SAASuE,CAAT,CAAgBW,CAAhB,CAA0B,IACxE3E,CAAAA,CAAI,CAAGjE,CAAC,CAAC4I,CAAD,CADgE,CAExEC,CAAW,CAAG5E,CAAI,CAACG,KAAL,EAF0D,CAG5EyE,CAAW,CAACC,WAAZ,GACAD,CAAW,CAAChI,QAAZ,CAAqB,QAArB,EACAgI,CAAW,CAAChI,QAAZ,CAAqB,SAAWL,CAAK,CAACuD,sBAAN,CAA6BE,CAA7B,CAAhC,EACA4E,CAAW,CAAChI,QAAZ,CAAqBL,CAAK,CAACuI,cAAN,CAAqB9E,CAArB,IAArB,EACA4E,CAAW,CAAChI,QAAZ,CAAqB,iBAArB,EACAoD,CAAI,CAAC+E,MAAL,CAAYH,CAAZ,CACH,CATD,CAUH,CAZD,CAoBAzI,CAAuB,CAACa,SAAxB,CAAkCgI,SAAlC,CAA8C,SAAShF,CAAT,CAAe,CACzD,MAAO,MAAK2D,yBAAL,CAA+B3D,CAA/B,CAAqC,QAArC,CACV,CAFD,CAWA7D,CAAuB,CAACa,SAAxB,CAAkC8H,cAAlC,CAAmD,SAAS9E,CAAT,CAAeiF,CAAf,CAAgC,CAC/E,GAAIC,CAAAA,CAAS,CAAG,SAAW,KAAKF,SAAL,CAAehF,CAAf,CAA3B,CACA,GAAI,KAAKmF,cAAL,CAAoBnF,CAApB,CAAJ,CAA+B,CAC3BkF,CAAS,CAAG,UACf,CAED,GAAID,CAAJ,CAAqB,CACjB,MAAO,IAAMC,CAChB,CAED,MAAOA,CAAAA,CACV,CAXD,CAmBA/I,CAAuB,CAACa,SAAxB,CAAkCqD,YAAlC,CAAiD,SAASL,CAAT,CAAe,CAC5D,MAAO,MAAKrD,OAAL,GAAeQ,IAAf,CAAoB,gCACX,KAAK2C,sBAAL,CAA4BE,CAA5B,CADW,CACyB,KAAK8E,cAAL,CAAoB9E,CAApB,IADzB,CAC2D,kBAD/E,CAEV,CAHD,CASA7D,CAAuB,CAACa,SAAxB,CAAkCoI,QAAlC,CAA6C,UAAW,CACpD,MAAO,MAAKzI,OAAL,GAAeQ,IAAf,CAAoB,cAApB,CACV,CAFD,CASAhB,CAAuB,CAACa,SAAxB,CAAkCoF,YAAlC,CAAiD,SAASpC,CAAT,CAAe,CAC5DA,CAAI,CAAC6E,WAAL,CAAiB,cAAjB,EACKjI,QADL,CACc,UADd,EAEKkC,GAFL,CAES,KAFT,CAEgB,EAFhB,EAGKA,GAHL,CAGS,MAHT,CAGiB,EAHjB,EAIKA,GAJL,CAIS,WAJT,CAIsB,EAJtB,EAKA,GAAI8F,CAAAA,CAAW,CAAG,KAAKvE,YAAL,CAAkBL,CAAlB,CAAlB,CACA4E,CAAW,CAACS,KAAZ,CAAkBrF,CAAlB,EACA4E,CAAW,CAACC,WAAZ,CAAwB,QAAxB,CACH,CATD,CAiBA1I,CAAuB,CAACa,SAAxB,CAAkCoD,cAAlC,CAAmD,SAASJ,CAAT,CAAesF,CAAf,CAA0B,CACzE,GAAIF,CAAAA,CAAQ,CAAG,KAAKA,QAAL,EAAf,CACIpH,CAAO,CAAG,KAAKA,OAAL,EADd,CAEAgC,CAAI,CAAC6E,WAAL,CAAiB,cAAjB,EAAiCA,WAAjC,CAA6C,UAA7C,EACA,GAAI3C,CAAAA,CAAM,CAAG,KAAKnB,gBAAL,CAAsB,GAAI9E,CAAAA,CAAM,CAAC2E,KAAX,CAAiBZ,CAAI,CAACjB,IAAL,CAAU,OAAV,CAAjB,CAAqCiB,CAAI,CAACjB,IAAL,CAAU,OAAV,CAArC,CAAtB,CAAb,CACA,GAAIuG,CAAJ,CAAe,CACXtF,CAAI,CAACjB,IAAL,CAAU,SAAV,CAAqBmD,CAAM,CAACvD,CAAP,CAAWX,CAAhC,EAAyCe,IAAzC,CAA8C,SAA9C,CAAyDmD,CAAM,CAACrD,CAAP,CAAWb,CAApE,EACAgC,CAAI,CAAClB,GAAL,CAAS,MAAT,CAAiBoD,CAAM,CAACvD,CAAxB,EAA2BG,GAA3B,CAA+B,KAA/B,CAAsCoD,CAAM,CAACrD,CAA7C,CACH,CAHD,IAGO,CACHmB,CAAI,CAACjB,IAAL,CAAU,SAAV,CAAqBmD,CAAM,CAACvD,CAA5B,EAA+BI,IAA/B,CAAoC,SAApC,CAA+CmD,CAAM,CAACrD,CAAtD,EACAmB,CAAI,CAAClB,GAAL,CAAS,MAAT,CAAiBoD,CAAM,CAACvD,CAAP,CAAWX,CAA5B,EAAqCc,GAArC,CAAyC,KAAzC,CAAgDoD,CAAM,CAACrD,CAAP,CAAWb,CAA3D,CACH,CACDoH,CAAQ,CAAC/G,MAAT,CAAgB2B,CAAhB,EACA,KAAKb,kBAAL,CAAwBa,CAAxB,CAA8B,UAA9B,CACH,CAdD,CAqBA7D,CAAuB,CAACa,SAAxB,CAAkCsD,iBAAlC,CAAsD,SAASN,CAAT,CAAe,CACjE,GAAIO,CAAAA,CAAS,CAAG,KAAKgF,QAAL,CAAcvF,CAAd,CAAhB,CACIwF,CAAS,EAAU,KAAK7B,yBAAL,CAA+BpD,CAA/B,CAA0C,WAA1C,CADvB,CAEIkF,CAAwB,CAAG,KAAK9I,OAAL,GAAeQ,IAAf,CAAoB,8BAC3C,KAAK2C,sBAAL,CAA4BE,CAA5B,CAD2C,CACP,KAAK8E,cAAL,CAAoB9E,CAApB,IADb,EAC8C/C,MAH7E,CAIIyI,CAAyB,CAAG,KAAK/I,OAAL,GAAeQ,IAAf,CAAoB,+BAC5C,KAAK2C,sBAAL,CAA4BE,CAA5B,CAD4C,CACR,KAAK8E,cAAL,CAAoB9E,CAApB,IADZ,EAC6CR,GAD7C,CACiD,kBADjD,EACqEvC,MALrG,CAOA,GAAI,CAAC,KAAKkI,cAAL,CAAoBnF,CAApB,GACG,CAAC,KAAKmF,cAAL,CAAoBnF,CAApB,CAAD,EAA8ByF,CAAwB,CAAGD,CAD7D,GACyG,CAA9B,GAAAE,CAD/E,CACgH,CAC5G,GAAIC,CAAAA,CAAS,CAAG3F,CAAI,CAACG,KAAL,EAAhB,CACAwF,CAAS,CAAC/I,QAAV,CAAmB,UAAnB,EACKkC,GADL,CACS,KADT,CACgB,EADhB,EAEKA,GAFL,CAES,MAFT,CAEiB,EAFjB,EAGKA,GAHL,CAGS,WAHT,CAGsB,EAHtB,EAIA,KAAKuB,YAAL,CAAkBL,CAAlB,EACK6E,WADL,CACiB,QADjB,EAEKQ,KAFL,CAEWM,CAFX,EAGAC,CAAe,CAACC,wBAAhB,CAAyCF,CAAzC,CACH,CACJ,CApBD,CA2BAxJ,CAAuB,CAACa,SAAxB,CAAkCqF,kBAAlC,CAAuD,SAASrC,CAAT,CAAe,IAC9D8F,CAAAA,CAAW,CAAG,KAAKnJ,OAAL,GAAeQ,IAAf,CAAoB,+BAClC,KAAK2C,sBAAL,CAA4BE,CAA5B,CADkC,CACE,KAAK8E,cAAL,CAAoB9E,CAApB,IADtB,EACuDR,GADvD,CAC2D,kBAD3D,CADgD,CAG9DuG,CAAc,CAAGD,CAAW,CAAC7I,MAHiC,CAIlE,MAAwB,CAAjB,CAAA8I,CAAP,CAA2B,CACvBD,CAAW,CAACE,KAAZ,GAAoB5H,MAApB,GACA2H,CAAc,EACjB,CACJ,CARD,CAgBA5J,CAAuB,CAACa,SAAxB,CAAkCuI,QAAlC,CAA6C,SAASvF,CAAT,CAAe,CACxD,GAAIH,CAAAA,CAAQ,CAAG,KAAKC,sBAAL,CAA4BE,CAA5B,CAAf,CACA,MAAO,MAAKrD,OAAL,GAAeQ,IAAf,CAAoB,uBAAyB0C,CAA7C,CACV,CAHD,CAUA1D,CAAuB,CAACa,SAAxB,CAAkCgB,OAAlC,CAA4C,UAAW,IAC/CsF,CAAAA,CAAK,CAAG,KAAKpG,OAAL,EADuC,CAE/C+I,CAAiB,CAAG3C,CAAK,CAAC4C,GAAN,CAAU,CAAV,EAAaC,YAFc,CAG/CC,CAAgB,CAAG9C,CAAK,CAACpC,KAAN,EAH4B,CAKnD,MAAOkF,CAAAA,CAAgB,CAAGH,CAC7B,CAND,CAcA9J,CAAuB,CAACa,SAAxB,CAAkCmC,kBAAlC,CAAuD,SAASkH,CAAT,CAAkBC,CAAlB,CAAwB,CAC3E,GAAItI,CAAAA,CAAO,CAAGuG,UAAU,CAAC,KAAKvG,OAAL,EAAD,CAAxB,CACA,GAAI,KAAKtB,UAAT,CAAqB,CACjBsB,CAAO,CAAG,CACb,CACDjC,CAAC,CAACsK,CAAD,CAAD,CAAWvH,GAAX,CAAe,CACX,oBAAqB,SAAWd,CAAX,CAAqB,GAD/B,CAEX,iBAAkB,SAAWA,CAAX,CAAqB,GAF5B,CAGX,gBAAiB,SAAWA,CAAX,CAAqB,GAH3B,CAIX,eAAgB,SAAWA,CAAX,CAAqB,GAJ1B,CAKX,UAAa,SAAWA,CAAX,CAAqB,GALvB,CAMX,mBAAoBsI,CANT,CAAf,CAQH,CAbD,CAoBAnK,CAAuB,CAACa,SAAxB,CAAkCmI,cAAlC,CAAmD,SAASnF,CAAT,CAAe,CAC9D,MAAOA,CAAAA,CAAI,CAAC+B,QAAL,CAAc,UAAd,CACV,CAFD,CAUA,GAAI6D,CAAAA,CAAe,CAAG,CAKlBW,wBAAwB,GALN,CAUlB7J,UAAU,GAVQ,CAelB8J,oBAAoB,GAfF,CAoBlBC,SAAS,CAAE,EApBO,CA6BlBC,IAAI,CAAE,cAAStK,CAAT,CAAsBC,CAAtB,CAAgCC,CAAhC,CAAkD,CACpDsJ,CAAe,CAACa,SAAhB,CAA0BrK,CAA1B,EACI,GAAID,CAAAA,CAAJ,CAA4BC,CAA5B,CAAyCC,CAAzC,CAAmDC,CAAnD,CADJ,CAEA,GAAI,CAACsJ,CAAe,CAACW,wBAArB,CAA+C,CAC3CX,CAAe,CAACe,kBAAhB,GACAf,CAAe,CAACW,wBAAhB,GACH,CACJ,CApCiB,CAyClBI,kBAAkB,CAAE,6BAAW,CAE3Bf,CAAe,CAACC,wBAAhB,CAAyC9J,CAAC,CAAC,mEAAD,CAA1C,EACA6J,CAAe,CAACC,wBAAhB,CAAyC9J,CAAC,CAAC,kEAAD,CAA1C,EACAA,CAAC,CAAC6K,MAAD,CAAD,CAAUC,EAAV,CAAa,QAAb,CAAuB,UAAW,CAC9BjB,CAAe,CAACkB,kBAAhB,IACH,CAFD,EAGAF,MAAM,CAACG,gBAAP,CAAwB,aAAxB,CAAuC,UAAW,CAC9CnB,CAAe,CAAClJ,UAAhB,IACAkJ,CAAe,CAACkB,kBAAhB,CAAmClB,CAAe,CAAClJ,UAAnD,CACH,CAHD,EAIAkK,MAAM,CAACG,gBAAP,CAAwB,YAAxB,CAAsC,UAAW,CAC7CnB,CAAe,CAAClJ,UAAhB,IACAkJ,CAAe,CAACkB,kBAAhB,CAAmClB,CAAe,CAAClJ,UAAnD,CACH,CAHD,EAIAsK,UAAU,CAAC,UAAW,CAClBpB,CAAe,CAACqB,sBAAhB,EACH,CAFS,CAEP,GAFO,CAGb,CA3DiB,CAkElBpB,wBAAwB,CAAE,kCAASQ,CAAT,CAAkB,CACxCA,CAAO,CACFQ,EADL,CACQ,sBADR,CACgCjB,CAAe,CAACtE,eADhD,EAEKuF,EAFL,CAEQ,kBAFR,CAE4BjB,CAAe,CAAClD,cAF5C,EAGKwE,OAHL,CAGa,SAAS3F,CAAT,CAAY,CACjBqE,CAAe,CAACuB,mBAAhB,CAAoC5F,CAApC,IACH,CALL,EAMK6F,QANL,CAMc,SAAS7F,CAAT,CAAY,CAClBqE,CAAe,CAACuB,mBAAhB,CAAoC5F,CAApC,IACH,CARL,CASH,CA5EiB,CAkFlBD,eAAe,CAAE,yBAASC,CAAT,CAAY,CACzBA,CAAC,CAAC2B,cAAF,GACA,GAAImE,CAAAA,CAAQ,CAAGzB,CAAe,CAAC0B,mBAAhB,CAAoC/F,CAApC,CAAf,CACA,GAAI8F,CAAJ,CAAc,CACVA,CAAQ,CAAC/F,eAAT,CAAyBC,CAAzB,CACH,CACJ,CAxFiB,CA8FlBmB,cAAc,CAAE,wBAASnB,CAAT,CAAY,CACxB,GAAI8F,CAAAA,CAAQ,CAAGzB,CAAe,CAAC0B,mBAAhB,CAAoC/F,CAApC,CAAf,CACA,GAAI8F,CAAJ,CAAc,CACVA,CAAQ,CAAC3E,cAAT,CAAwBnB,CAAxB,CACH,CACJ,CAnGiB,CAyGlBuF,kBAAkB,CAAE,4BAASpK,CAAT,CAAqB,CACrC,IAAK,GAAIN,CAAAA,CAAT,GAAwBwJ,CAAAA,CAAe,CAACa,SAAxC,CAAmD,CAC/C,GAAIb,CAAe,CAACa,SAAhB,CAA0Bc,cAA1B,CAAyCnL,CAAzC,CAAJ,CAA2D,CACvDwJ,CAAe,CAACa,SAAhB,CAA0BrK,CAA1B,EAAuCM,UAAvC,CAAoDA,CAApD,CACAkJ,CAAe,CAACa,SAAhB,CAA0BrK,CAA1B,EAAuCkI,YAAvC,EACH,CACJ,CACJ,CAhHiB,CAuHlB6C,mBAAmB,CAAE,6BAAS5F,CAAT,CAAYiG,CAAZ,CAA0B,CAC3C5B,CAAe,CAACY,oBAAhB,CAAuCgB,CAC1C,CAzHiB,CAgIlBP,sBAAsB,CAAE,iCAAW,CAC/B,GAAI,CAACrB,CAAe,CAACY,oBAArB,CAA2C,CACvC,KAAKM,kBAAL,CAAwBlB,CAAe,CAAClJ,UAAxC,CACH,CAIDsK,UAAU,CAAC,UAAW,CAClBpB,CAAe,CAACqB,sBAAhB,CAAuCrB,CAAe,CAAClJ,UAAvD,CACH,CAFS,CAEP,GAFO,CAGb,CA1IiB,CAiJlB4K,mBAAmB,CAAE,6BAAS/F,CAAT,CAAY,CAC7B,GAAInF,CAAAA,CAAW,CAAGL,CAAC,CAACwF,CAAC,CAACkG,aAAH,CAAD,CAAmB/F,OAAnB,CAA2B,eAA3B,EAA4CoC,IAA5C,CAAiD,IAAjD,CAAlB,CACA,MAAO8B,CAAAA,CAAe,CAACa,SAAhB,CAA0BrK,CAA1B,CACV,CApJiB,CAAtB,CA0JA,MAAO,CASHsK,IAAI,CAAEd,CAAe,CAACc,IATnB,CAWV,CA7yBK,CAAN","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Question class for drag and drop marker question type, used to support the question and preview pages.\n *\n * @package qtype_ddmarker\n * @subpackage question\n * @copyright 2018 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], function($, dragDrop, Shapes, keys) {\n\n \"use strict\";\n\n /**\n * Object to handle one drag-drop markers question.\n *\n * @param {String} containerId id of the outer div for this question.\n * @param {boolean} readOnly whether the question is being displayed read-only.\n * @param {Object[]} visibleDropZones the geometry of any drop-zones to show.\n * Objects have fields shape, coords and markertext.\n * @constructor\n */\n function DragDropMarkersQuestion(containerId, readOnly, visibleDropZones) {\n var thisQ = this;\n this.containerId = containerId;\n this.visibleDropZones = visibleDropZones;\n this.shapes = [];\n this.shapeSVGs = [];\n this.isPrinting = false;\n if (readOnly) {\n this.getRoot().addClass('qtype_ddmarker-readonly');\n }\n thisQ.cloneDrags();\n thisQ.repositionDrags();\n thisQ.drawDropzones();\n }\n\n /**\n * Draws the svg shapes of any drop zones that should be visible for feedback purposes.\n */\n DragDropMarkersQuestion.prototype.drawDropzones = function() {\n if (this.visibleDropZones.length > 0) {\n var bgImage = this.bgImage();\n\n this.getRoot().find('div.dropzones').html('');\n var svg = this.getRoot().find('svg.dropzones');\n\n var nextColourIndex = 0;\n for (var dropZoneNo = 0; dropZoneNo < this.visibleDropZones.length; dropZoneNo++) {\n var colourClass = 'color' + nextColourIndex;\n nextColourIndex = (nextColourIndex + 1) % 8;\n this.addDropzone(svg, dropZoneNo, colourClass);\n }\n }\n };\n\n /**\n * Adds a dropzone shape with colour, coords and link provided to the array of shapes.\n *\n * @param {jQuery} svg the SVG image to which to add this drop zone.\n * @param {int} dropZoneNo which drop-zone to add.\n * @param {string} colourClass class name\n */\n DragDropMarkersQuestion.prototype.addDropzone = function(svg, dropZoneNo, colourClass) {\n var dropZone = this.visibleDropZones[dropZoneNo],\n shape = Shapes.make(dropZone.shape, ''),\n existingmarkertext,\n bgRatio = this.bgRatio();\n if (!shape.parse(dropZone.coords, bgRatio)) {\n return;\n }\n\n existingmarkertext = this.getRoot().find('div.markertexts span.markertext' + dropZoneNo);\n if (existingmarkertext.length) {\n if (dropZone.markertext !== '') {\n existingmarkertext.html(dropZone.markertext);\n } else {\n existingmarkertext.remove();\n }\n } else if (dropZone.markertext !== '') {\n var classnames = 'markertext markertext' + dropZoneNo;\n this.getRoot().find('div.markertexts').append('' +\n dropZone.markertext + '');\n var markerspan = this.getRoot().find('div.ddarea div.markertexts span.markertext' + dropZoneNo);\n if (markerspan.length) {\n var handles = shape.getHandlePositions();\n var positionLeft = handles.moveHandle.x - (markerspan.outerWidth() / 2) - 4;\n var positionTop = handles.moveHandle.y - (markerspan.outerHeight() / 2);\n markerspan\n .css('left', positionLeft)\n .css('top', positionTop);\n markerspan\n .data('originX', markerspan.position().left / bgRatio)\n .data('originY', markerspan.position().top / bgRatio);\n this.handleElementScale(markerspan, 'center');\n }\n }\n\n var shapeSVG = shape.makeSvg(svg[0]);\n shapeSVG.setAttribute('class', 'dropzone ' + colourClass);\n\n this.shapes[this.shapes.length] = shape;\n this.shapeSVGs[this.shapeSVGs.length] = shapeSVG;\n };\n\n /**\n * Draws the drag items on the page (and drop zones if required).\n * The idea is to re-draw all the drags and drops whenever there is a change\n * like a widow resize or an item dropped in place.\n */\n DragDropMarkersQuestion.prototype.repositionDrags = function() {\n var root = this.getRoot(),\n thisQ = this;\n\n root.find('div.draghomes .marker').not('.dragplaceholder').each(function(key, item) {\n $(item).addClass('unneeded');\n });\n\n root.find('input.choices').each(function(key, input) {\n var choiceNo = thisQ.getChoiceNoFromElement(input),\n coords = thisQ.getCoords(input);\n if (coords.length) {\n var drag = thisQ.getRoot().find('.draghomes' + ' span.marker' + '.choice' + choiceNo).not('.dragplaceholder');\n drag.remove();\n for (var i = 0; i < coords.length; i++) {\n var dragInDrop = drag.clone();\n dragInDrop.data('pagex', coords[i].x).data('pagey', coords[i].y);\n thisQ.sendDragToDrop(dragInDrop, false);\n }\n thisQ.getDragClone(drag).addClass('active');\n thisQ.cloneDragIfNeeded(drag);\n }\n });\n };\n\n /**\n * Determine what drag items need to be shown and\n * return coords of all drag items except any that are currently being dragged\n * based on contents of hidden inputs and whether drags are 'infinite' or how many\n * drags should be shown.\n *\n * @param {jQuery} inputNode\n * @returns {Point[]} coordinates of however many copies of the drag item should be shown.\n */\n DragDropMarkersQuestion.prototype.getCoords = function(inputNode) {\n var coords = [],\n val = $(inputNode).val();\n if (val !== '') {\n var coordsStrings = val.split(';');\n for (var i = 0; i < coordsStrings.length; i++) {\n coords[i] = this.convertToWindowXY(Shapes.Point.parse(coordsStrings[i]));\n }\n }\n return coords;\n };\n\n /**\n * Converts the relative x and y position coordinates into\n * absolute x and y position coordinates.\n *\n * @param {Point} point relative to the background image.\n * @returns {Point} point relative to the page.\n */\n DragDropMarkersQuestion.prototype.convertToWindowXY = function(point) {\n var bgImage = this.bgImage();\n // The +1 seems rather odd, but seems to give the best results in\n // the three main browsers at a range of zoom levels.\n // (Its due to the 1px border around the image, that shifts the\n // image pixels by 1 down and to the left.)\n return point.offset(bgImage.offset().left + 1, bgImage.offset().top + 1);\n };\n\n /**\n * Utility function converting window coordinates to relative to the\n * background image coordinates.\n *\n * @param {Point} point relative to the page.\n * @returns {Point} point relative to the background image.\n */\n DragDropMarkersQuestion.prototype.convertToBgImgXY = function(point) {\n var bgImage = this.bgImage();\n return point.offset(-bgImage.offset().left - 1, -bgImage.offset().top - 1);\n };\n\n /**\n * Is the point within the background image?\n *\n * @param {Point} point relative to the BG image.\n * @return {boolean} true it they are.\n */\n DragDropMarkersQuestion.prototype.coordsInBgImg = function(point) {\n var bgImage = this.bgImage();\n var bgPosition = bgImage.offset();\n\n return point.x >= bgPosition.left && point.x < bgPosition.left + bgImage.width()\n && point.y >= bgPosition.top && point.y < bgPosition.top + bgImage.height();\n };\n\n /**\n * Get the outer div for this question.\n * @returns {jQuery} containing that div.\n */\n DragDropMarkersQuestion.prototype.getRoot = function() {\n return $(document.getElementById(this.containerId));\n };\n\n /**\n * Get the img that is the background image.\n * @returns {jQuery} containing that img.\n */\n DragDropMarkersQuestion.prototype.bgImage = function() {\n return this.getRoot().find('img.dropbackground');\n };\n\n DragDropMarkersQuestion.prototype.handleDragStart = function(e) {\n var thisQ = this,\n dragged = $(e.target).closest('.marker');\n\n var info = dragDrop.prepare(e);\n if (!info.start) {\n return;\n }\n\n dragged.addClass('beingdragged').css('transform', '');\n\n var placed = !dragged.hasClass('unneeded');\n if (!placed) {\n var hiddenDrag = thisQ.getDragClone(dragged);\n if (hiddenDrag.length) {\n hiddenDrag.addClass('active');\n dragged.offset(hiddenDrag.offset());\n }\n }\n\n dragDrop.start(e, dragged, function() {\n void (1);\n }, function(x, y, dragged) {\n thisQ.dragEnd(dragged);\n });\n };\n\n /**\n * Functionality at the end of a drag drop.\n * @param {jQuery} dragged the marker that was dragged.\n */\n DragDropMarkersQuestion.prototype.dragEnd = function(dragged) {\n var placed = false,\n choiceNo = this.getChoiceNoFromElement(dragged),\n bgRatio = this.bgRatio(),\n dragXY;\n\n dragged.data('pagex', dragged.offset().left).data('pagey', dragged.offset().top);\n dragXY = new Shapes.Point(dragged.data('pagex'), dragged.data('pagey'));\n if (this.coordsInBgImg(dragXY)) {\n this.sendDragToDrop(dragged, true);\n placed = true;\n\n // It seems that the dragdrop sometimes leaves the drag\n // one pixel out of position. Put it in exactly the right place.\n var bgImgXY = this.convertToBgImgXY(dragXY);\n bgImgXY = new Shapes.Point(bgImgXY.x / bgRatio, bgImgXY.y / bgRatio);\n dragged.data('originX', bgImgXY.x).data('originY', bgImgXY.y);\n }\n\n if (!placed) {\n this.sendDragHome(dragged);\n this.removeDragIfNeeded(dragged);\n } else {\n this.cloneDragIfNeeded(dragged);\n }\n\n this.saveCoordsForChoice(choiceNo);\n };\n\n /**\n * Save the coordinates for a dropped item in the form field.\n * @param {Number} choiceNo which copy of the choice this was.\n */\n DragDropMarkersQuestion.prototype.saveCoordsForChoice = function(choiceNo) {\n var coords = [],\n items = this.getRoot().find('div.droparea span.marker.choice' + choiceNo),\n thiQ = this,\n bgRatio = this.bgRatio();\n\n if (items.length) {\n items.each(function() {\n var drag = $(this);\n if (!drag.hasClass('beingdragged')) {\n var dragXY = new Shapes.Point(drag.data('pagex'), drag.data('pagey'));\n if (thiQ.coordsInBgImg(dragXY)) {\n var bgImgXY = thiQ.convertToBgImgXY(dragXY);\n bgImgXY = new Shapes.Point(bgImgXY.x / bgRatio, bgImgXY.y / bgRatio);\n coords[coords.length] = bgImgXY;\n }\n }\n });\n }\n\n this.getRoot().find('input.choice' + choiceNo).val(coords.join(';'));\n };\n\n /**\n * Handle key down / press events on markers.\n * @param {KeyboardEvent} e\n */\n DragDropMarkersQuestion.prototype.handleKeyPress = function(e) {\n var drag = $(e.target).closest('.marker'),\n point = new Shapes.Point(drag.offset().left, drag.offset().top),\n choiceNo = this.getChoiceNoFromElement(drag);\n\n switch (e.keyCode) {\n case keys.arrowLeft:\n case 65: // A.\n point.x -= 1;\n break;\n case keys.arrowRight:\n case 68: // D.\n point.x += 1;\n break;\n case keys.arrowDown:\n case 83: // S.\n point.y += 1;\n break;\n case keys.arrowUp:\n case 87: // W.\n point.y -= 1;\n break;\n case keys.space:\n case keys.escape:\n point = null;\n break;\n default:\n return; // Ingore other keys.\n }\n e.preventDefault();\n\n if (point !== null) {\n point = this.constrainToBgImg(point);\n drag.offset({'left': point.x, 'top': point.y});\n drag.data('pagex', drag.offset().left).data('pagey', drag.offset().top);\n var dragXY = this.convertToBgImgXY(new Shapes.Point(drag.data('pagex'), drag.data('pagey')));\n drag.data('originX', dragXY.x / this.bgRatio()).data('originY', dragXY.y / this.bgRatio());\n if (this.coordsInBgImg(new Shapes.Point(drag.offset().left, drag.offset().top))) {\n if (drag.hasClass('unneeded')) {\n this.sendDragToDrop(drag, true);\n var hiddenDrag = this.getDragClone(drag);\n if (hiddenDrag.length) {\n hiddenDrag.addClass('active');\n }\n this.cloneDragIfNeeded(drag);\n }\n }\n } else {\n drag.css('left', '').css('top', '');\n drag.data('pagex', drag.offset().left).data('pagey', drag.offset().top);\n this.sendDragHome(drag);\n this.removeDragIfNeeded(drag);\n }\n drag.focus();\n this.saveCoordsForChoice(choiceNo);\n };\n\n /**\n * Makes sure the dragged item always exists within the background image area.\n *\n * @param {Point} windowxy\n * @returns {Point} coordinates\n */\n DragDropMarkersQuestion.prototype.constrainToBgImg = function(windowxy) {\n var bgImg = this.bgImage(),\n bgImgXY = this.convertToBgImgXY(windowxy);\n bgImgXY.x = Math.max(0, bgImgXY.x);\n bgImgXY.y = Math.max(0, bgImgXY.y);\n bgImgXY.x = Math.min(bgImg.width(), bgImgXY.x);\n bgImgXY.y = Math.min(bgImg.height(), bgImgXY.y);\n return this.convertToWindowXY(bgImgXY);\n };\n\n /**\n * Returns the choice number for a node.\n *\n * @param {Element|jQuery} node\n * @returns {Number}\n */\n DragDropMarkersQuestion.prototype.getChoiceNoFromElement = function(node) {\n return Number(this.getClassnameNumericSuffix(node, 'choice'));\n };\n\n /**\n * Returns the numeric part of a class with the given prefix.\n *\n * @param {Element|jQuery} node\n * @param {String} prefix\n * @returns {Number|null}\n */\n DragDropMarkersQuestion.prototype.getClassnameNumericSuffix = function(node, prefix) {\n var classes = $(node).attr('class');\n if (classes !== undefined && classes !== '') {\n var classesarr = classes.split(' ');\n for (var index = 0; index < classesarr.length; index++) {\n var patt1 = new RegExp('^' + prefix + '([0-9])+$');\n if (patt1.test(classesarr[index])) {\n var patt2 = new RegExp('([0-9])+$');\n var match = patt2.exec(classesarr[index]);\n return Number(match[0]);\n }\n }\n }\n return null;\n };\n\n /**\n * Handle when the window is resized.\n */\n DragDropMarkersQuestion.prototype.handleResize = function() {\n var thisQ = this,\n bgRatio = this.bgRatio();\n if (this.isPrinting) {\n bgRatio = 1;\n }\n\n this.getRoot().find('div.droparea .marker').not('.beingdragged').each(function(key, drag) {\n $(drag)\n .css('left', parseFloat($(drag).data('originX')) * parseFloat(bgRatio))\n .css('top', parseFloat($(drag).data('originY')) * parseFloat(bgRatio));\n thisQ.handleElementScale(drag, 'left top');\n });\n\n this.getRoot().find('div.droparea svg.dropzones')\n .width(this.bgImage().width())\n .height(this.bgImage().height());\n\n for (var dropZoneNo = 0; dropZoneNo < this.visibleDropZones.length; dropZoneNo++) {\n var dropZone = thisQ.visibleDropZones[dropZoneNo];\n var originCoords = dropZone.coords;\n var shape = thisQ.shapes[dropZoneNo];\n var shapeSVG = thisQ.shapeSVGs[dropZoneNo];\n shape.parse(originCoords, bgRatio);\n shape.updateSvg(shapeSVG);\n\n var handles = shape.getHandlePositions();\n var markerSpan = this.getRoot().find('div.ddarea div.markertexts span.markertext' + dropZoneNo);\n markerSpan\n .css('left', handles.moveHandle.x - (markerSpan.outerWidth() / 2) - 4)\n .css('top', handles.moveHandle.y - (markerSpan.outerHeight() / 2));\n thisQ.handleElementScale(markerSpan, 'center');\n }\n };\n\n /**\n * Clone the drag.\n */\n DragDropMarkersQuestion.prototype.cloneDrags = function() {\n var thisQ = this;\n this.getRoot().find('div.draghomes span.marker').each(function(index, draghome) {\n var drag = $(draghome);\n var placeHolder = drag.clone();\n placeHolder.removeClass();\n placeHolder.addClass('marker');\n placeHolder.addClass('choice' + thisQ.getChoiceNoFromElement(drag));\n placeHolder.addClass(thisQ.getDragNoClass(drag, false));\n placeHolder.addClass('dragplaceholder');\n drag.before(placeHolder);\n });\n };\n\n /**\n * Get the drag number of a drag.\n *\n * @param {jQuery} drag the drag.\n * @returns {Number} the drag number.\n */\n DragDropMarkersQuestion.prototype.getDragNo = function(drag) {\n return this.getClassnameNumericSuffix(drag, 'dragno');\n };\n\n /**\n * Get the drag number prefix of a drag.\n *\n * @param {jQuery} drag the drag.\n * @param {Boolean} includeSelector include the CSS selector prefix or not.\n * @return {String} Class name\n */\n DragDropMarkersQuestion.prototype.getDragNoClass = function(drag, includeSelector) {\n var className = 'dragno' + this.getDragNo(drag);\n if (this.isInfiniteDrag(drag)) {\n className = 'infinite';\n }\n\n if (includeSelector) {\n return '.' + className;\n }\n\n return className;\n };\n\n /**\n * Get drag clone for a given drag.\n *\n * @param {jQuery} drag the drag.\n * @returns {jQuery} the drag's clone.\n */\n DragDropMarkersQuestion.prototype.getDragClone = function(drag) {\n return this.getRoot().find('.draghomes' + ' span.marker' +\n '.choice' + this.getChoiceNoFromElement(drag) + this.getDragNoClass(drag, true) + '.dragplaceholder');\n };\n\n /**\n * Get the drop area element.\n * @returns {jQuery} droparea element.\n */\n DragDropMarkersQuestion.prototype.dropArea = function() {\n return this.getRoot().find('div.droparea');\n };\n\n /**\n * Animate a drag back to its home.\n *\n * @param {jQuery} drag the item being moved.\n */\n DragDropMarkersQuestion.prototype.sendDragHome = function(drag) {\n drag.removeClass('beingdragged')\n .addClass('unneeded')\n .css('top', '')\n .css('left', '')\n .css('transform', '');\n var placeHolder = this.getDragClone(drag);\n placeHolder.after(drag);\n placeHolder.removeClass('active');\n };\n\n /**\n * Animate a drag item into a given place.\n *\n * @param {jQuery} drag the item to place.\n * @param {boolean} isScaling Scaling or not\n */\n DragDropMarkersQuestion.prototype.sendDragToDrop = function(drag, isScaling) {\n var dropArea = this.dropArea(),\n bgRatio = this.bgRatio();\n drag.removeClass('beingdragged').removeClass('unneeded');\n var dragXY = this.convertToBgImgXY(new Shapes.Point(drag.data('pagex'), drag.data('pagey')));\n if (isScaling) {\n drag.data('originX', dragXY.x / bgRatio).data('originY', dragXY.y / bgRatio);\n drag.css('left', dragXY.x).css('top', dragXY.y);\n } else {\n drag.data('originX', dragXY.x).data('originY', dragXY.y);\n drag.css('left', dragXY.x * bgRatio).css('top', dragXY.y * bgRatio);\n }\n dropArea.append(drag);\n this.handleElementScale(drag, 'left top');\n };\n\n /**\n * Clone the drag at the draghome area if needed.\n *\n * @param {jQuery} drag the item to place.\n */\n DragDropMarkersQuestion.prototype.cloneDragIfNeeded = function(drag) {\n var inputNode = this.getInput(drag),\n noOfDrags = Number(this.getClassnameNumericSuffix(inputNode, 'noofdrags')),\n displayedDragsInDropArea = this.getRoot().find('div.droparea .marker.choice' +\n this.getChoiceNoFromElement(drag) + this.getDragNoClass(drag, true)).length,\n displayedDragsInDragHomes = this.getRoot().find('div.draghomes .marker.choice' +\n this.getChoiceNoFromElement(drag) + this.getDragNoClass(drag, true)).not('.dragplaceholder').length;\n\n if ((this.isInfiniteDrag(drag) ||\n !this.isInfiniteDrag(drag) && displayedDragsInDropArea < noOfDrags) && displayedDragsInDragHomes === 0) {\n var dragClone = drag.clone();\n dragClone.addClass('unneeded')\n .css('top', '')\n .css('left', '')\n .css('transform', '');\n this.getDragClone(drag)\n .removeClass('active')\n .after(dragClone);\n questionManager.addEventHandlersToMarker(dragClone);\n }\n };\n\n /**\n * Remove the clone drag at the draghome area if needed.\n *\n * @param {jQuery} drag the item to place.\n */\n DragDropMarkersQuestion.prototype.removeDragIfNeeded = function(drag) {\n var dragsInHome = this.getRoot().find('div.draghomes .marker.choice' +\n this.getChoiceNoFromElement(drag) + this.getDragNoClass(drag, true)).not('.dragplaceholder');\n var displayedDrags = dragsInHome.length;\n while (displayedDrags > 1) {\n dragsInHome.first().remove();\n displayedDrags--;\n }\n };\n\n /**\n * Get the input belong to drag.\n *\n * @param {jQuery} drag the item to place.\n * @returns {jQuery} input element.\n */\n DragDropMarkersQuestion.prototype.getInput = function(drag) {\n var choiceNo = this.getChoiceNoFromElement(drag);\n return this.getRoot().find('input.choices.choice' + choiceNo);\n };\n\n /**\n * Return the background ratio.\n *\n * @returns {number} Background ratio.\n */\n DragDropMarkersQuestion.prototype.bgRatio = function() {\n var bgImg = this.bgImage();\n var bgImgNaturalWidth = bgImg.get(0).naturalWidth;\n var bgImgClientWidth = bgImg.width();\n\n return bgImgClientWidth / bgImgNaturalWidth;\n };\n\n /**\n * Scale the drag if needed.\n *\n * @param {jQuery} element the item to place.\n * @param {String} type scaling type\n */\n DragDropMarkersQuestion.prototype.handleElementScale = function(element, type) {\n var bgRatio = parseFloat(this.bgRatio());\n if (this.isPrinting) {\n bgRatio = 1;\n }\n $(element).css({\n '-webkit-transform': 'scale(' + bgRatio + ')',\n '-moz-transform': 'scale(' + bgRatio + ')',\n '-ms-transform': 'scale(' + bgRatio + ')',\n '-o-transform': 'scale(' + bgRatio + ')',\n 'transform': 'scale(' + bgRatio + ')',\n 'transform-origin': type\n });\n };\n\n /**\n * Check if the given drag is in infinite mode or not.\n *\n * @param {jQuery} drag The drag item need to check.\n */\n DragDropMarkersQuestion.prototype.isInfiniteDrag = function(drag) {\n return drag.hasClass('infinite');\n };\n\n /**\n * Singleton that tracks all the DragDropToTextQuestions on this page, and deals\n * with event dispatching.\n *\n * @type {Object}\n */\n var questionManager = {\n\n /**\n * {boolean} ensures that the event handlers are only initialised once per page.\n */\n eventHandlersInitialised: false,\n\n /**\n * {boolean} is printing or not.\n */\n isPrinting: false,\n\n /**\n * {boolean} is keyboard navigation.\n */\n isKeyboardNavigation: false,\n\n /**\n * {Object} all the questions on this page, indexed by containerId (id on the .que div).\n */\n questions: {}, // An object containing all the information about each question on the page.\n\n /**\n * Initialise one question.\n *\n * @param {String} containerId the id of the div.que that contains this question.\n * @param {boolean} readOnly whether the question is read-only.\n * @param {Object[]} visibleDropZones data on any drop zones to draw as part of the feedback.\n */\n init: function(containerId, readOnly, visibleDropZones) {\n questionManager.questions[containerId] =\n new DragDropMarkersQuestion(containerId, readOnly, visibleDropZones);\n if (!questionManager.eventHandlersInitialised) {\n questionManager.setupEventHandlers();\n questionManager.eventHandlersInitialised = true;\n }\n },\n\n /**\n * Set up the event handlers that make this question type work. (Done once per page.)\n */\n setupEventHandlers: function() {\n // We do not use the body event here to prevent the other event on Mobile device, such as scroll event.\n questionManager.addEventHandlersToMarker($('.que.ddmarker:not(.qtype_ddmarker-readonly) div.draghomes .marker'));\n questionManager.addEventHandlersToMarker($('.que.ddmarker:not(.qtype_ddmarker-readonly) div.droparea .marker'));\n $(window).on('resize', function() {\n questionManager.handleWindowResize(false);\n });\n window.addEventListener('beforeprint', function() {\n questionManager.isPrinting = true;\n questionManager.handleWindowResize(questionManager.isPrinting);\n });\n window.addEventListener('afterprint', function() {\n questionManager.isPrinting = false;\n questionManager.handleWindowResize(questionManager.isPrinting);\n });\n setTimeout(function() {\n questionManager.fixLayoutIfThingsMoved();\n }, 100);\n },\n\n /**\n * Binding the event again for newly created element.\n *\n * @param {jQuery} element Element to bind the event\n */\n addEventHandlersToMarker: function(element) {\n element\n .on('mousedown touchstart', questionManager.handleDragStart)\n .on('keydown keypress', questionManager.handleKeyPress)\n .focusin(function(e) {\n questionManager.handleKeyboardFocus(e, true);\n })\n .focusout(function(e) {\n questionManager.handleKeyboardFocus(e, false);\n });\n },\n\n /**\n * Handle mouse down / touch start events on markers.\n * @param {Event} e the DOM event.\n */\n handleDragStart: function(e) {\n e.preventDefault();\n var question = questionManager.getQuestionForEvent(e);\n if (question) {\n question.handleDragStart(e);\n }\n },\n\n /**\n * Handle key down / press events on markers.\n * @param {Event} e\n */\n handleKeyPress: function(e) {\n var question = questionManager.getQuestionForEvent(e);\n if (question) {\n question.handleKeyPress(e);\n }\n },\n\n /**\n * Handle when the window is resized.\n * @param {boolean} isPrinting\n */\n handleWindowResize: function(isPrinting) {\n for (var containerId in questionManager.questions) {\n if (questionManager.questions.hasOwnProperty(containerId)) {\n questionManager.questions[containerId].isPrinting = isPrinting;\n questionManager.questions[containerId].handleResize();\n }\n }\n },\n\n /**\n * Handle focus lost events on markers.\n * @param {Event} e\n * @param {boolean} isNavigating\n */\n handleKeyboardFocus: function(e, isNavigating) {\n questionManager.isKeyboardNavigation = isNavigating;\n },\n\n /**\n * Sometimes, despite our best efforts, things change in a way that cannot\n * be specifically caught (e.g. dock expanding or collapsing in Boost).\n * Therefore, we need to periodically check everything is in the right position.\n */\n fixLayoutIfThingsMoved: function() {\n if (!questionManager.isKeyboardNavigation) {\n this.handleWindowResize(questionManager.isPrinting);\n }\n // We use setTimeout after finishing work, rather than setInterval,\n // in case positioning things is slow. We want 100 ms gap\n // between executions, not what setInterval does.\n setTimeout(function() {\n questionManager.fixLayoutIfThingsMoved(questionManager.isPrinting);\n }, 100);\n },\n\n /**\n * Given an event, work out which question it effects.\n * @param {Event} e the event.\n * @returns {DragDropMarkersQuestion|undefined} The question, or undefined.\n */\n getQuestionForEvent: function(e) {\n var containerId = $(e.currentTarget).closest('.que.ddmarker').attr('id');\n return questionManager.questions[containerId];\n }\n };\n\n /**\n * @alias module:qtype_ddmarker/question\n */\n return {\n /**\n * Initialise one drag-drop markers question.\n *\n * @param {String} containerId id of the outer div for this question.\n * @param {String} bgImgUrl the URL of the background image.\n * @param {boolean} readOnly whether the question is being displayed read-only.\n * @param {String[]} visibleDropZones the geometry of any drop-zones to show.\n */\n init: questionManager.init\n };\n});\n"],"file":"question.min.js"} \ No newline at end of file diff --git a/question/type/ddmarker/amd/src/question.js b/question/type/ddmarker/amd/src/question.js index 5d516de01a8..ca8b9bccf99 100644 --- a/question/type/ddmarker/amd/src/question.js +++ b/question/type/ddmarker/amd/src/question.js @@ -473,8 +473,10 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f var drag = $(draghome); var placeHolder = drag.clone(); placeHolder.removeClass(); - placeHolder.addClass('marker choice' + - thisQ.getChoiceNoFromElement(drag) + ' dragno' + thisQ.getDragNo(drag) + ' dragplaceholder'); + placeHolder.addClass('marker'); + placeHolder.addClass('choice' + thisQ.getChoiceNoFromElement(drag)); + placeHolder.addClass(thisQ.getDragNoClass(drag, false)); + placeHolder.addClass('dragplaceholder'); drag.before(placeHolder); }); }; @@ -489,6 +491,26 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f return this.getClassnameNumericSuffix(drag, 'dragno'); }; + /** + * Get the drag number prefix of a drag. + * + * @param {jQuery} drag the drag. + * @param {Boolean} includeSelector include the CSS selector prefix or not. + * @return {String} Class name + */ + DragDropMarkersQuestion.prototype.getDragNoClass = function(drag, includeSelector) { + var className = 'dragno' + this.getDragNo(drag); + if (this.isInfiniteDrag(drag)) { + className = 'infinite'; + } + + if (includeSelector) { + return '.' + className; + } + + return className; + }; + /** * Get drag clone for a given drag. * @@ -497,7 +519,7 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f */ DragDropMarkersQuestion.prototype.getDragClone = function(drag) { return this.getRoot().find('.draghomes' + ' span.marker' + - '.choice' + this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag) + '.dragplaceholder'); + '.choice' + this.getChoiceNoFromElement(drag) + this.getDragNoClass(drag, true) + '.dragplaceholder'); }; /** @@ -555,20 +577,21 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f var inputNode = this.getInput(drag), noOfDrags = Number(this.getClassnameNumericSuffix(inputNode, 'noofdrags')), displayedDragsInDropArea = this.getRoot().find('div.droparea .marker.choice' + - this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag)).length, + this.getChoiceNoFromElement(drag) + this.getDragNoClass(drag, true)).length, displayedDragsInDragHomes = this.getRoot().find('div.draghomes .marker.choice' + - this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag)).not('.dragplaceholder').length; + this.getChoiceNoFromElement(drag) + this.getDragNoClass(drag, true)).not('.dragplaceholder').length; - if (displayedDragsInDropArea < noOfDrags && displayedDragsInDragHomes === 0) { - var dragclone = drag.clone(); - dragclone.addClass('unneeded') + if ((this.isInfiniteDrag(drag) || + !this.isInfiniteDrag(drag) && displayedDragsInDropArea < noOfDrags) && displayedDragsInDragHomes === 0) { + var dragClone = drag.clone(); + dragClone.addClass('unneeded') .css('top', '') .css('left', '') .css('transform', ''); this.getDragClone(drag) .removeClass('active') - .after(dragclone); - questionManager.addEventHandlersToMarker(dragclone); + .after(dragClone); + questionManager.addEventHandlersToMarker(dragClone); } }; @@ -578,11 +601,12 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f * @param {jQuery} drag the item to place. */ DragDropMarkersQuestion.prototype.removeDragIfNeeded = function(drag) { - var displayeddrags = this.getRoot().find('div.draghomes .marker.choice' + - this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag)).not('.dragplaceholder').length; - if (displayeddrags > 1) { - this.getRoot().find('div.draghomes .marker.choice' + - this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag)).not('.dragplaceholder').first().remove(); + var dragsInHome = this.getRoot().find('div.draghomes .marker.choice' + + this.getChoiceNoFromElement(drag) + this.getDragNoClass(drag, true)).not('.dragplaceholder'); + var displayedDrags = dragsInHome.length; + while (displayedDrags > 1) { + dragsInHome.first().remove(); + displayedDrags--; } }; @@ -631,6 +655,15 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f }); }; + /** + * Check if the given drag is in infinite mode or not. + * + * @param {jQuery} drag The drag item need to check. + */ + DragDropMarkersQuestion.prototype.isInfiniteDrag = function(drag) { + return drag.hasClass('infinite'); + }; + /** * Singleton that tracks all the DragDropToTextQuestions on this page, and deals * with event dispatching.