Commit ded76fc8 by Tim Krones

Merge pull request #1 from open-craft/initial-implementation

Initial implementation
parents 4fdb02a5 461371b2
__pycache__/
*.py[cod]
tests.integration.*.log
tests.integration.*.png
vectordraw_xblock.egg-info/
var/
language: python
python:
- 2.7
before_install:
- export DISPLAY=:99
- sh -e /etc/init.d/xvfb start
install:
- pip install -r test-requirements.txt
- pip install -r $VIRTUAL_ENV/src/xblock-sdk/requirements/base.txt
- pip install -r $VIRTUAL_ENV/src/xblock-sdk/requirements/test.txt
- pip install -r $VIRTUAL_ENV/src/xblock/requirements.txt
script:
- pep8 --max-line-length=100 vectordraw
- pylint vectordraw
- ./run_tests.py --with-coverage --cover-package=vectordraw
notifications:
email: false
addons:
firefox: 36.0
[REPORTS]
reports=no
[FORMAT]
max-line-length=100
[MESSAGES CONTROL]
disable=
I,
attribute-defined-outside-init,
maybe-no-member,
star-args,
too-few-public-methods,
too-many-ancestors,
too-many-instance-attributes,
too-many-public-methods
[VARIABLES]
dummy-variables-rgx=_$|dummy|unused
git+https://github.com/edx/XBlock.git@xblock-0.4.2#egg=XBlock
git+https://github.com/edx/xblock-utils.git@b4f9b51146c7fafa12f41d54af752b8f1516dffd#egg=xblock-utils
-e .
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Run tests for the Vector Drawing XBlock.
This script is required to run our selenium tests inside the xblock-sdk workbench
because the workbench SDK's settings file is not inside any python module.
"""
import os
import logging
import sys
from django.conf import settings
from django.core.management import execute_from_command_line
logging_level_overrides = {
'workbench.views': logging.ERROR,
'django.request': logging.ERROR,
'workbench.runtime': logging.ERROR,
}
if __name__ == '__main__':
# Use the workbench settings file:
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'workbench.settings')
# Configure a range of ports in case the default port of 8081 is in use
os.environ.setdefault('DJANGO_LIVE_TEST_SERVER_ADDRESS', 'localhost:8081-8099')
try:
os.mkdir('var')
except OSError:
# May already exist.
pass
settings.INSTALLED_APPS += ('vectordraw', )
for noisy_logger, log_level in logging_level_overrides.iteritems():
logging.getLogger(noisy_logger).setLevel(log_level)
args_iter = iter(sys.argv[1:])
options = []
paths = []
for arg in args_iter:
if arg == '--':
break
if arg.startswith('-'):
options.append(arg)
else:
paths.append(arg)
paths.extend(args_iter)
if not paths:
paths = ['tests/']
execute_from_command_line([sys.argv[0], 'test'] + options + ['--'] + paths)
"""Setup for vectordraw XBlock."""
import os
from setuptools import setup
def package_data(pkg, roots):
"""Generic function to find package_data.
All of the files under each of the `roots` will be declared as package
data for package `pkg`.
"""
data = []
for root in roots:
for dirname, _, files in os.walk(os.path.join(pkg, root)):
for fname in files:
data.append(os.path.relpath(os.path.join(dirname, fname), pkg))
return {pkg: data}
setup(
name='vectordraw-xblock',
version='0.1',
description='vectordraw XBlock', # TODO: write a better description.
packages=[
'vectordraw',
],
install_requires=[
'XBlock',
'xblock-utils',
],
entry_points={
'xblock.v1': [
'vectordraw = vectordraw:VectorDrawXBlock',
]
},
package_data=package_data("vectordraw", ["static", "public"]),
)
Django>=1.8, <1.9
-r requirements.txt
-e git+https://github.com/edx/xblock-sdk.git@8eb5f174dc59c0f4e40e10eaab56753958651d17#egg=xblock-sdk
ddt
selenium==2.47.3 # 2.48 is not working atm
<vertical_demo>
<vectordraw url_name="vectordraw_example"
display_name="Custom Exercise"
description="Custom exercise description"
width="600"
height="450"
axis="false"
show_navigation="true"
show_vector_properties="{{ show_vector_properties }}"
show_slope_for_lines="true"
add_vector_label="Custom button label"
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 }}"
/>
</vertical_demo>
<vertical_demo>
<vectordraw url_name="vectordraw_example" />
</vertical_demo>
"""
Top-level package for Vector Drawing XBlock.
See vectordraw.vectordraw for more information.
"""
from .vectordraw import VectorDrawXBlock
/* CSS for VectorDrawXBlock */
.vectordraw_block,
.vectordraw_block #vectordraw {
display: inline-block;
}
.vectordraw_block .vectordraw-description,
.vectordraw_block #vectordraw,
.vectordraw_block .vectordraw-status {
margin-bottom: 1.5em;
}
.vectordraw_block .jxgboard {
float: left;
border: 2px solid #1f628d;
}
.vectordraw_block .jxgboard .JXGtext {
pointer-events: none; /* prevents cursor from turning into caret when over a label */
}
/* Menu */
.vectordraw_block .menu {
width: 100%;
}
.vectordraw_block .menu,
.vectordraw_block .menu .controls {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
background-color: #e0e0e0;
font-size: 0;
}
.vectordraw_block .menu {
border-top: 2px solid #1f628d;
border-left: 2px solid #1f628d;
border-right: 2px solid #1f628d;
}
.vectordraw_block .menu .controls {
border-bottom: 2px solid #1f628d;
padding: 3px;
}
.vectordraw_block .menu .controls select {
width: 160px;
margin-right: 3px;
font-size: 18px;
}
.vectordraw_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_block .menu button:focus,
.vectordraw_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_block .menu .controls button.reset,
.vectordraw_block .menu .controls button.undo,
.vectordraw_block .menu .controls button.redo {
float: right;
}
.vectordraw_block .menu .vector-properties {
padding: 10px;
font-size: 16px;
line-height: 1.25;
background-color: #f7f7f7;
}
.vectordraw_block h3 {
font-size: 16px;
margin: 0 0 5px;
}
.vectordraw_block .menu .vector-properties .vector-prop-list {
display: table;
width: 100%
}
.vectordraw_block .menu .vector-properties .vector-prop-list .row {
display: table-row;
}
.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop {
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 span,
.vectordraw_block .menu .vector-properties .vector-prop-list .row select,
.vectordraw_block .menu .vector-properties .vector-prop-list .row input {
width: 50%;
}
.vectordraw_block .menu .vector-properties .vector-prop-list .row select,
.vectordraw_block .menu .vector-properties .vector-prop-list .row input {
float: right;
}
.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-update .update-error {
display: none;
color: #ff0000;
}
.vectordraw_block .menu .vector-properties button:disabled {
pointer-events: none;
border: 1px solid #707070;
background-color: #ececec;
color: #868686;
}
.vectordraw_block .action button {
height: 40px;
margin-right: 10px;
font-weight: 600;
text-transform: uppercase;
}
/* Make sure screen-reader content is hidden in the workbench: */
.vectordraw_block .sr {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
background: #ffffff;
color: #000000;
}
.vectordraw_block .vectordraw-status {
display: inline-block;
width: 100%;
}
.vectordraw_block .checkmark-correct {
font-size: 22pt;
color: #629b2b;
}
.vectordraw_block .checkmark-incorrect {
font-size: 22pt;
color: #ff0000;
}
.vectordraw_edit_block,
.vectordraw_edit_block .jxgboard {
display: block;
}
.vectordraw_edit_block {
border-top: 1px solid #e5e5e5;
margin-left: 20px;
margin-right: 20px;
padding-top: 20px;
}
.vectordraw_edit_block h2 {
margin-bottom: 1em;
}
.vectordraw_edit_block #wysiwyg-description {
display: none;
}
.vectordraw_edit_block p {
margin-bottom: 1em;
font-size: 0.9em;
}
.vectordraw_edit_block .jxgboard {
float: left;
margin-bottom: 1em;
}
/* Menu */
.vectordraw_edit_block .menu .controls .result-mode {
float: right;
}
.vectordraw_edit_block .menu .controls button:disabled {
pointer-events: none;
border: 1px solid #707070;
background-color: #ececec;
color: #868686;
}
.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;
}
.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-remove button {
float: right;
}
.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-update .update-pending {
display: none;
color: #ffa500;
}
.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="number"] {
height: 1.5em;
min-width: 0px;
margin-right: 12px;
}
.vectordraw_edit_block .checks .check input[type="number"] {
padding: 0 0 0 5px;
}
.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;
}
/* Javascript for StudioEditableXBlockMixin. */
function StudioEditableXBlockMixin(runtime, element) {
"use strict";
var fields = [];
var tinyMceAvailable = (typeof $.fn.tinymce !== 'undefined'); // Studio includes a copy of tinyMCE and its jQuery plugin
var errorMessage = gettext("This may be happening because of an error with our server or your internet connection. Make sure you are online, and try refreshing the page.");
$(element).find('.field-data-control').each(function() {
var $field = $(this);
var $wrapper = $field.closest('li');
var $resetButton = $wrapper.find('button.setting-clear');
var type = $wrapper.data('cast');
fields.push({
name: $wrapper.data('field-name'),
isSet: function() { return $wrapper.hasClass('is-set'); },
hasEditor: function() { return tinyMceAvailable && $field.tinymce(); },
val: function() {
var val = $field.val();
// Cast values to the appropriate type so that we send nice clean JSON over the wire:
if (type === 'boolean')
return (val === 'true' || val === '1');
if (type === "integer")
return parseInt(val, 10);
if (type === "float")
return parseFloat(val);
return val;
},
removeEditor: function() {
$field.tinymce().remove();
}
});
var fieldChanged = function() {
// Field value has been modified:
$wrapper.addClass('is-set');
$resetButton.removeClass('inactive').addClass('active');
};
$field.bind("change input paste", fieldChanged);
$resetButton.click(function() {
$field.val($wrapper.attr('data-default')); // Use attr instead of data to force treating the default value as a string
$wrapper.removeClass('is-set');
$resetButton.removeClass('active').addClass('inactive');
});
if (type === 'html' && tinyMceAvailable) {
tinyMCE.baseURL = baseUrl + "/js/vendor/tinymce/js/tinymce";
$field.tinymce({
theme: 'modern',
skin: 'studio-tmce4',
height: '200px',
formats: { code: { inline: 'code' } },
codemirror: { path: "" + baseUrl + "/js/vendor" },
convert_urls: false,
plugins: "link codemirror",
menubar: false,
statusbar: false,
toolbar_items_size: 'small',
toolbar: "formatselect | styleselect | bold italic underline forecolor wrapAsCode | bullist numlist outdent indent blockquote | link unlink | code",
resize: "both",
setup : function(ed) {
ed.on('change', fieldChanged);
}
});
}
});
var studio_submit = function(data) {
var handlerUrl = runtime.handlerUrl(element, 'submit_studio_edits');
runtime.notify('save', {state: 'start', message: gettext("Saving")});
$.ajax({
type: "POST",
url: handlerUrl,
data: JSON.stringify(data),
dataType: "json",
notifyOnError: false
}).done(function(response) {
runtime.notify('save', {state: 'end'});
}).fail(function(jqXHR) {
if (jqXHR.responseText) { // Is there a more specific error message we can show?
try {
errorMessage = JSON.parse(jqXHR.responseText).error;
if (_.isObject(errorMessage) && errorMessage.messages) {
// e.g. {"error": {"messages": [{"text": "Unknown user 'bob'!", "type": "error"}, ...]}} etc.
var errorMessages = _.pluck(errorMessage.messages, "text");
errorMessage = errorMessages.join(", ");
}
} catch (error) { errorMessage = jqXHR.responseText.substr(0, 300); }
}
runtime.notify('error', {title: gettext("Unable to update settings"), message: errorMessage});
});
};
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
_.each(fields, function(field) {
if (field.isSet()) {
values[field.name] = field.val();
} else {
notSet.push(field.name);
}
// Remove TinyMCE instances to make sure jQuery does not try to access stale instances
// when loading editor for another block:
if (field.hasEditor()) {
field.removeEditor();
}
});
// 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});
},
cancel: function() {
// Remove TinyMCE instances to make sure jQuery does not try to access stale instances
// when loading editor for another block:
_.each(fields, function(field) {
if (field.hasEditor()) {
field.removeEditor();
}
});
runtime.notify('cancel', {});
}
};
}
{% load i18n %}
<div class="vectordraw_block">
<h2>{{ self.display_name }}</h2>
{% if self.description %}
<div class="vectordraw-description">
{{ self.description|safe }}
</div>
{% endif %}
<div id="vectordraw">
<div class="menu" style="width: {{ self.width }}px;">
<div class="controls">
<label class="sr" for="element-list">{% trans "Select element to add to board" %}</label>
<select id="element-list" class="element-list-add">
{% for vector in self.get_vectors %}
<option value="vector-{{ forloop.counter0 }}">
{{ vector.description }}
</option>
{% endfor %}
{% for point in self.get_points %}
{% if not point.fixed %}
<option value="point-{{ forloop.counter0 }}">
{{ point.description }}
</option>
{% endif %}
{% endfor %}
</select>
<button class="add-vector">
{{ self.add_vector_label }}
</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="{% trans '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="{% trans '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" 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" %}:
</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 vector-prop-tail">
<span id="vector-prop-tail-label">
{% trans "tail position" %}:
</span>
<input type="text" disabled="disabled" placeholder="-" aria-labelledby="vector-prop-tail-label">
</div>
<div class="vector-prop vector-prop-length">
<span id="vector-prop-length-label">
{% trans "length" %}:
</span>
<input type="text" disabled="disabled" placeholder="-" 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" disabled="disabled" placeholder="-" 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" disabled="disabled" placeholder="-" aria-labelledby="vector-prop-slope-label">
</div>
</div>
<div class="row">
<div class="vector-prop vector-prop-update">
<button class="update" disabled="disabled">
<span class="update-label" aria-hidden="true">{% trans "Update" %}</span>
<span class="sr">{% trans "Update properties of selected element" %}</span>
</button>
<span class="update-error">{% trans "Invalid input." %}</span>
</div>
</div>
</div>
</div>
{% endif %}
</div>
<div class="jxgboard"
style="width: {{ self.width }}px; height: {{ self.height }}px;"
aria-live="polite"></div>
</div>
<div class="vectordraw-status">
<span class="correctness icon-2x"></span>
<div class="status-message"></div>
</div>
<div class="action">
<button class="check">
<span class="check-label" aria-hidden="true">{% trans "Check" %}</span>
<span class="sr">{% trans "Check your answer" %}</span>
</button>
</div>
</div>
<vertical_demo>
<vectordraw url_name="vectordraw_example"
description="&lt;p&gt;A box remains stationary on an inclined plane at an angle of 20 degrees with respect to the x-axis. Draw all appropriate vectors that define the force diagram for this system.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Be sure that the &quot;tail&quot; of each vector starts at the center of mass of the box.&lt;/em&gt;&lt;/p&gt;"
width="550"
height="450"
bounding_box_size="8"
background_url="https://github.com/open-craft/jsinput-vectordraw/raw/master/Notes_and_Examples/2_boxIncline_multiVector/box_on_incline.png"
background_width="20"
vectors="[{&quot;name&quot;:&quot;N&quot;,&quot;description&quot;:&quot;Normal force - N&quot;,&quot;tail&quot;:[2,2],&quot;length&quot;:4,&quot;angle&quot;:45,&quot;render&quot;:true},{&quot;name&quot;:&quot;f&quot;,&quot;description&quot;:&quot;Friction - f&quot;,&quot;coords&quot;:[[-2,-5],[-1,-3]],&quot;render&quot;:false},{&quot;name&quot;:&quot;g&quot;,&quot;description&quot;:&quot;Gravity - g&quot;,&quot;tail&quot;:[0,1.5],&quot;length&quot;:5,&quot;angle&quot;:-75,&quot;render&quot;:false}]"
points="[{&quot;name&quot;:&quot;cm&quot;,&quot;coords&quot;:[-0.6,0.4],&quot;fixed&quot;:false,&quot;description&quot;:&quot;A simple point&quot;}]"
expected_result="{&quot;N&quot;:{&quot;angle&quot;:110,&quot;angle_tolerance&quot;:2,&quot;tail&quot;:[-0.6,0.4],&quot;tail_tolerance&quot;:0.5},&quot;g&quot;:{&quot;angle&quot;:270,&quot;angle_tolerance&quot;:2,&quot;tail&quot;:[-0.6,0.4],&quot;tail_tolerance&quot;:0.5},&quot;f&quot;:{&quot;angle&quot;:20,&quot;angle_tolerance&quot;:2,&quot;tail&quot;:[-0.6,0.4],&quot;tail_tolerance&quot;:0.5}}"
/>
</vertical_demo>
"""
This module contains utility functions for the Vector Drawing XBlock.
"""
def get_doc_link(section, link_text="here"):
"""
Return link to specific `section` of README for Vector Drawing exercises.
"""
return (
'<a href="https://github.com/open-craft/jsinput-vectordraw#{section}" target="_blank">'
'{link_text}'
'</a>'
).format(section=section, link_text=link_text)
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