Commit 06eacfc7 by Tim Krones

Address review comments.

parent fbc4ea48
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
} }
.vectordraw_block .menu .controls { .vectordraw_block .menu .controls {
display: table;
margin-bottom: 20px; margin-bottom: 20px;
font-size: 0; font-size: 0;
} }
...@@ -39,7 +40,7 @@ ...@@ -39,7 +40,7 @@
.vectordraw_block .menu .controls button { .vectordraw_block .menu .controls button {
display: inline-block; display: inline-block;
background-color: #3498db; background-color: #c2e0f4;
border-radius: 5px; border-radius: 5px;
box-shadow: 0 1px 3px #666; box-shadow: 0 1px 3px #666;
color: #1f628d; color: #1f628d;
...@@ -52,22 +53,19 @@ ...@@ -52,22 +53,19 @@
} }
.vectordraw_block .menu .controls button:hover { .vectordraw_block .menu .controls button:hover {
background: #3cb0fd; background: #c2e0f4;
background-image: -webkit-linear-gradient(top, #3cb0fd, #3498db); background-image: -webkit-linear-gradient(top, #c2e0f4, #add5f0);
background-image: -moz-linear-gradient(top, #3cb0fd, #3498db); background-image: -moz-linear-gradient(top, #c2e0f4, #add5f0);
background-image: -ms-linear-gradient(top, #3cb0fd, #3498db); background-image: -ms-linear-gradient(top, #c2e0f4, #add5f0);
background-image: -o-linear-gradient(top, #3cb0fd, #3498db); background-image: -o-linear-gradient(top, #c2e0f4, #add5f0);
background-image: linear-gradient(to bottom, #3cb0fd, #3498db); background-image: linear-gradient(to bottom, #c2e0f4, #add5f0);
text-decoration: none; text-decoration: none;
} }
vectordraw_block .menu .controls button.undo, .vectordraw_block .menu .controls button.undo,
vectordraw_block .menu .controls button.redo { .vectordraw_block .menu .controls button.redo {
width: 78px; display: table-cell;
} width: 50%;
.vectordraw_block .menu .controls button.undo {
margin-right: 4px;
} }
.vectordraw_block .menu .vector-properties { .vectordraw_block .menu .vector-properties {
......
...@@ -9,7 +9,7 @@ function VectorDrawXBlock(runtime, element, init_args) { ...@@ -9,7 +9,7 @@ function VectorDrawXBlock(runtime, element, init_args) {
this.dragged_vector = null; this.dragged_vector = null;
this.drawMode = false; this.drawMode = false;
this.history_stack = {undo: [], redo: []}; this.history_stack = {undo: [], redo: []};
this.settings = this.sanitizeSettings(settings); this.settings = settings;
this.element = $('#' + element_id); this.element = $('#' + element_id);
this.element.on('click', '.reset', this.reset.bind(this)); this.element.on('click', '.reset', this.reset.bind(this));
...@@ -22,76 +22,6 @@ function VectorDrawXBlock(runtime, element, init_args) { ...@@ -22,76 +22,6 @@ function VectorDrawXBlock(runtime, element, init_args) {
this.render(); this.render();
}; };
VectorDraw.prototype.sanitizeSettings = function(settings) {
// Fill in defaults at top level of settings.
var default_settings = {
width: 550,
height: 400,
axis: false,
background: null,
bounding_box_size: 10,
show_navigation: false,
show_vector_properties: true,
add_vector_label: 'Add Selected Force',
vector_properties_label: 'Vector Properties',
vectors: [],
points: [],
expected_result: {},
custom_checks: [],
unit_vector_ratio: 1
};
_.defaults(settings, default_settings);
var width_scale = settings.width / settings.height,
box_size = settings.bounding_box_size;
settings.bounding_box = [-box_size*width_scale, box_size, box_size*width_scale, -box_size];
// Fill in defaults for vectors.
var default_vector = {
type: 'vector',
render: false,
length_factor: 1,
length_units: '',
base_angle: 0,
style: {}
};
var default_vector_style = {
pointSize: 1,
pointColor: 'red',
width: 4,
color: "blue",
label: null,
labelColor: 'black'
};
settings.vectors.forEach(function(vector) {
_.defaults(vector, default_vector);
_.defaults(vector.style, default_vector_style);
});
// Fill in defaults for points.
var default_point = {
fixed: true, // Default to true for backwards compatibility.
render: true,
style: {}
};
var default_point_style = {
size: 1,
withLabel: false,
color: 'pink',
showInfoBox: false
};
settings.points.forEach(function(point) {
_.defaults(point, default_point);
_.defaults(point.style, default_point_style);
point.style.name = point.name;
point.style.fixed = point.fixed;
point.style.strokeColor = point.style.color;
point.style.fillColor = point.style.color;
delete point.style.color;
});
return settings;
};
VectorDraw.prototype.render = function() { VectorDraw.prototype.render = function() {
// Assign the jxgboard element a random unique ID, // Assign the jxgboard element a random unique ID,
// because JXG.JSXGraph.initBoard needs it. // because JXG.JSXGraph.initBoard needs it.
...@@ -112,7 +42,7 @@ function VectorDrawXBlock(runtime, element, init_args) { ...@@ -112,7 +42,7 @@ function VectorDrawXBlock(runtime, element, init_args) {
}); });
function getImageRatio(bg, callback) { function getImageRatio(bg, callback) {
$('<img/>').attr('src', bg.src).load(function(){ $('<img/>').attr('src', bg.src).load(function() {
//technically it's inverse of ratio, but we need it to calculate height //technically it's inverse of ratio, but we need it to calculate height
var ratio = this.height / this.width; var ratio = this.height / this.width;
callback(bg, ratio); callback(bg, ratio);
...@@ -562,7 +492,7 @@ function VectorDrawXBlock(runtime, element, init_args) { ...@@ -562,7 +492,7 @@ function VectorDrawXBlock(runtime, element, init_args) {
}); });
}); });
input.checks = checks.concat(vectordraw.settings.custom_checks); input.checks = checks;
return input; return input;
} }
......
...@@ -228,10 +228,13 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock): ...@@ -228,10 +228,13 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
""" """
Return settings for this exercise. Return settings for this exercise.
""" """
width_scale = self.width / float(self.height)
box_size = self.bounding_box_size
bounding_box = [-box_size*width_scale, box_size, box_size*width_scale, -box_size]
return { return {
'width': self.width, 'width': self.width,
'height': self.height, 'height': self.height,
'bounding_box_size': self.bounding_box_size, 'bounding_box': bounding_box,
'axis': self.axis, 'axis': self.axis,
'show_navigation': self.show_navigation, 'show_navigation': self.show_navigation,
'show_vector_properties': self.show_vector_properties, 'show_vector_properties': self.show_vector_properties,
...@@ -241,7 +244,7 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock): ...@@ -241,7 +244,7 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
'background': self.background, 'background': self.background,
'vectors': self.get_vectors, 'vectors': self.get_vectors,
'points': self.get_points, 'points': self.get_points,
'expected_result': self.get_expected_result 'expected_result': self.get_expected_result,
} }
@property @property
...@@ -265,26 +268,83 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock): ...@@ -265,26 +268,83 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
'height': self.background_height, 'height': self.background_height,
} }
def _get_default_vector(self): # pylint: disable=no-self-use
"""
Return dictionary that represents vector with default values filled in.
"""
return {
'type': 'vector',
'render': False,
'length_factor': 1,
'length_units': '',
'base_angle': 0,
'style': {
'pointSize': 1,
'pointColor': 'red',
'width': 4,
'color': 'blue',
'label': None,
'labelColor': 'black'
}
}
@property @property
def get_vectors(self): def get_vectors(self):
""" """
Load info about vectors for this exercise from JSON string specified by course author. Return info about vectors belonging to this exercise.
To do this, load vector info from JSON string specified by course author,
and augment it with default values that are required for rendering vectors on the client.
"""
vectors = []
for vector in json.loads(self.vectors):
default_vector = self._get_default_vector()
default_vector_style = default_vector['style']
if 'style' in vector:
default_vector_style.update(vector['style'])
del vector['style']
default_vector.update(vector)
vectors.append(default_vector)
return vectors
def _get_default_point(self): # pylint: disable=no-self-use
""" """
return json.loads(self.vectors) Return dictionary that represents point with default values filled in.
"""
return {
'fixed': True, # Default to True for backwards compatibility
'render': True,
'style': {
'size': 1,
'withLabel': False,
'color': 'pink',
'showInfoBox': False
}
}
@property @property
def get_points(self): def get_points(self):
""" """
Load info about points for this exercise from JSON string specified by course author. Return info about points belonging to this exercise.
To do this, load point info from JSON string specified by course author,
and augment it with default values that are required for rendering points on the client.
""" """
points = json.loads(self.points) points = []
for point in points: for point in json.loads(self.points):
# If author did not specify whether point should be drawn in fixed location (True) default_point = self._get_default_point()
# or placed by student (False), we default to True; default_point_style = default_point['style']
# template needs this info to determine whether it should add option if 'style' in point:
# for selecting point to dropdown menu: default_point_style.update(point['style'])
if 'fixed' not in point: del point['style']
point['fixed'] = True default_point.update(point)
default_point_style['name'] = default_point['name']
default_point_style['fixed'] = default_point['fixed']
point_color = default_point_style['color']
default_point_style['strokeColor'] = point_color
default_point_style['fillColor'] = point_color
del default_point_style['color']
points.append(default_point)
return points return points
@property @property
...@@ -300,13 +360,14 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock): ...@@ -300,13 +360,14 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
The primary view of the VectorDrawXBlock, shown to students The primary view of the VectorDrawXBlock, shown to students
when viewing courses. when viewing courses.
""" """
context = context or {}
context['self'] = self context['self'] = self
fragment = Fragment() fragment = Fragment()
fragment.add_content(loader.render_template('static/html/vectordraw.html', context)) fragment.add_content(loader.render_template('templates/html/vectordraw.html', context))
fragment.add_css_url( fragment.add_css_url(
"//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.3.0/css/font-awesome.min.css" "//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.3.0/css/font-awesome.min.css"
) )
fragment.add_css(loader.load_unicode('static/css/vectordraw.css')) fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/vectordraw.css'))
# Workbench doesn't have Underscore.js, so add it: # Workbench doesn't have Underscore.js, so add it:
if WorkbenchRuntime and isinstance(self.runtime, WorkbenchRuntime): if WorkbenchRuntime and isinstance(self.runtime, WorkbenchRuntime):
fragment.add_javascript_url( fragment.add_javascript_url(
...@@ -315,41 +376,42 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock): ...@@ -315,41 +376,42 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
fragment.add_javascript_url( fragment.add_javascript_url(
"//cdnjs.cloudflare.com/ajax/libs/jsxgraph/0.98/jsxgraphcore.js" "//cdnjs.cloudflare.com/ajax/libs/jsxgraph/0.98/jsxgraphcore.js"
) )
fragment.add_javascript(loader.load_unicode("static/js/src/vectordraw.js")) fragment.add_javascript_url(
self.runtime.local_resource_url(self, 'public/js/vectordraw.js')
)
fragment.initialize_js( fragment.initialize_js(
'VectorDrawXBlock', {"settings": self.settings, "user_state": self.user_state} 'VectorDrawXBlock', {"settings": self.settings, "user_state": self.user_state}
) )
return fragment return fragment
def is_valid(self, data): # pylint: disable=no-self-use def _validate_check_answer_data(self, data): # pylint: disable=no-self-use
""" """
Validate answer data submitted by user. Validate answer data submitted by user.
""" """
# Check vectors # Check vectors
vectors = data.get('vectors') vectors = data.get('vectors')
if vectors is None: if not isinstance(vectors, dict):
return False return False
for vector_data in vectors.values(): for vector_data in vectors.values():
# Validate vector # Validate vector
vector_valid = 'tip' in vector_data and 'tail' in vector_data if not vector_data.viewkeys() == {'tail', 'tip'}:
if not vector_valid:
return False return False
# Validate tip and tail # Validate tip and tail
tip = vector_data['tip'] tip = vector_data['tip']
tip_valid = type(tip) == list and len(tip) == 2 tip_valid = isinstance(tip, list) and len(tip) == 2
tail = vector_data['tail'] tail = vector_data['tail']
tail_valid = type(tail) == list and len(tail) == 2 tail_valid = isinstance(tail, list) and len(tail) == 2
if not (tip_valid and tail_valid): if not (tip_valid and tail_valid):
return False return False
# Check points # Check points
points = data.get('points') points = data.get('points')
if points is None: if not isinstance(points, dict):
return False return False
for coords in points.values(): for coords in points.values():
# Validate point # Validate point
point_valid = type(coords) == list and len(coords) == 2 point_valid = isinstance(coords, list) and len(coords) == 2
if not point_valid: if not point_valid:
break return False
# If we get to this point, it means that vector and point data is valid; # If we get to this point, it means that vector and point data is valid;
# the only thing left to check is whether data contains a list of checks: # the only thing left to check is whether data contains a list of checks:
return 'checks' in data return 'checks' in data
...@@ -360,7 +422,7 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock): ...@@ -360,7 +422,7 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
Check and persist student's answer to this vector drawing problem. Check and persist student's answer to this vector drawing problem.
""" """
# Validate data # Validate data
if not self.is_valid(data): if not self._validate_check_answer_data(data):
raise JsonHandlerError(400, "Invalid data") raise JsonHandlerError(400, "Invalid data")
# Save answer # Save answer
self.answer = dict( self.answer = dict(
......
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