|
|
|
|
@ -22,22 +22,28 @@ |
|
|
|
|
var SvgElementModel = function (attributes) { |
|
|
|
|
this.attributes = attributes; |
|
|
|
|
this.id = 0; |
|
|
|
|
this.name = ""; |
|
|
|
|
this.questionId = 0; |
|
|
|
|
|
|
|
|
|
this.changeEvent = null; |
|
|
|
|
this.changeEvents = []; |
|
|
|
|
this.destroyEvents = []; |
|
|
|
|
}; |
|
|
|
|
SvgElementModel.prototype.set = function (key, value) { |
|
|
|
|
this.attributes[key] = value; |
|
|
|
|
|
|
|
|
|
if (this.changeEvent) { |
|
|
|
|
this.changeEvent(this); |
|
|
|
|
} |
|
|
|
|
this.changeEvents.forEach(function (event) { |
|
|
|
|
event(); |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
SvgElementModel.prototype.get = function (key) { |
|
|
|
|
return this.attributes[key]; |
|
|
|
|
}; |
|
|
|
|
SvgElementModel.prototype.onChange = function (callback) { |
|
|
|
|
this.changeEvent = callback; |
|
|
|
|
SvgElementModel.prototype.destroy = function () { |
|
|
|
|
this.destroyEvents.forEach(function (event) { |
|
|
|
|
event(); |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
SvgElementModel.prototype.on = function (event, callback) { |
|
|
|
|
this[event + 'Events'].push(callback); |
|
|
|
|
}; |
|
|
|
|
SvgElementModel.decode = function () { |
|
|
|
|
return new this(); |
|
|
|
|
@ -46,7 +52,12 @@ |
|
|
|
|
return ""; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
var SvgPathModel = function (attributes) { |
|
|
|
|
var SvgPathModel = function (userAttributes) { |
|
|
|
|
var attributes = $.extend({ |
|
|
|
|
color: "#FF0000", |
|
|
|
|
points: [] |
|
|
|
|
}, userAttributes); |
|
|
|
|
|
|
|
|
|
SvgElementModel.call(this, attributes); |
|
|
|
|
}; |
|
|
|
|
SvgPathModel.prototype = Object.create(SvgElementModel.prototype); |
|
|
|
|
@ -61,6 +72,9 @@ |
|
|
|
|
}; |
|
|
|
|
SvgPathModel.prototype.encode = function () { |
|
|
|
|
var pairedPoints = []; |
|
|
|
|
var typeProperties = [ |
|
|
|
|
this.get("color"), |
|
|
|
|
]; |
|
|
|
|
|
|
|
|
|
this.get("points").forEach(function (point) { |
|
|
|
|
pairedPoints.push( |
|
|
|
|
@ -68,54 +82,64 @@ |
|
|
|
|
); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
return "P)(" + pairedPoints.join(")("); |
|
|
|
|
return "P;" + typeProperties.join(";") + ")(" + pairedPoints.join(")("); |
|
|
|
|
}; |
|
|
|
|
SvgPathModel.decode = function (pathInfo) { |
|
|
|
|
var points = []; |
|
|
|
|
|
|
|
|
|
$(pathInfo).each(function (i, point) { |
|
|
|
|
points.push([point.x, point.y]); |
|
|
|
|
pathInfo.points = pathInfo.points.map(function (point) { |
|
|
|
|
return [point.x, point.y]; |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
return new SvgPathModel({points: points}); |
|
|
|
|
return new SvgPathModel(pathInfo); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
var TextModel = function (userAttributes) { |
|
|
|
|
var SvgTextModel = function (userAttributes) { |
|
|
|
|
var attributes = $.extend({ |
|
|
|
|
text: "", |
|
|
|
|
x: 0, |
|
|
|
|
y: 0, |
|
|
|
|
color: "red", |
|
|
|
|
color: "#FF0000", |
|
|
|
|
fontSize: 20 |
|
|
|
|
}, userAttributes); |
|
|
|
|
|
|
|
|
|
SvgElementModel.call(this, attributes); |
|
|
|
|
}; |
|
|
|
|
TextModel.prototype = Object.create(SvgElementModel.prototype); |
|
|
|
|
TextModel.prototype.encode = function () { |
|
|
|
|
return "T)(" + this.get("text") + ")(" + this.get("x") + ";" + this.get("y"); |
|
|
|
|
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"); |
|
|
|
|
}; |
|
|
|
|
TextModel.decode = function (textInfo) { |
|
|
|
|
return new TextModel({ |
|
|
|
|
text: textInfo.text, |
|
|
|
|
x: textInfo.x, |
|
|
|
|
y: textInfo.y |
|
|
|
|
}); |
|
|
|
|
SvgTextModel.decode = function (textInfo) { |
|
|
|
|
return new SvgTextModel(textInfo); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
var SvgPathView = function (model) { |
|
|
|
|
var SvgElementView = function (model) { |
|
|
|
|
var self = this; |
|
|
|
|
|
|
|
|
|
this.model = model; |
|
|
|
|
this.model.onChange(function () { |
|
|
|
|
this.model.on('change', function () { |
|
|
|
|
self.render(); |
|
|
|
|
}); |
|
|
|
|
this.model.on('destroy', function () { |
|
|
|
|
self.el.remove(); |
|
|
|
|
self.model = null; |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* @param {SvgPathModel} model |
|
|
|
|
* @constructor |
|
|
|
|
*/ |
|
|
|
|
var SvgPathView = function (model) { |
|
|
|
|
SvgElementView.call(this, model); |
|
|
|
|
|
|
|
|
|
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 = Object.create(SvgElementView.prototype); |
|
|
|
|
SvgPathView.prototype.render = function () { |
|
|
|
|
var d = ""; |
|
|
|
|
|
|
|
|
|
@ -128,39 +152,188 @@ |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
this.el.setAttribute("d", d); |
|
|
|
|
this.el.setAttribute('stroke', this.model.get('color')); |
|
|
|
|
this.el.setAttribute("stroke-width", "3"); |
|
|
|
|
|
|
|
|
|
return this; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
var TextView = function (model) { |
|
|
|
|
var self = this; |
|
|
|
|
|
|
|
|
|
this.model = model; |
|
|
|
|
this.model.onChange(function () { |
|
|
|
|
self.render(); |
|
|
|
|
}); |
|
|
|
|
/** |
|
|
|
|
* @param {SvgTextModel} model |
|
|
|
|
* @constructor |
|
|
|
|
*/ |
|
|
|
|
var SvgTextView = function (model) { |
|
|
|
|
SvgElementView.call(this, model); |
|
|
|
|
|
|
|
|
|
this.el = document.createElementNS('http://www.w3.org/2000/svg', 'text'); |
|
|
|
|
this.el.setAttribute('fill', this.model.get('color')); |
|
|
|
|
this.el.setAttribute('font-size', this.model.get('fontSize')); |
|
|
|
|
this.el.setAttribute('stroke', 'none'); |
|
|
|
|
}; |
|
|
|
|
TextView.prototype.render = function () { |
|
|
|
|
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(); |
|
|
|
|
self.model = null; |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
input.value = self.model instanceof SvgTextModel ? self.model.get('text') : '——————————'; |
|
|
|
|
|
|
|
|
|
return input; |
|
|
|
|
})(); |
|
|
|
|
elText.addEventListener('change', function () { |
|
|
|
|
self.model.set('text', this.value); |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
var txtColor = (function () { |
|
|
|
|
var input = document.createElement('input'); |
|
|
|
|
input.type = 'color'; |
|
|
|
|
input.value = self.model.get('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 () { |
|
|
|
|
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.value = self.model.get('fontSize'); |
|
|
|
|
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 () { |
|
|
|
|
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(); |
|
|
|
|
|
|
|
|
|
return this; |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
var ElementsCollection = function () { |
|
|
|
|
this.models = []; |
|
|
|
|
this.length = 0; |
|
|
|
|
this.lastId = 0; |
|
|
|
|
this.addEvent = null; |
|
|
|
|
this.resetEvent = null; |
|
|
|
|
}; |
|
|
|
|
ElementsCollection.prototype.add = function (pathModel) { |
|
|
|
|
pathModel.id = ++this.length; |
|
|
|
|
pathModel.id = ++this.lastId; |
|
|
|
|
|
|
|
|
|
this.models.push(pathModel); |
|
|
|
|
|
|
|
|
|
@ -172,18 +345,15 @@ |
|
|
|
|
return this.models[index]; |
|
|
|
|
}; |
|
|
|
|
ElementsCollection.prototype.reset = function () { |
|
|
|
|
if (this.resetEvent) { |
|
|
|
|
this.resetEvent(); |
|
|
|
|
} |
|
|
|
|
this.models.forEach(function (model) { |
|
|
|
|
model.destroy(); |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
this.models = []; |
|
|
|
|
} |
|
|
|
|
ElementsCollection.prototype.onAdd = function (callback) { |
|
|
|
|
this.addEvent = callback; |
|
|
|
|
}; |
|
|
|
|
ElementsCollection.prototype.onReset = function (callback) { |
|
|
|
|
this.resetEvent = callback; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var AnnotationCanvasView = function (elementsCollection, image, questionId) { |
|
|
|
|
var self = this; |
|
|
|
|
@ -208,15 +378,23 @@ |
|
|
|
|
|
|
|
|
|
this.elementsCollection = elementsCollection; |
|
|
|
|
this.elementsCollection.onAdd(function (pathModel) { |
|
|
|
|
self.renderElement(pathModel); |
|
|
|
|
}); |
|
|
|
|
this.elementsCollection.onReset(function () { |
|
|
|
|
$(self.el.parentNode).children('input').remove(); |
|
|
|
|
var svgElementView = null; |
|
|
|
|
|
|
|
|
|
if (pathModel instanceof SvgPathModel) { |
|
|
|
|
svgElementView = new SvgPathView(pathModel); |
|
|
|
|
} else if (pathModel instanceof SvgTextModel) { |
|
|
|
|
svgElementView = new SvgTextView(pathModel); |
|
|
|
|
} else { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
self.$el.children('text, path').remove(); |
|
|
|
|
self.el.appendChild(svgElementView.render().el); |
|
|
|
|
|
|
|
|
|
$('#annotation-toolbar-' + self.questionId + ' ul').empty(); |
|
|
|
|
}) |
|
|
|
|
var controllerView = new ControllerView(pathModel); |
|
|
|
|
|
|
|
|
|
$('#annotation-toolbar-' + self.questionId).append(controllerView.render().el); |
|
|
|
|
$(controllerView.el).children('input').eq(0).focus(); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
this.$rdbOptions = null; |
|
|
|
|
this.$btnReset = null; |
|
|
|
|
@ -247,7 +425,8 @@ |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var point = getPointOnImage(self.el, e.clientX, e.clientY); |
|
|
|
|
elementModel = new TextModel({x: point.x, y: point.y, text: ''}); |
|
|
|
|
elementModel = new SvgTextModel({x: point.x, y: point.y, text: ''}); |
|
|
|
|
elementModel.questionId = self.questionId; |
|
|
|
|
self.elementsCollection.add(elementModel); |
|
|
|
|
elementModel = null; |
|
|
|
|
isMoving = false; |
|
|
|
|
@ -261,6 +440,7 @@ |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
elementModel = new SvgPathModel({points: [[point.x, point.y]]}); |
|
|
|
|
elementModel.questionId = self.questionId; |
|
|
|
|
self.elementsCollection.add(elementModel); |
|
|
|
|
isMoving = true; |
|
|
|
|
}) |
|
|
|
|
@ -291,70 +471,6 @@ |
|
|
|
|
self.elementsCollection.reset(); |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
AnnotationCanvasView.prototype.renderElement = function (elementModel) { |
|
|
|
|
var elementView = null, |
|
|
|
|
self = this; |
|
|
|
|
|
|
|
|
|
if (elementModel instanceof SvgPathModel) { |
|
|
|
|
elementView = new SvgPathView(elementModel); |
|
|
|
|
} else if (elementModel instanceof TextModel) { |
|
|
|
|
elementView = new TextView(elementModel); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!elementView) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$('<input>') |
|
|
|
|
.attr({ |
|
|
|
|
type: 'hidden', |
|
|
|
|
name: 'choice[' + this.questionId + '][' + elementModel.id + ']' |
|
|
|
|
}) |
|
|
|
|
.val(elementModel.encode()) |
|
|
|
|
.appendTo(this.el.parentNode); |
|
|
|
|
|
|
|
|
|
$('<input>') |
|
|
|
|
.attr({ |
|
|
|
|
type: 'hidden', |
|
|
|
|
name: 'hotspot[' + this.questionId + '][' + elementModel.id + ']' |
|
|
|
|
}) |
|
|
|
|
.val(elementModel.encode()) |
|
|
|
|
.appendTo(this.el.parentNode); |
|
|
|
|
|
|
|
|
|
this.el.appendChild(elementView.render().el); |
|
|
|
|
|
|
|
|
|
elementModel.onChange(function () { |
|
|
|
|
elementView.render(); |
|
|
|
|
|
|
|
|
|
$('input[name="choice[' + self.questionId + '][' + elementModel.id + ']"]').val(elementModel.encode()); |
|
|
|
|
$('input[name="hotspot[' + self.questionId + '][' + elementModel.id + ']"]').val(elementModel.encode()); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
if (elementModel instanceof TextModel) { |
|
|
|
|
$('<input>') |
|
|
|
|
.attr({ |
|
|
|
|
type: 'text', |
|
|
|
|
name: 'text[' + this.questionId + '][' + elementModel.id + ']' |
|
|
|
|
}) |
|
|
|
|
.addClass('form-control input-sm') |
|
|
|
|
.on('change', function (e) { |
|
|
|
|
elementModel.set('text', this.value); |
|
|
|
|
|
|
|
|
|
e.preventDefault(); |
|
|
|
|
}) |
|
|
|
|
.on('keypress', function (e) { |
|
|
|
|
if (13 === e.keyCode) { |
|
|
|
|
e.preventDefault(); |
|
|
|
|
|
|
|
|
|
elementModel.set('text', this.value); |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
.val(elementModel.get('text')) |
|
|
|
|
.appendTo('#annotation-toolbar-' + this.questionId + ' ul') |
|
|
|
|
.wrap('<li class="form-group"></li>') |
|
|
|
|
.focus(); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
window.AnnotationQuestion = function (userSettings) { |
|
|
|
|
$(function () { |
|
|
|
|
@ -386,12 +502,14 @@ |
|
|
|
|
/** @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 = TextModel.decode(textInfo); |
|
|
|
|
var textModel = SvgTextModel.decode(textInfo); |
|
|
|
|
textModel.questionId = settings.questionId; |
|
|
|
|
elementsCollection.add(textModel); |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
|