Commit d2fe0450 by Tim Krones

Make LMS and Studio functionality accessible.

parent c3a098db
......@@ -12,6 +12,7 @@
vector_properties_label="Custom properties label"
background_url="https://github.com/open-craft/jsinput-vectordraw/raw/master/Notes_and_Examples/2_boxIncline_multiVector/box_on_incline.png"
background_width="20"
background_description="A very informative description"
vectors="{{ vectors }}"
points="{{ points }}"
expected_result="{{ expected_result }}"
......
......@@ -20,6 +20,8 @@
pointer-events: none; /* prevents cursor from turning into caret when over a label */
}
/* Menu */
.vectordraw_block .menu {
width: 100%;
}
......@@ -41,7 +43,7 @@
font-size: 18px;
}
.vectordraw_block .menu .controls button {
.vectordraw_block .menu button {
border: 1px solid #1f628d;
border-radius: 5px;
margin: 4px 0;
......@@ -53,7 +55,7 @@
text-decoration: none;
}
.vectordraw_block .menu .controls button:hover {
.vectordraw_block .menu button:hover {
background: #c2e0f4;
background-image: -webkit-linear-gradient(top, #c2e0f4, #add5f0);
background-image: -moz-linear-gradient(top, #c2e0f4, #add5f0);
......@@ -85,12 +87,39 @@
margin: 0 0 5px;
}
.vectordraw_block .menu .vector-properties .vector-prop-bold {
font-weight: bold;
.vectordraw_block .menu .vector-properties .vector-prop-list {
display: table;
width: 100%
}
.vectordraw_block .menu .vector-prop-slope {
display: none;
.vectordraw_block .menu .vector-properties .vector-prop-list .row {
display: table-row;
}
.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-name,
.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-tail,
.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-tail-label,
.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-length,
.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-length-label,
.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-angle,
.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-angle-label,
.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-slope,
.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-slope-label,
.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-update {
display: table-cell;
width: 50%
}
.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-name,
.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-tail,
.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-angle {
padding-right: 5px;
}
.vectordraw_block .menu .vector-properties .vector-prop-list .row select,
.vectordraw_block .menu .vector-properties .vector-prop-list .row input {
float: right;
width: 50%;
}
.vectordraw_block .action button {
......@@ -101,7 +130,7 @@
}
/* Make sure screen-reader content is hidden in the workbench: */
.vectordraw_block .action .sr {
.vectordraw_block .sr {
display: none;
border: 0;
clip: rect(0 0 0 0);
......
......@@ -27,3 +27,107 @@
pointer-events: none; /* prevents cursor from turning into caret when over a label */
}
/* Menu */
.vectordraw_edit_block .menu {
width: 100%;
}
.vectordraw_edit_block .menu .controls {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border-top: 2px solid #1f628d;
border-left: 2px solid #1f628d;
border-right: 2px solid #1f628d;
padding: 3px;
background-color: #e0e0e0;
font-size: 0;
}
.vectordraw_edit_block .menu .controls select {
width: 160px;
margin-right: 3px;
font-size: 18px;
}
.vectordraw_edit_block .menu button {
border: 1px solid #1f628d;
border-radius: 5px;
margin: 4px 0;
padding: 5px 10px 5px 10px;
box-shadow: 0 1px 3px #666;
background-color: #c2e0f4;
color: #1f628d;
font-size: 14px;
text-decoration: none;
}
.vectordraw_edit_block .menu button:hover {
background: #c2e0f4;
background-image: -webkit-linear-gradient(top, #c2e0f4, #add5f0);
background-image: -moz-linear-gradient(top, #c2e0f4, #add5f0);
background-image: -ms-linear-gradient(top, #c2e0f4, #add5f0);
background-image: -o-linear-gradient(top, #c2e0f4, #add5f0);
background-image: linear-gradient(to bottom, #c2e0f4, #add5f0);
text-decoration: none;
}
.vectordraw_edit_block .menu .vector-properties {
border-top: 2px solid #1f628d;
border-left: 2px solid #1f628d;
border-right: 2px solid #1f628d;
border-bottom: 0px none;
padding: 10px;
font-size: 16px;
line-height: 1.25;
background-color: #f7f7f7;
}
.vectordraw_edit_block .menu .vector-properties h3 {
font-size: 16px;
margin: 0 0 5px;
}
.vectordraw_edit_block .menu .vector-properties .vector-prop-list {
display: table;
width: 100%
}
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row {
display: table-row;
}
.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-tail-label,
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-length,
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-length-label,
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-angle,
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-angle-label,
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-slope,
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-slope-label,
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-update,
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-remove {
display: table-cell;
width: 50%
}
.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: 5px;
}
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row select,
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row input {
float: right;
width: 50%;
}
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-remove {
vertical-align: bottom;
}
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-remove button {
float: right;
}
......@@ -10,9 +10,10 @@
{% endif %}
<div id="vectordraw">
<div class="menu">
<div class="menu" style="width: {{ self.menu_width }}px;">
<div class="controls">
<select>
<span class="sr" id="element-list-add-label">{% trans "Select element to add to board" %}</span>
<select class="element-list-add" aria-labelledby="element-list-add-label">
{% for vector in self.get_vectors %}
<option value="vector-{{ forloop.counter0 }}">
{{ vector.description }}
......@@ -29,29 +30,88 @@
<button class="add-vector">
{{ self.add_vector_label }}
</button>
<button class="reset">Reset</button>
<button class="redo" title="Redo"><span class="fa fa-repeat" /></button>
<button class="undo" title="Undo"><span class="fa fa-undo" /></button>
<button class="reset">
<span class="reset-label" aria-hidden="true">{% trans "Reset" %}</span>
<span class="sr">{% trans "Reset board to initial state" %}</span>
</button>
<button class="redo" title="Redo">
<span class="redo-label fa fa-repeat" aria-hidden="true"></span>
<span class="sr">{% trans "Redo last action" %}</span>
</button>
<button class="undo" title="Undo">
<span class="undo-label fa fa-undo" aria-hidden="true"></span>
<span class="sr">{% trans "Undo last action" %}</span>
</button>
</div>
{% if self.show_vector_properties %}
<div class="vector-properties">
<div class="vector-properties" aria-live="polite">
<h3>{{ self.vector_properties_label }}</h3>
<div class="vector-prop-name">
{% trans "name" %}: <span class="value vector-prop-bold">-</span>
</div>
<div class="vector-prop-length">
{% trans "length" %}: <span class="value">-</span>
</div>
<div class="vector-prop-angle">
{% trans "angle" %}: <span class="value">-</span>&deg;
</div>
<div class="vector-prop-slope">
{% trans "slope" %}: <span class="value">-</span>
<div class="vector-prop-list">
<div class="row">
<div class="vector-prop-name">
<span id="vector-prop-name-label">
{% trans "name" %}:
</span>
<select class="element-list-edit" aria-labelledby="vector-prop-name-label">
<option value="-" selected="selected" disabled="disabled">-</option>
{% for vector in self.get_vectors %}
<option value="vector-{{ forloop.counter0 }}" data-vector-name="{{ vector.name }}">
{{ vector.name }}
</option>
{% endfor %}
{% for point in self.get_points %}
{% if not point.fixed %}
<option value="point-{{ forloop.counter0 }}">
{{ point.name }}
</option>
{% endif %}
{% endfor %}
</select>
</div>
</div>
<div class="row">
<div class="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 class="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-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-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-update">
<button class="update">
<span class="update-label" aria-hidden="true">{% trans "Update" %}</span>
<span class="sr">{% trans "Update properties of selected element" %}</span>
</button>
</div>
</div>
</div>
</div>
{% endif %}
</div>
<div class="jxgboard" style="width: {{ self.width }}px; height: {{ self.height }}px;"></div>
<div class="jxgboard"
style="width: {{ self.width }}px; height: {{ self.height }}px;"
aria-live="polite"></div>
</div>
<div class="vectordraw-status">
......@@ -62,7 +122,7 @@
<div class="action">
<button class="check">
<span class="check-label" aria-hidden="true">{% trans "Check" %}</span>
<span class="sr"> {% trans "Check your answer" %}</span>
<span class="sr">{% trans "Check your answer" %}</span>
</button>
</div>
......
......@@ -73,14 +73,91 @@
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
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.
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.
{% endblocktrans %}
</p>
<div id="vectordraw">
<div class="menu" style="width: {{ self.menu_width }}px;">
<div class="controls">
<button class="add-vector">{% trans "Create vector" %}</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-name">
<span id="vector-prop-name-label">
{% trans "name" %}:
</span>
<select class="element-list-edit" aria-labelledby="vector-prop-name-label">
<option value="-" selected="selected" disabled="disabled">-</option>
{% for vector in self.get_vectors %}
<option value="vector-{{ forloop.counter0 }}" data-vector-name="{{ vector.name }}">
{{ vector.name }}
</option>
{% endfor %}
</select>
</div>
</div>
<div class="row">
<div class="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 class="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-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-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-update">
<button class="update">
<span class="update-label" aria-hidden="true">{% trans "Update" %}</span>
<span class="sr">{% trans "Update properties of selected element" %}</span>
</button>
</div>
<div class="vector-remove">
<button class="remove">
<span class="remove-label" aria-hidden="true">{% trans "Remove" %}</span>
<span class="sr">{% trans "Remove selected element" %}</span>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="jxgboard"
style="width: {{ self.width }}px; height: {{ self.height }}px;"
tabindex="0">
......
......@@ -7,6 +7,7 @@ from xblock.core import XBlock
from xblock.exceptions import JsonHandlerError
from xblock.fields import Scope, Boolean, Dict, Float, Integer, String
from xblock.fragment import Fragment
from xblock.validation import ValidationMessage
from xblockutils.resources import ResourceLoader
from xblockutils.studio_editable import StudioEditableXBlockMixin
......@@ -133,6 +134,17 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
scope=Scope.content
)
background_description = String(
display_name="Background description",
help=(
"Please provide a description of the image for non-visual users. "
"The description should provide sufficient information that would allow anyone "
"to solve the problem if the image did not load."
),
default="",
scope=Scope.content
)
vectors = String(
display_name="Vectors",
help=(
......@@ -218,6 +230,7 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
'background_url',
'background_width',
'background_height',
'background_description',
'vectors',
'points',
'expected_result',
......@@ -251,6 +264,14 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
}
@property
def menu_width(self):
"""
Width of SVG canvas (controlled by JSXGraph) consistently ends up being 4px larger
than self.width. Adjust menu size accordingly to ensure that board and menu line up.
"""
return self.width + 4
@property
def user_state(self):
"""
Return user state, which is a combination of most recent answer and result.
......@@ -269,6 +290,7 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
'src': self.background_url,
'width': self.background_width,
'height': self.background_height,
'description': self.background_description,
}
def _get_default_vector(self): # pylint: disable=no-self-use
......@@ -420,6 +442,32 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
)
return fragment
def validate_field_data(self, validation, data):
"""
Validate this block's field data.
"""
super(VectorDrawXBlock, self).validate_field_data(validation, data)
def add_error(msg):
""" Helper function for adding validation messages. """
validation.add(ValidationMessage(ValidationMessage.ERROR, msg))
if data.background_url.strip():
if data.background_width == 0 and data.background_height == 0:
add_error(
u"You specified a background image but no width or height. "
"For the image to display, you need to specify a non-zero value "
"for at least one of them."
)
if not data.background_description.strip():
add_error(
u"No background description set. "
"This means that it will be more difficult for non-visual users "
"to solve the problem. "
"Please provide a description that contains sufficient information "
"that would allow anyone to solve the problem if the image did not load."
)
def _validate_check_answer_data(self, data): # pylint: disable=no-self-use
"""
Validate answer data submitted by user.
......
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