Commit d569c559 by Don Mitchell

Merge branch 'feature/btalbot/studio-softlanding' of github.com:MITx/mitx into…

Merge branch 'feature/btalbot/studio-softlanding' of github.com:MITx/mitx into feature/btalbot/studio-softlanding

Conflicts:
	cms/djangoapps/contentstore/views.py
	cms/static/js/views/settings/main_settings_view.js
	cms/templates/index.html
	cms/templates/settings.html
parents 40668133 540710f5
...@@ -94,6 +94,8 @@ def login_page(request): ...@@ -94,6 +94,8 @@ def login_page(request):
'forgot_password_link': "//{base}/#forgot-password-modal".format(base=settings.LMS_BASE), 'forgot_password_link': "//{base}/#forgot-password-modal".format(base=settings.LMS_BASE),
}) })
def howitworks(request):
return render_to_response('howitworks.html', {})
# ==== Views for any logged-in user ================================== # ==== Views for any logged-in user ==================================
...@@ -730,8 +732,6 @@ def clone_item(request): ...@@ -730,8 +732,6 @@ def clone_item(request):
#@login_required #@login_required
#@ensure_csrf_cookie #@ensure_csrf_cookie
def upload_asset(request, org, course, coursename): def upload_asset(request, org, course, coursename):
''' '''
cdodge: this method allows for POST uploading of files into the course asset library, which will cdodge: this method allows for POST uploading of files into the course asset library, which will
...@@ -796,8 +796,6 @@ def upload_asset(request, org, course, coursename): ...@@ -796,8 +796,6 @@ def upload_asset(request, org, course, coursename):
''' '''
This view will return all CMS users who are editors for the specified course This view will return all CMS users who are editors for the specified course
''' '''
@login_required @login_required
@ensure_csrf_cookie @ensure_csrf_cookie
def manage_users(request, location): def manage_users(request, location):
...@@ -819,7 +817,7 @@ def manage_users(request, location): ...@@ -819,7 +817,7 @@ def manage_users(request, location):
}) })
def create_json_response(errmsg=None): def create_json_response(errmsg = None):
if errmsg is not None: if errmsg is not None:
resp = HttpResponse(json.dumps({'Status': 'Failed', 'ErrMsg': errmsg})) resp = HttpResponse(json.dumps({'Status': 'Failed', 'ErrMsg': errmsg}))
else: else:
...@@ -831,8 +829,6 @@ def create_json_response(errmsg=None): ...@@ -831,8 +829,6 @@ def create_json_response(errmsg=None):
This POST-back view will add a user - specified by email - to the list of editors for This POST-back view will add a user - specified by email - to the list of editors for
the specified course the specified course
''' '''
@expect_json @expect_json
@login_required @login_required
@ensure_csrf_cookie @ensure_csrf_cookie
...@@ -865,8 +861,6 @@ def add_user(request, location): ...@@ -865,8 +861,6 @@ def add_user(request, location):
This POST-back view will remove a user - specified by email - from the list of editors for This POST-back view will remove a user - specified by email - from the list of editors for
the specified course the specified course
''' '''
@expect_json @expect_json
@login_required @login_required
@ensure_csrf_cookie @ensure_csrf_cookie
...@@ -1124,8 +1118,31 @@ def get_course_settings(request, org, course, name): ...@@ -1124,8 +1118,31 @@ def get_course_settings(request, org, course, name):
course_details = CourseDetails.fetch(location) course_details = CourseDetails.fetch(location)
return render_to_response('settings.html', { return render_to_response('settings.html', {
'active_tab': 'settings',
'context_course': course_module, 'context_course': course_module,
'course_location' : location,
'course_details' : json.dumps(course_details, cls=CourseSettingsEncoder)
})
@login_required
@ensure_csrf_cookie
def course_config_graders_page(request, org, course, name):
"""
Send models and views as well as html for editing the course settings to the client.
org, course, name: Attributes of the Location for the item to edit
"""
location = ['i4x', org, course, 'course', name]
# check that logged in user has permissions to this item
if not has_access(request.user, location):
raise PermissionDenied()
course_module = modulestore().get_item(location)
course_details = CourseGradingModel.fetch(location)
return render_to_response('settings_graders.html', {
'context_course': course_module,
'course_location' : location,
'course_details': json.dumps(course_details, cls=CourseSettingsEncoder) 'course_details': json.dumps(course_details, cls=CourseSettingsEncoder)
}) })
......
<li class="input input-existing multi course-grading-assignment-list-item"> <li class="field-group course-grading-assignment-list-item">
<div class="row row-col2"> <div class="field text" id="field-course-grading-assignment-name">
<label for="course-grading-assignment-name">Assignment Type Name:</label> <label for="course-grading-assignment-name">Assignment Type Name</label>
<input type="text" class="long" id="course-grading-assignment-name" value="<%= model.get('type') %>" />
<div class="field"> <span class="tip tip-stacked">e.g. Homework, Midterm Exams</span>
<div class="input course-grading-assignment-name"> </div>
<input type="text" class="long"
id="course-grading-assignment-name" value="<%= model.get('type') %>"> <div class="field text" id="field-course-grading-assignment-shortname">
<span class="tip tip-stacked">e.g. Homework, Labs, Midterm Exams, Final Exam</span> <label for="course-grading-shortname">Abbreviation:</label>
</div> <input type="text" class="short" id="course-grading-assignment-shortname" value="<%= model.get('short_label') %>" />
</div> <span class="tip tip-inline">e.g. HW, Midterm</span>
</div> </div>
<div class="row row-col2"> <div class="field text" id="field-course-grading-assignment-gradeweight">
<label for="course-grading-shortname">Abbreviation:</label> <label for="course-grading-gradeweight">Weight of Total Grade</label>
<input type="text" class="short" id="course-grading-assignment-gradeweight" value = "<%= model.get('weight') %>" />
<div class="field"> <span class="tip tip-inline">e.g. 25%</span>
<div class="input course-grading-shortname"> </div>
<input type="text" class="short"
id="course-grading-assignment-shortname" <div class="field text" id="field-course-grading-assignment-totalassignments">
value="<%= model.get('short_label') %>"> <label for="course-grading-gradeweight">Total
<span class="tip tip-inline">e.g. HW, Midterm, Final</span> Number</label>
</div> <input type="text" class="short" id="course-grading-assignment-totalassignments" value = "<%= model.get('min_count') %>" />
</div> <span class="tip tip-inline">total exercises assigned</span>
</div> </div>
<div class="row row-col2"> <div class="field text" id="field-course-grading-assignment-droppable">
<label for="course-grading-gradeweight">Weight of Total <label for="course-grading-gradeweight">Number of
Grade:</label> Droppable</label>
<input type="text" class="short" id="course-grading-assignment-droppable" value = "<%= model.get('drop_count') %>" />
<div class="field"> <span class="tip tip-inline">total exercises that won't be graded</span>
<div class="input course-grading-gradeweight"> </div>
<input type="text" class="short"
id="course-grading-assignment-gradeweight" <div class="actions">
value = "<%= model.get('weight') %>"> <a href="#" class="button delete-button standard remove-item remove-grading-data"><span class="delete-icon"></span>Delete</a>
<span class="tip tip-inline">e.g. 25%</span> </div>
</div>
</div>
</div>
<div class="row row-col2">
<label for="course-grading-assignment-totalassignments">Total
Number:</label>
<div class="field">
<div class="input course-grading-totalassignments">
<input type="text" class="short"
id="course-grading-assignment-totalassignments"
value = "<%= model.get('min_count') %>">
<span class="tip tip-inline">total exercises assigned</span>
</div>
</div>
</div>
<div class="row row-col2">
<label for="course-grading-assignment-droppable">Number of
Droppable:</label>
<div class="field">
<div class="input course-grading-droppable">
<input type="text" class="short"
id="course-grading-assignment-droppable"
value = "<%= model.get('drop_count') %>">
<span class="tip tip-inline">total exercises that won't be graded</span>
</div>
</div>
</div>
<a href="#" class="delete-button standard remove-item remove-grading-data"><span class="delete-icon"></span>Delete</a>
</li> </li>
...@@ -6,106 +6,141 @@ var $changedInput; ...@@ -6,106 +6,141 @@ var $changedInput;
var $spinner; var $spinner;
$(document).ready(function() { $(document).ready(function() {
$body = $('body'); $body = $('body');
$modal = $('.history-modal'); $modal = $('.history-modal');
$modalCover = $('<div class="modal-cover">'); $modalCover = $('<div class="modal-cover">');
// cdodge: this looks funny, but on AWS instances, this base.js get's wrapped in a separate scope as part of Django static // cdodge: this looks funny, but on AWS instances, this base.js get's wrapped in a separate scope as part of Django static
// pipelining (note, this doesn't happen on local runtimes). So if we set it on window, when we can access it from other // pipelining (note, this doesn't happen on local runtimes). So if we set it on window, when we can access it from other
// scopes (namely the course-info tab) // scopes (namely the course-info tab)
window.$modalCover = $modalCover; window.$modalCover = $modalCover;
// Control whether template caching in local memory occurs (see template_loader.js). Caching screws up development but may // Control whether template caching in local memory occurs (see template_loader.js). Caching screws up development but may
// be a good optimization in production (it works fairly well) // be a good optimization in production (it works fairly well)
window.cachetemplates = false; window.cachetemplates = false;
$body.append($modalCover);
$newComponentItem = $('.new-component-item');
$newComponentTypePicker = $('.new-component');
$newComponentTemplatePickers = $('.new-component-templates');
$newComponentButton = $('.new-component-button');
$spinner = $('<span class="spinner-in-field-icon"></span>');
$body.bind('keyup', onKeyUp);
$('.expand-collapse-icon').bind('click', toggleSubmodules);
$('.visibility-options').bind('change', setVisibility);
$modal.bind('click', hideModal);
$modalCover.bind('click', hideModal);
$('.assets .upload-button').bind('click', showUploadModal);
$('.upload-modal .close-button').bind('click', hideModal);
$body.on('click', '.embeddable-xml-input', function(){ $(this).select(); });
$('.unit .item-actions .delete-button').bind('click', deleteUnit);
$('.new-unit-item').bind('click', createNewUnit);
// toggling overview section details
$(function(){
if($('.courseware-section').length > 0) {
$('.toggle-button-sections').addClass('is-shown');
}
});
$('.toggle-button-sections').bind('click', toggleSections);
// autosave when a field is updated on the subsection page $body.append($modalCover);
$body.on('keyup', '.subsection-display-name-input, .unit-subtitle, .policy-list-value', checkForNewValue); $newComponentItem = $('.new-component-item');
$('.subsection-display-name-input, .unit-subtitle, .policy-list-name, .policy-list-value').each(function(i) { $newComponentTypePicker = $('.new-component');
this.val = $(this).val(); $newComponentTemplatePickers = $('.new-component-templates');
}); $newComponentButton = $('.new-component-button');
$("#start_date, #start_time, #due_date, #due_time").bind('change', autosaveInput); $spinner = $('<span class="spinner-in-field-icon"></span>');
$('.sync-date, .remove-date').bind('click', autosaveInput); $body.bind('keyup', onKeyUp);
// expand/collapse methods for optional date setters
$('.set-date').bind('click', showDateSetter);
$('.remove-date').bind('click', removeDateSetter);
// add new/delete section
$('.new-courseware-section-button').bind('click', addNewSection);
$('.delete-section-button').bind('click', deleteSection);
// add new/delete subsection
$('.new-subsection-item').bind('click', addNewSubsection);
$('.delete-subsection-button').bind('click', deleteSubsection);
// add/remove policy metadata button click handlers
$('.add-policy-data').bind('click', addPolicyMetadata);
$('.remove-policy-data').bind('click', removePolicyMetadata);
$body.on('click', '.policy-list-element .save-button', savePolicyMetadata);
$body.on('click', '.policy-list-element .cancel-button', cancelPolicyMetadata);
$('.sync-date').bind('click', syncReleaseDate);
// import form setup
$('.import .file-input').bind('change', showImportSubmit);
$('.import .choose-file-button, .import .choose-file-button-inline').bind('click', function(e) {
e.preventDefault();
$('.import .file-input').click();
});
$('.new-course-button').bind('click', addNewCourse); $('.expand-collapse-icon').bind('click', toggleSubmodules);
$('.visibility-options').bind('change', setVisibility);
// section name editing $modal.bind('click', hideModal);
$('.section-name').bind('click', editSectionName); $modalCover.bind('click', hideModal);
$('.edit-section-name-cancel').bind('click', cancelEditSectionName); $('.uploads .upload-button').bind('click', showUploadModal);
// $('.edit-section-name-save').bind('click', saveEditSectionName); $('.upload-modal .close-button').bind('click', hideModal);
// section date setting $body.on('click', '.embeddable-xml-input', function(){ $(this).select(); });
$('.set-publish-date').bind('click', setSectionScheduleDate);
$('.edit-section-start-cancel').bind('click', cancelSetSectionScheduleDate);
$('.edit-section-start-save').bind('click', saveSetSectionScheduleDate);
$('.upload-modal .choose-file-button').bind('click', showFileSelectionMenu); $('.unit .item-actions .delete-button').bind('click', deleteUnit);
$('.new-unit-item').bind('click', createNewUnit);
$body.on('click', '.section-published-date .edit-button', editSectionPublishDate); // nav-related
$body.on('click', '.section-published-date .schedule-button', editSectionPublishDate); $('body').addClass('js');
$body.on('click', '.edit-subsection-publish-settings .save-button', saveSetSectionScheduleDate);
$body.on('click', '.edit-subsection-publish-settings .cancel-button', hideModal); $('.nav-dropdown .nav-item .title').click(function(e){
$body.on('change', '.edit-subsection-publish-settings .start-date', function() {
if($('.edit-subsection-publish-settings').find('.start-time').val() == '') { $subnav = $(this).parent().find('.wrapper-nav-sub');
$('.edit-subsection-publish-settings').find('.start-time').val('12:00am'); $title = $(this).parent().find('.title');
}
}); e.preventDefault();
$('.edit-subsection-publish-settings').on('change', '.start-date, .start-time', function() {
$('.edit-subsection-publish-settings').find('.save-button').show(); if ($subnav.hasClass('is-shown')) {
}); $subnav.removeClass('is-shown');
$title.removeClass('is-selected');
}
else {
$('.nav-dropdown .nav-item .title').removeClass('is-selected');
$('.nav-dropdown .nav-item .wrapper-nav-sub').removeClass('is-shown');
$title.addClass('is-selected');
$subnav.addClass('is-shown');
}
});
// general link management - new window/tab
$('a[rel="external"]').attr('title','This link will open in a new browser window/tab').click(function(e) {
window.open($(this).attr('href'));
e.preventDefault();
});
// general link management - lean modal window
$('a[rel="modal"]').attr('title','This link will open in a modal window').leanModal({overlay : 0.50, closeButton: '.action-modal-close' });
$('.action-modal-close').click(function(e){
(e).preventDefault();
});
// toggling overview section details
$(function(){
if($('.courseware-section').length > 0) {
$('.toggle-button-sections').addClass('is-shown');
}
});
$('.toggle-button-sections').bind('click', toggleSections);
// autosave when a field is updated on the subsection page
$body.on('keyup', '.subsection-display-name-input, .unit-subtitle, .policy-list-value', checkForNewValue);
$('.subsection-display-name-input, .unit-subtitle, .policy-list-name, .policy-list-value').each(function(i) {
this.val = $(this).val();
});
$("#start_date, #start_time, #due_date, #due_time").bind('change', autosaveInput);
$('.sync-date, .remove-date').bind('click', autosaveInput);
// expand/collapse methods for optional date setters
$('.set-date').bind('click', showDateSetter);
$('.remove-date').bind('click', removeDateSetter);
// add new/delete section
$('.new-courseware-section-button').bind('click', addNewSection);
$('.delete-section-button').bind('click', deleteSection);
// add new/delete subsection
$('.new-subsection-item').bind('click', addNewSubsection);
$('.delete-subsection-button').bind('click', deleteSubsection);
// add/remove policy metadata button click handlers
$('.add-policy-data').bind('click', addPolicyMetadata);
$('.remove-policy-data').bind('click', removePolicyMetadata);
$body.on('click', '.policy-list-element .save-button', savePolicyMetadata);
$body.on('click', '.policy-list-element .cancel-button', cancelPolicyMetadata);
$('.sync-date').bind('click', syncReleaseDate);
// import form setup
$('.import .file-input').bind('change', showImportSubmit);
$('.import .choose-file-button, .import .choose-file-button-inline').bind('click', function(e) {
e.preventDefault();
$('.import .file-input').click();
});
$('.new-course-button').bind('click', addNewCourse);
// section name editing
$('.section-name').bind('click', editSectionName);
$('.edit-section-name-cancel').bind('click', cancelEditSectionName);
// $('.edit-section-name-save').bind('click', saveEditSectionName);
// section date setting
$('.set-publish-date').bind('click', setSectionScheduleDate);
$('.edit-section-start-cancel').bind('click', cancelSetSectionScheduleDate);
$('.edit-section-start-save').bind('click', saveSetSectionScheduleDate);
$('.upload-modal .choose-file-button').bind('click', showFileSelectionMenu);
$body.on('click', '.section-published-date .edit-button', editSectionPublishDate);
$body.on('click', '.section-published-date .schedule-button', editSectionPublishDate);
$body.on('click', '.edit-subsection-publish-settings .save-button', saveSetSectionScheduleDate);
$body.on('click', '.edit-subsection-publish-settings .cancel-button', hideModal);
$body.on('change', '.edit-subsection-publish-settings .start-date', function() {
if($('.edit-subsection-publish-settings').find('.start-time').val() == '') {
$('.edit-subsection-publish-settings').find('.start-time').val('12:00am');
}
});
$('.edit-subsection-publish-settings').on('change', '.start-date, .start-time', function() {
$('.edit-subsection-publish-settings').find('.save-button').show();
});
}); });
// function collapseAll(e) { // function collapseAll(e) {
...@@ -126,660 +161,660 @@ function toggleSections(e) { ...@@ -126,660 +161,660 @@ function toggleSections(e) {
$button.toggleClass('is-activated').html(buttonLabel); $button.toggleClass('is-activated').html(buttonLabel);
if($button.hasClass('is-activated')) { if($button.hasClass('is-activated')) {
$section.addClass('collapsed'); $section.addClass('collapsed');
// first child in order to avoid the icons on the subsection lists which are not in the first child // first child in order to avoid the icons on the subsection lists which are not in the first child
$section.find('header .expand-collapse-icon').removeClass('collapse').addClass('expand'); $section.find('header .expand-collapse-icon').removeClass('collapse').addClass('expand');
} else { } else {
$section.removeClass('collapsed'); $section.removeClass('collapsed');
// first child in order to avoid the icons on the subsection lists which are not in the first child // first child in order to avoid the icons on the subsection lists which are not in the first child
$section.find('header .expand-collapse-icon').removeClass('expand').addClass('collapse'); $section.find('header .expand-collapse-icon').removeClass('expand').addClass('collapse');
} }
} }
function editSectionPublishDate(e) { function editSectionPublishDate(e) {
e.preventDefault(); e.preventDefault();
$modal = $('.edit-subsection-publish-settings').show(); $modal = $('.edit-subsection-publish-settings').show();
$modal = $('.edit-subsection-publish-settings').show(); $modal = $('.edit-subsection-publish-settings').show();
$modal.attr('data-id', $(this).attr('data-id')); $modal.attr('data-id', $(this).attr('data-id'));
$modal.find('.start-date').val($(this).attr('data-date')); $modal.find('.start-date').val($(this).attr('data-date'));
$modal.find('.start-time').val($(this).attr('data-time')); $modal.find('.start-time').val($(this).attr('data-time'));
if($modal.find('.start-date').val() == '' && $modal.find('.start-time').val() == '') { if($modal.find('.start-date').val() == '' && $modal.find('.start-time').val() == '') {
$modal.find('.save-button').hide(); $modal.find('.save-button').hide();
} }
$modal.find('.section-name').html('"' + $(this).closest('.courseware-section').find('.section-name-span').text() + '"'); $modal.find('.section-name').html('"' + $(this).closest('.courseware-section').find('.section-name-span').text() + '"');
$modalCover.show(); $modalCover.show();
} }
function showImportSubmit(e) { function showImportSubmit(e) {
var filepath = $(this).val(); var filepath = $(this).val();
if(filepath.substr(filepath.length - 6, 6) == 'tar.gz') { if(filepath.substr(filepath.length - 6, 6) == 'tar.gz') {
$('.error-block').hide(); $('.error-block').hide();
$('.file-name').html($(this).val().replace('C:\\fakepath\\', '')); $('.file-name').html($(this).val().replace('C:\\fakepath\\', ''));
$('.file-name-block').show(); $('.file-name-block').show();
$('.import .choose-file-button').hide(); $('.import .choose-file-button').hide();
$('.submit-button').show(); $('.submit-button').show();
$('.progress').show(); $('.progress').show();
} else { } else {
$('.error-block').html('File format not supported. Please upload a file with a <code>tar.gz</code> extension.').show(); $('.error-block').html('File format not supported. Please upload a file with a <code>tar.gz</code> extension.').show();
} }
} }
function syncReleaseDate(e) { function syncReleaseDate(e) {
e.preventDefault(); e.preventDefault();
$(this).closest('.notice').hide(); $(this).closest('.notice').hide();
$("#start_date").val(""); $("#start_date").val("");
$("#start_time").val(""); $("#start_time").val("");
} }
function addPolicyMetadata(e) { function addPolicyMetadata(e) {
e.preventDefault(); e.preventDefault();
var template =$('#add-new-policy-element-template > li'); var template =$('#add-new-policy-element-template > li');
var newNode = template.clone(); var newNode = template.clone();
var _parent_el = $(this).parent('ol:.policy-list'); var _parent_el = $(this).parent('ol:.policy-list');
newNode.insertBefore('.add-policy-data'); newNode.insertBefore('.add-policy-data');
$('.remove-policy-data').bind('click', removePolicyMetadata); $('.remove-policy-data').bind('click', removePolicyMetadata);
newNode.find('.policy-list-name').focus(); newNode.find('.policy-list-name').focus();
} }
function savePolicyMetadata(e) { function savePolicyMetadata(e) {
e.preventDefault(); e.preventDefault();
var $policyElement = $(this).parents('.policy-list-element'); var $policyElement = $(this).parents('.policy-list-element');
saveSubsection() saveSubsection()
$policyElement.removeClass('new-policy-list-element'); $policyElement.removeClass('new-policy-list-element');
$policyElement.find('.policy-list-name').attr('disabled', 'disabled'); $policyElement.find('.policy-list-name').attr('disabled', 'disabled');
$policyElement.removeClass('editing'); $policyElement.removeClass('editing');
} }
function cancelPolicyMetadata(e) { function cancelPolicyMetadata(e) {
e.preventDefault(); e.preventDefault();
var $policyElement = $(this).parents('.policy-list-element'); var $policyElement = $(this).parents('.policy-list-element');
if(!$policyElement.hasClass('editing')) { if(!$policyElement.hasClass('editing')) {
$policyElement.remove(); $policyElement.remove();
} else { } else {
$policyElement.removeClass('new-policy-list-element'); $policyElement.removeClass('new-policy-list-element');
$policyElement.find('.policy-list-name').val($policyElement.data('currentValues')[0]); $policyElement.find('.policy-list-name').val($policyElement.data('currentValues')[0]);
$policyElement.find('.policy-list-value').val($policyElement.data('currentValues')[1]); $policyElement.find('.policy-list-value').val($policyElement.data('currentValues')[1]);
} }
$policyElement.removeClass('editing'); $policyElement.removeClass('editing');
} }
function removePolicyMetadata(e) { function removePolicyMetadata(e) {
e.preventDefault(); e.preventDefault();
if(!confirm('Are you sure you wish to delete this item. It cannot be reversed!')) if(!confirm('Are you sure you wish to delete this item. It cannot be reversed!'))
return; return;
policy_name = $(this).data('policy-name'); policy_name = $(this).data('policy-name');
var _parent_el = $(this).parent('li:.policy-list-element'); var _parent_el = $(this).parent('li:.policy-list-element');
if ($(_parent_el).hasClass("new-policy-list-element")) { if ($(_parent_el).hasClass("new-policy-list-element")) {
_parent_el.remove(); _parent_el.remove();
} else { } else {
_parent_el.appendTo("#policy-to-delete"); _parent_el.appendTo("#policy-to-delete");
} }
saveSubsection() saveSubsection()
} }
function getEdxTimeFromDateTimeVals(date_val, time_val, format) { function getEdxTimeFromDateTimeVals(date_val, time_val, format) {
var edxTimeStr = null; var edxTimeStr = null;
if (date_val != '') { if (date_val != '') {
if (time_val == '') if (time_val == '')
time_val = '00:00'; time_val = '00:00';
// Note, we are using date.js utility which has better parsing abilities than the built in JS date parsing // Note, we are using date.js utility which has better parsing abilities than the built in JS date parsing
date = Date.parse(date_val + " " + time_val); date = Date.parse(date_val + " " + time_val);
if (format == null) if (format == null)
format = 'yyyy-MM-ddTHH:mm'; format = 'yyyy-MM-ddTHH:mm';
edxTimeStr = date.toString(format); edxTimeStr = date.toString(format);
} }
return edxTimeStr; return edxTimeStr;
} }
function getEdxTimeFromDateTimeInputs(date_id, time_id, format) { function getEdxTimeFromDateTimeInputs(date_id, time_id, format) {
var input_date = $('#'+date_id).val(); var input_date = $('#'+date_id).val();
var input_time = $('#'+time_id).val(); var input_time = $('#'+time_id).val();
return getEdxTimeFromDateTimeVals(input_date, input_time, format); return getEdxTimeFromDateTimeVals(input_date, input_time, format);
} }
function checkForNewValue(e) { function checkForNewValue(e) {
if($(this).parents('.new-policy-list-element')[0]) { if($(this).parents('.new-policy-list-element')[0]) {
return; return;
} }
if(this.val) {
this.hasChanged = this.val != $(this).val();
} else {
this.hasChanged = false;
}
this.val = $(this).val(); if(this.val) {
if(this.hasChanged) { this.hasChanged = this.val != $(this).val();
if(this.saveTimer) { } else {
clearTimeout(this.saveTimer); this.hasChanged = false;
} }
this.saveTimer = setTimeout(function() {
$changedInput = $(e.target);
saveSubsection();
this.saveTimer = null;
}, 500);
}
}
function autosaveInput(e) { this.val = $(this).val();
if(this.hasChanged) {
if(this.saveTimer) { if(this.saveTimer) {
clearTimeout(this.saveTimer); clearTimeout(this.saveTimer);
} }
this.saveTimer = setTimeout(function() { this.saveTimer = setTimeout(function() {
$changedInput = $(e.target); $changedInput = $(e.target);
saveSubsection(); saveSubsection();
this.saveTimer = null; this.saveTimer = null;
}, 500); }, 500);
}
} }
function saveSubsection() { function autosaveInput(e) {
if($changedInput && !$changedInput.hasClass('no-spinner')) { if(this.saveTimer) {
$spinner.css({ clearTimeout(this.saveTimer);
'position': 'absolute', }
'top': Math.floor($changedInput.position().top + ($changedInput.outerHeight() / 2) + 3),
'left': $changedInput.position().left + $changedInput.outerWidth() - 24,
'margin-top': '-10px'
});
$changedInput.after($spinner);
$spinner.show();
}
var id = $('.subsection-body').data('id');
// pull all 'normalized' metadata editable fields on page this.saveTimer = setTimeout(function() {
var metadata_fields = $('input[data-metadata-name]'); $changedInput = $(e.target);
saveSubsection();
var metadata = {}; this.saveTimer = null;
for(var i=0; i< metadata_fields.length;i++) { }, 500);
var el = metadata_fields[i]; }
metadata[$(el).data("metadata-name")] = el.value;
}
// now add 'free-formed' metadata which are presented to the user as dual input fields (name/value)
$('ol.policy-list > li.policy-list-element').each( function(i, element) {
var name = $(element).children('.policy-list-name').val();
metadata[name] = $(element).children('.policy-list-value').val();
});
// now add any 'removed' policy metadata which is stored in a separate hidden div function saveSubsection() {
// 'null' presented to the server means 'remove' if($changedInput && !$changedInput.hasClass('no-spinner')) {
$("#policy-to-delete > li.policy-list-element").each(function(i, element) { $spinner.css({
var name = $(element).children('.policy-list-name').val(); 'position': 'absolute',
if (name != "") 'top': Math.floor($changedInput.position().top + ($changedInput.outerHeight() / 2) + 3),
metadata[name] = null; 'left': $changedInput.position().left + $changedInput.outerWidth() - 24,
'margin-top': '-10px'
}); });
$changedInput.after($spinner);
// Piece back together the date/time UI elements into one date/time string $spinner.show();
// NOTE: our various "date/time" metadata elements don't always utilize the same formatting string }
// so make sure we're passing back the correct format
metadata['start'] = getEdxTimeFromDateTimeInputs('start_date', 'start_time'); var id = $('.subsection-body').data('id');
metadata['due'] = getEdxTimeFromDateTimeInputs('due_date', 'due_time', 'MMMM dd HH:mm');
// pull all 'normalized' metadata editable fields on page
$.ajax({ var metadata_fields = $('input[data-metadata-name]');
url: "/save_item",
type: "POST", var metadata = {};
dataType: "json", for(var i=0; i< metadata_fields.length;i++) {
contentType: "application/json", var el = metadata_fields[i];
data:JSON.stringify({ 'id' : id, 'metadata' : metadata}), metadata[$(el).data("metadata-name")] = el.value;
success: function() { }
$spinner.delay(500).fadeOut(150);
}, // now add 'free-formed' metadata which are presented to the user as dual input fields (name/value)
error: function() { $('ol.policy-list > li.policy-list-element').each( function(i, element) {
showToastMessage('There has been an error while saving your changes.'); var name = $(element).children('.policy-list-name').val();
} metadata[name] = $(element).children('.policy-list-value').val();
}); });
// now add any 'removed' policy metadata which is stored in a separate hidden div
// 'null' presented to the server means 'remove'
$("#policy-to-delete > li.policy-list-element").each(function(i, element) {
var name = $(element).children('.policy-list-name').val();
if (name != "")
metadata[name] = null;
});
// Piece back together the date/time UI elements into one date/time string
// NOTE: our various "date/time" metadata elements don't always utilize the same formatting string
// so make sure we're passing back the correct format
metadata['start'] = getEdxTimeFromDateTimeInputs('start_date', 'start_time');
metadata['due'] = getEdxTimeFromDateTimeInputs('due_date', 'due_time', 'MMMM dd HH:mm');
$.ajax({
url: "/save_item",
type: "POST",
dataType: "json",
contentType: "application/json",
data:JSON.stringify({ 'id' : id, 'metadata' : metadata}),
success: function() {
$spinner.delay(500).fadeOut(150);
},
error: function() {
showToastMessage('There has been an error while saving your changes.');
}
});
} }
function createNewUnit(e) { function createNewUnit(e) {
e.preventDefault(); e.preventDefault();
parent = $(this).data('parent'); parent = $(this).data('parent');
template = $(this).data('template'); template = $(this).data('template');
$.post('/clone_item', $.post('/clone_item',
{'parent_location' : parent, {'parent_location' : parent,
'template' : template, 'template' : template,
'display_name': 'New Unit' 'display_name': 'New Unit'
}, },
function(data) { function(data) {
// redirect to the edit page // redirect to the edit page
window.location = "/edit/" + data['id']; window.location = "/edit/" + data['id'];
}); });
} }
function deleteUnit(e) { function deleteUnit(e) {
e.preventDefault(); e.preventDefault();
_deleteItem($(this).parents('li.leaf')); _deleteItem($(this).parents('li.leaf'));
} }
function deleteSubsection(e) { function deleteSubsection(e) {
e.preventDefault(); e.preventDefault();
_deleteItem($(this).parents('li.branch')); _deleteItem($(this).parents('li.branch'));
} }
function deleteSection(e) { function deleteSection(e) {
e.preventDefault(); e.preventDefault();
_deleteItem($(this).parents('section.branch')); _deleteItem($(this).parents('section.branch'));
} }
function _deleteItem($el) { function _deleteItem($el) {
if(!confirm('Are you sure you wish to delete this item. It cannot be reversed!')) if(!confirm('Are you sure you wish to delete this item. It cannot be reversed!'))
return; return;
var id = $el.data('id'); var id = $el.data('id');
$.post('/delete_item', $.post('/delete_item',
{'id': id, 'delete_children' : true, 'delete_all_versions' : true}, {'id': id, 'delete_children' : true, 'delete_all_versions' : true},
function(data) { function(data) {
$el.remove(); $el.remove();
}); });
} }
function showUploadModal(e) { function showUploadModal(e) {
e.preventDefault(); e.preventDefault();
$modal = $('.upload-modal').show(); $modal = $('.upload-modal').show();
$('.file-input').bind('change', startUpload); $('.file-input').bind('change', startUpload);
$modalCover.show(); $modalCover.show();
} }
function showFileSelectionMenu(e) { function showFileSelectionMenu(e) {
e.preventDefault(); e.preventDefault();
$('.file-input').click(); $('.file-input').click();
} }
function startUpload(e) { function startUpload(e) {
$('.upload-modal h1').html('Uploading…'); $('.upload-modal h1').html('Uploading…');
$('.upload-modal .file-name').html($('.file-input').val().replace('C:\\fakepath\\', '')); $('.upload-modal .file-name').html($('.file-input').val().replace('C:\\fakepath\\', ''));
$('.upload-modal .file-chooser').ajaxSubmit({ $('.upload-modal .file-chooser').ajaxSubmit({
beforeSend: resetUploadBar, beforeSend: resetUploadBar,
uploadProgress: showUploadFeedback, uploadProgress: showUploadFeedback,
complete: displayFinishedUpload complete: displayFinishedUpload
}); });
$('.upload-modal .choose-file-button').hide(); $('.upload-modal .choose-file-button').hide();
$('.upload-modal .progress-bar').removeClass('loaded').show(); $('.upload-modal .progress-bar').removeClass('loaded').show();
} }
function resetUploadBar(){ function resetUploadBar(){
var percentVal = '0%'; var percentVal = '0%';
$('.upload-modal .progress-fill').width(percentVal); $('.upload-modal .progress-fill').width(percentVal);
$('.upload-modal .progress-fill').html(percentVal); $('.upload-modal .progress-fill').html(percentVal);
} }
function showUploadFeedback(event, position, total, percentComplete) { function showUploadFeedback(event, position, total, percentComplete) {
var percentVal = percentComplete + '%'; var percentVal = percentComplete + '%';
$('.upload-modal .progress-fill').width(percentVal); $('.upload-modal .progress-fill').width(percentVal);
$('.upload-modal .progress-fill').html(percentVal); $('.upload-modal .progress-fill').html(percentVal);
} }
function displayFinishedUpload(xhr) { function displayFinishedUpload(xhr) {
if(xhr.status = 200){ if(xhr.status = 200){
markAsLoaded(); markAsLoaded();
} }
var resp = JSON.parse(xhr.responseText); var resp = JSON.parse(xhr.responseText);
$('.upload-modal .embeddable-xml-input').val(xhr.getResponseHeader('asset_url')); $('.upload-modal .embeddable-xml-input').val(xhr.getResponseHeader('asset_url'));
$('.upload-modal .embeddable').show(); $('.upload-modal .embeddable').show();
$('.upload-modal .file-name').hide(); $('.upload-modal .file-name').hide();
$('.upload-modal .progress-fill').html(resp.msg); $('.upload-modal .progress-fill').html(resp.msg);
$('.upload-modal .choose-file-button').html('Load Another File').show(); $('.upload-modal .choose-file-button').html('Load Another File').show();
$('.upload-modal .progress-fill').width('100%'); $('.upload-modal .progress-fill').width('100%');
// see if this id already exists, if so, then user must have updated an existing piece of content // see if this id already exists, if so, then user must have updated an existing piece of content
$("tr[data-id='" + resp.url + "']").remove(); $("tr[data-id='" + resp.url + "']").remove();
var template = $('#new-asset-element').html(); var template = $('#new-asset-element').html();
var html = Mustache.to_html(template, resp); var html = Mustache.to_html(template, resp);
$('table > tbody').prepend(html); $('table > tbody').prepend(html);
} }
function markAsLoaded() { function markAsLoaded() {
$('.upload-modal .copy-button').css('display', 'inline-block'); $('.upload-modal .copy-button').css('display', 'inline-block');
$('.upload-modal .progress-bar').addClass('loaded'); $('.upload-modal .progress-bar').addClass('loaded');
} }
function hideModal(e) { function hideModal(e) {
if(e) { if(e) {
e.preventDefault(); e.preventDefault();
} }
// Unit editors do not want the modal cover to hide when users click outside // Unit editors do not want the modal cover to hide when users click outside
// of the editor. Users must press Cancel or Save to exit the editor. // of the editor. Users must press Cancel or Save to exit the editor.
// module_edit adds and removes the "is-fixed" class. // module_edit adds and removes the "is-fixed" class.
if (!$modalCover.hasClass("is-fixed")) { if (!$modalCover.hasClass("is-fixed")) {
$('.file-input').unbind('change', startUpload); $('.file-input').unbind('change', startUpload);
$modal.hide(); $modal.hide();
$modalCover.hide(); $modalCover.hide();
} }
} }
function onKeyUp(e) { function onKeyUp(e) {
if(e.which == 87) { if(e.which == 87) {
$body.toggleClass('show-wip hide-wip'); $body.toggleClass('show-wip hide-wip');
} }
} }
function toggleSubmodules(e) { function toggleSubmodules(e) {
e.preventDefault(); e.preventDefault();
$(this).toggleClass('expand').toggleClass('collapse'); $(this).toggleClass('expand').toggleClass('collapse');
$(this).closest('.branch, .window').toggleClass('collapsed'); $(this).closest('.branch, .window').toggleClass('collapsed');
} }
function setVisibility(e) { function setVisibility(e) {
$(this).find('.checked').removeClass('checked'); $(this).find('.checked').removeClass('checked');
$(e.target).closest('.option').addClass('checked'); $(e.target).closest('.option').addClass('checked');
} }
function editComponent(e) { function editComponent(e) {
e.preventDefault(); e.preventDefault();
$(this).closest('.xmodule_edit').addClass('editing').find('.component-editor').slideDown(150); $(this).closest('.xmodule_edit').addClass('editing').find('.component-editor').slideDown(150);
} }
function closeComponentEditor(e) { function closeComponentEditor(e) {
e.preventDefault(); e.preventDefault();
$(this).closest('.xmodule_edit').removeClass('editing').find('.component-editor').slideUp(150); $(this).closest('.xmodule_edit').removeClass('editing').find('.component-editor').slideUp(150);
} }
function showDateSetter(e) { function showDateSetter(e) {
e.preventDefault(); e.preventDefault();
var $block = $(this).closest('.due-date-input'); var $block = $(this).closest('.due-date-input');
$(this).hide(); $(this).hide();
$block.find('.date-setter').show(); $block.find('.date-setter').show();
} }
function removeDateSetter(e) { function removeDateSetter(e) {
e.preventDefault(); e.preventDefault();
var $block = $(this).closest('.due-date-input'); var $block = $(this).closest('.due-date-input');
$block.find('.date-setter').hide(); $block.find('.date-setter').hide();
$block.find('.set-date').show(); $block.find('.set-date').show();
// clear out the values // clear out the values
$block.find('.date').val(''); $block.find('.date').val('');
$block.find('.time').val(''); $block.find('.time').val('');
} }
function showToastMessage(message, $button, lifespan) { function showToastMessage(message, $button, lifespan) {
var $toast = $('<div class="toast-notification"></div>'); var $toast = $('<div class="toast-notification"></div>');
var $closeBtn = $('<a href="#" class="close-button">×</a>'); var $closeBtn = $('<a href="#" class="close-button">×</a>');
$toast.append($closeBtn); $toast.append($closeBtn);
var $content = $('<div class="notification-content"></div>'); var $content = $('<div class="notification-content"></div>');
$content.html(message); $content.html(message);
$toast.append($content); $toast.append($content);
if($button) { if($button) {
$button.addClass('action-button'); $button.addClass('action-button');
$button.bind('click', hideToastMessage); $button.bind('click', hideToastMessage);
$content.append($button); $content.append($button);
} }
$closeBtn.bind('click', hideToastMessage); $closeBtn.bind('click', hideToastMessage);
if($('.toast-notification')[0]) { if($('.toast-notification')[0]) {
var targetY = $('.toast-notification').offset().top + $('.toast-notification').outerHeight(); var targetY = $('.toast-notification').offset().top + $('.toast-notification').outerHeight();
$toast.css('top', (targetY + 10) + 'px'); $toast.css('top', (targetY + 10) + 'px');
} }
$body.prepend($toast); $body.prepend($toast);
$toast.fadeIn(200); $toast.fadeIn(200);
if(lifespan) { if(lifespan) {
$toast.timer = setTimeout(function() { $toast.timer = setTimeout(function() {
$toast.fadeOut(300); $toast.fadeOut(300);
}, lifespan * 1000); }, lifespan * 1000);
} }
} }
function hideToastMessage(e) { function hideToastMessage(e) {
e.preventDefault(); e.preventDefault();
$(this).closest('.toast-notification').remove(); $(this).closest('.toast-notification').remove();
} }
function addNewSection(e, isTemplate) { function addNewSection(e, isTemplate) {
e.preventDefault(); e.preventDefault();
$(e.target).addClass('disabled'); $(e.target).addClass('disabled');
var $newSection = $($('#new-section-template').html()); var $newSection = $($('#new-section-template').html());
var $cancelButton = $newSection.find('.new-section-name-cancel'); var $cancelButton = $newSection.find('.new-section-name-cancel');
$('.courseware-overview').prepend($newSection); $('.courseware-overview').prepend($newSection);
$newSection.find('.new-section-name').focus().select(); $newSection.find('.new-section-name').focus().select();
$newSection.find('.section-name-form').bind('submit', saveNewSection); $newSection.find('.section-name-form').bind('submit', saveNewSection);
$cancelButton.bind('click', cancelNewSection); $cancelButton.bind('click', cancelNewSection);
$body.bind('keyup', { $cancelButton: $cancelButton }, checkForCancel); $body.bind('keyup', { $cancelButton: $cancelButton }, checkForCancel);
} }
function checkForCancel(e) { function checkForCancel(e) {
if(e.which == 27) { if(e.which == 27) {
$body.unbind('keyup', checkForCancel); $body.unbind('keyup', checkForCancel);
e.data.$cancelButton.click(); e.data.$cancelButton.click();
} }
} }
function saveNewSection(e) { function saveNewSection(e) {
e.preventDefault(); e.preventDefault();
var $saveButton = $(this).find('.new-section-name-save');
var parent = $saveButton.data('parent');
var template = $saveButton.data('template');
var display_name = $(this).find('.new-section-name').val();
$.post('/clone_item', { var $saveButton = $(this).find('.new-section-name-save');
'parent_location' : parent, var parent = $saveButton.data('parent');
'template' : template, var template = $saveButton.data('template');
'display_name': display_name, var display_name = $(this).find('.new-section-name').val();
},
function(data) { $.post('/clone_item', {
if (data.id != undefined) 'parent_location' : parent,
location.reload(); 'template' : template,
} 'display_name': display_name,
); },
function(data) {
if (data.id != undefined)
location.reload();
}
);
} }
function cancelNewSection(e) { function cancelNewSection(e) {
e.preventDefault(); e.preventDefault();
$('.new-courseware-section-button').removeClass('disabled'); $('.new-courseware-section-button').removeClass('disabled');
$(this).parents('section.new-section').remove(); $(this).parents('section.new-section').remove();
} }
function addNewCourse(e) { function addNewCourse(e) {
e.preventDefault(); e.preventDefault();
$(e.target).hide(); $(e.target).hide();
var $newCourse = $($('#new-course-template').html()); var $newCourse = $($('#new-course-template').html());
var $cancelButton = $newCourse.find('.new-course-cancel'); var $cancelButton = $newCourse.find('.new-course-cancel');
$('.new-course-button').after($newCourse); $('.new-course-button').after($newCourse);
$newCourse.find('.new-course-name').focus().select(); $newCourse.find('.new-course-name').focus().select();
$newCourse.find('form').bind('submit', saveNewCourse); $newCourse.find('form').bind('submit', saveNewCourse);
$cancelButton.bind('click', cancelNewCourse); $cancelButton.bind('click', cancelNewCourse);
$body.bind('keyup', { $cancelButton: $cancelButton }, checkForCancel); $body.bind('keyup', { $cancelButton: $cancelButton }, checkForCancel);
} }
function saveNewCourse(e) { function saveNewCourse(e) {
e.preventDefault(); e.preventDefault();
var $newCourse = $(this).closest('.new-course'); var $newCourse = $(this).closest('.new-course');
var template = $(this).find('.new-course-save').data('template'); var template = $(this).find('.new-course-save').data('template');
var org = $newCourse.find('.new-course-org').val(); var org = $newCourse.find('.new-course-org').val();
var number = $newCourse.find('.new-course-number').val(); var number = $newCourse.find('.new-course-number').val();
var display_name = $newCourse.find('.new-course-name').val(); var display_name = $newCourse.find('.new-course-name').val();
if (org == '' || number == '' || display_name == ''){ if (org == '' || number == '' || display_name == ''){
alert('You must specify all fields in order to create a new course.'); alert('You must specify all fields in order to create a new course.');
return; return;
} }
$.post('/create_new_course', { $.post('/create_new_course', {
'template' : template, 'template' : template,
'org' : org, 'org' : org,
'number' : number, 'number' : number,
'display_name': display_name 'display_name': display_name
}, },
function(data) { function(data) {
if (data.id != undefined) { if (data.id != undefined) {
window.location = '/' + data.id.replace(/.*:\/\//, ''); window.location = '/' + data.id.replace(/.*:\/\//, '');
} else if (data.ErrMsg != undefined) { } else if (data.ErrMsg != undefined) {
alert(data.ErrMsg); alert(data.ErrMsg);
} }
}); });
} }
function cancelNewCourse(e) { function cancelNewCourse(e) {
e.preventDefault(); e.preventDefault();
$('.new-course-button').show(); $('.new-course-button').show();
$(this).parents('section.new-course').remove(); $(this).parents('section.new-course').remove();
} }
function addNewSubsection(e) { function addNewSubsection(e) {
e.preventDefault(); e.preventDefault();
var $section = $(this).closest('.courseware-section'); var $section = $(this).closest('.courseware-section');
var $newSubsection = $($('#new-subsection-template').html()); var $newSubsection = $($('#new-subsection-template').html());
$section.find('.subsection-list > ol').append($newSubsection); $section.find('.subsection-list > ol').append($newSubsection);
$section.find('.new-subsection-name-input').focus().select(); $section.find('.new-subsection-name-input').focus().select();
var $saveButton = $newSubsection.find('.new-subsection-name-save'); var $saveButton = $newSubsection.find('.new-subsection-name-save');
var $cancelButton = $newSubsection.find('.new-subsection-name-cancel'); var $cancelButton = $newSubsection.find('.new-subsection-name-cancel');
var parent = $(this).parents("section.branch").data("id"); var parent = $(this).parents("section.branch").data("id");
$saveButton.data('parent', parent); $saveButton.data('parent', parent);
$saveButton.data('template', $(this).data('template')); $saveButton.data('template', $(this).data('template'));
$newSubsection.find('.new-subsection-form').bind('submit', saveNewSubsection); $newSubsection.find('.new-subsection-form').bind('submit', saveNewSubsection);
$cancelButton.bind('click', cancelNewSubsection); $cancelButton.bind('click', cancelNewSubsection);
$body.bind('keyup', { $cancelButton: $cancelButton }, checkForCancel); $body.bind('keyup', { $cancelButton: $cancelButton }, checkForCancel);
} }
function saveNewSubsection(e) { function saveNewSubsection(e) {
e.preventDefault(); e.preventDefault();
var parent = $(this).find('.new-subsection-name-save').data('parent'); var parent = $(this).find('.new-subsection-name-save').data('parent');
var template = $(this).find('.new-subsection-name-save').data('template'); var template = $(this).find('.new-subsection-name-save').data('template');
var display_name = $(this).find('.new-subsection-name-input').val(); var display_name = $(this).find('.new-subsection-name-input').val();
$.post('/clone_item', { $.post('/clone_item', {
'parent_location' : parent, 'parent_location' : parent,
'template' : template, 'template' : template,
'display_name': display_name 'display_name': display_name
}, },
function(data) { function(data) {
if (data.id != undefined) { if (data.id != undefined) {
location.reload(); location.reload();
} }
} }
); );
} }
function cancelNewSubsection(e) { function cancelNewSubsection(e) {
e.preventDefault(); e.preventDefault();
$(this).parents('li.branch').remove(); $(this).parents('li.branch').remove();
} }
function editSectionName(e) { function editSectionName(e) {
e.preventDefault(); e.preventDefault();
$(this).unbind('click', editSectionName); $(this).unbind('click', editSectionName);
$(this).children('.section-name-edit').show(); $(this).children('.section-name-edit').show();
$(this).find('.edit-section-name').focus(); $(this).find('.edit-section-name').focus();
$(this).children('.section-name-span').hide(); $(this).children('.section-name-span').hide();
$(this).find('.section-name-edit').bind('submit', saveEditSectionName); $(this).find('.section-name-edit').bind('submit', saveEditSectionName);
$(this).find('.edit-section-name-cancel').bind('click', cancelNewSection); $(this).find('.edit-section-name-cancel').bind('click', cancelNewSection);
$body.bind('keyup', { $cancelButton: $(this).find('.edit-section-name-cancel') }, checkForCancel); $body.bind('keyup', { $cancelButton: $(this).find('.edit-section-name-cancel') }, checkForCancel);
} }
function cancelEditSectionName(e) { function cancelEditSectionName(e) {
e.preventDefault(); e.preventDefault();
$(this).parent().hide(); $(this).parent().hide();
$(this).parent().siblings('.section-name-span').show(); $(this).parent().siblings('.section-name-span').show();
$(this).closest('.section-name').bind('click', editSectionName); $(this).closest('.section-name').bind('click', editSectionName);
e.stopPropagation(); e.stopPropagation();
} }
function saveEditSectionName(e) { function saveEditSectionName(e) {
e.preventDefault(); e.preventDefault();
$(this).closest('.section-name').unbind('click', editSectionName); $(this).closest('.section-name').unbind('click', editSectionName);
var id = $(this).closest('.courseware-section').data('id'); var id = $(this).closest('.courseware-section').data('id');
var display_name = $.trim($(this).find('.edit-section-name').val()); var display_name = $.trim($(this).find('.edit-section-name').val());
$(this).closest('.courseware-section .section-name').append($spinner); $(this).closest('.courseware-section .section-name').append($spinner);
$spinner.show(); $spinner.show();
if (display_name == '') { if (display_name == '') {
alert("You must specify a name before saving."); alert("You must specify a name before saving.");
return; return;
} }
var $_this = $(this); var $_this = $(this);
// call into server to commit the new order // call into server to commit the new order
$.ajax({ $.ajax({
url: "/save_item", url: "/save_item",
type: "POST", type: "POST",
dataType: "json", dataType: "json",
contentType: "application/json", contentType: "application/json",
data:JSON.stringify({ 'id' : id, 'metadata' : {'display_name' : display_name}}) data:JSON.stringify({ 'id' : id, 'metadata' : {'display_name' : display_name}})
}).success(function() }).success(function()
{ {
$spinner.delay(250).fadeOut(250); $spinner.delay(250).fadeOut(250);
$_this.closest('h3').find('.section-name-span').html(display_name).show(); $_this.closest('h3').find('.section-name-span').html(display_name).show();
$_this.hide(); $_this.hide();
$_this.closest('.section-name').bind('click', editSectionName); $_this.closest('.section-name').bind('click', editSectionName);
e.stopPropagation(); e.stopPropagation();
}); });
} }
function setSectionScheduleDate(e) { function setSectionScheduleDate(e) {
e.preventDefault(); e.preventDefault();
$(this).closest("h4").hide(); $(this).closest("h4").hide();
$(this).parent().siblings(".datepair").show(); $(this).parent().siblings(".datepair").show();
} }
function cancelSetSectionScheduleDate(e) { function cancelSetSectionScheduleDate(e) {
e.preventDefault(); e.preventDefault();
$(this).closest(".datepair").hide(); $(this).closest(".datepair").hide();
$(this).parent().siblings("h4").show(); $(this).parent().siblings("h4").show();
} }
function saveSetSectionScheduleDate(e) { function saveSetSectionScheduleDate(e) {
e.preventDefault(); e.preventDefault();
var input_date = $('.edit-subsection-publish-settings .start-date').val();
var input_time = $('.edit-subsection-publish-settings .start-time').val();
var start = getEdxTimeFromDateTimeVals(input_date, input_time);
var id = $modal.attr('data-id');
// call into server to commit the new order var input_date = $('.edit-subsection-publish-settings .start-date').val();
$.ajax({ var input_time = $('.edit-subsection-publish-settings .start-time').val();
url: "/save_item",
type: "POST", var start = getEdxTimeFromDateTimeVals(input_date, input_time);
dataType: "json",
contentType: "application/json", var id = $modal.attr('data-id');
data:JSON.stringify({ 'id' : id, 'metadata' : {'start' : start}})
}).success(function() // call into server to commit the new order
{ $.ajax({
var $thisSection = $('.courseware-section[data-id="' + id + '"]'); url: "/save_item",
$thisSection.find('.section-published-date').html('<span class="published-status"><strong>Will Release:</strong> ' + input_date + ' at ' + input_time + '</span><a href="#" class="edit-button" data-date="' + input_date + '" data-time="' + input_time + '" data-id="' + id + '">Edit</a>'); type: "POST",
$thisSection.find('.section-published-date').animate({ dataType: "json",
'background-color': 'rgb(182,37,104)' contentType: "application/json",
}, 300).animate({ data:JSON.stringify({ 'id' : id, 'metadata' : {'start' : start}})
'background-color': '#edf1f5' }).success(function()
}, 300).animate({ {
'background-color': 'rgb(182,37,104)' var $thisSection = $('.courseware-section[data-id="' + id + '"]');
}, 300).animate({ $thisSection.find('.section-published-date').html('<span class="published-status"><strong>Will Release:</strong> ' + input_date + ' at ' + input_time + '</span><a href="#" class="edit-button" data-date="' + input_date + '" data-time="' + input_time + '" data-id="' + id + '">Edit</a>');
'background-color': '#edf1f5' $thisSection.find('.section-published-date').animate({
}, 300); 'background-color': 'rgb(182,37,104)'
}, 300).animate({
hideModal(); 'background-color': '#edf1f5'
}); }, 300).animate({
'background-color': 'rgb(182,37,104)'
}, 300).animate({
'background-color': '#edf1f5'
}, 300);
hideModal();
});
} }
if (!CMS.Models['Settings']) CMS.Models.Settings = new Object(); if (!CMS.Models['Settings']) CMS.Models.Settings = new Object();
CMS.Models.Settings.CourseDetails = Backbone.Model.extend({ CMS.Models.Settings.CourseDetails = Backbone.Model.extend({
defaults: { defaults: {
location : null, // the course's Location model, required location : null, // the course's Location model, required
start_date: null, // maps to 'start' start_date: null, // maps to 'start'
end_date: null, // maps to 'end' end_date: null, // maps to 'end'
enrollment_start: null, enrollment_start: null,
enrollment_end: null, enrollment_end: null,
syllabus: null, syllabus: null,
overview: "", overview: "",
intro_video: null, intro_video: null,
effort: null // an int or null effort: null // an int or null
}, },
// When init'g from html script, ensure you pass {parse: true} as an option (2nd arg to reset) // When init'g from html script, ensure you pass {parse: true} as an option (2nd arg to reset)
parse: function(attributes) { parse: function(attributes) {
if (attributes['course_location']) { if (attributes['course_location']) {
attributes.location = new CMS.Models.Location(attributes.course_location, {parse:true}); attributes.location = new CMS.Models.Location(attributes.course_location, {parse:true});
} }
if (attributes['start_date']) { if (attributes['start_date']) {
attributes.start_date = new Date(attributes.start_date); attributes.start_date = new Date(attributes.start_date);
} }
if (attributes['end_date']) { if (attributes['end_date']) {
attributes.end_date = new Date(attributes.end_date); attributes.end_date = new Date(attributes.end_date);
} }
if (attributes['enrollment_start']) { if (attributes['enrollment_start']) {
attributes.enrollment_start = new Date(attributes.enrollment_start); attributes.enrollment_start = new Date(attributes.enrollment_start);
} }
if (attributes['enrollment_end']) { if (attributes['enrollment_end']) {
attributes.enrollment_end = new Date(attributes.enrollment_end); attributes.enrollment_end = new Date(attributes.enrollment_end);
} }
return attributes; return attributes;
}, },
validate: function(newattrs) { validate: function(newattrs) {
// Returns either nothing (no return call) so that validate works or an object of {field: errorstring} pairs // Returns either nothing (no return call) so that validate works or an object of {field: errorstring} pairs
// A bit funny in that the video key validation is asynchronous; so, it won't stop the validation. // A bit funny in that the video key validation is asynchronous; so, it won't stop the validation.
var errors = {}; var errors = {};
if (newattrs.start_date && newattrs.end_date && newattrs.start_date >= newattrs.end_date) { if (newattrs.start_date && newattrs.end_date && newattrs.start_date >= newattrs.end_date) {
errors.end_date = "The course end date cannot be before the course start date."; errors.end_date = "The course end date cannot be before the course start date.";
} }
if (newattrs.start_date && newattrs.enrollment_start && newattrs.start_date < newattrs.enrollment_start) { if (newattrs.start_date && newattrs.enrollment_start && newattrs.start_date < newattrs.enrollment_start) {
errors.enrollment_start = "The course start date cannot be before the enrollment start date."; errors.enrollment_start = "The course start date cannot be before the enrollment start date.";
} }
if (newattrs.enrollment_start && newattrs.enrollment_end && newattrs.enrollment_start >= newattrs.enrollment_end) { if (newattrs.enrollment_start && newattrs.enrollment_end && newattrs.enrollment_start >= newattrs.enrollment_end) {
errors.enrollment_end = "The enrollment start date cannot be after the enrollment end date."; errors.enrollment_end = "The enrollment start date cannot be after the enrollment end date.";
} }
if (newattrs.end_date && newattrs.enrollment_end && newattrs.end_date < newattrs.enrollment_end) { if (newattrs.end_date && newattrs.enrollment_end && newattrs.end_date < newattrs.enrollment_end) {
errors.enrollment_end = "The enrollment end date cannot be after the course end date."; errors.enrollment_end = "The enrollment end date cannot be after the course end date.";
} }
if (newattrs.intro_video && newattrs.intro_video !== this.get('intro_video')) { if (newattrs.intro_video && newattrs.intro_video !== this.get('intro_video')) {
if (this._videokey_illegal_chars.exec(newattrs.intro_video)) { if (this._videokey_illegal_chars.exec(newattrs.intro_video)) {
errors.intro_video = "Key should only contain letters, numbers, _, or -"; errors.intro_video = "Key should only contain letters, numbers, _, or -";
} }
// TODO check if key points to a real video using google's youtube api // TODO check if key points to a real video using google's youtube api
} }
if (!_.isEmpty(errors)) return errors; if (!_.isEmpty(errors)) return errors;
// NOTE don't return empty errors as that will be interpreted as an error state // NOTE don't return empty errors as that will be interpreted as an error state
}, },
url: function() { url: function() {
var location = this.get('location'); var location = this.get('location');
return '/' + location.get('org') + "/" + location.get('course') + '/settings/' + location.get('name') + '/section/details'; return '/' + location.get('org') + "/" + location.get('course') + '/settings-details/' + location.get('name') + '/section/details';
}, },
_videokey_illegal_chars : /[^a-zA-Z0-9_-]/g, _videokey_illegal_chars : /[^a-zA-Z0-9_-]/g,
save_videosource: function(newsource) { save_videosource: function(newsource) {
// newsource either is <video youtube="speed:key, *"/> or just the "speed:key, *" string // newsource either is <video youtube="speed:key, *"/> or just the "speed:key, *" string
// returns the videosource for the preview which iss the key whose speed is closest to 1 // returns the videosource for the preview which iss the key whose speed is closest to 1
if (_.isEmpty(newsource) && !_.isEmpty(this.get('intro_video'))) this.save({'intro_video': null}, if (_.isEmpty(newsource) && !_.isEmpty(this.get('intro_video'))) this.set({'intro_video': null});
{ error : CMS.ServerError}); // TODO remove all whitespace w/in string
// TODO remove all whitespace w/in string else {
else { if (this.get('intro_video') !== newsource) this.set('intro_video', newsource);
if (this.get('intro_video') !== newsource) this.save('intro_video', newsource, }
{ error : CMS.ServerError});
} return this.videosourceSample();
},
return this.videosourceSample(); videosourceSample : function() {
}, if (this.has('intro_video')) return "http://www.youtube.com/embed/" + this.get('intro_video');
videosourceSample : function() { else return "";
if (this.has('intro_video')) return "http://www.youtube.com/embed/" + this.get('intro_video'); }
else return "";
}
}); });
if (!CMS.Models['Settings']) CMS.Models.Settings = new Object(); if (!CMS.Models['Settings']) CMS.Models.Settings = new Object();
CMS.Models.Settings.CourseGradingPolicy = Backbone.Model.extend({ CMS.Models.Settings.CourseGradingPolicy = Backbone.Model.extend({
defaults : { defaults : {
course_location : null, course_location : null,
graders : null, // CourseGraderCollection graders : null, // CourseGraderCollection
grade_cutoffs : null, // CourseGradeCutoff model grade_cutoffs : null, // CourseGradeCutoff model
grace_period : null // either null or { hours: n, minutes: m, ...} grace_period : null // either null or { hours: n, minutes: m, ...}
}, },
parse: function(attributes) { parse: function(attributes) {
if (attributes['course_location']) { if (attributes['course_location']) {
attributes.course_location = new CMS.Models.Location(attributes.course_location, {parse:true}); attributes.course_location = new CMS.Models.Location(attributes.course_location, {parse:true});
} }
if (attributes['graders']) { if (attributes['graders']) {
var graderCollection; var graderCollection;
if (this.has('graders')) { // interesting race condition: if {parse:true} when newing, then parse called before .attributes created
graderCollection = this.get('graders'); if (this.attributes && this.has('graders')) {
graderCollection.reset(attributes.graders); graderCollection = this.get('graders');
} graderCollection.reset(attributes.graders);
else { }
graderCollection = new CMS.Models.Settings.CourseGraderCollection(attributes.graders); else {
graderCollection.course_location = attributes['course_location'] || this.get('course_location'); graderCollection = new CMS.Models.Settings.CourseGraderCollection(attributes.graders);
} graderCollection.course_location = attributes['course_location'] || this.get('course_location');
attributes.graders = graderCollection; }
} attributes.graders = graderCollection;
return attributes; }
}, return attributes;
url : function() { },
var location = this.get('course_location'); url : function() {
return '/' + location.get('org') + "/" + location.get('course') + '/settings/' + location.get('name') + '/section/grading'; var location = this.get('course_location');
}, return '/' + location.get('org') + "/" + location.get('course') + '/settings-details/' + location.get('name') + '/section/grading';
gracePeriodToDate : function() { },
var newDate = new Date(); gracePeriodToDate : function() {
if (this.has('grace_period') && this.get('grace_period')['hours']) var newDate = new Date();
newDate.setHours(this.get('grace_period')['hours']); if (this.has('grace_period') && this.get('grace_period')['hours'])
else newDate.setHours(0); newDate.setHours(this.get('grace_period')['hours']);
if (this.has('grace_period') && this.get('grace_period')['minutes']) else newDate.setHours(0);
newDate.setMinutes(this.get('grace_period')['minutes']); if (this.has('grace_period') && this.get('grace_period')['minutes'])
else newDate.setMinutes(0); newDate.setMinutes(this.get('grace_period')['minutes']);
if (this.has('grace_period') && this.get('grace_period')['seconds']) else newDate.setMinutes(0);
newDate.setSeconds(this.get('grace_period')['seconds']); if (this.has('grace_period') && this.get('grace_period')['seconds'])
else newDate.setSeconds(0); newDate.setSeconds(this.get('grace_period')['seconds']);
else newDate.setSeconds(0);
return newDate;
}, return newDate;
dateToGracePeriod : function(date) { },
return {hours : date.getHours(), minutes : date.getMinutes(), seconds : date.getSeconds() }; dateToGracePeriod : function(date) {
} return {hours : date.getHours(), minutes : date.getMinutes(), seconds : date.getSeconds() };
}
}); });
CMS.Models.Settings.CourseGrader = Backbone.Model.extend({ CMS.Models.Settings.CourseGrader = Backbone.Model.extend({
defaults: { defaults: {
"type" : "", // must be unique w/in collection (ie. w/in course) "type" : "", // must be unique w/in collection (ie. w/in course)
"min_count" : 1, "min_count" : 1,
"drop_count" : 0, "drop_count" : 0,
...@@ -57,71 +58,71 @@ CMS.Models.Settings.CourseGrader = Backbone.Model.extend({ ...@@ -57,71 +58,71 @@ CMS.Models.Settings.CourseGrader = Backbone.Model.extend({
"weight" : 0 // int 0..100 "weight" : 0 // int 0..100
}, },
parse : function(attrs) { parse : function(attrs) {
if (attrs['weight']) { if (attrs['weight']) {
if (!_.isNumber(attrs.weight)) attrs.weight = parseInt(attrs.weight); if (!_.isNumber(attrs.weight)) attrs.weight = parseInt(attrs.weight);
} }
if (attrs['min_count']) { if (attrs['min_count']) {
if (!_.isNumber(attrs.min_count)) attrs.min_count = parseInt(attrs.min_count); if (!_.isNumber(attrs.min_count)) attrs.min_count = parseInt(attrs.min_count);
} }
if (attrs['drop_count']) { if (attrs['drop_count']) {
if (!_.isNumber(attrs.drop_count)) attrs.drop_count = parseInt(attrs.drop_count); if (!_.isNumber(attrs.drop_count)) attrs.drop_count = parseInt(attrs.drop_count);
} }
return attrs; return attrs;
}, },
validate : function(attrs) { validate : function(attrs) {
var errors = {}; var errors = {};
if (attrs['type']) { if (attrs['type']) {
if (_.isEmpty(attrs['type'])) { if (_.isEmpty(attrs['type'])) {
errors.type = "The assignment type must have a name."; errors.type = "The assignment type must have a name.";
} }
else { else {
// FIXME somehow this.collection is unbound sometimes. I can't track down when // FIXME somehow this.collection is unbound sometimes. I can't track down when
var existing = this.collection && this.collection.some(function(other) { return (other != this) && (other.get('type') == attrs['type']);}, this); var existing = this.collection && this.collection.some(function(other) { return (other != this) && (other.get('type') == attrs['type']);}, this);
if (existing) { if (existing) {
errors.type = "There's already another assignment type with this name."; errors.type = "There's already another assignment type with this name.";
} }
} }
} }
if (attrs['weight']) { if (attrs['weight']) {
if (!isFinite(attrs.weight) || /\D+/.test(attrs.weight)) { if (!isFinite(attrs.weight) || /\D+/.test(attrs.weight)) {
errors.weight = "Please enter an integer between 0 and 100."; errors.weight = "Please enter an integer between 0 and 100.";
} }
else { else {
attrs.weight = parseInt(attrs.weight); // see if this ensures value saved is int attrs.weight = parseInt(attrs.weight); // see if this ensures value saved is int
if (this.collection && attrs.weight > 0) { if (this.collection && attrs.weight > 0) {
// FIXME b/c saves don't update the models if validation fails, we should // FIXME b/c saves don't update the models if validation fails, we should
// either revert the field value to the one in the model and make them make room // either revert the field value to the one in the model and make them make room
// or figure out a wholistic way to balance the vals across the whole // or figure out a wholistic way to balance the vals across the whole
// if ((this.collection.sumWeights() + attrs.weight - this.get('weight')) > 100) // if ((this.collection.sumWeights() + attrs.weight - this.get('weight')) > 100)
// errors.weight = "The weights cannot add to more than 100."; // errors.weight = "The weights cannot add to more than 100.";
} }
}} }}
if (attrs['min_count']) { if (attrs['min_count']) {
if (!isFinite(attrs.min_count) || /\D+/.test(attrs.min_count)) { if (!isFinite(attrs.min_count) || /\D+/.test(attrs.min_count)) {
errors.min_count = "Please enter an integer."; errors.min_count = "Please enter an integer.";
} }
else attrs.min_count = parseInt(attrs.min_count); else attrs.min_count = parseInt(attrs.min_count);
} }
if (attrs['drop_count']) { if (attrs['drop_count']) {
if (!isFinite(attrs.drop_count) || /\D+/.test(attrs.drop_count)) { if (!isFinite(attrs.drop_count) || /\D+/.test(attrs.drop_count)) {
errors.drop_count = "Please enter an integer."; errors.drop_count = "Please enter an integer.";
} }
else attrs.drop_count = parseInt(attrs.drop_count); else attrs.drop_count = parseInt(attrs.drop_count);
} }
if (attrs['min_count'] && attrs['drop_count'] && attrs.drop_count > attrs.min_count) { if (attrs['min_count'] && attrs['drop_count'] && attrs.drop_count > attrs.min_count) {
errors.drop_count = "Cannot drop more " + attrs.type + " than will assigned."; errors.drop_count = "Cannot drop more " + attrs.type + " than will assigned.";
} }
if (!_.isEmpty(errors)) return errors; if (!_.isEmpty(errors)) return errors;
} }
}); });
CMS.Models.Settings.CourseGraderCollection = Backbone.Collection.extend({ CMS.Models.Settings.CourseGraderCollection = Backbone.Collection.extend({
model : CMS.Models.Settings.CourseGrader, model : CMS.Models.Settings.CourseGrader,
course_location : null, // must be set to a Location object course_location : null, // must be set to a Location object
url : function() { url : function() {
return '/' + this.course_location.get('org') + "/" + this.course_location.get('course') + '/grades/' + this.course_location.get('name') + '/'; return '/' + this.course_location.get('org') + "/" + this.course_location.get('course') + '/settings-grading/' + this.course_location.get('name') + '/';
}, },
sumWeights : function() { sumWeights : function() {
return this.reduce(function(subtotal, grader) { return subtotal + grader.get('weight'); }, 0); return this.reduce(function(subtotal, grader) { return subtotal + grader.get('weight'); }, 0);
} }
}); });
\ No newline at end of file
if (!CMS.Models['Settings']) CMS.Models.Settings = new Object(); if (!CMS.Models['Settings']) CMS.Models.Settings = new Object();
CMS.Models.Settings.CourseSettings = Backbone.Model.extend({ CMS.Models.Settings.CourseSettings = Backbone.Model.extend({
// a container for the models representing the n possible tabbed states // a container for the models representing the n possible tabbed states
defaults: { defaults: {
courseLocation: null, courseLocation: null,
// NOTE: keep these sync'd w/ the data-section names in settings-page-menu details: null,
details: null, faculty: null,
faculty: null, grading: null,
grading: null, problems: null,
problems: null, discussions: null
discussions: null },
},
retrieve: function(submodel, callback) { retrieve: function(submodel, callback) {
if (this.get(submodel)) callback(); if (this.get(submodel)) callback();
else { else {
var cachethis = this; var cachethis = this;
switch (submodel) { switch (submodel) {
case 'details': case 'details':
var details = new CMS.Models.Settings.CourseDetails({location: this.get('courseLocation')}); var details = new CMS.Models.Settings.CourseDetails({location: this.get('courseLocation')});
details.fetch( { details.fetch( {
success : function(model) { success : function(model) {
cachethis.set('details', model); cachethis.set('details', model);
callback(model); callback(model);
} }
}); });
break; break;
case 'grading': case 'grading':
var grading = new CMS.Models.Settings.CourseGradingPolicy({course_location: this.get('courseLocation')}); var grading = new CMS.Models.Settings.CourseGradingPolicy({course_location: this.get('courseLocation')});
grading.fetch( { grading.fetch( {
success : function(model) { success : function(model) {
cachethis.set('grading', model); cachethis.set('grading', model);
callback(model); callback(model);
} }
}); });
break; break;
default: default:
break; break;
} }
} }
} }
}) })
\ No newline at end of file
if (!CMS.Views['Settings']) CMS.Views.Settings = {}; if (!CMS.Views['Settings']) CMS.Views.Settings = {}; // ensure the pseudo pkg exists
// TODO move to common place CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
CMS.Views.ValidatingView = Backbone.View.extend({ // Model class is CMS.Models.Settings.CourseDetails
// Intended as an abstract class which catches validation errors on the model and events : {
// decorates the fields. Needs wiring per class, but this initialization shows how "blur input" : "updateModel",
// either have your init call this one or copy the contents "blur textarea" : "updateModel",
initialize : function() { 'click .remove-course-syllabus' : "removeSyllabus",
this.model.on('error', this.handleValidationError, this); 'click .new-course-syllabus' : 'assetSyllabus',
this.selectorToField = _.invert(this.fieldToSelectorMap); 'click .remove-course-introduction-video' : "removeVideo",
}, 'focus #course-overview' : "codeMirrorize",
'mouseover #timezone' : "updateTime",
errorTemplate : _.template('<span class="message-error"><%= message %></span>'), // would love to move to a general superclass, but event hashes don't inherit in backbone :-(
'focus :input' : "inputFocus",
'blur :input' : "inputUnfocus"
events : { },
"blur input" : "clearValidationErrors", initialize : function() {
"blur textarea" : "clearValidationErrors" this.fileAnchorTemplate = _.template('<a href="<%= fullpath %>"> <i class="ss-icon ss-standard">&#x1F4C4;</i><%= filename %></a>');
}, // fill in fields
fieldToSelectorMap : { this.$el.find("#course-name").val(this.model.get('location').get('name'));
// Your subclass must populate this w/ all of the model keys and dom selectors this.$el.find("#course-organization").val(this.model.get('location').get('org'));
// which may be the subjects of validation errors this.$el.find("#course-number").val(this.model.get('location').get('course'));
}, this.$el.find('.set-date').datepicker({ 'dateFormat': 'm/d/yy' });
_cacheValidationErrors : [],
handleValidationError : function(model, error) { var dateIntrospect = new Date();
// error is object w/ fields and error strings this.$el.find('#timezone').html("(" + dateIntrospect.getTimezone() + ")");
for (var field in error) {
var ele = this.$el.find('#' + this.fieldToSelectorMap[field]); this.model.on('error', this.handleValidationError, this);
this._cacheValidationErrors.push(ele); this.selectorToField = _.invert(this.fieldToSelectorMap);
if ($(ele).is('div')) { },
// put error on the contained inputs
$(ele).find('input, textarea').addClass('error');
}
else $(ele).addClass('error');
$(ele).parent().append(this.errorTemplate({message : error[field]}));
}
},
clearValidationErrors : function() {
// error is object w/ fields and error strings
while (this._cacheValidationErrors.length > 0) {
var ele = this._cacheValidationErrors.pop();
if ($(ele).is('div')) {
// put error on the contained inputs
$(ele).find('input, textarea').removeClass('error');
}
else $(ele).removeClass('error');
$(ele).nextAll('.message-error').remove();
}
},
saveIfChanged : function(event) {
// returns true if the value changed and was thus sent to server
var field = this.selectorToField[event.currentTarget.id];
var currentVal = this.model.get(field);
var newVal = $(event.currentTarget).val();
if (currentVal != newVal) {
this.clearValidationErrors();
this.model.save(field, newVal, { error : CMS.ServerError});
return true;
}
else return false;
}
});
CMS.Views.Settings.Main = Backbone.View.extend({ render: function() {
// Model class is CMS.Models.Settings.CourseSettings this.setupDatePicker('start_date');
// allow navigation between the tabs this.setupDatePicker('end_date');
events: { this.setupDatePicker('enrollment_start');
'click .settings-page-menu a': "showSettingsTab", this.setupDatePicker('enrollment_end');
'mouseover #timezone' : "updateTime"
}, if (this.model.has('syllabus')) {
this.$el.find(this.fieldToSelectorMap['syllabus']).html(
currentTab: null, this.fileAnchorTemplate({
subviews: {}, // indexed by tab name fullpath : this.model.get('syllabus'),
filename: 'syllabus'}));
this.$el.find('.remove-course-syllabus').show();
}
else {
this.$el.find('#' + this.fieldToSelectorMap['syllabus']).html("");
this.$el.find('.remove-course-syllabus').hide();
}
initialize: function() { this.$el.find('#' + this.fieldToSelectorMap['overview']).val(this.model.get('overview'));
// load templates this.codeMirrorize(null, $('#course-overview')[0]);
this.currentTab = this.$el.find('.settings-page-menu .is-shown').attr('data-section');
// create the initial subview
this.subviews[this.currentTab] = this.createSubview();
// fill in fields
this.$el.find("#course-name").val(this.model.get('courseLocation').get('name'));
this.$el.find("#course-organization").val(this.model.get('courseLocation').get('org'));
this.$el.find("#course-number").val(this.model.get('courseLocation').get('course'));
this.$el.find('.set-date').datepicker({ 'dateFormat': 'm/d/yy' });
this.$el.find(":input, textarea").focus(function() {
$("label[for='" + this.id + "']").addClass("is-focused");
}).blur(function() {
$("label").removeClass("is-focused");
});
this.render();
},
render: function() {
// create any necessary subviews and put them onto the page
if (!this.model.has(this.currentTab)) {
// TODO disable screen until fetch completes?
var cachethis = this;
this.model.retrieve(this.currentTab, function() {
cachethis.subviews[cachethis.currentTab] = cachethis.createSubview();
cachethis.subviews[cachethis.currentTab].render();
});
}
else this.subviews[this.currentTab].render();
var dateIntrospect = new Date();
this.$el.find('#timezone').html("(" + dateIntrospect.getTimezone() + ")");
return this;
},
createSubview: function() {
switch (this.currentTab) {
case 'details':
return new CMS.Views.Settings.Details({
el: this.$el.find('.settings-' + this.currentTab),
model: this.model.get(this.currentTab)
});
case 'faculty':
break;
case 'grading':
return new CMS.Views.Settings.Grading({
el: this.$el.find('.settings-' + this.currentTab),
model: this.model.get(this.currentTab)
});
case 'problems':
break;
case 'discussions':
break;
}
},
updateTime : function(e) {
var now = new Date();
var hours = now.getHours();
var minutes = now.getMinutes();
$(e.currentTarget).attr('title', (hours % 12 === 0 ? 12 : hours % 12) + ":" + (minutes < 10 ? "0" : "") +
now.getMinutes() + (hours < 12 ? "am" : "pm") + " (current local time)");
},
showSettingsTab: function(e) {
this.currentTab = $(e.target).attr('data-section');
$('.settings-page-section > section').hide();
$('.settings-' + this.currentTab).show();
$('.settings-page-menu .is-shown').removeClass('is-shown');
$(e.target).addClass('is-shown');
// fetch model for the tab if not loaded already
this.render();
}
}); this.$el.find('.current-course-introduction-video iframe').attr('src', this.model.videosourceSample());
if (this.model.has('intro_video')) {
this.$el.find('.remove-course-introduction-video').show();
this.$el.find('#' + this.fieldToSelectorMap['intro_video']).val(this.model.get('intro_video'));
}
else this.$el.find('.remove-course-introduction-video').hide();
CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({ this.$el.find('#' + this.fieldToSelectorMap['effort']).val(this.model.get('effort'));
// Model class is CMS.Models.Settings.CourseDetails
events : { return this;
"blur input" : "updateModel", },
"blur textarea" : "updateModel", fieldToSelectorMap : {
'click .remove-course-syllabus' : "removeSyllabus", 'start_date' : "course-start",
'click .new-course-syllabus' : 'assetSyllabus', 'end_date' : 'course-end',
'click .remove-course-introduction-video' : "removeVideo", 'enrollment_start' : 'enrollment-start',
'focus #course-overview' : "codeMirrorize" 'enrollment_end' : 'enrollment-end',
}, 'syllabus' : '.current-course-syllabus .doc-filename',
initialize : function() { 'overview' : 'course-overview',
// TODO move the html frag to a loaded asset 'intro_video' : 'course-introduction-video',
this.fileAnchorTemplate = _.template('<a href="<%= fullpath %>"> <i class="ss-icon ss-standard">&#x1F4C4;</i><%= filename %></a>'); 'effort' : "course-effort"
this.model.on('error', this.handleValidationError, this); },
this.selectorToField = _.invert(this.fieldToSelectorMap);
}, updateTime : function(e) {
var now = new Date();
render: function() { var hours = now.getHours();
this.setupDatePicker('start_date'); var minutes = now.getMinutes();
this.setupDatePicker('end_date'); $(e.currentTarget).attr('title', (hours % 12 === 0 ? 12 : hours % 12) + ":" + (minutes < 10 ? "0" : "") +
this.setupDatePicker('enrollment_start'); now.getMinutes() + (hours < 12 ? "am" : "pm") + " (current local time)");
this.setupDatePicker('enrollment_end'); },
if (this.model.has('syllabus')) {
this.$el.find(this.fieldToSelectorMap['syllabus']).html(
this.fileAnchorTemplate({
fullpath : this.model.get('syllabus'),
filename: 'syllabus'}));
this.$el.find('.remove-course-syllabus').show();
}
else {
this.$el.find('#' + this.fieldToSelectorMap['syllabus']).html("");
this.$el.find('.remove-course-syllabus').hide();
}
this.$el.find('#' + this.fieldToSelectorMap['overview']).val(this.model.get('overview'));
this.codeMirrorize(null, $('#course-overview')[0]);
this.$el.find('.current-course-introduction-video iframe').attr('src', this.model.videosourceSample());
if (this.model.has('intro_video')) {
this.$el.find('.remove-course-introduction-video').show();
this.$el.find('#' + this.fieldToSelectorMap['intro_video']).val(this.model.get('intro_video'));
}
else this.$el.find('.remove-course-introduction-video').hide();
this.$el.find('#' + this.fieldToSelectorMap['effort']).val(this.model.get('effort'));
return this;
},
fieldToSelectorMap : {
'start_date' : "course-start",
'end_date' : 'course-end',
'enrollment_start' : 'enrollment-start',
'enrollment_end' : 'enrollment-end',
'syllabus' : '.current-course-syllabus .doc-filename',
'overview' : 'course-overview',
'intro_video' : 'course-introduction-video',
'effort' : "course-effort"
},
setupDatePicker: function (fieldName) { setupDatePicker: function (fieldName) {
var cacheModel = this.model; var cacheModel = this.model;
var div = this.$el.find('#' + this.fieldToSelectorMap[fieldName]); var div = this.$el.find('#' + this.fieldToSelectorMap[fieldName]);
var datefield = $(div).find(".date"); var datefield = $(div).find("input:.date");
var timefield = $(div).find(".time"); var timefield = $(div).find("input:.time");
var cachethis = this; var cachethis = this;
var savefield = function () { var savefield = function () {
cachethis.clearValidationErrors(); cachethis.clearValidationErrors();
...@@ -228,7 +97,7 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({ ...@@ -228,7 +97,7 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
} }
var newVal = new Date(date.getTime() + time * 1000); var newVal = new Date(date.getTime() + time * 1000);
if (!cacheModel.has(fieldName) || cacheModel.get(fieldName).getTime() !== newVal.getTime()) { if (!cacheModel.has(fieldName) || cacheModel.get(fieldName).getTime() !== newVal.getTime()) {
cacheModel.save(fieldName, newVal, { error: CMS.ServerError}); cacheModel.save(fieldName, newVal);
} }
} }
}; };
...@@ -245,58 +114,57 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({ ...@@ -245,58 +114,57 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
datefield.datepicker('setDate', this.model.get(fieldName)); datefield.datepicker('setDate', this.model.get(fieldName));
if (this.model.has(fieldName)) timefield.timepicker('setTime', this.model.get(fieldName)); if (this.model.has(fieldName)) timefield.timepicker('setTime', this.model.get(fieldName));
}, },
updateModel: function(event) {
switch (event.currentTarget.id) {
case 'course-start-date': // handled via onSelect method
case 'course-end-date':
case 'course-enrollment-start-date':
case 'course-enrollment-end-date':
break;
case 'course-overview': updateModel: function(event) {
// handled via code mirror switch (event.currentTarget.id) {
break; case 'course-start-date': // handled via onSelect method
case 'course-end-date':
case 'course-enrollment-start-date':
case 'course-enrollment-end-date':
break;
case 'course-overview':
// handled via code mirror
break;
case 'course-effort':
this.saveIfChanged(event);
break;
case 'course-introduction-video':
this.clearValidationErrors();
var previewsource = this.model.save_videosource($(event.currentTarget).val());
this.$el.find(".current-course-introduction-video iframe").attr("src", previewsource);
if (this.model.has('intro_video')) {
this.$el.find('.remove-course-introduction-video').show();
}
else {
this.$el.find('.remove-course-introduction-video').hide();
}
break;
default:
break;
}
},
removeSyllabus: function() {
if (this.model.has('syllabus')) this.model.save({'syllabus': null});
},
assetSyllabus : function() {
// TODO implement
},
case 'course-effort': removeVideo: function() {
this.saveIfChanged(event); if (this.model.has('intro_video')) {
break; this.model.save_videosource(null);
case 'course-introduction-video': this.$el.find(".current-course-introduction-video iframe").attr("src", "");
this.clearValidationErrors(); this.$el.find('#' + this.fieldToSelectorMap['intro_video']).val("");
var previewsource = this.model.save_videosource($(event.currentTarget).val()); this.$el.find('.remove-course-introduction-video').hide();
this.$el.find(".current-course-introduction-video iframe").attr("src", previewsource); }
if (this.model.has('intro_video')) { },
this.$el.find('.remove-course-introduction-video').show(); codeMirrors : {},
}
else {
this.$el.find('.remove-course-introduction-video').hide();
}
break;
default:
break;
}
},
removeSyllabus: function() {
if (this.model.has('syllabus')) this.model.save({'syllabus': null},
{ error : CMS.ServerError});
},
assetSyllabus : function() {
// TODO implement
},
removeVideo: function() {
if (this.model.has('intro_video')) {
this.model.save_videosource(null);
this.$el.find(".current-course-introduction-video iframe").attr("src", "");
this.$el.find('#' + this.fieldToSelectorMap['intro_video']).val("");
this.$el.find('.remove-course-introduction-video').hide();
}
},
codeMirrors : {},
codeMirrorize: function (e, forcedTarget) { codeMirrorize: function (e, forcedTarget) {
var thisTarget; var thisTarget;
if (forcedTarget) { if (forcedTarget) {
...@@ -315,374 +183,11 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({ ...@@ -315,374 +183,11 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
mirror.save(); mirror.save();
cachethis.clearValidationErrors(); cachethis.clearValidationErrors();
var newVal = mirror.getValue(); var newVal = mirror.getValue();
if (cachethis.model.get(field) != newVal) cachethis.model.save(field, newVal, if (cachethis.model.get(field) != newVal) cachethis.model.save(field, newVal);
{ error: CMS.ServerError});
} }
}); });
} }
} }
});
CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
// Model class is CMS.Models.Settings.CourseGradingPolicy
events : {
"blur input" : "updateModel",
"blur textarea" : "updateModel",
"blur span[contenteditable=true]" : "updateDesignation",
"click .settings-extra header" : "showSettingsExtras",
"click .new-grade-button" : "addNewGrade",
"click .remove-button" : "removeGrade",
"click .add-grading-data" : "addAssignmentType"
},
initialize : function() {
// load template for grading view
var self = this;
this.gradeCutoffTemplate = _.template('<li class="grade-specific-bar" style="width:<%= width %>%"><span class="letter-grade" contenteditable>' +
'<%= descriptor %>' +
'</span><span class="range"></span>' +
'<% if (removable) {%><a href="#" class="remove-button">remove</a><% ;} %>' +
'</li>');
// Instrument grading scale
// convert cutoffs to inversely ordered list
var modelCutoffs = this.model.get('grade_cutoffs');
for (var cutoff in modelCutoffs) {
this.descendingCutoffs.push({designation: cutoff, cutoff: Math.round(modelCutoffs[cutoff] * 100)});
}
this.descendingCutoffs = _.sortBy(this.descendingCutoffs,
function (gradeEle) { return -gradeEle['cutoff']; });
// Instrument grace period
this.$el.find('#course-grading-graceperiod').timepicker();
// instantiates an editor template for each update in the collection
// Because this calls render, put it after everything which render may depend upon to prevent race condition.
window.templateLoader.loadRemoteTemplate("course_grade_policy",
"/static/client_templates/course_grade_policy.html",
function (raw_template) {
self.template = _.template(raw_template);
self.render();
}
);
this.model.on('error', this.handleValidationError, this);
this.model.get('graders').on('remove', this.render, this);
this.model.get('graders').on('reset', this.render, this);
this.model.get('graders').on('add', this.render, this);
this.selectorToField = _.invert(this.fieldToSelectorMap);
},
render: function() {
// prevent bootstrap race condition by event dispatch
if (!this.template) return;
// Create and render the grading type subs
var self = this;
var gradelist = this.$el.find('.course-grading-assignment-list');
// Undo the double invocation error. At some point, fix the double invocation
$(gradelist).empty();
var gradeCollection = this.model.get('graders');
gradeCollection.each(function(gradeModel) {
$(gradelist).append(self.template({model : gradeModel }));
var newEle = gradelist.children().last();
var newView = new CMS.Views.Settings.GraderView({el: newEle,
model : gradeModel, collection : gradeCollection });
});
// render the grade cutoffs
this.renderCutoffBar();
var graceEle = this.$el.find('#course-grading-graceperiod');
graceEle.timepicker({'timeFormat' : 'H:i'}); // init doesn't take setTime
if (this.model.has('grace_period')) graceEle.timepicker('setTime', this.model.gracePeriodToDate());
// remove any existing listeners to keep them from piling on b/c render gets called frequently
graceEle.off('change', this.setGracePeriod);
graceEle.on('change', this, this.setGracePeriod);
return this;
},
addAssignmentType : function(e) {
e.preventDefault();
this.model.get('graders').push({});
},
fieldToSelectorMap : {
'grace_period' : 'course-grading-graceperiod'
},
setGracePeriod : function(event) {
event.data.clearValidationErrors();
var newVal = event.data.model.dateToGracePeriod($(event.currentTarget).timepicker('getTime'));
if (event.data.model.get('grace_period') != newVal) event.data.model.save('grace_period', newVal,
{ error : CMS.ServerError});
},
updateModel : function(event) {
if (!this.selectorToField[event.currentTarget.id]) return;
switch (this.selectorToField[event.currentTarget.id]) {
case 'grace_period': // handled above
break;
default:
this.saveIfChanged(event);
break;
}
},
// Grade sliders attributes and methods
// Grade bars are li's ordered A -> F with A taking whole width, B overlaying it with its paint, ...
// The actual cutoff for each grade is the width % of the next lower grade; so, the hack here
// is to lay down a whole width bar claiming it's A and then lay down bars for each actual grade
// starting w/ A but posting the label in the preceding li and setting the label of the last to "Fail" or "F"
// A does not have a drag bar (cannot change its upper limit)
// Need to insert new bars in right place.
GRADES : ['A', 'B', 'C', 'D'], // defaults for new grade designators
descendingCutoffs : [], // array of { designation : , cutoff : }
gradeBarWidth : null, // cache of value since it won't change (more certain)
renderCutoffBar: function() {
var gradeBar =this.$el.find('.grade-bar');
this.gradeBarWidth = gradeBar.width();
var gradelist = gradeBar.children('.grades');
// HACK fixing a duplicate call issue by undoing previous call effect. Need to figure out why called 2x
gradelist.empty();
var nextWidth = 100; // first width is 100%
// Can probably be simplified to one variable now.
var removable = false;
var draggable = false; // first and last are not removable, first is not draggable
_.each(this.descendingCutoffs,
function(cutoff, index) {
var newBar = this.gradeCutoffTemplate({
descriptor : cutoff['designation'] ,
width : nextWidth,
removable : removable });
gradelist.append(newBar);
if (draggable) {
newBar = gradelist.children().last(); // get the dom object not the unparsed string
newBar.resizable({
handles: "e",
containment : "parent",
start : this.startMoveClosure(),
resize : this.moveBarClosure(),
stop : this.stopDragClosure()
});
}
// prepare for next
nextWidth = cutoff['cutoff'];
removable = true; // first is not removable, all others are
draggable = true;
},
this);
// add fail which is not in data
var failBar = this.gradeCutoffTemplate({ descriptor : this.failLabel(),
width : nextWidth, removable : false});
$(failBar).find("span[contenteditable=true]").attr("contenteditable", false);
gradelist.append(failBar);
gradelist.children().last().resizable({
handles: "e",
containment : "parent",
start : this.startMoveClosure(),
resize : this.moveBarClosure(),
stop : this.stopDragClosure()
});
this.renderGradeRanges();
},
showSettingsExtras : function(event) {
$(event.currentTarget).toggleClass('active');
$(event.currentTarget).siblings.toggleClass('is-shown');
},
startMoveClosure : function() {
// set min/max widths
var cachethis = this;
var widthPerPoint = cachethis.gradeBarWidth / 100;
return function(event, ui) {
var barIndex = ui.element.index();
// min and max represent limits not labels (note, can's make smaller than 3 points wide)
var min = (barIndex < cachethis.descendingCutoffs.length ? cachethis.descendingCutoffs[barIndex]['cutoff'] + 3 : 3);
// minus 2 b/c minus 1 is the element we're effecting. It's max is just shy of the next one above it
var max = (barIndex >= 2 ? cachethis.descendingCutoffs[barIndex - 2]['cutoff'] - 3 : 97);
ui.element.resizable("option",{minWidth : min * widthPerPoint, maxWidth : max * widthPerPoint});
};
},
moveBarClosure : function() {
// 0th ele doesn't have a bar; so, will never invoke this
var cachethis = this;
return function(event, ui) {
var barIndex = ui.element.index();
// min and max represent limits not labels (note, can's make smaller than 3 points wide)
var min = (barIndex < cachethis.descendingCutoffs.length ? cachethis.descendingCutoffs[barIndex]['cutoff'] + 3 : 3);
// minus 2 b/c minus 1 is the element we're effecting. It's max is just shy of the next one above it
var max = (barIndex >= 2 ? cachethis.descendingCutoffs[barIndex - 2]['cutoff'] - 3 : 100);
var percentage = Math.min(Math.max(ui.size.width / cachethis.gradeBarWidth * 100, min), max);
cachethis.descendingCutoffs[barIndex - 1]['cutoff'] = Math.round(percentage);
cachethis.renderGradeRanges();
};
},
renderGradeRanges: function() {
// the labels showing the range e.g., 71-80
var cutoffs = this.descendingCutoffs;
this.$el.find('.range').each(function(i) {
var min = (i < cutoffs.length ? cutoffs[i]['cutoff'] : 0);
var max = (i > 0 ? cutoffs[i - 1]['cutoff'] : 100);
$(this).text(min + '-' + max);
});
},
stopDragClosure: function() {
var cachethis = this;
return function(event, ui) {
// for some reason the resize is setting height to 0
cachethis.saveCutoffs();
};
},
saveCutoffs: function() {
this.model.save('grade_cutoffs',
_.reduce(this.descendingCutoffs,
function(object, cutoff) {
object[cutoff['designation']] = cutoff['cutoff'] / 100.0;
return object;
},
{}),
{ error : CMS.ServerError});
},
addNewGrade: function(e) {
e.preventDefault();
var gradeLength = this.descendingCutoffs.length; // cutoffs doesn't include fail/f so this is only the passing grades
if(gradeLength > 3) {
// TODO shouldn't we disable the button
return;
}
var failBarWidth = this.descendingCutoffs[gradeLength - 1]['cutoff'];
// going to split the grade above the insertion point in half leaving fail in same place
var nextGradeTop = (gradeLength > 1 ? this.descendingCutoffs[gradeLength - 2]['cutoff'] : 100);
var targetWidth = failBarWidth + ((nextGradeTop - failBarWidth) / 2);
this.descendingCutoffs.push({designation: this.GRADES[gradeLength], cutoff: failBarWidth});
this.descendingCutoffs[gradeLength - 1]['cutoff'] = Math.round(targetWidth);
var $newGradeBar = this.gradeCutoffTemplate({ descriptor : this.GRADES[gradeLength],
width : targetWidth, removable : true });
var gradeDom = this.$el.find('.grades');
gradeDom.children().last().before($newGradeBar);
var newEle = gradeDom.children()[gradeLength];
$(newEle).resizable({
handles: "e",
containment : "parent",
start : this.startMoveClosure(),
resize : this.moveBarClosure(),
stop : this.stopDragClosure()
});
// Munge existing grade labels?
// If going from Pass/Fail to 3 levels, change to Pass to A
if (gradeLength === 1 && this.descendingCutoffs[0]['designation'] === 'Pass') {
this.descendingCutoffs[0]['designation'] = this.GRADES[0];
this.setTopGradeLabel();
}
this.setFailLabel();
this.renderGradeRanges();
this.saveCutoffs();
},
removeGrade: function(e) {
e.preventDefault();
var domElement = $(e.currentTarget).closest('li');
var index = domElement.index();
// copy the boundary up to the next higher grade then remove
this.descendingCutoffs[index - 1]['cutoff'] = this.descendingCutoffs[index]['cutoff'];
this.descendingCutoffs.splice(index, 1);
domElement.remove();
if (this.descendingCutoffs.length === 1 && this.descendingCutoffs[0]['designation'] === this.GRADES[0]) {
this.descendingCutoffs[0]['designation'] = 'Pass';
this.setTopGradeLabel();
}
this.setFailLabel();
this.renderGradeRanges();
this.saveCutoffs();
},
updateDesignation: function(e) {
var index = $(e.currentTarget).closest('li').index();
this.descendingCutoffs[index]['designation'] = $(e.currentTarget).html();
this.saveCutoffs();
},
failLabel: function() {
if (this.descendingCutoffs.length === 1) return 'Fail';
else return 'F';
},
setFailLabel: function() {
this.$el.find('.grades .letter-grade').last().html(this.failLabel());
},
setTopGradeLabel: function() {
this.$el.find('.grades .letter-grade').first().html(this.descendingCutoffs[0]['designation']);
}
}); });
CMS.Views.Settings.GraderView = CMS.Views.ValidatingView.extend({
// Model class is CMS.Models.Settings.CourseGrader
events : {
"blur input" : "updateModel",
"blur textarea" : "updateModel",
"click .remove-grading-data" : "deleteModel"
},
initialize : function() {
this.model.on('error', this.handleValidationError, this);
this.selectorToField = _.invert(this.fieldToSelectorMap);
this.render();
},
render: function() {
return this;
},
fieldToSelectorMap : {
'type' : 'course-grading-assignment-name',
'short_label' : 'course-grading-assignment-shortname',
'min_count' : 'course-grading-assignment-totalassignments',
'drop_count' : 'course-grading-assignment-droppable',
'weight' : 'course-grading-assignment-gradeweight'
},
updateModel : function(event) {
// HACK to fix model sometimes losing its pointer to the collection [I think I fixed this but leaving
// this in out of paranoia. If this error ever happens, the user will get a warning that they cannot
// give 2 assignments the same name.]
if (!this.model.collection) {
this.model.collection = this.collection;
}
switch (event.currentTarget.id) {
case 'course-grading-assignment-totalassignments':
this.$el.find('#course-grading-assignment-droppable').attr('max', $(event.currentTarget).val());
this.saveIfChanged(event);
break;
case 'course-grading-assignment-name':
var oldName = this.model.get('type');
if (this.saveIfChanged(event) && !_.isEmpty(oldName)) {
// overload the error display logic
this._cacheValidationErrors.push(event.currentTarget);
$(event.currentTarget).parent().append(
this.errorTemplate({message : 'For grading to work, you must change all "' + oldName +
'" subsections to "' + this.model.get('type') + '".'}));
}
break;
default:
this.saveIfChanged(event);
break;
}
},
deleteModel : function(e) {
this.model.destroy(
{ error : CMS.ServerError});
e.preventDefault();
}
});
\ No newline at end of file
if (!CMS.Views['Settings']) CMS.Views.Settings = {}; // ensure the pseudo pkg exists
CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
// Model class is CMS.Models.Settings.CourseGradingPolicy
events : {
"blur input" : "updateModel",
"blur textarea" : "updateModel",
"blur span[contenteditable=true]" : "updateDesignation",
"click .settings-extra header" : "showSettingsExtras",
"click .new-grade-button" : "addNewGrade",
"click .remove-button" : "removeGrade",
"click .add-grading-data" : "addAssignmentType",
// would love to move to a general superclass, but event hashes don't inherit in backbone :-(
'focus :input' : "inputFocus",
'blur :input' : "inputUnfocus"
},
initialize : function() {
// load template for grading view
var self = this;
this.gradeCutoffTemplate = _.template('<li class="grade-specific-bar" style="width:<%= width %>%"><span class="letter-grade" contenteditable>' +
'<%= descriptor %>' +
'</span><span class="range"></span>' +
'<% if (removable) {%><a href="#" class="remove-button">remove</a><% ;} %>' +
'</li>');
// Instrument grading scale
// convert cutoffs to inversely ordered list
var modelCutoffs = this.model.get('grade_cutoffs');
for (var cutoff in modelCutoffs) {
this.descendingCutoffs.push({designation: cutoff, cutoff: Math.round(modelCutoffs[cutoff] * 100)});
}
this.descendingCutoffs = _.sortBy(this.descendingCutoffs,
function (gradeEle) { return -gradeEle['cutoff']; });
// Instrument grace period
this.$el.find('#course-grading-graceperiod').timepicker();
// instantiates an editor template for each update in the collection
// Because this calls render, put it after everything which render may depend upon to prevent race condition.
window.templateLoader.loadRemoteTemplate("course_grade_policy",
"/static/client_templates/course_grade_policy.html",
function (raw_template) {
self.template = _.template(raw_template);
self.render();
}
);
this.model.on('error', this.handleValidationError, this);
this.model.get('graders').on('remove', this.render, this);
this.model.get('graders').on('reset', this.render, this);
this.model.get('graders').on('add', this.render, this);
this.selectorToField = _.invert(this.fieldToSelectorMap);
},
render: function() {
// prevent bootstrap race condition by event dispatch
if (!this.template) return;
// Create and render the grading type subs
var self = this;
var gradelist = this.$el.find('.course-grading-assignment-list');
// Undo the double invocation error. At some point, fix the double invocation
$(gradelist).empty();
var gradeCollection = this.model.get('graders');
gradeCollection.each(function(gradeModel) {
$(gradelist).append(self.template({model : gradeModel }));
var newEle = gradelist.children().last();
var newView = new CMS.Views.Settings.GraderView({el: newEle,
model : gradeModel, collection : gradeCollection });
});
// render the grade cutoffs
this.renderCutoffBar();
var graceEle = this.$el.find('#course-grading-graceperiod');
graceEle.timepicker({'timeFormat' : 'H:i'}); // init doesn't take setTime
if (this.model.has('grace_period')) graceEle.timepicker('setTime', this.model.gracePeriodToDate());
// remove any existing listeners to keep them from piling on b/c render gets called frequently
graceEle.off('change', this.setGracePeriod);
graceEle.on('change', this, this.setGracePeriod);
return this;
},
addAssignmentType : function(e) {
e.preventDefault();
this.model.get('graders').push({});
},
fieldToSelectorMap : {
'grace_period' : 'course-grading-graceperiod'
},
setGracePeriod : function(event) {
event.data.clearValidationErrors();
var newVal = event.data.model.dateToGracePeriod($(event.currentTarget).timepicker('getTime'));
if (event.data.model.get('grace_period') != newVal) event.data.model.save('grace_period', newVal,
{ error : CMS.ServerError});
},
updateModel : function(event) {
if (!this.selectorToField[event.currentTarget.id]) return;
switch (this.selectorToField[event.currentTarget.id]) {
case 'grace_period': // handled above
break;
default:
this.saveIfChanged(event);
break;
}
},
// Grade sliders attributes and methods
// Grade bars are li's ordered A -> F with A taking whole width, B overlaying it with its paint, ...
// The actual cutoff for each grade is the width % of the next lower grade; so, the hack here
// is to lay down a whole width bar claiming it's A and then lay down bars for each actual grade
// starting w/ A but posting the label in the preceding li and setting the label of the last to "Fail" or "F"
// A does not have a drag bar (cannot change its upper limit)
// Need to insert new bars in right place.
GRADES : ['A', 'B', 'C', 'D'], // defaults for new grade designators
descendingCutoffs : [], // array of { designation : , cutoff : }
gradeBarWidth : null, // cache of value since it won't change (more certain)
renderCutoffBar: function() {
var gradeBar =this.$el.find('.grade-bar');
this.gradeBarWidth = gradeBar.width();
var gradelist = gradeBar.children('.grades');
// HACK fixing a duplicate call issue by undoing previous call effect. Need to figure out why called 2x
gradelist.empty();
var nextWidth = 100; // first width is 100%
// Can probably be simplified to one variable now.
var removable = false;
var draggable = false; // first and last are not removable, first is not draggable
_.each(this.descendingCutoffs,
function(cutoff, index) {
var newBar = this.gradeCutoffTemplate({
descriptor : cutoff['designation'] ,
width : nextWidth,
removable : removable });
gradelist.append(newBar);
if (draggable) {
newBar = gradelist.children().last(); // get the dom object not the unparsed string
newBar.resizable({
handles: "e",
containment : "parent",
start : this.startMoveClosure(),
resize : this.moveBarClosure(),
stop : this.stopDragClosure()
});
}
// prepare for next
nextWidth = cutoff['cutoff'];
removable = true; // first is not removable, all others are
draggable = true;
},
this);
// add fail which is not in data
var failBar = this.gradeCutoffTemplate({ descriptor : this.failLabel(),
width : nextWidth, removable : false});
$(failBar).find("span[contenteditable=true]").attr("contenteditable", false);
gradelist.append(failBar);
gradelist.children().last().resizable({
handles: "e",
containment : "parent",
start : this.startMoveClosure(),
resize : this.moveBarClosure(),
stop : this.stopDragClosure()
});
this.renderGradeRanges();
},
showSettingsExtras : function(event) {
$(event.currentTarget).toggleClass('active');
$(event.currentTarget).siblings.toggleClass('is-shown');
},
startMoveClosure : function() {
// set min/max widths
var cachethis = this;
var widthPerPoint = cachethis.gradeBarWidth / 100;
return function(event, ui) {
var barIndex = ui.element.index();
// min and max represent limits not labels (note, can's make smaller than 3 points wide)
var min = (barIndex < cachethis.descendingCutoffs.length ? cachethis.descendingCutoffs[barIndex]['cutoff'] + 3 : 3);
// minus 2 b/c minus 1 is the element we're effecting. It's max is just shy of the next one above it
var max = (barIndex >= 2 ? cachethis.descendingCutoffs[barIndex - 2]['cutoff'] - 3 : 97);
ui.element.resizable("option",{minWidth : min * widthPerPoint, maxWidth : max * widthPerPoint});
};
},
moveBarClosure : function() {
// 0th ele doesn't have a bar; so, will never invoke this
var cachethis = this;
return function(event, ui) {
var barIndex = ui.element.index();
// min and max represent limits not labels (note, can's make smaller than 3 points wide)
var min = (barIndex < cachethis.descendingCutoffs.length ? cachethis.descendingCutoffs[barIndex]['cutoff'] + 3 : 3);
// minus 2 b/c minus 1 is the element we're effecting. It's max is just shy of the next one above it
var max = (barIndex >= 2 ? cachethis.descendingCutoffs[barIndex - 2]['cutoff'] - 3 : 100);
var percentage = Math.min(Math.max(ui.size.width / cachethis.gradeBarWidth * 100, min), max);
cachethis.descendingCutoffs[barIndex - 1]['cutoff'] = Math.round(percentage);
cachethis.renderGradeRanges();
};
},
renderGradeRanges: function() {
// the labels showing the range e.g., 71-80
var cutoffs = this.descendingCutoffs;
this.$el.find('.range').each(function(i) {
var min = (i < cutoffs.length ? cutoffs[i]['cutoff'] : 0);
var max = (i > 0 ? cutoffs[i - 1]['cutoff'] : 100);
$(this).text(min + '-' + max);
});
},
stopDragClosure: function() {
var cachethis = this;
return function(event, ui) {
// for some reason the resize is setting height to 0
cachethis.saveCutoffs();
};
},
saveCutoffs: function() {
this.model.save('grade_cutoffs',
_.reduce(this.descendingCutoffs,
function(object, cutoff) {
object[cutoff['designation']] = cutoff['cutoff'] / 100.0;
return object;
},
{}),
{ error : CMS.ServerError});
},
addNewGrade: function(e) {
e.preventDefault();
var gradeLength = this.descendingCutoffs.length; // cutoffs doesn't include fail/f so this is only the passing grades
if(gradeLength > 3) {
// TODO shouldn't we disable the button
return;
}
var failBarWidth = this.descendingCutoffs[gradeLength - 1]['cutoff'];
// going to split the grade above the insertion point in half leaving fail in same place
var nextGradeTop = (gradeLength > 1 ? this.descendingCutoffs[gradeLength - 2]['cutoff'] : 100);
var targetWidth = failBarWidth + ((nextGradeTop - failBarWidth) / 2);
this.descendingCutoffs.push({designation: this.GRADES[gradeLength], cutoff: failBarWidth});
this.descendingCutoffs[gradeLength - 1]['cutoff'] = Math.round(targetWidth);
var $newGradeBar = this.gradeCutoffTemplate({ descriptor : this.GRADES[gradeLength],
width : targetWidth, removable : true });
var gradeDom = this.$el.find('.grades');
gradeDom.children().last().before($newGradeBar);
var newEle = gradeDom.children()[gradeLength];
$(newEle).resizable({
handles: "e",
containment : "parent",
start : this.startMoveClosure(),
resize : this.moveBarClosure(),
stop : this.stopDragClosure()
});
// Munge existing grade labels?
// If going from Pass/Fail to 3 levels, change to Pass to A
if (gradeLength === 1 && this.descendingCutoffs[0]['designation'] === 'Pass') {
this.descendingCutoffs[0]['designation'] = this.GRADES[0];
this.setTopGradeLabel();
}
this.setFailLabel();
this.renderGradeRanges();
this.saveCutoffs();
},
removeGrade: function(e) {
e.preventDefault();
var domElement = $(e.currentTarget).closest('li');
var index = domElement.index();
// copy the boundary up to the next higher grade then remove
this.descendingCutoffs[index - 1]['cutoff'] = this.descendingCutoffs[index]['cutoff'];
this.descendingCutoffs.splice(index, 1);
domElement.remove();
if (this.descendingCutoffs.length === 1 && this.descendingCutoffs[0]['designation'] === this.GRADES[0]) {
this.descendingCutoffs[0]['designation'] = 'Pass';
this.setTopGradeLabel();
}
this.setFailLabel();
this.renderGradeRanges();
this.saveCutoffs();
},
updateDesignation: function(e) {
var index = $(e.currentTarget).closest('li').index();
this.descendingCutoffs[index]['designation'] = $(e.currentTarget).html();
this.saveCutoffs();
},
failLabel: function() {
if (this.descendingCutoffs.length === 1) return 'Fail';
else return 'F';
},
setFailLabel: function() {
this.$el.find('.grades .letter-grade').last().html(this.failLabel());
},
setTopGradeLabel: function() {
this.$el.find('.grades .letter-grade').first().html(this.descendingCutoffs[0]['designation']);
}
});
CMS.Views.Settings.GraderView = CMS.Views.ValidatingView.extend({
// Model class is CMS.Models.Settings.CourseGrader
events : {
"blur input" : "updateModel",
"blur textarea" : "updateModel",
"click .remove-grading-data" : "deleteModel",
// would love to move to a general superclass, but event hashes don't inherit in backbone :-(
'focus :input' : "inputFocus",
'blur :input' : "inputUnfocus"
},
initialize : function() {
this.model.on('error', this.handleValidationError, this);
this.selectorToField = _.invert(this.fieldToSelectorMap);
this.render();
},
render: function() {
return this;
},
fieldToSelectorMap : {
'type' : 'course-grading-assignment-name',
'short_label' : 'course-grading-assignment-shortname',
'min_count' : 'course-grading-assignment-totalassignments',
'drop_count' : 'course-grading-assignment-droppable',
'weight' : 'course-grading-assignment-gradeweight'
},
updateModel : function(event) {
// HACK to fix model sometimes losing its pointer to the collection [I think I fixed this but leaving
// this in out of paranoia. If this error ever happens, the user will get a warning that they cannot
// give 2 assignments the same name.]
if (!this.model.collection) {
this.model.collection = this.collection;
}
switch (event.currentTarget.id) {
case 'course-grading-assignment-totalassignments':
this.$el.find('#course-grading-assignment-droppable').attr('max', $(event.currentTarget).val());
this.saveIfChanged(event);
break;
case 'course-grading-assignment-name':
var oldName = this.model.get('type');
if (this.saveIfChanged(event) && !_.isEmpty(oldName)) {
// overload the error display logic
this._cacheValidationErrors.push(event.currentTarget);
$(event.currentTarget).parent().append(
this.errorTemplate({message : 'For grading to work, you must change all "' + oldName +
'" subsections to "' + this.model.get('type') + '".'}));
}
break;
default:
this.saveIfChanged(event);
break;
}
},
deleteModel : function(e) {
this.model.destroy(
{ error : CMS.ServerError});
e.preventDefault();
}
});
\ No newline at end of file
CMS.Views.ValidatingView = Backbone.View.extend({
// Intended as an abstract class which catches validation errors on the model and
// decorates the fields. Needs wiring per class, but this initialization shows how
// either have your init call this one or copy the contents
initialize : function() {
this.model.on('error', this.handleValidationError, this);
this.selectorToField = _.invert(this.fieldToSelectorMap);
},
errorTemplate : _.template('<span class="message-error"><%= message %></span>'),
events : {
"blur input" : "clearValidationErrors",
"blur textarea" : "clearValidationErrors"
},
fieldToSelectorMap : {
// Your subclass must populate this w/ all of the model keys and dom selectors
// which may be the subjects of validation errors
},
_cacheValidationErrors : [],
handleValidationError : function(model, error) {
// error triggered either by validation or server error
// error is object w/ fields and error strings
for (var field in error) {
var ele = this.$el.find('#' + this.fieldToSelectorMap[field]);
if (ele.length === 0) {
// check if it might a server error: note a typo in the field name
// or failure to put in a map may cause this to muffle validation errors
if (_.has(error, 'error') && _.has(error, 'responseText')) {
CMS.ServerError(model, error);
return;
}
else continue;
}
this._cacheValidationErrors.push(ele);
if ($(ele).is('div')) {
// put error on the contained inputs
$(ele).find('input, textarea').addClass('error');
}
else $(ele).addClass('error');
$(ele).parent().append(this.errorTemplate({message : error[field]}));
}
},
clearValidationErrors : function() {
// error is object w/ fields and error strings
while (this._cacheValidationErrors.length > 0) {
var ele = this._cacheValidationErrors.pop();
if ($(ele).is('div')) {
// put error on the contained inputs
$(ele).find('input, textarea').removeClass('error');
}
else $(ele).removeClass('error');
$(ele).nextAll('.message-error').remove();
}
},
saveIfChanged : function(event) {
// returns true if the value changed and was thus sent to server
var field = this.selectorToField[event.currentTarget.id];
var currentVal = this.model.get(field);
var newVal = $(event.currentTarget).val();
this.clearValidationErrors(); // curr = new if user reverts manually
if (currentVal != newVal) {
this.model.save(field, newVal);
return true;
}
else return false;
},
// these should perhaps go into a superclass but lack of event hash inheritance demotivates me
inputFocus : function(event) {
$("label[for='" + event.currentTarget.id + "']").addClass("is-focused");
},
inputUnfocus : function(event) {
$("label[for='" + event.currentTarget.id + "']").removeClass("is-focused");
}
});
// Studio - Sign In/Up
// ====================
body.signup, body.signin {
.wrapper-content {
margin: 0;
padding: 0 $baseline;
position: relative;
width: 100%;
}
.content {
@include clearfix();
@include font-size(16);
max-width: $fg-max-width;
min-width: $fg-min-width;
width: flex-grid(12);
margin: 0 auto;
color: $gray-d2;
header {
position: relative;
margin-bottom: $baseline;
border-bottom: 1px solid $gray-l4;
padding-bottom: ($baseline/2);
h1 {
@include font-size(32);
margin: 0;
padding: 0;
font-weight: 600;
}
.action {
@include font-size(13);
position: absolute;
right: 0;
top: 40%;
}
}
.introduction {
@include font-size(14);
margin: 0 0 $baseline 0;
}
}
.content-primary, .content-supplementary {
@include box-sizing(border-box);
float: left;
}
.content-primary {
width: flex-grid(8, 12);
margin-right: flex-gutter();
form {
@include box-sizing(border-box);
@include box-shadow(0 1px 2px $shadow-l1);
@include border-radius(2px);
width: 100%;
border: 1px solid $gray-l2;
padding: $baseline ($baseline*1.5);
background: $white;
.form-actions {
margin-top: $baseline;
.action-primary {
@include blue-button;
@include transition(all .15s);
@include font-size(15);
display:block;
width: 100%;
padding: ($baseline*0.75) ($baseline/2);
font-weight: 600;
text-transform: uppercase;
}
}
.list-input {
margin: 0;
padding: 0;
list-style: none;
.field {
margin: 0 0 ($baseline*0.75) 0;
&:last-child {
margin-bottom: 0;
}
&.required {
label {
font-weight: 600;
}
label:after {
margin-left: ($baseline/4);
content: "*";
}
}
label, input, textarea {
display: block;
}
label {
@include font-size(14);
@include transition(color, 0.15s, ease-in-out);
margin: 0 0 ($baseline/4) 0;
&.is-focused {
color: $blue;
}
}
input, textarea {
@include font-size(16);
height: 100%;
width: 100%;
padding: ($baseline/2);
&.long {
width: 100%;
}
&.short {
width: 25%;
}
::-webkit-input-placeholder {
color: $gray-l4;
}
:-moz-placeholder {
color: $gray-l3;
}
::-moz-placeholder {
color: $gray-l3;
}
:-ms-input-placeholder {
color: $gray-l3;
}
}
textarea.long {
height: ($baseline*5);
}
input[type="checkbox"] {
display: inline-block;
margin-right: ($baseline/4);
width: auto;
height: auto;
& + label {
display: inline-block;
}
}
.tip {
@include font-size(13);
display: block;
margin-top: ($baseline/4);
color: $gray-l3;
}
}
.field-group {
@include clearfix();
margin: 0 0 ($baseline/2) 0;
.field {
display: block;
width: 47%;
border-bottom: none;
margin: 0 $baseline 0 0;
padding-bottom: 0;
&:nth-child(odd) {
float: left;
}
&:nth-child(even) {
float: right;
margin-right: 0;
}
input, textarea {
width: 100%;
}
}
}
}
}
}
.content-supplementary {
width: flex-grid(4, 12);
.bit {
@include font-size(13);
margin: 0 0 $baseline 0;
border-bottom: 1px solid $gray-l4;
padding: 0 0 $baseline 0;
color: $gray-l1;
&:last-child {
margin-bottom: 0;
border: none;
padding-bottom: 0;
}
h3 {
@include font-size(14);
margin: 0 0 ($baseline/4) 0;
color: $gray-d2;
font-weight: 600;
}
}
}
}
.signup {
}
.signin {
#field-password {
position: relative;
.action-forgotpassword {
@include font-size(13);
position: absolute;
top: 0;
right: 0;
}
}
}
// ====================
// messages
.message {
@include font-size(14);
display: block;
}
.message-status {
display: none;
@include border-top-radius(2px);
@include box-sizing(border-box);
border-bottom: 2px solid $yellow-d2;
margin: 0 0 $baseline 0;
padding: ($baseline/2) $baseline;
font-weight: 500;
background: $yellow-d1;
color: $white;
.ss-icon {
position: relative;
top: 3px;
@include font-size(16);
display: inline-block;
margin-right: ($baseline/2);
}
.text {
display: inline-block;
}
&.error {
border-color: shade($red, 50%);
background: tint($red, 20%);
}
&.is-shown {
display: block;
}
}
.assets { .uploads {
input.asset-search-input { input.asset-search-input {
float: left; float: left;
width: 260px; width: 260px;
......
// ------------------------------------- // studio base styling
// // ====================
// Universal
// // basic reset
// ------------------------------------- html {
font-size: 62.5%;
}
body { body {
min-width: 980px; @include font-size(16);
background: rgb(240, 241, 245); min-width: 980px;
font-size: 16px; background: $gray-l5;
line-height: 1.6; line-height: 1.6;
color: $baseFontColor; color: $baseFontColor;
} }
body, body, input {
input { font-family: 'Open Sans', sans-serif;
font-family: 'Open Sans', sans-serif;
} }
a { a {
text-decoration: none; text-decoration: none;
color: $blue; color: $blue;
@include transition(color .15s); @include transition(color .15s);
&:hover { &:hover {
color: #cb9c40; color: #cb9c40;
} }
} }
h1 { h1 {
float: left; @include font-size(28);
font-size: 28px; font-weight: 300;
font-weight: 300;
margin: 24px 6px;
} }
.waiting { .waiting {
opacity: 0.1; opacity: 0.1;
} }
.page-actions { .page-actions {
margin-bottom: 30px; margin-bottom: 30px;
} }
.main-wrapper { .wrapper {
position: relative; @include clearfix();
margin: 0 40px; @include box-sizing(border-box);
width: 100%;
} }
.inner-wrapper { // ====================
// layout - basic
.wrapper-content {
margin: 0;
padding: 0 $baseline;
position: relative;
}
.content {
@include clearfix();
@include font-size(16);
max-width: $fg-max-width;
min-width: $fg-min-width;
width: flex-grid(12);
margin: 0 auto;
color: $gray-d2;
header {
position: relative; position: relative;
max-width: 1280px; margin-bottom: $baseline;
margin: auto; border-bottom: 1px solid $gray-l4;
padding-bottom: ($baseline/2);
.title-sub {
@include font-size(14);
display: block;
margin: 0;
color: $gray-l2;
}
> article { .title, .title-1 {
clear: both; @include font-size(32);
margin: 0;
padding: 0;
font-weight: 600;
color: $gray-d3;
} }
}
.introduction {
@include font-size(14);
margin: 0 0 $baseline 0;
}
}
.content-primary, .content-supplementary {
@include box-sizing(border-box);
}
.content-primary {
.title-1, .title-2, .title-3, .title-4, .title-5, .title-5 {
color: $gray-d3;
}
.title-1 {
}
.title-2 {
@include font-size(24);
margin: 0 0 ($baseline/2) 0;
font-weight: 600;
}
.title-3 {
@include font-size(16);
margin: 0 0 ($baseline/4) 0;
font-weight: 500;
}
.title-4 {
}
.title-5 {
}
}
.content-supplementary {
.bit {
@include font-size(13);
margin: 0 0 $baseline 0;
border-bottom: 1px solid $gray-l4;
padding: 0 0 $baseline 0;
color: $gray-l1;
&:last-child {
margin-bottom: 0;
border: none;
padding-bottom: 0;
}
h3 {
@include font-size(14);
margin: 0 0 ($baseline/4) 0;
color: $gray-d2;
font-weight: 600;
}
p {
margin: 0 0 $baseline 0;
&:last-child {
margin-bottom: 0;
}
}
}
}
// ====================
// layout - grandfathered
.main-wrapper {
position: relative;
margin: 0 40px;
}
.inner-wrapper {
position: relative;
max-width: 1280px;
margin: auto;
> article {
clear: both;
}
} }
.sidebar { .sidebar {
float: right; float: right;
width: 28%; width: 28%;
} }
.left { .left {
float: left; float: left;
} }
.right { .right {
float: right; float: right;
} }
footer { // ====================
clear: both;
height: 100px;
}
// forms
input[type="text"], input[type="text"],
input[type="email"], input[type="email"],
input[type="password"], input[type="password"],
textarea.text { textarea.text {
padding: 6px 8px 8px; padding: 6px 8px 8px;
@include box-sizing(border-box); @include box-sizing(border-box);
border: 1px solid $mediumGrey; border: 1px solid $mediumGrey;
border-radius: 2px; border-radius: 2px;
@include linear-gradient($lightGrey, tint($lightGrey, 90%)); @include linear-gradient($lightGrey, tint($lightGrey, 90%));
background-color: $lightGrey; background-color: $lightGrey;
@include box-shadow(0 1px 2px rgba(0, 0, 0, .1) inset); @include box-shadow(0 1px 2px rgba(0, 0, 0, .1) inset);
font-family: 'Open Sans', sans-serif; font-family: 'Open Sans', sans-serif;
font-size: 11px; font-size: 11px;
color: $baseFontColor; color: $baseFontColor;
outline: 0; outline: 0;
&::-webkit-input-placeholder, &::-webkit-input-placeholder,
&:-moz-placeholder, &:-moz-placeholder,
&:-ms-input-placeholder { &:-ms-input-placeholder {
color: #979faf; color: #979faf;
} }
&:focus { &:focus {
@include linear-gradient($paleYellow, tint($paleYellow, 90%)); @include linear-gradient($paleYellow, tint($paleYellow, 90%));
outline: 0; outline: 0;
} }
} }
// forms - specific
input.search { input.search {
padding: 6px 15px 8px 30px; padding: 6px 15px 8px 30px;
@include box-sizing(border-box); @include box-sizing(border-box);
border: 1px solid $darkGrey; border: 1px solid $darkGrey;
border-radius: 20px; border-radius: 20px;
background: url(../img/search-icon.png) no-repeat 8px 7px #edf1f5; background: url(../img/search-icon.png) no-repeat 8px 7px #edf1f5;
font-family: 'Open Sans', sans-serif; font-family: 'Open Sans', sans-serif;
color: $baseFontColor; color: $baseFontColor;
outline: 0; outline: 0;
&::-webkit-input-placeholder { &::-webkit-input-placeholder {
color: #979faf; color: #979faf;
} }
} }
label { label {
font-size: 12px; font-size: 12px;
} }
code { code {
padding: 0 4px; padding: 0 4px;
border-radius: 3px; border-radius: 3px;
background: #eee; background: #eee;
font-family: Monaco, monospace; font-family: Monaco, monospace;
} }
.CodeMirror { .CodeMirror {
font-size: 13px; font-size: 13px;
border: 1px solid $darkGrey; border: 1px solid $darkGrey;
background: #fff; background: #fff;
} }
.text-editor { .text-editor {
width: 100%; width: 100%;
min-height: 80px; min-height: 80px;
padding: 10px; padding: 10px;
@include box-sizing(border-box); @include box-sizing(border-box);
border: 1px solid $mediumGrey; border: 1px solid $mediumGrey;
@include linear-gradient(top, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.3)); @include linear-gradient(top, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.3));
background-color: #edf1f5; background-color: #edf1f5;
@include box-shadow(0 1px 2px rgba(0, 0, 0, 0.1) inset); @include box-shadow(0 1px 2px rgba(0, 0, 0, 0.1) inset);
font-family: Monaco, monospace; font-family: Monaco, monospace;
}
// ====================
// UI - chrome
.window {
@include clearfix();
@include border-radius(3px);
@include box-shadow(0 1px 1px $shadow-l1);
margin-bottom: $baseline;
border: 1px solid $gray-l2;
background: $white;
} }
// ====================
// UI - actions
.new-unit-item, .new-unit-item,
.new-subsection-item, .new-subsection-item,
.new-policy-item { .new-policy-item {
@include grey-button; @include grey-button;
margin: 5px 8px; margin: 5px 8px;
padding: 3px 10px 4px 10px; padding: 3px 10px 4px 10px;
font-size: 10px; font-size: 10px;
.new-folder-icon, .new-folder-icon,
.new-policy-icon, .new-policy-icon,
.new-unit-icon { .new-unit-icon {
position: relative; position: relative;
top: 2px; top: 2px;
} }
} }
.item-actions { .item-actions {
position: absolute; position: absolute;
top: 5px; top: 5px;
right: 5px; right: 5px;
.edit-button, .edit-button,
.delete-button, .delete-button,
.visibility-toggle { .visibility-toggle {
float: left; float: left;
margin-right: 13px; margin-right: 13px;
color: #a4aab7; color: #a4aab7;
} }
}
// ====================
// misc
hr.divide {
@include text-sr();
} }
.item-details { .item-details {
...@@ -189,81 +331,56 @@ code { ...@@ -189,81 +331,56 @@ code {
} }
.window { .window {
margin-bottom: 20px; // @include border-radius(3px);
border: 1px solid $mediumGrey; // @include box-shadow(0 1px 1px $shadow-l1);
border-radius: 3px; // margin-bottom: $baseline;
background: #fff; // border: 1px solid $gray-l2;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.1)); // background: $white;
.window-contents { .window-contents {
padding: 20px; padding: 20px;
} }
.header {
padding: 6px 14px;
border-bottom: 1px solid $mediumGrey;
border-radius: 2px 2px 0 0;
@include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0));
background-color: $lightBluishGrey;
@include box-shadow(0 1px 0 rgba(255, 255, 255, 0.2) inset);
font-size: 14px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.3);
}
label { .header {
display: block; padding: 6px 14px;
margin-bottom: 6px; border-bottom: 1px solid $mediumGrey;
font-weight: 700; border-radius: 2px 2px 0 0;
@include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0));
&.inline-label { background-color: $lightBluishGrey;
display: inline; @include box-shadow(0 1px 0 rgba(255, 255, 255, 0.2) inset);
} font-size: 14px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.3);
.description { }
display: block;
font-size: 11px;
font-weight: 400;
font-style: italic;
line-height: 1.3;
color: #999;
}
}
.row { label {
margin-bottom: 10px; display: block;
padding-bottom: 10px; margin-bottom: 6px;
border-bottom: 1px solid #cbd1db; font-weight: 700;
}
}
body.hide-wip { &.inline-label {
.wip, .wip-box { display: inline;
display: none !important;
} }
}
body.show-wip { .description {
.wip { display: block;
outline: 1px solid #f00 !important; font-size: 11px;
position: relative; font-weight: 400;
font-style: italic;
line-height: 1.3;
color: #999;
} }
}
.wip-box { .row {
@extend .wip; margin-bottom: 10px;
&:after { padding-bottom: 10px;
content: "WIP"; border-bottom: 1px solid #cbd1db;
font-size: 8px; }
padding: 2px;
background: #f00;
color: #fff;
@include position(absolute, 0px 0px 0 0);
}
}
} }
.waiting { // ====================
} // system notifications
.toast-notification { .toast-notification {
display: none; display: none;
position: fixed; position: fixed;
...@@ -323,50 +440,50 @@ body.show-wip { ...@@ -323,50 +440,50 @@ body.show-wip {
} }
.waiting { .waiting {
position: relative; position: relative;
&:before { &:before {
content: ''; content: '';
display: block; display: block;
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
z-index: 999998; z-index: 999998;
width: 100%; width: 100%;
height: 100%; height: 100%;
border-radius: inherit; border-radius: inherit;
background: rgba(255, 255, 255, .9); background: rgba(255, 255, 255, .9);
} }
&:after { &:after {
content: ''; content: '';
@extend .spinner-icon; @extend .spinner-icon;
display: block; display: block;
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
margin-left: -10px; margin-left: -10px;
margin-top: -10px; margin-top: -10px;
z-index: 999999; z-index: 999999;
} }
} }
.waiting-inline { .waiting-inline {
&:after { &:after {
content: ''; content: '';
@extend .spinner-icon; @extend .spinner-icon;
} }
} }
.new-button { .new-button {
@include green-button; @include green-button;
font-size: 13px; font-size: 13px;
padding: 8px 20px 10px; padding: 8px 20px 10px;
text-align: center; text-align: center;
&.big { &.big {
display: block; display: block;
} }
} }
.edit-button.standard, .edit-button.standard,
...@@ -386,9 +503,9 @@ body.show-wip { ...@@ -386,9 +503,9 @@ body.show-wip {
.delete-button.standard { .delete-button.standard {
&:hover { &:hover {
background-color: tint($orange, 75%); background-color: tint($orange, 75%);
} }
} }
.tooltip { .tooltip {
...@@ -417,4 +534,38 @@ body.show-wip { ...@@ -417,4 +534,38 @@ body.show-wip {
font-size: 20px; font-size: 20px;
color: rgba(0, 0, 0, 0.85); color: rgba(0, 0, 0, 0.85);
} }
}
// ====================
// basic utility
.sr {
@include text-sr();
}
.fake-link {
cursor: pointer;
}
.non-list {
list-style: none;
margin: 0;
padding: 0;
}
.wrap {
text-wrap: wrap;
white-space: pre-wrap;
white-space: -moz-pre-wrap;
word-wrap: break-word;
}
// ====================
// works in progress
body.hide-wip {
.wip-box {
display: none;
}
} }
\ No newline at end of file
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
} }
} }
&:hover { &:hover, &.active {
@include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset, 0 1px 1px rgba(0, 0, 0, .15)); @include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset, 0 1px 1px rgba(0, 0, 0, .15));
} }
} }
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
background-color: $blue; background-color: $blue;
color: #fff; color: #fff;
&:hover { &:hover, &.active {
background-color: #62aaf5; background-color: #62aaf5;
color: #fff; color: #fff;
} }
......
.faded-hr-divider {
@include background-image(linear-gradient(180deg, rgba(200,200,200, 0) 0%,
rgba(200,200,200, 1) 50%,
rgba(200,200,200, 0)));
height: 1px;
width: 100%;
}
.faded-hr-divider-medium {
@include background-image(linear-gradient(180deg, rgba(240,240,240, 0) 0%,
rgba(240,240,240, 1) 50%,
rgba(240,240,240, 0)));
height: 1px;
width: 100%;
}
.faded-hr-divider-light {
@include background-image(linear-gradient(180deg, rgba(255,255,255, 0) 0%,
rgba(255,255,255, 0.8) 50%,
rgba(255,255,255, 0)));
height: 1px;
width: 100%;
}
.faded-vertical-divider {
@include background-image(linear-gradient(90deg, rgba(200,200,200, 0) 0%,
rgba(200,200,200, 1) 50%,
rgba(200,200,200, 0)));
height: 100%;
width: 1px;
}
.faded-vertical-divider-light {
@include background-image(linear-gradient(90deg, rgba(255,255,255, 0) 0%,
rgba(255,255,255, 0.6) 50%,
rgba(255,255,255, 0)));
height: 100%;
width: 1px;
}
.vertical-divider {
@extend .faded-vertical-divider;
position: relative;
&::after {
@extend .faded-vertical-divider-light;
content: "";
display: block;
position: absolute;
left: 1px;
}
}
.horizontal-divider {
border: none;
@extend .faded-hr-divider;
position: relative;
&::after {
@extend .faded-hr-divider-light;
content: "";
display: block;
position: absolute;
top: 1px;
}
}
.fade-right-hr-divider {
@include background-image(linear-gradient(180deg, rgba(200,200,200, 0) 0%,
rgba(200,200,200, 1)));
border: none;
}
.fade-left-hr-divider {
@include background-image(linear-gradient(180deg, rgba(200,200,200, 1) 0%,
rgba(200,200,200, 0)));
border: none;
}
\ No newline at end of file
//studio global footer
.wrapper-footer {
margin: ($baseline*1.5) 0 $baseline 0;
padding: $baseline;
position: relative;
width: 100%;
footer.primary {
@include clearfix();
@include font-size(13);
max-width: $fg-max-width;
min-width: $fg-min-width;
width: flex-grid(12);
margin: 0 auto;
padding-top: $baseline;
border-top: 1px solid $gray-l4;
color: $gray-l2;
.colophon {
width: flex-grid(4, 12);
float: left;
margin-right: flex-gutter(2);
}
.nav-peripheral {
width: flex-grid(6, 12);
float: right;
text-align: right;
.nav-item {
display: inline-block;
margin-right: ($baseline/2);
&:last-child {
margin-right: 0;
}
}
}
a {
color: $gray-l1;
&:hover, &:active {
color: $blue;
}
}
}
}
\ No newline at end of file
body.no-header { // studio global header and navigation
.primary-header { // ====================
display: none;
} .wrapper-header {
margin: 0 0 ($baseline*1.5) 0;
padding: $baseline;
border-bottom: 1px solid $gray;
@include box-shadow(0 1px 5px 0 rgba(0,0,0, 0.1));
background: $white;
height: 76px;
position: relative;
width: 100%;
z-index: 10;
a {
color: $baseFontColor;
display: block;
&:hover, &:active {
color: $blue;
}
}
header.primary {
@include clearfix();
max-width: $fg-max-width;
min-width: $fg-min-width;
width: flex-grid(12);
margin: 0 auto;
color: $gray-l1;
}
nav .nav-item {
display: inline-block;
}
}
// ====================
// basic layout
.wrapper-left, .wrapper-right {
@include box-sizing(border-box);
} }
@mixin active { .wrapper-left {
@include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0)); width: flex-grid(10, 12);
background-color: rgba(255, 255, 255, .3); float: left;
@include box-shadow(0 -1px 0 rgba(0, 0, 0, .2) inset, 0 1px 0 #fff inset); margin-right: flex-gutter();
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
} }
.primary-header { .wrapper-right {
width: 100%; width: flex-grid(2, 12);
margin-bottom: 30px; float: right;
}
&.active-tab-courseware #courseware-tab {
@include active; // general nav styles
} // ====================
&.active-tab-assets #assets-tab { // ====================
@include active;
} // specific elements - branding
.branding, .info-course, .nav-course, .nav-account, .nav-unauth, .nav-pitch {
&.active-tab-pages #pages-tab { display: inline-block;
@include active; vertical-align: top;
} }
&.active-tab-users #users-tab { .branding {
@include active; position: relative;
} margin: 0 ($baseline*0.75) 0 0;
padding-right: ($baseline*0.75);
&.active-tab-settings #settings-tab {
@include active; a {
} @include text-hide();
display: block;
&.active-tab-import #import-tab { width: 164px;
@include active; height: 32px;
} background: transparent url('../img/logo-edx-studio.png') 0 0 no-repeat;
}
&.active-tab-export #export-tab { }
@include active;
} // ====================
.drop-icon { // specific elements - course name/info
margin-left: 5px; .info-course {
font-size: 11px; @include font-size(14);
} position: relative;
margin: -3px ($baseline/2) 0 0;
.settings-icon { padding-right: ($baseline*0.75);
font-size: 18px;
line-height: 18px; &:before {
} @extend .faded-vertical-divider;
content: "";
.class-nav-bar { display: block;
clear: both; height: 50px;
@include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0)); position: absolute;
background-color: $lightBluishGrey; right: 1px;
@include box-shadow(0 1px 0 rgba(255, 255, 255, 0.2) inset, 0 -1px 0 rgba(0, 0, 0, 0.2) inset); top: -8px;
} width: 1px;
}
.class-nav {
@include clearfix; &:after {
@extend .faded-vertical-divider-light;
a { content: "";
float: left; display: block;
display: inline-block; height: 50px;
padding: 15px 25px 17px; position: absolute;
font-size: 15px; right: 0px;
color: #3c3c3c; top: -12px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.3); width: 1px;
}
&:hover {
@include linear-gradient(top, rgba(255, 255, 255, .2), rgba(255, 255, 255, 0)); .course-org {
} margin-right: ($baseline/4);
} }
li { .course-number, .course-org {
float: left; @include font-size(12);
} display: inline-block;
} }
.class { .course-title {
@include clearfix; display: block;
height: 100%; width: 100%;
font-size: 12px; max-width: 220px;
color: rgb(163, 171, 184); overflow: hidden;
@include linear-gradient(top, rgba(255, 255, 255, 0), rgba(255, 255, 255, .1)); white-space: nowrap;
background-color: rgb(47, 53, 63); text-overflow: ellipsis;
@include font-size(16);
a { font-weight: 600;
display: inline-block; }
height: 20px; }
padding: 5px 10px 6px;
color: rgb(163, 171, 184); // ====================
}
// specific elements - course nav
.home { .nav-course {
position: relative; width: 335px;
top: 1px; margin-top: -($baseline/4);
} @include font-size(14);
.log-out { > ol > .nav-item {
position: relative; vertical-align: bottom;
top: 3px; margin: 0 ($baseline/2) 0 0;
}
} &:last-child {
margin-right: 0;
}
.title {
display: block;
padding: 5px;
text-transform: uppercase;
font-weight: 600;
color: $gray-d3;
&:hover, &:active {
color: $blue;
}
.label-prefix {
display: block;
@include font-size(11);
font-weight: 400;
}
&.is-selected {
color: $blue-d1;
.icon-expand {
color: $blue-d1;
}
}
}
// specific nav items
&.nav-course-courseware {
}
&.nav-course-settings {
}
&.nav-course-tools {
}
}
}
// ====================
// specific elements - account-based nav
.nav-account {
width: 100%;
margin-top: ($baseline*0.75);
@include font-size(14);
text-align: right;
.nav-account-username {
width: 100%;
.icon-user {
display: inline-block;
vertical-align: middle;
margin-right: 3px;
@include font-size(12);
}
.account-username {
display: inline-block;
width: 80%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
// ====================
// UI - dropdown
.nav-dropdown {
.nav-item {
position: relative;
.icon-expand {
@include font-size(14);
@include transition (color 0.5s ease-in-out, opacity 0.5s ease-in-out);
margin-left: 2px;
opacity: 0.5;
color: $gray-l2;
}
&:hover {
.icon-expand {
color: $blue;
opacity: 1.0;
}
}
}
.wrapper-nav-sub {
position: absolute;
left: -7px;
top: 47px;
width: 140px;
opacity: 0;
pointer-events: none;
}
.nav-sub {
@include border-radius(2px);
@include box-sizing(border-box);
@include box-shadow(0 1px 5px 0 rgba(0,0,0, 0.1));
position: relative;
width: 100%;
border: 1px solid $gray-l2;
padding: ($baseline/4) ($baseline/2);
background: $white;
&:after, &:before {
bottom: 100%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}
&:after {
border-color: rgba(255, 255, 255, 0);
border-bottom-color: #fff;
border-width: 5px;
right: 3px;
margin-left: -5px;
}
&:before {
border-color: rgba(178, 178, 178, 0);
border-bottom-color: $gray-l2;
border-width: 6px;
right: 3px;
margin-left: -6px;
}
.nav-item {
display: block;
margin: 0 0 ($baseline/4) 0;
border-bottom: 1px solid $gray-l5;
padding: 0 0($baseline/4) 0;
@include font-size(13);
&:last-child {
margin-bottom: 0;
border-bottom: none;
padding-bottom: 0;
}
a {
display: block;
}
}
}
// UI - dropdown - specific navs
&.nav-account {
.wrapper-nav-sub {
top: 27px;
left: auto;
right: -($baseline/2);
width: 110px;
}
.nav-sub {
text-align: left;
.icon-expand {
top: -2px;
}
}
.nav-sub:after {
left: auto;
right: 11px;
}
.nav-sub:before {
left: auto;
right: 10px;
}
}
&.nav-course {
.nav-course-courseware {
.nav-sub:after {
left: 88px;
}
.nav-sub:before {
left: 88px;
}
}
.nav-course-settings {
.nav-sub:after {
left: 88px;
}
.nav-sub:before {
left: 88px;
}
}
.nav-course-tools {
.wrapper-nav-sub {
top: ($baseline*1.5);
width: 100px;
}
.nav-sub:after {
left: 68px;
}
.nav-sub:before {
left: 68px;
}
}
}
}
// ====================
// STATE: is-signed in
.is-signedin {
&.course .branding {
&:before {
@extend .faded-vertical-divider;
content: "";
display: block;
height: 50px;
position: absolute;
right: 1px;
top: -8px;
width: 1px;
}
&:after {
@extend .faded-vertical-divider-light;
content: "";
display: block;
height: 50px;
position: absolute;
right: 0px;
top: -12px;
width: 1px;
}
}
}
// ====================
// STATE: not signed in
.not-signedin {
.wrapper-left {
width: flex-grid(4, 12);
}
.wrapper-right {
width: flex-grid(8, 12);
}
// STATE: not signed in - unauthenticated nav
.nav-not-signedin {
float: right;
margin-top: ($baseline/4);
.nav-item {
@include font-size(16);
vertical-align: middle;
margin: 0 $baseline 0 0;
&:last-child {
margin-right: 0;
}
.action {
margin-top: -($baseline/4);
display: inline-block;
padding: ($baseline/4) ($baseline/2);
}
}
// STATE: not signed in - specific items
.nav-not-signedin-help {
}
.nav-not-signedin-signup {
margin-right: ($baseline/2);
.action-signup {
@include blue-button;
@include transition(all .15s);
@include font-size(14);
padding: ($baseline/4) ($baseline/2);
text-transform: uppercase;
font-weight: 600;
}
}
.nav-not-signedin-signin {
.action-signin {
@include white-button;
@include transition(all .15s);
@include font-size(14);
padding: ($baseline/4) ($baseline/2);
text-transform: uppercase;
font-weight: 600;
}
}
}
}
// ====================
// STATE: active/current nav states
.nav-item.is-current,
body.howitworks .nav-not-signedin-hiw,
// dashboard
body.dashboard .nav-account-dashboard,
// course content
body.course.outline .nav-course-courseware .title,
body.course.updates .nav-course-courseware .title,
body.course.pages .nav-course-courseware .title,
body.course.uploads .nav-course-courseware .title,
body.course.outline .nav-course-courseware-outline,
body.course.updates .nav-course-courseware-updates,
body.course.pages .nav-course-courseware-pages,
body.course.uploads .nav-course-courseware-uploads,
// course settings
body.course.schedule .nav-course-settings .title,
body.course.grading .nav-course-settings .title,
body.course.team .nav-course-settings .title,
body.course.advanced .nav-course-settings .title,
body.course.schedule .nav-course-settings-schedule,
body.course.grading .nav-course-settings-grading,
body.course.team .nav-course-settings-team,
body.course.advanced .nav-course-settings-advanced,
// course tools
body.course.import .nav-course-tools .title,
body.course.export .nav-course-tools .title,
body.course.import .nav-course-tools-import,
body.course.export .nav-course-tools-export,
{
color: $blue;
a {
color: $blue;
}
}
body.signup .nav-not-signedin-signin {
a {
background-color: #d9e3ee;
color: #6d788b;
}
}
body.signin .nav-not-signedin-signup {
a {
background-color: #62aaf5;
color: #fff;
}
}
// ====================
// STATE: js enabled
.js {
.nav-dropdown {
.nav-item .title {
cursor: pointer;
}
}
.wrapper-nav-sub {
@include transition (opacity 1.0s ease-in-out 0s);
opacity: 0;
pointer-events: none;
&.is-shown {
opacity: 1.0;
pointer-events: auto;
}
}
} }
\ No newline at end of file
body.index { // how it works/not signed in index
> header { .index {
display: none;
}
> h1 { &.not-signedin {
font-weight: 300;
color: lighten($dark-blue, 40%); .wrapper-header {
text-shadow: 0 1px 0 #fff; margin-bottom: 0;
-webkit-font-smoothing: antialiased; }
max-width: 600px;
text-align: center;
margin: 80px auto 30px;
}
section.main-container { .wrapper-footer {
border-right: 3px; margin: 0;
background: #FFF; border-top: 2px solid $gray-l3;
max-width: 600px;
margin: 0 auto; footer.primary {
display: block; border: none;
@include box-sizing(border-box); margin-top: 0;
border: 1px solid lighten( $dark-blue , 30% ); padding-top: 0;
@include border-radius(3px); }
overflow: hidden; }
@include bounce-in-animation(.8s);
.wrapper-content-header, .wrapper-content-features, .wrapper-content-cta {
header { @include box-sizing(border-box);
border-bottom: 1px solid lighten($dark-blue, 50%); margin: 0;
@include linear-gradient(#fff, lighten($dark-blue, 62%)); padding: 0 $baseline;
position: relative;
width: 100%;
}
.content {
@include clearfix(); @include clearfix();
@include box-shadow( 0 2px 0 $light-blue, inset 0 -1px 0 #fff); @include font-size(16);
text-shadow: 0 1px 0 #fff; max-width: $fg-max-width;
min-width: $fg-min-width;
width: flex-grid(12);
margin: 0 auto;
color: $gray-d2;
header {
border: none;
padding-bottom: 0;
margin-bottom: 0;
}
h1, h2, h3, h4, h5, h6 {
color: $gray-d3;
}
h2 {
}
h3 {
}
h4 {
}
}
// welcome content
.wrapper-content-header {
@include linear-gradient($blue-l1,$blue,$blue-d1);
padding-bottom: ($baseline*4);
padding-top: ($baseline*4);
}
.content-header {
position: relative;
text-align: center;
color: $white;
h1 { h1 {
font-size: 14px; @include font-size(52);
padding: 8px 20px; float: none;
float: left; margin: 0 0 ($baseline/2) 0;
color: $dark-blue; border-bottom: 1px solid $blue-l1;
margin: 0; padding: 0;
font-weight: 500;
color: $white;
} }
a { .logo {
float: right; @include text-hide();
padding: 8px 20px; position: relative;
border-left: 1px solid lighten($dark-blue, 50%); top: 3px;
@include box-shadow( inset -1px 0 0 #fff); display: inline-block;
font-weight: bold; vertical-align: baseline;
font-size: 22px; width: 282px;
line-height: 1; height: 57px;
color: $dark-blue; background: transparent url('../img/logo-edx-studio-white.png') 0 0 no-repeat;
}
.tagline {
@include font-size(24);
margin: 0;
color: $blue-l3;
} }
} }
ol { .arrow_box {
list-style: none; position: relative;
margin: 0; background: #fff;
padding: 0; border: 4px solid #000;
}
.arrow_box:after, .arrow_box:before {
top: 100%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}
.arrow_box:after {
border-color: rgba(255, 255, 255, 0);
border-top-color: #fff;
border-width: 30px;
left: 50%;
margin-left: -30px;
}
.arrow_box:before {
border-color: rgba(0, 0, 0, 0);
border-top-color: #000;
border-width: 36px;
left: 50%;
margin-left: -36px;
}
li { // feature content
border-bottom: 1px solid lighten($dark-blue, 50%); .wrapper-content-features {
@include box-shadow(0 -1px ($baseline/4) $shadow);
padding-bottom: ($baseline*2);
padding-top: ($baseline*3);
background: $white;
}
a { .content-features {
display: block;
padding: 10px 20px; .list-features {
}
&:hover { // indiv features
color: $dark-blue; .feature {
background: lighten($yellow, 10%); @include clearfix();
text-shadow: 0 1px 0 #fff; margin: 0 0 ($baseline*2) 0;
border-bottom: 1px solid $gray-l4;
padding: 0 0 ($baseline*2) 0;
.img {
@include box-sizing(border-box);
float: left;
width: flex-grid(3, 12);
margin-right: flex-gutter();
a {
@include box-sizing(border-box);
@include box-shadow(0 1px ($baseline/10) $shadow-l1);
position: relative;
top: 0;
display: block;
overflow: hidden;
border: 1px solid $gray-l3;
padding: ($baseline/4);
background: $white;
.action-zoom {
@include transition(bottom .50s ease-in-out);
position: absolute;
bottom: -30px;
right: ($baseline/2);
opacity: 0;
.ss-icon {
@include font-size(18);
@include border-top-radius(3px);
display: inline-block;
padding: ($baseline/4) ($baseline/2);
background: $blue;
color: $white;
text-align: center;
}
}
&:hover {
border-color: $blue;
.action-zoom {
opacity: 1.0;
bottom: -2px;
}
}
}
img {
display: block;
width: 100%;
height: 100%;
} }
} }
.copy {
float: left;
width: flex-grid(9, 12);
margin-top: -($baseline/4);
h3 {
margin: 0 0 ($baseline/2) 0;
@include font-size(24);
font-weight: 600;
}
> p {
@include font-size(18);
color: $gray-d1;
}
strong {
color: $pink-l1;
font-weight: 500;
}
.list-proofpoints {
@include clearfix();
@include font-size(14);
width: flex-grid(9, 9);
margin: ($baseline*1.5) 0 0 0;
.proofpoint {
@include box-sizing(border-box);
@include border-radius(($baseline/4));
@include transition(color .50s ease-in-out);
position: relative;
top: 0;
float: left;
width: flex-grid(3, 9);
min-height: ($baseline*8);
margin-right: flex-gutter();
padding: ($baseline*0.75) $baseline;
color: $gray-l1;
cursor: pointer;
.title {
@include font-size(16);
margin: 0 0 ($baseline/4) 0;
font-weight: 500;
color: $gray-d3;
}
&:hover {
@include box-shadow(0 1px ($baseline/10) $shadow-l1);
background: $blue-l5;
top: -($baseline/5);
.title, strong {
color: $blue;
}
}
&:last-child {
margin-right: 0;
}
}
}
}
&:last-child { &:last-child {
border-bottom: none; margin-bottom: 0;
border: none;
padding-bottom: 0;
}
&:nth-child(even) {
.img {
float: right;
margin-right: 0;
margin-left: flex-gutter();
}
.copy {
float: right;
text-align: right;
}
.list-proofpoints {
.proofpoint {
float: right;
width: flex-grid(3, 9);
margin-left: flex-gutter();
margin-right: 0;
&:last-child {
margin-left: 0;
}
}
}
}
}
}
// call to action content
.wrapper-content-cta {
padding-bottom: ($baseline*2);
padding-top: ($baseline*2);
background: $white;
}
.content-cta {
border-top: 1px solid $gray-l4;
header {
border: none;
margin: 0;
padding: 0;
}
.list-actions {
position: relative;
margin-top: -($baseline*1.5);
li {
width: flex-grid(6, 12);
margin: 0 auto;
}
.action {
display: block;
width: 100%;
text-align: center;
}
.action-primary {
@include blue-button;
@include transition(all .15s);
@include font-size(18);
padding: ($baseline*0.75) ($baseline/2);
font-weight: 600;
text-align: center;
text-transform: uppercase;
}
.action-secondary {
@include font-size(14);
margin-top: ($baseline/2);
}
}
}
}
// js dependant
&.js {
.content-modal {
@include border-bottom-radius(2px);
@include box-sizing(border-box);
@include box-shadow(0 2px 4px $shadow-d1);
position: relative;
display: none;
width: 700px;
overflow: hidden;
border: 1px solid $gray-d1;
padding: ($baseline);
background: $white;
.action-modal-close {
@include transition(top .25s ease-in-out);
@include border-bottom-radius(3px);
position: absolute;
top: -3px;
right: $baseline;
padding: ($baseline/4) ($baseline/2) 0 ($baseline/2);
background: $gray-l3;
text-align: center;
.label {
@include text-sr();
}
.ss-icon {
@include font-size(18);
color: $white;
}
&:hover {
background: $blue;
top: 0;
} }
} }
img {
@include box-sizing(border-box);
width: 100%;
overflow-y: scroll;
padding: ($baseline/10);
border: 1px solid $gray-l4;
}
.title {
@include font-size(18);
margin: 0 0 ($baseline/2) 0;
font-weight: 600;
color: $gray-d3;
}
.description {
@include font-size(13);
margin-top: ($baseline/2);
color: $gray-l1;
}
} }
} }
} }
...@@ -54,4 +54,16 @@ ...@@ -54,4 +54,16 @@
@include white-button; @include white-button;
margin-top: 13px; margin-top: 13px;
} }
}
// lean modal alternative
#lean_overlay {
position: fixed;
z-index: 10000;
top: 0px;
left: 0px;
display: none;
height: 100%;
width: 100%;
background: $black;
} }
\ No newline at end of file
.settings { // Studio - Course Settings
.settings-overview { // ====================
@extend .window; body.course.settings {
@include clearfix;
display: table;
width: 100%;
// layout
.sidebar {
display: table-cell;
float: none;
width: 20%;
padding: 30px 0 30px 20px;
@include border-radius(3px 0 0 3px);
background: $lightGrey;
}
.main-column { .content-primary, .content-supplementary {
display: table-cell; @include box-sizing(border-box);
float: none; float: left;
width: 80%; }
padding: 30px 40px 30px 60px;
}
.settings-page-menu { .content-primary {
a { @extend .window;
display: block; width: flex-grid(9, 12);
padding-left: 20px; margin-right: flex-gutter();
line-height: 52px; padding: $baseline ($baseline*1.5);
}
&.is-shown { .group-settings {
background: #fff; margin: 0 0 ($baseline*2) 0;
@include border-radius(5px 0 0 5px);
}
}
}
.settings-page-section { header {
> .alert { @include clearfix();
display: none;
&.is-shown { .title-2 {
display: block; width: flex-grid(4, 9);
} margin: 0 flex-gutter() 0 0;
float: left;
} }
> section { .tip {
display: none; @include font-size(13);
margin-bottom: 40px; width: flex-grid(5, 9);
float: right;
&.is-shown { margin-top: ($baseline/2);
display: block; text-align: right;
} color: $gray-l2;
&:last-child {
border-bottom: none;
}
> .title {
margin-bottom: 30px;
font-size: 28px;
font-weight: 300;
color: $blue;
}
> section {
margin-bottom: 100px;
@include clearfix;
header {
@include clearfix;
border-bottom: 1px solid $mediumGrey;
margin-bottom: 20px;
padding-bottom: 10px;
h3 {
color: $darkGrey;
float: left;
margin: 0 40px 0 0;
text-transform: uppercase;
}
.detail {
float: right;
margin-top: 3px;
color: $mediumGrey;
font-size: 13px;
}
}
&:last-child {
padding-bottom: 0;
border-bottom: none;
}
}
} }
} }
// form basics // basic layout/elements
label, .label { .title-2 {
padding: 0;
border: none;
background: none;
font-size: 15px;
font-weight: 400;
&.check-label {
display: inline;
margin-left: 10px;
}
&.ranges {
margin-bottom: 20px;
}
} }
input, textarea { .title-3 {
@include transition(all 1s ease-in-out);
@include box-sizing(border-box);
font-size: 15px;
&.long {
width: 100%;
min-width: 400px;
}
&.tall {
height: 200px;
}
&.short {
min-width: 100px;
width: 25%;
}
&.date {
display: block !important;
}
&.time {
width: 85px !important;
min-width: 85px !important;
}
&:disabled {
border: none;
@include box-shadow(none);
padding: 0;
color: $darkGrey !important;
font-weight: bold;
background: #fff;
}
} }
textarea.tinymce { // UI hints/tips/messages
border: 1px solid $darkGrey; .tip {
height: 300px; @include font-size(13);
} display: block;
margin-top: ($baseline/4);
input[type="checkbox"], input[type="radio"] { color: $gray-l3;
}
input:disabled + .copy > label, input:disabled + .label {
color: $mediumGrey;
} }
.message-error {
.input-default input, .input-default textarea { @include font-size(13);
color: $mediumGrey; display: block;
background: $lightGrey; margin-top: ($baseline/4);
margin-bottom: ($baseline/2);
color: $red;
} }
::-webkit-input-placeholder { // buttons
color: $mediumGrey; .remove-item {
font-size: 13px; @include white-button;
} @include font-size(13);
:-moz-placeholder { font-weight: 400;
color: $mediumGrey;
font-size: 13px;
} }
.tip { .new-button {
color: $mediumGrey; @include font-size(13);
font-size: 13px;
} }
// form basics
.list-input {
margin: 0;
padding: 0;
list-style: none;
// form layouts
.row {
margin-bottom: 30px;
padding-bottom: 30px;
border-bottom: 1px solid $lightGrey;
&:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
// structural labels, not semantic labels per se
> label, .label {
display: inline-block;
vertical-align: top;
}
// tips
.tip-inline {
display: inline-block;
margin-left: 10px;
}
.tip-stacked {
display: block;
margin-top: 10px;
}
// structural field, not semantic fields per se
.field { .field {
display: inline-block; margin: 0 0 $baseline 0;
width: 100%;
> input, > textarea, .input { &:last-child {
display: inline-block; margin-bottom: 0;
}
&:last-child {
margin-bottom: 0;
}
.group { &.required {
input, textarea {
margin-bottom: 5px;
}
.label, label { label {
font-size: 13px; font-weight: 600;
}
} }
// multi-field label:after {
&.multi { margin-left: ($baseline/4);
display: block; content: "*";
background: tint($lightGrey, 50%); }
padding: 20px; }
@include border-radius(4px);
@include box-sizing(border-box);
.group { label, input, textarea {
margin-bottom: 10px; display: block;
max-width: 175px; }
&:last-child { label {
margin-bottom: 0; @include font-size(14);
} @include transition(color, 0.15s, ease-in-out);
margin: 0 0 ($baseline/4) 0;
font-weight: 400;
input, .input, textarea { &.is-focused {
color: $blue;
}
}
} input, textarea {
@include placeholder($gray-l4);
@include font-size(16);
@include size(100%,100%);
padding: ($baseline/2);
.tip-stacked { &.long {
margin-top: 0;
}
}
} }
// multi stacked &.short {
&.multi-stacked {
.group {
input, .input, textarea {
min-width: 370px;
width: 370px;
}
}
} }
// multi-field inline &.error {
&.multi-inline { border-color: $red;
@include clearfix;
.group {
float: left;
margin-right: 20px;
&:nth-child(2) {
margin-right: 0;
}
.input, input, textarea {
width: 100%;
}
}
} }
} }
// input-list textarea.long {
.input-list { height: ($baseline*5);
}
.input {
margin-bottom: 15px;
padding-bottom: 15px;
border-bottom: 1px dotted $lightGrey;
@include clearfix();
&:last-child { input[type="checkbox"] {
border: 0; display: inline-block;
} margin-right: ($baseline/4);
width: auto;
height: auto;
.row { & + label {
} display: inline-block;
} }
} }
}
//radio buttons and checkboxes .field-group {
.input-radio { @include clearfix();
@include clearfix(); margin: 0 0 ($baseline/2) 0;
}
input {
display: block;
float: left;
margin-right: 10px;
}
.copy { // enumerated/grouped lists
position: relative; &.enum {
top: -5px;
float: left;
width: 350px;
}
label { .field-group {
display: block; @include box-sizing(border-box);
margin-bottom: 0; @include border-radius(3px);
} background: $gray-l5;
padding: ($baseline/2);
.tip { &:last-child {
display: block; padding-bottom: ($baseline/2);
margin-top: 0;
} }
.message-error { .actions {
@include clearfix();
margin-top: ($baseline/2);
border-top: 1px solid $gray-l4;
padding-top: ($baseline/2);
.remove-item {
float: right;
}
} }
} }
.input-checkbox {
}
// enumerated inputs
&.enum {
}
} }
}
// layout - aligned label/field pairs
&.row-col2 {
> label, .label { // existing inputs
width: 200px; .input-existing {
} margin: 0 0 $baseline 0;
.field { .actions {
width: 400px ! important; margin: ($baseline/4) 0 0 0;
} }
}
&.multi-inline { // not editable fields
@include clearfix(); .field.is-not-editable {
.group { label, .label {
width: 170px; color: $gray-l3;
}
}
} }
.field-additional { input {
margin-left: 204px; opacity: 0.25;
} }
} }
// editing controls - adding // field with error
.new-item, .replace-item { .field.error {
clear: both;
display: block;
margin-top: 10px;
padding-bottom: 10px;
@include grey-button;
@include box-sizing(border-box);
}
// editing controls - removing input, textarea {
.delete-button { border-color: $red;
float: right; }
} }
// specific fields - basic
&.basic {
// editing controls - preview .list-input {
.input-existing {
display: block !important;
.current {
width: 100%;
margin: 10px 0;
padding: 10px;
@include box-sizing(border-box);
@include border-radius(5px);
font-size: 14px;
background: tint($lightGrey, 50%);
@include clearfix(); @include clearfix();
.doc-filename {
display: inline-block;
width: 220px;
overflow: hidden;
text-overflow: ellipsis;
}
.remove-doc-data { .field {
display: inline-block; margin-bottom: 0;
margin-top: 0;
width: 150px;
} }
} }
}
// specific sections #field-course-organization {
.settings-details { float: left;
width: flex-grid(2, 9);
margin-right: flex-gutter();
}
} #field-course-number {
float: left;
width: flex-grid(2, 9);
margin-right: flex-gutter();
}
.settings-faculty { #field-course-name {
float: left;
width: flex-grid(5, 9);
}
}
.settings-faculty-members { // specific fields - schedule
&.schedule {
> header { .list-input {
display: none; margin-bottom: ($baseline*1.5);
}
.field .multi { &:last-child {
display: block; margin-bottom: 0;
margin-bottom: 40px;
padding: 20px;
background: tint($lightGrey, 50%);
@include border-radius(4px);
@include box-sizing(border-box);
} }
}
.course-faculty-list-item { .field-group {
@include clearfix();
.row { border-bottom: 1px solid $gray-l5;
padding-bottom: ($baseline/2);
&:nth-child(4) {
padding-bottom: 0;
border-bottom: none;
}
}
.remove-faculty-photo {
display: inline-block;
}
}
#course-faculty-bio-input { &:last-child {
margin-bottom: 0; border: none;
padding-bottom: 0;
} }
.new-course-faculty-item { .field {
float: left;
width: flex-grid(3, 9);
margin-bottom: ($baseline/4);
margin-right: flex-gutter();
} }
.current-faculty-photo { .field.time {
padding: 0; position: relative;
img { .tip {
display: block; position: absolute;
@include box-shadow(0 1px 3px rgba(0,0,0,0.1)); top: 0;
padding: 10px; right: 0;
border: 2px solid $mediumGrey;
background: #fff;
} }
} }
} }
} }
// specific fields - overview
#field-course-overview {
.settings-grading { #course-overview {
height: ($baseline*20);
.setting-grading-assignment-types {
.row .field.enum {
width: 684px;
}
} }
}
.course-grading-assignment-list-item { // specific fields - video
#field-course-introduction-video {
} .input-existing {
@include box-sizing(border-box);
@include border-radius(3px);
background: $gray-l5;
padding: ($baseline/2);
.input-list { .actions {
.row { @include clearfix();
margin-top: ($baseline/2);
border-top: 1px solid $gray-l4;
padding-top: ($baseline/2);
.input { .remove-item {
&:last-child { float: right;
margin-bottom: 0;
padding-bottom: 0;
}
} }
} }
} }
}
.settings-handouts {
.actions {
margin-top: ($baseline/2);
border-top: 1px solid $gray-l5;
padding-top: ($baseline/2);
}
} }
.settings-problems { // specific fields - requirements
&.requirements {
> section {
&.is-shown { #field-course-effort {
display: block; width: flex-grid(3, 9);
}
} }
} }
.settings-discussions { // specific fields - grading range (artifact styling)
&.grade-range {
.course-discussions-categories-list-item { margin-bottom: ($baseline*3);
label {
display: none;
}
.group { .grade-controls {
display: inline-block; @include clearfix;
} width: flex-grid(9,9);
}
.remove-item { .new-grade-button {
display: inline-block !important; @include box-sizing(border-box);
margin-left: 10px; @include linear-gradient(top, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0));
@include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset);
width: flex-grid(1,9);
height: ($baseline*2);
position: relative;
display: inline-block;
vertical-align: middle;
margin-right: flex-gutter();
border-radius: 20px;
border: 1px solid $darkGrey;
background-color: #d1dae3;
color: #6d788b;
.plus-icon {
position: absolute;
top: 50%;
left: 50%;
margin-left: -6px;
margin-top: -6px;
} }
} }
.grade-slider {
@include box-sizing(border-box);
width: flex-grid(8,9);
display: inline-block;
vertical-align: middle;
} .grade-bar {
position: relative;
// states width: 100%;
label.is-focused { height: ($baseline*2.5);
color: $blue; background: $lightGrey;
@include transition(color 1s ease-in-out);
}
// extras/abbreviations
// .settings-extras {
// > header {
// cursor: pointer;
// &.active {
// } .increments {
// } position: relative;
// > div {
// display: none;
// @include transition(display 0.25s ease-in-out);
// &.is-shown { li {
// display: block; position: absolute;
// } top: 52px;
// } width: 30px;
// } margin-left: -15px;
font-size: 9px;
text-align: center;
input.error, textarea.error { &.increment-0 {
border-color: $red; left: 0;
} }
.message-error { &.increment-10 {
display: block; left: 10%;
margin-top: 5px; }
color: $red;
font-size: 13px;
}
// misc &.increment-20 {
.divide { left: 20%;
display: none; }
}
i.ss-icon { &.increment-30 {
position: relative; left: 30%;
top: 1px; }
margin-right: 5px;
}
.well { &.increment-40 {
padding: 20px; left: 40%;
background: $lightGrey; }
border: 1px solid $mediumGrey;
@include border-radius(4px);
@include box-shadow(0 1px 1px rgba(0,0,0,0.05) inset)
}
}
&.increment-50 {
left: 50%;
}
&.increment-60 {
left: 60%;
}
h3 { &.increment-70 {
margin-bottom: 30px; left: 70%;
font-size: 15px; }
font-weight: 700;
color: $blue;
}
.grade-controls { &.increment-80 {
@include clearfix; left: 80%;
width: 642px; }
}
.new-grade-button { &.increment-90 {
position: relative; left: 90%;
float: left; }
display: block;
width: 29px;
height: 29px;
margin: 10px 20px 0 0;
border-radius: 20px;
border: 1px solid $darkGrey;
@include linear-gradient(top, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0));
background-color: #d1dae3;
@include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset);
color: #6d788b;
.plus-icon {
position: absolute;
top: 50%;
left: 50%;
margin-left: -6px;
margin-top: -6px;
}
}
.grade-slider { &.increment-100 {
float: left; left: 100%;
width: 580px; }
margin-bottom: 10px; }
}
.grade-specific-bar {
height: 50px !important;
}
.grade-bar { .grades {
position: relative; position: relative;
width: 100%;
height: 50px;
background: $lightGrey;
.increments { li {
position: relative; position: absolute;
top: 0;
height: 50px;
text-align: right;
@include border-radius(2px);
&:hover,
&.is-dragging {
.remove-button {
display: block;
}
}
li { &.is-dragging {
position: absolute;
top: 52px;
width: 30px;
margin-left: -15px;
font-size: 9px;
text-align: center;
&.increment-0 {
left: 0;
}
&.increment-10 { }
left: 10%;
}
&.increment-20 { .remove-button {
left: 20%; display: none;
} position: absolute;
top: -17px;
right: 1px;
height: 17px;
font-size: 10px;
}
&.increment-30 { &:nth-child(1) {
left: 30%; background: #4fe696;
} }
&.increment-40 { &:nth-child(2) {
left: 40%; background: #ffdf7e;
} }
&.increment-50 { &:nth-child(3) {
left: 50%; background: #ffb657;
} }
&.increment-60 { &:nth-child(4) {
left: 60%; background: #ef54a1;
} }
&.increment-70 { &:nth-child(5),
left: 70%; &.bar-fail {
} background: #fb336c;
}
&.increment-80 { .letter-grade {
left: 80%; display: block;
} margin: 10px 15px 0 0;
font-size: 16px;
font-weight: 700;
line-height: 14px;
}
&.increment-90 { .range {
left: 90%; display: block;
} margin-right: 15px;
font-size: 10px;
line-height: 12px;
}
&.increment-100 { .drag-bar {
left: 100%; position: absolute;
top: 0;
right: -1px;
height: 50px;
width: 2px;
background-color: #fff;
@include box-shadow(-1px 0 3px rgba(0,0,0,0.1));
cursor: ew-resize;
@include transition(none);
&:hover {
width: 6px;
right: -2px;
}
}
}
} }
} }
} }
}
.grade-specific-bar {
height: 50px !important;
}
.grades {
position: relative;
li {
position: absolute;
top: 0;
height: 50px;
text-align: right;
@include border-radius(2px);
&:hover,
&.is-dragging {
.remove-button {
display: block;
}
}
&.is-dragging {
// specific fields - grading rules
&.grade-rules {
} #field-course-grading-graceperiod {
width: flex-grid(3, 9);
}
}
.remove-button { &.assignment-types {
display: none;
position: absolute;
top: -17px;
right: 1px;
height: 17px;
font-size: 10px;
}
&:nth-child(1) { .list-input {
background: #4fe696;
}
&:nth-child(2) { &:last-child {
background: #ffdf7e; margin-bottom: 0;
} }
}
&:nth-child(3) { .field-group {
background: #ffb657; @include clearfix();
} width: flex-grid(9, 9);
margin-bottom: ($baseline*1.5);
border-bottom: 1px solid $gray-l5;
padding-bottom: ($baseline*1.5);
&:nth-child(4) { &:last-child {
background: #ef54a1; border: none;
} padding-bottom: 0;
}
&:nth-child(5), .field {
&.bar-fail { display: inline-block;
background: #fb336c; vertical-align: top;
} width: flex-grid(3, 6);
margin-bottom: ($baseline/2);
margin-right: flex-gutter();
}
.letter-grade { #field-course-grading-assignment-shortname,
display: block; #field-course-grading-assignment-totalassignments,
margin: 10px 15px 0 0; #field-course-grading-assignment-gradeweight,
font-size: 16px; #field-course-grading-assignment-droppable {
font-weight: 700; width: flex-grid(2, 6);
line-height: 14px; }
} }
.range { .actions {
display: block; float: left;
margin-right: 15px; width: flex-grid(9, 9);
font-size: 10px;
line-height: 12px;
}
.drag-bar { .delete-button {
position: absolute; margin: 0;
top: 0;
right: -1px;
height: 50px;
width: 2px;
background-color: #fff;
@include box-shadow(-1px 0 3px rgba(0,0,0,0.1));
cursor: ew-resize;
@include transition(none);
&:hover {
width: 6px;
right: -2px;
}
}
} }
} }
} }
} }
.content-supplementary {
width: flex-grid(3, 12);
}
} }
\ No newline at end of file
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
&.new-component-item { &.new-component-item {
margin-top: 20px; margin-top: 20px;
background: transparent;
} }
} }
......
.subsection .main-wrapper {
margin: 40px;
}
.subsection .inner-wrapper {
@include clearfix();
}
.subsection-body { .subsection-body {
padding: 32px 40px; padding: 32px 40px;
@include clearfix; @include clearfix;
......
.unit .main-wrapper, .unit .main-wrapper {
.subsection .main-wrapper { @include clearfix();
margin: 40px; margin: 40px;
} }
......
$gw-column: 80px; $baseline: 20px;
$gw-gutter: 20px;
// grid
$gw-column: ($baseline*3);
$gw-gutter: $baseline;
$fg-column: $gw-column; $fg-column: $gw-column;
$fg-gutter: $gw-gutter; $fg-gutter: $gw-gutter;
$fg-max-columns: 12; $fg-max-columns: 12;
$fg-max-width: 1400px; $fg-max-width: 1280px;
$fg-min-width: 810px; $fg-min-width: 900px;
// type
$sans-serif: 'Open Sans', $verdana; $sans-serif: 'Open Sans', $verdana;
$body-line-height: golden-ratio(.875em, 1); $body-line-height: golden-ratio(.875em, 1);
$error-red: rgb(253, 87, 87);
$white: rgb(255,255,255); // colors - new for re-org
$black: rgb(0,0,0); $black: rgb(0,0,0);
$pink: rgb(182,37,104); $white: rgb(255,255,255);
$error-red: rgb(253, 87, 87);
$gray: rgb(127,127,127);
$gray-l1: tint($gray,20%);
$gray-l2: tint($gray,40%);
$gray-l3: tint($gray,60%);
$gray-l4: tint($gray,80%);
$gray-l5: tint($gray,90%);
$gray-d1: shade($gray,20%);
$gray-d2: shade($gray,40%);
$gray-d3: shade($gray,60%);
$gray-d4: shade($gray,80%);
$blue: rgb(85, 151, 221);
$blue-l1: tint($blue,20%);
$blue-l2: tint($blue,40%);
$blue-l3: tint($blue,60%);
$blue-l4: tint($blue,80%);
$blue-l5: tint($blue,90%);
$blue-d1: shade($blue,20%);
$blue-d2: shade($blue,40%);
$blue-d3: shade($blue,60%);
$blue-d4: shade($blue,80%);
$pink: rgb(183, 37, 103);
$pink-l1: tint($pink,20%);
$pink-l2: tint($pink,40%);
$pink-l3: tint($pink,60%);
$pink-l4: tint($pink,80%);
$pink-l5: tint($pink,90%);
$pink-d1: shade($pink,20%);
$pink-d2: shade($pink,40%);
$pink-d3: shade($pink,60%);
$pink-d4: shade($pink,80%);
$green: rgb(37, 184, 90);
$green-l1: tint($green,20%);
$green-l2: tint($green,40%);
$green-l3: tint($green,60%);
$green-l4: tint($green,80%);
$green-l5: tint($green,90%);
$green-d1: shade($green,20%);
$green-d2: shade($green,40%);
$green-d3: shade($green,60%);
$green-d4: shade($green,80%);
$yellow: rgb(231, 214, 143);
$yellow-l1: tint($yellow,20%);
$yellow-l2: tint($yellow,40%);
$yellow-l3: tint($yellow,60%);
$yellow-l4: tint($yellow,80%);
$yellow-l5: tint($yellow,90%);
$yellow-d1: shade($yellow,20%);
$yellow-d2: shade($yellow,40%);
$yellow-d3: shade($yellow,60%);
$yellow-d4: shade($yellow,80%);
$shadow: rgba(0,0,0,0.2);
$shadow-l1: rgba(0,0,0,0.1);
$shadow-d1: rgba(0,0,0,0.4);
// colors - inherited
$baseFontColor: #3c3c3c; $baseFontColor: #3c3c3c;
$offBlack: #3c3c3c; $offBlack: #3c3c3c;
$black: rgb(0,0,0);
$white: rgb(255,255,255);
$blue: #5597dd;
$orange: #edbd3c; $orange: #edbd3c;
$red: #b20610; $red: #b20610;
$green: #108614; $green: #108614;
...@@ -34,4 +94,4 @@ $brightGreen: rgb(22, 202, 87); ...@@ -34,4 +94,4 @@ $brightGreen: rgb(22, 202, 87);
$disabledGreen: rgb(124, 206, 153); $disabledGreen: rgb(124, 206, 153);
$darkGreen: rgb(52, 133, 76); $darkGreen: rgb(52, 133, 76);
$lightBluishGrey: rgb(197, 207, 223); $lightBluishGrey: rgb(197, 207, 223);
$lightBluishGrey2: rgb(213, 220, 228); $lightBluishGrey2: rgb(213, 220, 228);
\ No newline at end of file
@import 'bourbon/bourbon'; @import 'bourbon/bourbon';
@import 'bourbon/addons/button';
@import 'vendor/normalize'; @import 'vendor/normalize';
@import 'keyframes'; @import 'keyframes';
...@@ -8,8 +9,10 @@ ...@@ -8,8 +9,10 @@
@import "fonts"; @import "fonts";
@import "variables"; @import "variables";
@import "cms_mixins"; @import "cms_mixins";
@import "extends";
@import "base"; @import "base";
@import "header"; @import "header";
@import "footer";
@import "dashboard"; @import "dashboard";
@import "courseware"; @import "courseware";
@import "subsection"; @import "subsection";
...@@ -26,6 +29,8 @@ ...@@ -26,6 +29,8 @@
@import "modal"; @import "modal";
@import "alerts"; @import "alerts";
@import "login"; @import "login";
@import "account";
@import "index";
@import 'jquery-ui-calendar'; @import 'jquery-ui-calendar';
@import 'content-types'; @import 'content-types';
......
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="bodyclass">assets</%block> <%block name="bodyclass">is-signedin course uploads</%block>
<%block name="title">Courseware Assets</%block> <%block name="title">Uploads &amp; Files</%block>
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
......
...@@ -5,23 +5,29 @@ ...@@ -5,23 +5,29 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>
<%block name="title"></%block> |
% if context_course:
<% ctx_loc = context_course.location %>
${context_course.display_name} |
% endif
edX Studio
</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="path_prefix" content="${MITX_ROOT_URL}">
<%static:css group='base-style'/> <%static:css group='base-style'/>
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/skins/simple/style.css')}" /> <link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/skins/simple/style.css')}" />
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/sets/wiki/style.css')}" /> <link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/sets/wiki/style.css')}" />
<link rel="stylesheet" type="text/css" href="${static.url('css/vendor/symbolset.ss-standard.css')}" />
<link rel="stylesheet" type="text/css" href="${static.url('css/vendor/symbolset.ss-symbolicons-block.css')}" /> <link rel="stylesheet" type="text/css" href="${static.url('css/vendor/symbolset.ss-symbolicons-block.css')}" />
<link rel="stylesheet" type="text/css" href="${static.url('css/vendor/symbolset.ss-standard.css')}" />
<title><%block name="title"></%block></title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="path_prefix" content="${MITX_ROOT_URL}">
<%block name="header_extras"></%block> <%block name="header_extras"></%block>
</head> </head>
<body class="<%block name='bodyclass'></%block> hide-wip"> <body class="<%block name='bodyclass'></%block> hide-wip">
<%include file="widgets/header.html" args="active_tab=active_tab"/> <%include file="widgets/header.html" />
<%include file="courseware_vendor_js.html"/> <%include file="courseware_vendor_js.html"/>
<script type="text/javascript" src="${static.url('js/vendor/json2.js')}"></script> <script type="text/javascript" src="${static.url('js/vendor/json2.js')}"></script>
...@@ -47,9 +53,9 @@ ...@@ -47,9 +53,9 @@
</script> </script>
<%block name="content"></%block> <%block name="content"></%block>
<%include file="widgets/footer.html" />
<%block name="jsextra"></%block> <%block name="jsextra"></%block>
</body> </body>
</html> </html>
......
<%inherit file="base.html" /> <%inherit file="base.html" />
<%block name="title">Course Manager</%block>
<%include file="widgets/header.html"/> <%include file="widgets/header.html"/>
<%block name="content"> <%block name="content">
......
...@@ -2,8 +2,9 @@ ...@@ -2,8 +2,9 @@
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<!-- TODO decode course # from context_course into title --> <!-- TODO decode course # from context_course into title -->
<%block name="title">Course Info</%block> <%block name="title">Updates</%block>
<%block name="bodyclass">course-info</%block> <%block name="bodyclass">is-signedin course course-info updates</%block>
<%block name="jsextra"> <%block name="jsextra">
<script type="text/javascript" src="${static.url('js/template_loader.js')}"></script> <script type="text/javascript" src="${static.url('js/template_loader.js')}"></script>
......
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="title">Edit Static Page</%block> <%block name="title">Editing Static Page</%block>
<%block name="bodyclass">edit-static-page</%block> <%block name="bodyclass">is-signedin course pages edit-static-page</%block>
<%block name="content"> <%block name="content">
<div class="main-wrapper"> <div class="main-wrapper">
......
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="title">Tabs</%block> <%block name="title">Static Pages</%block>
<%block name="bodyclass">static-pages</%block> <%block name="bodyclass">is-signedin course pages static-pages</%block>
<%block name="jsextra"> <%block name="jsextra">
<script type='text/javascript'> <script type='text/javascript'>
......
...@@ -7,8 +7,9 @@ ...@@ -7,8 +7,9 @@
%> %>
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="bodyclass">subsection</%block>
<%block name="title">CMS Subsection</%block> <%block name="title">CMS Subsection</%block>
<%block name="bodyclass">is-signedin course subsection</%block>
<%namespace name="units" file="widgets/units.html" /> <%namespace name="units" file="widgets/units.html" />
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
...@@ -97,6 +98,7 @@ ...@@ -97,6 +98,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</%block> </%block>
<%block name="jsextra"> <%block name="jsextra">
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="title">Export</%block> <%block name="title">Export Course</%block>
<%block name="bodyclass">export</%block> <%block name="bodyclass">is-signedin course tools export</%block>
<%block name="content"> <%block name="content">
<div class="main-wrapper"> <div class="main-wrapper">
......
<%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %>
<%block name="title">Welcome</%block>
<%block name="bodyclass">not-signedin index howitworks</%block>
<%block name="content">
<div class="wrapper-content-header wrapper">
<section class="content content-header">
<header>
<h1>Welcome to <span class="logo">edX Studio</span></h1>
<p class="tagline">Studio helps manage your courses online, so you can focus on teaching them</p>
</header>
</section>
</div>
<div class="wrapper-content-features wrapper">
<section class="content content-features">
<header>
<h2 class="sr">Studio's Many Features</h2>
</header>
<ol class="list-features">
<li class="feature">
<figure class="img zoom">
<a rel="modal" href="#hiw-feature1">
<img src="/static/img/thumb-hiw-feature1.png" alt="Studio Helps You Keep Your Courses Organized" />
<figcaption class="sr">Studio Helps You Keep Your Courses Organized</figcaption>
<span class="action-zoom">
<i class="ss-icon ss-symbolicons-block">&#xE002;</i>
</span>
</a>
</figure>
<div class="copy">
<h3>Keeping Your Course Organized</h3>
<p>The backbone of your course is how it is organized. Studio offers an <strong>Outline</strong> editor, providing a simple hierarchy and easy drag and drop to help you and your students stay organized.</p>
<ul class="list-proofpoints">
<li class="proofpoint">
<h4 class="title">Simple Organization For Content</h4>
<p>Studio uses <strong>Sections</strong> and <strong>Learning Sequences</strong> to organize your content into a simple hierarchy.</p>
</li>
<li class="proofpoint">
<h4 class="title">Change Your Mind Anytime</h4>
<p>Draft your outline and build content anywhere. Simple drag and drop tools let your reorganize quickly.</p>
</li>
<li class="proofpoint">
<h4 class="title">Go A Week Or A Semester At A Time</h4>
<p>Build and release <strong>Sections</strong> to your students incrementally. You don't have to have it all done at once.</p>
</li>
</ul>
</div>
</li>
<li class="feature">
<figure class="img zoom">
<a rel="modal" href="#hiw-feature2">
<img src="/static/img/thumb-hiw-feature2.png" alt="Studio Keeps Your Learning Sequences and Lectures, Together" />
<figcaption class="sr">Studio Keeps Your Learning Sequences and Lectures, Together</figcaption>
<span class="action-zoom">
<i class="ss-icon ss-symbolicons-block">&#xE002;</i>
</span>
</a>
</figure>
<div class="copy">
<h3>Learning Sequences: Lectures and Exercises, Together </h3>
<p>The heart of the student experience is being immersed in <strong>Learning Sequences</strong> &mdash; short video lectures interleaved with exercises. Studio allows you to insert videos and author a wide variety of exercise types with just a few clicks. </p>
<ul class="list-proofpoints">
<li class="proofpoint">
<h4 class="title">Create Learning Pathways</h4>
<p>Help your students understand a small interactive piece at a time. <strong>Learning Sequences</strong> are built from <strong>Learning Units</strong>.</p>
</li>
<li class="proofpoint">
<h4 class="title">Work Visually, Organize Quickly</h4>
<p>Work visually and see exactly what your students will see. Reorganize your <strong>Learning Units</strong> with drag and drop.</p>
</li>
<li class="proofpoint">
<h4 class="title">A Broad Library of Problem Types</h4>
<p>It's more than just multiple choice. Studio has nearly a dozen types of problems to challenge your learners.</p>
</li>
</ul>
</div>
</li>
<li class="feature">
<figure class="img zoom">
<a rel="modal" href="#hiw-feature3">
<img src="/static/img/thumb-hiw-feature3.png" alt="Studio Gives You Simple, Fast, and Incremental Publishing. With Friends." />
<figcaption class="sr">Studio Gives You Simple, Fast, and Incremental Publishing. With Friends.</figcaption>
<span class="action-zoom">
<i class="ss-icon ss-symbolicons-block">&#xE002;</i>
</span>
</a>
</figure>
<div class="copy">
<h3>Simple, Fast, and Incremental Publishing. With Friends.</h3>
<p>Studio works like web applications you already know, yet understands how you build curriculum. Instant publishing to the web when you want it, incremental release when it makes sense. And with co-authors, you can have a whole team building a course, together.</p>
<ul class="list-proofpoints">
<li class="proofpoint">
<h4 class="title">Instant Changes</h4>
<p>Caught a bug? No problem. When you want, your changes to live when you hit Save.</p>
</li>
<li class="proofpoint">
<h4 class="title">Release-On Date Publishing</h4>
<p>When you've finished a <strong>Section</strong>, pick when you want it to go live and Studio takes care of the rest. Build your course incrementally.</p>
</li>
<li class="proofpoint">
<h4 class="title">Work in Teams</h4>
<p>Co-authors have full access to all the same authoring tools. Make your course better through a team effort.</p>
</li>
</ul>
</div>
</li>
</ol>
</section>
</div>
<div class="wrapper-content-cta wrapper">
<section class="content content-cta">
<header>
<h2 class="sr">Sign Up for Studio Today!</h2>
</header>
<ul class="list-actions">
<li>
<a href="${reverse('signup')}" class="action action-primary">Sign Up &amp; Start Making an edX Course</a>
</li>
<li>
<a href="${reverse('login')}" class="action action-secondary">Already have a Studio Account? Sign In</a>
</li>
</ul>
</section>
</div>
<div class="content-modal" id="hiw-feature1">
<h3 class="title">Outlining Your Course</h3>
<figure>
<img src="/static/img/hiw-feature1.png" alt="" />
<figcaption class="description">Simple two-level outline to organize your couse. Drag and drop, and see your course at a glance.</figcaption>
</figure>
<a href="#" rel="view" class="action action-modal-close">
<i class="ss-icon ss-symbolicons-block">&#x2421;</i>
<span class="label">close modal</span>
</a>
</div>
<div class="content-modal" id="hiw-feature2">
<h3 class="title">Editing a Learning Sequence</h3>
<figure>
<img src="/static/img/hiw-feature2.png" alt="" />
<figcaption class="description">Quickly create videos, text snippets, inline discussions, and a variety of problem types.</figcaption>
</figure>
<a href="#" rel="view" class="action action-modal-close">
<i class="ss-icon ss-symbolicons-block">&#x2421;</i>
<span class="label">close modal</span>
</a>
</div>
<div class="content-modal" id="hiw-feature3">
<h3 class="title">Publishing on Date</h3>
<figure>
<img src="/static/img/hiw-feature3.png" alt="" />
<figcaption class="description">Simply set the date on a Section or Learning Sequence, and Studio will publish it to your students for you.</figcaption>
</figure>
<a href="#" rel="view" class="action action-modal-close">
<i class="ss-icon ss-symbolicons-block">&#x2421;</i>
<span class="label">close modal</span>
</a>
</div>
</%block>
<%block name="jsextra">
<script type="text/javascript">
(function() {
// lean modal window
$('a[rel*=modal]').leanModal({overlay : 0.50, closeButton: '.action-modal-close' });
$('a.action-modal-close').click(function(e){
(e).preventDefault();
});
})(this)
</script>
</%block>
\ No newline at end of file
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="title">Import</%block> <%block name="title">Import Course</%block>
<%block name="bodyclass">import</%block> <%block name="bodyclass">is-signedin course tools import</%block>
<%block name="content"> <%block name="content">
<div class="main-wrapper"> <div class="main-wrapper">
......
<%inherit file="base.html" /> <%inherit file="base.html" />
<%block name="bodyclass">index</%block>
<%block name="title">Courses</%block> <%block name="title">Courses</%block>
<%block name="bodyclass">is-signedin index dashboard</%block>
<%block name="header_extras"> <%block name="header_extras">
<script type="text/template" id="new-course-template"> <script type="text/template" id="new-course-template">
......
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="title">Log in</%block> <%block name="title">Sign In</%block>
<%block name="bodyclass">no-header</%block> <%block name="bodyclass">not-signedin signin</%block>
<%block name="content"> <%block name="content">
<div class="edx-studio-logo-large"></div> <div class="wrapper-content wrapper">
<section class="content">
<article class="log-in-box">
<header> <header>
<h1>Log in to edX studio</h1> <h1 class="title title-1">Sign into edX Studio</h1>
<a href="${reverse('signup')}" class="action action-signin">Don't have a Studio Account? Sign up!</a>
</header> </header>
<form class="log-in-form" id="login_form" action="login_post" method="post">
<div class="row"> <article class="content-primary" role="main">
<label>Email</label> <form id="login_form" method="post" action="login_post">
<input name="email" type="email" class="email-field" tabindex="1">
</div> <fieldset>
<div class="row"> <legend class="sr">Required Information to Sign into edX Studio</legend>
<label>Password <a href="${forgot_password_link}" class="forgot-button">Forgot password?</a></label>
<input name="password" type="password" class="password-field" tabindex="2"> <ol class="list-input">
</div> <li class="field text required" id="field-email">
<div class="row form-actions"> <label for="email">Email Address</label>
<input name="submit" type="submit" value="Log In" class="log-in-button" tabindex="3"> <input id="email" type="email" name="email" placeholder="e.g. jane.doe@gmail.com" />
<span class="or">or</span> </li>
<a href="${reverse('signup')}" class="sign-up-button" tabindex="4">Sign up</a>
<li class="field text required" id="field-password">
<a href="${forgot_password_link}" class="action action-forgotpassword" tabindex="-1">Forgot password?</a>
<label for="password">Password</label>
<input id="password" type="password" name="password" />
</li>
</ol>
</fieldset>
<div class="form-actions">
<button type="submit" id="submit" name="submit" class="action action-primary">Sign into edX Studio</button>
</div>
<!-- no honor code for CMS, but need it because we're using the lms student object -->
<input name="honor_code" type="checkbox" value="true" checked="true" hidden="true">
</form>
</article>
<aside class="content-supplementary" role="complimentary">
<h2 class="sr">Studio Support</h2>
<div class="bit">
<h3 class="title-3">Need Help?</h3>
<p>Having trouble with your account? Use <a href="" rel="external">our support center</a> to look over self help steps, find solutions others have found to the same problem, or let us know of your issue.</p>
</div> </div>
</form> </aside>
</article> </section>
</div>
</%block>
<%block name="jsextra">
<script type="text/javascript"> <script type="text/javascript">
(function() { (function() {
function getCookie(name) { function getCookie(name) {
...@@ -53,10 +79,10 @@ ...@@ -53,10 +79,10 @@
if(json.success) { if(json.success) {
location.href = "${reverse('index')}"; location.href = "${reverse('index')}";
} else if($('#login_error').length == 0) { } else if($('#login_error').length == 0) {
$('#login_form').prepend('<div id="login_error">' + json.value + '</div>'); $('#login_form').prepend('<div id="login_error" class="message message-status error">' + json.value + '</span></div>');
$('#login_error').slideDown(150); $('#login_error').addClass('is-shown');
} else { } else {
$('#login_error').stop().slideDown(150); $('#login_error').stop().addClass('is-shown');
$('#login_error').html(json.value); $('#login_error').html(json.value);
} }
} }
...@@ -64,5 +90,4 @@ ...@@ -64,5 +90,4 @@
}); });
})(this) })(this)
</script> </script>
</%block>
</%block> \ No newline at end of file
<%inherit file="base.html" /> <%inherit file="base.html" />
<%block name="title">Course Staff Manager</%block> <%block name="title">Course Staff Manager</%block>
<%block name="bodyclass">users</%block> <%block name="bodyclass">is-signedin course users settings team</%block>
<%block name="content"> <%block name="content">
<div class="main-wrapper"> <div class="main-wrapper">
......
...@@ -6,7 +6,8 @@ ...@@ -6,7 +6,8 @@
from datetime import datetime from datetime import datetime
%> %>
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="title">CMS Courseware Overview</%block> <%block name="title">Course Outline</%block>
<%block name="bodyclass">is-signedin course outline</%block>
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<%namespace name="units" file="widgets/units.html" /> <%namespace name="units" file="widgets/units.html" />
......
<%inherit file="base.html" /> <%inherit file="base.html" />
<%block name="bodyclass">settings</%block> <%block name="title">Schedule &amp; Details</%block>
<%block name="title">Settings</%block> <%block name="bodyclass">is-signedin course schedule settings</%block>
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<%! <%!
...@@ -15,24 +15,24 @@ from contentstore import utils ...@@ -15,24 +15,24 @@ from contentstore import utils
<script src="${static.url('js/vendor/date.js')}"></script> <script src="${static.url('js/vendor/date.js')}"></script>
<script type="text/javascript" src="${static.url('js/template_loader.js')}"></script> <script type="text/javascript" src="${static.url('js/template_loader.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/server_error.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/course_relative.js')}"></script> <script type="text/javascript" src="${static.url('js/models/course_relative.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/settings/course_details.js')}"></script> <script type="text/javascript" src="${static.url('js/views/validating_view.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/settings/course_settings.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/settings/course_grading_policy.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/settings/main_settings_view.js')}"></script> <script type="text/javascript" src="${static.url('js/views/settings/main_settings_view.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/server_error.js')}"></script> <script type="text/javascript" src="${static.url('js/models/settings/course_details.js')}"></script>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function(){ $(document).ready(function(){
var settingsModel = new CMS.Models.Settings.CourseSettings({ $("form :input").focus(function() {
courseLocation: new CMS.Models.Location('${context_course.location}',{parse:true}), $("label[for='" + this.id + "']").addClass("is-focused");
details: new CMS.Models.Settings.CourseDetails(${course_details|n},{parse:true}) }).blur(function() {
$("label").removeClass("is-focused");
}); });
var editor = new CMS.Views.Settings.Main({ var editor = new CMS.Views.Settings.Details({
el: $('.main-wrapper'), el: $('.settings-details'),
model : settingsModel model: new CMS.Models.Settings.CourseDetails(${course_details|n},{parse:true})
}); });
editor.render(); editor.render();
...@@ -42,689 +42,188 @@ from contentstore import utils ...@@ -42,689 +42,188 @@ from contentstore import utils
</%block> </%block>
<%block name="content"> <%block name="content">
<!-- --> <div class="wrapper-content wrapper">
<div class="main-wrapper"> <section class="content">
<div class="inner-wrapper"> <header class="page">
<h1>Settings</h1> <span class="title-sub">Settings</span>
<article class="settings-overview"> <h1 class="title-1">Schedule &amp; Details</h1>
<div class="sidebar"> </header>
<nav class="settings-page-menu">
<ul> <!-- <div class="introduction">
<li><a href="#" class="is-shown" data-section="details">Course Details</a></li> <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.</p>
<!-- <li><a href="#" data-section="faculty">Faculty</a></li> --> </div> -->
<li><a href="#" data-section="grading">Grading</a></li>
<!-- <li><a href="#" data-section="problems">Problems</a></li> --> <article class="content-primary" role="main">
<!-- <li><a href="#" data-section="discussions">Discussions</a></li> --> <form id="settings_details" class="settings-details" method="post" action="">
</ul> <section class="group-settings basic">
</nav>
</div>
<div class="settings-page-section main-column">
<section class="settings-details is-shown">
<h2 class="title">Course Details</h2>
<section class="settings-details-basic">
<header> <header>
<h3>Basic Information</h3> <h2 class="title-2">Basic Information</h2>
<span class="detail">The nuts and bolts of your course</span> <span class="tip">The nuts and bolts of your course</span>
</header> </header>
<div class="row row-col2"> <ol class="list-input">
<label for="course-name">Course Name:</label> <li class="field text is-not-editable" id="field-course-organization">
<div class="field"> <label for="course-organization">Organization</label>
<div class="input"> <input type="text" class="long" id="course-organization" value="[Course Organization]" disabled="disabled" />
<input type="text" class="long" id="course-name" value="[Course Name]" disabled="disabled"> </li>
<span class="tip tip-stacked">This is used in <a href="${utils.get_lms_link_for_about_page(context_course.location)}">your course URL</a>, and cannot be changed</span>
</div> <li class="field text is-not-editable" id="field-course-number">
</div> <label for="course-number">Course Number</label>
</div> <input type="text" class="short" id="course-number" value="[Course No.]" disabled="disabled">
</li>
<div class="row row-col2">
<label for="course-organization">Organization:</label> <li class="field text is-not-editable" id="field-course-name">
<div class="field"> <label for="course-name">Course Name</label>
<div class="input"> <input type="text" class="long" id="course-name" value="[Course Name]" disabled="disabled" />
<input type="text" class="long" id="course-organization" value="[Course Organization]" disabled="disabled"> </li>
<span class="tip tip-stacked">This is used in <a href="${utils.get_lms_link_for_about_page(context_course.location)}">your course URL</a>, and cannot be changed</span> </ol>
</div> <span class="tip tip-stacked">These are used in <a rel="external" href="${utils.get_lms_link_for_about_page(course_location)}" />your course URL</a>, and cannot be changed</span>
</div> </section>
</div>
<div class="row row-col2">
<label for="course-number">Course Number:</label>
<div class="field">
<div class="input">
<input type="text" class="short" id="course-number" value="[Course No.]" disabled="disabled">
<span class="tip tip-stacked">This is used in <a href="${utils.get_lms_link_for_about_page(context_course.location)}">your course URL</a>, and cannot be changed</span>
</div>
</div>
</div>
</section><!-- .settings-details-basic -->
<hr class="divide" /> <hr class="divide" />
<section class="settings-details-schedule"> <section class="group-settings schedule">
<header> <header>
<h3>Course Schedule</h3> <h2 class="title-2">Course Schedule</h2>
<span class="detail">Important steps and segments of your course</span> <span class="tip">Important steps and segments of your course</span>
</header> </header>
<div class="row row-col2"> <ol class="list-input">
<h4 class="label">Course Dates:</h4> <li class="field-group field-group-course-start" id="course-start">
<div class="field date" id="field-course-start-date">
<div class="field"> <label for="course-start-date">Course Start Date</label>
<div class="input multi multi-inline" id="course-start"> <input type="text" class="start-date date start datepicker" id="course-start-date" placeholder="MM/DD/YYYY" autocomplete="off" />
<div class="group">
<label for="course-start-date">Start Date</label>
<input type="text" class="start-date date start datepicker" id="course-start-date" placeholder="MM/DD/YYYY" autocomplete="off">
<span class="tip tip-stacked">First day the course begins</span> <span class="tip tip-stacked">First day the course begins</span>
</div> </div>
<div class="group"> <div class="field time" id="field-course-start-time">
<label for="course-start-time">Start Time</label> <label for="course-start-time">Course Start Time</label>
<input type="text" class="time start timepicker" id="course-start-time" value="" placeholder="HH:MM" autocomplete="off"> <input type="text" class="time start timepicker" id="course-start-time" value="" placeholder="HH:MM" autocomplete="off" />
<span class="tip tip-stacked" id="timezone"></span> <span class="tip tip-stacked" id="timezone"></span>
</div> </div>
</div> </li>
</div>
<div class="field field-additional"> <li class="field-group field-group-course-end" id="course-end">
<div class="input multi multi-inline" id="course-end"> <div class="field date" id="field-course-end-date">
<div class="group"> <label for="course-end-date">Course End Date</label>
<label for="course-end-date">End Date</label> <input type="text" class="end-date date end" id="course-end-date" placeholder="MM/DD/YYYY" autocomplete="off" />
<input type="text" class="end-date date end" id="course-end-date" placeholder="MM/DD/YYYY" autocomplete="off"> <span class="tip tip-stacked">Last day your course is active</span>
<span class="tip tip-stacked">Last day the course is active</span>
</div> </div>
<div class="group"> <div class="field time" id="field-course-end-time">
<label for="course-end-time">End Time</label> <label for="course-end-time">Course End Time</label>
<input type="text" class="time end" id="course-end-time" value="" placeholder="HH:MM" autocomplete="off"> <input type="text" class="time end" id="course-end-time" value="" placeholder="HH:MM" autocomplete="off" />
<span class="tip tip-stacked" id="timezone"></span> <span class="tip tip-stacked" id="timezone"></span>
</div> </div>
</div> </li>
</div> </ol>
</div>
<ol class="list-input">
<div class="row row-col2"> <li class="field-group field-group-enrollment-start" id="enrollment-start">
<h4 class="label">Enrollment Dates:</h4> <div class="field date" id="field-enrollment-start-date">
<label for="course-enrollment-start-date">Enrollment Start Date</label>
<div class="field"> <input type="text" class="start-date date start" id="course-enrollment-start-date" placeholder="MM/DD/YYYY" autocomplete="off" />
<div class="input multi multi-inline" id="enrollment-start">
<div class="group">
<label for="course-enrollment-start-date">Start Date</label>
<input type="text" class="start-date date start" id="course-enrollment-start-date" placeholder="MM/DD/YYYY" autocomplete="off">
<span class="tip tip-stacked">First day students can enroll</span> <span class="tip tip-stacked">First day students can enroll</span>
</div> </div>
<div class="group"> <div class="field time" id="field-enrollment-start-time">
<label for="course-enrollment-start-time">Start Time</label> <label for="course-enrollment-start-time">Enrollment Start Time</label>
<input type="text" class="time start" id="course-enrollment-start-time" value="" placeholder="HH:MM" autocomplete="off"> <input type="text" class="time start" id="course-enrollment-start-time" value="" placeholder="HH:MM" autocomplete="off" />
<span class="tip tip-stacked" id="timezone"></span> <span class="tip tip-stacked" id="timezone"></span>
</div> </div>
</div> </li>
</div>
<div class="field field-additional"> <li class="field-group field-group-enrollment-end" id="enrollment-end">
<div class="input multi multi-inline" id="enrollment-end"> <div class="field date" id="field-enrollment-end-date">
<div class="group"> <label for="course-enrollment-end-date">Enrollment End Date</label>
<label for="course-enrollment-end-date">End Date</label> <input type="text" class="end-date date end" id="course-enrollment-end-date" placeholder="MM/DD/YYYY" autocomplete="off" />
<input type="text" class="end-date date end" id="course-enrollment-end-date" placeholder="MM/DD/YYYY" autocomplete="off">
<span class="tip tip-stacked">Last day students can enroll</span> <span class="tip tip-stacked">Last day students can enroll</span>
</div> </div>
<div class="group"> <div class="field time" id="field-enrollment-end-time">
<label for="course-enrollment-end-time">End Time</label> <label for="course-enrollment-end-time">Enrollment End Time</label>
<input type="text" class="time end" id="course-enrollment-end-time" value="" placeholder="HH:MM" autocomplete="off"> <input type="text" class="time end" id="course-enrollment-end-time" value="" placeholder="HH:MM" autocomplete="off" />
<span class="tip tip-stacked" id="timezone"></span> <span class="tip tip-stacked" id="timezone"></span>
</div> </div>
</div> </li>
</div> </ol>
</div> </section>
<!-- <div class="row row-col2">
<label for="course-syllabus">Course Syllabus</label>
<div class="field">
<div class="input input-existing">
<div class="current current-course-syllabus">
<span class="doc-filename"></span>
<a href="#" class="remove-item remove-course-syllabus remove-doc-data" id="course-syllabus"><span class="delete-icon"></span> Delete Syllabus</a>
</div>
</div>
<div class="input">
<a href="#" class="new-item new-course-syllabus add-syllabus-data" id="course-syllabus">
<span class="upload-icon"></span>Upload Syllabus
</a>
<span class="tip tip-inline">PDF formatting preferred</span>
</div>
</div>
</div> -->
</section><!-- .settings-details-schedule -->
<hr class="divide" /> <hr class="divide" />
<section class="setting-details-marketing"> <section class="group-settings marketing">
<header> <header>
<h3>Introducing Your Course</h3> <h2 class="title-2">Introducing Your Course</h2>
<span class="detail">Information for prospective students</span> <span class="tip">Information for prospective students</span>
</header> </header>
<div class="row row-col2"> <ol class="list-input">
<label for="course-overview">Course Overview:</label> <li class="field text" id="field-course-overview">
<div class="field"> <label for="course-overview">Course Overview</label>
<div class="input"> <textarea class="tinymce text-editor" id="course-overview"></textarea>
<textarea class="long tall tinymce text-editor" id="course-overview"></textarea> <span class="tip tip-stacked">Introductions, prerequisites, FAQs that are used on <a href="${utils.get_lms_link_for_about_page(course_location)}">your course summary page</a></span>
<span class="tip tip-stacked">Introductions, prerequisites, FAQs that are used on <a href="${utils.get_lms_link_for_about_page(context_course.location)}">your course summary page</a></span> </li>
</div>
</div>
</div>
<div class="row row-col2"> <li class="field video" id="field-course-introduction-video">
<label for="course-introduction-video">Introduction Video:</label> <label for="course-overview">Course Introduction Video</label>
<div class="field">
<div class="input input-existing"> <div class="input input-existing">
<div class="current current-course-introduction-video"> <div class="current current-course-introduction-video">
<iframe width="380" height="215" src="" frameborder="0" allowfullscreen></iframe> <iframe width="618" height="350" src="" frameborder="0" allowfullscreen></iframe>
<a href="#" class="remove-item remove-course-introduction-video remove-video-data"><span class="delete-icon"></span> Delete Video</a>
</div> </div>
<div class="actions">
<a href="#" class="remove-item remove-course-introduction-video remove-video-data"><span class="delete-icon"></span> Delete Current Video</a>
</div>
</div> </div>
<div class="input"> <div class="input">
<input type="text" class="long new-course-introduction-video add-video-data" id="course-introduction-video" value="" placeholder="id" autocomplete="off"> <input type="text" class="long new-course-introduction-video add-video-data" id="course-introduction-video" value="" placeholder="your YouTube video's ID" autocomplete="off" />
<span class="tip tip-stacked">Video restrictions go here</span> <span class="tip tip-stacked">Enter your YouTube video's ID (along with any restriction parameters)</span>
</div> </div>
</div> </li>
</div> </ol>
</section><!-- .settings-details-marketing --> </section>
<hr class="divide" /> <hr class="divide" />
<section class="settings-details-requirements"> <section class="group-settings requirements">
<header> <header>
<h3>Requirements</h3> <h2 class="title-2">Requirements</h2>
<span class="detail">Expectations of the students taking this course</span> <span class="tip">Expectations of the students taking this course</span>
</header> </header>
<div class="row row-col2"> <ol class="list-input">
<label for="course-effort">Hours of Effort per Week:</label> <li class="field text" id="field-course-effort">
<div class="field"> <label for="course-effort">Hours of Effort per Week</label>
<div class="input"> <input type="text" class="short time" id="course-effort" placeholder="HH:MM" />
<input type="text" class="short time" id="course-effort" placeholder="HH:MM">
<span class="tip tip-inline">Time spent on all course work</span> <span class="tip tip-inline">Time spent on all course work</span>
</div>
</div>
</div>
</section>
</section><!-- .settings-details -->
<section class="settings-faculty">
<h2 class="title">Faculty</h2>
<section class="settings-faculty-members">
<header>
<h3>Faculty Members</h3>
<span class="detail">Individuals instructing and help with this course</span>
</header>
<div class="row">
<div class="field enum">
<ul class="input-list course-faculty-list">
<li class="input input-existing multi course-faculty-list-item">
<div class="row row-col2">
<label for="course-faculty-1-firstname">Faculty First Name:</label>
<div class="field">
<input type="text" class="long" id="course-faculty-1-firstname">
</div>
</div>
<div class="row row-col2">
<label for="course-faculty-1-lastname">Faculty Last Name:</label>
<div class="field">
<input type="text" class="long" id="course-faculty-1-lastname">
</div>
</div>
<div class="row row-col2">
<label for="course-faculty-1-photo">Faculty Photo</label>
<div class="field">
<div class="input input-existing">
<div class="current current-faculty-1-photo">
<a href="#" class="remove-item remove-faculty-photo remove-video-data"><span class="delete-icon"></span> Delete Faculty Photo</a>
</div>
</div>
</div>
</div>
<div class="row">
<label for="course-faculty-1-bio">Faculty Bio:</label>
<div class="field">
<textarea class="long tall edit-box tinymce" id="course-faculty-1-bio"></textarea>
<span class="tip tip-stacked">A brief description of your education, experience, and expertise</span>
</div>
</div>
<a href="#" class="remove-item remove-faculty-data"><span class="delete-icon"></span> Delete Faculty Member</a>
</li>
<li class="input multi course-faculty-list-item">
<div class="row row-col2">
<label for="course-faculty-2-firstname">Faculty First Name:</label>
<div class="field">
<input type="text" class="long" id="course-faculty-2-firstname">
</div>
</div>
<div class="row row-col2">
<label for="course-faculty-2-lastname">Faculty Last Name:</label>
<div class="field">
<input type="text" class="long" id="course-faculty-2-lastname">
</div>
</div>
<div class="row row-col2">
<label for="course-faculty-2-photo">Faculty Photo</label>
<div class="field">
<div class="input">
<a href="#" class="new-item new-faculty-photo add-faculty-photo-data" id="course-faculty-2-photo">
<span class="upload-icon"></span>Upload Faculty Photo
</a>
<span class="tip tip-inline">Max size: 30KB</span>
</div>
</div>
</div>
<div class="row">
<label for="course-faculty-2-bio">Faculty Bio:</label>
<div class="field">
<div clas="input">
<textarea class="long tall edit-box tinymce" id="course-faculty-2-bio"></textarea>
<span class="tip tip-stacked">A brief description of your education, experience, and expertise</span>
</div>
</div>
</div>
</li> </li>
</ul>
<a href="#" class="new-item new-course-faculty-item add-faculty-data">
<span class="plus-icon"></span>New Faculty Member
</a>
</div>
</div>
</section>
</section><!-- .settings-staff -->
<section class="settings-grading">
<h2 class="title">Grading</h2>
<section class="settings-grading-range">
<header>
<h3>Overall Grade Range</h3>
<span class="detail">Course grade ranges and their values</span>
</header>
<div class="row">
<div class="grade-controls course-grading-range well">
<a href="#" class="new-grade-button"><span class="plus-icon"></span></a>
<div class="grade-slider">
<div class="grade-bar">
<ol class="increments">
<li class="increment-0">0</li>
<li class="increment-10">10</li>
<li class="increment-20">20</li>
<li class="increment-30">30</li>
<li class="increment-40">40</li>
<li class="increment-50">50</li>
<li class="increment-60">60</li>
<li class="increment-70">70</li>
<li class="increment-80">80</li>
<li class="increment-90">90</li>
<li class="increment-100">100</li>
</ol> </ol>
<ol class="grades">
</ol>
</div>
</div>
</div>
</div>
</section> </section>
</form>
</article>
<section class="settings-grading-general"> <aside class="content-supplementary" role="complimentary">
<header> <div class="bit">
<h3>General Grading</h3> <h3 class="title-3">How will these settings be used</h3>
<span class="detail">Deadlines and Requirements</span> <p>Your course's schedule settings determine when students can enroll in and begin a course as well as when the course.</p>
</header>
<div class="row row-col2">
<label for="course-grading-graceperiod">Grace Period on Deadline:</label>
<div class="field"> <p>Additionally, details provided on this page are also used in edX's catalog of courses, which new and returning students use to choose new courses to study.</p>
<div class="input">
<input type="text" class="short time" id="course-grading-graceperiod" value="0:00" placeholder="e.g. 10 minutes">
<span class="tip tip-inline">leeway on due dates</span>
</div>
</div> </div>
</div>
</section>
<section class="setting-grading-assignment-types">
<header>
<h3>Assignment Types</h3>
</header>
<div class="row"> <div class="bit">
<div class="field enum"> % if context_course:
<ul class="input-list course-grading-assignment-list"> <% ctx_loc = context_course.location %>
<%! from django.core.urlresolvers import reverse %>
<h3 class="title-3">Other Course Settings</h3>
<nav class="nav-related">
<ul>
<li class="nav-item"><a href="${reverse('contentstore.views.course_config_graders_page', kwargs={'org' : ctx_loc.org, 'course' : ctx_loc.course, 'name': ctx_loc.name})}">Grading</a></li>
</ul> </ul>
</nav>
<a href="#" class="new-button new-course-grading-item add-grading-data"> % endif
<span class="plus-icon white"></span>New Assignment Type
</a>
</div> </div>
</div> </aside>
</section> </section>
</section><!-- .settings-grading -->
<section class="settings-problems">
<h2 class="title">Problems</h2>
<section class="settings-problems-general">
<header>
<h3>General Settings</h3>
<span class="detail">Course-wide settings for all problems</span>
</header>
<div class="row row-col2">
<h4 class="label">Problem Randomization:</h4>
<div class="field">
<div class="input input-radio">
<input checked="checked" type="radio" name="course-problems-general-randomization" id="course-problems-general-randomization-always" value="Always">
<div class="copy">
<label for="course-problems-general-randomization-always">Always</label>
<span class="tip tip-stacked"><strong>randomize all</strong> problems</span>
</div>
</div>
<div class="input input-radio">
<input type="radio" name="course-problems-general-randomization" id="course-problems-general-randomization-never" value="Never">
<div class="copy">
<label for="course-problems-general-randomization-never">Never</label>
<span class="tip tip-stacked"><strong>do not randomize</strong> problems</span>
</div>
</div>
<div class="input input-radio">
<input type="radio" name="course-problems-general-randomization" id="course-problems-general-randomization-perstudent" value="Per Student">
<div class="copy">
<label for="course-problems-general-randomization-perstudent">Per Student</label>
<span class="tip tip-stacked">randomize problems <strong>per student</strong></span>
</div> </div>
</div>
</div>
</div>
<div class="row row-col2">
<h4 class="label">Show Answers:</h4>
<div class="field">
<div class="input input-radio">
<input checked="checked" type="radio" name="course-problems-general-showanswer" id="course-problems-general-showanswer-always" value="Always">
<div class="copy">
<label for="course-problems-general-showanswer-always">Always</label>
<span class="tip tip-stacked">Answers will be shown after the number of attempts has been met</span>
</div>
</div>
<div class="input input-radio">
<input type="radio" name="course-problems-general-showanswer" id="course-problems-general-showanswer-never" value="Never">
<div class="copy">
<label for="course-problems-general-showanswer-never">Never</label>
<span class="tip tip-stacked">Answers will never be shown, regardless of attempts</span>
</div>
</div>
</div>
</div>
<div class="row row-col2">
<label for="pcourse-roblems-general-attempts">Number of Attempts <br /> Allowed on Problems: </label>
<div class="field">
<div class="input">
<input type="text" class="short" id="course-problems-general-attempts" placeholder="0 or higher" value="0">
<span class="tip tip-stacked">Students will this have this number of chances to answer a problem. To set infinite atttempts, use "0"</span>
</div>
</div>
</div>
</section><!-- .settings-problems-general -->
<section class="settings-problems-assignment-1 settings-extras">
<header>
<h3>[Assignment Type Name]</h3>
</header>
<div class="row row-col2">
<h4 class="label">Problem Randomization:</h4>
<div class="field">
<div class="input input-radio">
<input checked="checked" type="radio" name="course-problems-assignment-1-randomization" id="course-problems-assignment-1-randomization-always" value="Always">
<div class="copy">
<label for="course-problems-assignment-1-randomization-always">Always</label>
<span class="tip tip-stacked"><strong>randomize all</strong> problems</span>
</div>
</div>
<div class="input input-radio">
<input type="radio" name="course-problems-assignment-1-randomization" id="course-problems-assignment-1-randomization-never" value="Never">
<div class="copy">
<label for="course-problems-assignment-1-randomization-never">Never</label>
<span class="tip tip-stacked"><strong>do not randomize</strong> problems</span>
</div>
</div>
<div class="input input-radio">
<input type="radio" name="course-problems-assignment-1-randomization" id="course-problems-assignment-1-randomization-perstudent" value="Per Student">
<div class="copy">
<label for="course-problems-assignment-1-randomization-perstudent">Per Student</label>
<span class="tip tip-stacked">randomize problems <strong>per student</strong></span>
</div>
</div>
</div>
</div>
<div class="row row-col2">
<h4 class="label">Show Answers:</h4>
<div class="field">
<div class="input input-radio">
<input checked="checked" type="radio" name="course-problems-assignment-1-showanswer" id="course-problems-assignment-1-showanswer-always" value="Always">
<div class="copy">
<label for="course-problems-assignment-1-showanswer-always">Always</label>
<span class="tip tip-stacked">Answers will be shown after the number of attempts has been met</span>
</div>
</div>
<div class="input input-radio">
<input type="radio" name="course-problems-assignment-1-showanswer" id="course-problems-assignment-1-showanswer-never" value="Never">
<div class="copy">
<label for="pcourse-roblems-assignment-1-showanswer-never">Never</label>
<span class="tip tip-stacked">Answers will never be shown, regardless of attempts</span>
</div>
</div>
</div>
</div>
<div class="row row-col2">
<label for="course-problems-assignment-1-attempts">Number of Attempts <br /> Allowed on Problems: </label>
<div class="field">
<div class="input">
<input type="text" class="short" id="course-problems-assignment-1-attempts" placeholder="0 or higher" value="0">
<span class="tip tip-stacked">Students will this have this number of chances to answer a problem. To set infinite atttempts, use "0"</span>
</div>
</div>
</div>
</section><!-- .settings-problems-assignment-1 -->
</section><!-- .settings-problems -->
<section class="settings-discussions">
<h2 class="title">Discussions</h2>
<section class="settings-discussions-general">
<header>
<h3>General Settings</h3>
<span class="detail">Course-wide settings for online discussion</span>
</header>
<div class="row row-col2">
<h4 class="label">Anonymous Discussions:</h4>
<div class="field">
<div class="input input-radio">
<input type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-allow" value="Allow">
<div class="copy">
<label for="course-discussions-anonymous-allow">Allow</label>
<span class="tip tip-stacked">Students and faculty <strong>will be able to post anonymously</strong></span>
</div>
</div>
<div class="input input-radio">
<input checked="checked" type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-dontallow" value="Do Not Allow">
<div class="copy">
<label for="course-discussions-anonymous-dontallow">Do not allow</label>
<span class="tip tip-stacked"><strong>Posting anonymously is not allowed</strong>. Any previous anonymous posts <strong>will be reverted to non-anonymous</strong></span>
</div>
</div>
</div>
</div>
<div class="row row-col2">
<h4 class="label">Anonymous Discussions:</h4>
<div class="field">
<div class="input input-radio">
<input checked="checked" type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-allow" value="Allow">
<div class="copy">
<label for="course-discussions-anonymous-allow">Allow</label>
<span class="tip tip-stacked">Students and faculty <strong>will be able to post anonymously</strong></span>
</div>
</div>
<div class="input input-radio">
<input disabled="disabled" type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-dontallow" value="Do Not Allow">
<div class="copy">
<label for="course-discussions-anonymous-dontallow">Do not allow</label>
<span class="tip tip-stacked">This option is disabled since there are previous discussions that are anonymous.</span>
</div>
</div>
</div>
</div>
<div class="row row-col2">
<h4 class="label">Discussion Categories</h4>
<div class="field enum">
<ul class="input-list course-discussions-categories-list sortable">
<li class="input input-existing input-default course-discussions-categories-list-item sortable-item">
<div class="group">
<label for="course-discussions-categories-1-name">Category Name: </label>
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-1-name" placeholder="" value="General" disabled="disabled">
</div>
<a href="#" class="drag-handle"></a>
</li>
<li class="input input-existing input-default course-discussions-categories-list-item sortable-item">
<div class="group">
<label for="course-discussions-categories-2-name">Category Name: </label>
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-2-name" placeholder="" value="Feedback" disabled="disabled">
</div>
<a href="#" class="drag-handle"></a>
</li>
<li class="input input-existing input-default course-discussions-categories-list-item sortable-item">
<div class="group">
<label for="course-discussions-categories-3-name">Category Name: </label>
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-3-name" placeholder="" value="Troubleshooting" disabled="disabled">
</div>
<a href="#" class="drag-handle"></a>
</li>
<li class="input input-existing course-discussions-categories-list-item sortable-item">
<div class="group">
<label for="course-discussions-categories-4-name">Category Name: </label>
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-4-name" placeholder="" value="Study Groups">
<a href="#" class="remove-item remove-course-discussions-categories-data"><span class="delete-icon"></span> Delete Category</a>
</div>
<a href="#" class="drag-handle"></a>
</li>
<li class="input input-existing course-discussions-categories-list-item sortable-item">
<div class="group">
<label for="course-discussions-categories-5-name">Category Name: </label>
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-5-name" placeholder="" value="Lectures">
</div>
<a href="#" class="remove-item remove-course-discussions-categories-data"><span class="delete-icon"></span> Delete Category</a>
<a href="#" class="drag-handle"></a>
</li>
<li class="input input-existing course-discussions-categories-list-item sortable-item">
<div class="group">
<label for="course-discussions-categories-6-name">Category Name: </label>
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-6-name" placeholder="" value="Labs">
</div>
<a href="#" class="remove-item remove-course-discussions-categories-data"><span class="delete-icon"></span> Delete Category</a>
<a href="#" class="drag-handle"></a>
</li>
<li class="input input-existing course-discussions-categories-list-item sortable-item">
<div class="group">
<label for="course-discussions-categories-6-name">Category Name: </label>
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-6-name" placeholder="" value="">
</div>
<a href="#" class="remove-item remove-course-discussions-categories-data"><span class="delete-icon"></span> Delete Category</a>
<a href="#" class="drag-handle"></a>
</li>
</ul>
<a href="#" class="new-item new-course-discussions-categories-item add-categories-data">
<span class="plus-icon"></span>New Discussion Category
</a>
</div>
</div>
</section><!-- .settings-discussions-general -->
</section><!-- .settings-discussions -->
</div>
</article>
</div>
</div>
<footer></footer>
</%block> </%block>
<!-- NOTE not used currently but retained b/c it's yet-to-be-wired functionality -->
<%inherit file="base.html" />
<%block name="title">Schedule and details</%block>
<%block name="bodyclass">is-signedin course settings</%block>
<%namespace name='static' file='static_content.html'/>
<%!
from contentstore import utils
%>
<%block name="jsextra">
<script type="text/javascript" src="${static.url('js/template_loader.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/server_error.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/course_relative.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/validating_view.js')}"></script>
<script type="text/javascript">
$(document).ready(function(){
});
</script>
</%block>
<%block name="content">
<!-- -->
<div class="main-wrapper">
<div class="inner-wrapper">
<h1>Settings</h1>
<article class="settings-overview">
<div class="settings-page-section main-column">
<section class="settings-faculty">
<h2 class="title">Faculty</h2>
<section class="settings-faculty-members">
<header>
<h3>Faculty Members</h3>
<span class="detail">Individuals instructing and help with this course</span>
</header>
<div class="row">
<div class="field enum">
<ul class="input-list course-faculty-list">
<li class="input input-existing multi course-faculty-list-item">
<div class="row row-col2">
<label for="course-faculty-1-firstname">Faculty First Name:</label>
<div class="field">
<input type="text" class="long" id="course-faculty-1-firstname">
</div>
</div>
<div class="row row-col2">
<label for="course-faculty-1-lastname">Faculty Last Name:</label>
<div class="field">
<input type="text" class="long" id="course-faculty-1-lastname">
</div>
</div>
<div class="row row-col2">
<label for="course-faculty-1-photo">Faculty Photo</label>
<div class="field">
<div class="input input-existing">
<div class="current current-faculty-1-photo">
<a href="#" class="remove-item remove-faculty-photo remove-video-data"><span class="delete-icon"></span> Delete Faculty Photo</a>
</div>
</div>
</div>
</div>
<div class="row">
<label for="course-faculty-1-bio">Faculty Bio:</label>
<div class="field">
<textarea class="long tall edit-box tinymce" id="course-faculty-1-bio"></textarea>
<span class="tip tip-stacked">A brief description of your education, experience, and expertise</span>
</div>
</div>
<a href="#" class="remove-item remove-faculty-data"><span class="delete-icon"></span> Delete Faculty Member</a>
</li>
<li class="input multi course-faculty-list-item">
<div class="row row-col2">
<label for="course-faculty-2-firstname">Faculty First Name:</label>
<div class="field">
<input type="text" class="long" id="course-faculty-2-firstname">
</div>
</div>
<div class="row row-col2">
<label for="course-faculty-2-lastname">Faculty Last Name:</label>
<div class="field">
<input type="text" class="long" id="course-faculty-2-lastname">
</div>
</div>
<div class="row row-col2">
<label for="course-faculty-2-photo">Faculty Photo</label>
<div class="field">
<div class="input">
<a href="#" class="new-item new-faculty-photo add-faculty-photo-data" id="course-faculty-2-photo">
<span class="upload-icon"></span>Upload Faculty Photo
</a>
<span class="tip tip-inline">Max size: 30KB</span>
</div>
</div>
</div>
<div class="row">
<label for="course-faculty-2-bio">Faculty Bio:</label>
<div class="field">
<div clas="input">
<textarea class="long tall edit-box tinymce" id="course-faculty-2-bio"></textarea>
<span class="tip tip-stacked">A brief description of your education, experience, and expertise</span>
</div>
</div>
</div>
</li>
</ul>
<a href="#" class="new-item new-course-faculty-item add-faculty-data">
<span class="plus-icon"></span>New Faculty Member
</a>
</div>
</div>
</section>
</section><!-- .settings-staff -->
<section class="settings-problems">
<h2 class="title">Problems</h2>
<section class="settings-problems-general">
<header>
<h3>General Settings</h3>
<span class="detail">Course-wide settings for all problems</span>
</header>
<div class="row row-col2">
<h4 class="label">Problem Randomization:</h4>
<div class="field">
<div class="input input-radio">
<input checked="checked" type="radio" name="course-problems-general-randomization" id="course-problems-general-randomization-always" value="Always">
<div class="copy">
<label for="course-problems-general-randomization-always">Always</label>
<span class="tip tip-stacked"><strong>randomize all</strong> problems</span>
</div>
</div>
<div class="input input-radio">
<input type="radio" name="course-problems-general-randomization" id="course-problems-general-randomization-never" value="Never">
<div class="copy">
<label for="course-problems-general-randomization-never">Never</label>
<span class="tip tip-stacked"><strong>do not randomize</strong> problems</span>
</div>
</div>
<div class="input input-radio">
<input type="radio" name="course-problems-general-randomization" id="course-problems-general-randomization-perstudent" value="Per Student">
<div class="copy">
<label for="course-problems-general-randomization-perstudent">Per Student</label>
<span class="tip tip-stacked">randomize problems <strong>per student</strong></span>
</div>
</div>
</div>
</div>
<div class="row row-col2">
<h4 class="label">Show Answers:</h4>
<div class="field">
<div class="input input-radio">
<input checked="checked" type="radio" name="course-problems-general-showanswer" id="course-problems-general-showanswer-always" value="Always">
<div class="copy">
<label for="course-problems-general-showanswer-always">Always</label>
<span class="tip tip-stacked">Answers will be shown after the number of attempts has been met</span>
</div>
</div>
<div class="input input-radio">
<input type="radio" name="course-problems-general-showanswer" id="course-problems-general-showanswer-never" value="Never">
<div class="copy">
<label for="course-problems-general-showanswer-never">Never</label>
<span class="tip tip-stacked">Answers will never be shown, regardless of attempts</span>
</div>
</div>
</div>
</div>
<div class="row row-col2">
<label for="pcourse-roblems-general-attempts">Number of Attempts <br /> Allowed on Problems: </label>
<div class="field">
<div class="input">
<input type="text" class="short" id="course-problems-general-attempts" placeholder="0 or higher" value="0">
<span class="tip tip-stacked">Students will this have this number of chances to answer a problem. To set infinite atttempts, use "0"</span>
</div>
</div>
</div>
</section><!-- .settings-problems-general -->
<section class="settings-problems-assignment-1 settings-extras">
<header>
<h3>[Assignment Type Name]</h3>
</header>
<div class="row row-col2">
<h4 class="label">Problem Randomization:</h4>
<div class="field">
<div class="input input-radio">
<input checked="checked" type="radio" name="course-problems-assignment-1-randomization" id="course-problems-assignment-1-randomization-always" value="Always">
<div class="copy">
<label for="course-problems-assignment-1-randomization-always">Always</label>
<span class="tip tip-stacked"><strong>randomize all</strong> problems</span>
</div>
</div>
<div class="input input-radio">
<input type="radio" name="course-problems-assignment-1-randomization" id="course-problems-assignment-1-randomization-never" value="Never">
<div class="copy">
<label for="course-problems-assignment-1-randomization-never">Never</label>
<span class="tip tip-stacked"><strong>do not randomize</strong> problems</span>
</div>
</div>
<div class="input input-radio">
<input type="radio" name="course-problems-assignment-1-randomization" id="course-problems-assignment-1-randomization-perstudent" value="Per Student">
<div class="copy">
<label for="course-problems-assignment-1-randomization-perstudent">Per Student</label>
<span class="tip tip-stacked">randomize problems <strong>per student</strong></span>
</div>
</div>
</div>
</div>
<div class="row row-col2">
<h4 class="label">Show Answers:</h4>
<div class="field">
<div class="input input-radio">
<input checked="checked" type="radio" name="course-problems-assignment-1-showanswer" id="course-problems-assignment-1-showanswer-always" value="Always">
<div class="copy">
<label for="course-problems-assignment-1-showanswer-always">Always</label>
<span class="tip tip-stacked">Answers will be shown after the number of attempts has been met</span>
</div>
</div>
<div class="input input-radio">
<input type="radio" name="course-problems-assignment-1-showanswer" id="course-problems-assignment-1-showanswer-never" value="Never">
<div class="copy">
<label for="pcourse-roblems-assignment-1-showanswer-never">Never</label>
<span class="tip tip-stacked">Answers will never be shown, regardless of attempts</span>
</div>
</div>
</div>
</div>
<div class="row row-col2">
<label for="course-problems-assignment-1-attempts">Number of Attempts <br /> Allowed on Problems: </label>
<div class="field">
<div class="input">
<input type="text" class="short" id="course-problems-assignment-1-attempts" placeholder="0 or higher" value="0">
<span class="tip tip-stacked">Students will this have this number of chances to answer a problem. To set infinite atttempts, use "0"</span>
</div>
</div>
</div>
</section><!-- .settings-problems-assignment-1 -->
</section><!-- .settings-problems -->
<section class="settings-discussions">
<h2 class="title">Discussions</h2>
<section class="settings-discussions-general">
<header>
<h3>General Settings</h3>
<span class="detail">Course-wide settings for online discussion</span>
</header>
<div class="row row-col2">
<h4 class="label">Anonymous Discussions:</h4>
<div class="field">
<div class="input input-radio">
<input type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-allow" value="Allow">
<div class="copy">
<label for="course-discussions-anonymous-allow">Allow</label>
<span class="tip tip-stacked">Students and faculty <strong>will be able to post anonymously</strong></span>
</div>
</div>
<div class="input input-radio">
<input checked="checked" type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-dontallow" value="Do Not Allow">
<div class="copy">
<label for="course-discussions-anonymous-dontallow">Do not allow</label>
<span class="tip tip-stacked"><strong>Posting anonymously is not allowed</strong>. Any previous anonymous posts <strong>will be reverted to non-anonymous</strong></span>
</div>
</div>
</div>
</div>
<div class="row row-col2">
<h4 class="label">Anonymous Discussions:</h4>
<div class="field">
<div class="input input-radio">
<input checked="checked" type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-allow" value="Allow">
<div class="copy">
<label for="course-discussions-anonymous-allow">Allow</label>
<span class="tip tip-stacked">Students and faculty <strong>will be able to post anonymously</strong></span>
</div>
</div>
<div class="input input-radio">
<input disabled="disabled" type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-dontallow" value="Do Not Allow">
<div class="copy">
<label for="course-discussions-anonymous-dontallow">Do not allow</label>
<span class="tip tip-stacked">This option is disabled since there are previous discussions that are anonymous.</span>
</div>
</div>
</div>
</div>
<div class="row row-col2">
<h4 class="label">Discussion Categories</h4>
<div class="field enum">
<ul class="input-list course-discussions-categories-list sortable">
<li class="input input-existing input-default course-discussions-categories-list-item sortable-item">
<div class="group">
<label for="course-discussions-categories-1-name">Category Name: </label>
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-1-name" placeholder="" value="General" disabled="disabled">
</div>
<a href="#" class="drag-handle"></a>
</li>
<li class="input input-existing input-default course-discussions-categories-list-item sortable-item">
<div class="group">
<label for="course-discussions-categories-2-name">Category Name: </label>
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-2-name" placeholder="" value="Feedback" disabled="disabled">
</div>
<a href="#" class="drag-handle"></a>
</li>
<li class="input input-existing input-default course-discussions-categories-list-item sortable-item">
<div class="group">
<label for="course-discussions-categories-3-name">Category Name: </label>
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-3-name" placeholder="" value="Troubleshooting" disabled="disabled">
</div>
<a href="#" class="drag-handle"></a>
</li>
<li class="input input-existing course-discussions-categories-list-item sortable-item">
<div class="group">
<label for="course-discussions-categories-4-name">Category Name: </label>
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-4-name" placeholder="" value="Study Groups">
<a href="#" class="remove-item remove-course-discussions-categories-data"><span class="delete-icon"></span> Delete Category</a>
</div>
<a href="#" class="drag-handle"></a>
</li>
<li class="input input-existing course-discussions-categories-list-item sortable-item">
<div class="group">
<label for="course-discussions-categories-5-name">Category Name: </label>
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-5-name" placeholder="" value="Lectures">
</div>
<a href="#" class="remove-item remove-course-discussions-categories-data"><span class="delete-icon"></span> Delete Category</a>
<a href="#" class="drag-handle"></a>
</li>
<li class="input input-existing course-discussions-categories-list-item sortable-item">
<div class="group">
<label for="course-discussions-categories-6-name">Category Name: </label>
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-6-name" placeholder="" value="Labs">
</div>
<a href="#" class="remove-item remove-course-discussions-categories-data"><span class="delete-icon"></span> Delete Category</a>
<a href="#" class="drag-handle"></a>
</li>
<li class="input input-existing course-discussions-categories-list-item sortable-item">
<div class="group">
<label for="course-discussions-categories-6-name">Category Name: </label>
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-6-name" placeholder="" value="">
</div>
<a href="#" class="remove-item remove-course-discussions-categories-data"><span class="delete-icon"></span> Delete Category</a>
<a href="#" class="drag-handle"></a>
</li>
</ul>
<a href="#" class="new-item new-course-discussions-categories-item add-categories-data">
<span class="plus-icon"></span>New Discussion Category
</a>
</div>
</div>
</section><!-- .settings-discussions-general -->
</section><!-- .settings-discussions -->
</div>
</article>
</div>
</div>
<footer></footer>
</%block>
<%inherit file="base.html" />
<%block name="title">Grading</%block>
<%block name="bodyclass">is-signedin course grading settings</%block>
<%namespace name='static' file='static_content.html'/>
<%!
from contentstore import utils
%>
<%block name="jsextra">
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/timepicker/jquery.timepicker.css')}" />
<script src="${static.url('js/vendor/timepicker/jquery.timepicker.js')}"></script>
<script type="text/javascript" src="${static.url('js/template_loader.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/server_error.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/course_relative.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/validating_view.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/settings/course_grading_policy.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/settings/settings_grading_view.js')}"></script>
<script type="text/javascript">
$(document).ready(function(){
$("form :input").focus(function() {
$("label[for='" + this.id + "']").addClass("is-focused");
}).blur(function() {
$("label").removeClass("is-focused");
});
var editor = new CMS.Views.Settings.Grading({
el: $('.settings-grading'),
model : new CMS.Models.Settings.CourseGradingPolicy(${course_details|n},{parse:true})
});
editor.render();
});
</script>
</%block>
<%block name="content">
<div class="wrapper-content wrapper">
<section class="content">
<header class="page">
<span class="title-sub">Settings</span>
<h1 class="title-1">Grading</h1>
</header>
<!-- <div class="introduction">
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.</p>
</div> -->
<article class="content-primary" role="main">
<form id="settings_details" class="settings-grading" method="post" action="">
<section class="group-settings grade-range">
<header>
<h2 class="title-2">Overall Grade Range</h2>
<span class="tip">Your overall grading scale for student final grades</span>
</header>
<ol class="list-input">
<li class="field" id="field-course-grading-range">
<div class="grade-controls course-grading-range well">
<a href="#" class="new-grade-button"><span class="plus-icon"></span></a>
<div class="grade-slider">
<div class="grade-bar">
<ol class="increments">
<li class="increment-0">0</li>
<li class="increment-10">10</li>
<li class="increment-20">20</li>
<li class="increment-30">30</li>
<li class="increment-40">40</li>
<li class="increment-50">50</li>
<li class="increment-60">60</li>
<li class="increment-70">70</li>
<li class="increment-80">80</li>
<li class="increment-90">90</li>
<li class="increment-100">100</li>
</ol>
<ol class="grades">
</ol>
</div>
</div>
</div>
</li>
</ol>
</section>
<hr class="divide" />
<section class="group-settings grade-rules">
<header>
<h2 class="title-2">Grading Rules &amp; Policies</h2>
<span class="tip">Deadlines, requirements, and logistics around grading student work</span>
</header>
<ol class="list-input">
<li class="field text" id="field-course-grading-graceperiod">
<label for="course-grading-graceperiod">Grace Period on Deadline:</label>
<input type="text" class="short time" id="course-grading-graceperiod" value="0:00" placeholder="e.g. 10 minutes">
<span class="tip tip-inline">Leeway on due dates</span>
</li>
</ol>
</section>
<hr class="divide" />
<section class="group-settings assignment-types">
<header>
<h2 class="title-2">Assignment Types</h2>
<span class="tip">Categories and labels for any exercises that are gradable</span>
</header>
<ol class="list-input course-grading-assignment-list enum">
</ol>
<div class="actions">
<a href="#" class="new-button new-course-grading-item add-grading-data">
<span class="plus-icon white"></span>New Assignment Type
</a>
</div>
</section>
</form>
</article>
<aside class="content-supplementary" role="complimentary">
<div class="bit">
<h3 class="title-3">How will these settings be used</h3>
<p>Your grading settings will be used to calculate students grades and performance.</p>
<p>Overall grade range will be used in students' final grades, which are calculated by the weighting you determine for each custom assignment type.</p>
</div>
<div class="bit">
% if context_course:
<% ctx_loc = context_course.location %>
<%! from django.core.urlresolvers import reverse %>
<h3 class="title-3">Other Course Settings</h3>
<nav class="nav-related">
<ul>
<li class="nav-item"><a href="${reverse('contentstore.views.get_course_settings', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Details &amp; Schedule</a></li>
</ul>
</nav>
% endif
</div>
</aside>
</section>
</div>
</%block>
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="title">Sign up</%block> <%block name="title">Sign Up</%block>
<%block name="bodyclass">no-header</%block> <%block name="bodyclass">not-signedin signup</%block>
<%block name="content"> <%block name="content">
<div class="edx-studio-logo-large"></div> <div class="wrapper-content wrapper">
<section class="content">
<article class="sign-up-box"> <header>
<header> <h1 class="title title-1">Sign Up for edX Studio</h1>
<h1>Register for edX studio</h1> <a href="${reverse('login')}" class="action action-signin">Already have a Studio Account? Sign in</a>
</header> </header>
<form id="register_form" method="post">
<div id="register_error" name="register_error"></div> <p class="introduction">Ready to start creating online courses? Sign up below and start creating your first edX course today.</p>
<div class="row">
<label>Email</label> <article class="content-primary" role="main">
<input name="email" type="email"> <form id="register_form" method="post" action="register_post">
</div> <div id="register_error" name="register_error" class="message message-status message-status error">
<div class="row"> </div>
<label>Password</label>
<input name="password" type="password"> <fieldset>
</div> <legend class="sr">Required Information to Sign Up for edX Studio</legend>
<div class="row">
<label>Public Username</label> <ol class="list-input">
<input name="username" type="text"> <li class="field text required" id="field-email">
</div> <label for="email">Email Address</label>
<div class="row"> <input id="email" type="email" name="email" placeholder="e.g. jane.doe@gmail.com" />
<label>Full Name</label> </li>
<input name="name" type="text">
</div> <li class="field text required" id="field-password">
<div class="row"> <label for="password">Password</label>
<div class="split"> <input id="password" type="password" name="password" />
<label>Your Location</label> </li>
<input name="location" type="text">
<li class="field text required" id="field-username">
<label for="username">Public Username</label>
<input id="username" type="text" name="username" placeholder="e.g. janedoe" />
<span class="tip tip-stacked">This will be used in public discussions with your courses and in our edX101 support forums</span>
</li>
<li class="field text required" id="field-name">
<label for="name">Full Name</label>
<input id="name" type="text" name="name" placeholder="e.g. Jane Doe" />
</li>
<li class="field-group">
<div class="field text" id="field-location">
<label for="location">Your Location</label>
<input class="short" id="location" type="text" name="location" />
</div>
<div class="field text" id="field-language">
<label for="language">Preferred Language</label>
<input class="short" id="language" type="text" name="language" />
</div>
</li>
<li class="field checkbox required" id="field-tos">
<input id="tos" name="terms_of_service" type="checkbox" value="true" />
<label for="tos">I agree to the Terms of Service</label>
</li>
</ol>
</fieldset>
<div class="form-actions">
<button type="submit" id="submit" name="submit" class="action action-primary">Create My Account & Start Authoring Courses</button>
</div>
<!-- no honor code for CMS, but need it because we're using the lms student object -->
<input name="honor_code" type="checkbox" value="true" checked="true" hidden="true">
</form>
</article>
<aside class="content-supplementary" role="complimentary">
<h2 class="sr">Common Studio Questions</h2>
<div class="bit">
<h3 class="title-3">Who is Studio for?</h3>
<p>Studio is for anyone that wants to create online courses that leverage the global edX platform. Our users are often faculty members, teaching assistants and course staff, and members of instructional technology groups.</p>
</div>
<div class="bit">
<h3 class="title-3">How technically savvy do I need to be to create courses in Studio?</h3>
<p>Studio is designed to be easy to use by almost anyone familiar with common web-based authoring environments (Wordpress, Moodle, etc.). No programming knowledge is required, but for some of the more advanced features, a technical background would be helpful. As always, we are here to help, so don't hesitate to dive right in.</p>
</div> </div>
<div class="split">
<label>Preferred Language</label> <div class="bit">
<input name="language" type="text"> <h3 class="title-3">I've never authored a course online before. Is there help?</h3>
<p>Absolutely. We have created an online course, edX101, that describes some best practices: from filming video, creating exercises, to the basics of running an online course. Additionally, we're always here to help, just drop us a note.</p>
</div> </div>
</div> </aside>
<div class="row"> </section>
<label class="terms-of-service"> </div>
<input name="terms_of_service" type="checkbox" value="true"> </%block>
I agree to the
<a href="#">Terms of Service</a> <%block name="jsextra">
</label> <script type="text/javascript">
</div> (function() {
<!-- no honor code for CMS, but need it because we're using the lms student object --> $("form :input").focus(function() {
<input name="honor_code" type="checkbox" value="true" checked="true" hidden="true"> $("label[for='" + this.id + "']").addClass("is-focused");
}).blur(function() {
<div class="row form-actions submit"> $("label").removeClass("is-focused");
<input name="submit" type="submit" value="Create My Account" class="create-account-button">
<p class="enrolled">Already enrolled? <a href="/">Log In.</a></p>
</div>
</form>
</article>
<script type="text/javascript">
(function() {
function getCookie(name) {
return $.cookie(name);
}
function postJSON(url, data, callback) {
$.ajax({type:'POST',
url: url,
dataType: 'json',
data: data,
success: callback,
headers : {'X-CSRFToken':getCookie('csrftoken')}
}); });
}
$('form#register_form').submit(function(e) { function getCookie(name) {
e.preventDefault(); return $.cookie(name);
var submit_data = $('#register_form').serialize(); }
postJSON('/create_account', // form validation
submit_data, function postJSON(url, data, callback) {
function(json) { $.ajax({type:'POST',
if(json.success) { url: url,
location.href = "${reverse('index')}"; dataType: 'json',
} else { data: data,
$('#register_error').html(json.value).stop().slideDown(150); success: callback,
headers : {'X-CSRFToken':getCookie('csrftoken')}
});
}
$('form#register_form').submit(function(e) {
e.preventDefault();
var submit_data = $('#register_form').serialize();
postJSON('/create_account',
submit_data,
function(json) {
if(json.success) {
location.href = "${reverse('index')}";
} else {
$('#register_error').html(json.value).stop().addClass('is-shown');
}
} }
} );
); });
}); })(this)
})(this) </script>
</script>
</%block> </%block>
\ No newline at end of file
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%namespace name="units" file="widgets/units.html" /> <%namespace name="units" file="widgets/units.html" />
<%block name="bodyclass">unit</%block> <%block name="title">Individual Unit</%block>
<%block name="title">CMS Unit</%block> <%block name="bodyclass">is-signedin course unit</%block>
<%block name="jsextra"> <%block name="jsextra">
<script type='text/javascript'> <script type='text/javascript'>
$(document).ready(function() { $(document).ready(function() {
......
<%! from django.core.urlresolvers import reverse %>
<div class="wrapper-footer wrapper">
<footer class="primary" role="contentinfo">
<div class="colophon">
<p>&copy; 2013 <a href="http://www.edx.org" rel="external">edX</a>. All rights reserved.</p>
</div>
<nav class="nav-peripheral">
<ol>
<!-- <li class="nav-item nav-peripheral-tos">
<a href="#">Terms of Service</a>
</li>
<li class="nav-item nav-peripheral-pp">
<a href="#">Privacy Policy</a>
</li> -->
<li class="nav-item nav-peripheral-help">
<a href="http://help.edge.edx.org/" rel="external">edX Studio Help</a>
</li>
<li class="nav-item nav-peripheral-contact">
<a href="#">Contact edX</a>
</li>
% if user.is_authenticated():
<!-- add in zendesk/tender feedback form UI -->
% endif
</ol>
</nav>
</footer>
</div>
\ No newline at end of file
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<% active_tab_class = 'active-tab-' + active_tab if active_tab else '' %> <div class="wrapper-header wrapper">
<header class="primary-header ${active_tab_class}"> <header class="primary" role="banner">
<div class="class">
<div class="inner-wrapper">
<div class="left">
% if context_course:
<% ctx_loc = context_course.location %>
<a href="/" class="home"><span class="small-home-icon"></span></a>
<a href="${reverse('course_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" class="class-name">${context_course.display_name}</a>
% endif
</div>
<div class="right"> <div class="wrapper wrapper-left ">
<span class="username">${ user.username }</span> <h1 class="branding"><a href="/">edX Studio</a></h1>
% if user.is_authenticated():
<a href="${reverse('logout')}" class="log-out"><span class="log-out-icon"></span></a> % if context_course:
% else: <% ctx_loc = context_course.location %>
<a href="${reverse('login')}">Log in</a> <div class="info-course">
% endif <h2 class="sr">Current Course:</h2>
<a href="${reverse('course_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">
<span class="course-org">${ctx_loc.org}</span><span class="course-number">${ctx_loc.course}</span>
<span class="course-title" title="${context_course.display_name}">${context_course.display_name}</span>
</a>
</div> </div>
<nav class="nav-course primary nav-dropdown" role="navigation">
<h2 class="sr">PH207x's Navigation:</h2>
<ol>
<li class="nav-item nav-course-courseware">
<h3 class="title"><span class="label-prefix">Course </span>Content <i class="ss-icon ss-symbolicons-block icon-expand">&#x25BE;</i></h3>
<div class="wrapper wrapper-nav-sub">
<div class="nav-sub">
<ul>
<li class="nav-item nav-course-courseware-outline"><a href="${reverse('course_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Outline</a></li>
<li class="nav-item nav-course-courseware-updates"><a href="${reverse('course_info', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Updates</a></li>
<li class="nav-item nav-course-courseware-pages"><a href="${reverse('edit_tabs', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, coursename=ctx_loc.name))}">Static Pages</a></li>
<li class="nav-item nav-course-courseware-uploads"><a href="${reverse('asset_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Files &amp; Uploads</a></li>
</ul>
</div>
</div>
</li>
<li class="nav-item nav-course-settings">
<h3 class="title"><span class="label-prefix">Course </span>Settings <i class="ss-icon ss-symbolicons-block icon-expand">&#x25BE;</i></h3>
<div class="wrapper wrapper-nav-sub">
<div class="nav-sub">
<ul>
<li class="nav-item nav-course-settings-schedule"><a href="${reverse('contentstore.views.get_course_settings', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Schedule &amp; Details</a></li>
<li class="nav-item nav-course-settings-grading"><a href="${reverse('contentstore.views.course_config_graders_page', kwargs={'org' : ctx_loc.org, 'course' : ctx_loc.course, 'name': ctx_loc.name})}">Grading</a></li>
<li class="nav-item nav-course-settings-team"><a href="${reverse('manage_users', kwargs=dict(location=ctx_loc))}">Course Team</a></li>
<!-- <li class="nav-item nav-course-settings-advanced"><a href="${reverse('course_settings', kwargs={'org' : ctx_loc.org, 'course' : ctx_loc.course, 'name': ctx_loc.name})}">Advanced Settings</a></li> -->
</ul>
</div>
</div>
</li>
<li class="nav-item nav-course-tools">
<h3 class="title">Tools <i class="ss-icon ss-symbolicons-block icon-expand">&#x25BE;</i></h3>
<div class="wrapper wrapper-nav-sub">
<div class="nav-sub">
<ul>
<li class="nav-item nav-course-tools-import"><a href="${reverse('import_course', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Import</a></li>
<li class="nav-item nav-course-tools-export"><a href="${reverse('export_course', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Export</a></li>
</ul>
</div>
</div>
</li>
</ol>
</nav>
% endif
</div>
<div class="wrapper wrapper-right">
% if user.is_authenticated():
<nav class="nav-account nav-is-signedin nav-dropdown">
<h2 class="sr">Currently logged in as:</h2>
<ol>
<li class="nav-item nav-account-username">
<a href="#" class="title">
<span class="account-username">
<i class="ss-icon ss-symbolicons-standard icon-user">&#x1F464;</i>
${ user.username }
</span>
<i class="ss-icon ss-symbolicons-block icon-expand">&#x25BE;</i>
</a>
<div class="wrapper wrapper-nav-sub">
<div class="nav-sub">
<ul>
<li class="nav-item nav-account-dashboard"><a href="#">My Courses</a></li>
<li class="nav-item nav-account-help"><a href="http://help.edge.edx.org/" rel="external">Studio Help</a></li>
<li class="nav-item nav-account-signout"><a class="action action-signout" href="${reverse('logout')}">Sign Out</a></li>
</ul>
</div>
</div>
</li>
</ol>
</nav>
% else:
<nav class="nav-not-signedin">
<h2 class="sr">You're not currently signed in</h2>
<ol>
<li class="nav-item nav-not-signedin-hiw">
<a href="#">How Studio Works</a>
</li>
<li class="nav-item nav-not-signedin-help">
<a href="http://help.edge.edx.org/" rel="external">Studio Help</a>
</li>
<li class="nav-item nav-not-signedin-signup">
<a class="action action-signup" href="${reverse('signup')}">Sign Up</a>
</li>
<li class="nav-item nav-not-signedin-signin">
<a class="action action-signin" href="${reverse('login')}">Sign In</a>
</li>
</ol>
</nav>
% endif
</div> </div>
</div> </header>
<nav class="class-nav-bar"> </div>
% if context_course: \ No newline at end of file
<% ctx_loc = context_course.location %>
<ul class="class-nav inner-wrapper">
<li><a href="${reverse('course_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='courseware-tab'>Courseware</a></li>
<li><a href="${reverse('course_info', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='courseinfo-tab'>Course Info</a></li>
<li><a href="${reverse('edit_tabs', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, coursename=ctx_loc.name))}" id='pages-tab'>Pages</a></li>
<li><a href="${reverse('manage_users', kwargs=dict(location=ctx_loc))}" id='users-tab'>Users</a></li>
<li><a href="${reverse('asset_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='assets-tab'>Assets</a></li>
<li><a href="${reverse('course_settings', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='settings-tab'>Settings</a></li>
<li><a href="${reverse('import_course', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='import-tab'>Import</a></li>
<li><a href="${reverse('export_course', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='export-tab'>Export</a></li>
</ul>
% endif
</nav>
</header>
...@@ -42,9 +42,10 @@ urlpatterns = ('', ...@@ -42,9 +42,10 @@ urlpatterns = ('',
'contentstore.views.remove_user', name='remove_user'), 'contentstore.views.remove_user', name='remove_user'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/info/(?P<name>[^/]+)$', 'contentstore.views.course_info', name='course_info'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/info/(?P<name>[^/]+)$', 'contentstore.views.course_info', name='course_info'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course_info/updates/(?P<provided_id>.*)$', 'contentstore.views.course_info_updates', name='course_info'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course_info/updates/(?P<provided_id>.*)$', 'contentstore.views.course_info_updates', name='course_info'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/settings/(?P<name>[^/]+)$', 'contentstore.views.get_course_settings', name='course_settings'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/settings-details/(?P<name>[^/]+)$', 'contentstore.views.get_course_settings', name='course_settings'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/settings/(?P<name>[^/]+)/section/(?P<section>[^/]+).*$', 'contentstore.views.course_settings_updates', name='course_settings'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/settings-grading/(?P<name>[^/]+)$', 'contentstore.views.course_config_graders_page', name='course_settings'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/grades/(?P<name>[^/]+)/(?P<grader_index>.*)$', 'contentstore.views.course_grader_updates', name='course_settings'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/settings-details/(?P<name>[^/]+)/section/(?P<section>[^/]+).*$', 'contentstore.views.course_settings_updates', name='course_settings'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/settings-grading/(?P<name>[^/]+)/(?P<grader_index>.*)$', 'contentstore.views.course_grader_updates', name='course_settings'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/(?P<category>[^/]+)/(?P<name>[^/]+)/gradeas.*$', 'contentstore.views.assignment_type_update', name='assignment_type_update'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/(?P<category>[^/]+)/(?P<name>[^/]+)/gradeas.*$', 'contentstore.views.assignment_type_update', name='assignment_type_update'),
...@@ -76,6 +77,7 @@ urlpatterns = ('', ...@@ -76,6 +77,7 @@ urlpatterns = ('',
# User creation and updating views # User creation and updating views
urlpatterns += ( urlpatterns += (
url(r'^howitworks$', 'contentstore.views.howitworks', name='howitworks'),
url(r'^signup$', 'contentstore.views.signup', name='signup'), url(r'^signup$', 'contentstore.views.signup', name='signup'),
url(r'^create_account$', 'student.views.create_account'), url(r'^create_account$', 'student.views.create_account'),
......
// font-sizing
@function em($pxval, $base: 16) { @function em($pxval, $base: 16) {
@return #{$pxval / $base}em; @return #{$pxval / $base}em;
} }
// Line-height @mixin font-size($sizeValue: 1.6){
font-size: $sizeValue + px;
font-size: ($sizeValue/10) + rem;
}
// line-height
@function lh($amount: 1) { @function lh($amount: 1) {
@return $body-line-height * $amount; @return $body-line-height * $amount;
} }
@mixin hide-text(){ // image-replacement hidden text
text-indent: -9999px; @mixin text-hide() {
text-indent: -100%;
white-space: nowrap;
overflow: hidden;
}
// hidden elems - screenreaders
@mixin text-sr() {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden; overflow: hidden;
display: block; padding: 0;
position: absolute;
width: 1px;
} }
@mixin vertically-and-horizontally-centered ( $height, $width ) { // vertical and horizontal centering
@mixin vertically-and-horizontally-centered ($height, $width) {
left: 50%; left: 50%;
margin-left: -$width / 2; margin-left: -$width / 2;
//margin-top: -$height / 2; //margin-top: -$height / 2;
...@@ -22,3 +42,26 @@ ...@@ -22,3 +42,26 @@
position: absolute; position: absolute;
top: 150px; top: 150px;
} }
// sizing
@mixin size($width: $baseline, $height: $baseline) {
height: $height;
width: $width;
}
@mixin square($size: $baseline) {
@include size($size);
}
// placeholder styling
@mixin placeholder($color) {
:-moz-placeholder {
color: $color;
}
::-webkit-input-placeholder {
color: $color;
}
:-ms-input-placeholder {
color: $color;
}
}
\ No newline at end of file
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