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):
is_correct = True
feedback = item['feedback']['correct']
state = {
'top': attempt['top'],
'left': attempt['left'],
'absolute': True # flag for backwards compatibility (values used to be relative)
'x_percent': attempt['x_percent'],
'y_percent': attempt['y_percent'],
}
if state:
......@@ -300,6 +299,9 @@ class DragAndDropBlock(XBlock):
if 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
def _get_item_state(self):
......
.xblock--drag-and-drop {
width: 770px;
width: auto;
max-width: 770px;
margin: 0;
padding: 0;
background: #fff;
}
/* Header, instruction text, etc. */
.xblock--drag-and-drop .problem-header {
display: inline-block;
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 {
margin-bottom: 1.41575em;
}
.xblock--drag-and-drop .drag-container {
width: 515px;
padding: 1%;
background: #ebf0f2;
position: relative;
}
.xblock--drag-and-drop .clear {
clear: both;
}
/* Shared styles used in header and footer */
.xblock--drag-and-drop .title1 {
color: rgb(85, 85, 85);
......@@ -42,22 +27,40 @@
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 **/
.xblock--drag-and-drop .drag-container .items {
display: block;
padding: 0 !important; /* LMS tries to override this */
.xblock--drag-and-drop .item-bank {
display: flex;
flex-flow: row wrap;
/* TODO: prefix the above and test in IE 10 */
justify-content: flex-start;
align-items: center;
position: relative;
border: 1px solid rgba(0,0,0, 0.1);
border-radius: 3px;
}
.xblock--drag-and-drop .drag-container .option {
position: relative;
display: inline-block;
width: auto;
min-width: 4em;
max-width: calc(100% / 3 - 1% - 1% - 20px);
border: 1px solid transparent;
border-radius: 3px;
margin: 1%;
padding: 10px;
background: #2e83cd;
background-color: #2e83cd;
font-size: 14px;
color: #fff;
opacity: 1;
......@@ -65,10 +68,18 @@
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 {
box-shadow: 0 16px 32px 0 rgba(0,0,0,.3);
border: 1px solid #ccc;
opacity: .65;
z-index: 20 !important;
}
.xblock--drag-and-drop .drag-container .option img {
......@@ -117,18 +128,23 @@
/*** Drop Target ***/
.xblock--drag-and-drop .target {
display: block;
width: 515px;
height: 510px;
display: table;
width: 0;
/* '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;
margin-top: 1%;
background: #fff;
}
.xblock--drag-and-drop .target-img {
background: url('../img/triangle.png') no-repeat;
width: 100%;
height: 100%;
width: auto;
max-width: 100%;
height: auto;
max-height: 80vh; /* On mobile, don't ever fill the whole screen with the image */
}
.xblock--drag-and-drop .zone {
......
......@@ -5,7 +5,10 @@ function DragAndDropBlock(runtime, element) {
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 __vdom = virtualDom.h(); // blank virtual DOM
......@@ -15,37 +18,21 @@ function DragAndDropBlock(runtime, element) {
dataType: 'json'
}).done(function(data){
setState(data);
setItemsHeight();
initDroppable();
});
$(document).on('mousedown touchstart', closePopup);
$(element).on('click', '.reset-button', resetExercise);
$(element).on('click', '.submit-input', submitInput);
$element.on('click', '.reset-button', resetExercise);
$element.on('click', '.submit-input', submitInput);
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() {
return __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 !== __state.state.feedback) {
publishEvent({
......@@ -72,6 +59,7 @@ function DragAndDropBlock(runtime, element) {
var new_vdom = render(state);
var patches = virtualDom.diff(__vdom, new_vdom);
root = virtualDom.patch(root, patches);
$root = $(root);
__vdom = new_vdom;
};
......@@ -84,38 +72,43 @@ function DragAndDropBlock(runtime, element) {
};
var initDroppable = function() {
$(root).find('.zone').droppable({
accept: '.xblock--drag-and-drop .items .option',
$root.find('.zone').droppable({
accept: '.xblock--drag-and-drop .item-bank .option',
tolerance: 'pointer',
drop: function(evt, ui) {
var item_id = ui.draggable.data('value');
var zone = $(this).data('zone');
var position = ui.draggable.position();
var top = position.top + 'px';
var left = position.left + 'px';
var $target_img = $root.find('.target-img');
// 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();
state.state.items[item_id] = {
top: top,
left: left,
absolute: true,
submitting_location: true
x_percent: x_pos_percent,
y_percent: y_pos_percent,
submitting_location: true,
};
// Wrap in setTimeout to let the droppable event finish.
setTimeout(function() {
setState(state);
submitLocation(item_id, zone, top, left);
submitLocation(item_id, zone, x_pos_percent, y_pos_percent);
}, 0);
}
});
};
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 {
$(this).draggable({
containment: '.xblock--drag-and-drop .drag-container',
cursor: 'move',
stack: '.xblock--drag-and-drop .items .option',
stack: '.xblock--drag-and-drop .item-bank .option',
revert: 'invalid',
revertDuration: 150,
start: function(evt, ui) {
......@@ -134,7 +127,7 @@ function DragAndDropBlock(runtime, element) {
};
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 {
$(this).draggable('destroy');
} catch (e) {
......@@ -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) {
return;
}
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){
var state = getState();
if (data.correct_location) {
......@@ -238,32 +236,33 @@ function DragAndDropBlock(runtime, element) {
var render = function(state) {
var items = state.items.map(function(item) {
var item_state = state.state.items[item.id];
var position = item_state || {};
var input = null;
var item_user_state = state.state.items[item.id];
if (item.inputOptions) {
input = {
is_visible: item_state && !item_state.submitting_location,
has_value: Boolean(item_state && 'input' in item_state),
value : (item_state && item_state.input) || '',
class_name: undefined
is_visible: item_user_state && !item_user_state.submitting_location,
has_value: Boolean(item_user_state && 'input' in item_user_state),
value : (item_user_state && item_user_state.input) || '',
class_name: undefined,
};
if (input.has_value && !item_state.submitting_input) {
input.class_name = item_state.correct_input ? 'correct' : 'incorrect';
if (input.has_value && !item_user_state.submitting_input) {
input.class_name = item_user_state.correct_input ? 'correct' : 'incorrect';
}
}
var itemProperties = {
value: item.id,
drag_disabled: Boolean(item_state || state.state.finished),
drag_disabled: Boolean(item_user_state || state.state.finished),
width: item.size.width,
height: item.size.height,
top: position.top,
left: position.left,
position: position.absolute ? 'absolute' : 'relative',
class_name: item_state && ('input' in item_state || item_state.correct_input) ? 'fade': undefined,
class_name: item_user_state && ('input' in item_user_state || item_user_state.correct_input) ? 'fade': undefined,
input: input,
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) {
itemProperties.background_color = state.item_background_color;
}
......@@ -282,7 +281,7 @@ function DragAndDropBlock(runtime, element) {
feedback_html: $.trim(state.state.finished ? state.feedback.finish : state.feedback.start),
target_img_src: state.targetImg,
display_zone_labels: state.displayLabels,
display_reset_button: state.state.finished,
display_reset_button: Object.keys(state.state.items).length > 0,
zones: state.zones,
items: items
};
......
(function(h) {
"use strict";
var FocusHook = function() {
if (!(this instanceof FocusHook)) {
return new FocusHook();
......@@ -40,26 +40,29 @@
};
var itemTemplate = function(item) {
var style = {
width: item.width,
height: item.height,
top: item.top,
left: item.left,
position: item.position
};
var style = {};
if (item.background_color) {
style['background-color'] = item.background_color;
}
if (item.color) {
style.color = item.color;
}
if (item.is_placed) {
style.left = item.x_percent + "%";
style.top = item.y_percent + "%";
}
return (
h('div.option', {className: item.class_name,
attributes: {'data-value': item.value, 'data-drag-disabled': item.drag_disabled},
style: style}, [
h('div', {innerHTML: item.content_html}),
itemInputTemplate(item.input)
])
h('div.option',
{
key: item.value,
className: item.class_name,
attributes: {'data-value': item.value, 'data-drag-disabled': item.drag_disabled},
style: style
}, [
h('div', {innerHTML: item.content_html}),
itemInputTemplate(item.input)
]
)
);
};
......@@ -88,28 +91,30 @@
var mainTemplate = function(ctx) {
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 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 (
h('section.xblock--drag-and-drop', [
problemHeader,
h('section.problem', {role: 'application'}, [
questionHeader,
h('p', {innerHTML: ctx.question_html})
h('p', {innerHTML: ctx.question_html}),
]),
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.popup', {style: {display: ctx.popup_html ? 'block' : 'none'}}, [
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 ?
'url(' + ctx.target_img_src + ')' :
undefined}},
renderCollection(zoneTemplate, ctx.zones, ctx))
h('img.target-img', {src: ctx.target_img_src, alt: "Image Description here"}),
renderCollection(zoneTemplate, ctx.zones, ctx),
renderCollection(itemTemplate, items_placed, 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