Commit 8ca10a83 by Calen Pennington

Separate unit page from vertical implementation for editing

parent 82a28f06
......@@ -43,6 +43,9 @@ from cache_toolbox.core import set_cached_content, get_cached_content, del_cache
log = logging.getLogger(__name__)
COMPONENT_TYPES = ['customtag', 'discussion', 'html', 'problem', 'video']
# ==== Public views ==================================================
@ensure_csrf_cookie
......@@ -142,43 +145,54 @@ def edit_unit(request, location):
else:
lms_link = None
<<<<<<< HEAD
return render_to_response('unit.html', {
'module': item,
'editable_preview': get_module_previews(request, item)[0],
})
=======
component_templates = defaultdict(list)
>>>>>>> Separate unit page from vertical implementation for editing
templates = modulestore().get_items(Location('i4x', 'edx', 'templates'))
for template in templates:
if template.location.category in COMPONENT_TYPES:
component_templates[template.location.category].append((
template.display_name,
template.location.url(),
))
components = [
component.location.url()
for component
in item.get_children()
]
@login_required
def delete_unit(request, location):
pass
return render_to_response('unit.html', {
'unit_name': item.display_name,
'components': components,
'component_templates': component_templates,
})
@login_required
def new_item(request):
"""
Display a page where the user can create a new item from a template
Expects a GET request with the parameter 'parent_location', which is the element to add
the newly created item to as a child.
parent_location: A Location URL
"""
def preview_component(request, location):
# TODO (vshnayder): change name from id to location in coffee+html as well.
if not has_access(request.user, location):
raise Http404 # TODO (vshnayder): better error
parent_location = request.GET['parent_location']
if not has_access(request.user, parent_location):
raise Http404
component = modulestore().get_item(location)
parent = modulestore().get_item(parent_location)
templates = modulestore().get_items(Location('i4x', 'edx', 'templates'))
return render_to_response('component.html', {
'preview': get_module_previews(request, component)[0],
'editor': wrap_xmodule(component.get_html, component, 'xmodule_edit.html')(),
})
templates.sort(key=attrgetter('location.category', 'display_name'))
return render_to_response('new_item.html', {
'parent_name': parent.display_name,
'parent_location': parent.location.url(),
'templates': groupby(templates, attrgetter('location.category')),
})
@login_required
def delete_unit(request, location):
pass
def user_author_string(user):
......@@ -321,20 +335,10 @@ def load_preview_module(request, preview_id, descriptor, instance_state, shared_
error_msg=exc_info_to_str(sys.exc_info())
).xmodule_constructor(system)(None, None)
module.get_html = wrap_xmodule(
module.get_html,
module,
"xmodule_edit.html",
{
'location': descriptor.location.url(),
'editor_content': descriptor.get_html(),
'editor_type': descriptor.js_module_name,
'editor_class': descriptor.__class__.__name__,
# TODO (cpennington): Make descriptors know if they have data that can be editng
'editable_data': descriptor.definition.get('data'),
'editable_class': 'editable' if descriptor.definition.get('data') else '',
}
"xmodule_display.html",
)
module.get_html = replace_static_urls(
module.get_html,
......
......@@ -4,9 +4,3 @@ class CMS.Models.Module extends Backbone.Model
data: ''
children: ''
metadata: {}
initialize: (attributes) ->
@module = attributes.module
@unset('module')
delete attributes.module
super(attributes)
class CMS.Views.ModuleEdit extends Backbone.View
tagName: 'div'
className: 'xmodule_edit'
tagName: 'li'
className: 'component'
initialize: ->
@module = @options.module
@module.onUpdate(@save)
events:
"click .component-editor .cancel-button": 'clickCancelButton'
"click .component-editor .save-button": 'clickSaveButton'
"click .component-actions .edit-button": 'clickEditButton'
@setEvents()
$component_editor: -> @$el.find('.component-editor')
setEvents: ->
id = @$el.data('id')
initialize: ->
@module = @options.module
@render()
@events = {}
@events["click .component-editor[data-id=#{ id }] .cancel-button"] = 'clickCancelButton'
@events["click .component-editor[data-id=#{ id }] .save-button"] = 'clickSaveButton'
@events["click .component-actions[data-id=#{ id }] .edit-button"] = 'clickEditButton'
$component_editor: => @$el.find('.component-editor')
@delegateEvents()
loadModules: ->
@module = XModule.loadModule(@$el.find('.xmodule_edit'))
XModule.loadModule(@$el.find('.xmodule_display'))
metadata: ->
# cdodge: package up metadata which is separated into a number of input fields
......@@ -32,30 +30,26 @@ class CMS.Views.ModuleEdit extends Backbone.View
# build up a object to pass back to the server on the subsequent POST
_metadata[$(el).data("metadata-name")] = el.value for el in $('[data-metadata-name]', $metadata)
_metadata
save: (data) =>
@model.unset('preview')
@model.set(data)
@model.save().done( (resp) =>
alert("Your changes have been saved.")
$preview = $(resp.preview)
@$el.replaceWith($preview)
@setElement($preview)
@module.constructor(@$el)
XModule.loadModules(@$el)
return _metadata
).fail( ->
alert("There was an error saving your changes. Please try again.")
render: ->
@$el.load("/preview_component/#{@model.id}", =>
@loadModules()
@delegateEvents()
)
clickSaveButton: (event) =>
event.preventDefault()
data = @module.save()
data.metadata = @metadata()
@model.save(data).done( =>
alert("Your changes have been saved.")
@save(data)
@render()
@$el.removeClass('editing')
).fail( ->
alert("There was an error saving your changes. Please try again.")
)
clickCancelButton: (event) ->
event.preventDefault()
......
......@@ -10,32 +10,23 @@ $(document).ready(function() {
$modal = $('.history-modal');
$modalCover = $('.modal-cover');
$newComponentItem = $('.new-component-item');
$newComponentStep1 = $('.new-component-step-1');
$newComponentStep2 = $('.new-component-step-2');
$newComponentChooser = $('.new-component');
$newComponentButton = $('.new-component-button');
$(document).bind('XModule.loaded.edit', function(e, element, module) {
var previewType = $(element).data('preview-type');
var moduleType = $(element).data('type');
$('li.component').each(function(idx, element) {
new CMS.Views.ModuleEdit({
el: element,
module: module,
model: new CMS.Models.Module({
id: $(element).data('id'),
type: moduleType == 'None' ? null : moduleType,
previewType: previewType == 'None' ? null : previewType,
})
});
});
XModule.loadModules()
$('.expand-collapse-icon').bind('click', toggleSubmodules);
$('.visibility-options').bind('change', setVisibility);
$newComponentButton.bind('click', showNewComponentForm);
$newComponentStep1.find('.new-component-type a').bind('click', showNewComponentProperties);
$newComponentStep2.find('.save-button').bind('click', saveNewComponent);
$newComponentStep2.find('.cancel-button').bind('click', cancelNewComponent);
$newComponentChooser.find('.new-component-type a').bind('click', showComponentTemplates);
$('.unit-history ol a').bind('click', showHistoryModal);
$modal.bind('click', hideHistoryModal);
......@@ -64,70 +55,18 @@ function closeComponentEditor(e) {
}
function showNewComponentForm(e) {
e.preventDefault();
e.preventDefault();
$newComponentItem.addClass('adding');
$(this).slideUp(150);
$newComponentStep1.slideDown(150);
$newComponentChooser.slideDown(150);
}
function showNewComponentProperties(e) {
function showComponentTemplates(e) {
e.preventDefault();
var displayName;
var componentSource;
var selectionRange;
var $renderedComponent;
switch($(this).attr('data-type')) {
case 'video':
displayName = 'Video';
componentSource = '<video youtube="1.50:___,1.25:___,1.0:___,0.75:___"/>';
selectionRange = [21, 24];
$renderedComponent = $('<div class="rendered-component"><div class="video-unit"><img src="images/video-module.png"></div></div>');
break;
case 'textbook':
displayName = 'Textbook';
componentSource = '<customtag page="___"><impl>book</impl></customtag>';
selectionRange = [17, 20];
$renderedComponent = $('<div class="rendered-component"><p><span class="textbook-icon"></span>More information given in the text.</p></div>');
break;
case 'slide':
displayName = 'Slide';
componentSource = '<customtag page="___"><customtag lecnum="___"><impl>slides</impl></customtag>';
selectionRange = [17, 20];
$renderedComponent = $('<div class="rendered-component"><p><span class="slides-icon"></span>Lecture Slides Handout [Clean] [Annotated]</p></div>');
break;
case 'discussion':
displayName = 'Discussion';
componentSource = '<discussion for="___" id="___" discussion_category="___"/>';
selectionRange = [17, 20];
$renderedComponent = $('<div class="rendered-component"><div class="discussion-unit"><img src="images/discussion-module.png"></div></div>');
break;
case 'problem':
displayName = 'Problem';
componentSource = '<problem>___</problem>';
selectionRange = [9, 12];
$renderedComponent = $('<div class="rendered-component"></div>');
break;
case 'freeform':
displayName = 'Freeform HTML';
componentSource = '';
selectionRange = [0, 0];
$renderedComponent = $('<div class="rendered-component"></div>');
break;
}
$newComponentItem.prepend($renderedComponent);
$renderedComponent.slideDown(250);
$newComponentStep2.find('h5').html('Edit ' + displayName + ' Component');
$newComponentStep2.find('textarea').html(componentSource);
setTimeout(function() {
$newComponentStep2.find('textarea').focus().get(0).setSelectionRange(selectionRange[0], selectionRange[1]);
}, 10);
$newComponentStep1.slideUp(250);
$newComponentStep2.slideDown(250);
var type = $(this).data('type');
$newComponentChooser.slideUp(250);
$('.new-component-'+type).slideDown(250);
}
function cancelNewComponent(e) {
......
......@@ -268,7 +268,7 @@ input.courseware-unit-search-input {
header {
height: 67px;
.item-actions {
.item-actions {
margin-top: 11px;
margin-right: 12px;
......@@ -359,7 +359,7 @@ input.courseware-unit-search-input {
}
}
.item-actions {
.component-actions {
float: right;
.edit-button,
......@@ -499,8 +499,8 @@ input.courseware-unit-search-input {
text-transform: uppercase;
}
.edit-pane {
.xmodule_edit.editable {
.components {
.component {
position: relative;
border: 1px solid transparent;
z-index: 10;
......@@ -577,82 +577,76 @@ input.courseware-unit-search-input {
opacity: 0;
-webkit-transition: opacity .15s;
}
}
.new-component-item {
padding: 0;
border: 1px solid #8891a1;
border-radius: 3px;
background: -webkit-linear-gradient(top, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0)) #d1dae3;
box-shadow: 0 1px 0 rgba(255, 255, 255, .2) inset;
-webkit-transition: background-color .15s, border-color .15s;
&.new-component-item {
padding: 0;
border: 1px solid #8891a1;
border-radius: 3px;
background: -webkit-linear-gradient(top, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0)) #d1dae3;
box-shadow: 0 1px 0 rgba(255, 255, 255, .2) inset;
-webkit-transition: background-color .15s, border-color .15s;
&.adding {
background-color: $blue;
border-color: #437fbf;
}
&.adding {
background-color: $blue;
border-color: #437fbf;
}
.new-component-button {
display: block;
padding: 20px;
text-align: center;
color: #6d788b;
}
.new-component-button {
display: block;
padding: 20px;
text-align: center;
color: #6d788b;
}
h5 {
margin-bottom: 8px;
color: #fff;
font-weight: 700;
}
h5 {
margin-bottom: 8px;
color: #fff;
font-weight: 700;
}
.rendered-component {
display: none;
background: #fff;
border-radius: 3px 3px 0 0;
}
.rendered-component {
display: none;
background: #fff;
border-radius: 3px 3px 0 0;
}
.new-component-type {
@include clearfix;
a {
position: relative;
float: left;
width: 100px;
height: 100px;
margin-right: 10px;
border-radius: 8px;
font-size: 13px;
line-height: 14px;
color: #fff;
text-align: center;
box-shadow: 0 1px 1px rgba(0, 0, 0, .4), 0 1px 0 rgba(255, 255, 255, .4) inset;
-webkit-transition: background-color .15s;
.new-component-type {
@include clearfix;
a {
position: relative;
float: left;
width: 100px;
height: 100px;
margin-right: 10px;
border-radius: 8px;
font-size: 13px;
line-height: 14px;
color: #fff;
text-align: center;
box-shadow: 0 1px 1px rgba(0, 0, 0, .4), 0 1px 0 rgba(255, 255, 255, .4) inset;
-webkit-transition: background-color .15s;
&:hover {
background-color: rgba(255, 255, 255, .2);
}
.name {
position: absolute;
bottom: 5px;
left: 0;
width: 100%;
padding: 10px;
box-sizing: border-box;
}
&:hover {
background-color: rgba(255, 255, 255, .2);
}
}
.new-component-step-1,
.new-component-step-2 {
display: none;
padding: 20px;
.name {
position: absolute;
bottom: 5px;
left: 0;
width: 100%;
padding: 10px;
box-sizing: border-box;
}
}
}
}
.video-unit img,
.discussion-unit img {
width: 100%;
.new-component,
.new-component-templates {
display: none;
padding: 20px;
}
}
}
......
${preview}
<div class="component-actions">
<a href="#" class="edit-button"><span class="edit-icon white"></span>Edit</a>
<a href="#" class="delete-button"><span class="delete-icon white"></span>Delete</a>
</div>
<a href="#" class="drag-handle"></a>
<div class="component-editor">
${editor}
<a href="#" class="save-button">Save</a>
<a href="#" class="cancel-button">Cancel</a>
</div>
......@@ -9,19 +9,52 @@
<ul>
<li><a href="#">Week 2</a></li>
<li><a href="#">Linearity and Superposition</a></li>
<li><span class="current-page">S3V2: Properties of Linearity</span></li>
<li><span class="current-page">${unit_name}</span></li>
</ul>
</nav>
<section class='edit-pane'>
${editable_preview}
</section>
<ol class="components">
% for id in components:
<li class="component" data-id="${id}"/>
% endfor
<li class="new-component-item">
<a href="#" class="new-component-button">
<span class="plus-icon"></span>New Component
</a>
<div class="new-component">
<h5>Select Component Type</h5>
<ul class="new-component-type">
% for type in sorted(component_templates.keys()):
<li>
<a href="#" data-type="${type}">
<span class="large-template-icon large-${type}-icon"></span>
<span class="name">${type}</span>
</a>
</li>
% endfor
</ul>
</div>
% for type, templates in sorted(component_templates.items()):
<div class="new-component-templates new-component-${type}">
<ul class="new-component-template">
% for name, location in templates:
<li>
<a href="#" data-location="${location}">
<span class="name">${name}</span>
</a>
</li>
% endfor
</ul>
</div>
% endfor
</li>
</ol>
</article>
<div class="sidebar">
<div class="unit-properties window">
<h4>Properties</h4>
<div class="window-contents">
<div class="row"><label>Display Name:</label><input type="text" value="${module.display_name}" /></div>
<div class="row"><label>Display Name:</label><input type="text" value="${unit_name}" /></div>
<div class="row">
<label>State:</label>
<div class="visibility-options">
......
......@@ -9,9 +9,9 @@ import django.contrib.auth.views
urlpatterns = ('',
url(r'^$', 'contentstore.views.index', name='index'),
url(r'^new_item$', 'contentstore.views.new_item', name='new_item'),
url(r'^edit/(?P<location>.*?)$', 'contentstore.views.edit_unit', name='edit_unit'),
url(r'^delete/(?P<location>.*?)$', 'contentstore.views.delete_unit', name='delete_unit'),
url(r'^preview_component/(?P<location>.*?)$', 'contentstore.views.preview_component', name='preview_component'),
url(r'^save_item$', 'contentstore.views.save_item', name='save_item'),
url(r'^clone_item$', 'contentstore.views.clone_item', name='clone_item'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<name>[^/]+)$',
......
......@@ -17,6 +17,8 @@
if $(element).hasClass('xmodule_display')
$(document).trigger('XModule.loaded.display', [element, module])
return module
catch error
console.error "Unable to load #{moduleType}: #{error.message}" if console
......@@ -33,7 +35,7 @@
else
modules = $(selector)
modules.each (idx, element) -> XModule.loadModule element
modules.each((idx, element) -> XModule.loadModule element)
class @XModule.Descriptor
......
<div class="xmodule_edit xmodule_${editor_class} ${editable_class}" data-type="${editor_type}" data-id="${location}">
<%include file="xmodule_display.html"/>
% if editable_data:
<div class="component-actions" data-id="${location}">
<a href="#" class="edit-button"><span class="edit-icon white"></span>Edit</a>
<a href="#" class="delete-button"><span class="delete-icon white"></span>Delete</a>
</div>
<div class="component-editor" data-id="${location}">
${editor_content}
<a href="#" class="save-button">Save</a>
<a href="#" class="cancel-button">Cancel</a>
</div>
% endif
</div>
<section class="xmodule_edit xmodule_${class_}" data-type="${module_name}">
${content}
</section>
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