Commit 513573a3 by Braden MacDonald

Studio editing of resizable images

parent ed9513f4
...@@ -164,7 +164,10 @@ class DragAndDropBlock(XBlock): ...@@ -164,7 +164,10 @@ class DragAndDropBlock(XBlock):
for js_url in js_urls: for js_url in js_urls:
fragment.add_javascript_url(self.runtime.local_resource_url(self, js_url)) fragment.add_javascript_url(self.runtime.local_resource_url(self, js_url))
fragment.initialize_js('DragAndDropEditBlock') fragment.initialize_js('DragAndDropEditBlock', {
'data': self.data,
'target_img_expanded_url': self.target_img_expanded_url,
})
return fragment return fragment
...@@ -267,6 +270,37 @@ class DragAndDropBlock(XBlock): ...@@ -267,6 +270,37 @@ class DragAndDropBlock(XBlock):
self.item_state = {} self.item_state = {}
return self._get_data() return self._get_data()
def _expand_static_url(self, url):
"""
This is required to make URLs like '/static/dnd-test-image.png' work (note: that is the
only portable URL format for static files that works across export/import and reruns).
This method is unfortunately a bit hackish since XBlock does not provide a low-level API
for this.
"""
if hasattr(self.runtime, 'replace_urls'):
url = self.runtime.replace_urls('"{}"'.format(url))[1:-1]
elif hasattr(self.runtime, 'course_id'):
# edX Studio uses a different runtime for 'studio_view' than 'student_view',
# and the 'studio_view' runtime doesn't provide the replace_urls API.
try:
from static_replace import replace_static_urls
url = replace_static_urls('"{}"'.format(url), None, course_id=self.runtime.course_id)[1:-1]
except ImportError:
pass
return url
@XBlock.json_handler
def expand_static_url(self, url, suffix=''):
""" AJAX-accessible handler for expanding URLs to static [image] files """
return {'url': self._expand_static_url(url)}
@property
def target_img_expanded_url(self):
""" Get the expanded URL to the target image (the image items are dragged onto). """
if self.data.get("targetImg"):
return self._expand_static_url(self.data["targetImg"])
return self.runtime.local_resource_url(self, "public/img/triangle.png")
def _get_data(self): def _get_data(self):
data = copy.deepcopy(self.data) data = copy.deepcopy(self.data)
...@@ -299,8 +333,7 @@ class DragAndDropBlock(XBlock): ...@@ -299,8 +333,7 @@ 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.target_img_expanded_url
data["targetImg"] = self.runtime.local_resource_url(self, "public/img/triangle.png")
return data return data
......
/*** xBlock styles ***/ .modal-window .xblock--drag-and-drop--editor.editor-with-buttons {
.xblock--drag-and-drop { /* Fix Studio editor height */
width: 100%;
margin: 0;
padding: 0;
background: #fff;
}
.modal-window .editor-with-buttons.xblock--drag-and-drop {
/* Fix Studio edito height */
margin-bottom: 0; margin-bottom: 0;
height: 380px; height: 380px;
} }
/** Draggable Items **/
.xblock--drag-and-drop .items {
width: 210px;
margin: 10px;
padding: 0;
font-size: 14px;
position: relative;
display: inline;
float: left;
list-style-type: none;
}
/*** Drop Target ***/ /*** Drop Target ***/
.xblock--drag-and-drop .target { .xblock--drag-and-drop--editor .zone {
width: 515px;
height: 510px;
position: relative;
display: inline;
float: left;
margin: 10px 0 15px 5px;
background: #fff;
z-index: 1;
}
.xblock--drag-and-drop .target-img {
background: url('../img/triangle.png') no-repeat;
width: 100%;
height: 100%;
}
.xblock--drag-and-drop .zone {
position: absolute; position: absolute;
display: -webkit-box; display: -webkit-box;
...@@ -70,7 +32,7 @@ ...@@ -70,7 +32,7 @@
} }
.xblock--drag-and-drop .zone p { .xblock--drag-and-drop--editor .zone p {
width: 100%; width: 100%;
font-family: Arial; font-family: Arial;
font-size: 16px; font-size: 16px;
...@@ -82,153 +44,172 @@ ...@@ -82,153 +44,172 @@
} }
/*** IE9 alignment fix ***/ /*** IE9 alignment fix ***/
.lt-ie10 .xblock--drag-and-drop .zone { .lt-ie10 .xblock--drag-and-drop--editor .zone {
display: table; display: table;
} }
.lt-ie10 .xblock--drag-and-drop .zone p { .lt-ie10 .xblock--drag-and-drop--editor .zone p {
display: table-cell; display: table-cell;
vertical-align: middle; vertical-align: middle;
text-align: center; text-align: center;
} }
/** Builder **/ /** Builder **/
.xblock--drag-and-drop .hidden { .xblock--drag-and-drop--editor .hidden {
display: none !important; display: none !important;
} }
.xblock--drag-and-drop .drag-builder { .xblock--drag-and-drop--editor {
height: 100%; height: 100%;
overflow: scroll; overflow: scroll;
} }
.xblock--drag-and-drop .drag-builder .tab { .xblock--drag-and-drop--editor .tab {
width: 100%; width: 100%;
background: #eee; background: #eee;
padding: 3px 0; padding: 3px 0;
position: relative; position: relative;
} }
.xblock--drag-and-drop .drag-builder .tab:after, .xblock--drag-and-drop--editor .tab:after,
.xblock--drag-and-drop .drag-builder .tab-footer:after, .xblock--drag-and-drop--editor .tab-footer:after,
.xblock--drag-and-drop .drag-builder .target:after { .xblock--drag-and-drop--editor .target:after {
content: ""; content: "";
display: table; display: table;
clear: both; clear: both;
} }
.xblock--drag-and-drop .drag-builder .tab h3 { .xblock--drag-and-drop--editor .tab h3 {
margin: 20px 0 8px 0; margin: 20px 0 8px 0;
} }
.xblock--drag-and-drop .drag-builder .tab-header, .xblock--drag-and-drop--editor .tab-header,
.xblock--drag-and-drop .drag-builder .tab-content, .xblock--drag-and-drop--editor .tab-content,
.xblock--drag-and-drop .drag-builder .tab-footer { .xblock--drag-and-drop--editor .tab-footer {
width: 96%; width: 96%;
margin: 2%; margin: 2%;
} }
.xblock--drag-and-drop .drag-builder .tab-footer { .xblock--drag-and-drop--editor .tab-footer {
height: 25px; height: 25px;
position: relative; position: relative;
display: block; display: block;
float: left; float: left;
} }
.xblock--drag-and-drop .drag-builder .items { .xblock--drag-and-drop--editor .items {
width: calc(100% - 515px); width: calc(100% - 515px);
margin: 10px 0 0 0; margin: 10px 0 0 0;
} }
.xblock--drag-and-drop .drag-builder .target { .xblock--drag-and-drop--editor .target-image-form input {
margin-left: 0; width: 50%;
} }
.xblock--drag-and-drop .drag-builder .target-image-form input { /* Zones Tab */
width: 50%; .xblock--drag-and-drop--editor .zones-tab .zone-editor {
position: relative;
}
.xblock--drag-and-drop--editor .zones-tab .tab-content .controls {
width: 40%;
}
.xblock--drag-and-drop--editor .zones-tab .tab-content .target {
position: absolute;
left: 40%;
top: 0;
right: 0;
bottom: 0;
border: 1px solid #ccc;
}
.xblock--drag-and-drop--editor .zones-tab .tab-content .target-img {
width: auto;
height: auto;
max-width: 100%;
} }
.xblock--drag-and-drop .zones-form .zone-row label { .xblock--drag-and-drop--editor .zones-form .zone-row label {
display: inline-block; display: inline-block;
width: 18%; width: 18%;
} }
.xblock--drag-and-drop .zones-form .zone-row .title { .xblock--drag-and-drop--editor .zones-form .zone-row .title {
width: 60%; width: 60%;
margin: 0 0 5px; margin: 0 0 5px;
} }
.xblock--drag-and-drop .zones-form .zone-row .layout { .xblock--drag-and-drop--editor .zones-form .zone-row .layout {
margin-bottom: 15px; margin-bottom: 15px;
} }
.xblock--drag-and-drop .zones-form .zone-row .layout .size, .xblock--drag-and-drop--editor .zones-form .zone-row .layout .size,
.xblock--drag-and-drop .zones-form .zone-row .layout .coord { .xblock--drag-and-drop--editor .zones-form .zone-row .layout .coord {
width: 15%; width: 15%;
margin: 0 19px 5px 0; margin: 0 19px 5px 0;
} }
.xblock--drag-and-drop .drag-builder .target { .xblock--drag-and-drop--editor .target {
margin-bottom: 40px; margin-bottom: 40px;
} }
.xblock--drag-and-drop .drag-builder .zone { .xblock--drag-and-drop--editor .zone {
border: 1px dotted #666; border: 1px dotted #666;
} }
.xblock--drag-and-drop .feedback-form textarea { .xblock--drag-and-drop--editor .feedback-form textarea {
width: 99%; width: 99%;
height: 128px; height: 128px;
} }
.xblock--drag-and-drop .item-styles-form .item-styles-form-help { .xblock--drag-and-drop--editor .item-styles-form .item-styles-form-help {
margin-top: 5px; margin-top: 5px;
font-size: small; font-size: small;
} }
.xblock--drag-and-drop .item-styles-form, .xblock--drag-and-drop--editor .item-styles-form,
.xblock--drag-and-drop .items-form { .xblock--drag-and-drop--editor .items-form {
margin-bottom: 30px; margin-bottom: 30px;
} }
.xblock--drag-and-drop .items-form .item { .xblock--drag-and-drop--editor .items-form .item {
background: #73bde7; background: #73bde7;
padding: 10px 0 1px; padding: 10px 0 1px;
margin: 15px 0; margin: 15px 0;
} }
.xblock--drag-and-drop .items-form label { .xblock--drag-and-drop--editor .items-form label {
margin: 0 1%; margin: 0 1%;
} }
.xblock--drag-and-drop .items-form input, .xblock--drag-and-drop--editor .items-form input,
.xblock--drag-and-drop .items-form select { .xblock--drag-and-drop--editor .items-form select {
width: 35%; width: 35%;
} }
.xblock--drag-and-drop .items-form .item-width, .xblock--drag-and-drop--editor .items-form .item-width,
.xblock--drag-and-drop .items-form .item-height { .xblock--drag-and-drop--editor .items-form .item-height {
width: 40px; width: 40px;
} }
.xblock--drag-and-drop .items-form .item-numerical-value, .xblock--drag-and-drop--editor .items-form .item-numerical-value,
.xblock--drag-and-drop .items-form .item-numerical-margin { .xblock--drag-and-drop--editor .items-form .item-numerical-margin {
width: 60px; width: 60px;
} }
.xblock--drag-and-drop .items-form textarea { .xblock--drag-and-drop--editor .items-form textarea {
width: 97%; width: 97%;
margin: 0 1%; margin: 0 1%;
} }
.xblock--drag-and-drop .items-form .row { .xblock--drag-and-drop--editor .items-form .row {
margin-bottom: 20px; margin-bottom: 20px;
} }
/** Buttons **/ /** Buttons **/
.xblock--drag-and-drop .btn { .xblock--drag-and-drop--editor .btn {
background: #2e83cd; background: #2e83cd;
color: #fff; color: #fff;
border: 1px solid #156ab4; border: 1px solid #156ab4;
...@@ -236,33 +217,33 @@ ...@@ -236,33 +217,33 @@
padding: 5px 10px; padding: 5px 10px;
} }
.xblock--drag-and-drop .btn:hover { .xblock--drag-and-drop--editor .btn:hover {
opacity: 0.8; opacity: 0.8;
cursor: pointer; cursor: pointer;
} }
.xblock--drag-and-drop .btn:focus { .xblock--drag-and-drop--editor .btn:focus {
outline: none; outline: none;
opacity: 0.5; opacity: 0.5;
} }
.xblock--drag-and-drop .add-element { .xblock--drag-and-drop--editor .add-element {
text-decoration: none; text-decoration: none;
color: #2e83cd; color: #2e83cd;
} }
.xblock--drag-and-drop .remove-zone { .xblock--drag-and-drop--editor .remove-zone {
float: right; float: right;
margin-top: 2px; margin-top: 2px;
margin-right: 16px; margin-right: 16px;
} }
.xblock--drag-and-drop .remove-item { .xblock--drag-and-drop--editor .remove-item {
display: inline-block; display: inline-block;
margin-left: 95px; margin-left: 95px;
} }
.xblock--drag-and-drop .icon { .xblock--drag-and-drop--editor .icon {
width: 14px; width: 14px;
height: 14px; height: 14px;
border-radius: 7px; border-radius: 7px;
...@@ -272,14 +253,14 @@ ...@@ -272,14 +253,14 @@
margin: 0 5px 0 0; margin: 0 5px 0 0;
} }
.xblock--drag-and-drop .add-zone:hover, .xblock--drag-and-drop--editor .add-zone:hover,
.xblock--drag-and-drop .add-zone:hover .icon, .xblock--drag-and-drop--editor .add-zone:hover .icon,
.xblock--drag-and-drop .remove-zone:hover, .xblock--drag-and-drop--editor .remove-zone:hover,
.xblock--drag-and-drop .remove-zone:hover .icon { .xblock--drag-and-drop--editor .remove-zone:hover .icon {
opacity: 0.7; opacity: 0.7;
} }
.xblock--drag-and-drop .icon.add:before { .xblock--drag-and-drop--editor .icon.add:before {
content: ''; content: '';
height: 10px; height: 10px;
width: 2px; width: 2px;
...@@ -291,7 +272,7 @@ ...@@ -291,7 +272,7 @@
left: 6px; left: 6px;
} }
.xblock--drag-and-drop .icon.add:after { .xblock--drag-and-drop--editor .icon.add:after {
content: ''; content: '';
height: 2px; height: 2px;
width: 10px; width: 10px;
...@@ -303,7 +284,7 @@ ...@@ -303,7 +284,7 @@
left: 0; left: 0;
} }
.xblock--drag-and-drop .icon.remove:before { .xblock--drag-and-drop--editor .icon.remove:before {
content: ''; content: '';
height: 10px; height: 10px;
width: 2px; width: 2px;
...@@ -318,7 +299,7 @@ ...@@ -318,7 +299,7 @@
transform: rotate(45deg); transform: rotate(45deg);
} }
.xblock--drag-and-drop .icon.remove:after { .xblock--drag-and-drop--editor .icon.remove:after {
content: ''; content: '';
height: 2px; height: 2px;
width: 10px; width: 10px;
...@@ -333,10 +314,10 @@ ...@@ -333,10 +314,10 @@
transform: rotate(45deg); transform: rotate(45deg);
} }
.xblock--drag-and-drop .remove-item .icon.remove { .xblock--drag-and-drop--editor .remove-item .icon.remove {
background: #fff; background: #fff;
} }
.xblock--drag-and-drop .remove-item .icon.remove:before, .xblock--drag-and-drop--editor .remove-item .icon.remove:before,
.xblock--drag-and-drop .remove-item .icon.remove:after { .xblock--drag-and-drop--editor .remove-item .icon.remove:after {
background: #2e83cd; background: #2e83cd;
} }
function DragAndDropEditBlock(runtime, element) { function DragAndDropEditBlock(runtime, element, params) {
// Set up gettext in case it isn't available in the client runtime: // Set up gettext in case it isn't available in the client runtime:
if (typeof gettext == "undefined") { if (typeof gettext == "undefined") {
...@@ -14,7 +14,7 @@ function DragAndDropEditBlock(runtime, element) { ...@@ -14,7 +14,7 @@ function DragAndDropEditBlock(runtime, element) {
var _fn = { var _fn = {
// DOM Elements // DOM Elements
$target: $('.xblock--drag-and-drop .target-img', element), $target: $('.target-img', element),
tpl: { tpl: {
init: function() { init: function() {
...@@ -30,21 +30,21 @@ function DragAndDropEditBlock(runtime, element) { ...@@ -30,21 +30,21 @@ function DragAndDropEditBlock(runtime, element) {
build: { build: {
$el: { $el: {
feedback: { feedback: {
form: $('.xblock--drag-and-drop .drag-builder .feedback-form', element), form: $('.drag-builder .feedback-form', element),
tab: $('.xblock--drag-and-drop .drag-builder .feedback-tab', element) tab: $('.drag-builder .feedback-tab', element)
}, },
zones: { zones: {
form: $('.xblock--drag-and-drop .drag-builder .zones-form', element), form: $('.drag-builder .zones-form', element),
tab: $('.xblock--drag-and-drop .drag-builder .zones-tab', element) tab: $('.drag-builder .zones-tab', element)
}, },
items: { items: {
form: $('.xblock--drag-and-drop .drag-builder .items-form', element), form: $('.drag-builder .items-form', element),
tab: $('.xblock--drag-and-drop .drag-builder .items-tab', element) tab: $('.drag-builder .items-tab', element)
}, },
target: $('.xblock--drag-and-drop .drag-builder .target-img', element) target: $('.drag-builder .target', element)
}, },
init: function(data) { init: function() {
_fn.data = data; _fn.data = params.data;
// Compile templates // Compile templates
_fn.tpl.init(); _fn.tpl.init();
...@@ -71,9 +71,9 @@ function DragAndDropEditBlock(runtime, element) { ...@@ -71,9 +71,9 @@ function DragAndDropEditBlock(runtime, element) {
_fn.build.form.zone.add(); _fn.build.form.zone.add();
} }
if (_fn.data.targetImg) { // Set the target image:
_fn.$target.css('background', 'url(' + _fn.data.targetImg + ') no-repeat'); $('.target-image-form input', element).val(_fn.data.targetImg);
} $('.target-img', element).attr('src', params.target_img_expanded_url);
if (_fn.data.displayLabels) { if (_fn.data.displayLabels) {
$('.display-labels-form input', element).prop('checked', true); $('.display-labels-form input', element).prop('checked', true);
...@@ -122,8 +122,14 @@ function DragAndDropEditBlock(runtime, element) { ...@@ -122,8 +122,14 @@ function DragAndDropEditBlock(runtime, element) {
.on('click', '.target-image-form button', function(e) { .on('click', '.target-image-form button', function(e) {
e.preventDefault(); e.preventDefault();
_fn.data.targetImg = $('.target-image-form input', element).val(); var new_img_url = $('.target-image-form input', element).val();
_fn.$target.css('background', 'url(' + _fn.data.targetImg + ') no-repeat'); _fn.data.targetImg = new_img_url;
// We may need to 'expand' the URL before it will be valid.
// e.g. '/static/blah.png' becomes '/asset-v1:course+id/blah.png'
var handlerUrl = runtime.handlerUrl(element, 'expand_static_url');
$.post(handlerUrl, JSON.stringify(new_img_url), function(result) {
_fn.$target.attr('src', result.url);
}).error(function(a, b, c) { console.log(a, b, c); });
// Placeholder shim for IE9 // Placeholder shim for IE9
$.placeholder.shim(); $.placeholder.shim();
...@@ -450,7 +456,7 @@ function DragAndDropEditBlock(runtime, element) { ...@@ -450,7 +456,7 @@ function DragAndDropEditBlock(runtime, element) {
}; };
return { return {
builder: _fn.build.init init: _fn.build.init
}; };
})(jQuery); })(jQuery);
...@@ -458,5 +464,5 @@ function DragAndDropEditBlock(runtime, element) { ...@@ -458,5 +464,5 @@ function DragAndDropEditBlock(runtime, element) {
runtime.notify('cancel', {}); runtime.notify('cancel', {});
}); });
dragAndDrop.builder(window.DragAndDropV2BlockPreviousData); dragAndDrop.init();
} }
...@@ -114,7 +114,6 @@ ...@@ -114,7 +114,6 @@
renderCollection(zoneTemplate, ctx.zones, ctx), renderCollection(zoneTemplate, ctx.zones, ctx),
renderCollection(itemTemplate, items_placed, ctx), renderCollection(itemTemplate, items_placed, ctx),
]), ]),
//h('div.clear'),
]), ]),
feedbackTemplate(ctx), feedbackTemplate(ctx),
]) ])
......
{% load i18n %} {% load i18n %}
<div class="xblock--drag-and-drop editor-with-buttons"> <div class="xblock--drag-and-drop--editor editor-with-buttons">
{{ js_templates|safe }} {{ js_templates|safe }}
<script type="text/javascript">
var DragAndDropV2BlockPreviousData = JSON.parse(decodeURIComponent('{{ data|safe }}'));
</script>
<section class="drag-builder"> <section class="drag-builder">
<div class="tab feedback-tab"> <div class="tab feedback-tab">
<p class="tab-content"> <p class="tab-content">
...@@ -57,12 +53,14 @@ ...@@ -57,12 +53,14 @@
<label for="display-labels">{% trans "Display label names on the image" %}:</label> <label for="display-labels">{% trans "Display label names on the image" %}:</label>
<input name="display-labels" id="display-labels" type="checkbox" /> <input name="display-labels" id="display-labels" type="checkbox" />
</section> </section>
<div class="zone-editor">
<div class="items"> <div class="items">
<form class="zones-form"></form> <form class="zones-form"></form>
<a href="#" class="add-zone add-element"><div class="icon add"></div>{% trans "Add a zone" %}</a> <a href="#" class="add-zone add-element"><div class="icon add"></div>{% trans "Add a zone" %}</a>
</div> </div>
<div class="target"> <div class="target">
<div class="target-img"></div> <img class="target-img">
</div>
</div> </div>
</section> </section>
</div> </div>
......
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