Commit 602d0212 by Tim Krones

WYSIWYG functionality for Studio, Part 2:

- Add support for setting names and labels for vectors.
- Add support for defining expected result.

Add support for setting name and label of a vector.

Add WYSIWYG functionality for defining expected result.
parent 224391aa
......@@ -86,7 +86,7 @@
background-color: #f7f7f7;
}
.vectordraw_block .menu .vector-properties h3 {
.vectordraw_block h3 {
font-size: 16px;
margin: 0 0 5px;
}
......
.vectordraw_edit_block,
.vectordraw_edit_block .jxgboard {
display: block;
}
.vectordraw_edit_block {
border-top: 1px solid #e5e5e5;
margin-left: 20px;
......@@ -15,12 +20,37 @@
}
.vectordraw_edit_block .jxgboard {
float: none;
float: left;
margin-bottom: 1em;
}
/* Menu */
.vectordraw_edit_block .menu .controls .result-mode {
float: right;
}
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-name,
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-tail,
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-angle {
padding-right: 0px;
}
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-select,
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-label,
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-length {
padding-right: 5px;
}
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row select,
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row input {
height: 2em;
}
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row select {
padding: 0px;
}
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-remove {
vertical-align: bottom;
}
......@@ -28,3 +58,44 @@
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-remove button {
float: right;
}
.vectordraw_edit_block h3 {
margin-top: 5px;
margin-bottom: 5px;
}
.vectordraw_edit_block .checks {
display: none;
width: 220px;
float: right;
border-top: 2px solid #1f628d;
border-right: 2px solid #1f628d;
border-bottom: 2px solid #1f628d;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
padding-left: 10px;
background-color: #f7f7f7;
overflow: auto;
}
.vectordraw_edit_block .checks .check .row {
height: 2em;
}
.vectordraw_edit_block .checks .check span,
.vectordraw_edit_block .checks .check input[type="text"] {
height: 1.5em;
margin-right: 12px;
}
.vectordraw_edit_block .checks .check input[type="checkbox"] {
height: 1.2em;
width: 20%;
min-width: 0px;
margin-top: 2px;
}
.vectordraw_edit_block .checks .check input {
float: right;
vertical-align: middle;
}
......@@ -91,6 +91,10 @@ function StudioEditableXBlockMixin(runtime, element) {
return {
getContents: function(fieldName) {
return _.findWhere(fields, {name: fieldName}).val();
},
save: function(data) {
var values = {};
var notSet = []; // List of field names that should be set to default values
......@@ -107,9 +111,12 @@ function StudioEditableXBlockMixin(runtime, element) {
field.removeEditor();
}
}
// If WYSIWYG editor was used, prefer its data over value of "vectors" field:
if (data.vectors) {
// If WYSIWYG editor was used,
// prefer its data over values of "Vectors" and "Expected result" fields:
if (!_.isEmpty(data)) {
values.vectors = JSON.stringify(data.vectors, undefined, 4);
values.expected_result_positions = data.expected_result_positions;
values.expected_result = JSON.stringify(data.expected_result, undefined, 4);
}
studio_submit({values: values, defaults: notSet});
......
......@@ -6,22 +6,42 @@ function VectorDrawXBlockEdit(runtime, element, init_args) {
this.dragged_vector = null;
this.drawMode = false;
this.wasUsed = false;
this.resultMode = false;
this.settings = settings;
this.numberOfVectors = this.settings.vectors.length;
this.element = $('#' + element_id, element);
this.element.on('click', '.add-vector', this.onAddVector.bind(this));
this.element.on('click', '.controls .add-vector', this.onAddVector.bind(this));
this.element.on('click', '.controls .result-mode', this.onEditResult.bind(this));
this.element.on('change', '.menu .element-list-edit', this.onEditStart.bind(this));
this.element.on('click', '.menu .vector-prop-update', this.onEditSubmit.bind(this));
this.element.on('click', '.vector-remove', this.onRemoveVector.bind(this));
this.element.on('click', '.menu .update', this.onEditSubmit.bind(this));
this.element.on('click', '.menu .remove', this.onRemoveVector.bind(this));
// Prevents default image drag and drop actions in some browsers.
this.element.on('mousedown', '.jxgboard image', function(evt) { evt.preventDefault(); });
this.discardStaleData();
this.render();
};
VectorDraw.prototype.discardStaleData = function() {
// If author removed or renamed vectors via the "Vectors" field
// (without making necessary adjustments in "Expected results" field)
// discard stale information about expected positions and checks
var vectorData = JSON.parse(fieldEditor.getContents('vectors')),
vectorNames = _.pluck(vectorData, 'name');
_.each(_.keys(this.settings.expected_result_positions), function(key) {
if (!_.contains(vectorNames, key)) {
delete this.settings.expected_result_positions[key];
}
}, this);
_.each(_.keys(this.settings.expected_result), function(key) {
if (!_.contains(vectorNames, key)) {
delete this.settings.expected_result[key];
}
}, this);
};
VectorDraw.prototype.render = function() {
$('.vector-prop-slope', this.element).hide();
// Assign the jxgboard element a random unique ID,
// because JXG.JSXGraph.initBoard needs it.
this.element.find('.jxgboard').prop('id', _.uniqueId('jxgboard'));
......@@ -218,10 +238,56 @@ function VectorDrawXBlockEdit(runtime, element, init_args) {
var idx = _.indexOf(this.settings.vectors, vectorSettings),
editOption = this.getEditMenuOption("vector", idx);
editOption.remove();
// 4. Reset input fields for vector properties to default values
// 4. Discard information about expected position
var expectedPositions = this.settings.expected_result_positions;
if (expectedPositions[vectorName]) {
delete expectedPositions[vectorName];
this.settings.expected_result_positions = expectedPositions;
}
// 5. Discard information about expected result
var expectedResults = this.settings.expected_result;
if (expectedResults[vectorName]) {
delete expectedResults[vectorName];
this.settings.expected_result = expectedResults;
}
// 6. Reset input fields for vector properties to default values
this.resetVectorProperties();
};
VectorDraw.prototype.onEditResult = function(evt) {
// Switch to result mode
this.resultMode = true;
// Save vector positions
this.settings.vectors = this.getState(); // Discards vectors that were removed from board
// Update vector positions using positions from expected result
var expectedResultPositions = this.settings.expected_result_positions;
if (!_.isEmpty(expectedResultPositions)) {
// Loop over vectors and update their positions based on expected result positions
_.each(this.settings.vectors, function(vec) {
var vectorName = vec.name,
resultPosition = expectedResultPositions[vectorName];
if (resultPosition) {
var resultTail = resultPosition.tail,
resultTip = resultPosition.tip,
boardObject = this.board.elementsByName[vectorName];
boardObject.point1.setPosition(JXG.COORDS_BY_USER, resultTail);
boardObject.point2.setPosition(JXG.COORDS_BY_USER, resultTip);
}
}, this);
this.board.update();
}
// Hide or disable operations that are specific to defining initial state
$(evt.currentTarget).prop('disabled', true);
$('.add-vector', element).css('visibility', 'hidden');
$('.vector-prop-name input', element).prop('disabled', true);
$('.vector-prop-label input', element).prop('disabled', true);
$('.vector-remove button').hide();
// Reset vector properties to ensure a clean slate
this.resetVectorProperties();
// Show controls for opting in and out of checks
$('.checks', element).show();
};
VectorDraw.prototype.resetVectorProperties = function(vector) {
// Select default value
$('.menu .element-list-edit option[value="-"]', element).attr('selected', true);
......@@ -269,13 +335,14 @@ function VectorDrawXBlockEdit(runtime, element, init_args) {
if (angle < 0) {
angle += 360;
}
var slope = (y2-y1)/(x2-x1);
// Update menu for selecting vector to edit
this.element.find('.menu .element-list-edit option').attr('selected', false);
var idx = _.indexOf(this.settings.vectors, vec_settings),
editOption = this.getEditMenuOption("vector", idx);
editOption.attr('selected', true);
// Update properties
$('.vector-prop-name input', this.element).val(vector.name);
$('.vector-prop-label input', this.element).val(vec_settings.style.label || '-');
$('.vector-prop-angle input', this.element).val(angle.toFixed(2));
if (vector.elType !== "line") {
var tailInput = x1.toFixed(2) + ", " + y1.toFixed(2);
......@@ -286,14 +353,73 @@ function VectorDrawXBlockEdit(runtime, element, init_args) {
$('.vector-prop-tail input', this.element).val(tailInput);
$('.vector-prop-length', this.element).show();
$('.vector-prop-length input', this.element).val(lengthInput);
$('.vector-prop-slope', this.element).hide();
}
else {
$('.vector-prop-length', this.element).hide();
if (this.settings.show_slope_for_lines) {
$('.vector-prop-slope', this.element).show();
$('.vector-prop-slope input', this.element).val(slope.toFixed(2));
}
};
VectorDraw.prototype.updateChecks = function(vector) {
var expectedResult = this.settings.expected_result[vector.name] || {};
var checks = [
'tail', 'tail_x', 'tail_y', 'tip', 'tip_x', 'tip_y', 'coords', 'length', 'angle'
];
_.each(checks, function(check) {
var checkElement = $('#check-' + check, element);
// Update checkbox
if (expectedResult[check]) {
checkElement.find('input[type="checkbox"]').prop('checked', true);
} else {
checkElement.find('input[type="checkbox"]').prop('checked', false);
}
// Update tolerance
var tolerance = expectedResult[check + '_tolerance'];
if (tolerance) {
checkElement.find('input[type="text"]').val(tolerance.toFixed(1));
} else {
var defaultTolerance = check === 'angle' ? 2.0 : 1.0;
checkElement.find('input[type="text"]').val(defaultTolerance.toFixed(1));
}
});
};
VectorDraw.prototype.saveExpectedPosition = function(vectorName, coords, length, angle) {
var expectedPosition = {
coords: coords,
tail: coords[0],
tip: coords[1],
tail_x: coords[0][0],
tail_y: coords[0][1],
tip_x: coords[1][0],
tip_y: coords[1][1],
length: length,
angle: angle
};
this.settings.expected_result_positions[vectorName] = expectedPosition;
};
VectorDraw.prototype.saveChecks = function(vectorName) {
var expectedResult = {};
var checks = [
'tail', 'tail_x', 'tail_y', 'tip', 'tip_x', 'tip_y', 'coords', 'length', 'angle'
];
_.each(checks, function(check) {
var checkElement = $('#check-' + check, element);
if (checkElement.find('input[type="checkbox"]').prop('checked')) {
// Insert (or update) check: Need current position of selected vector
expectedResult[check] = this.settings.expected_result_positions[vectorName][check];
// Insert (or update) tolerance
var tolerance = checkElement.find('input[type="text"]').val();
expectedResult[check + '_tolerance'] = parseFloat(tolerance);
}
}, this);
if (_.isEmpty(expectedResult)) { // If author doesn't select any checks,
// assume they also want to skip the presence check
// (which the grader will perform automatically
// for each vector that has an entry in expected_result)
delete this.settings.expected_result[vectorName];
} else {
this.settings.expected_result[vectorName] = expectedResult;
}
};
......@@ -357,6 +483,9 @@ function VectorDrawXBlockEdit(runtime, element, init_args) {
var coords = this.getMouseCoords(evt);
var targetObjects = this.objectsUnderMouse(coords);
if (!targetObjects || _.all(targetObjects, this.canCreateVectorOnTopOf.bind(this))) {
if (this.resultMode) {
return;
}
// Add vector to board
var point_coords = [coords.usrCoords[1], coords.usrCoords[2]];
var defaultVector = this.getDefaultVector([point_coords, point_coords]);
......@@ -364,6 +493,7 @@ function VectorDrawXBlockEdit(runtime, element, init_args) {
var lastIndex = this.numberOfVectors - 1;
this.drawMode = true;
this.dragged_vector = this.renderVector(lastIndex);
this.addEditMenuOption(defaultVector.name, lastIndex);
}
else {
// Move existing vector around
......@@ -373,6 +503,9 @@ function VectorDrawXBlockEdit(runtime, element, init_args) {
this.dragged_vector = this.getVectorForObject(vectorPoint);
this.dragged_vector.point1.setProperty({fixed: false});
this.updateVectorProperties(this.dragged_vector);
if (this.resultMode) {
this.updateChecks(this.dragged_vector);
}
}
}
};
......@@ -402,26 +535,66 @@ function VectorDrawXBlockEdit(runtime, element, init_args) {
var vectorName = $(evt.currentTarget).find('option:selected').data('vector-name');
var vectorObject = this.board.elementsByName[vectorName];
this.updateVectorProperties(vectorObject);
if (this.resultMode) {
this.updateChecks(vectorObject);
}
};
VectorDraw.prototype.onEditSubmit = function(evt) {
if (!this.wasUsed) {
this.wasUsed = true;
}
// Get vector that is currently "selected"
var vectorName = $('.element-list-edit', element).find('option:selected').data('vector-name');
// Get name of vector that is currently "selected"
var vectorName = "" + $('.element-list-edit', element).find('option:selected').data('vector-name');
// Get values from input fields
var newTail = $('.vector-prop-tail input', element).val(),
var newName = $('.vector-prop-name input', element).val(),
newLabel = $('.vector-prop-label input', element).val(),
newTail = $('.vector-prop-tail input', element).val(),
newLength = $('.vector-prop-length input', element).val(),
newAngle = $('.vector-prop-angle input', element).val();
// Process values
newName = $.trim(newName);
newLabel = $.trim(newLabel);
newTail = _.map(newTail.split(/ *, */), function(coord) {
return parseFloat(coord);
});
newLength = parseFloat(newLength);
newAngle = parseFloat(newAngle);
// Validate and update values
var vectorSettings = this.getVectorSettingsByName(vectorName),
editOption = $('.menu .element-list-edit option[data-vector-name="' + vectorName + '"]', element),
boardObject = this.board.elementsByName[vectorName];
if (newName && newName !== vectorName) {
// Update vector settings
vectorSettings.name = newName;
// Update dropdown for selecting vector to edit
editOption.data('vector-name', newName);
editOption.text(newName);
// Update board
boardObject.name = newName;
boardObject.point2.name = newName;
this.board.elementsByName[newName] = boardObject;
delete this.board.elementsByName[vectorName];
// Update expected position
var expectedPositions = this.settings.expected_result_positions,
expectedPosition = expectedPositions[vectorName];
if (expectedPosition) {
expectedPositions[newName] = expectedPosition;
delete expectedPositions[vectorName];
}
// Update expected result
var expectedResults = this.settings.expected_result,
expectedResult = expectedResults[vectorName];
if (expectedResult) {
expectedResults[newName] = expectedResult;
delete expectedResults[vectorName];
}
}
if (newLabel && newLabel !== '-') {
vectorSettings.style.label = newLabel;
boardObject.point2.name = newLabel; // Always prefer label for labeling vector on board
}
var values = [newTail[0], newTail[1], newLength, newAngle];
// Validate values
if (!_.some(values, Number.isNaN)) {
$('.vector-prop-update .update-error', element).css('visibility', 'hidden');
// Use coordinates of new tail, new length, new angle to calculate new position of tip
......@@ -431,10 +604,17 @@ function VectorDrawXBlockEdit(runtime, element, init_args) {
newTail[1] + Math.sin(radians) * newLength
];
// Update position of vector
var board_object = this.board.elementsByName[vectorName];
board_object.point1.setPosition(JXG.COORDS_BY_USER, newTail);
board_object.point2.setPosition(JXG.COORDS_BY_USER, newTip);
boardObject.point1.setPosition(JXG.COORDS_BY_USER, newTail);
boardObject.point2.setPosition(JXG.COORDS_BY_USER, newTip);
this.board.update();
// If board is in result mode, also save
// - expected position
// - check data
// for "selected" vector
if (this.resultMode) {
this.saveExpectedPosition(vectorName, [newTail, newTip], newLength, newAngle);
this.saveChecks(vectorName);
}
} else {
$('.vector-prop-update .update-error', element).css('visibility', 'visible');
}
......@@ -466,7 +646,7 @@ function VectorDrawXBlockEdit(runtime, element, init_args) {
vec.tail = tail;
vec.tip = tip;
// Update length, angle
vec.length = Math.sqrt(Math.pow(x2-x1, 2) + Math.pow(y2-y1, 2));;
vec.length = Math.sqrt(Math.pow(x2-x1, 2) + Math.pow(y2-y1, 2));
// Update angle
vec.angle = ((Math.atan2(y2-y1, x2-x1)/Math.PI*180)) % 360;
vectors.push(vec);
......@@ -485,10 +665,23 @@ function VectorDrawXBlockEdit(runtime, element, init_args) {
// Set up click handlers
$('.save-button', element).on('click', function(e) {
e.preventDefault();
var data = {};
if (vectordraw.wasUsed) {
var data;
if (vectordraw.resultMode) { // Author edited both initial state and result
data = {
vectors: vectordraw.settings.vectors, // Corresponds to state vectors were in
// when author switched to result mode
expected_result_positions: vectordraw.settings.expected_result_positions,
expected_result: vectordraw.settings.expected_result
};
} else if (vectordraw.wasUsed) { // Author edited initial state
var state = vectordraw.getState();
data = { vectors: state };
data = {
vectors: state,
expected_result_positions: vectordraw.settings.expected_result_positions,
expected_result: vectordraw.settings.expected_result
};
} else { // Author did not use WYSIWYG editor
data = {};
}
fieldEditor.save(data);
});
......
......@@ -68,25 +68,42 @@
<p id="wysiwyg-description">
{% blocktrans %}
Instead of using the "Vectors" field above to define or modify
the set of working vectors for this exercise, you can also use the board below.
Instead of using the "Vectors" and "Expected result" fields above
to define or modify the set of working vectors and expected result for this exercise,
you can also use the board below.
To add a vector, left-click the board where you want the vector to originate.
Keep holding down the left mouse button and drag your mouse pointer across the board
to achieve the desired length and angle for the vector.
Alternatively, you can click "Create vector", which will add a new vector
that starts at the center of the board and has a predefined length (3) and angle (90).
To modify an existing vector, left-click it, hold down the left mouse button,
and move your mouse pointer across the board.
Alternatively, you can select an existing vector from the dropdown menu
To modify the position of an existing vector, left-click it, hold down the left mouse button,
and move your mouse pointer across the board. To modify length and/or angle,
left-click the tip of the vector and drag your mouse pointer across the board.
Alternatively, you can select an existing vector from the dropdown menu,
modify its tail position, length, and angle by changing the values
in the corresponding input fields, and click "Update" to update its position on the board.
You can also modify the name and label of a vector using this technique.
To remove an existing vector, left-click it or select it from the dropdown menu,
then click "Remove".
Note that if you make changes using the board below, any changes you made via the "Vectors" field above
will be overwritten when you save the settings for this exercise by clicking the "Save" button below.
When you are done defining the set of working vectors, click "Edit result"
to switch the editor to a mode that will allow you to define the expected result for this exercise.
In this mode you can operate on vectors as described above but you can not add or remove vectors,
and you may not change the name and label of a selected vector.
To define the expected result for the exercise, place each vector where it would be located
in a correct solution. When a vector is selected, use the menu to the right of the board
to select the checks that you would like the grader to perform for this vector,
and click "Update". Note that if you do not select any checks for a given vector,
no checks at all will be performed for it during grading (i.e., the grader will skip a presence check).
Finally, note that if you make changes using the board below, any changes you made
via the "Vectors" and "Expected results" fields above will be overwritten
when you save the settings for this exercise by clicking the "Save" button
at the bottom of this dialog.
{% endblocktrans %}
</p>
......@@ -94,16 +111,17 @@
<div class="menu" style="width: {{ self.width }}px;">
<div class="controls">
<button class="add-vector">{% trans "Create vector" %}</button>
<button class="result-mode">{% trans "Edit result" %}</button>
</div>
<div class="vector-properties" aria-live="polite">
<h3>{{ self.vector_properties_label }}</h3>
<div class="vector-prop-list">
<div class="row">
<div class="vector-prop vector-prop-name">
<span id="vector-prop-name-label">
{% trans "name" %}:
<div class="vector-prop vector-select">
<span id="vector-select-label">
{% trans "vector" %}:
</span>
<select class="element-list-edit" aria-labelledby="vector-prop-name-label">
<select class="element-list-edit" aria-labelledby="vector-select-label">
<option value="-" selected="selected" disabled="disabled">-</option>
{% for vector in self.get_vectors %}
<option value="vector-{{ forloop.counter0 }}" data-vector-name="{{ vector.name }}">
......@@ -112,34 +130,40 @@
{% endfor %}
</select>
</div>
<div class="vector-prop vector-prop-name">
<span id="vector-prop-name-label">
{% trans "name" %}:
</span>
<input type="text" value="-" aria-labelledby="vector-prop-name-label">
</div>
</div>
<div class="row">
<div class="vector-prop vector-prop-label">
<span id="vector-prop-label-label">
{% trans "label" %}:
</span>
<input type="text" value="-" aria-labelledby="vector-prop-label-label">
</div>
<div class="vector-prop vector-prop-tail">
<span id="vector-prop-tail-label">
{% trans "tail position" %}:
</span>
<input type="text" value="-" aria-labelledby="vector-prop-tail-label">
</div>
</div>
<div class="row">
<div class="vector-prop vector-prop-length">
<span id="vector-prop-length-label">
{% trans "length" %}:
</span>
<input type="text" value="-" aria-labelledby="vector-prop-length-label">
</div>
</div>
<div class="row">
<div class="vector-prop vector-prop-angle">
<span id="vector-prop-angle-label">
{% trans "angle" %}:
</span>
<input type="text" value="-" aria-labelledby="vector-prop-angle-label">
</div>
<div class="vector-prop vector-prop-slope">
<span id="vector-prop-slope-label">
{% trans "slope" %}:
</span>
<input type="text" value="-" aria-labelledby="vector-prop-slope-label" disabled="disabled">
</div>
</div>
<div class="row">
<div class="vector-prop vector-prop-update">
......@@ -163,6 +187,135 @@
style="width: {{ self.width }}px; height: {{ self.height }}px;"
tabindex="0">
</div>
<div class="checks" style="height: {{ self.height }}px;">
<h3>{% trans "Checks" %}</h3>
<div class="check" id="check-tail">
<div class="row">
<span id="tail-check-label">
{% trans "check tail" %}:
</span>
<input type="checkbox" aria-labelledby="tail-check-label">
</div>
<div class="row">
<span id="tail-tolerance-label">
{% trans "tolerance" %}:
</span>
<input type="text" value="1.0" aria-labelledby="tail-tolerance-label">
</div>
</div>
<div class="check" id="check-tip">
<div class="row">
<span id="tip-check-label">
{% trans "check tip" %}:
</span>
<input type="checkbox" aria-labelledby="tip-check-label">
</div>
<div class="row">
<span id="tip-tolerance-label">
{% trans "tolerance" %}:
</span>
<input type="text" value="1.0" aria-labelledby="tip-tolerance-label">
</div>
</div>
<div class="check" id="check-tail_x">
<div class="row">
<span id="tail-x-check-label">
{% trans "check tail(x)" %}:
</span>
<input type="checkbox" aria-labelledby="tail-x-check-label">
</div>
<div class="row">
<span id="tail-x-tolerance-label">
{% trans "tolerance" %}:
</span>
<input type="text" value="1.0" aria-labelledby="tail-x-tolerance-label">
</div>
</div>
<div class="check" id="check-tail_y">
<div class="row">
<span id="tail-y-check-label">
{% trans "check tail(y)" %}:
</span>
<input type="checkbox" aria-labelledby="tail-y-check-label">
</div>
<div class="row">
<span id="tail-y-tolerance-label">
{% trans "tolerance" %}:
</span>
<input type="text" value="1.0" aria-labelledby="tail-y-tolerance-label">
</div>
</div>
<div class="check" id="check-tip_x">
<div class="row">
<span id="tip-x-check-label">
{% trans "check tip(x)" %}:
</span>
<input type="checkbox" aria-labelledby="tip-x-check-label">
</div>
<div class="row">
<span id="tip-x-tolerance-label">
{% trans "tolerance" %}:
</span>
<input type="text" value="1.0" aria-labelledby="tip-x-tolerance-label">
</div>
</div>
<div class="check" id="check-tip_y">
<div class="row">
<span id="tip-y-check-label">
{% trans "check tip(y)" %}:
</span>
<input type="checkbox" aria-labelledby="tip-y-check-label">
</div>
<div class="row">
<span id="tip-y-tolerance-label">
{% trans "tolerance" %}:
</span>
<input type="text" value="1.0" aria-labelledby="tip-y-tolerance-label">
</div>
</div>
<div class="check" id="check-coords">
<div class="row">
<span id="coords-check-label">
{% trans "check coords" %}:
</span>
<input type="checkbox" aria-labelledby="coords-check-label">
</div>
<div class="row">
<span id="coords-tolerance-label">
{% trans "tolerance" %}:
</span>
<input type="text" value="1.0" aria-labelledby="coords-tolerance-label">
</div>
</div>
<div class="check" id="check-length">
<div class="row">
<span id="length-check-label">
{% trans "check length" %}:
</span>
<input type="checkbox" aria-labelledby="length-check-label">
</div>
<div class="row">
<span id="length-tolerance-label">
{% trans "tolerance" %}:
</span>
<input type="text" value="1.0" aria-labelledby="length-tolerance-label">
</div>
</div>
<div class="check" id="check-angle">
<div class="row">
<span id="angle-check-label">
{% trans "check angle" %}:
</span>
<input type="checkbox" aria-labelledby="angle-check-label">
</div>
<div class="row">
<span id="angle-tolerance-label">
{% trans "tolerance" %}:
</span>
<input type="text" value="2.0" aria-labelledby="angle-tolerance-label">
</div>
</div>
</div>
</div>
</div>
</li>
......
......@@ -178,7 +178,11 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
display_name="Expected result",
help=(
"Defines vector properties for grading. "
"Vectors omitted from this setting are ignored when grading."
"Vectors omitted from this setting are ignored when grading. "
"Note that you can also use the WYSIWYG editor below to opt in and out of checks "
"for individual vectors. "
"If you use the WYSIWYG editor at all, any changes you make here "
"will be overwritten when saving."
),
default="{}",
multiline_editor=True,
......@@ -206,6 +210,11 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
enforce_type=True
)
# Dictionary that keeps track of vector positions for correct answer;
# treated as an editable field but hidden from author in Studio
# since changes to it are implicit
expected_result_positions = Dict(scope=Scope.content)
# User state
# Dictionary containing vectors and points present on the board when user last clicked "Check",
......@@ -234,6 +243,7 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
'vectors',
'points',
'expected_result',
'expected_result_positions',
'custom_checks'
)
......@@ -261,6 +271,7 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
'vectors': self.get_vectors,
'points': self.get_points,
'expected_result': self.get_expected_result,
'expected_result_positions': self.expected_result_positions,
}
@property
......@@ -406,6 +417,8 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
context = {'fields': [], 'self': self}
# Build a list of all the fields that can be edited:
for field_name in self.editable_fields:
if field_name == "expected_result_positions":
continue
field = self.fields[field_name]
assert field.scope in (Scope.content, Scope.settings), (
"Only Scope.content or Scope.settings fields can be used with "
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment