Actualización

This commit is contained in:
Xes
2025-04-10 12:49:05 +02:00
parent 4aff98e77b
commit 1cdd00920f
9151 changed files with 1800913 additions and 0 deletions

View File

@@ -0,0 +1,783 @@
/* For licensing terms, see /license.txt */
(function (window, $) {
"use strict";
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.questionId = 0;
this.changeEvents = [];
this.destroyEvents = [];
};
/**
* @param {string} key
* @param {*} value
*/
SvgElementModel.prototype.set = function (key, value) {
this.attributes[key] = value;
this.changeEvents.forEach(function (event) {
event();
});
};
SvgElementModel.prototype.get = function (key) {
return this.attributes[key];
};
SvgElementModel.prototype.destroy = function () {
this.destroyEvents.forEach(function (event) {
event();
});
};
/**
* @param {string} eventName
* @param {(SvgElementModel~changeEvents|SvgElementModel~destroyEvents)} callback
*/
SvgElementModel.prototype.on = function (eventName, callback) {
this[eventName + 'Events'].push(callback);
};
/**
* @abstract
* @static
* @param {Object} info
* @returns {SvgElementModel}
*/
SvgElementModel.decode = function (info) {
return new this();
};
/**
* @abstract
* @returns {string}
*/
SvgElementModel.prototype.encode = function () {
return "";
};
/**
* @param {Object} userAttributes
* @constructor
* @extends SvgElementModel
*/
var SvgPathModel = function (userAttributes) {
var attributes = $.extend({
color: "#FF0000",
points: []
}, userAttributes);
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 = [];
var typeProperties = [
this.get("color"),
];
this.get("points").forEach(function (point) {
pairedPoints.push(
point.join(";")
);
});
return "P;" + typeProperties.join(";") + ")(" + pairedPoints.join(")(");
};
/**
* @static
* @param {Object} pathInfo
* @returns {SvgPathModel}
*/
SvgPathModel.decode = function (pathInfo) {
pathInfo.points = pathInfo.points.map(function (point) {
return [point.x, point.y];
});
return new SvgPathModel(pathInfo);
};
/**
* @param {Object} userAttributes
* @constructor
* @extends SvgElementModel
*/
var SvgTextModel = function (userAttributes) {
var attributes = $.extend({
text: "",
x: 0,
y: 0,
color: "#FF0000",
fontSize: 20
}, userAttributes);
SvgElementModel.call(this, attributes);
};
SvgTextModel.prototype = Object.create(SvgElementModel.prototype);
SvgTextModel.prototype.encode = function () {
var typeProperties = [
this.get("color"),
this.get("fontSize"),
];
return "T;" + typeProperties.join(";") + ")(" + this.get("text") + ")(" + this.get("x") + ';' + this.get("y");
};
/**
* @static
* @param {Object} textInfo
* @returns {SvgTextModel}
*/
SvgTextModel.decode = function (textInfo) {
return new SvgTextModel(textInfo);
};
/**
* @param {SvgElementModel} model
* @constructor
*/
var SvgElementView = function (model) {
var self = this;
this.model = model;
this.model.on('change', function () {
self.render();
});
this.model.on('destroy', function () {
self.el.remove();
});
};
/**
* @param {SvgPathModel} model
* @constructor
* @extends SvgElementView
*/
var SvgPathView = function (model) {
SvgElementView.call(this, model);
this.el = document.createElementNS("http://www.w3.org/2000/svg", "path");
this.el.setAttribute("fill", "transparent");
};
SvgPathView.prototype = Object.create(SvgElementView.prototype);
SvgPathView.prototype.render = function () {
var d = "";
$.each(
this.model.get("points"),
function (i, point) {
d += (i === 0) ? "M" : " L ";
d += point[0] + " " + point[1];
}
);
this.el.setAttribute("d", d);
this.el.setAttribute('stroke', this.model.get('color'));
this.el.setAttribute("stroke-width", "3");
return this;
};
/**
* @param {SvgTextModel} model
* @constructor
* @extends SvgElementView
*/
var SvgTextView = function (model) {
SvgElementView.call(this, model);
this.el = document.createElementNS('http://www.w3.org/2000/svg', 'text');
this.el.setAttribute('stroke', 'none');
};
SvgTextView.prototype = Object.create(SvgElementView.prototype);
SvgTextView.prototype.render = function () {
this.el.setAttribute('x', this.model.get('x'));
this.el.setAttribute('y', this.model.get('y'));
this.el.setAttribute('fill', this.model.get('color'));
this.el.setAttribute('font-size', this.model.get('fontSize'));
this.el.textContent = this.model.get('text');
return this;
};
/**
* @param {SvgElementModel} model
* @constructor
*/
var ControllerView = function (model) {
var self = this;
this.model = model;
this.model.on('change', function () {
self.render();
});
this.model.on('destroy', function () {
self.el.remove();
});
var elChoice = (function () {
var input = document.createElement('input');
input.type = 'hidden';
input.name = 'choice[' + self.model.questionId + '][' + self.model.id + ']';
return input;
})();
var elHotspot = (function () {
var input = document.createElement('input');
input.type = 'hidden';
input.name = 'hotspot[' + self.model.questionId + '][' + self.model.id + ']';
return input;
})();
var elText = (function () {
var input = document.createElement('input');
input.type = 'text';
input.className = 'form-control';
input.disabled = self.model instanceof SvgPathModel;
return input;
})();
elText.addEventListener('change', function () {
commandsHistory.add(new TextElementCommand(self.model, this.value));
self.model.set('text', this.value);
})
var txtColor = (function () {
var input = document.createElement('input');
input.type = 'color';
input.style.border = '0 none';
input.style.padding = '0';
input.style.margin = '0';
input.style.width = '26px';
input.style.height = '26px';
input.style.lineHeight = '28px';
input.style.verticalAlign = 'middle';
return input;
})();
txtColor.addEventListener('change', function () {
commandsHistory.add(new ColorElementCommand(self.model, this.value));
self.model.set('color', this.value);
})
var spanAddonColor = (function () {
var span = document.createElement('span');
span.className = 'input-group-addon';
span.style.padding = '0';
return span;
})();
spanAddonColor.appendChild(txtColor);
var txtSize = (function () {
var input = document.createElement('input');
input.type = 'number';
input.step = '1';
input.min = '15';
input.max = '30';
input.style.border = '0 none';
input.style.padding = '0 0 0 4px';
input.style.margin = '0';
input.style.width = '41px';
input.style.height = '26px';
input.style.lineHeight = '28px';
input.style.verticalAlign = 'middle';
input.disabled = self.model instanceof SvgPathModel;
return input;
})();
txtSize.addEventListener('change', function () {
commandsHistory.add(new SizeElementCommand(self.model, this.value));
self.model.set('fontSize', this.value);
})
var spanAddonSize = (function () {
var span = document.createElement('span');
span.className = 'input-group-addon';
span.style.padding = '0';
return span;
})();
spanAddonSize.appendChild(txtSize);
// var btnRemove = (function () {
// var button = document.createElement('button');
// button.type = 'button';
// button.className = 'btn btn-default';
// button.innerHTML = '<span class="fa fa-trash text-danger" aria-hidden="true"></span>';
//
// return button;
// })();
// btnRemove.addEventListener('click', function (e) {
// e.preventDefault();
// e.stopPropagation();
//
// self.model.destroy();
// });
//
// var spanGroupBtn = (function () {
// var span = document.createElement('span');
// span.className = 'input-group-btn';
//
// return span;
// })();
// spanGroupBtn.appendChild(btnRemove);
this.el = (function () {
var div = document.createElement('div');
div.className = 'input-group input-group-sm';
div.style.marginBottom = '10px';
return div;
})();
this.el.appendChild(elText);
this.el.appendChild(elHotspot);
this.el.appendChild(elChoice);
this.el.appendChild(spanAddonColor);
this.el.appendChild(spanAddonSize);
// this.el.appendChild(spanGroupBtn);
this.render = function () {
elChoice.value = this.model.encode();
elHotspot.value = this.model.encode();
elText.value = self.model instanceof SvgTextModel ? self.model.get('text') : '——————————';
txtColor.value = this.model.get('color');
txtSize.value = this.model.get('fontSize');
return this;
}
};
/**
* @constructor
*/
var ElementsCollection = function () {
/**
* @type {SvgElementModel[]}
*/
this.models = [];
this.addEvent = null;
var lastId = 0;
/**
* @param {SvgElementModel} pathModel
*/
this.add = function (pathModel) {
pathModel.id = ++lastId;
this.models.push(pathModel);
if (this.addEvent) {
this.addEvent(pathModel);
}
};
/**
* @param {number} index
* @returns {SvgElementModel}
*/
this.get = function (index) {
return this.models[index];
};
this.reset = function () {
this.models.forEach(function (model) {
model.destroy();
})
this.models = [];
};
/**
* @param {ElementsCollection~addEvent} callback
*/
this.onAdd = function (callback) {
this.addEvent = callback;
};
};
/**
* @param {ElementsCollection} elementsCollection
* @param {Image} image
* @param {number} questionId
* @constructor
*/
var AnnotationCanvasView = function (elementsCollection, image, questionId) {
var self = this;
this.questionId = questionId;
this.image = image;
var svgImage = (function () {
var image = document.createElementNS('http://www.w3.org/2000/svg', 'image');
image.setAttributeNS('http://www.w3.org/1999/xlink', 'href', self.image.src);
image.setAttribute('width', self.image.width);
image.setAttribute('height', self.image.height);
return image;
})();
this.el = (function () {
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('version', '1.1');
svg.setAttribute('viewBox', '0 0 ' + self.image.width + ' ' + self.image.height);
svg.setAttribute('width', self.image.width);
svg.setAttribute('height', self.image.height);
return svg;
})();
this.el.appendChild(svgImage);
this.elementsCollection = elementsCollection;
this.elementsCollection.onAdd(function (pathModel) {
var svgElementView = null;
if (pathModel instanceof SvgPathModel) {
svgElementView = new SvgPathView(pathModel);
} else if (pathModel instanceof SvgTextModel) {
svgElementView = new SvgTextView(pathModel);
} else {
return;
}
self.el.appendChild(svgElementView.render().el);
var controllerView = new ControllerView(pathModel);
$('#annotation-toolbar-' + self.questionId).append(controllerView.render().el);
$(controllerView.el).children('input').eq(0).focus();
});
var $rdbOptions = null;
var $btnReset = null;
var $btnUndo = null;
var $btnRedo = null;
this.render = function () {
$rdbOptions = $('[name="' + this.questionId + '-options"]');
$btnReset = $('#btn-reset-' + this.questionId);
$btnUndo = $('#btn-undo-' + this.questionId);
$btnRedo = $('#btn-redo-' + this.questionId);
setEvents();
return this;
};
function setEvents() {
var isMoving = false,
elementModel = null;
$(self.el)
.on('dragstart', function (e) {
e.preventDefault();
})
.on('click', function (e) {
e.preventDefault();
if ("1" !== $rdbOptions.filter(':checked').val()) {
return;
}
var point = getPointOnImage(self.el, e.clientX, e.clientY);
elementModel = new SvgTextModel({x: point.x, y: point.y, text: ''});
elementModel.questionId = self.questionId;
commandsHistory.add(new AddElementCommand(self.elementsCollection, elementModel));
self.elementsCollection.add(elementModel);
elementModel = null;
isMoving = false;
})
.on('mousedown', function (e) {
e.preventDefault();
var point = getPointOnImage(self.el, e.clientX, e.clientY);
if (isMoving || "0" !== $rdbOptions.filter(':checked').val() || elementModel) {
return;
}
elementModel = new SvgPathModel({points: [[point.x, point.y]]});
elementModel.questionId = self.questionId;
self.elementsCollection.add(elementModel);
isMoving = true;
})
.on('mousemove', function (e) {
e.preventDefault();
if (!isMoving || "0" !== $rdbOptions.filter(':checked').val() || !elementModel) {
return;
}
var point = getPointOnImage(self.el, e.clientX, e.clientY);
elementModel.addPoint(point.x, point.y);
})
.on('mouseup', function (e) {
e.preventDefault();
if (!isMoving || "0" !== $rdbOptions.filter(':checked').val() || !elementModel) {
return;
}
commandsHistory.add(new AddElementCommand(self.elementsCollection, elementModel));
elementModel = null;
isMoving = false;
});
$btnReset.on('click', function (e) {
e.preventDefault();
commandsHistory.add(new ResetCommand(self.elementsCollection));
self.elementsCollection.reset();
});
$btnUndo.on('click', function () {
commandsHistory.undo();
});
$btnRedo.on('click', function () {
commandsHistory.redo();
});
}
};
/**
* @constructor
* @abstract
*/
function Command() {}
/**
* @abstract
*/
Command.prototype.before = function () {
throw new Error('Implement');
}
/**
* @abstract
*/
Command.prototype.after = function () {
throw new Error('Implement');
}
/**
* @param {ElementsCollection} collection
* @param {SvgElementModel} model
* @constructor
*/
function AddElementCommand(collection, model) {
Command.call(this);
this.collection = collection;
this.model = model;
}
AddElementCommand.prototype = Object.create(Command.prototype);
AddElementCommand.prototype.after = function () {
this.collection.add(this.model);
};
AddElementCommand.prototype.before = function () {
this.model.destroy();
};
/**
* @param {SvgElementModel} model
* @param {string} attribute
* @param {*} newValue
* @constructor
* @abstract
* @extends Command
*/
function ElementCommand(model, attribute, newValue) {
Command.call(this);
this.model = model;
this.attribute = attribute;
this.oldValue = this.model.get(this.attribute);
this.newValue = newValue;
}
ElementCommand.prototype = Object.create(Command.prototype);
ElementCommand.prototype.after = function () {
this.model.set(this.attribute, this.newValue);
};
ElementCommand.prototype.before = function () {
this.model.set(this.attribute, this.oldValue);
};
/**
* @param {SvgElementModel} model
* @param {*} newValue
* @constructor
* @extends ElementCommand
*/
function TextElementCommand(model, newValue) {
ElementCommand.call(this, model, 'text', newValue);
}
TextElementCommand.prototype = Object.create(ElementCommand.prototype);
/**
* @param {SvgElementModel} model
* @param {*} newValue
* @constructor
* @extends ElementCommand
*/
function ColorElementCommand(model, newValue) {
ElementCommand.call(this, model, 'color', newValue);
}
ColorElementCommand.prototype = Object.create(ElementCommand.prototype);
/**
* @param {SvgElementModel} model
* @param {*} newValue
* @constructor
* @extends ElementCommand
*/
function SizeElementCommand(model, newValue) {
ElementCommand.call(this, model, 'fontSize', newValue);
}
SizeElementCommand.prototype = Object.create(ElementCommand.prototype);
/**
*
* @param {ElementsCollection} collection
* @constructor
* @extends Command
*/
function ResetCommand(collection) {
Command.call(this);
this.collection = collection;
this.oldModels = collection.models;
}
ResetCommand.prototype = Object.create(Command.prototype);
ResetCommand.prototype.after = function () {
this.collection.reset();
};
ResetCommand.prototype.before = function () {
var self = this;
this.oldModels.forEach(function (model) {
self.collection.add(model);
});
};
function CommandHistory() {
var index = -1;
/**
* @type {Command[]}
*/
var commands = [];
/**
* @param {Command} command
*/
this.add = function (command) {
if (index > -1) {
commands = commands.slice(0, index + 1);
} else {
commands = [];
}
commands.push(command);
++index;
}
this.undo = function () {
(commands, index);
if (-1 === index) {
return;
}
var command = commands[index];
command.before();
--index;
};
this.redo = function () {
if (index + 1 === commands.length) {
return;
}
++index;
var command = commands[index];
command.after();
};
};
var commandsHistory = new CommandHistory();
window.AnnotationQuestion = function (userSettings) {
$(function () {
var settings = $.extend(
{
questionId: 0,
exerciseId: 0,
relPath: '/'
},
userSettings
),
xhrUrl = 'exercise/annotation_user.php?' + _p.web_cid_query,
$container = $('#annotation-canvas-' + settings.questionId);
$
.getJSON(settings.relPath + xhrUrl, {
question_id: parseInt(settings.questionId),
exe_id: parseInt(settings.exerciseId),
course_id: parseInt(settings.courseId)
})
.done(function (questionInfo) {
var image = new Image();
image.onload = function () {
var elementsCollection = new ElementsCollection(),
canvas = new AnnotationCanvasView(elementsCollection, this, parseInt(settings.questionId));
$container.html(canvas.render().el);
/** @namespace questionInfo.answers.paths */
$.each(questionInfo.answers.paths, function (i, pathInfo) {
var pathModel = SvgPathModel.decode(pathInfo);
pathModel.questionId = settings.questionId;
elementsCollection.add(pathModel);
});
/** @namespace questionInfo.answers.texts */
$(questionInfo.answers.texts).each(function (i, textInfo) {
var textModel = SvgTextModel.decode(textInfo);
textModel.questionId = settings.questionId;
elementsCollection.add(textModel);
});
};
image.src = questionInfo.image.path;
});
});
};
})(window, window.jQuery);