From 489ce9659bc6036a54622e3a625e663a7ac5dcd4 Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Mon, 30 Nov 2015 10:54:51 -0500 Subject: [PATCH] Add new HotSpot library in SVG/JS - refs #7705 --- main/plugin/hotspot2/css/hotspot.css | 15 + main/plugin/hotspot2/js/hotspot.js | 502 +++++++++++++++++++++++++++ 2 files changed, 517 insertions(+) create mode 100644 main/plugin/hotspot2/css/hotspot.css create mode 100755 main/plugin/hotspot2/js/hotspot.js diff --git a/main/plugin/hotspot2/css/hotspot.css b/main/plugin/hotspot2/css/hotspot.css new file mode 100644 index 0000000000..c69f180a26 --- /dev/null +++ b/main/plugin/hotspot2/css/hotspot.css @@ -0,0 +1,15 @@ +#hotspot-container { + line-height: 0; + /*position: relative; + width: 100%; + vertical-align: middle; + padding-bottom: 408px; + margin: 0; + overflow: hidden;*/ +} +#hotspot-container svg { + /*display: inline-block; + position: absolute; + top: 0; + left: 0;*/ +} diff --git a/main/plugin/hotspot2/js/hotspot.js b/main/plugin/hotspot2/js/hotspot.js new file mode 100755 index 0000000000..009b7b3455 --- /dev/null +++ b/main/plugin/hotspot2/js/hotspot.js @@ -0,0 +1,502 @@ +var HotSpotAdmin = (function () { + var HotSpotSquare = function () { + this.x = 0; + this.y = 0; + this.width = 0; + this.height = 0; + }; + HotSpotSquare.prototype.setStartPoint = function (x, y) { + this.x = parseInt(x); + this.y = parseInt(y); + }; + HotSpotSquare.prototype.setEndPoint = function (x, y) { + var x2, y2; + + x = parseInt(x); + y = parseInt(y); + + if (x < this.x) { + x2 = this.x; + this.x = x; + } else { + x2 = x; + } + + if (y < this.y) { + y2 = this.y; + this.y = y; + } else { + y2 = y; + } + + this.width = Math.round(x2 - this.x); + this.height = Math.round(y2 - this.y); + }; + HotSpotSquare.prototype.encode = function () { + var encodedPosition = [this.x, this.y].join(';'); + + return [ + encodedPosition, + this.width, + this.height + ].join('|'); + } + + var HotSpotEllipse = function () { + this.centerX = 0; + this.centerY = 0; + this.radiusX = 0; + this.radiusY = 0; + }; + HotSpotEllipse.prototype.setStartPoint = function (x, y) { + this.centerX = parseInt(x); + this.centerY = parseInt(y); + }; + HotSpotEllipse.prototype.setEndPoint = function (x, y) { + var startX = this.centerX, + startY = this.centerY, + endX = 0, + endY = 0; + + x = parseInt(x); + y = parseInt(y); + + if (x < startX) { + endX = startX; + startX = x; + } else { + endX = x; + } + + if (y < startY) { + endY = startY; + startY = y; + } else { + endY = y; + } + + var width = Math.round(endX - startX); + var height = Math.round(endY - startY); + + this.radiusX = Math.round(width / 2); + this.radiusY = Math.round(height / 2); + this.centerX = startX + this.radiusX; + this.centerY = startY + this.radiusY; + }; + HotSpotEllipse.prototype.encode = function () { + var encodedCenter = [this.centerX, this.centerY].join(';'); + + return [ + encodedCenter, + this.radiusX, + this.radiusY + ].join('|'); + }; + + var HotSpotPolygon = function () { + this.points = []; + }; + HotSpotPolygon.prototype.addPoint = function (x, y) { + this.points.push([ + parseInt(x), + parseInt(y) + ]); + }; + HotSpotPolygon.prototype.encode = function () { + var encodedPoints = []; + + this.points.forEach(function (point) { + encodedPoints.push(point.join(';')); + }); + + return encodedPoints.join('|'); + }; + + var HotSpotEl = function (hotspot, color) { + this.hotspot = hotspot; + this.element = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); + this.elStroke = 'rgb(' + color + ')'; + this.elFill = 'rgba(' + color + ', 0.5)'; + }; + HotSpotEl.prototype.render = function () { + return this; + }; + HotSpotEl.prototype.remove = function () { + if (!this.element) { + return; + } + + this.element.parentNode.removeChild(this.element); + this.hotspot = null; + }; + + var SquareEl = function (hotspot, color) { + HotSpotEl.call(this, hotspot, color); + + this.element = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); + this.element.setAttribute('stroke-width', 2); + this.element.setAttribute('stroke', this.elStroke); + this.element.setAttribute('fill', this.elFill); + }; + SquareEl.prototype = Object.create(HotSpotEl.prototype); + SquareEl.prototype.render = function () { + this.element.setAttribute('x', this.hotspot.x); + this.element.setAttribute('y', this.hotspot.y); + this.element.setAttribute('width', this.hotspot.width); + this.element.setAttribute('height', this.hotspot.height); + + return this.element; + }; + + var EllipseEl = function (hotspot, color) { + HotSpotEl.call(this, hotspot, color); + + this.element = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse'); + this.element.setAttribute('stroke-width', 2); + this.element.setAttribute('stroke', this.elStroke); + this.element.setAttribute('fill', this.elFill); + }; + EllipseEl.prototype = Object.create(HotSpotEl.prototype); + EllipseEl.prototype.render = function () { + this.element.setAttribute('cx', this.hotspot.centerX); + this.element.setAttribute('cy', this.hotspot.centerY); + this.element.setAttribute('rx', this.hotspot.radiusX); + this.element.setAttribute('ry', this.hotspot.radiusY); + + return this.element; + }; + + var PolygonEl = function (hotspot, color) { + HotSpotEl.call(this, hotspot, color); + + this.element = document.createElementNS('http://www.w3.org/2000/svg', 'polygon'); + this.element.setAttribute('stroke-width', 2); + this.element.setAttribute('stroke', this.elStroke); + this.element.setAttribute('fill', this.elFill); + }; + PolygonEl.prototype = Object.create(HotSpotEl.prototype); + PolygonEl.prototype.render = function () { + var pointsPaired = []; + + this.hotspot.points.forEach(function (point) { + pointsPaired.push(point.join(',')); + }); + + this.element.setAttribute( + 'points', + pointsPaired.join(' ') + ); + + return this.element; + }; + + var HotSpotSelectorEl = function (color, index, selectedValue) { + this.hotSpotIndex = parseInt(index); + this.elStroke = 'rgb(' + color + ')'; + this.elFill = 'rgba(' + color + ', 0.5)'; + + switch (selectedValue) { + case 'square': + default: + this.selectedValue = 'square'; + break; + + case 'circle': + this.selectedValue = 'ellipse'; + break; + + case 'poly': + this.selectedValue = 'polygon'; + break; + } + }; + HotSpotSelectorEl.prototype.render = function () { + var self = this; + + this.el = document.createElement('div'); + this.el.className = 'col-xs-6 col-sm-3 col-md-2'; + this.el.innerHTML = '\n\ +
\n\ + \n\ + \n\ + \n\ + \n\ +
\n\ + '; + + $(this.el).find('select').val(this.selectedValue); + + var selectShapeEvent = function (e) { + switch (this.value) { + case 'square': + //no break + case 'ellipse': + //no break + case 'polygon': + currentShapeType = this.value; + currentHotSpotIndex = self.hotSpotIndex; + break; + + default: + break; + } + }; + + $(this.el).on('click', selectShapeEvent); + $(this.el).find('select').on('change', selectShapeEvent); + + return this.el; + }; + + var colors = [ + '66, 113, 181', + '254, 142, 22', + '69, 199, 240', + '188, 214, 49', + '214, 49, 115', + '215, 215, 215', + '144, 175, 221', + '174, 134, 64', + '79, 146, 66', + '244, 235, 36', + '237, 32, 36', + '59, 59, 59', + '247, 189, 226' + ]; + + var getPointOnImage = function (x, y) { + var pointerPosition = { + left: x + window.scrollX, + top: y + window.scrollY + }, + canvasOffset = { + x: canvas.getBoundingClientRect().x + window.scrollX, + y: canvas.getBoundingClientRect().y + window.scrollY + }; + + return { + x: Math.round(pointerPosition.left - canvasOffset.x), + y: Math.round(pointerPosition.top - canvasOffset.y) + }; + }; + + var startCanvas = function () { + var newHotSpotEl = null, + pressingShift = false; + + document.addEventListener('keydown', function (e) { + if (e.keyCode === 16) { + pressingShift = true; + } + }, false); + document.addEventListener('keyup', function (e) { + if (e.keyCode === 16) { + pressingShift = false; + } + }, false); + + canvas.addEventListener('click', function (e) { + e.preventDefault(); + }, false); + + container.addEventListener('dragstart', function (e) { + e.preventDefault(); + }, false); + container.addEventListener('click', function (e) { + if (shapes.length >= colors.length) { + return; + } + + newHotSpotEl = draw(newHotSpotEl, e.clientX, e.clientY, pressingShift); + + if (!newHotSpotEl) { + updateValues(); + } + }, false); + }; + + var draw = function (hotSpotEl, x, y, isPressingShift) { + var pointerPosition = getPointOnImage(x, y), + hotSpot = null; + + if (!hotSpotEl) { + switch (currentShapeType) { + case 'square': + //no break + case 'ellipse': + if (currentShapeType === 'ellipse') { + hotSpot = new HotSpotEllipse(); + hotSpotEl = new EllipseEl(hotSpot, colors[currentHotSpotIndex]); + } else { + hotSpot = new HotSpotSquare(); + hotSpotEl = new SquareEl(hotSpot, colors[currentHotSpotIndex]); + } + + hotSpot.setStartPoint(pointerPosition.x, pointerPosition.y); + break; + + case 'polygon': + hotSpot = new HotSpotPolygon(); + hotSpotEl = new PolygonEl(hotSpot, colors[currentHotSpotIndex]); + + hotSpot.addPoint(pointerPosition.x, pointerPosition.y); + break; + } + + shapes[currentHotSpotIndex].remove(); + shapes.splice(currentHotSpotIndex, 1, hotSpotEl); + + canvas.appendChild(hotSpotEl.render()); + + return hotSpotEl; + } + + switch (currentShapeType) { + case 'square': + //no break + case 'ellipse': + hotSpotEl.hotspot.setEndPoint(pointerPosition.x, pointerPosition.y); + hotSpotEl.render(); + + hotSpotEl = null; + break; + + case 'polygon': + hotSpotEl.hotspot.addPoint(pointerPosition.x, pointerPosition.y); + hotSpotEl.render(); + + if (isPressingShift) { + hotSpotEl = null; + } + break; + } + + return hotSpotEl; + }; + + var updateValues = function () { + var currentHotSpotEl = shapes[currentHotSpotIndex]; + + if (currentHotSpotIndex === undefined) { + return; + } + + if (currentHotSpotEl.hotspot instanceof HotSpotSquare) { + $('[name="hotspot_type[' + (currentHotSpotIndex + 1) + ']"]').val('square'); + } else if (currentHotSpotEl.hotspot instanceof HotSpotEllipse) { + $('[name="hotspot_type[' + (currentHotSpotIndex + 1) + ']"]').val('circle'); + } else if (currentHotSpotEl.hotspot instanceof HotSpotPolygon) { + $('[name="hotspot_type[' + (currentHotSpotIndex + 1) + ']"]').val('poly'); + } + + $('[name="hotspot_coordinates[' + (currentHotSpotIndex + 1) + ']"]').val( + currentHotSpotEl.hotspot.encode() + ); + }; + + var loadHotSpots = function (hotSpotList) { + hotSpotList.forEach(function (hotSpotData, index) { + var hotSpot = null, + hotSpotEl = null, + color = colors[shapes.length]; + + switch (hotSpotData.type) { + case 'square': + hotSpot = new HotSpotSquare(); + hotSpotEl = new SquareEl(hotSpot, color); + + var coords = hotSpotData.coord.split('|'), + position = coords[0].split(';'), + x = parseInt(position[0]), + y = parseInt(position[1]), + width = parseInt(coords[1]), + height = parseInt(coords[2]); + + hotSpot.setStartPoint(x, y); + hotSpot.setEndPoint(x + width, y + height); + break; + case 'circle': + hotSpot = new HotSpotEllipse(); + hotSpotEl = new EllipseEl(hotSpot, color); + + var coords = hotSpotData.coord.split('|'), + position = coords[0].split(';'), + x = parseInt(position[0] - coords[1]), + y = parseInt(position[1] - coords[2]), + width = parseInt(coords[1]) * 2, + height = parseInt(coords[2]) * 2; + + hotSpot.setStartPoint(x, y); + hotSpot.setEndPoint(x + width, y + height); + break; + + case 'poly': + hotSpot = new HotSpotPolygon(); + hotSpotEl = new PolygonEl(hotSpot, color); + + hotSpotData.coord.split('|').forEach(function (point) { + var exis = point.split(';'); + + hotSpot.addPoint(exis[0], exis[1]); + }); + break; + } + + if (hotSpotEl) { + var hotSpotSelector = new HotSpotSelectorEl(color, index, hotSpotData.type); + + selectors.appendChild(hotSpotSelector.render()); + canvas.appendChild(hotSpotEl.render()); + shapes.push(hotSpotEl); + } + }); + }; + + var container, canvas, selectors, currentShapeType, currentHotSpotIndex, shapes = []; + + return { + init: function (questionId, imageSrc) { + if (!questionId || !imageSrc) { + return; + } + + selectors = document.querySelector('#hotspot-selectors'); + container = document.querySelector('#hotspot-container'); + canvas = document.querySelector('#hotspot-container svg'); + currentShapeType = 'square'; + + var xhrImage = new $.Deferred(); + + var image = new Image(); + image.onload = function () { + xhrImage.resolve(this); + }; + image.onerror = function () { + xhrImage.reject(); + }; + image.src = imageSrc; + + var xhrHotSpots = $.get('/main/exercice/hotspot_actionscript_admin.as.php', { + modifyAnswers: parseInt(questionId) + }); + + $.when.apply($, [xhrImage, xhrHotSpots]).done(function (imageRequest, hotSpotsRequest) { + var imageSvg = document.createElementNS('http://www.w3.org/2000/svg', 'image'); + imageSvg.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', imageRequest.src); + imageSvg.setAttribute('width', imageRequest.width); + imageSvg.setAttribute('height', imageRequest.height); + + container.style.width = imageRequest.width + 'px'; + canvas.setAttribute('viewBox', '0 0 ' + imageRequest.width + ' ' + imageRequest.height); + canvas.appendChild(imageSvg); + + loadHotSpots(hotSpotsRequest[0].hotspots); + startCanvas(); + }); + } + }; +})();