parent
e8eab30010
commit
a1c4c6fe62
@ -0,0 +1,103 @@ |
|||||||
|
<?php |
||||||
|
/* For licensing terms, see /license.txt */ |
||||||
|
|
||||||
|
/** |
||||||
|
* Class Annotation |
||||||
|
* Allow instanciate an object of type HotSpot extending the class question |
||||||
|
* @author Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com> |
||||||
|
* @package chamilo. |
||||||
|
*/ |
||||||
|
class Annotation extends Question |
||||||
|
{ |
||||||
|
public static $typePicture = 'annotation.png'; |
||||||
|
public static $explanationLangVar = 'Annotation'; |
||||||
|
|
||||||
|
/** |
||||||
|
* Annotation constructor. |
||||||
|
*/ |
||||||
|
public function __construct() |
||||||
|
{ |
||||||
|
parent::__construct(); |
||||||
|
$this->type = ANNOTATION; |
||||||
|
} |
||||||
|
|
||||||
|
public function display() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param FormValidator $form |
||||||
|
* @param int $fck_config |
||||||
|
*/ |
||||||
|
public function createForm(&$form, $fck_config = 0) |
||||||
|
{ |
||||||
|
parent::createForm($form, $fck_config); |
||||||
|
|
||||||
|
if (isset($_GET['editQuestion'])) { |
||||||
|
$form->addButtonUpdate(get_lang('ModifyExercise'), 'submitQuestion'); |
||||||
|
|
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
$form->addElement( |
||||||
|
'file', |
||||||
|
'imageUpload', |
||||||
|
array( |
||||||
|
Display::img( |
||||||
|
Display::return_icon('annotation.png', null, null, ICON_SIZE_BIG, false, true) |
||||||
|
), |
||||||
|
get_lang('UploadJpgPicture'), |
||||||
|
) |
||||||
|
); |
||||||
|
|
||||||
|
$form->addButtonSave(get_lang('GoToQuestion'), 'submitQuestion'); |
||||||
|
$form->addRule('imageUpload', get_lang('OnlyImagesAllowed'), 'filetype', array('jpg', 'jpeg', 'png', 'gif')); |
||||||
|
$form->addRule('imageUpload', get_lang('NoImage'), 'uploadedfile'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param FormValidator $form |
||||||
|
* @param null $objExercise |
||||||
|
* @return null|false |
||||||
|
*/ |
||||||
|
public function processCreation($form, $objExercise = null) |
||||||
|
{ |
||||||
|
$fileInfo = $form->getSubmitValue('imageUpload'); |
||||||
|
$courseInfo = api_get_course_info(); |
||||||
|
|
||||||
|
parent::processCreation($form, $objExercise); |
||||||
|
|
||||||
|
if (!empty($fileInfo['tmp_name'])) { |
||||||
|
$this->uploadPicture($fileInfo['tmp_name'], $fileInfo['name']); |
||||||
|
|
||||||
|
$documentPath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'; |
||||||
|
$picturePath = $documentPath.'/images'; |
||||||
|
|
||||||
|
// fixed width ang height |
||||||
|
if (!file_exists($picturePath.'/'.$this->picture)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
$this->resizePicture('width', 800); |
||||||
|
$this->save(); |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param FormValidator $form |
||||||
|
*/ |
||||||
|
function createAnswersForm($form) |
||||||
|
{ |
||||||
|
// nothing |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param FormValidator $form |
||||||
|
*/ |
||||||
|
function processAnswersCreation($form) |
||||||
|
{ |
||||||
|
// nothing |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,65 @@ |
|||||||
|
<?php |
||||||
|
/* For licensing terms, see /license.txt */ |
||||||
|
|
||||||
|
use ChamiloSession as Session; |
||||||
|
|
||||||
|
session_cache_limiter("none"); |
||||||
|
|
||||||
|
require_once __DIR__.'/../inc/global.inc.php'; |
||||||
|
|
||||||
|
$questionId = isset($_GET['question_id']) ? intval($_GET['question_id']) : 0; |
||||||
|
$exerciseId = isset($_GET['exe_id']) ? intval($_GET['exe_id']) : 0; |
||||||
|
|
||||||
|
$objQuestion = Question::read($questionId); |
||||||
|
$documentPath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'; |
||||||
|
$picturePath = $documentPath.'/images'; |
||||||
|
$pictureName = $objQuestion->selectPicture(); |
||||||
|
$pictureSize = getimagesize($picturePath.'/'.$objQuestion->selectPicture()); |
||||||
|
$pictureWidth = $pictureSize[0]; |
||||||
|
$pictureHeight = $pictureSize[1]; |
||||||
|
|
||||||
|
$data = [ |
||||||
|
'use' => 'user', |
||||||
|
'image' => [ |
||||||
|
'path' => $objQuestion->selectPicturePath(), |
||||||
|
'width' => $pictureSize[0], |
||||||
|
'height' => $pictureSize[1] |
||||||
|
], |
||||||
|
'answers' => [ |
||||||
|
'paths' => [], |
||||||
|
'texts' => [] |
||||||
|
] |
||||||
|
]; |
||||||
|
|
||||||
|
$attemptList = Event::getAllExerciseEventByExeId($exerciseId); |
||||||
|
|
||||||
|
if (!empty($attemptList) && isset($attemptList[$questionId])) { |
||||||
|
$questionAttempt = $attemptList[$questionId][0]; |
||||||
|
|
||||||
|
if (!empty($questionAttempt['answer'])) { |
||||||
|
$answers = explode('|', $questionAttempt['answer']); |
||||||
|
|
||||||
|
foreach ($answers as $answer) { |
||||||
|
$parts = explode(')(', $answer); |
||||||
|
$type = array_shift($parts); |
||||||
|
|
||||||
|
switch ($type) { |
||||||
|
case 'P': |
||||||
|
$points = []; |
||||||
|
|
||||||
|
foreach ($parts as $partPoint) { |
||||||
|
$points[] = Geometry::decodePoint($partPoint); |
||||||
|
} |
||||||
|
|
||||||
|
$data['answers']['paths'][] = $points; |
||||||
|
break; |
||||||
|
case 'T': |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
header('Content-Type: application/json'); |
||||||
|
|
||||||
|
echo json_encode($data); |
||||||
@ -0,0 +1,299 @@ |
|||||||
|
/* For licensing terms, see /license.txt */ |
||||||
|
|
||||||
|
(function (window) { |
||||||
|
/** |
||||||
|
* @param referenceElement Element to get the point |
||||||
|
* @param x MouseEvent's clientX |
||||||
|
* @param y MouseEvent's clientY |
||||||
|
* @returns {{x: number, y: number}} |
||||||
|
*/ |
||||||
|
function getPointOnImage(referenceElement, x, y) { |
||||||
|
var pointerPosition = { |
||||||
|
left: x + window.scrollX, |
||||||
|
top: y + window.scrollY |
||||||
|
}, |
||||||
|
canvasOffset = { |
||||||
|
x: referenceElement.getBoundingClientRect().left + window.scrollX, |
||||||
|
y: referenceElement.getBoundingClientRect().top + window.scrollY |
||||||
|
}; |
||||||
|
|
||||||
|
return { |
||||||
|
x: Math.round(pointerPosition.left - canvasOffset.x), |
||||||
|
y: Math.round(pointerPosition.top - canvasOffset.y) |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* @param Object attributes |
||||||
|
* @constructor |
||||||
|
*/ |
||||||
|
var SvgElementModel = function (attributes) { |
||||||
|
this.attributes = attributes; |
||||||
|
this.id = 0; |
||||||
|
this.name = ''; |
||||||
|
|
||||||
|
this.changeEvent = null; |
||||||
|
}; |
||||||
|
SvgElementModel.prototype.set = function (key, value) { |
||||||
|
this.attributes[key] = value; |
||||||
|
|
||||||
|
if (this.changeEvent) { |
||||||
|
this.changeEvent(this); |
||||||
|
} |
||||||
|
}; |
||||||
|
SvgElementModel.prototype.get = function (key) { |
||||||
|
return this.attributes[key]; |
||||||
|
}; |
||||||
|
SvgElementModel.prototype.onChange = function (callback) { |
||||||
|
this.changeEvent = callback; |
||||||
|
}; |
||||||
|
SvgElementModel.decode = function () { |
||||||
|
return new this; |
||||||
|
}; |
||||||
|
SvgElementModel.prototype.encode = function () { |
||||||
|
return ''; |
||||||
|
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* @param Object attributes |
||||||
|
* @constructor |
||||||
|
*/ |
||||||
|
var SvgPathModel = function (attributes) { |
||||||
|
SvgElementModel.call(this, attributes); |
||||||
|
}; |
||||||
|
SvgPathModel.prototype = Object.create(SvgElementModel.prototype); |
||||||
|
SvgPathModel.prototype.addPoint = function (x, y) { |
||||||
|
x = parseInt(x); |
||||||
|
y = parseInt(y); |
||||||
|
|
||||||
|
var points = this.get('points'); |
||||||
|
points.push([x, y]); |
||||||
|
|
||||||
|
this.set('points', points); |
||||||
|
}; |
||||||
|
SvgPathModel.prototype.encode = function () { |
||||||
|
var pairedPoints = []; |
||||||
|
|
||||||
|
this.get('points').forEach(function (point) { |
||||||
|
pairedPoints.push( |
||||||
|
point.join(';') |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
return 'P)(' + pairedPoints.join(')('); |
||||||
|
}; |
||||||
|
SvgPathModel.decode = function (pathInfo) { |
||||||
|
var points = []; |
||||||
|
|
||||||
|
$(pathInfo).each(function (i, point) { |
||||||
|
points.push([point.x, point.y]); |
||||||
|
}); |
||||||
|
|
||||||
|
return new SvgPathModel({points: points}); |
||||||
|
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* @param Object model |
||||||
|
* @constructor |
||||||
|
*/ |
||||||
|
var SvgPathView = function (model) { |
||||||
|
var self = this; |
||||||
|
|
||||||
|
this.model = model; |
||||||
|
this.model.onChange(function () { |
||||||
|
self.render(); |
||||||
|
}); |
||||||
|
|
||||||
|
this.el = document.createElementNS('http://www.w3.org/2000/svg', 'path'); |
||||||
|
this.el.setAttribute('fill', 'transparent'); |
||||||
|
this.el.setAttribute('stroke', 'red'); |
||||||
|
this.el.setAttribute('stroke-width', 3); |
||||||
|
}; |
||||||
|
SvgPathView.prototype.render = function () { |
||||||
|
var d = '', |
||||||
|
points = this.model.get('points'); |
||||||
|
|
||||||
|
$.each( |
||||||
|
this.model.get('points'), |
||||||
|
function (i, point) { |
||||||
|
d += (i === 0) ? 'M' : ' L '; |
||||||
|
d += point[0] + ' ' + point[1]; |
||||||
|
} |
||||||
|
); |
||||||
|
|
||||||
|
this.el.setAttribute('d', d); |
||||||
|
|
||||||
|
return this; |
||||||
|
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* @constructor |
||||||
|
*/ |
||||||
|
var PathsCollection = function () { |
||||||
|
this.models = []; |
||||||
|
this.length = 0; |
||||||
|
this.addEvent = null; |
||||||
|
}; |
||||||
|
PathsCollection.prototype.add = function (pathModel) { |
||||||
|
pathModel.id = ++this.length; |
||||||
|
|
||||||
|
this.models.push(pathModel); |
||||||
|
|
||||||
|
if (this.addEvent) { |
||||||
|
this.addEvent(pathModel); |
||||||
|
} |
||||||
|
}; |
||||||
|
PathsCollection.prototype.get = function (index) { |
||||||
|
return this.models[index]; |
||||||
|
}; |
||||||
|
PathsCollection.prototype.onAdd = function (callback) { |
||||||
|
this.addEvent = callback; |
||||||
|
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* @param pathsCollection |
||||||
|
* @param image |
||||||
|
* @param questionId |
||||||
|
* @constructor |
||||||
|
*/ |
||||||
|
var AnnotationCanvasView = function (pathsCollection, image, questionId) { |
||||||
|
var self = this; |
||||||
|
|
||||||
|
this.el = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); |
||||||
|
this.$el = $(this.el); |
||||||
|
|
||||||
|
this.questionId = parseInt(questionId); |
||||||
|
|
||||||
|
this.image = image; |
||||||
|
|
||||||
|
this.pathsCollection = pathsCollection; |
||||||
|
this.pathsCollection.onAdd(function (pathModel) { |
||||||
|
self.renderPath(pathModel); |
||||||
|
}); |
||||||
|
}; |
||||||
|
AnnotationCanvasView.prototype.render = function () { |
||||||
|
this.el.setAttribute('version', '1.1'); |
||||||
|
this.el.setAttribute('viewBox', '0 0 ' + this.image.width + ' ' + this.image.height); |
||||||
|
|
||||||
|
var svgImage = document.createElementNS('http://www.w3.org/2000/svg', 'image'); |
||||||
|
svgImage.setAttributeNS('http://www.w3.org/1999/xlink', 'href', this.image.src); |
||||||
|
svgImage.setAttribute('width', this.image.width); |
||||||
|
svgImage.setAttribute('height', this.image.height); |
||||||
|
|
||||||
|
this.el.appendChild(svgImage); |
||||||
|
this.setEvents(); |
||||||
|
|
||||||
|
return this; |
||||||
|
}; |
||||||
|
AnnotationCanvasView.prototype.setEvents = function () { |
||||||
|
var self = this; |
||||||
|
|
||||||
|
var isMoving = false, |
||||||
|
pathModel = null; |
||||||
|
|
||||||
|
self.$el |
||||||
|
.on('dragstart', function (e) { |
||||||
|
e.preventDefault(); |
||||||
|
}) |
||||||
|
.on('mousedown', function (e) { |
||||||
|
e.preventDefault(); |
||||||
|
|
||||||
|
var point = getPointOnImage(self.el, e.clientX, e.clientY); |
||||||
|
|
||||||
|
pathModel = new SvgPathModel({points: [[point.x, point.y]]}); |
||||||
|
|
||||||
|
self.pathsCollection.add(pathModel); |
||||||
|
|
||||||
|
isMoving = true; |
||||||
|
}) |
||||||
|
.on('mousemove', function (e) { |
||||||
|
e.preventDefault(); |
||||||
|
|
||||||
|
if (!isMoving) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
var point = getPointOnImage(self.el, e.clientX, e.clientY); |
||||||
|
|
||||||
|
if (!pathModel) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
pathModel.addPoint(point.x, point.y); |
||||||
|
}) |
||||||
|
.on('mouseup', function (e) { |
||||||
|
e.preventDefault(); |
||||||
|
|
||||||
|
if (!isMoving) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
$('input[name="choice[' + self.questionId + '][' + pathModel.id + ']"]').val(pathModel.encode()); |
||||||
|
$('input[name="hotspot[' + self.questionId + '][' + pathModel.id + ']"]').val(pathModel.encode()); |
||||||
|
|
||||||
|
pathModel = null; |
||||||
|
|
||||||
|
isMoving = false; |
||||||
|
}); |
||||||
|
}; |
||||||
|
AnnotationCanvasView.prototype.renderPath = function (pathModel) { |
||||||
|
var pathView = new SvgPathView(pathModel); |
||||||
|
|
||||||
|
this.el.appendChild(pathView.render().el); |
||||||
|
|
||||||
|
$('<input>') |
||||||
|
.attr({ |
||||||
|
type: 'hidden', |
||||||
|
name: 'choice[' + this.questionId + '][' + pathModel.id + ']' |
||||||
|
}) |
||||||
|
.val(pathModel.encode()) |
||||||
|
.appendTo(this.el.parentNode); |
||||||
|
|
||||||
|
$('<input>') |
||||||
|
.attr({ |
||||||
|
type: 'hidden', |
||||||
|
name: 'hotspot[' + this.questionId + '][' + pathModel.id + ']' |
||||||
|
}) |
||||||
|
.val(pathModel.encode()) |
||||||
|
.appendTo(this.el.parentNode); |
||||||
|
}; |
||||||
|
|
||||||
|
window.AnnotationQuestion = function (userSettings) { |
||||||
|
var settings = $.extend({ |
||||||
|
questionId: 0, |
||||||
|
exerciseId: 0, |
||||||
|
relPath: '/', |
||||||
|
use: 'user' |
||||||
|
}, userSettings); |
||||||
|
|
||||||
|
var xhrUrl = (settings.use == 'preview') |
||||||
|
? 'exercise/annotation_preview.php' |
||||||
|
: (settings.use == 'admin') |
||||||
|
? 'exercise/annotation_admin.php' |
||||||
|
: 'exercise/annotation_user.php'; |
||||||
|
|
||||||
|
$ |
||||||
|
.getJSON(settings.relPath + xhrUrl, { |
||||||
|
question_id: parseInt(settings.questionId), |
||||||
|
exe_id: parseInt(settings.exerciseId) |
||||||
|
}) |
||||||
|
.done(function (questionInfo) { |
||||||
|
var image = new Image(); |
||||||
|
image.onload = function () { |
||||||
|
var pathsCollection = new PathsCollection(), |
||||||
|
canvas = new AnnotationCanvasView(pathsCollection, this, settings.questionId); |
||||||
|
|
||||||
|
$('#annotation-canvas-' + settings.questionId) |
||||||
|
.css({width: this.width}) |
||||||
|
.html(canvas.render().el); |
||||||
|
|
||||||
|
$.each(questionInfo.answers.paths, function (i, pathInfo) { |
||||||
|
var pathModel = SvgPathModel.decode(pathInfo); |
||||||
|
|
||||||
|
pathsCollection.add(pathModel); |
||||||
|
}); |
||||||
|
}; |
||||||
|
image.src = questionInfo.image.path; |
||||||
|
}); |
||||||
|
}; |
||||||
|
})(window); |
||||||
Loading…
Reference in new issue