Commit a1852925 by Braden MacDonald

Make the image resize to fit the container, make item placement relative

parent 1ace6d32
...@@ -220,9 +220,8 @@ class DragAndDropBlock(XBlock): ...@@ -220,9 +220,8 @@ class DragAndDropBlock(XBlock):
is_correct = True is_correct = True
feedback = item['feedback']['correct'] feedback = item['feedback']['correct']
state = { state = {
'top': attempt['top'], 'x_percent': attempt['x_percent'],
'left': attempt['left'], 'y_percent': attempt['y_percent'],
'absolute': True # flag for backwards compatibility (values used to be relative)
} }
if state: if state:
...@@ -300,6 +299,9 @@ class DragAndDropBlock(XBlock): ...@@ -300,6 +299,9 @@ class DragAndDropBlock(XBlock):
if self.item_text_color: if self.item_text_color:
data['item_text_color'] = self.item_text_color data['item_text_color'] = self.item_text_color
if not data.get("targetImg"):
data["targetImg"] = self.runtime.local_resource_url(self, "public/img/triangle.png")
return data return data
def _get_item_state(self): def _get_item_state(self):
......
.xblock--drag-and-drop { .xblock--drag-and-drop {
width: 770px; width: auto;
max-width: 770px;
margin: 0; margin: 0;
padding: 0; padding: 0;
background: #fff;
} }
/* Header, instruction text, etc. */
.xblock--drag-and-drop .problem-header { .xblock--drag-and-drop .problem-header {
display: inline-block; display: inline-block;
margin: 0 0 15px 0; margin: 0 0 15px 0;
} }
.xblock--drag-and-drop .problem-progress {
display: inline-block;
padding-left: 5px;
color: #666;
font-weight: 100;
font-size: 1em;
}
.xblock--drag-and-drop .problem p { .xblock--drag-and-drop .problem p {
margin-bottom: 1.41575em; margin-bottom: 1.41575em;
} }
.xblock--drag-and-drop .drag-container { /* Shared styles used in header and footer */
width: 515px;
padding: 1%;
background: #ebf0f2;
position: relative;
}
.xblock--drag-and-drop .clear {
clear: both;
}
.xblock--drag-and-drop .title1 { .xblock--drag-and-drop .title1 {
color: rgb(85, 85, 85); color: rgb(85, 85, 85);
...@@ -42,22 +27,40 @@ ...@@ -42,22 +27,40 @@
margin-top: 20px; margin-top: 20px;
} }
/* drag-container holds the .items and the .target image */
.xblock--drag-and-drop .drag-container {
width: auto;
padding: 5px;
background: #ebf0f2;
}
/*.xblock--drag-and-drop .clear {
clear: both;
}*/
/** Draggable Items **/ /** Draggable Items **/
.xblock--drag-and-drop .drag-container .items { .xblock--drag-and-drop .item-bank {
display: block; display: flex;
padding: 0 !important; /* LMS tries to override this */ flex-flow: row wrap;
/* TODO: prefix the above and test in IE 10 */
justify-content: flex-start;
align-items: center;
position: relative; position: relative;
border: 1px solid rgba(0,0,0, 0.1);
border-radius: 3px;
} }
.xblock--drag-and-drop .drag-container .option { .xblock--drag-and-drop .drag-container .option {
position: relative;
display: inline-block; display: inline-block;
width: auto; width: auto;
min-width: 4em;
max-width: calc(100% / 3 - 1% - 1% - 20px);
border: 1px solid transparent;
border-radius: 3px; border-radius: 3px;
margin: 1%; margin: 1%;
padding: 10px; padding: 10px;
background: #2e83cd; background-color: #2e83cd;
font-size: 14px; font-size: 14px;
color: #fff; color: #fff;
opacity: 1; opacity: 1;
...@@ -65,10 +68,18 @@ ...@@ -65,10 +68,18 @@
z-index: 10 !important; z-index: 10 !important;
} }
/* Placed option */
.xblock--drag-and-drop .drag-container .target .option {
position: absolute;
margin: 0;
transform: translate(-50%, -50%); /* These blocks are to be centered on their absolute x,y position */
}
.xblock--drag-and-drop .drag-container .ui-draggable-dragging { .xblock--drag-and-drop .drag-container .ui-draggable-dragging {
box-shadow: 0 16px 32px 0 rgba(0,0,0,.3); box-shadow: 0 16px 32px 0 rgba(0,0,0,.3);
border: 1px solid #ccc; border: 1px solid #ccc;
opacity: .65; opacity: .65;
z-index: 20 !important;
} }
.xblock--drag-and-drop .drag-container .option img { .xblock--drag-and-drop .drag-container .option img {
...@@ -117,18 +128,23 @@ ...@@ -117,18 +128,23 @@
/*** Drop Target ***/ /*** Drop Target ***/
.xblock--drag-and-drop .target { .xblock--drag-and-drop .target {
display: block; display: table;
width: 515px; width: 0;
height: 510px; /* 'display: table' & 'width: 0' make this have the smallest size that fits the .target-img
while still allowing the image to use 'max-width: 100%' and scale proportionally.
The end result is that this element has the same widdth and height as the image, so we
can use it as a 'position: relative' anchor for the placed elements. */
height: auto;
position: relative; position: relative;
margin-top: 1%; margin-top: 1%;
background: #fff; background: #fff;
} }
.xblock--drag-and-drop .target-img { .xblock--drag-and-drop .target-img {
background: url('../img/triangle.png') no-repeat; width: auto;
width: 100%; max-width: 100%;
height: 100%; height: auto;
max-height: 80vh; /* On mobile, don't ever fill the whole screen with the image */
} }
.xblock--drag-and-drop .zone { .xblock--drag-and-drop .zone {
......
...@@ -5,7 +5,10 @@ function DragAndDropBlock(runtime, element) { ...@@ -5,7 +5,10 @@ function DragAndDropBlock(runtime, element) {
window.gettext = function gettext_stub(string) { return string; }; window.gettext = function gettext_stub(string) { return string; };
} }
var root = $(element).find('.xblock--drag-and-drop')[0]; var $element = $(element);
// root: root node managed by the virtual DOM
var root = $element.find('.xblock--drag-and-drop')[0];
var $root = $(root);
var __state; var __state;
var __vdom = virtualDom.h(); // blank virtual DOM var __vdom = virtualDom.h(); // blank virtual DOM
...@@ -15,37 +18,21 @@ function DragAndDropBlock(runtime, element) { ...@@ -15,37 +18,21 @@ function DragAndDropBlock(runtime, element) {
dataType: 'json' dataType: 'json'
}).done(function(data){ }).done(function(data){
setState(data); setState(data);
setItemsHeight();
initDroppable(); initDroppable();
}); });
$(document).on('mousedown touchstart', closePopup); $(document).on('mousedown touchstart', closePopup);
$(element).on('click', '.reset-button', resetExercise); $element.on('click', '.reset-button', resetExercise);
$(element).on('click', '.submit-input', submitInput); $element.on('click', '.submit-input', submitInput);
publishEvent({event_type: 'xblock.drag-and-drop-v2.loaded'}); publishEvent({event_type: 'xblock.drag-and-drop-v2.loaded'});
}; };
var setItemsHeight = function() {
var itemsHeight = $('.items', element).height();
// Need to update the DOM here, otherwise .items will resize when first item is moved to target
$('.items', element).height(itemsHeight);
__state.itemsHeight = itemsHeight;
};
var getState = function() { var getState = function() {
return __state; return __state;
}; };
var setState = function(new_state) { var setState = function(new_state) {
var itemsHeight;
if (__state === undefined) {
itemsHeight = 'auto';
} else {
itemsHeight = __state.itemsHeight;
}
new_state.itemsHeight = itemsHeight;
if (new_state.state.feedback) { if (new_state.state.feedback) {
if (new_state.state.feedback !== __state.state.feedback) { if (new_state.state.feedback !== __state.state.feedback) {
publishEvent({ publishEvent({
...@@ -72,6 +59,7 @@ function DragAndDropBlock(runtime, element) { ...@@ -72,6 +59,7 @@ function DragAndDropBlock(runtime, element) {
var new_vdom = render(state); var new_vdom = render(state);
var patches = virtualDom.diff(__vdom, new_vdom); var patches = virtualDom.diff(__vdom, new_vdom);
root = virtualDom.patch(root, patches); root = virtualDom.patch(root, patches);
$root = $(root);
__vdom = new_vdom; __vdom = new_vdom;
}; };
...@@ -84,38 +72,43 @@ function DragAndDropBlock(runtime, element) { ...@@ -84,38 +72,43 @@ function DragAndDropBlock(runtime, element) {
}; };
var initDroppable = function() { var initDroppable = function() {
$(root).find('.zone').droppable({ $root.find('.zone').droppable({
accept: '.xblock--drag-and-drop .items .option', accept: '.xblock--drag-and-drop .item-bank .option',
tolerance: 'pointer', tolerance: 'pointer',
drop: function(evt, ui) { drop: function(evt, ui) {
var item_id = ui.draggable.data('value'); var item_id = ui.draggable.data('value');
var zone = $(this).data('zone'); var zone = $(this).data('zone');
var position = ui.draggable.position(); var $target_img = $root.find('.target-img');
var top = position.top + 'px';
var left = position.left + 'px'; // Calculate the position of the center of the dropped element relative to
// the image.
var x_pos = (ui.helper.offset().left + (ui.helper.outerWidth()/2) - $target_img.offset().left);
var x_pos_percent = x_pos / $target_img.width() * 100;
var y_pos = (ui.helper.offset().top + (ui.helper.outerHeight()/2) - $target_img.offset().top);
var y_pos_percent = y_pos / $target_img.height() * 100;
var state = getState(); var state = getState();
state.state.items[item_id] = { state.state.items[item_id] = {
top: top, x_percent: x_pos_percent,
left: left, y_percent: y_pos_percent,
absolute: true, submitting_location: true,
submitting_location: true
}; };
// Wrap in setTimeout to let the droppable event finish. // Wrap in setTimeout to let the droppable event finish.
setTimeout(function() { setTimeout(function() {
setState(state); setState(state);
submitLocation(item_id, zone, top, left); submitLocation(item_id, zone, x_pos_percent, y_pos_percent);
}, 0); }, 0);
} }
}); });
}; };
var initDraggable = function() { var initDraggable = function() {
$(root).find('.items .option').not('[data-drag-disabled=true]').each(function() { $root.find('.item-bank .option').not('[data-drag-disabled=true]').each(function() {
try { try {
$(this).draggable({ $(this).draggable({
containment: '.xblock--drag-and-drop .drag-container', containment: '.xblock--drag-and-drop .drag-container',
cursor: 'move', cursor: 'move',
stack: '.xblock--drag-and-drop .items .option', stack: '.xblock--drag-and-drop .item-bank .option',
revert: 'invalid', revert: 'invalid',
revertDuration: 150, revertDuration: 150,
start: function(evt, ui) { start: function(evt, ui) {
...@@ -134,7 +127,7 @@ function DragAndDropBlock(runtime, element) { ...@@ -134,7 +127,7 @@ function DragAndDropBlock(runtime, element) {
}; };
var destroyDraggable = function() { var destroyDraggable = function() {
$(root).find('.items .option[data-drag-disabled=true]').each(function() { $root.find('.item-bank .option[data-drag-disabled=true]').each(function() {
try { try {
$(this).draggable('destroy'); $(this).draggable('destroy');
} catch (e) { } catch (e) {
...@@ -144,12 +137,17 @@ function DragAndDropBlock(runtime, element) { ...@@ -144,12 +137,17 @@ function DragAndDropBlock(runtime, element) {
}); });
}; };
var submitLocation = function(item_id, zone, top, left) { var submitLocation = function(item_id, zone, x_percent, y_percent) {
if (!zone) { if (!zone) {
return; return;
} }
var url = runtime.handlerUrl(element, 'do_attempt'); var url = runtime.handlerUrl(element, 'do_attempt');
var data = {val: item_id, zone: zone, top: top, left: left}; var data = {
val: item_id,
zone: zone,
x_percent: x_percent,
y_percent: y_percent,
};
$.post(url, JSON.stringify(data), 'json').done(function(data){ $.post(url, JSON.stringify(data), 'json').done(function(data){
var state = getState(); var state = getState();
if (data.correct_location) { if (data.correct_location) {
...@@ -238,32 +236,33 @@ function DragAndDropBlock(runtime, element) { ...@@ -238,32 +236,33 @@ function DragAndDropBlock(runtime, element) {
var render = function(state) { var render = function(state) {
var items = state.items.map(function(item) { var items = state.items.map(function(item) {
var item_state = state.state.items[item.id];
var position = item_state || {};
var input = null; var input = null;
var item_user_state = state.state.items[item.id];
if (item.inputOptions) { if (item.inputOptions) {
input = { input = {
is_visible: item_state && !item_state.submitting_location, is_visible: item_user_state && !item_user_state.submitting_location,
has_value: Boolean(item_state && 'input' in item_state), has_value: Boolean(item_user_state && 'input' in item_user_state),
value : (item_state && item_state.input) || '', value : (item_user_state && item_user_state.input) || '',
class_name: undefined class_name: undefined,
}; };
if (input.has_value && !item_state.submitting_input) { if (input.has_value && !item_user_state.submitting_input) {
input.class_name = item_state.correct_input ? 'correct' : 'incorrect'; input.class_name = item_user_state.correct_input ? 'correct' : 'incorrect';
} }
} }
var itemProperties = { var itemProperties = {
value: item.id, value: item.id,
drag_disabled: Boolean(item_state || state.state.finished), drag_disabled: Boolean(item_user_state || state.state.finished),
width: item.size.width, width: item.size.width,
height: item.size.height, height: item.size.height,
top: position.top, class_name: item_user_state && ('input' in item_user_state || item_user_state.correct_input) ? 'fade': undefined,
left: position.left,
position: position.absolute ? 'absolute' : 'relative',
class_name: item_state && ('input' in item_state || item_state.correct_input) ? 'fade': undefined,
input: input, input: input,
content_html: item.backgroundImage ? '<img src="' + item.backgroundImage + '"/>' : item.displayName content_html: item.backgroundImage ? '<img src="' + item.backgroundImage + '"/>' : item.displayName
}; };
if (item_user_state) {
itemProperties.is_placed = true;
itemProperties.x_percent = item_user_state.x_percent;
itemProperties.y_percent = item_user_state.y_percent;
}
if (state.item_background_color) { if (state.item_background_color) {
itemProperties.background_color = state.item_background_color; itemProperties.background_color = state.item_background_color;
} }
...@@ -282,7 +281,7 @@ function DragAndDropBlock(runtime, element) { ...@@ -282,7 +281,7 @@ function DragAndDropBlock(runtime, element) {
feedback_html: $.trim(state.state.finished ? state.feedback.finish : state.feedback.start), feedback_html: $.trim(state.state.finished ? state.feedback.finish : state.feedback.start),
target_img_src: state.targetImg, target_img_src: state.targetImg,
display_zone_labels: state.displayLabels, display_zone_labels: state.displayLabels,
display_reset_button: state.state.finished, display_reset_button: Object.keys(state.state.items).length > 0,
zones: state.zones, zones: state.zones,
items: items items: items
}; };
......
(function(h) { (function(h) {
"use strict";
var FocusHook = function() { var FocusHook = function() {
if (!(this instanceof FocusHook)) { if (!(this instanceof FocusHook)) {
return new FocusHook(); return new FocusHook();
...@@ -40,26 +40,29 @@ ...@@ -40,26 +40,29 @@
}; };
var itemTemplate = function(item) { var itemTemplate = function(item) {
var style = { var style = {};
width: item.width,
height: item.height,
top: item.top,
left: item.left,
position: item.position
};
if (item.background_color) { if (item.background_color) {
style['background-color'] = item.background_color; style['background-color'] = item.background_color;
} }
if (item.color) { if (item.color) {
style.color = item.color; style.color = item.color;
} }
if (item.is_placed) {
style.left = item.x_percent + "%";
style.top = item.y_percent + "%";
}
return ( return (
h('div.option', {className: item.class_name, h('div.option',
{
key: item.value,
className: item.class_name,
attributes: {'data-value': item.value, 'data-drag-disabled': item.drag_disabled}, attributes: {'data-value': item.value, 'data-drag-disabled': item.drag_disabled},
style: style}, [ style: style
}, [
h('div', {innerHTML: item.content_html}), h('div', {innerHTML: item.content_html}),
itemInputTemplate(item.input) itemInputTemplate(item.input)
]) ]
)
); );
}; };
...@@ -88,28 +91,30 @@ ...@@ -88,28 +91,30 @@
var mainTemplate = function(ctx) { var mainTemplate = function(ctx) {
var problemHeader = ctx.show_title ? h('h2.problem-header', {innerHTML: ctx.header_html}) : null; var problemHeader = ctx.show_title ? h('h2.problem-header', {innerHTML: ctx.header_html}) : null;
var questionHeader = ctx.show_question_header ? h('h3.title1', gettext('Question')) : null; var questionHeader = ctx.show_question_header ? h('h3.title1', gettext('Question')) : null;
var is_item_placed = function(i) { return i.is_placed; };
var items_placed = $.grep(ctx.items, is_item_placed);
var items_in_bank = $.grep(ctx.items, is_item_placed, true);
return ( return (
h('section.xblock--drag-and-drop', [ h('section.xblock--drag-and-drop', [
problemHeader, problemHeader,
h('section.problem', {role: 'application'}, [ h('section.problem', {role: 'application'}, [
questionHeader, questionHeader,
h('p', {innerHTML: ctx.question_html}) h('p', {innerHTML: ctx.question_html}),
]), ]),
h('section.drag-container', [ h('section.drag-container', [
h('div.items', {height: ctx.itemsHeight}, renderCollection(itemTemplate, ctx.items, ctx)), h('div.item-bank', renderCollection(itemTemplate, items_in_bank, ctx)),
h('div.target', [ h('div.target', [
h('div.popup', {style: {display: ctx.popup_html ? 'block' : 'none'}}, [ h('div.popup', {style: {display: ctx.popup_html ? 'block' : 'none'}}, [
h('div.close.icon-remove-sign.fa-times-circle'), h('div.close.icon-remove-sign.fa-times-circle'),
h('p.popup-content', {innerHTML: ctx.popup_html}) h('p.popup-content', {innerHTML: ctx.popup_html}),
]), ]),
h('div.target-img', {style: {backgroundImage: ctx.target_img_src ? h('img.target-img', {src: ctx.target_img_src, alt: "Image Description here"}),
'url(' + ctx.target_img_src + ')' : renderCollection(zoneTemplate, ctx.zones, ctx),
undefined}}, renderCollection(itemTemplate, items_placed, ctx),
renderCollection(zoneTemplate, ctx.zones, ctx))
]), ]),
h('div.clear') //h('div.clear'),
]), ]),
feedbackTemplate(ctx) feedbackTemplate(ctx),
]) ])
); );
}; };
......
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