');
@@ -13,7 +13,7 @@ $(document).ready(function() {
// 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)
window.$modalCover = $modalCover;
-
+
// 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)
window.cachetemplates = false;
@@ -31,25 +31,73 @@ $(document).ready(function() {
$modal.bind('click', hideModal);
$modalCover.bind('click', hideModal);
- $('.assets .upload-button').bind('click', showUploadModal);
+ $('.uploads .upload-button').bind('click', showUploadModal);
$('.upload-modal .close-button').bind('click', hideModal);
- $body.on('click', '.embeddable-xml-input', function(){ $(this).select(); });
+ $body.on('click', '.embeddable-xml-input', function () {
+ $(this).select();
+ });
$('.unit .item-actions .delete-button').bind('click', deleteUnit);
$('.new-unit-item').bind('click', createNewUnit);
+ $('body').addClass('js');
+
+ // lean/simple modal
+ $('a[rel*=modal]').leanModal({overlay : 0.80, closeButton: '.action-modal-close' });
+ $('a.action-modal-close').click(function(e){
+ (e).preventDefault();
+ });
+
+ // nav - dropdown related
+ $body.click(function (e) {
+ $('.nav-dropdown .nav-item .wrapper-nav-sub').removeClass('is-shown');
+ $('.nav-dropdown .nav-item .title').removeClass('is-selected');
+ });
+
+ $('.nav-dropdown .nav-item .title').click(function (e) {
+
+ $subnav = $(this).parent().find('.wrapper-nav-sub');
+ $title = $(this).parent().find('.title');
+ e.preventDefault();
+ e.stopPropagation();
+
+ 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');
- }
+ $(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) {
+ $('.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);
@@ -61,7 +109,7 @@ $(document).ready(function() {
// 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);
@@ -75,7 +123,7 @@ $(document).ready(function() {
// import form setup
$('.import .file-input').bind('change', showImportSubmit);
- $('.import .choose-file-button, .import .choose-file-button-inline').bind('click', function(e) {
+ $('.import .choose-file-button, .import .choose-file-button-inline').bind('click', function (e) {
e.preventDefault();
$('.import .file-input').click();
});
@@ -98,12 +146,12 @@ $(document).ready(function() {
$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');
+ $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').on('change', '.start-date, .start-time', function () {
$('.edit-subsection-publish-settings').find('.save-button').show();
});
});
@@ -114,26 +162,26 @@ $(document).ready(function() {
// }
function toggleSections(e) {
- e.preventDefault();
+ e.preventDefault();
- $section = $('.courseware-section');
- sectionCount = $section.length;
- $button = $(this);
- $labelCollapsed = $('
up Collapse All Sections');
- $labelExpanded = $('
down Expand All Sections');
+ $section = $('.courseware-section');
+ sectionCount = $section.length;
+ $button = $(this);
+ $labelCollapsed = $('
up Collapse All Sections');
+ $labelExpanded = $('
down Expand All Sections');
- var buttonLabel = $button.hasClass('is-activated') ? $labelCollapsed : $labelExpanded;
- $button.toggleClass('is-activated').html(buttonLabel);
+ var buttonLabel = $button.hasClass('is-activated') ? $labelCollapsed : $labelExpanded;
+ $button.toggleClass('is-activated').html(buttonLabel);
- if($button.hasClass('is-activated')) {
- $section.addClass('collapsed');
- // 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');
- } else {
- $section.removeClass('collapsed');
- // 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');
- }
+ if ($button.hasClass('is-activated')) {
+ $section.addClass('collapsed');
+ // 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');
+ } else {
+ $section.removeClass('collapsed');
+ // 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');
+ }
}
function editSectionPublishDate(e) {
@@ -143,16 +191,16 @@ function editSectionPublishDate(e) {
$modal.attr('data-id', $(this).attr('data-id'));
$modal.find('.start-date').val($(this).attr('data-date'));
$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('.section-name').html('"' + $(this).closest('.courseware-section').find('.section-name-span').text() + '"');
$modalCover.show();
}
function showImportSubmit(e) {
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();
$('.file-name').html($(this).val().replace('C:\\fakepath\\', ''));
$('.file-name-block').show();
@@ -173,7 +221,7 @@ function syncReleaseDate(e) {
function addPolicyMetadata(e) {
e.preventDefault();
- var template =$('#add-new-policy-element-template > li');
+ var template = $('#add-new-policy-element-template > li');
var newNode = template.clone();
var _parent_el = $(this).parent('ol:.policy-list');
newNode.insertBefore('.add-policy-data');
@@ -195,7 +243,7 @@ function cancelPolicyMetadata(e) {
e.preventDefault();
var $policyElement = $(this).parents('.policy-list-element');
- if(!$policyElement.hasClass('editing')) {
+ if (!$policyElement.hasClass('editing')) {
$policyElement.remove();
} else {
$policyElement.removeClass('new-policy-list-element');
@@ -208,13 +256,13 @@ function cancelPolicyMetadata(e) {
function removePolicyMetadata(e) {
e.preventDefault();
- if(!confirm('Are you sure you wish to delete this item. It cannot be reversed!'))
- return;
-
+ if (!confirm('Are you sure you wish to delete this item. It cannot be reversed!'))
+ return;
+
policy_name = $(this).data('policy-name');
var _parent_el = $(this).parent('li:.policy-list-element');
if ($(_parent_el).hasClass("new-policy-list-element")) {
- _parent_el.remove();
+ _parent_el.remove();
} else {
_parent_el.appendTo("#policy-to-delete");
}
@@ -225,7 +273,7 @@ function getEdxTimeFromDateTimeVals(date_val, time_val, format) {
var edxTimeStr = null;
if (date_val != '') {
- if (time_val == '')
+ if (time_val == '')
time_val = '00:00';
// Note, we are using date.js utility which has better parsing abilities than the built in JS date parsing
@@ -240,30 +288,30 @@ function getEdxTimeFromDateTimeVals(date_val, time_val, format) {
}
function getEdxTimeFromDateTimeInputs(date_id, time_id, format) {
- var input_date = $('#'+date_id).val();
- var input_time = $('#'+time_id).val();
+ var input_date = $('#' + date_id).val();
+ var input_time = $('#' + time_id).val();
return getEdxTimeFromDateTimeVals(input_date, input_time, format);
}
function checkForNewValue(e) {
- if($(this).parents('.new-policy-list-element')[0]) {
+ if ($(this).parents('.new-policy-list-element')[0]) {
return;
}
- if(this.val) {
- this.hasChanged = this.val != $(this).val();
+ if (this.val) {
+ this.hasChanged = this.val != $(this).val();
} else {
this.hasChanged = false;
}
this.val = $(this).val();
- if(this.hasChanged) {
- if(this.saveTimer) {
+ if (this.hasChanged) {
+ if (this.saveTimer) {
clearTimeout(this.saveTimer);
}
- this.saveTimer = setTimeout(function() {
+ this.saveTimer = setTimeout(function () {
$changedInput = $(e.target);
saveSubsection();
this.saveTimer = null;
@@ -272,11 +320,11 @@ function checkForNewValue(e) {
}
function autosaveInput(e) {
- if(this.saveTimer) {
+ if (this.saveTimer) {
clearTimeout(this.saveTimer);
}
- this.saveTimer = setTimeout(function() {
+ this.saveTimer = setTimeout(function () {
$changedInput = $(e.target);
saveSubsection();
this.saveTimer = null;
@@ -284,7 +332,7 @@ function autosaveInput(e) {
}
function saveSubsection() {
- if($changedInput && !$changedInput.hasClass('no-spinner')) {
+ if ($changedInput && !$changedInput.hasClass('no-spinner')) {
$spinner.css({
'position': 'absolute',
'top': Math.floor($changedInput.position().top + ($changedInput.outerHeight() / 2) + 3),
@@ -294,30 +342,30 @@ function saveSubsection() {
$changedInput.after($spinner);
$spinner.show();
}
-
+
var id = $('.subsection-body').data('id');
// pull all 'normalized' metadata editable fields on page
var metadata_fields = $('input[data-metadata-name]');
-
+
var metadata = {};
- for(var i=0; i< metadata_fields.length;i++) {
- var el = metadata_fields[i];
- metadata[$(el).data("metadata-name")] = el.value;
- }
+ for (var i = 0; i < metadata_fields.length; i++) {
+ 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) {
+ $('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
// 'null' presented to the server means 'remove'
- $("#policy-to-delete > li.policy-list-element").each(function(i, element) {
+ $("#policy-to-delete > li.policy-list-element").each(function (i, element) {
var name = $(element).children('.policy-list-name').val();
if (name != "")
- metadata[name] = null;
+ metadata[name] = null;
});
// Piece back together the date/time UI elements into one date/time string
@@ -327,18 +375,18 @@ function saveSubsection() {
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() {
+ 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() {
+ },
+ error: function () {
showToastMessage('There has been an error while saving your changes.');
- }
- });
+ }
+ });
}
@@ -349,14 +397,14 @@ function createNewUnit(e) {
template = $(this).data('template');
$.post('/clone_item',
- {'parent_location' : parent,
- 'template' : template,
- 'display_name': 'New Unit'
- },
- function(data) {
- // redirect to the edit page
- window.location = "/edit/" + data['id'];
- });
+ {'parent_location': parent,
+ 'template': template,
+ 'display_name': 'New Unit'
+ },
+ function (data) {
+ // redirect to the edit page
+ window.location = "/edit/" + data['id'];
+ });
}
function deleteUnit(e) {
@@ -375,16 +423,16 @@ function deleteSection(e) {
}
function _deleteItem($el) {
- if(!confirm('Are you sure you wish to delete this item. It cannot be reversed!'))
- return;
-
+ if (!confirm('Are you sure you wish to delete this item. It cannot be reversed!'))
+ return;
+
var id = $el.data('id');
-
- $.post('/delete_item',
- {'id': id, 'delete_children' : true, 'delete_all_versions' : true},
- function(data) {
- $el.remove();
- });
+
+ $.post('/delete_item',
+ {'id': id, 'delete_children': true, 'delete_all_versions': true},
+ function (data) {
+ $el.remove();
+ });
}
function showUploadModal(e) {
@@ -411,7 +459,7 @@ function startUpload(e) {
$('.upload-modal .progress-bar').removeClass('loaded').show();
}
-function resetUploadBar(){
+function resetUploadBar() {
var percentVal = '0%';
$('.upload-modal .progress-fill').width(percentVal);
$('.upload-modal .progress-fill').html(percentVal);
@@ -424,7 +472,7 @@ function showUploadFeedback(event, position, total, percentComplete) {
}
function displayFinishedUpload(xhr) {
- if(xhr.status = 200){
+ if (xhr.status = 200) {
markAsLoaded();
}
@@ -448,10 +496,10 @@ function displayFinishedUpload(xhr) {
function markAsLoaded() {
$('.upload-modal .copy-button').css('display', 'inline-block');
$('.upload-modal .progress-bar').addClass('loaded');
-}
+}
function hideModal(e) {
- if(e) {
+ if (e) {
e.preventDefault();
}
// Unit editors do not want the modal cover to hide when users click outside
@@ -465,7 +513,7 @@ function hideModal(e) {
}
function onKeyUp(e) {
- if(e.which == 87) {
+ if (e.which == 87) {
$body.toggleClass('show-wip hide-wip');
}
}
@@ -515,14 +563,14 @@ function showToastMessage(message, $button, lifespan) {
var $content = $('
');
$content.html(message);
$toast.append($content);
- if($button) {
+ if ($button) {
$button.addClass('action-button');
$button.bind('click', hideToastMessage);
$content.append($button);
}
$closeBtn.bind('click', hideToastMessage);
- if($('.toast-notification')[0]) {
+ if ($('.toast-notification')[0]) {
var targetY = $('.toast-notification').offset().top + $('.toast-notification').outerHeight();
$toast.css('top', (targetY + 10) + 'px');
}
@@ -530,8 +578,8 @@ function showToastMessage(message, $button, lifespan) {
$body.prepend($toast);
$toast.fadeIn(200);
- if(lifespan) {
- $toast.timer = setTimeout(function() {
+ if (lifespan) {
+ $toast.timer = setTimeout(function () {
$toast.fadeOut(300);
}, lifespan * 1000);
}
@@ -557,7 +605,7 @@ function addNewSection(e, isTemplate) {
}
function checkForCancel(e) {
- if(e.which == 27) {
+ if (e.which == 27) {
$body.unbind('keyup', checkForCancel);
e.data.$cancelButton.click();
}
@@ -573,11 +621,11 @@ function saveNewSection(e) {
var display_name = $(this).find('.new-section-name').val();
$.post('/clone_item', {
- 'parent_location' : parent,
- 'template' : template,
+ 'parent_location': parent,
+ 'template': template,
'display_name': display_name,
},
- function(data) {
+ function (data) {
if (data.id != undefined)
location.reload();
}
@@ -596,7 +644,7 @@ function addNewCourse(e) {
$(e.target).hide();
var $newCourse = $($('#new-course-template').html());
var $cancelButton = $newCourse.find('.new-course-cancel');
- $('.new-course-button').after($newCourse);
+ $('.inner-wrapper').prepend($newCourse);
$newCourse.find('.new-course-name').focus().select();
$newCourse.find('form').bind('submit', saveNewCourse);
$cancelButton.bind('click', cancelNewCourse);
@@ -612,18 +660,18 @@ function saveNewCourse(e) {
var number = $newCourse.find('.new-course-number').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.');
return;
}
$.post('/create_new_course', {
- 'template' : template,
- 'org' : org,
- 'number' : number,
- 'display_name': display_name
+ 'template': template,
+ 'org': org,
+ 'number': number,
+ 'display_name': display_name
},
- function(data) {
+ function (data) {
if (data.id != undefined) {
window.location = '/' + data.id.replace(/.*:\/\//, '');
} else if (data.ErrMsg != undefined) {
@@ -667,13 +715,13 @@ function saveNewSubsection(e) {
var display_name = $(this).find('.new-subsection-name-input').val();
$.post('/clone_item', {
- 'parent_location' : parent,
- 'template' : template,
- 'display_name': display_name
+ 'parent_location': parent,
+ 'template': template,
+ 'display_name': display_name
},
- function(data) {
+ function (data) {
if (data.id != undefined) {
- location.reload();
+ location.reload();
}
}
);
@@ -720,21 +768,20 @@ function saveEditSectionName(e) {
}
var $_this = $(this);
- // call into server to commit the new order
+ // call into server to commit the new order
$.ajax({
url: "/save_item",
type: "POST",
dataType: "json",
contentType: "application/json",
- data:JSON.stringify({ 'id' : id, 'metadata' : {'display_name' : display_name}})
- }).success(function()
- {
- $spinner.delay(250).fadeOut(250);
- $_this.closest('h3').find('.section-name-span').html(display_name).show();
- $_this.hide();
- $_this.closest('.section-name').bind('click', editSectionName);
- e.stopPropagation();
- });
+ data: JSON.stringify({ 'id': id, 'metadata': {'display_name': display_name}})
+ }).success(function () {
+ $spinner.delay(250).fadeOut(250);
+ $_this.closest('h3').find('.section-name-span').html(display_name).show();
+ $_this.hide();
+ $_this.closest('.section-name').bind('click', editSectionName);
+ e.stopPropagation();
+ });
}
function setSectionScheduleDate(e) {
@@ -765,21 +812,20 @@ function saveSetSectionScheduleDate(e) {
type: "POST",
dataType: "json",
contentType: "application/json",
- data:JSON.stringify({ 'id' : id, 'metadata' : {'start' : start}})
- }).success(function()
- {
- var $thisSection = $('.courseware-section[data-id="' + id + '"]');
- $thisSection.find('.section-published-date').html('
Will Release: ' + input_date + ' at ' + input_time + 'Edit');
- $thisSection.find('.section-published-date').animate({
- 'background-color': 'rgb(182,37,104)'
- }, 300).animate({
- 'background-color': '#edf1f5'
- }, 300).animate({
- 'background-color': 'rgb(182,37,104)'
- }, 300).animate({
- 'background-color': '#edf1f5'
- }, 300);
-
- hideModal();
- });
-}
+ data: JSON.stringify({ 'id': id, 'metadata': {'start': start}})
+ }).success(function () {
+ var $thisSection = $('.courseware-section[data-id="' + id + '"]');
+ $thisSection.find('.section-published-date').html('
Will Release: ' + input_date + ' at ' + input_time + 'Edit');
+ $thisSection.find('.section-published-date').animate({
+ 'background-color': 'rgb(182,37,104)'
+ }, 300).animate({
+ 'background-color': '#edf1f5'
+ }, 300).animate({
+ 'background-color': 'rgb(182,37,104)'
+ }, 300).animate({
+ 'background-color': '#edf1f5'
+ }, 300);
+
+ hideModal();
+ });
+}
\ No newline at end of file
diff --git a/cms/static/js/models/settings/course_details.js b/cms/static/js/models/settings/course_details.js
index bdbb46b3b1..168cb960be 100644
--- a/cms/static/js/models/settings/course_details.js
+++ b/cms/static/js/models/settings/course_details.js
@@ -1,85 +1,83 @@
if (!CMS.Models['Settings']) CMS.Models.Settings = new Object();
CMS.Models.Settings.CourseDetails = Backbone.Model.extend({
- defaults: {
- location : null, // the course's Location model, required
- start_date: null, // maps to 'start'
- end_date: null, // maps to 'end'
- enrollment_start: null,
- enrollment_end: null,
- syllabus: null,
- overview: "",
- intro_video: 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)
- parse: function(attributes) {
- if (attributes['course_location']) {
- attributes.location = new CMS.Models.Location(attributes.course_location, {parse:true});
- }
- if (attributes['start_date']) {
- attributes.start_date = new Date(attributes.start_date);
- }
- if (attributes['end_date']) {
- attributes.end_date = new Date(attributes.end_date);
- }
- if (attributes['enrollment_start']) {
- attributes.enrollment_start = new Date(attributes.enrollment_start);
- }
- if (attributes['enrollment_end']) {
- attributes.enrollment_end = new Date(attributes.enrollment_end);
- }
- return attributes;
- },
-
- validate: function(newattrs) {
- // 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.
- var errors = {};
- 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.";
- }
- 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.";
- }
- 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.";
- }
- 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.";
- }
- if (newattrs.intro_video && newattrs.intro_video !== this.get('intro_video')) {
- if (this._videokey_illegal_chars.exec(newattrs.intro_video)) {
- errors.intro_video = "Key should only contain letters, numbers, _, or -";
- }
- // TODO check if key points to a real video using google's youtube api
- }
- if (!_.isEmpty(errors)) return errors;
- // NOTE don't return empty errors as that will be interpreted as an error state
- },
-
- url: function() {
- var location = this.get('location');
- return '/' + location.get('org') + "/" + location.get('course') + '/settings/' + location.get('name') + '/section/details';
- },
-
- _videokey_illegal_chars : /[^a-zA-Z0-9_-]/g,
- save_videosource: function(newsource) {
- // newsource either is
or just the "speed:key, *" string
- // 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},
- { error : CMS.ServerError});
- // TODO remove all whitespace w/in string
- else {
- if (this.get('intro_video') !== newsource) this.save('intro_video', newsource,
- { error : CMS.ServerError});
- }
-
- return this.videosourceSample();
- },
- videosourceSample : function() {
- if (this.has('intro_video')) return "http://www.youtube.com/embed/" + this.get('intro_video');
- else return "";
- }
+ defaults: {
+ location : null, // the course's Location model, required
+ start_date: null, // maps to 'start'
+ end_date: null, // maps to 'end'
+ enrollment_start: null,
+ enrollment_end: null,
+ syllabus: null,
+ overview: "",
+ intro_video: 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)
+ parse: function(attributes) {
+ if (attributes['course_location']) {
+ attributes.location = new CMS.Models.Location(attributes.course_location, {parse:true});
+ }
+ if (attributes['start_date']) {
+ attributes.start_date = new Date(attributes.start_date);
+ }
+ if (attributes['end_date']) {
+ attributes.end_date = new Date(attributes.end_date);
+ }
+ if (attributes['enrollment_start']) {
+ attributes.enrollment_start = new Date(attributes.enrollment_start);
+ }
+ if (attributes['enrollment_end']) {
+ attributes.enrollment_end = new Date(attributes.enrollment_end);
+ }
+ return attributes;
+ },
+
+ validate: function(newattrs) {
+ // 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.
+ var errors = {};
+ 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.";
+ }
+ 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.";
+ }
+ 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.";
+ }
+ 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.";
+ }
+ if (newattrs.intro_video && newattrs.intro_video !== this.get('intro_video')) {
+ if (this._videokey_illegal_chars.exec(newattrs.intro_video)) {
+ errors.intro_video = "Key should only contain letters, numbers, _, or -";
+ }
+ // TODO check if key points to a real video using google's youtube api
+ }
+ if (!_.isEmpty(errors)) return errors;
+ // NOTE don't return empty errors as that will be interpreted as an error state
+ },
+
+ url: function() {
+ var location = this.get('location');
+ return '/' + location.get('org') + "/" + location.get('course') + '/settings-details/' + location.get('name') + '/section/details';
+ },
+
+ _videokey_illegal_chars : /[^a-zA-Z0-9_-]/g,
+ save_videosource: function(newsource) {
+ // newsource either is
or just the "speed:key, *" string
+ // 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.set({'intro_video': null});
+ // TODO remove all whitespace w/in string
+ else {
+ if (this.get('intro_video') !== newsource) this.set('intro_video', newsource);
+ }
+
+ return this.videosourceSample();
+ },
+ videosourceSample : function() {
+ if (this.has('intro_video')) return "http://www.youtube.com/embed/" + this.get('intro_video');
+ else return "";
+ }
});
diff --git a/cms/static/js/models/settings/course_grading_policy.js b/cms/static/js/models/settings/course_grading_policy.js
index cce4e0207d..3f8b1bf29a 100644
--- a/cms/static/js/models/settings/course_grading_policy.js
+++ b/cms/static/js/models/settings/course_grading_policy.js
@@ -1,55 +1,56 @@
if (!CMS.Models['Settings']) CMS.Models.Settings = new Object();
CMS.Models.Settings.CourseGradingPolicy = Backbone.Model.extend({
- defaults : {
- course_location : null,
- graders : null, // CourseGraderCollection
- grade_cutoffs : null, // CourseGradeCutoff model
+ defaults : {
+ course_location : null,
+ graders : null, // CourseGraderCollection
+ grade_cutoffs : null, // CourseGradeCutoff model
grace_period : null // either null or { hours: n, minutes: m, ...}
- },
- parse: function(attributes) {
- if (attributes['course_location']) {
- attributes.course_location = new CMS.Models.Location(attributes.course_location, {parse:true});
- }
- if (attributes['graders']) {
- var graderCollection;
- if (this.has('graders')) {
- graderCollection = this.get('graders');
- graderCollection.reset(attributes.graders);
- }
- else {
- graderCollection = new CMS.Models.Settings.CourseGraderCollection(attributes.graders);
- graderCollection.course_location = attributes['course_location'] || this.get('course_location');
- }
- attributes.graders = graderCollection;
- }
- return attributes;
- },
- url : function() {
- var location = this.get('course_location');
- return '/' + location.get('org') + "/" + location.get('course') + '/settings/' + location.get('name') + '/section/grading';
- },
- gracePeriodToDate : function() {
- var newDate = new Date();
- if (this.has('grace_period') && this.get('grace_period')['hours'])
- newDate.setHours(this.get('grace_period')['hours']);
- else newDate.setHours(0);
- if (this.has('grace_period') && this.get('grace_period')['minutes'])
- newDate.setMinutes(this.get('grace_period')['minutes']);
- else newDate.setMinutes(0);
- if (this.has('grace_period') && this.get('grace_period')['seconds'])
- newDate.setSeconds(this.get('grace_period')['seconds']);
- else newDate.setSeconds(0);
-
- return newDate;
- },
- dateToGracePeriod : function(date) {
- return {hours : date.getHours(), minutes : date.getMinutes(), seconds : date.getSeconds() };
- }
+ },
+ parse: function(attributes) {
+ if (attributes['course_location']) {
+ attributes.course_location = new CMS.Models.Location(attributes.course_location, {parse:true});
+ }
+ if (attributes['graders']) {
+ var graderCollection;
+ // interesting race condition: if {parse:true} when newing, then parse called before .attributes created
+ if (this.attributes && this.has('graders')) {
+ graderCollection = this.get('graders');
+ graderCollection.reset(attributes.graders);
+ }
+ else {
+ graderCollection = new CMS.Models.Settings.CourseGraderCollection(attributes.graders);
+ graderCollection.course_location = attributes['course_location'] || this.get('course_location');
+ }
+ attributes.graders = graderCollection;
+ }
+ return attributes;
+ },
+ url : function() {
+ 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();
+ if (this.has('grace_period') && this.get('grace_period')['hours'])
+ newDate.setHours(this.get('grace_period')['hours']);
+ else newDate.setHours(0);
+ if (this.has('grace_period') && this.get('grace_period')['minutes'])
+ newDate.setMinutes(this.get('grace_period')['minutes']);
+ else newDate.setMinutes(0);
+ if (this.has('grace_period') && this.get('grace_period')['seconds'])
+ newDate.setSeconds(this.get('grace_period')['seconds']);
+ else newDate.setSeconds(0);
+
+ return newDate;
+ },
+ dateToGracePeriod : function(date) {
+ return {hours : date.getHours(), minutes : date.getMinutes(), seconds : date.getSeconds() };
+ }
});
CMS.Models.Settings.CourseGrader = Backbone.Model.extend({
- defaults: {
+ defaults: {
"type" : "", // must be unique w/in collection (ie. w/in course)
"min_count" : 1,
"drop_count" : 0,
@@ -57,71 +58,71 @@ CMS.Models.Settings.CourseGrader = Backbone.Model.extend({
"weight" : 0 // int 0..100
},
parse : function(attrs) {
- if (attrs['weight']) {
- if (!_.isNumber(attrs.weight)) attrs.weight = parseInt(attrs.weight);
- }
- if (attrs['min_count']) {
- if (!_.isNumber(attrs.min_count)) attrs.min_count = parseInt(attrs.min_count);
- }
- if (attrs['drop_count']) {
- if (!_.isNumber(attrs.drop_count)) attrs.drop_count = parseInt(attrs.drop_count);
- }
- return attrs;
+ if (attrs['weight']) {
+ if (!_.isNumber(attrs.weight)) attrs.weight = parseInt(attrs.weight);
+ }
+ if (attrs['min_count']) {
+ if (!_.isNumber(attrs.min_count)) attrs.min_count = parseInt(attrs.min_count);
+ }
+ if (attrs['drop_count']) {
+ if (!_.isNumber(attrs.drop_count)) attrs.drop_count = parseInt(attrs.drop_count);
+ }
+ return attrs;
},
validate : function(attrs) {
- var errors = {};
- if (attrs['type']) {
- if (_.isEmpty(attrs['type'])) {
- errors.type = "The assignment type must have a name.";
- }
- else {
- // 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);
- if (existing) {
- errors.type = "There's already another assignment type with this name.";
- }
- }
- }
- if (attrs['weight']) {
- if (!isFinite(attrs.weight) || /\D+/.test(attrs.weight)) {
- errors.weight = "Please enter an integer between 0 and 100.";
- }
- else {
- attrs.weight = parseInt(attrs.weight); // see if this ensures value saved is int
- if (this.collection && attrs.weight > 0) {
- // 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
- // or figure out a wholistic way to balance the vals across the whole
-// if ((this.collection.sumWeights() + attrs.weight - this.get('weight')) > 100)
-// errors.weight = "The weights cannot add to more than 100.";
- }
- }}
- if (attrs['min_count']) {
- if (!isFinite(attrs.min_count) || /\D+/.test(attrs.min_count)) {
- errors.min_count = "Please enter an integer.";
- }
- else attrs.min_count = parseInt(attrs.min_count);
- }
- if (attrs['drop_count']) {
- if (!isFinite(attrs.drop_count) || /\D+/.test(attrs.drop_count)) {
- errors.drop_count = "Please enter an integer.";
- }
- else attrs.drop_count = parseInt(attrs.drop_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.";
- }
- if (!_.isEmpty(errors)) return errors;
+ var errors = {};
+ if (attrs['type']) {
+ if (_.isEmpty(attrs['type'])) {
+ errors.type = "The assignment type must have a name.";
+ }
+ else {
+ // 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);
+ if (existing) {
+ errors.type = "There's already another assignment type with this name.";
+ }
+ }
+ }
+ if (attrs['weight']) {
+ if (!isFinite(attrs.weight) || /\D+/.test(attrs.weight)) {
+ errors.weight = "Please enter an integer between 0 and 100.";
+ }
+ else {
+ attrs.weight = parseInt(attrs.weight); // see if this ensures value saved is int
+ if (this.collection && attrs.weight > 0) {
+ // 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
+ // or figure out a wholistic way to balance the vals across the whole
+// if ((this.collection.sumWeights() + attrs.weight - this.get('weight')) > 100)
+// errors.weight = "The weights cannot add to more than 100.";
+ }
+ }}
+ if (attrs['min_count']) {
+ if (!isFinite(attrs.min_count) || /\D+/.test(attrs.min_count)) {
+ errors.min_count = "Please enter an integer.";
+ }
+ else attrs.min_count = parseInt(attrs.min_count);
+ }
+ if (attrs['drop_count']) {
+ if (!isFinite(attrs.drop_count) || /\D+/.test(attrs.drop_count)) {
+ errors.drop_count = "Please enter an integer.";
+ }
+ else attrs.drop_count = parseInt(attrs.drop_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.";
+ }
+ if (!_.isEmpty(errors)) return errors;
}
});
CMS.Models.Settings.CourseGraderCollection = Backbone.Collection.extend({
- model : CMS.Models.Settings.CourseGrader,
- course_location : null, // must be set to a Location object
- url : function() {
- return '/' + this.course_location.get('org') + "/" + this.course_location.get('course') + '/grades/' + this.course_location.get('name') + '/';
- },
- sumWeights : function() {
- return this.reduce(function(subtotal, grader) { return subtotal + grader.get('weight'); }, 0);
- }
+ model : CMS.Models.Settings.CourseGrader,
+ course_location : null, // must be set to a Location object
+ url : function() {
+ return '/' + this.course_location.get('org') + "/" + this.course_location.get('course') + '/settings-grading/' + this.course_location.get('name') + '/';
+ },
+ sumWeights : function() {
+ return this.reduce(function(subtotal, grader) { return subtotal + grader.get('weight'); }, 0);
+ }
});
\ No newline at end of file
diff --git a/cms/static/js/models/settings/course_settings.js b/cms/static/js/models/settings/course_settings.js
index 9d09e4bdc5..62b214e853 100644
--- a/cms/static/js/models/settings/course_settings.js
+++ b/cms/static/js/models/settings/course_settings.js
@@ -1,43 +1,42 @@
if (!CMS.Models['Settings']) CMS.Models.Settings = new Object();
CMS.Models.Settings.CourseSettings = Backbone.Model.extend({
- // a container for the models representing the n possible tabbed states
- defaults: {
- courseLocation: null,
- // NOTE: keep these sync'd w/ the data-section names in settings-page-menu
- details: null,
- faculty: null,
- grading: null,
- problems: null,
- discussions: null
- },
+ // a container for the models representing the n possible tabbed states
+ defaults: {
+ courseLocation: null,
+ details: null,
+ faculty: null,
+ grading: null,
+ problems: null,
+ discussions: null
+ },
- retrieve: function(submodel, callback) {
- if (this.get(submodel)) callback();
- else {
- var cachethis = this;
- switch (submodel) {
- case 'details':
- var details = new CMS.Models.Settings.CourseDetails({location: this.get('courseLocation')});
- details.fetch( {
- success : function(model) {
- cachethis.set('details', model);
- callback(model);
- }
- });
- break;
- case 'grading':
- var grading = new CMS.Models.Settings.CourseGradingPolicy({course_location: this.get('courseLocation')});
- grading.fetch( {
- success : function(model) {
- cachethis.set('grading', model);
- callback(model);
- }
- });
- break;
+ retrieve: function(submodel, callback) {
+ if (this.get(submodel)) callback();
+ else {
+ var cachethis = this;
+ switch (submodel) {
+ case 'details':
+ var details = new CMS.Models.Settings.CourseDetails({location: this.get('courseLocation')});
+ details.fetch( {
+ success : function(model) {
+ cachethis.set('details', model);
+ callback(model);
+ }
+ });
+ break;
+ case 'grading':
+ var grading = new CMS.Models.Settings.CourseGradingPolicy({course_location: this.get('courseLocation')});
+ grading.fetch( {
+ success : function(model) {
+ cachethis.set('grading', model);
+ callback(model);
+ }
+ });
+ break;
- default:
- break;
- }
- }
- }
+ default:
+ break;
+ }
+ }
+ }
})
\ No newline at end of file
diff --git a/cms/static/js/views/course_info_edit.js b/cms/static/js/views/course_info_edit.js
index cb396b2a7f..277a15b57c 100644
--- a/cms/static/js/views/course_info_edit.js
+++ b/cms/static/js/views/course_info_edit.js
@@ -10,7 +10,7 @@ CMS.Views.CourseInfoEdit = Backbone.View.extend({
render: function() {
// instantiate the ClassInfoUpdateView and delegate the proper dom to it
new CMS.Views.ClassInfoUpdateView({
- el: this.$('#course-update-view'),
+ el: $('body.updates'),
collection: this.model.get('updates')
});
@@ -27,10 +27,10 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
// collection is CourseUpdateCollection
events: {
"click .new-update-button" : "onNew",
- "click .save-button" : "onSave",
- "click .cancel-button" : "onCancel",
- "click .edit-button" : "onEdit",
- "click .delete-button" : "onDelete"
+ "click #course-update-view .save-button" : "onSave",
+ "click #course-update-view .cancel-button" : "onCancel",
+ "click .post-actions > .edit-button" : "onEdit",
+ "click .post-actions > .delete-button" : "onDelete"
},
initialize: function() {
diff --git a/cms/static/js/views/settings/main_settings_view.js b/cms/static/js/views/settings/main_settings_view.js
index f4c7df41a6..7a2ad914eb 100644
--- a/cms/static/js/views/settings/main_settings_view.js
+++ b/cms/static/js/views/settings/main_settings_view.js
@@ -1,222 +1,91 @@
-if (!CMS.Views['Settings']) CMS.Views.Settings = {};
-
-// TODO move to common place
-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('
<%= message %>'),
-
- 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 is object w/ fields and error strings
- for (var field in error) {
- var ele = this.$el.find('#' + this.fieldToSelectorMap[field]);
- 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();
- 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({
- // Model class is CMS.Models.Settings.CourseSettings
- // allow navigation between the tabs
- events: {
- 'click .settings-page-menu a': "showSettingsTab",
- 'mouseover #timezone' : "updateTime"
- },
-
- currentTab: null,
- subviews: {}, // indexed by tab name
-
- initialize: function() {
- // load templates
- 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();
- }
-
-});
+if (!CMS.Views['Settings']) CMS.Views.Settings = {}; // ensure the pseudo pkg exists
CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
- // Model class is CMS.Models.Settings.CourseDetails
- events : {
- "blur input" : "updateModel",
- "blur textarea" : "updateModel",
- 'click .remove-course-syllabus' : "removeSyllabus",
- 'click .new-course-syllabus' : 'assetSyllabus',
- 'click .remove-course-introduction-video' : "removeVideo",
- 'focus #course-overview' : "codeMirrorize"
- },
- initialize : function() {
- // TODO move the html frag to a loaded asset
- this.fileAnchorTemplate = _.template('
📄<%= filename %>');
- this.model.on('error', this.handleValidationError, this);
- this.selectorToField = _.invert(this.fieldToSelectorMap);
- },
-
- render: function() {
- this.setupDatePicker('start_date');
- this.setupDatePicker('end_date');
- this.setupDatePicker('enrollment_start');
- 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"
- },
+ // Model class is CMS.Models.Settings.CourseDetails
+ events : {
+ "blur input" : "updateModel",
+ "blur textarea" : "updateModel",
+ 'click .remove-course-syllabus' : "removeSyllabus",
+ 'click .new-course-syllabus' : 'assetSyllabus',
+ 'click .remove-course-introduction-video' : "removeVideo",
+ 'focus #course-overview' : "codeMirrorize",
+ 'mouseover #timezone' : "updateTime",
+ // 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.fileAnchorTemplate = _.template('
📄<%= filename %>');
+ // fill in fields
+ this.$el.find("#course-name").val(this.model.get('location').get('name'));
+ this.$el.find("#course-organization").val(this.model.get('location').get('org'));
+ this.$el.find("#course-number").val(this.model.get('location').get('course'));
+ this.$el.find('.set-date').datepicker({ 'dateFormat': 'm/d/yy' });
+
+ var dateIntrospect = new Date();
+ this.$el.find('#timezone').html("(" + dateIntrospect.getTimezone() + ")");
+
+ this.model.on('error', this.handleValidationError, this);
+ this.selectorToField = _.invert(this.fieldToSelectorMap);
+ },
+
+ render: function() {
+ this.setupDatePicker('start_date');
+ this.setupDatePicker('end_date');
+ this.setupDatePicker('enrollment_start');
+ 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"
+ },
+
+ 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)");
+ },
setupDatePicker: function (fieldName) {
var cacheModel = this.model;
var div = this.$el.find('#' + this.fieldToSelectorMap[fieldName]);
- var datefield = $(div).find(".date");
- var timefield = $(div).find(".time");
+ var datefield = $(div).find("input:.date");
+ var timefield = $(div).find("input:.time");
var cachethis = this;
var savefield = function () {
cachethis.clearValidationErrors();
@@ -245,58 +114,57 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
datefield.datepicker('setDate', 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':
- // handled via code mirror
- break;
+ 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-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},
- { 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 : {},
+ 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
+ },
+
+ 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) {
var thisTarget;
if (forcedTarget) {
@@ -315,374 +183,11 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
mirror.save();
cachethis.clearValidationErrors();
var newVal = mirror.getValue();
- if (cachethis.model.get(field) != newVal) cachethis.model.save(field, newVal,
- { error: CMS.ServerError});
+ if (cachethis.model.get(field) != newVal) cachethis.model.save(field, newVal);
}
});
}
}
-
-});
-
-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('
' +
- '<%= descriptor %>' +
- '' +
- '<% if (removable) {%>remove<% ;} %>' +
- '');
-
- // 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
diff --git a/cms/static/js/views/settings/settings_grading_view.js b/cms/static/js/views/settings/settings_grading_view.js
new file mode 100644
index 0000000000..426936c031
--- /dev/null
+++ b/cms/static/js/views/settings/settings_grading_view.js
@@ -0,0 +1,370 @@
+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('
' +
+ '<%= descriptor %>' +
+ '' +
+ '<% if (removable) {%>remove<% ;} %>' +
+ '');
+
+ // 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
diff --git a/cms/static/js/views/validating_view.js b/cms/static/js/views/validating_view.js
new file mode 100644
index 0000000000..99a34f16a2
--- /dev/null
+++ b/cms/static/js/views/validating_view.js
@@ -0,0 +1,77 @@
+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('
<%= message %>'),
+
+ 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");
+ }
+});
diff --git a/cms/static/sass/_account.scss b/cms/static/sass/_account.scss
new file mode 100644
index 0000000000..650743979f
--- /dev/null
+++ b/cms/static/sass/_account.scss
@@ -0,0 +1,294 @@
+// 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;
+ }
+
+ &:focus {
+
+ + .tip {
+ color: $gray;
+ }
+ }
+ }
+
+ 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 transition(color, 0.15s, ease-in-out);
+ @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;
+ }
+}
diff --git a/cms/static/sass/_assets.scss b/cms/static/sass/_assets.scss
index 5f735dd82b..d9b215faec 100644
--- a/cms/static/sass/_assets.scss
+++ b/cms/static/sass/_assets.scss
@@ -1,4 +1,4 @@
-.assets {
+.uploads {
input.asset-search-input {
float: left;
width: 260px;
diff --git a/cms/static/sass/_base.scss b/cms/static/sass/_base.scss
index 6e5c547b87..5d4bc7c773 100644
--- a/cms/static/sass/_base.scss
+++ b/cms/static/sass/_base.scss
@@ -1,180 +1,507 @@
-// -------------------------------------
-//
-// Universal
-//
-// -------------------------------------
+// studio base styling
+// ====================
-body {
- min-width: 980px;
- background: rgb(240, 241, 245);
- font-size: 16px;
- line-height: 1.6;
- color: $baseFontColor;
+// basic reset
+html {
+ font-size: 62.5%;
+ overflow-y: scroll;
}
-body,
-input {
- font-family: 'Open Sans', sans-serif;
+body {
+ @include font-size(16);
+ min-width: 980px;
+ background: $gray-l5;
+ line-height: 1.6;
+ color: $baseFontColor;
+}
+
+body, input {
+ font-family: 'Open Sans', sans-serif;
}
a {
- text-decoration: none;
- color: $blue;
- @include transition(color .15s);
+ text-decoration: none;
+ color: $blue;
+ @include transition(color .15s);
- &:hover {
- color: #cb9c40;
- }
+ &:hover {
+ color: #cb9c40;
+ }
}
h1 {
- float: left;
- font-size: 28px;
- font-weight: 300;
- margin: 24px 6px;
+ @include font-size(28);
+ font-weight: 300;
}
.waiting {
- opacity: 0.1;
+ opacity: 0.1;
}
.page-actions {
- margin-bottom: 30px;
+ margin-bottom: 30px;
}
-.main-wrapper {
+.wrapper {
+ @include clearfix();
+ @include box-sizing(border-box);
+ width: 100%;
+}
+
+// ====================
+
+// layout - basic page header
+.wrapper-mast {
+ margin: 0;
+ padding: 0 $baseline;
+ position: relative;
+
+ .mast, .metadata {
+ @include clearfix();
+ @include font-size(16);
position: relative;
- margin: 0 40px;
+ max-width: $fg-max-width;
+ min-width: $fg-min-width;
+ width: flex-grid(12);
+ margin: 0 auto $baseline auto;
+ color: $gray-d2;
+ }
+
+ .mast {
+ border-bottom: 1px solid $gray-l4;
+ padding-bottom: ($baseline/2);
+
+ .title-sub {
+ @include font-size(14);
+ position: relative;
+ top: ($baseline/4);
+ display: block;
+ margin: 0;
+ color: $gray-l2;
+ font-weight: 400;
+ }
+
+ .title, .title-1 {
+ @include font-size(32);
+ margin: 0;
+ padding: 0;
+ font-weight: 600;
+ color: $gray-d3;
+ }
+
+ .nav-hierarchy {
+ @include font-size(14);
+ display: block;
+ margin: 0;
+ color: $gray-l2;
+ font-weight: 400;
+
+ .nav-item {
+ display: inline;
+ vertical-align: middle;
+ margin-right: ($baseline/4);
+
+ &:after {
+ content: ">>";
+ margin-left: ($baseline/4);
+ }
+
+ &:last-child {
+ margin-right: 0;
+
+ &:after {
+ content: none;
+ }
+ }
+ }
+ }
+
+ // layout with actions
+ .title {
+ width: flex-grid(12);
+ }
+
+ // layout with actions
+ &.has-actions {
+ @include clearfix();
+
+ .title {
+ float: left;
+ width: flex-grid(6,12);
+ margin-right: flex-gutter();
+ }
+
+ .nav-actions {
+ position: relative;
+ bottom: -($baseline*0.75);
+ float: right;
+ width: flex-grid(6,12);
+ text-align: right;
+
+ .nav-item {
+ display: inline-block;
+ vertical-align: top;
+ margin-right: ($baseline/2);
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+
+ // buttons
+ .button {
+ padding: ($baseline/4) ($baseline/2) ($baseline/3) ($baseline/2) !important;
+ font-weight: 400 !important;
+ }
+
+ .new-button {
+ font-weight: 700 !important;
+ }
+
+ .view-button {
+
+ font-weight: 700 !important;
+ }
+
+ .upload-button .icon-create {
+ @include font-size(18);
+ margin-top: ($baseline/4);
+ }
+ }
+ }
+
+ // layout with actions
+ &.has-subtitle {
+
+ .nav-actions {
+ bottom: -($baseline*1.5);
+ }
+ }
+ }
+
+ // page metadata/action bar
+ .metadata {
+
+ }
+}
+
+// layout - basic page content
+.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;
+ margin-bottom: $baseline;
+ border-bottom: 1px solid $gray-l4;
+ padding-bottom: ($baseline/2);
+
+ .title-sub {
+ @include font-size(14);
+ display: block;
+ margin: 0;
+ color: $gray-l2;
+ }
+
+ .title, .title-1 {
+ @include font-size(32);
+ margin: 0;
+ padding: 0;
+ font-weight: 600;
+ color: $gray-d3;
+ }
+ }
+
+ .introduction {
+ @include box-sizing(border-box);
+ @include font-size(14);
+ width: flex-grid(12);
+ margin: 0 0 $baseline 0;
+
+ .copy strong {
+ font-weight: 600;
+ }
+
+ &.has-links {
+ @include clearfix();
+
+ .copy {
+ float: left;
+ width: flex-grid(8,12);
+ margin-right: flex-gutter();
+ }
+
+ .nav-introduction-supplementary {
+ @include font-size(13);
+ float: right;
+ width: flex-grid(4,12);
+ display: block;
+ text-align: right;
+
+ .icon {
+ @include font-size(14);
+ display: inline-block;
+ vertical-align: middle;
+ margin-right: ($baseline/4);
+ }
+ }
+ }
+ }
+}
+
+.content-primary, .content-supplementary {
+ @include box-sizing(border-box);
+}
+
+// layout - primary content
+.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 {
+
+ }
+}
+
+// layout - supplemental content
+.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;
+ }
+ }
+
+ .nav-related {
+
+ .nav-item {
+ margin-bottom: ($baseline/4);
+ border-bottom: 1px dotted $gray-l4;
+ padding-bottom: ($baseline/4);
+
+
+ &:last-child {
+ margin-bottom: 0;
+ border: none;
+ padding-bottom: 0;
+ }
+ }
+ }
+ }
+}
+
+// ====================
+
+// layout - grandfathered
+.main-wrapper {
+ position: relative;
+ margin: 0 40px;
}
.inner-wrapper {
- position: relative;
- max-width: 1280px;
- margin: auto;
+ position: relative;
+ max-width: 1280px;
+ margin: auto;
- > article {
- clear: both;
- }
+ > article {
+ clear: both;
+ }
}
.sidebar {
- float: right;
- width: 28%;
+ float: right;
+ width: 28%;
}
.left {
- float: left;
+ float: left;
}
.right {
- float: right;
+ float: right;
}
-footer {
- clear: both;
- height: 100px;
-}
+// ====================
+// forms
input[type="text"],
input[type="email"],
input[type="password"],
textarea.text {
- padding: 6px 8px 8px;
- @include box-sizing(border-box);
- border: 1px solid $mediumGrey;
- border-radius: 2px;
- @include linear-gradient($lightGrey, tint($lightGrey, 90%));
- background-color: $lightGrey;
- @include box-shadow(0 1px 2px rgba(0, 0, 0, .1) inset);
- font-family: 'Open Sans', sans-serif;
- font-size: 11px;
- color: $baseFontColor;
+ padding: 6px 8px 8px;
+ @include box-sizing(border-box);
+ border: 1px solid $mediumGrey;
+ border-radius: 2px;
+ @include linear-gradient($lightGrey, tint($lightGrey, 90%));
+ background-color: $lightGrey;
+ @include box-shadow(0 1px 2px rgba(0, 0, 0, .1) inset);
+ font-family: 'Open Sans', sans-serif;
+ font-size: 11px;
+ color: $baseFontColor;
+ outline: 0;
+
+ &::-webkit-input-placeholder,
+ &:-moz-placeholder,
+ &:-ms-input-placeholder {
+ color: #979faf;
+ }
+
+ &:focus {
+ @include linear-gradient($paleYellow, tint($paleYellow, 90%));
outline: 0;
-
- &::-webkit-input-placeholder,
- &:-moz-placeholder,
- &:-ms-input-placeholder {
- color: #979faf;
- }
-
- &:focus {
- @include linear-gradient($paleYellow, tint($paleYellow, 90%));
- outline: 0;
- }
+ }
}
+// forms - specific
input.search {
- padding: 6px 15px 8px 30px;
- @include box-sizing(border-box);
- border: 1px solid $darkGrey;
- border-radius: 20px;
- background: url(../img/search-icon.png) no-repeat 8px 7px #edf1f5;
- font-family: 'Open Sans', sans-serif;
- color: $baseFontColor;
- outline: 0;
+ padding: 6px 15px 8px 30px;
+ @include box-sizing(border-box);
+ border: 1px solid $darkGrey;
+ border-radius: 20px;
+ background: url(../img/search-icon.png) no-repeat 8px 7px #edf1f5;
+ font-family: 'Open Sans', sans-serif;
+ color: $baseFontColor;
+ outline: 0;
- &::-webkit-input-placeholder {
- color: #979faf;
- }
+ &::-webkit-input-placeholder {
+ color: #979faf;
+ }
}
label {
- font-size: 12px;
+ font-size: 12px;
}
code {
- padding: 0 4px;
- border-radius: 3px;
- background: #eee;
- font-family: Monaco, monospace;
+ padding: 0 4px;
+ border-radius: 3px;
+ background: #eee;
+ font-family: Monaco, monospace;
}
.CodeMirror {
- font-size: 13px;
- border: 1px solid $darkGrey;
- background: #fff;
+ font-size: 13px;
+ border: 1px solid $darkGrey;
+ background: #fff;
}
.text-editor {
- width: 100%;
- min-height: 80px;
- padding: 10px;
- @include box-sizing(border-box);
- border: 1px solid $mediumGrey;
- @include linear-gradient(top, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.3));
- background-color: #edf1f5;
- @include box-shadow(0 1px 2px rgba(0, 0, 0, 0.1) inset);
- font-family: Monaco, monospace;
+ width: 100%;
+ min-height: 80px;
+ padding: 10px;
+ @include box-sizing(border-box);
+ border: 1px solid $mediumGrey;
+ @include linear-gradient(top, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.3));
+ background-color: #edf1f5;
+ @include box-shadow(0 1px 2px rgba(0, 0, 0, 0.1) inset);
+ 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-subsection-item,
.new-policy-item {
- @include grey-button;
- margin: 5px 8px;
- padding: 3px 10px 4px 10px;
- font-size: 10px;
+ @include grey-button;
+ margin: 5px 8px;
+ padding: 3px 10px 4px 10px;
+ font-size: 10px;
- .new-folder-icon,
- .new-policy-icon,
- .new-unit-icon {
- position: relative;
- top: 2px;
- }
+ .new-folder-icon,
+ .new-policy-icon,
+ .new-unit-icon {
+ position: relative;
+ top: 2px;
+ }
}
.item-actions {
- position: absolute;
- top: 5px;
- right: 5px;
+ position: absolute;
+ top: 5px;
+ right: 5px;
- .edit-button,
- .delete-button,
- .visibility-toggle {
- float: left;
- margin-right: 13px;
- color: #a4aab7;
- }
+ .edit-button,
+ .delete-button,
+ .visibility-toggle {
+ float: left;
+ margin-right: 13px;
+ color: #a4aab7;
+ }
+}
+
+// ====================
+
+// misc
+hr.divide {
+ @include text-sr();
}
.item-details {
@@ -189,81 +516,56 @@ code {
}
.window {
- margin-bottom: 20px;
- border: 1px solid $mediumGrey;
- border-radius: 3px;
- background: #fff;
- @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.1));
+ // @include border-radius(3px);
+ // @include box-shadow(0 1px 1px $shadow-l1);
+ // margin-bottom: $baseline;
+ // border: 1px solid $gray-l2;
+ // background: $white;
- .window-contents {
- padding: 20px;
+ .window-contents {
+ 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 {
+ display: block;
+ margin-bottom: 6px;
+ font-weight: 700;
+
+ &.inline-label {
+ display: inline;
}
- .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);
+ .description {
+ display: block;
+ font-size: 11px;
+ font-weight: 400;
+ font-style: italic;
+ line-height: 1.3;
+ color: #999;
}
+ }
- label {
- display: block;
- margin-bottom: 6px;
- font-weight: 700;
-
- &.inline-label {
- display: inline;
- }
-
- .description {
- display: block;
- font-size: 11px;
- font-weight: 400;
- font-style: italic;
- line-height: 1.3;
- color: #999;
- }
- }
-
- .row {
- margin-bottom: 10px;
- padding-bottom: 10px;
- border-bottom: 1px solid #cbd1db;
- }
+ .row {
+ margin-bottom: 10px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid #cbd1db;
+ }
}
-body.hide-wip {
- .wip, .wip-box {
- display: none !important;
- }
-}
+// ====================
-body.show-wip {
- .wip {
- outline: 1px solid #f00 !important;
- position: relative;
- }
-
- .wip-box {
- @extend .wip;
- &:after {
- content: "WIP";
- font-size: 8px;
- padding: 2px;
- background: #f00;
- color: #fff;
- @include position(absolute, 0px 0px 0 0);
- }
- }
-}
-
-.waiting {
-
-}
+// system notifications
.toast-notification {
display: none;
position: fixed;
@@ -323,59 +625,86 @@ body.show-wip {
}
.waiting {
- position: relative;
+ position: relative;
- &:before {
- content: '';
- display: block;
- position: absolute;
- top: 0;
- left: 0;
- z-index: 999998;
- width: 100%;
- height: 100%;
- border-radius: inherit;
- background: rgba(255, 255, 255, .9);
- }
+ &:before {
+ content: '';
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 999998;
+ width: 100%;
+ height: 100%;
+ border-radius: inherit;
+ background: rgba(255, 255, 255, .9);
+ }
- &:after {
- content: '';
- @extend .spinner-icon;
- display: block;
- position: absolute;
- top: 50%;
- left: 50%;
- margin-left: -10px;
- margin-top: -10px;
- z-index: 999999;
- }
+ &:after {
+ content: '';
+ @extend .spinner-icon;
+ display: block;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ margin-left: -10px;
+ margin-top: -10px;
+ z-index: 999999;
+ }
}
.waiting-inline {
- &:after {
- content: '';
- @extend .spinner-icon;
- }
+ &:after {
+ content: '';
+ @extend .spinner-icon;
+ }
}
.new-button {
- @include green-button;
- font-size: 13px;
- padding: 8px 20px 10px;
- text-align: center;
+ @include green-button;
+ @include font-size(13);
+ padding: 8px 20px 10px;
+ text-align: center;
- &.big {
- display: block;
- }
+ &.big {
+ display: block;
+ }
+
+ .icon-create {
+ display: inline-block;
+ vertical-align: middle;
+ margin-right: ($baseline/4);
+ margin-top: ($baseline/10);
+ line-height: 0;
+ }
+}
+
+.view-button {
+ @include blue-button;
+ @include font-size(13);
+ text-align: center;
+
+ &.big {
+ display: block;
+ }
+
+ .icon-view {
+ @include font-size(15);
+ display: inline-block;
+ vertical-align: middle;
+ margin-right: ($baseline/2);
+ margin-top: ($baseline/5);
+ line-height: 0;
+ }
}
.edit-button.standard,
.delete-button.standard {
- float: left;
+ @include font-size(12);
@include white-button;
+ float: left;
padding: 3px 10px 4px;
margin-left: 7px;
- font-size: 12px;
font-weight: 400;
.edit-icon,
@@ -386,9 +715,9 @@ body.show-wip {
.delete-button.standard {
- &:hover {
- background-color: tint($orange, 75%);
- }
+ &:hover {
+ background-color: tint($orange, 75%);
+ }
}
.tooltip {
@@ -417,4 +746,114 @@ body.show-wip {
font-size: 20px;
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;
+}
+
+// ====================
+
+// js dependant
+body.js {
+
+ // lean/simple modal window
+ .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;
+ }
+ }
+}
+
+// ====================
+
+// works in progress
+body.hide-wip {
+
+ .wip-box {
+ display: none;
+ }
+}
+
+// ====================
+
+// needed fudges for now
+body.dashboard {
+
+ .my-classes {
+ margin-top: $baseline;
+ }
}
\ No newline at end of file
diff --git a/cms/static/sass/_cms_mixins.scss b/cms/static/sass/_cms_mixins.scss
index 0f2e139455..b8d9a8ae2e 100644
--- a/cms/static/sass/_cms_mixins.scss
+++ b/cms/static/sass/_cms_mixins.scss
@@ -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));
}
}
@@ -41,7 +41,7 @@
background-color: $blue;
color: #fff;
- &:hover {
+ &:hover, &.active {
background-color: #62aaf5;
color: #fff;
}
@@ -285,4 +285,11 @@
padding: 0;
position: absolute;
width: 1px;
+}
+
+@mixin active {
+ @include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0));
+ background-color: rgba(255, 255, 255, .3);
+ @include box-shadow(0 -1px 0 rgba(0, 0, 0, .2) inset, 0 1px 0 #fff inset);
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
}
\ No newline at end of file
diff --git a/cms/static/sass/_course-info.scss b/cms/static/sass/_course-info.scss
index f36172c4df..5a2a5a9432 100644
--- a/cms/static/sass/_course-info.scss
+++ b/cms/static/sass/_course-info.scss
@@ -37,6 +37,11 @@
padding: 34px 0 42px;
border-top: 1px solid #cbd1db;
+ &:first-child {
+ padding-top: 0;
+ border: none;
+ }
+
&.editing {
position: relative;
z-index: 1001;
diff --git a/cms/static/sass/_courseware.scss b/cms/static/sass/_courseware.scss
index f2bd25c601..45ea111b6f 100644
--- a/cms/static/sass/_courseware.scss
+++ b/cms/static/sass/_courseware.scss
@@ -498,6 +498,7 @@ input.courseware-unit-search-input {
}
&.new-section {
+
header {
height: auto;
@include clearfix();
@@ -506,6 +507,15 @@ input.courseware-unit-search-input {
.expand-collapse-icon {
visibility: hidden;
}
+
+ .item-details {
+ padding: 25px 0 0 0;
+
+ .section-name {
+ float: none;
+ width: 100%;
+ }
+ }
}
}
diff --git a/cms/static/sass/_dashboard.scss b/cms/static/sass/_dashboard.scss
index 0a9e992650..0d4d046e57 100644
--- a/cms/static/sass/_dashboard.scss
+++ b/cms/static/sass/_dashboard.scss
@@ -6,19 +6,27 @@
@include box-shadow(0 1px 2px rgba(0, 0, 0, .1));
li {
+ position: relative;
border-bottom: 1px solid $mediumGrey;
&:last-child {
border-bottom: none;
}
- }
- a {
- padding: 20px 25px;
- line-height: 1.3;
-
- &:hover {
- background: $paleYellow;
+ .class-link {
+ z-index: 100;
+ display: block;
+ padding: 20px 25px;
+ line-height: 1.3;
+
+ &:hover {
+ background: $paleYellow;
+
+ + .view-live-button {
+ opacity: 1.0;
+ pointer-events: auto;
+ }
+ }
}
}
@@ -34,6 +42,22 @@
margin-right: 20px;
color: #3c3c3c;
}
+
+ // view live button
+ .view-live-button {
+ z-index: 10000;
+ position: absolute;
+ top: 15px;
+ right: $baseline;
+ padding: ($baseline/4) ($baseline/2);
+ opacity: 0;
+ pointer-events: none;
+
+ &:hover {
+ opacity: 1.0;
+ pointer-events: auto;
+ }
+ }
}
.new-course {
diff --git a/cms/static/sass/_extends.scss b/cms/static/sass/_extends.scss
new file mode 100644
index 0000000000..5907481bd1
--- /dev/null
+++ b/cms/static/sass/_extends.scss
@@ -0,0 +1,78 @@
+.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
diff --git a/cms/static/sass/_footer.scss b/cms/static/sass/_footer.scss
new file mode 100644
index 0000000000..66a9ce0e95
--- /dev/null
+++ b/cms/static/sass/_footer.scss
@@ -0,0 +1,48 @@
+//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
diff --git a/cms/static/sass/_header.scss b/cms/static/sass/_header.scss
index e031e16f50..ca1092f44b 100644
--- a/cms/static/sass/_header.scss
+++ b/cms/static/sass/_header.scss
@@ -1,109 +1,562 @@
-body.no-header {
- .primary-header {
- display: none;
- }
+// studio global header and navigation
+// ====================
+
+.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;
+ }
}
-@mixin active {
- @include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0));
- background-color: rgba(255, 255, 255, .3);
- @include box-shadow(0 -1px 0 rgba(0, 0, 0, .2) inset, 0 1px 0 #fff inset);
- text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+// ====================
+
+// basic layout
+.wrapper-left, .wrapper-right {
+ @include box-sizing(border-box);
}
-.primary-header {
- width: 100%;
- margin-bottom: 30px;
+.wrapper-left {
+ width: flex-grid(10, 12);
+ float: left;
+ margin-right: flex-gutter();
+}
- &.active-tab-courseware #courseware-tab {
- @include active;
- }
+.wrapper-right {
+ width: flex-grid(2, 12);
+ float: right;
+}
- &.active-tab-assets #assets-tab {
- @include active;
- }
-
- &.active-tab-pages #pages-tab {
- @include active;
- }
+// ====================
- &.active-tab-users #users-tab {
- @include active;
- }
+// specific elements - branding
+.branding, .info-course, .nav-course, .nav-account, .nav-unauth, .nav-pitch {
+ display: inline-block;
+ vertical-align: top;
+}
- &.active-tab-settings #settings-tab {
- @include active;
- }
+.branding {
+ position: relative;
+ margin: 0 ($baseline/2) 0 0;
+ padding-right: ($baseline*0.75);
- &.active-tab-import #import-tab {
- @include active;
- }
+ a {
+ @include text-hide();
+ display: block;
+ width: 164px;
+ height: 32px;
+ background: transparent url('../img/logo-edx-studio.png') 0 0 no-repeat;
+ }
+}
- &.active-tab-export #export-tab {
- @include active;
- }
+// ====================
- .drop-icon {
- margin-left: 5px;
- font-size: 11px;
- }
+// specific elements - course name/info
+.info-course {
+ @include font-size(14);
+ position: relative;
+ margin: -3px ($baseline/2) 0 0;
+ padding-right: ($baseline*0.75);
- .settings-icon {
- font-size: 18px;
- line-height: 18px;
- }
+ &:before {
+ @extend .faded-vertical-divider;
+ content: "";
+ display: block;
+ height: 50px;
+ position: absolute;
+ right: 1px;
+ top: -8px;
+ width: 1px;
+ }
- .class-nav-bar {
- clear: both;
- @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, 0 -1px 0 rgba(0, 0, 0, 0.2) inset);
- }
+ &:after {
+ @extend .faded-vertical-divider-light;
+ content: "";
+ display: block;
+ height: 50px;
+ position: absolute;
+ right: 0px;
+ top: -12px;
+ width: 1px;
+ }
- .class-nav {
- @include clearfix;
+ .course-org {
+ margin-right: ($baseline/4);
+ }
- a {
- float: left;
- display: inline-block;
- padding: 15px 25px 17px;
- font-size: 15px;
- color: #3c3c3c;
- text-shadow: 0 1px 0 rgba(255, 255, 255, 0.3);
+ .course-number, .course-org {
+ @include font-size(12);
+ display: inline-block;
+ }
- &:hover {
- @include linear-gradient(top, rgba(255, 255, 255, .2), rgba(255, 255, 255, 0));
- }
- }
+ .course-title {
+ display: block;
+ width: 100%;
+ max-width: 220px;
+ overflow: hidden;
+ margin-top: -4px;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ @include font-size(16);
+ font-weight: 600;
+ }
+}
- li {
- float: left;
- }
- }
+// ====================
- .class {
- @include clearfix;
- height: 100%;
- font-size: 12px;
- color: rgb(163, 171, 184);
- @include linear-gradient(top, rgba(255, 255, 255, 0), rgba(255, 255, 255, .1));
- background-color: rgb(47, 53, 63);
+// specific elements - course nav
+.nav-course {
+ width: 335px;
+ margin-top: -($baseline/4);
+ @include font-size(14);
- a {
- display: inline-block;
- height: 20px;
- padding: 5px 10px 6px;
- color: rgb(163, 171, 184);
- }
+ > ol > .nav-item {
+ vertical-align: bottom;
+ margin: 0 ($baseline/2) 0 0;
- .home {
- position: relative;
- top: 1px;
- }
+ &:last-child {
+ margin-right: 0;
+ }
- .log-out {
- position: relative;
- top: 3px;
- }
- }
+ .title {
+ display: block;
+ padding: 5px;
+ text-transform: uppercase;
+ font-weight: 600;
+ color: $gray-d3;
+
+ .label-prefix {
+ display: block;
+ @include font-size(11);
+ font-weight: 400;
+ }
+ }
+
+ // 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;
+ vertical-align: middle;
+ width: 80%;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+
+ .icon-expand {
+ display: inline-block;
+ vertical-align: middle;
+ }
+ }
+}
+
+// ====================
+
+// 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);
+ display: inline-block;
+ 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: -13px;
+ 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;
+ pointer-events: none;
+ }
+}
+
+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 {
+ outline: 0;
+ cursor: pointer;
+
+ &:hover, &:active, &.is-selected {
+ color: $blue;
+
+ .icon-expand {
+ color: $blue;
+ }
+ }
+ }
+ }
+
+ .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
diff --git a/cms/static/sass/_index.scss b/cms/static/sass/_index.scss
index a3e210b558..e0f6d0f2b7 100644
--- a/cms/static/sass/_index.scss
+++ b/cms/static/sass/_index.scss
@@ -1,80 +1,353 @@
-body.index {
- > header {
- display: none;
- }
+// how it works/not signed in index
+.index {
- > h1 {
- font-weight: 300;
- color: lighten($dark-blue, 40%);
- text-shadow: 0 1px 0 #fff;
- -webkit-font-smoothing: antialiased;
- max-width: 600px;
- text-align: center;
- margin: 80px auto 30px;
- }
+ &.not-signedin {
- section.main-container {
- border-right: 3px;
- background: #FFF;
- max-width: 600px;
- margin: 0 auto;
- display: block;
- @include box-sizing(border-box);
- border: 1px solid lighten( $dark-blue , 30% );
- @include border-radius(3px);
- overflow: hidden;
- @include bounce-in-animation(.8s);
+ .wrapper-header {
+ margin-bottom: 0;
+ }
- header {
- border-bottom: 1px solid lighten($dark-blue, 50%);
- @include linear-gradient(#fff, lighten($dark-blue, 62%));
- @include clearfix();
- @include box-shadow( 0 2px 0 $light-blue, inset 0 -1px 0 #fff);
- text-shadow: 0 1px 0 #fff;
+ .wrapper-footer {
+ margin: 0;
+ border-top: 2px solid $gray-l3;
- h1 {
- font-size: 14px;
- padding: 8px 20px;
- float: left;
- color: $dark-blue;
- margin: 0;
- }
-
- a {
- float: right;
- padding: 8px 20px;
- border-left: 1px solid lighten($dark-blue, 50%);
- @include box-shadow( inset -1px 0 0 #fff);
- font-weight: bold;
- font-size: 22px;
- line-height: 1;
- color: $dark-blue;
+ footer.primary {
+ border: none;
+ margin-top: 0;
+ padding-top: 0;
}
}
- ol {
- list-style: none;
+ .wrapper-content-header, .wrapper-content-features, .wrapper-content-cta {
+ @include box-sizing(border-box);
margin: 0;
- padding: 0;
+ padding: 0 $baseline;
+ position: relative;
+ width: 100%;
+ }
- li {
- border-bottom: 1px solid lighten($dark-blue, 50%);
+ .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 {
+ border: none;
+ padding-bottom: 0;
+ margin-bottom: 0;
+ }
- a {
- display: block;
- padding: 10px 20px;
+ h1, h2, h3, h4, h5, h6 {
+ color: $gray-d3;
+ }
- &:hover {
- color: $dark-blue;
- background: lighten($yellow, 10%);
- text-shadow: 0 1px 0 #fff;
+ 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 {
+ @include font-size(52);
+ float: none;
+ margin: 0 0 ($baseline/2) 0;
+ border-bottom: 1px solid $blue-l1;
+ padding: 0;
+ font-weight: 500;
+ color: $white;
+ }
+
+ .logo {
+ @include text-hide();
+ position: relative;
+ top: 3px;
+ display: inline-block;
+ vertical-align: baseline;
+ width: 282px;
+ height: 57px;
+ background: transparent url('../img/logo-edx-studio-white.png') 0 0 no-repeat;
+ }
+
+ .tagline {
+ @include font-size(24);
+ margin: 0;
+ color: $blue-l3;
+ }
+ }
+
+ .arrow_box {
+ position: relative;
+ background: #fff;
+ 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;
+ }
+
+ // feature content
+ .wrapper-content-features {
+ @include box-shadow(0 -1px ($baseline/4) $shadow);
+ padding-bottom: ($baseline*2);
+ padding-top: ($baseline*3);
+ background: $white;
+ }
+
+ .content-features {
+
+ .list-features {
+
+ }
+
+ // indiv features
+ .feature {
+ @include clearfix();
+ 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: $gray-d2;
+ 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;
+
+ .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 {
+ color: $blue;
+ }
+ }
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+ }
+ }
+
+
&: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);
}
}
}
}
-}
+}
\ No newline at end of file
diff --git a/cms/static/sass/_modal.scss b/cms/static/sass/_modal.scss
index 854d1e1045..f9fbf81a8f 100644
--- a/cms/static/sass/_modal.scss
+++ b/cms/static/sass/_modal.scss
@@ -54,4 +54,16 @@
@include white-button;
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
diff --git a/cms/static/sass/_reset.scss b/cms/static/sass/_reset.scss
index 6b4b653e87..ee03a0fca3 100644
--- a/cms/static/sass/_reset.scss
+++ b/cms/static/sass/_reset.scss
@@ -54,4 +54,118 @@ del {
table {
border-collapse: collapse;
border-spacing: 0;
+}
+
+/* Reset styles to remove ui-lightness jquery ui theme
+from the tabs component (used in the add component problem tab menu)
+*/
+
+.ui-tabs {
+ padding: 0;
+ white-space: normal;
+}
+
+.ui-corner-all, .ui-corner-bottom, .ui-corner-left, ui-corner-top, .ui-corner-br, .ui-corner-right {
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+ border-top-right-radius: 0;
+ border-top-left-radius: 0;
+}
+
+
+.ui-widget-content {
+ border: 0;
+ background: none;
+}
+.ui-widget {
+ font-family: 'Open Sans', sans-serif;
+ font-size: 16px;
+}
+
+.ui-widget-header {
+ border:none;
+ background: none;
+}
+
+.ui-tabs .ui-tabs-nav {
+ padding: 0;
+}
+
+.ui-tabs .ui-tabs-nav li {
+ margin: 0;
+ padding: 0;
+ border: none;
+ top: 0;
+ margin: 0;
+ float: none;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+}
+
+.ui-tabs-nav {
+
+ li {
+ top: 0;
+ margin: 0;
+ }
+ a {
+ float: none;
+ font-weight: normal;
+ }
+}
+
+.ui-tabs .ui-tabs-panel {
+ padding: 0;
+}
+
+/* reapplying the tab styles from unit.scss after
+removing jquery ui ui-lightness styling
+*/
+
+.problem-type-tabs {
+ border:none;
+ list-style-type: none;
+ width: 100%;
+ @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, 0 -1px 0 rgba(0, 0, 0, 0.2) inset);
+
+ li:first-child {
+ margin-left: 20px;
+ }
+ li {
+ opacity: .8;
+
+ &:ui-state-active {
+ background-color: rgba(255, 255, 255, .3);
+ opacity: 1;
+ font-weight: 400;
+ }
+ a:focus {
+ outline: none;
+ border: 0px;
+ }
+ }
+/*
+ li {
+ float:left;
+ display:inline-block;
+ text-align:center;
+ width: auto;
+ //@include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0));
+ //background-color: tint($lightBluishGrey, 20%);
+ //@include box-shadow(0 1px 0 rgba(255, 255, 255, 0.2) inset, 0 -1px 0 rgba(0, 0, 0, 0.2) inset);
+ opacity:.8;
+
+ &:hover {
+ opacity:1;
+ }
+
+ &.current {
+ border: 0px;
+ //@include active;
+ opacity:1;
+ }
+ }
+*/
}
\ No newline at end of file
diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss
index b11378145b..d25c78f35b 100644
--- a/cms/static/sass/_settings.scss
+++ b/cms/static/sass/_settings.scss
@@ -1,800 +1,584 @@
-.settings {
- .settings-overview {
+// Studio - Course Settings
+// ====================
+body.course.settings {
+
+ .content-primary, .content-supplementary {
+ @include box-sizing(border-box);
+ float: left;
+ }
+
+ .content-primary {
@extend .window;
- @include clearfix;
- display: table;
- width: 100%;
+ width: flex-grid(9, 12);
+ margin-right: flex-gutter();
+ padding: $baseline ($baseline*1.5);
+ }
- // layout
- .sidebar {
- display: table-cell;
- float: none;
- width: 20%;
- padding: 30px 0 30px 20px;
- @include border-radius(3px 0 0 3px);
- background: $lightGrey;
- }
+ .group-settings {
+ margin: 0 0 ($baseline*2) 0;
- .main-column {
- display: table-cell;
- float: none;
- width: 80%;
- padding: 30px 40px 30px 60px;
- }
+ header {
+ @include clearfix();
- .settings-page-menu {
- a {
- display: block;
- padding-left: 20px;
- line-height: 52px;
+ .title-2 {
+ width: flex-grid(4, 9);
+ margin: 0 flex-gutter() 0 0;
+ float: left;
+ }
- &.is-shown {
- background: #fff;
- @include border-radius(5px 0 0 5px);
- }
+ .tip {
+ @include font-size(13);
+ width: flex-grid(5, 9);
+ float: right;
+ margin-top: ($baseline/2);
+ text-align: right;
+ color: $gray-l2;
}
}
- .settings-page-section {
- > .alert {
- display: none;
-
- &.is-shown {
- display: block;
- }
- }
-
- > section {
- display: none;
- margin-bottom: 40px;
-
- &.is-shown {
- display: block;
- }
-
- &: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
- label, .label {
- 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 {
- @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 {
- border: 1px solid $darkGrey;
- height: 300px;
- }
-
- input[type="checkbox"], input[type="radio"] {
+ // basic layout/elements
+ .title-2 {
}
- input:disabled + .copy > label, input:disabled + .label {
- color: $mediumGrey;
- }
-
-
- .input-default input, .input-default textarea {
- color: $mediumGrey;
- background: $lightGrey;
- }
-
- ::-webkit-input-placeholder {
- color: $mediumGrey;
- font-size: 13px;
- }
- :-moz-placeholder {
- color: $mediumGrey;
- font-size: 13px;
+ .title-3 {
+
}
+ // UI hints/tips/messages
.tip {
- color: $mediumGrey;
- font-size: 13px;
- }
-
-
- // 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 {
- display: inline-block;
- width: 100%;
-
- > input, > textarea, .input {
- display: inline-block;
-
- &:last-child {
- margin-bottom: 0;
- }
-
- .group {
- input, textarea {
- margin-bottom: 5px;
- }
-
- .label, label {
- font-size: 13px;
- }
- }
-
- // multi-field
- &.multi {
- display: block;
- background: tint($lightGrey, 50%);
- padding: 20px;
- @include border-radius(4px);
- @include box-sizing(border-box);
-
- .group {
- margin-bottom: 10px;
- max-width: 175px;
-
- &:last-child {
- margin-bottom: 0;
- }
-
- input, .input, textarea {
-
- }
-
- .tip-stacked {
- margin-top: 0;
- }
- }
- }
-
- // multi stacked
- &.multi-stacked {
-
- .group {
- input, .input, textarea {
- min-width: 370px;
- width: 370px;
- }
- }
- }
-
- // multi-field inline
- &.multi-inline {
- @include clearfix;
-
- .group {
- float: left;
- margin-right: 20px;
-
- &:nth-child(2) {
- margin-right: 0;
- }
-
- .input, input, textarea {
- width: 100%;
- }
- }
- }
- }
-
- // input-list
- .input-list {
-
- .input {
- margin-bottom: 15px;
- padding-bottom: 15px;
- border-bottom: 1px dotted $lightGrey;
- @include clearfix();
-
- &:last-child {
- border: 0;
- }
-
- .row {
- }
- }
- }
-
- //radio buttons and checkboxes
- .input-radio {
- @include clearfix();
-
- input {
- display: block;
- float: left;
- margin-right: 10px;
- }
-
- .copy {
- position: relative;
- top: -5px;
- float: left;
- width: 350px;
- }
-
- label {
- display: block;
- margin-bottom: 0;
- }
-
- .tip {
- display: block;
- margin-top: 0;
- }
-
- .message-error {
-
- }
- }
-
- .input-checkbox {
-
- }
-
- // enumerated inputs
- &.enum {
- }
- }
-
- // layout - aligned label/field pairs
- &.row-col2 {
-
- > label, .label {
- width: 200px;
- }
-
- .field {
- width: 400px ! important;
- }
-
- &.multi-inline {
- @include clearfix();
-
- .group {
- width: 170px;
- }
- }
- }
-
- .field-additional {
- margin-left: 204px;
- }
- }
-
- // editing controls - adding
- .new-item, .replace-item {
- clear: both;
+ @include transition(color, 0.15s, ease-in-out);
+ @include font-size(13);
display: block;
- margin-top: 10px;
- padding-bottom: 10px;
- @include grey-button;
- @include box-sizing(border-box);
- }
-
-
- // editing controls - removing
- .delete-button {
- float: right;
- }
-
- // editing controls - preview
- .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();
-
- .doc-filename {
- display: inline-block;
- width: 220px;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .remove-doc-data {
- display: inline-block;
- margin-top: 0;
- width: 150px;
- }
- }
- }
-
- // specific sections
- .settings-details {
-
- }
-
- .settings-faculty {
-
- .settings-faculty-members {
-
- > header {
- display: none;
- }
-
- .field .multi {
- display: block;
- margin-bottom: 40px;
- padding: 20px;
- background: tint($lightGrey, 50%);
- @include border-radius(4px);
- @include box-sizing(border-box);
- }
-
- .course-faculty-list-item {
-
- .row {
-
- &:nth-child(4) {
- padding-bottom: 0;
- border-bottom: none;
- }
- }
-
- .remove-faculty-photo {
- display: inline-block;
- }
- }
-
- #course-faculty-bio-input {
- margin-bottom: 0;
- }
-
- .new-course-faculty-item {
- }
-
- .current-faculty-photo {
- padding: 0;
-
- img {
- display: block;
- @include box-shadow(0 1px 3px rgba(0,0,0,0.1));
- padding: 10px;
- border: 2px solid $mediumGrey;
- background: #fff;
- }
- }
- }
- }
-
- .settings-grading {
-
- .setting-grading-assignment-types {
-
- .row .field.enum {
- width: 684px;
- }
- }
-
- .course-grading-assignment-list-item {
-
- }
-
- .input-list {
- .row {
-
- .input {
- &:last-child {
- margin-bottom: 0;
- padding-bottom: 0;
- }
- }
- }
- }
- }
-
- .settings-handouts {
-
- }
-
- .settings-problems {
-
- > section {
-
- &.is-shown {
- display: block;
- }
- }
- }
-
- .settings-discussions {
-
- .course-discussions-categories-list-item {
-
- label {
- display: none;
- }
-
- .group {
- display: inline-block;
- }
-
- .remove-item {
- display: inline-block !important;
- margin-left: 10px;
- }
- }
-
-
- }
-
- // states
- label.is-focused {
- color: $blue;
- @include transition(color 1s ease-in-out);
- }
-
- // extras/abbreviations
- // .settings-extras {
-
- // > header {
- // cursor: pointer;
-
- // &.active {
-
- // }
- // }
-
- // > div {
- // display: none;
- // @include transition(display 0.25s ease-in-out);
-
- // &.is-shown {
- // display: block;
- // }
- // }
- // }
-
- input.error, textarea.error {
- border-color: $red;
+ margin-top: ($baseline/4);
+ color: $gray-l3;
}
.message-error {
+ @include font-size(13);
display: block;
- margin-top: 5px;
+ margin-top: ($baseline/4);
+ margin-bottom: ($baseline/2);
color: $red;
- font-size: 13px;
}
- // misc
- .divide {
- display: none;
+ // buttons
+ .remove-item {
+ @include white-button;
+ @include font-size(13);
+ font-weight: 400;
}
- i.ss-icon {
- position: relative;
- top: 1px;
- margin-right: 5px;
+ .new-button {
+ @include font-size(13);
}
- .well {
- padding: 20px;
- background: $lightGrey;
- border: 1px solid $mediumGrey;
- @include border-radius(4px);
- @include box-shadow(0 1px 1px rgba(0,0,0,0.05) inset)
- }
- }
+ // form basics
+ .list-input {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ .field {
+ margin: 0 0 $baseline 0;
+ &:last-child {
+ margin-bottom: 0;
+ }
- h3 {
- margin-bottom: 30px;
- font-size: 15px;
- font-weight: 700;
- color: $blue;
- }
+ &.required {
- .grade-controls {
- @include clearfix;
- width: 642px;
- }
-
- .new-grade-button {
- position: relative;
- 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 {
- float: left;
- width: 580px;
- margin-bottom: 10px;
-
- .grade-bar {
- position: relative;
- width: 100%;
- height: 50px;
- background: $lightGrey;
-
- .increments {
- position: relative;
-
- li {
- position: absolute;
- top: 52px;
- width: 30px;
- margin-left: -15px;
- font-size: 9px;
- text-align: center;
-
- &.increment-0 {
- left: 0;
+ label {
+ font-weight: 600;
}
- &.increment-10 {
- left: 10%;
+ 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;
+ font-weight: 400;
+
+ &.is-focused {
+ color: $blue;
+ }
+ }
+
+ input, textarea {
+ @include placeholder($gray-l4);
+ @include font-size(16);
+ @include size(100%,100%);
+ padding: ($baseline/2);
+
+ &.long {
}
- &.increment-20 {
- left: 20%;
+ &.short {
}
- &.increment-30 {
- left: 30%;
+ &.error {
+ border-color: $red;
}
- &.increment-40 {
- left: 40%;
- }
+ &:focus {
- &.increment-50 {
- left: 50%;
+ + .tip {
+ color: $gray;
+ }
}
+ }
- &.increment-60 {
- left: 60%;
- }
+ textarea.long {
+ height: ($baseline*5);
+ }
- &.increment-70 {
- left: 70%;
- }
+ input[type="checkbox"] {
+ display: inline-block;
+ margin-right: ($baseline/4);
+ width: auto;
+ height: auto;
- &.increment-80 {
- left: 80%;
- }
-
- &.increment-90 {
- left: 90%;
- }
-
- &.increment-100 {
- left: 100%;
+ & + label {
+ display: inline-block;
}
}
}
-
- .grade-specific-bar {
- height: 50px !important;
+
+ .field-group {
+ @include clearfix();
+ margin: 0 0 ($baseline/2) 0;
}
- .grades {
- position: relative;
+ // enumerated/grouped lists
+ &.enum {
- li {
- position: absolute;
- top: 0;
- height: 50px;
- text-align: right;
- @include border-radius(2px);
+ .field-group {
+ @include box-sizing(border-box);
+ @include border-radius(3px);
+ background: $gray-l5;
+ padding: $baseline;
- &:hover,
- &.is-dragging {
- .remove-button {
- display: block;
+ &:last-child {
+ padding-bottom: $baseline;
+ }
+
+ .actions {
+ @include clearfix();
+ margin-top: ($baseline/2);
+ border-top: 1px solid $gray-l4;
+ padding-top: ($baseline/2);
+
+ .remove-item {
+ float: right;
}
}
+ }
+ }
+ }
- &.is-dragging {
+ // existing inputs
+ .input-existing {
+ margin: 0 0 $baseline 0;
+ .actions {
+ margin: ($baseline/4) 0 0 0;
+ }
+ }
- }
+ // not editable fields
+ .field.is-not-editable {
- .remove-button {
- display: none;
- position: absolute;
- top: -17px;
- right: 1px;
- height: 17px;
- font-size: 10px;
- }
+ label, .label {
+ color: $gray-l3;
+ }
- &:nth-child(1) {
- background: #4fe696;
- }
+ input {
+ opacity: 0.25;
+ }
+ }
- &:nth-child(2) {
- background: #ffdf7e;
- }
+ // field with error
+ .field.error {
- &:nth-child(3) {
- background: #ffb657;
- }
+ input, textarea {
+ border-color: $red;
+ }
+ }
+
+ // specific fields - basic
+ &.basic {
- &:nth-child(4) {
- background: #ef54a1;
- }
+ .list-input {
+ @include clearfix();
- &:nth-child(5),
- &.bar-fail {
- background: #fb336c;
- }
+ .field {
+ margin-bottom: 0;
+ }
+ }
- .letter-grade {
- display: block;
- margin: 10px 15px 0 0;
- font-size: 16px;
- font-weight: 700;
- line-height: 14px;
- }
+ #field-course-organization {
+ float: left;
+ width: flex-grid(2, 9);
+ margin-right: flex-gutter();
+ }
- .range {
- display: block;
- margin-right: 15px;
- font-size: 10px;
- line-height: 12px;
- }
+ #field-course-number {
+ float: left;
+ width: flex-grid(2, 9);
+ margin-right: flex-gutter();
+ }
- .drag-bar {
+ #field-course-name {
+ float: left;
+ width: flex-grid(5, 9);
+ }
+ }
+
+ // specific fields - schedule
+ &.schedule {
+
+ .list-input {
+ margin-bottom: ($baseline*1.5);
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ .field-group {
+ @include clearfix();
+ border-bottom: 1px solid $gray-l5;
+ padding-bottom: ($baseline/2);
+
+ &:last-child {
+ border: none;
+ padding-bottom: 0;
+ }
+
+ .field {
+ float: left;
+ width: flex-grid(3, 9);
+ margin-bottom: ($baseline/4);
+ margin-right: flex-gutter();
+ }
+
+ .field.time {
+ position: relative;
+
+ .tip {
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));
+ right: 0;
+ }
+ }
+ }
+ }
+
+ // specific fields - overview
+ #field-course-overview {
- cursor: ew-resize;
- @include transition(none);
+ #course-overview {
+ height: ($baseline*20);
+ }
+ }
- &:hover {
- width: 6px;
- right: -2px;
+ // specific fields - video
+ #field-course-introduction-video {
+
+ .input-existing {
+ @include box-sizing(border-box);
+ @include border-radius(3px);
+ background: $gray-l5;
+ padding: ($baseline/2);
+
+ .actions {
+ @include clearfix();
+ margin-top: ($baseline/2);
+ border-top: 1px solid $gray-l4;
+ padding-top: ($baseline/2);
+
+ .remove-item {
+ float: right;
+ }
+ }
+ }
+
+ .actions {
+ margin-top: ($baseline/2);
+ border-top: 1px solid $gray-l5;
+ padding-top: ($baseline/2);
+ }
+ }
+
+ // specific fields - requirements
+ &.requirements {
+
+ #field-course-effort {
+ width: flex-grid(3, 9);
+ }
+ }
+
+ // specific fields - grading range (artifact styling)
+ &.grade-range {
+ margin-bottom: ($baseline*3);
+
+ .grade-controls {
+ @include clearfix;
+ width: flex-grid(9,9);
+ }
+
+ .new-grade-button {
+ @include box-sizing(border-box);
+ @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;
+ width: 100%;
+ height: ($baseline*2.5);
+ background: $lightGrey;
+
+ .increments {
+ position: relative;
+
+ li {
+ 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 {
+ left: 20%;
+ }
+
+ &.increment-30 {
+ left: 30%;
+ }
+
+ &.increment-40 {
+ left: 40%;
+ }
+
+ &.increment-50 {
+ left: 50%;
+ }
+
+ &.increment-60 {
+ left: 60%;
+ }
+
+ &.increment-70 {
+ left: 70%;
+ }
+
+ &.increment-80 {
+ left: 80%;
+ }
+
+ &.increment-90 {
+ left: 90%;
+ }
+
+ &.increment-100 {
+ left: 100%;
+ }
+ }
+ }
+
+ .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 {
+
+ }
+
+ .remove-button {
+ display: none;
+ position: absolute;
+ top: -17px;
+ right: 1px;
+ height: 17px;
+ font-size: 10px;
+ }
+
+ &:nth-child(1) {
+ background: #4fe696;
+ }
+
+ &:nth-child(2) {
+ background: #ffdf7e;
+ }
+
+ &:nth-child(3) {
+ background: #ffb657;
+ }
+
+ &:nth-child(4) {
+ background: #ef54a1;
+ }
+
+ &:nth-child(5),
+ &.bar-fail {
+ background: #fb336c;
+ }
+
+ .letter-grade {
+ display: block;
+ margin: 10px 15px 0 0;
+ font-size: 16px;
+ font-weight: 700;
+ line-height: 14px;
+ }
+
+ .range {
+ display: block;
+ margin-right: 15px;
+ font-size: 10px;
+ line-height: 12px;
+ }
+
+ .drag-bar {
+ 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;
+ }
+ }
}
}
}
}
}
+
+ // specific fields - grading rules
+ &.grade-rules {
+
+ #field-course-grading-graceperiod {
+ width: flex-grid(3, 9);
+ }
+ }
+
+ &.assignment-types {
+
+ .list-input {
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ .field-group {
+ @include clearfix();
+ width: flex-grid(9, 9);
+ margin-bottom: ($baseline*1.5);
+ border-bottom: 1px solid $gray-l5;
+ padding-bottom: ($baseline*1.5);
+
+ &:last-child {
+ border: none;
+ padding-bottom: 0;
+ }
+
+ .field {
+ display: inline-block;
+ vertical-align: top;
+ width: flex-grid(3, 6);
+ margin-bottom: ($baseline/2);
+ margin-right: flex-gutter();
+ }
+
+ #field-course-grading-assignment-shortname,
+ #field-course-grading-assignment-totalassignments,
+ #field-course-grading-assignment-gradeweight,
+ #field-course-grading-assignment-droppable {
+ width: flex-grid(2, 6);
+ }
+ }
+
+ .actions {
+ float: left;
+ width: flex-grid(9, 9);
+
+ .delete-button {
+ margin: 0;
+ }
+ }
+ }
+ }
+
+ .content-supplementary {
+ width: flex-grid(3, 12);
}
}
\ No newline at end of file
diff --git a/cms/static/sass/_static-pages.scss b/cms/static/sass/_static-pages.scss
index 1ab3413682..138e817769 100644
--- a/cms/static/sass/_static-pages.scss
+++ b/cms/static/sass/_static-pages.scss
@@ -28,7 +28,9 @@
border-radius: 0;
&.new-component-item {
- margin-top: 20px;
+ background: transparent;
+ border: none;
+ @include box-shadow(none);
}
}
diff --git a/cms/static/sass/_subsection.scss b/cms/static/sass/_subsection.scss
index 32c983ee3a..a39c0d757a 100644
--- a/cms/static/sass/_subsection.scss
+++ b/cms/static/sass/_subsection.scss
@@ -1,3 +1,11 @@
+.subsection .main-wrapper {
+ margin: 40px;
+}
+
+.subsection .inner-wrapper {
+ @include clearfix();
+}
+
.subsection-body {
padding: 32px 40px;
@include clearfix;
diff --git a/cms/static/sass/_unit.scss b/cms/static/sass/_unit.scss
index bdc76c811c..b7600e4205 100644
--- a/cms/static/sass/_unit.scss
+++ b/cms/static/sass/_unit.scss
@@ -1,8 +1,14 @@
-.unit .main-wrapper,
-.subsection .main-wrapper {
+.unit .main-wrapper {
+ @include clearfix();
margin: 40px;
}
+//Problem Selector tab menu requirements
+.js .tabs .tab {
+ display: none;
+}
+//end problem selector reqs
+
.main-column {
clear: both;
float: left;
@@ -58,6 +64,7 @@
margin: 20px 40px;
+
.title {
margin: 0 0 15px 0;
color: $mediumGrey;
@@ -67,22 +74,25 @@
}
&.new-component-item {
- padding: 20px;
- border: none;
- border-radius: 3px;
- background: $lightGrey;
+ margin: 20px 0px;
+ border-top: 1px solid $mediumGrey;
+ box-shadow: 0 2px 1px rgba(182, 182, 182, 0.75) inset;
+ background-color: $lightGrey;
+ margin-bottom: 0px;
+ padding-bottom: 20px;
.new-component-button {
display: block;
padding: 20px;
text-align: center;
- color: #6d788b;
+ color: #edf1f5;
}
h5 {
- margin-bottom: 8px;
+ margin: 20px 0px;
color: #fff;
- font-weight: 700;
+ font-weight: 600;
+ font-size: 18px;
}
.rendered-component {
@@ -92,18 +102,21 @@
}
.new-component-type {
+
a,
li {
display: inline-block;
}
a {
+ border: 1px solid $mediumGrey;
width: 100px;
height: 100px;
- margin-right: 10px;
- margin-bottom: 10px;
+ color: #fff;
+ margin-right: 15px;
+ margin-bottom: 20px;
border-radius: 8px;
- font-size: 13px;
+ font-size: 15px;
line-height: 14px;
text-align: center;
@include box-shadow(0 1px 1px rgba(0, 0, 0, .2), 0 1px 0 rgba(255, 255, 255, .4) inset);
@@ -115,25 +128,40 @@
width: 100%;
padding: 10px;
@include box-sizing(border-box);
+ color: #fff;
}
}
}
.new-component-templates {
display: none;
- padding: 20px;
+ margin: 20px 40px 20px 40px;
+ border-radius: 3px;
+ border: 1px solid $mediumGrey;
+ background-color: #fff;
+ @include box-shadow(0 1px 1px rgba(0, 0, 0, .2), 0 1px 0 rgba(255, 255, 255, .4) inset);
@include clearfix;
.cancel-button {
+ margin: 20px 0px 10px 10px;
@include white-button;
}
+ .problem-type-tabs {
+ display: none;
+ }
+
// specific menu types
&.new-component-problem {
+ padding-bottom:10px;
.ss-icon, .editor-indicator {
display: inline-block;
}
+
+ .problem-type-tabs {
+ display: inline-block;
+ }
}
}
@@ -146,7 +174,6 @@
border: 1px solid $darkGreen;
background: tint($green,20%);
color: #fff;
- @include transition(background-color .15s);
&:hover {
background: $brightGreen;
@@ -154,19 +181,81 @@
}
}
- .new-component-template {
- margin-bottom: 20px;
+ .problem-type-tabs {
+ list-style-type: none;
+ border-radius: 0;
+ width: 100%;
+ @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, 0 -1px 0 rgba(0, 0, 0, 0.2) inset);
- li:last-child {
+ li:first-child {
+ margin-left: 20px;
+ }
+
+ li {
+ float:left;
+ display:inline-block;
+ text-align:center;
+ width: auto;
+ @include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0));
+ background-color: tint($lightBluishGrey, 10%);
+ @include box-shadow(0 1px 0 rgba(255, 255, 255, 0.2) inset, 0 -1px 0 rgba(0, 0, 0, 0.2) inset);
+ opacity:.8;
+
+ &:hover {
+ opacity:1;
+ background-color: tint($lightBluishGrey, 20%);
+ }
+
+ &.ui-state-active {
+ border: 0px;
+ @include active;
+ opacity:1;
+ }
+ }
+
+ a{
+ display: block;
+ padding: 15px 25px;
+ font-size: 15px;
+ line-height: 16px;
+ text-align: center;
+ color: #3c3c3c;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.3);
+ }
+ }
+
+ .new-component-template {
+
+ a {
+ background: #fff;
+ border: 0px;
+ color: #3c3c3c;
+ @include transition (none);
+
+ &:hover {
+ background: tint($green,30%);
+ color: #fff;
+ @include transition(background-color .15s);
+ }
+ }
+
+ li {
+ border:none;
+ border-bottom: 1px dashed $lightGrey;
+ color: #fff;
+ }
+
+ li:first-child {
a {
- border-radius: 0 0 3px 3px;
- border-bottom: 1px solid $darkGreen;
+ border-top: 0px;
}
}
li:nth-child(2) {
a {
- border-radius: 3px 3px 0 0;
+ border-radius: 0px;
}
}
@@ -175,18 +264,20 @@
display: block;
padding: 7px 20px;
border-bottom: none;
- font-weight: 300;
+ font-weight: 500;
.name {
float: left;
.ss-icon {
@include transition(opacity .15s);
- position: relative;
+ display: inline-block;
top: 1px;
- font-size: 13px;
margin-right: 5px;
opacity: 0.5;
+ width: 17;
+ height: 21px;
+ vertical-align: middle;
}
}
@@ -204,6 +295,7 @@
}
&:hover {
+ color: #fff;
.ss-icon {
opacity: 1.0;
@@ -217,14 +309,18 @@
// specific editor types
.empty {
- @include box-shadow(0 1px 3px rgba(0,0,0,0.2));
- margin-bottom: 10px;
a {
- border-bottom: 1px solid $darkGreen;
- border-radius: 3px;
- font-weight: 500;
- background: $green;
+ line-height: 1.4;
+ font-weight: 400;
+ background: #fff;
+ color: #3c3c3c;
+
+
+ &:hover {
+ background: tint($green,30%);
+ color: #fff;
+ }
}
}
}
@@ -233,7 +329,7 @@
text-align: center;
h5 {
- color: $green;
+ color: $darkGreen;
}
}
@@ -507,6 +603,7 @@
.edit-state-draft {
.visibility,
+
.edit-draft-message,
.view-button {
display: none;
diff --git a/cms/static/sass/_variables.scss b/cms/static/sass/_variables.scss
index a783abeaeb..4d8e26b2f9 100644
--- a/cms/static/sass/_variables.scss
+++ b/cms/static/sass/_variables.scss
@@ -1,25 +1,85 @@
-$gw-column: 80px;
-$gw-gutter: 20px;
+$baseline: 20px;
+// grid
+$gw-column: ($baseline*3);
+$gw-gutter: $baseline;
$fg-column: $gw-column;
$fg-gutter: $gw-gutter;
$fg-max-columns: 12;
-$fg-max-width: 1400px;
-$fg-min-width: 810px;
+$fg-max-width: 1280px;
+$fg-min-width: 900px;
+// type
$sans-serif: 'Open Sans', $verdana;
$body-line-height: golden-ratio(.875em, 1);
-
-$white: rgb(255,255,255);
-$black: rgb(0,0,0);
-$pink: rgb(182,37,104);
$error-red: rgb(253, 87, 87);
-$baseFontColor: #3c3c3c;
-$offBlack: #3c3c3c;
+// colors - new for re-org
$black: rgb(0,0,0);
$white: rgb(255,255,255);
-$blue: #5597dd;
+
+$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;
+$offBlack: #3c3c3c;
$orange: #edbd3c;
$red: #b20610;
$green: #108614;
@@ -34,4 +94,4 @@ $brightGreen: rgb(22, 202, 87);
$disabledGreen: rgb(124, 206, 153);
$darkGreen: rgb(52, 133, 76);
$lightBluishGrey: rgb(197, 207, 223);
-$lightBluishGrey2: rgb(213, 220, 228);
+$lightBluishGrey2: rgb(213, 220, 228);
\ No newline at end of file
diff --git a/cms/static/sass/base-style.scss b/cms/static/sass/base-style.scss
index e3463477c1..dceac4233d 100644
--- a/cms/static/sass/base-style.scss
+++ b/cms/static/sass/base-style.scss
@@ -1,4 +1,5 @@
@import 'bourbon/bourbon';
+@import 'bourbon/addons/button';
@import 'vendor/normalize';
@import 'keyframes';
@@ -8,8 +9,10 @@
@import "fonts";
@import "variables";
@import "cms_mixins";
+@import "extends";
@import "base";
@import "header";
+@import "footer";
@import "dashboard";
@import "courseware";
@import "subsection";
@@ -26,6 +29,8 @@
@import "modal";
@import "alerts";
@import "login";
+@import "account";
+@import "index";
@import 'jquery-ui-calendar';
@import 'content-types';
diff --git a/cms/templates/activation_active.html b/cms/templates/activation_active.html
index 07d3a37969..712c73abf9 100644
--- a/cms/templates/activation_active.html
+++ b/cms/templates/activation_active.html
@@ -7,7 +7,7 @@
Account already active!
- This account has already been activated. Log in here.
+ This account has already been activated. Log in here.
diff --git a/cms/templates/activation_complete.html b/cms/templates/activation_complete.html
index 5d9437ccb3..1e195a632c 100644
--- a/cms/templates/activation_complete.html
+++ b/cms/templates/activation_complete.html
@@ -5,7 +5,7 @@