Commit 97e893b2 by Brian Talbot

Merge branch 'master' into feature/btalbot/studio-courselink

parents 01b2fefd 81f94fc4
...@@ -35,6 +35,7 @@ load-plugins= ...@@ -35,6 +35,7 @@ load-plugins=
# it should appear only once). # it should appear only once).
disable= disable=
# C0301: Line too long # C0301: Line too long
# C0302: Too many lines in module
# W0141: Used builtin function 'map' # W0141: Used builtin function 'map'
# W0142: Used * or ** magic # W0142: Used * or ** magic
# R0201: Method could be a function # R0201: Method could be a function
...@@ -42,8 +43,11 @@ disable= ...@@ -42,8 +43,11 @@ disable=
# R0902: Too many instance attributes # R0902: Too many instance attributes
# R0903: Too few public methods (1/2) # R0903: Too few public methods (1/2)
# R0904: Too many public methods # R0904: Too many public methods
# R0911: Too many return statements
# R0912: Too many branches
# R0913: Too many arguments # R0913: Too many arguments
C0301,W0141,W0142,R0201,R0901,R0902,R0903,R0904,R0913 # R0914: Too many local variables
C0301,C0302,W0141,W0142,R0201,R0901,R0902,R0903,R0904,R0911,R0912,R0913,R0914
[REPORTS] [REPORTS]
...@@ -92,7 +96,7 @@ zope=no ...@@ -92,7 +96,7 @@ zope=no
# List of members which are set dynamically and missed by pylint inference # List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E0201 when accessed. Python regular # system, and so shouldn't trigger E0201 when accessed. Python regular
# expressions are accepted. # expressions are accepted.
generated-members=REQUEST,acl_users,aq_parent,objects,DoesNotExist,can_read,can_write,get_url,size generated-members=REQUEST,acl_users,aq_parent,objects,DoesNotExist,can_read,can_write,get_url,size,content
[BASIC] [BASIC]
......
...@@ -628,6 +628,113 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -628,6 +628,113 @@ class ContentStoreTest(ModuleStoreTestCase):
self.assertIn('markdown', context, "markdown is missing from context") self.assertIn('markdown', context, "markdown is missing from context")
self.assertNotIn('markdown', problem.editable_metadata_fields, "Markdown slipped into the editable metadata fields") self.assertNotIn('markdown', problem.editable_metadata_fields, "Markdown slipped into the editable metadata fields")
def test_cms_imported_course_walkthrough(self):
"""
Import and walk through some common URL endpoints. This just verifies non-500 and no other
correct behavior, so it is not a deep test
"""
import_from_xml(modulestore(), 'common/test/data/', ['simple'])
loc = Location(['i4x', 'edX', 'simple', 'course', '2012_Fall', None])
resp = self.client.get(reverse('course_index',
kwargs={'org': loc.org,
'course': loc.course,
'name': loc.name}))
self.assertEqual(200, resp.status_code)
self.assertContains(resp, 'Chapter 2')
# go to various pages
# import page
resp = self.client.get(reverse('import_course',
kwargs={'org': loc.org,
'course': loc.course,
'name': loc.name}))
self.assertEqual(200, resp.status_code)
# export page
resp = self.client.get(reverse('export_course',
kwargs={'org': loc.org,
'course': loc.course,
'name': loc.name}))
self.assertEqual(200, resp.status_code)
# manage users
resp = self.client.get(reverse('manage_users',
kwargs={'location': loc.url()}))
self.assertEqual(200, resp.status_code)
# course info
resp = self.client.get(reverse('course_info',
kwargs={'org': loc.org,
'course': loc.course,
'name': loc.name}))
self.assertEqual(200, resp.status_code)
# settings_details
resp = self.client.get(reverse('settings_details',
kwargs={'org': loc.org,
'course': loc.course,
'name': loc.name}))
self.assertEqual(200, resp.status_code)
# settings_details
resp = self.client.get(reverse('settings_grading',
kwargs={'org': loc.org,
'course': loc.course,
'name': loc.name}))
self.assertEqual(200, resp.status_code)
# static_pages
resp = self.client.get(reverse('static_pages',
kwargs={'org': loc.org,
'course': loc.course,
'coursename': loc.name}))
self.assertEqual(200, resp.status_code)
# static_pages
resp = self.client.get(reverse('asset_index',
kwargs={'org': loc.org,
'course': loc.course,
'name': loc.name}))
self.assertEqual(200, resp.status_code)
# go look at a subsection page
subsection_location = loc._replace(category='sequential', name='test_sequence')
resp = self.client.get(reverse('edit_subsection',
kwargs={'location': subsection_location.url()}))
self.assertEqual(200, resp.status_code)
# go look at the Edit page
unit_location = loc._replace(category='vertical', name='test_vertical')
resp = self.client.get(reverse('edit_unit',
kwargs={'location': unit_location.url()}))
self.assertEqual(200, resp.status_code)
# delete a component
del_loc = loc._replace(category='html', name='test_html')
resp = self.client.post(reverse('delete_item'),
json.dumps({'id': del_loc.url()}), "application/json")
self.assertEqual(200, resp.status_code)
# delete a unit
del_loc = loc._replace(category='vertical', name='test_vertical')
resp = self.client.post(reverse('delete_item'),
json.dumps({'id': del_loc.url()}), "application/json")
self.assertEqual(200, resp.status_code)
# delete a unit
del_loc = loc._replace(category='sequential', name='test_sequence')
resp = self.client.post(reverse('delete_item'),
json.dumps({'id': del_loc.url()}), "application/json")
self.assertEqual(200, resp.status_code)
# delete a chapter
del_loc = loc._replace(category='chapter', name='chapter_2')
resp = self.client.post(reverse('delete_item'),
json.dumps({'id': del_loc.url()}), "application/json")
self.assertEqual(200, resp.status_code)
def test_import_metadata_with_attempts_empty_string(self): def test_import_metadata_with_attempts_empty_string(self):
import_from_xml(modulestore(), 'common/test/data/', ['simple']) import_from_xml(modulestore(), 'common/test/data/', ['simple'])
module_store = modulestore('direct') module_store = modulestore('direct')
......
...@@ -120,6 +120,12 @@ def howitworks(request): ...@@ -120,6 +120,12 @@ def howitworks(request):
else: else:
return render_to_response('howitworks.html', {}) return render_to_response('howitworks.html', {})
# static/proof-of-concept views
def ux_alerts(request):
return render_to_response('ux-alerts.html', {})
# ==== Views for any logged-in user ================================== # ==== Views for any logged-in user ==================================
......
if (!window.CmsUtils) window.CmsUtils = {};
var $body; var $body;
var $modal; var $modal;
var $modalCover; var $modalCover;
...@@ -48,6 +50,10 @@ $(document).ready(function () { ...@@ -48,6 +50,10 @@ $(document).ready(function () {
(e).preventDefault(); (e).preventDefault();
}); });
// alerts/notifications - manual close
$('.action-alert-close, .alert.has-actions .nav-actions a').bind('click', hideAlert);
$('.action-notification-close').bind('click', hideNotification);
// nav - dropdown related // nav - dropdown related
$body.click(function (e) { $body.click(function (e) {
$('.nav-dropdown .nav-item .wrapper-nav-sub').removeClass('is-shown'); $('.nav-dropdown .nav-item .wrapper-nav-sub').removeClass('is-shown');
...@@ -87,7 +93,7 @@ $(document).ready(function () { ...@@ -87,7 +93,7 @@ $(document).ready(function () {
$('a[rel*="view"][href^="#"]').bind('click', smoothScrollLink); $('a[rel*="view"][href^="#"]').bind('click', smoothScrollLink);
// tender feedback window scrolling // tender feedback window scrolling
$('a.show-tender').bind('click', smoothScrollTop); $('a.show-tender').bind('click', window.CmsUtils.smoothScrollTop);
// toggling footer additional support // toggling footer additional support
$('.cta-show-sock').bind('click', toggleSock); $('.cta-show-sock').bind('click', toggleSock);
...@@ -168,7 +174,10 @@ function smoothScrollLink(e) { ...@@ -168,7 +174,10 @@ function smoothScrollLink(e) {
}); });
} }
function smoothScrollTop(e) { // On AWS instances, this base.js gets wrapped in a separate scope as part of Django static
// pipelining (note, this doesn't happen on local runtimes). So if we set it on window,
// when we can access it from other scopes (namely Course Advanced Settings).
window.CmsUtils.smoothScrollTop = function (e) {
(e).preventDefault(); (e).preventDefault();
$.smoothScroll({ $.smoothScroll({
...@@ -538,6 +547,17 @@ function removeDateSetter(e) { ...@@ -538,6 +547,17 @@ function removeDateSetter(e) {
$block.find('.time').val(''); $block.find('.time').val('');
} }
function hideNotification(e) {
(e).preventDefault();
$(this).closest('.wrapper-notification').removeClass('is-shown').addClass('is-hiding').attr('aria-hidden','true');
}
function hideAlert(e) {
(e).preventDefault();
$(this).closest('.wrapper-alert').removeClass('is-shown');
}
function showToastMessage(message, $button, lifespan) { function showToastMessage(message, $button, lifespan) {
var $toast = $('<div class="toast-notification"></div>'); var $toast = $('<div class="toast-notification"></div>');
var $closeBtn = $('<a href="#" class="close-button">×</a>'); var $closeBtn = $('<a href="#" class="close-button">×</a>');
......
...@@ -101,13 +101,13 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({ ...@@ -101,13 +101,13 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({
}); });
}, },
showMessage: function (type) { showMessage: function (type) {
this.$el.find(".message-status").removeClass("is-shown"); $(".wrapper-alert").removeClass("is-shown");
if (type) { if (type) {
if (type === this.error_saving) { if (type === this.error_saving) {
this.$el.find(".message-status.error").addClass("is-shown"); $(".wrapper-alert-error").addClass("is-shown").attr('aria-hidden','false');
} }
else if (type === this.successful_changes) { else if (type === this.successful_changes) {
this.$el.find(".message-status.confirm").addClass("is-shown"); $(".wrapper-alert-confirmation").addClass("is-shown").attr('aria-hidden','false');
this.hideSaveCancelButtons(); this.hideSaveCancelButtons();
} }
} }
...@@ -117,17 +117,20 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({ ...@@ -117,17 +117,20 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({
} }
}, },
showSaveCancelButtons: function(event) { showSaveCancelButtons: function(event) {
if (!this.buttonsVisible) { if (!this.notificationBarShowing) {
this.$el.find(".message-status").removeClass("is-shown"); this.$el.find(".message-status").removeClass("is-shown");
$('.wrapper-notification').addClass('is-shown'); $('.wrapper-notification').removeClass('is-hiding').addClass('is-shown').attr('aria-hidden','false');
this.buttonsVisible = true; this.notificationBarShowing = true;
} }
}, },
hideSaveCancelButtons: function() { hideSaveCancelButtons: function() {
$('.wrapper-notification').removeClass('is-shown'); if (this.notificationBarShowing) {
this.buttonsVisible = false; $('.wrapper-notification').removeClass('is-shown').addClass('is-hiding').attr('aria-hidden','true');
this.notificationBarShowing = false;
}
}, },
saveView : function(event) { saveView : function(event) {
window.CmsUtils.smoothScrollTop(event);
// TODO one last verification scan: // TODO one last verification scan:
// call validateKey on each to ensure proper format // call validateKey on each to ensure proper format
// check for dupes // check for dupes
...@@ -146,6 +149,7 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({ ...@@ -146,6 +149,7 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({
}); });
}, },
revertView : function(event) { revertView : function(event) {
event.preventDefault();
var self = event.data; var self = event.data;
self.model.deleteKeys = []; self.model.deleteKeys = [];
self.model.clear({silent : true}); self.model.clear({silent : true});
......
...@@ -25,7 +25,7 @@ a { ...@@ -25,7 +25,7 @@ a {
@include transition(color 0.25s ease-in-out); @include transition(color 0.25s ease-in-out);
&:hover { &:hover {
color: #cb9c40; color: $orange-d1;
} }
} }
...@@ -50,8 +50,69 @@ h1 { ...@@ -50,8 +50,69 @@ h1 {
// ==================== // ====================
// typography - basic
.title-1, .title-2, .title-3, .title-4, .title-5, .title-6 {
font-weight: 600;
color: $gray-d3;
margin: 0;
padding: 0;
}
.title-1 {
@include font-size(32);
margin-bottom: ($baseline*1.5);
}
.title-2 {
@include font-size(24);
margin-bottom: $baseline;
}
.title-3 {
@include font-size(18);
margin-bottom: ($baseline/2);
}
.title-4 {
@include font-size(14);
margin-bottom: $baseline;
font-weight: 500
}
.title-5 {
@include font-size(14);
color: $gray-l1;
margin-bottom: $baseline;
font-weight: 500
}
.title-6 {
@include font-size(14);
color: $gray-l2;
margin-bottom: $baseline;
font-weight: 500
}
p, ul, ol, dl {
margin-bottom: ($baseline/2);
&:last-child {
margin-bottom: 0;
}
}
// ====================
// layout - basic
.wrapper-view {
}
// ====================
// layout - basic page header // layout - basic page header
.wrapper-mast { .wrapper-mast {
margin: ($baseline*1.5) 0 0 0;
padding: 0 $baseline; padding: 0 $baseline;
position: relative; position: relative;
...@@ -62,7 +123,7 @@ h1 { ...@@ -62,7 +123,7 @@ h1 {
max-width: $fg-max-width; max-width: $fg-max-width;
min-width: $fg-min-width; min-width: $fg-min-width;
width: flex-grid(12); width: flex-grid(12);
margin: ($baseline*1.5) auto $baseline auto; margin: 0 auto $baseline auto;
color: $gray-d2; color: $gray-d2;
} }
...@@ -284,18 +345,33 @@ h1 { ...@@ -284,18 +345,33 @@ h1 {
margin: 0 0 ($baseline/2) 0; margin: 0 0 ($baseline/2) 0;
} }
.title-4 { header {
@include clearfix();
.title-2 {
width: flex-grid(5, 12);
margin: 0 flex-gutter() 0 0;
float: left;
} }
.title-5 { .tip {
@include font-size(13);
width: flex-grid(7, 12);
float: right;
margin-top: ($baseline/2);
text-align: right;
color: $gray-l2;
}
} }
} }
// layout - supplemental content // layout - supplemental content
.content-supplementary { .content-supplementary {
> section {
margin: 0 0 $baseline 0;
}
.bit { .bit {
@include font-size(13); @include font-size(13);
margin: 0 0 $baseline 0; margin: 0 0 $baseline 0;
...@@ -761,7 +837,7 @@ body.js { ...@@ -761,7 +837,7 @@ body.js {
// ==================== // ====================
// works in progress // works in progress & testing
body.hide-wip { body.hide-wip {
.wip-box { .wip-box {
......
...@@ -15,17 +15,17 @@ ...@@ -15,17 +15,17 @@
// mixins - grandfathered // mixins - grandfathered
@mixin button { @mixin button {
display: inline-block; display: inline-block;
padding: 4px 20px 6px; padding: ($baseline/5) $baseline ($baseline/4);
font-size: 14px; @include font-size(14);
font-weight: 700; font-weight: 700;
@include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset, 0 0 0 rgba(0, 0, 0, 0)); @include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset, 0 0 0 rgba(0, 0, 0, 0));
@include transition(background-color .15s, box-shadow .15s); @include transition(background-color .15s, box-shadow .15s);
&.disabled { &.disabled {
border: 1px solid $lightGrey !important; border: 1px solid $gray-l1 !important;
border-radius: 3px !important; border-radius: 3px !important;
background: $lightGrey !important; background: $gray-l1 !important;
color: $darkGrey !important; color: $gray-d1 !important;
pointer-events: none; pointer-events: none;
cursor: none; cursor: none;
&:hover { &:hover {
...@@ -38,31 +38,110 @@ ...@@ -38,31 +38,110 @@
} }
} }
@mixin green-button {
@include button;
border: 1px solid $green-d1;
border-radius: 3px;
@include linear-gradient(top, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0));
background-color: $green;
@include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset);
color: $white;
&:hover {
background-color: $green-s1;
color: $white;
}
&.disabled {
border: 1px solid $green-l3 !important;
background: $green-l3 !important;
color: $white !important;
@include box-shadow(none);
}
}
@mixin blue-button { @mixin blue-button {
@include button; @include button;
border: 1px solid #437fbf; border: 1px solid $blue-d1;
border-radius: 3px; border-radius: 3px;
@include linear-gradient(top, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0)); @include linear-gradient(top, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0));
background-color: $blue; background-color: $blue;
color: #fff; color: $white;
&:hover, &.active { &:hover, &.active {
background-color: #62aaf5; background-color: $blue-s2;
color: #fff; color: $white;
}
&.disabled {
border: 1px solid $blue-l3 !important;
background: $blue-l3 !important;
color: $white !important;
@include box-shadow(none);
} }
} }
@mixin green-button { @mixin red-button {
@include button; @include button;
border: 1px solid #0d7011; border: 1px solid $red-d1;
border-radius: 3px; border-radius: 3px;
@include linear-gradient(top, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0)); @include linear-gradient(top, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0));
background-color: $green; background-color: $red;
color: #fff; color: $white;
&:hover, &.active {
background-color: $red-s1;
color: $white;
}
&.disabled {
border: 1px solid $red-l3 !important;
background: $red-l3 !important;
color: $white !important;
@include box-shadow(none);
}
}
@mixin pink-button {
@include button;
border: 1px solid $pink-d1;
border-radius: 3px;
@include linear-gradient(top, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0));
background-color: $pink;
color: $white;
&:hover, &.active {
background-color: $pink-s1;
color: $white;
}
&.disabled {
border: 1px solid $pink-l3 !important;
background: $pink-l3 !important;
color: $white !important;
@include box-shadow(none);
}
}
@mixin orange-button {
@include button;
border: 1px solid $orange-d1;
border-radius: 3px;
@include linear-gradient(top, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0) 60%);
background-color: $orange;
@include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset);
color: $gray-d2;
&:hover { &:hover {
background-color: #129416; background-color: $orange-s2;
color: #fff; color: $gray-d2;
}
&.disabled {
border: 1px solid $orange-l3 !important;
background: $orange-l2 !important;
color: $gray-l1 !important;
@include box-shadow(none);
} }
} }
...@@ -82,24 +161,9 @@ ...@@ -82,24 +161,9 @@
} }
} }
@mixin orange-button {
@include button;
border: 1px solid #bda046;
border-radius: 3px;
@include linear-gradient(top, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0) 60%);
background-color: #edbd3c;
@include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset);
color: #3c3c3c;
&:hover {
background-color: #ffcd46;
color: #3c3c3c;
}
}
@mixin grey-button { @mixin grey-button {
@include button; @include button;
border: 1px solid $darkGrey; border: 1px solid $gray-d2;
border-radius: 3px; border-radius: 3px;
@include linear-gradient(top, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0)); @include linear-gradient(top, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0));
background-color: #d1dae3; background-color: #d1dae3;
...@@ -127,39 +191,17 @@ ...@@ -127,39 +191,17 @@
} }
} }
@mixin green-button {
@include button;
border: 1px solid $darkGreen;
border-radius: 3px;
@include linear-gradient(top, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0));
background-color: $green;
@include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset);
color: #fff;
&:hover {
background-color: $brightGreen;
color: #fff;
}
&.disabled {
border: 1px solid $disabledGreen !important;
background: $disabledGreen !important;
color: #fff !important;
@include box-shadow(none);
}
}
@mixin dark-grey-button { @mixin dark-grey-button {
@include button; @include button;
border: 1px solid #1c1e20; border: 1px solid $gray-d2;
border-radius: 3px; border-radius: 3px;
background: -webkit-linear-gradient(top, rgba(255, 255, 255, .2), rgba(255, 255, 255, 0)) $extraDarkGrey; background: -webkit-linear-gradient(top, rgba(255, 255, 255, .2), rgba(255, 255, 255, 0)) $gray-d1;
box-shadow: 0 1px 0 rgba(255, 255, 255, .2) inset; box-shadow: 0 1px 0 rgba(255, 255, 255, .2) inset;
color: #fff; color: $white;
&:hover { &:hover {
background-color: #595f64; background-color: $gray-d4;
color: #fff; color: $white;
} }
} }
...@@ -296,6 +338,9 @@ ...@@ -296,6 +338,9 @@
} }
} }
// ====================
// sunsetted mixins
@mixin active { @mixin active {
@include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0)); @include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0));
background-color: rgba(255, 255, 255, .3); background-color: rgba(255, 255, 255, .3);
......
...@@ -113,7 +113,7 @@ $green-u1: desaturate($green,15%); ...@@ -113,7 +113,7 @@ $green-u1: desaturate($green,15%);
$green-u2: desaturate($green,30%); $green-u2: desaturate($green,30%);
$green-u3: desaturate($green,45%); $green-u3: desaturate($green,45%);
$yellow: rgb(231, 214, 143); $yellow: rgb(237, 189, 60);
$yellow-l1: tint($yellow,20%); $yellow-l1: tint($yellow,20%);
$yellow-l2: tint($yellow,40%); $yellow-l2: tint($yellow,40%);
$yellow-l3: tint($yellow,60%); $yellow-l3: tint($yellow,60%);
...@@ -149,8 +149,13 @@ $orange-u3: desaturate($orange,45%); ...@@ -149,8 +149,13 @@ $orange-u3: desaturate($orange,45%);
$shadow: rgba(0,0,0,0.2); $shadow: rgba(0,0,0,0.2);
$shadow-l1: rgba(0,0,0,0.1); $shadow-l1: rgba(0,0,0,0.1);
$shadow-l2: rgba(0,0,0,0.05);
$shadow-d1: rgba(0,0,0,0.4); $shadow-d1: rgba(0,0,0,0.4);
// specific UI
$notification-height: ($baseline*10);
// colors - inherited // colors - inherited
$baseFontColor: $gray-d2; $baseFontColor: $gray-d2;
$offBlack: #3c3c3c; $offBlack: #3c3c3c;
......
@mixin bounce-in { // studio animations & keyframes
// ====================
// rotate clockwise
@mixin rotateClockwise {
0% {
@include transform(rotate(0deg));
}
100% {
@include transform(rotate(360deg));
}
}
@-moz-keyframes rotateClockwise { @include rotateClockwise(); }
@-webkit-keyframes rotateClockwise { @include rotateClockwise(); }
@-o-keyframes rotateClockwise { @include rotateClockwise(); }
@keyframes rotateClockwise { @include rotateClockwise();}
@mixin anim-rotateClockwise($duration, $timing: ease-in-out, $count: 1, $delay: 0) {
@include animation-name(rotateClockwise);
@include animation-duration($duration);
@include animation-delay($delay);
@include animation-timing-function($timing);
@include animation-iteration-count($count);
@include animation-fill-mode(both);
}
// ====================
// notifications slide up
@mixin notificationsSlideUp {
0% {
@include transform(translateY(0));
}
90% {
@include transform(translateY(-($notification-height)));
}
100% {
@include transform(translateY(-($notification-height*0.99)));
}
}
@-moz-keyframes notificationsSlideUp { @include notificationsSlideUp(); }
@-webkit-keyframes notificationsSlideUp { @include notificationsSlideUp(); }
@-o-keyframes notificationsSlideUp { @include notificationsSlideUp(); }
@keyframes notificationsSlideUp { @include notificationsSlideUp();}
@mixin anim-notificationsSlideUp($duration, $timing: ease-in-out, $count: 1, $delay: 0) {
@include animation-name(notificationsSlideUp);
@include animation-duration($duration);
@include animation-delay($delay);
@include animation-timing-function($timing);
@include animation-iteration-count($count);
@include animation-fill-mode(both);
}
// ====================
// notifications slide down
@mixin notificationsSlideDown {
0% {
@include transform(translateY(-($notification-height*0.99)));
}
10% {
@include transform(translateY(-($notification-height)));
}
100% {
@include transform(translateY(0));
}
}
@-moz-keyframes notificationsSlideDown { @include notificationsSlideDown(); }
@-webkit-keyframes notificationsSlideDown { @include notificationsSlideDown(); }
@-o-keyframes notificationsSlideDown { @include notificationsSlideDown(); }
@keyframes notificationsSlideDown { @include notificationsSlideDown();}
@mixin anim-notificationsSlideDown($duration, $timing: ease-in-out, $count: 1, $delay: 0) {
@include animation-name(notificationsSlideDown);
@include animation-duration($duration);
@include animation-delay($delay);
@include animation-timing-function($timing);
@include animation-iteration-count($count);
@include animation-fill-mode(both);
}
// ====================
// notifications slide up then down
@mixin notificationsSlideUpDown {
0%, 100% {
@include transform(translateY(0));
}
15%, 85% {
@include transform(translateY(-($notification-height)));
}
20%, 80% {
@include transform(translateY(-($notification-height*0.99)));
}
}
@-moz-keyframes notificationsSlideUpDown { @include notificationsSlideUpDown(); }
@-webkit-keyframes notificationsSlideUpDown { @include notificationsSlideUpDown(); }
@-o-keyframes notificationsSlideUpDown { @include notificationsSlideUpDown(); }
@keyframes notificationsSlideUpDown { @include notificationsSlideUpDown();}
@mixin anim-notificationsSlideUpDown($duration, $timing: ease-in-out, $count: 1, $delay: 0) {
@include animation-name(notificationsSlideUpDown);
@include animation-duration($duration);
@include animation-delay($delay);
@include animation-timing-function($timing);
@include animation-iteration-count($count);
@include animation-fill-mode(both);
}
// ====================
// bounce in
@mixin bounceIn {
0% {
opacity: 0;
@include transform(scale(0.3));
}
50% {
opacity: 1;
@include transform(scale(1.05));
}
100% {
@include transform(scale(1));
}
}
@-moz-keyframes bounceIn { @include bounceIn(); }
@-webkit-keyframes bounceIn { @include bounceIn(); }
@-o-keyframes bounceIn { @include bounceIn(); }
@keyframes bounceIn { @include bounceIn();}
@mixin anim-bounceIn($duration, $timing: ease-in-out, $count: 1, $delay: 0) {
@include animation-name(bounceIn);
@include animation-duration($duration);
@include animation-delay($delay);
@include animation-timing-function($timing);
@include animation-iteration-count($count);
@include animation-fill-mode(both);
}
// ====================
// bounce in
@mixin bounceOut {
0% { 0% {
opacity: 0; opacity: 0;
@include transform(scale(.3)); @include transform(scale(0.3));
} }
50% { 50% {
...@@ -12,16 +171,32 @@ ...@@ -12,16 +171,32 @@
100% { 100% {
@include transform(scale(1)); @include transform(scale(1));
} }
0% {
@include transform(scale(1));
}
50% {
opacity: 1;
@include transform(scale(1.05));
}
100% {
opacity: 0;
@include transform(scale(0.3));
}
} }
@-moz-keyframes bounce-in { @include bounce-in(); } @-moz-keyframes bounceOut { @include bounceOut(); }
@-webkit-keyframes bounce-in { @include bounce-in(); } @-webkit-keyframes bounceOut { @include bounceOut(); }
@-o-keyframes bounce-in { @include bounce-in(); } @-o-keyframes bounceOut { @include bounceOut(); }
@keyframes bounce-in { @include bounce-in();} @keyframes bounceOut { @include bounceOut();}
@mixin bounce-in-animation($duration, $timing: ease-in-out) { @mixin anim-bounceOut($duration, $timing: ease-in-out, $count: 1, $delay: 0) {
@include animation-name(bounce-in); @include animation-name(bounceOut);
@include animation-duration($duration); @include animation-duration($duration);
@include animation-delay($delay);
@include animation-timing-function($timing); @include animation-timing-function($timing);
@include animation-iteration-count($count);
@include animation-fill-mode(both); @include animation-fill-mode(both);
} }
\ No newline at end of file
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
// bourbon libs and resets // bourbon libs and resets
@import 'bourbon/bourbon'; @import 'bourbon/bourbon';
@import 'bourbon/addons/button'; @import 'bourbon/addons/button';
@import "variables";
@import 'vendor/normalize'; @import 'vendor/normalize';
@import 'reset'; @import 'reset';
......
// studio - elements - alerts, notifications, prompts // studio alerts, prompts and notifications
// ====================
// shared
.wrapper-notification, .wrapper-alert, .prompt {
@include box-sizing(border-box);
.copy {
@include font-size(13);
}
}
.wrapper-notification, .wrapper-alert, .prompt {
background: $gray-d3;
.copy {
color: $gray-l2;
.title {
color: $white;
}
.nav-actions {
.action-primary {
color: $gray-d4;
}
}
}
}
.alert, .notification, .prompt {
// types - confirm
&.confirm {
.nav-actions .action-primary {
@include blue-button();
border-color: $blue-d2;
}
a {
color: $blue;
&:hover {
color: $blue-s2;
}
}
}
// types - warning
&.warning {
.nav-actions .action-primary {
@include orange-button();
border-color: $orange-d2;
color: $gray-d4;
}
a {
color: $orange;
&:hover {
color: $orange-s2;
}
}
}
// types - error
&.error {
.nav-actions .action-primary {
@include red-button();
border-color: $red-d2;
}
a {
color: $red-l1;
&:hover {
color: $red;
}
}
}
// types - announcement
&.announcement {
.nav-actions .action-primary {
@include blue-button();
border-color: $blue-d2;
}
a {
color: $blue;
&:hover {
color: $blue-s2;
}
}
}
// types - confirmation
&.confirmation {
.nav-actions .action-primary {
@include green-button();
border-color: $green-d2;
}
a {
color: $green;
&:hover {
color: $green-s2;
}
}
}
// types - step required
&.step-required {
.nav-actions .action-primary {
border-color: $pink-d2;
@include pink-button();
}
a {
color: $pink;
&:hover {
color: $pink-s1;
}
}
}
}
// prompts
.wrapper-prompt {
@include transition(all 0.05s ease-in-out);
position: fixed;
top: 0;
background: $black-t0;
width: 100%;
height: 100%;
text-align: center;
z-index: 10000;
&:before {
content: '';
display: inline-block;
height: 100%;
vertical-align: middle;
margin-right: -0.25em; /* Adjusts for spacing */
}
.prompt {
@include border-radius(($baseline/5));
@include box-shadow(0 0 3px $shadow-d1);
display: inline-block;
vertical-align: middle;
width: $baseline*17.5;
border: 4px solid $black;
text-align: left;
.copy {
border-top: 4px solid $blue;
padding: $baseline;
}
.nav-actions {
@include box-shadow(inset 0 1px 2px $shadow-d1);
border-top: 1px solid $black-t1;
padding: ($baseline*0.75) $baseline;
background: $gray-d4;
.nav-item {
display: inline-block;
margin-right: ($baseline*0.75);
&:last-child {
margin-right: 0;
}
}
.action-primary {
@include font-size(13);
font-weight: 600;
}
.action-secondary {
@include font-size(13);
}
}
}
// types of prompts - error
.prompt.error {
.icon-error {
color: $red-l1;
}
.copy {
border-top-color: $red-l1;
}
}
// types of prompts - confirmation
.prompt.confirmation {
.icon-error {
color: $green;
}
.copy {
border-top-color: $green;
}
}
// types of prompts - error
.prompt.warning {
.icon-warning {
color: $orange;
}
.copy {
border-top-color: $orange;
}
}
}
// ==================== // ====================
// notifications // notifications
.wrapper-notification { .wrapper-notification {
@include clearfix(); @include clearfix();
@include box-sizing(border-box); @include box-shadow(0 -1px 3px $shadow, inset 0 3px 1px $blue);
@include transition (bottom 2.0s ease-in-out 5s);
@include box-shadow(0 -1px 2px rgba(0,0,0,0.1));
position: fixed; position: fixed;
bottom: -100px; bottom: 0;
z-index: 1000; z-index: 1000;
width: 100%; width: 100%;
overflow: hidden; padding: $baseline ($baseline*2);
opacity: 0;
border-top: 1px solid $darkGrey;
padding: 20px 40px;
&.is-shown {
bottom: 0;
opacity: 1.0;
}
&.wrapper-notification-warning { &.wrapper-notification-warning {
border-color: shade($yellow, 25%); @include box-shadow(0 -1px 3px $shadow, inset 0 3px 1px $orange);
background: tint($yellow, 25%);
.icon-warning {
color: $orange;
}
} }
&.wrapper-notification-error { &.wrapper-notification-error {
border-color: shade($red, 50%); @include box-shadow(0 -1px 3px $shadow, inset 0 3px 1px $red-l1);
background: tint($red, 20%);
color: $white; .icon-error {
color: $red-l1;
}
} }
&.wrapper-notification-confirm { &.wrapper-notification-confirmation {
border-color: shade($green, 30%); @include box-shadow(0 -1px 3px $shadow, inset 0 3px 1px $green);
background: tint($green, 40%);
color: shade($green, 30%); .icon-confirmation {
color: $green;
}
}
&.wrapper-notification-saving {
@include box-shadow(0 -1px 3px $shadow, inset 0 3px 1px $pink);
}
// shorter/status notifications
&.wrapper-notification-status {
@include border-top-radius(3px);
width: ($baseline*8);
right: ($baseline);
border: 4px solid $black;
border-bottom: none;
padding: ($baseline/2) $baseline;
.notification {
@include box-sizing(border-box);
@include clearfix();
width: 100%;
max-width: none;
min-width: none;
.icon, .copy {
float: none;
display: inline-block;
vertical-align: middle;
}
.icon {
width: $baseline;
height: ($baseline*1.25);
margin-right: ($baseline*0.75);
line-height: 3rem;
}
.copy {
}
}
}
// help notifications
&.wrapper-notification-help {
@include border-top-radius(3px);
width: ($baseline*14);
right: ($baseline);
border: 4px solid $black;
border-bottom: none;
padding: $baseline;
.notification {
@include box-sizing(border-box);
@include clearfix();
width: 100%;
max-width: none;
min-width: none;
.icon-help {
width: $baseline;
margin-right: ($baseline*0.75);
}
.action-notification-close {
right: 0;
}
.copy {
width: ($baseline*10);
}
}
} }
} }
.notification { .notification {
@include box-sizing(border-box); @include box-sizing(border-box);
@include clearfix();
margin: 0 auto; margin: 0 auto;
width: flex-grid(12); width: flex-grid(12);
max-width: $fg-max-width; max-width: $fg-max-width;
min-width: $fg-min-width; min-width: $fg-min-width;
.copy { strong {
font-weight: 700;
}
.icon, .copy {
float: left; float: left;
width: flex-grid(9, 12); display: inline-block;
vertical-align: middle;
}
.icon {
@include transition (color 0.5s ease-in-out);
@include font-size(22);
width: flex-grid(1, 12);
height: ($baseline*1.25);
margin-right: flex-gutter(); margin-right: flex-gutter();
margin-top: 5px; text-align: right;
font-size: 14px; color: $white;
}
.copy {
@include font-size(13);
width: flex-grid(10, 12);
color: $gray-l2;
.title {
@include font-size(14);
margin-bottom: 0;
color: $white;
}
}
// with actions
&.has-actions {
.icon { .icon {
display: inline-block; width: flex-grid(1, 12);
vertical-align: top; }
margin-right: 5px;
font-size: 20px; .copy {
width: flex-grid(7, 12);
margin-right: flex-gutter();
} }
p { .nav-actions {
width: flex-grid(8, 9); width: flex-grid(4, 12);
float: right;
margin-top: ($baseline/4);
text-align: right;
.nav-item {
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: middle;
margin-right: ($baseline/2);
&:last-child {
margin-right: 0;
}
}
}
.action-primary {
@include blue-button();
@include font-size(13);
border-color: $blue-d2;
font-weight: 600;
}
.action-secondary {
@include font-size(13);
}
}
&.confirmation {
.copy {
margin-top: ($baseline/5);
}
}
&.saving {
.icon-saving {
@include anim-rotateClockwise(3s, linear, infinite);
width: 22px;
}
.copy p {
@include text-sr();
}
}
}
// ====================
// alerts
.wrapper-alert {
@include box-sizing(border-box);
@include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $blue);
position: relative;
z-index: 100;
overflow: hidden;
width: 100%;
border-top: 1px solid $black;
padding: $baseline ($baseline*2) ($baseline*1.5) ($baseline*2);
background: $gray-d3;
// needed since page load is very slow
display: none;
// needed since page load is very slow
&.is-shown {
display: block;
}
&.wrapper-alert-warning {
@include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $orange);
.icon-warning {
color: $orange;
}
}
&.wrapper-alert-error {
@include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $red-l1);
.icon-error {
color: $red-l1;
}
}
&.wrapper-alert-confirmation {
@include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $green);
.icon-confirmation {
color: $green;
}
}
&.wrapper-alert-announcement {
@include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $blue);
.icon-announcement {
color: $blue;
}
}
&.wrapper-alert-step-required {
@include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $pink);
.icon-step-required {
color: $pink;
}
}
}
// adopted alerts
.alert {
@include font-size(14);
@include box-sizing(border-box);
@include clearfix();
margin: 0 auto;
width: flex-grid(12);
max-width: $fg-max-width;
min-width: $fg-min-width;
color: $white;
strong {
font-weight: 700;
}
.icon, .copy {
float: left;
}
.icon {
@include transition (color 0.5s ease-in-out);
@include font-size(22);
width: flex-grid(1, 12);
margin: ($baseline/4) flex-gutter() 0 0;
text-align: right;
}
.copy {
@include font-size(13);
width: flex-grid(10, 12);
color: $gray-l2;
.title {
margin-bottom: 0;
color: $white;
}
}
// with actions
&.has-actions {
.icon {
width: flex-grid(1, 12);
} }
.copy {
width: flex-grid(7, 12);
margin-right: flex-gutter();
} }
.actions { .nav-actions {
width: flex-grid(4, 12);
float: right; float: right;
width: flex-grid(3, 12);
margin-top: ($baseline/2); margin-top: ($baseline/2);
text-align: right; text-align: right;
li { .nav-item {
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
margin-right: 10px; margin-right: ($baseline/2);
&:last-child { &:last-child {
margin-right: 0; margin-right: 0;
} }
.action-primary {
@include font-size(13);
font-weight: 600;
} }
.save-button { .action-secondary {
@include blue-button; @include font-size(13);
}
} }
}
}
// with cancel
.action-alert-close {
@include border-bottom-radius(($baseline/5));
position: absolute;
top: -($baseline/10);
right: $baseline;
padding: ($baseline/4) ($baseline/2) 0 ($baseline/2);
background: $gray-d4;
text-align: center;
.cancel-button { .label {
@include white-button; @include text-sr();
} }
.icon {
@include font-size(14);
color: $white;
width: auto;
margin: 0;
padding: 2px;
} }
strong { &:hover {
font-weight: 700; background: $gray-d1;
}
} }
} }
// adopted alerts // ====================
.alert {
// js enabled
.js {
// prompt set-up
.wrapper-prompt {
visibility: hidden;
pointer-events: none;
.prompt {
opacity: 0;
}
}
// prompt showing/hiding
&.prompt-is-shown {
.wrapper-view {
-webkit-filter: blur(2px) grayscale(25%);
filter: blur(2px) grayscale(25%);
}
.wrapper-prompt.is-shown {
visibility: visible;
pointer-events: auto;
.prompt {
@include anim-bounceIn(0.5s);
opacity: 1.0;
}
}
}
// alert showing/hiding
.wrapper-alert {
display: none;
&.is-shown {
display: block;
}
}
// notification showing/hiding
.wrapper-notification {
bottom: -($notification-height);
// varying animations
&.is-shown {
@include anim-notificationsSlideUp(1s);
}
&.is-hiding {
@include anim-notificationsSlideDown(0.25s);
}
&.is-fleeting {
@include anim-notificationsSlideUpDown(2s);
}
}
}
// ====================
// temporary
body.uxdesign.alerts {
.content-primary, .content-supplementary {
@include box-sizing(border-box);
float: left;
}
.content-primary {
@extend .window;
width: flex-grid(12, 12);
margin-right: flex-gutter();
padding: $baseline ($baseline*1.5);
> section {
margin-bottom: ($baseline*2);
&:last-child {
margin-bottom: 0;
}
}
ul {
li {
@include clearfix();
width: flex-grid(12, 12);
margin-bottom: ($baseline/4);
border-bottom: 1px solid $gray-l4;
padding-bottom: ($baseline/4);
&:last-child {
margin-bottom: 0;
border-bottom: none;
padding-bottom: 0;
}
a {
float: left;
width: flex-grid(5, 12);
margin-right: flex-gutter();
}
}
}
}
}
// ====================
// artifact styles
.main-wrapper {
.alert {
padding: 15px 20px; padding: 15px 20px;
margin-bottom: 30px; margin-bottom: 30px;
border-radius: 3px; border-radius: 3px;
...@@ -111,7 +735,8 @@ ...@@ -111,7 +735,8 @@
.alert-message { .alert-message {
float: left; float: left;
margin-top: 4px; margin: 4px 0 0 0;
color: $gray-d3;
} }
strong { strong {
...@@ -125,11 +750,12 @@ ...@@ -125,11 +750,12 @@
@include orange-button; @include orange-button;
} }
} }
}
} }
body.error { body.error {
background: $darkGrey; background: $gray-d4;
color: #3c3c3c; color: $gray-d3;
.primary-header { .primary-header {
display: none; display: none;
...@@ -140,7 +766,7 @@ body.error { ...@@ -140,7 +766,7 @@ body.error {
margin: 150px auto; margin: 150px auto;
padding: 60px 50px 90px; padding: 60px 50px 90px;
border-radius: 3px; border-radius: 3px;
background: #fff; background: $white;
text-align: center; text-align: center;
} }
...@@ -149,7 +775,7 @@ body.error { ...@@ -149,7 +775,7 @@ body.error {
margin: 0; margin: 0;
font-size: 60px; font-size: 60px;
font-weight: 300; font-weight: 300;
color: #3c3c3c; color: $gray-d3;
} }
.description { .description {
......
...@@ -5,12 +5,12 @@ ...@@ -5,12 +5,12 @@
margin: 0; margin: 0;
padding: $baseline; padding: $baseline;
border-bottom: 1px solid $gray; border-bottom: 1px solid $gray;
@include box-shadow(0 1px 5px 0 rgba(0,0,0, 0.1)); @include box-shadow(0 1px 5px 0 rgba(0,0,0, 0.2));
background: $white; background: $white;
height: 76px; height: 76px;
position: relative; position: relative;
width: 100%; width: 100%;
z-index: 10; z-index: 1000;
a { a {
color: $baseFontColor; color: $baseFontColor;
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
body.signup, body.signin { body.signup, body.signin {
.wrapper-content { .wrapper-content {
margin: 0; margin: ($baseline*1.5) 0 0 0;
padding: 0 $baseline; padding: 0 $baseline;
position: relative; position: relative;
width: 100%; width: 100%;
......
...@@ -53,8 +53,13 @@ ...@@ -53,8 +53,13 @@
document.location.protocol + '//www.youtube.com/player_api">\x3C/script>'); document.location.protocol + '//www.youtube.com/player_api">\x3C/script>');
</script> </script>
<!-- view -->
<div class="wrapper wrapper-view">
<%include file="widgets/header.html" /> <%include file="widgets/header.html" />
<%block name="view_alerts"></%block>
<%block name="view_banners"></%block>
<%block name="content"></%block> <%block name="content"></%block>
% if user.is_authenticated(): % if user.is_authenticated():
...@@ -63,6 +68,11 @@ ...@@ -63,6 +68,11 @@
<%include file="widgets/footer.html" /> <%include file="widgets/footer.html" />
<%include file="widgets/tender.html" /> <%include file="widgets/tender.html" />
<%block name="view_notifications"></%block>
</div>
<%block name="view_prompts"></%block>
<%block name="jsextra"></%block> <%block name="jsextra"></%block>
</body> </body>
......
...@@ -69,7 +69,7 @@ ...@@ -69,7 +69,7 @@
<article class="my-classes"> <article class="my-classes">
% if user.is_active: % if user.is_active:
<ul class="class-list"> <ul class="class-list">
%for course, url, lms_link in sorted(courses, key=lambda s: s[0].lower()): %for course, url, lms_link in sorted(courses, key=lambda s: s[0].lower() if s[0] is not None else ''):
<li> <li>
<a class="class-link" href="${url}" class="class-name"> <a class="class-link" href="${url}" class="class-name">
<span class="class-name">${course}</span> <span class="class-name">${course}</span>
......
...@@ -42,13 +42,17 @@ editor.render(); ...@@ -42,13 +42,17 @@ editor.render();
</%block> </%block>
<%block name="content"> <%block name="content">
<div class="wrapper-content wrapper"> <div class="wrapper-mast wrapper">
<section class="content"> <header class="mast has-subtitle">
<header class="page"> <div class="title">
<span class="title-sub">Settings</span> <span class="title-sub">Settings</span>
<h1 class="title-1">Advanced Settings</h1> <h1 class="title-1">Advanced Settings</h1>
</div>
</header> </header>
</div>
<div class="wrapper-content wrapper">
<section class="content">
<article class="content-primary" role="main"> <article class="content-primary" role="main">
<form id="settings_advanced" class="settings-advanced" method="post" action=""> <form id="settings_advanced" class="settings-advanced" method="post" action="">
...@@ -100,22 +104,60 @@ editor.render(); ...@@ -100,22 +104,60 @@ editor.render();
</aside> </aside>
</section> </section>
</div> </div>
</%block>
<%block name="view_notifications">
<!-- notification: change has been made and a save is needed --> <!-- notification: change has been made and a save is needed -->
<div class="wrapper wrapper-notification wrapper-notification-warning"> <div class="wrapper wrapper-notification wrapper-notification-warning" aria-hidden="true" role="dialog" aria-labelledby="notification-changesMade-title" aria-describedby="notification-changesMade-description">
<div class="notification warning"> <div class="notification warning has-actions">
<div class="copy">
<i class="ss-icon ss-symbolicons-block icon icon-warning">&#x26A0;</i> <i class="ss-icon ss-symbolicons-block icon icon-warning">&#x26A0;</i>
<p><strong>Note: </strong>Your changes will not take effect until you <strong>save your <div class="copy">
progress</strong>. Take care with policy value formatting, as validation is <strong>not implemented</strong>.</p> <h2 class="title title-3" id="notification-changesMade-title">You've Made Some Changes</h2>
<p id="notification-changesMade-description">Your changes will not take effect until you <strong>save your progress</strong>. Take care with key and value formatting, as validation is <strong>not implemented</strong>.</p>
</div> </div>
<div class="actions"> <nav class="nav-actions">
<h3 class="sr">Notification Actions</h3>
<ul> <ul>
<li><a href="#" class="save-button">Save</a></li> <li class="nav-item">
<li><a href="#" class="cancel-button">Cancel</a></li> <a href="" class="action-primary save-button">Save Changes</a>
</li>
<li class="nav-item">
<a href="" class="action-secondary cancel-button">Cancel</a>
</li>
</ul> </ul>
</nav>
</div>
</div>
</%block>
<%block name="view_alerts">
<!-- alert: save confirmed with close -->
<div class="wrapper wrapper-alert wrapper-alert-confirmation" role="status">
<div class="alert confirmation">
<i class="ss-icon ss-symbolicons-standard icon icon-confirmation">&#x2713;</i>
<div class="copy">
<h2 class="title title-3">Your policy changes have been saved.</h2>
<p>Please note that validation of your policy key and value pairs is not currently in place yet. If you are having difficulties, please review your policy pairs.</p>
</div>
<a href="" rel="view" class="action action-alert-close">
<i class="ss-icon ss-symbolicons-block icon icon-close">&#x2421;</i>
<span class="label">close alert</span>
</a>
</div>
</div>
<!-- alert: error -->
<div class="wrapper wrapper-alert wrapper-alert-error" role="status">
<div class="alert error">
<i class="ss-icon ss-symbolicons-block icon icon-error">&#x26A0;</i>
<div class="copy">
<h2 class="title title-3">There was an error saving your information</h2>
<p>Please see the error below and correct it to ensure there are no problems in rendering your course.</p>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
}); });
$(document).ready(function() { $(document).ready(function() {
$('body').addClass('js');
// tabs // tabs
$('.tab-group').tabs(); $('.tab-group').tabs();
......
<%inherit file="base.html" />
<%block name="title">Studio Alerts</%block>
<%block name="bodyclass">is-signedin course uxdesign alerts</%block>
<%block name="jsextra">
<script type="text/javascript">
// notifications - demo
$(document).ready(function() {
// alert and notifications - manual close
$('.action-alert-close, .alert.has-actions .nav-actions a').click(function(e) {
(e).preventDefault();
console.log('closing alert');
$(this).closest('.wrapper-alert').removeClass('is-shown');
});
// alert and notifications - manual & action-based close
$('.action-notification-close').click(function(e) {
(e).preventDefault();
$(this).closest('.wrapper-notification').removeClass('is-shown').addClass('is-hiding');
});
// prompt pop
$('.action-prompt').click(function(e){
(e).preventDefault();
$body.toggleClass('prompt-is-shown');
$(this).closest('.wrapper-prompt').attr('aria-hidden','false');
});
// prompt close
$('.prompt .action-cancel, .prompt .action-proceed').click(function(e) {
(e).preventDefault();
$body.removeClass('prompt-is-shown');
$(this).closest('.wrapper-prompt').attr('aria-hidden','true');
});
$('.hide-notification').click(function(e) {
(e).preventDefault();
$('.wrapper-notification').removeClass('is-hiding is-shown').attr('aria-hidden','true');
$(this.hash).addClass('is-hiding').attr('aria-hidden','true');
});
$('.show-notification').click(function(e) {
(e).preventDefault();
$('.wrapper-notification').removeClass('is-shown is-hiding');
$(this.hash).addClass('is-shown').attr('aria-hidden','false');
});
$('.show-notification-fleeting').click(function(e) {
(e).preventDefault();
$('.wrapper-notification').removeClass('is-fleeting').attr('aria-hidden','true');
$(this.hash).addClass('is-fleeting').attr('aria-hidden','false');
});
$('.hide-alert').click(function(e) {
(e).preventDefault();
$('.wrapper-alert').removeClass('is-hiding');
$(this.hash).addClass('is-hiding');
});
$('.show-alert').click(function(e) {
(e).preventDefault();
$('.wrapper-alert').removeClass('is-shown');
$(this.hash).addClass('is-shown');
});
$('.hide-prompt').click(function(e){
(e).preventDefault();
$body.removeClass('prompt-is-shown');
});
$('.show-prompt').click(function(e) {
(e).preventDefault();
$body.toggleClass('prompt-is-shown');
$('.wrapper-prompt').removeClass('is-shown');
$(this.hash).addClass('is-shown').attr('aria-hidden','false');
});
});
</script>
</%block>
<%block name="content">
<div class="wrapper-mast wrapper">
<header class="mast has-actions has-subtitle">
<div class="title">
<span class="title-sub">UX Design</span>
<h1 class="title-1">System Notifications</h1>
</div>
</header>
</div>
<div class="wrapper-content wrapper">
<section class="content">
<article class="content-primary" role="main">
<section>
<header>
<h2 class="title-2">Alerts</h2>
<span class="tip">persistant, static messages to the user</span>
</header>
<p>In Studio, alerts are 1) general warnings/notes (e.g. drafts, published content, next steps) about the current view a user is interacting with or 2) notes about the status (e.g. saved confirmations, errors, next system steps) of any previous state that need to communicated to the user when arriving at the current view.</p>
<h3 class="title-3">Different Static Examples of Alerts</h3>
<p>Note: alerts will probably never been shown based on click or page action and will primarily be loaded along with a pageload and initial display</p>
<ul>
<li><a href="#alert-draft" class="show-alert">Show Draft</a></li>
<li><a href="#alert-version" class="show-alert">Show Version</a></li>
<li><a href="#alert-saved" class="show-alert">Show Previous View/Action</a></li>
<li><a href="#alert-close" class="show-alert">Show Alert with Close Option</a></li>
<li><a href="#alert-removed" class="show-alert">Show Previous View/Action Removed</a></li>
<li><a href="#alert-system-error" class="show-alert">Show System Error</a></li>
<li><a href="#alert-user-error" class="show-alert">Show User Error</a></li>
<li><a href="#alert-announcement2" class="show-alert">Show Announcement</a></li>
<li><a href="#alert-announcement1" class="show-alert">Show Announcement with Actions</a></li>
<li><a href="#alert-activation" class="show-alert">Show Activiation</a></li>
</ul>
</section>
<section>
<header>
<h2 class="title-2">Notifications</h2>
<span class="tip">contextual, feedback-based, and temporal messages to the user</span>
</header>
<p>In Studio, notifications are meant to inform the user of 1) any system status (e.g. saving, processing/validating) occurring based on any action they have taken or 2) any decisions (e.g. saving/discarding) a user must make to confirm.</p>
<h3 class="title-3">Different Static Examples of Notifications</h3>
<ul>
<li>
<a href="#notification-change" class="show-notification">Show Change Warning</a>
<a href="#notification-change" class="hide-notification">Hide Change Warning</a>
</li>
<li>
<a href="#notification-version" class="show-notification">Show New Version Warning</a>
<a href="#notification-version" class="hide-notification">Hide New Version Warning</a>
</li>
<li>
<a href="#notification-dangerous" class="show-notification">Show Editing Warning</a>
<a href="#notification-dangerous" class="hide-notification">Hide Editing Warning</a>
</li>
<li>
<a href="#notification-confirmation" class="show-notification-fleeting">Show Confirmation (Fleeting Notification)</a>
</li>
<li>
<a href="#notification-saving" class="show-notification">Show Saving</a>
<a href="#notification-saving" class="hide-notification">Hide Saving</a>
</li>
<li>
<a href="#notification-help" class="show-notification">Show Help</a>
<a href="#notification-help" class="hide-notification">Hide Help</a>
</li>
</ul>
</section>
<section>
<header>
<h2 class="title-2">Prompts</h2>
<span class="tip">presents a user with a choice, based on their previous interaction, that must be decided before they can proceed</span>
</header>
<p>In Studio, prompts are dialogs that are presented above all other page components and present a user with a choice, based on their previous interaction, that must be decided before they can proceed (or return to the previous interaction step).</p>
<h3 class="title-3">Different Static Examples of Prompts</h3>
<ul>
<li>
<a href="#prompt-confirm" class="show-prompt">Show Confirm Prompt</a>
</li>
<li>
<a href="#prompt-warning" class="show-prompt">Show Warning Prompt</a>
</li>
<li>
<a href="#prompt-error" class="show-prompt">Show Error Prompt</a>
</li>
</ul>
</section>
</article>
</section>
</div>
</%block>
<%block name="view_alerts">
<!-- alert: you're editing a draft -->
<div class="wrapper wrapper-alert wrapper-alert-warning" id="alert-draft">
<div class="alert warning has-actions">
<i class="ss-icon ss-symbolicons-block icon icon-warning">&#x26A0;</i>
<div class="copy">
<h2 class="title title-3">You are editing a draft</h2>
<p class="message">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.</p>
</div>
<nav class="nav-actions">
<h3 class="sr">Alert Actions</h3>
<ul>
<li class="nav-item">
<a href="#" class="button save-button action-primary">Save Draft</a>
</li>
<li class="nav-item">
<a href="#" class="button cancel-button action-secondary">Disgard Draft</a>
</li>
</ul>
</nav>
</div>
</div>
<!-- alert: newer version -->
<div class="wrapper wrapper-alert wrapper-alert-warning" id="alert-version">
<div class="alert warning has-actions">
<i class="ss-icon ss-symbolicons-block icon icon-warning">&#x26A0;</i>
<div class="copy">
<h2 class="title title-3">A Newer Version of This Exists</h2>
<p class="message">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.</p>
</div>
<nav class="nav-actions">
<h3 class="sr">Alert Actions</h3>
<ul>
<li class="nav-item">
<a href="#" class="button save-button action-primary">Go to Newer Version</a>
</li>
<li class="nav-item">
<a href="#" class="button cancel-button action-secondary">Continue Editing</a>
</li>
</ul>
</nav>
</div>
</div>
<!-- alert: save confirmed -->
<div class="wrapper wrapper-alert wrapper-alert-confirmation" id="alert-saved">
<div class="alert confirmation">
<i class="ss-icon ss-symbolicons-standard icon icon-confirmation">&#x2713;</i>
<div class="copy">
<h2 class="title title-3">Your changes have been saved</h2>
<p class="message">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. <a rel="page" href="#test">Please see below</a></p>
</div>
</div>
</div>
<!-- alert: save confirmed with close -->
<div class="wrapper wrapper-alert wrapper-alert-confirmation" id="alert-close">
<div class="alert confirmation">
<i class="ss-icon ss-symbolicons-standard icon icon-confirmation">&#x2713;</i>
<div class="copy">
<h2 class="title title-3">Your changes have been saved</h2>
<p class="message">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. <a rel="page" href="#test">Please see below</a></p>
</div>
<a href="#" rel="view" class="action action-alert-close">
<i class="ss-icon ss-symbolicons-block icon icon-close">&#x2421;</i>
<span class="label">close alert</span>
</a>
</div>
</div>
<!-- alert: delete confirmed -->
<div class="wrapper wrapper-alert wrapper-alert-confirmation" id="alert-removed">
<div class="alert confirmation">
<i class="ss-icon ss-symbolicons-standard icon icon-confirmation">&#x2713;</i>
<div class="copy">
<h2 class="title title-3">X Has been removed</h2>
<p class="message">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.</p>
</div>
</div>
</div>
<!-- alert: system error -->
<div class="wrapper wrapper-alert wrapper-alert-error" id="alert-system-error">
<div class="alert error">
<i class="ss-icon ss-symbolicons-block icon icon-error">&#x26A0;</i>
<div class="copy">
<h2 class="title title-3">We're sorry, there was a error with Studio</h2>
<p class="message">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.</p>
</div>
</div>
</div>
<!-- alert: user error -->
<div class="wrapper wrapper-alert wrapper-alert-error" id="alert-user-error">
<div class="alert error has-actions">
<i class="ss-icon ss-symbolicons-block icon icon-error">&#x26A0;</i>
<div class="copy">
<h2 class="title title-3">There was an error in your submission</h2>
<p class="message">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. <a rel="page" href="#test">Please see below</a></p>
</div>
<nav class="nav-actions">
<h3 class="sr">Alert Actions</h3>
<ul>
<li class="nav-item">
<a href="#" class="button cancel-button action-primary">Cancel Your Submission</a>
</li>
</ul>
</nav>
</div>
</div>
<!-- alert: announcement w/ actions -->
<div class="wrapper wrapper-alert wrapper-alert-announcement" id="alert-announcement1">
<div class="alert announcement has-actions">
<i class="ss-icon ss-symbolicons-block icon icon-announcement">&#x1F4E2;</i>
<div class="copy">
<h2 class="title title-3">Studio will be unavailable this weekend</h2>
<p class="message">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.</p>
</div>
<nav class="nav-actions">
<h3 class="sr">Alert Actions</h3>
<ul>
<li class="nav-item">
<a href="#" rel="external" class="action-primary">Contact edX Staff</a>
</li>
<li class="nav-item">
<a href="#" class="action-secondary">Manage Your edX Notifications</a>
</li>
</ul>
</nav>
</div>
</div>
<!-- alert: announcement -->
<div class="wrapper wrapper-alert wrapper-alert-announcement" id="alert-announcement2">
<div class="alert announcement">
<i class="ss-icon ss-symbolicons-block icon icon-announcement">&#x1F4E2;</i>
<div class="copy">
<h2 class="title title-3">Studio will be unavailable this weekend</h2>
<p class="message">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.</p>
</div>
</div>
</div>
<!-- alert: step required -->
<div class="wrapper wrapper-alert wrapper-alert-step-required" id="alert-activation">
<div class="alert step-required has-actions">
<i class="ss-icon ss-symbolicons-block icon icon-step-required">&#xE0D1;</i>
<div class="copy">
<h2 class="title title-3">Your Studio account has been created, but needs to be activated</h2>
<p class="message">Donec sed odio dui. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Cras mattis consectetur purus sit amet fermentum. Curabitur blandit tempus porttitor.</p>
</div>
<nav class="nav-actions">
<h3 class="sr">Alert Actions</h3>
<ul>
<li class="nav-item">
<a href="#" class="action-primary">Re-send Activation Message</a>
</li>
<li class="nav-item">
<a href="#" rel="external" class="action-secondary">Contact edX Support</a>
</li>
</ul>
</nav>
</div>
</div>
</%block>
<%block name="view_notifications">
<!-- notification: change has been made and a save is needed -->
<div class="wrapper wrapper-notification wrapper-notification-change" id="notification-change" role="status">
<div class="notification change has-actions">
<i class="ss-icon ss-symbolicons-block icon icon-change">&#x1F4DD;</i>
<div class="copy">
<h2 class="title title-3">You've Made Some Changes</h2>
<p class="message">Your changes will not take effect until you <strong>save your progress</strong>.</p>
</div>
<nav class="nav-actions">
<h3 class="sr">Notification Actions</h3>
<ul>
<li class="nav-item">
<a href="#" class="action-primary">Save Changes</a>
</li>
<li class="nav-item">
<a href="#" class="action-secondary">Don't Save</a>
</li>
</ul>
</nav>
</div>
</div>
<!-- notification: newer version exists -->
<div class="wrapper wrapper-notification wrapper-notification-warning" id="notification-version" aria-hidden="true" role="dialog" aria-labelledby="notification-warning-title" aria-describedby="notification-warning-description">
<div class="notification warning has-actions">
<i class="ss-icon ss-symbolicons-block icon icon-warning">&#x26A0;</i>
<div class="copy">
<h2 class="title title-3" id="notification-warning-title">A Newer Version of This Exists</h2>
<p class="message" id="notification-warning-description">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.</p>
</div>
<nav class="nav-actions">
<h3 class="sr">Notification Actions</h3>
<ul>
<li class="nav-item">
<a href="#" class="button save-button action-primary">Go to Newer Version</a>
</li>
<li class="nav-item">
<a href="#" class="button cancel-button action-secondary">Continue Editing</a>
</li>
</ul>
</nav>
</div>
</div>
<!-- notification: warning about editing something dangerous -->
<div class="wrapper wrapper-notification wrapper-notification-warning" id="notification-dangerous" aria-hidden="true" role="dialog" aria-labelledby="notification-dangerous-title" aria-describedby="notification-dangerous-description">
<div class="notification warning has-actions">
<i class="ss-icon ss-symbolicons-block icon icon-warning">&#x26A0;</i>
<div class="copy">
<h2 class="title title-3" id="notification-dangerous-title">Are You Sure You Want to Edit That?</h2>
<p class="message" id="notification-dangerous-description">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.</p>
</div>
<nav class="nav-actions">
<h3 class="sr">Notification Actions</h3>
<ul>
<li class="nav-item">
<a href="#" class="action-primary">Yes, I want to Edit X</a>
</li>
<li class="nav-item">
<a href="#" class="action-secondary">No, I do not</a>
</li>
</ul>
</nav>
</div>
</div>
<!-- notification: status - saving -->
<div class="wrapper wrapper-notification wrapper-notification-status wrapper-notification-saving" id="notification-saving">
<div class="notification saving">
<i class="ss-icon ss-symbolicons-block icon icon-saving">&#x2699;</i>
<div class="copy">
<h2 class="title title-3" role="status">Saving &hellip;</h2>
</div>
</div>
</div>
<!-- notification: status- confirmed -->
<div class="wrapper wrapper-notification wrapper-notification-confirmation" id="notification-confirmation">
<div class="notification confirmation">
<i class="ss-icon ss-symbolicons-standard icon icon-confirmation">&#x2713;</i>
<div class="copy">
<h2 class="title title-3" role="status"><a href="#">Your Section</a> Has Been Created</h2>
</div>
</div>
</div>
<!-- notification: help - DYK -->
<div class="wrapper wrapper-notification wrapper-notification-help" id="notification-help">
<div class="notification help">
<i class="ss-icon ss-symbolicons-block icon icon-help">&#x2753;</i>
<div class="copy">
<h2 class="title title-3">Fun Fact:</h2>
<p class="message">Using the checkmark will allow you make a subsection gradable as an assignment, which counts towards a student's total grade</p>
</div>
<a href="#" rel="view" class="action action-notification-close">
<i class="ss-icon ss-symbolicons-block icon icon-close">&#x2421;</i>
<span class="label">close notification</span>
</a>
</div>
</div>
</%block>
<%block name="view_prompts">
<!-- prompt - confirm deletion -->
<div class="wrapper wrapper-prompt wrapper-prompt-confirm" id="prompt-confirm" aria-hidden="true" role="dialog" aria-labelledby="prompt-confirm-sectionDelete-title" aria-describedby="prompt-confirm-sectionDelete-description">
<div class="prompt confirm">
<div class="copy">
<h2 class="title title-3" id="prompt-confirm-sectionDelete-title">Delete "Introduction &amp; Overview"?</h2>
<p class="message" id="prompt-confirm-sectionDelete-description">Deleting a section cannot be undone and its contents cannot be recovered.</p>
</div>
<nav class="nav-actions">
<h3 class="sr">Prompt Actions</h3>
<ul>
<li class="nav-item">
<a href="#" class="action-primary action-proceed">Yes, delete this section</a>
</li>
<li class="nav-item">
<a href="#" class="action-secondary action-cancel">Cancel</a>
</li>
</ul>
</nav>
</div>
</div>
<!-- prompt - warning -->
<div class="wrapper wrapper-prompt wrapper-prompt-warning" id="prompt-warning" aria-hidden="true" role="dialog" aria-labelledby="prompt-warning-useAdvanced-title" aria-describedby="prompt-warning-useAdvanced-description">
<div class="prompt warning">
<div class="copy">
<h2 class="title title-3" id="prompt-warning-useAdvanced-title">Use Advanced Problem Editor?</h2>
<p class="message" id="prompt-warning-useAdvanced-description">If you proceed, you cannot edit this problem using the simple problem editor.</p>
</div>
<nav class="nav-actions">
<h3 class="sr">Prompt Actions</h3>
<ul>
<li class="nav-item">
<a href="#" class="action-primary action-proceed">Continue to Advanced Editor</a>
</li>
<li class="nav-item">
<a href="#" class="action-secondary action-cancel">Cancel</a>
</li>
</ul>
</nav>
</div>
</div>
<!-- prompt - error -->
<div class="wrapper wrapper-prompt wrapper-prompt-error" id="prompt-error" aria-hidden="true" role="dialog" aria-labelledby="prompt-errorUser-title" aria-describedby="prompt-errorUser-description">
<div class="prompt error">
<div class="copy">
<h2 class="title title-3" id="prompt-errorUser-title">There Were Errors in Your Submission</h2>
<p class="message" id="prompt-errorUser-description">Please correct the errors noted on the page and try again.</p>
</div>
<nav class="nav-actions">
<h3 class="sr">Prompt Actions</h3>
<ul>
<li class="nav-item">
<a href="#" class="action-primary action-proceed">Correct errors &amp; try again</a>
</li>
</ul>
</nav>
</div>
</div>
</%block>
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
<h3 class="title">Studio Support</h3> <h3 class="title">Studio Support</h3>
<div class="copy"> <div class="copy">
<p>Need help with Studio? Creating a course is complex, so we're here to help? Take advantage of our documentation, help center, as well as our edX101 introduction course for course authors.</p> <p>Need help with Studio? Creating a course is complex, so we're here to help. Take advantage of our documentation, help center, as well as our edX101 introduction course for course authors.</p>
</div> </div>
<ul class="list-actions"> <ul class="list-actions">
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
<h3 class="title">Contact us about Studio</h3> <h3 class="title">Contact us about Studio</h3>
<div class="copy"> <div class="copy">
<p>Have problems, questions, or suggestions about Studio? We're here to help and listen to any feedback you want to share.</p> <p>Have problems, questions, or suggestions about Studio? We're also here to listen to any feedback you want to share.</p>
</div> </div>
<ul class="list-actions"> <ul class="list-actions">
......
...@@ -116,6 +116,8 @@ urlpatterns += ( ...@@ -116,6 +116,8 @@ urlpatterns += (
url(r'^logout$', 'student.views.logout_user', name='logout'), url(r'^logout$', 'student.views.logout_user', name='logout'),
# static/proof-of-concept views
url(r'^ux-alerts$', 'contentstore.views.ux_alerts', name='ux-alerts')
) )
if settings.ENABLE_JASMINE: if settings.ENABLE_JASMINE:
......
...@@ -258,10 +258,10 @@ class MongoModuleStore(ModuleStoreBase): ...@@ -258,10 +258,10 @@ class MongoModuleStore(ModuleStoreBase):
# get all collections in the course, this query should not return any leaf nodes # get all collections in the course, this query should not return any leaf nodes
# note this is a bit ugly as when we add new categories of containers, we have to add it here # note this is a bit ugly as when we add new categories of containers, we have to add it here
query = { query = {'_id.org': location.org,
'_id.org': location.org,
'_id.course': location.course, '_id.course': location.course,
'_id.category': {'$in': ['course', 'chapter', 'sequential', 'vertical']} '_id.category': {'$in': ['course', 'chapter', 'sequential', 'vertical',
'wrapper', 'problemset', 'conditional']}
} }
# we just want the Location, children, and inheritable metadata # we just want the Location, children, and inheritable metadata
record_filter = {'_id': 1, 'definition.children': 1} record_filter = {'_id': 1, 'definition.children': 1}
......
...@@ -301,16 +301,18 @@ def import_from_xml(store, data_dir, course_dirs=None, ...@@ -301,16 +301,18 @@ def import_from_xml(store, data_dir, course_dirs=None,
# Note the dropped element closing tag. This causes the LMS to fail when rendering modules - that's # Note the dropped element closing tag. This causes the LMS to fail when rendering modules - that's
# no good, so we have to do this kludge # no good, so we have to do this kludge
if isinstance(module_data, str) or isinstance(module_data, unicode): # some module 'data' fields are non strings which blows up the link traversal code if isinstance(module_data, str) or isinstance(module_data, unicode): # some module 'data' fields are non strings which blows up the link traversal code
lxml_rewrite_links(module_data, lambda link: verify_content_links(module, course_data_path, lxml_rewrite_links(module_data,
static_content_store, link, remap_dict)) lambda link: verify_content_links(module, course_data_path, static_content_store, link, remap_dict))
for key in remap_dict.keys(): for key in remap_dict.keys():
module_data = module_data.replace(key, remap_dict[key]) module_data = module_data.replace(key, remap_dict[key])
except Exception, e: except Exception:
logging.exception("failed to rewrite links on {0}. Continuing...".format(module.location)) logging.exception("failed to rewrite links on {0}. Continuing...".format(module.location))
else:
module_data = content
store.update_item(module.location, content) store.update_item(module.location, module_data)
if hasattr(module, 'children') and module.children != []: if hasattr(module, 'children') and module.children != []:
store.update_children(module.location, module.children) store.update_children(module.location, module.children)
......
...@@ -199,8 +199,8 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -199,8 +199,8 @@ class PeerGradingModule(PeerGradingFields, XModule):
self.student_data_for_location = response self.student_data_for_location = response
score_dict = { score_dict = {
'score': int(count_graded >= count_required), 'score': int(count_graded >= count_required and count_graded>0) * int(self.weight),
'total': self.max_grade, 'total': self.max_grade * int(self.weight),
} }
return score_dict return score_dict
......
...@@ -24,6 +24,11 @@ def strip_filenames(descriptor): ...@@ -24,6 +24,11 @@ def strip_filenames(descriptor):
""" """
print "strip filename from {desc}".format(desc=descriptor.location.url()) print "strip filename from {desc}".format(desc=descriptor.location.url())
descriptor._model_data.pop('filename', None) descriptor._model_data.pop('filename', None)
if hasattr(descriptor, 'xml_attributes'):
if 'filename' in descriptor.xml_attributes:
del descriptor.xml_attributes['filename']
for d in descriptor.get_children(): for d in descriptor.get_children():
strip_filenames(d) strip_filenames(d)
......
...@@ -11,11 +11,15 @@ ...@@ -11,11 +11,15 @@
font-size: ($sizeValue/10) + rem; font-size: ($sizeValue/10) + rem;
} }
// ====================
// line-height // line-height
@function lh($amount: 1) { @function lh($amount: 1) {
@return $body-line-height * $amount; @return $body-line-height * $amount;
} }
// ====================
// image-replacement hidden text // image-replacement hidden text
@mixin text-hide() { @mixin text-hide() {
text-indent: 100%; text-indent: 100%;
...@@ -35,6 +39,8 @@ ...@@ -35,6 +39,8 @@
width: 1px; width: 1px;
} }
// ====================
// vertical and horizontal centering // vertical and horizontal centering
@mixin vertically-and-horizontally-centered ($height, $width) { @mixin vertically-and-horizontally-centered ($height, $width) {
left: 50%; left: 50%;
...@@ -46,6 +52,8 @@ ...@@ -46,6 +52,8 @@
top: 150px; top: 150px;
} }
// ====================
// sizing // sizing
@mixin size($width: $baseline, $height: $baseline) { @mixin size($width: $baseline, $height: $baseline) {
height: $height; height: $height;
...@@ -56,6 +64,8 @@ ...@@ -56,6 +64,8 @@
@include size($size); @include size($size);
} }
// ====================
// placeholder styling // placeholder styling
@mixin placeholder($color) { @mixin placeholder($color) {
:-moz-placeholder { :-moz-placeholder {
......
...@@ -12,15 +12,15 @@ ...@@ -12,15 +12,15 @@
</sequential> </sequential>
</section> </section>
</chapter> </chapter>
<chapter name="Chapter 2"> <chapter name="Chapter 2" url_name='chapter_2'>
<section name="Problem Set 1"> <section name="Problem Set 1">
<sequential> <sequential>
<problem type="lecture" showanswer="attempted" rerandomize="true" display_name="A simple coding problem" name="Simple coding problem" filename="ps01-simple" url_name="ps01-simple"/> <problem type="lecture" showanswer="attempted" rerandomize="true" display_name="A simple coding problem" name="Simple coding problem" filename="ps01-simple" url_name="ps01-simple"/>
</sequential> </sequential>
</section> </section>
<video name="Lost Video" youtube="1.0:TBvX7HzxexQ"/> <video name="Lost Video" youtube="1.0:TBvX7HzxexQ"/>
<sequential format="Lecture Sequence"> <sequential format="Lecture Sequence" url_name='test_sequence'>
<vertical url_name='test_verical'> <vertical url_name='test_vertical'>
<html url_name='test_html'> <html url_name='test_html'>
Foobar Foobar
</html> </html>
......
...@@ -3,17 +3,12 @@ ...@@ -3,17 +3,12 @@
# django management command: dump grades to csv files # django management command: dump grades to csv files
# for use by batch processes # for use by batch processes
import os import csv
import sys
import string
import datetime
import json
from instructor.views import * from instructor.views import get_student_grade_summary_data
from courseware.courses import get_course_by_id from courseware.courses import get_course_by_id
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from django.conf import settings
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
...@@ -45,7 +40,7 @@ class Command(BaseCommand): ...@@ -45,7 +40,7 @@ class Command(BaseCommand):
request = self.DummyRequest() request = self.DummyRequest()
try: try:
course = get_course_by_id(course_id) course = get_course_by_id(course_id)
except Exception as err: except Exception:
if course_id in modulestore().courses: if course_id in modulestore().courses:
course = modulestore().courses[course_id] course = modulestore().courses[course_id]
else: else:
......
...@@ -11,7 +11,6 @@ import requests ...@@ -11,7 +11,6 @@ import requests
from requests.status_codes import codes from requests.status_codes import codes
import urllib import urllib
from collections import OrderedDict from collections import OrderedDict
import json
from StringIO import StringIO from StringIO import StringIO
...@@ -21,7 +20,6 @@ from django.http import HttpResponse ...@@ -21,7 +20,6 @@ from django.http import HttpResponse
from django_future.csrf import ensure_csrf_cookie from django_future.csrf import ensure_csrf_cookie
from django.views.decorators.cache import cache_control from django.views.decorators.cache import cache_control
from mitxmako.shortcuts import render_to_response from mitxmako.shortcuts import render_to_response
import requests
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from courseware import grades from courseware import grades
...@@ -36,11 +34,7 @@ from django_comment_client.models import (Role, ...@@ -36,11 +34,7 @@ from django_comment_client.models import (Role,
from django_comment_client.utils import has_forum_access from django_comment_client.utils import has_forum_access
from psychometrics import psychoanalyze from psychometrics import psychoanalyze
from student.models import CourseEnrollment, CourseEnrollmentAllowed from student.models import CourseEnrollment, CourseEnrollmentAllowed
from xmodule.course_module import CourseDescriptor
from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundError, NoPathToItem
from xmodule.modulestore.search import path_to_location
import xmodule.graders as xmgraders import xmodule.graders as xmgraders
import track.views import track.views
...@@ -48,14 +42,15 @@ from .offline_gradecalc import student_grades, offline_grades_available ...@@ -48,14 +42,15 @@ from .offline_gradecalc import student_grades, offline_grades_available
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
template_imports = {'urllib': urllib}
# internal commands for managing forum roles: # internal commands for managing forum roles:
FORUM_ROLE_ADD = 'add' FORUM_ROLE_ADD = 'add'
FORUM_ROLE_REMOVE = 'remove' FORUM_ROLE_REMOVE = 'remove'
def split_by_comma_and_whitespace(s): def split_by_comma_and_whitespace(s):
"""
Return string s, split by , or whitespace
"""
return re.split(r'[\s,]', s) return re.split(r'[\s,]', s)
...@@ -141,7 +136,7 @@ def instructor_dashboard(request, course_id): ...@@ -141,7 +136,7 @@ def instructor_dashboard(request, course_id):
# 'beta', so adding it to get_access_group_name doesn't really make # 'beta', so adding it to get_access_group_name doesn't really make
# sense. # sense.
name = course_beta_test_group_name(course.location) name = course_beta_test_group_name(course.location)
(group, created) = Group.objects.get_or_create(name=name) (group, _) = Group.objects.get_or_create(name=name)
return group return group
# process actions from form POST # process actions from form POST
...@@ -237,13 +232,13 @@ def instructor_dashboard(request, course_id): ...@@ -237,13 +232,13 @@ def instructor_dashboard(request, course_id):
if '/' not in problem_to_reset: # allow state of modules other than problem to be reset if '/' not in problem_to_reset: # allow state of modules other than problem to be reset
problem_to_reset = "problem/" + problem_to_reset # but problem is the default problem_to_reset = "problem/" + problem_to_reset # but problem is the default
try: try:
(org, course_name, run) = course_id.split("/") (org, course_name, _) = course_id.split("/")
module_state_key = "i4x://" + org + "/" + course_name + "/" + problem_to_reset module_state_key = "i4x://" + org + "/" + course_name + "/" + problem_to_reset
module_to_reset = StudentModule.objects.get(student_id=student_to_reset.id, module_to_reset = StudentModule.objects.get(student_id=student_to_reset.id,
course_id=course_id, course_id=course_id,
module_state_key=module_state_key) module_state_key=module_state_key)
msg += "Found module to reset. " msg += "Found module to reset. "
except Exception as e: except Exception:
msg += "<font color='red'>Couldn't find module with that urlname. </font>" msg += "<font color='red'>Couldn't find module with that urlname. </font>"
if "Delete student state for problem" in action: if "Delete student state for problem" in action:
...@@ -352,7 +347,7 @@ def instructor_dashboard(request, course_id): ...@@ -352,7 +347,7 @@ def instructor_dashboard(request, course_id):
return_csv('', datatable, fp=fp) return_csv('', datatable, fp=fp)
fp.seek(0) fp.seek(0)
files = {'datafile': fp} files = {'datafile': fp}
msg2, dataset = _do_remote_gradebook(request.user, course, 'post-grades', files=files) msg2, _ = _do_remote_gradebook(request.user, course, 'post-grades', files=files)
msg += msg2 msg += msg2
...@@ -423,7 +418,7 @@ def instructor_dashboard(request, course_id): ...@@ -423,7 +418,7 @@ def instructor_dashboard(request, course_id):
datatable = {'header': ['username', 'email'] + profkeys} datatable = {'header': ['username', 'email'] + profkeys}
def getdat(u): def getdat(u):
p = u.profile p = u.profile
return [u.username, u.email] + [getattr(p,x,'') for x in profkeys] return [u.username, u.email] + [getattr(p, x, '') for x in profkeys]
datatable['data'] = [getdat(u) for u in enrolled_students] datatable['data'] = [getdat(u) for u in enrolled_students]
datatable['title'] = 'Student profile data for course %s' % course_id datatable['title'] = 'Student profile data for course %s' % course_id
...@@ -433,17 +428,17 @@ def instructor_dashboard(request, course_id): ...@@ -433,17 +428,17 @@ def instructor_dashboard(request, course_id):
elif 'Download CSV of all responses to problem' in action: elif 'Download CSV of all responses to problem' in action:
problem_to_dump = request.POST.get('problem_to_dump','') problem_to_dump = request.POST.get('problem_to_dump','')
if problem_to_dump[-4:]==".xml": if problem_to_dump[-4:] == ".xml":
problem_to_dump=problem_to_dump[:-4] problem_to_dump = problem_to_dump[:-4]
try: try:
(org, course_name, run)=course_id.split("/") (org, course_name, run) = course_id.split("/")
module_state_key="i4x://"+org+"/"+course_name+"/problem/"+problem_to_dump module_state_key = "i4x://" + org + "/" + course_name + "/problem/" + problem_to_dump
smdat = StudentModule.objects.filter(course_id=course_id, smdat = StudentModule.objects.filter(course_id=course_id,
module_state_key=module_state_key) module_state_key=module_state_key)
smdat = smdat.order_by('student') smdat = smdat.order_by('student')
msg += "Found %d records to dump " % len(smdat) msg += "Found %d records to dump " % len(smdat)
except Exception as err: except Exception as err:
msg+="<font color='red'>Couldn't find module with that urlname. </font>" msg += "<font color='red'>Couldn't find module with that urlname. </font>"
msg += "<pre>%s</pre>" % escape(err) msg += "<pre>%s</pre>" % escape(err)
smdat = [] smdat = []
...@@ -741,7 +736,7 @@ def _list_course_forum_members(course_id, rolename, datatable): ...@@ -741,7 +736,7 @@ def _list_course_forum_members(course_id, rolename, datatable):
# make sure datatable is set up properly for display first, before checking for errors # make sure datatable is set up properly for display first, before checking for errors
datatable['header'] = ['Username', 'Full name', 'Roles'] datatable['header'] = ['Username', 'Full name', 'Roles']
datatable['title'] = 'List of Forum {0}s in course {1}'.format(rolename, course_id) datatable['title'] = 'List of Forum {0}s in course {1}'.format(rolename, course_id)
datatable['data'] = []; datatable['data'] = []
try: try:
role = Role.objects.get(name=rolename, course_id=course_id) role = Role.objects.get(name=rolename, course_id=course_id)
except Role.DoesNotExist: except Role.DoesNotExist:
...@@ -1040,7 +1035,8 @@ def _do_enroll_students(course, course_id, students, overload=False): ...@@ -1040,7 +1035,8 @@ def _do_enroll_students(course, course_id, students, overload=False):
datatable['data'] = [[x, status[x]] for x in status] datatable['data'] = [[x, status[x]] for x in status]
datatable['title'] = 'Enrollment of students' datatable['title'] = 'Enrollment of students'
def sf(stat): return [x for x in status if status[x] == stat] def sf(stat):
return [x for x in status if status[x] == stat]
data = dict(added=sf('added'), rejected=sf('rejected') + sf('exists'), data = dict(added=sf('added'), rejected=sf('rejected') + sf('exists'),
deleted=sf('deleted'), datatable=datatable) deleted=sf('deleted'), datatable=datatable)
......
...@@ -27,8 +27,6 @@ from mitxmako.shortcuts import render_to_string ...@@ -27,8 +27,6 @@ from mitxmako.shortcuts import render_to_string
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
template_imports = {'urllib': urllib}
system = ModuleSystem( system = ModuleSystem(
ajax_url=None, ajax_url=None,
track_function=None, track_function=None,
......
...@@ -75,8 +75,8 @@ ...@@ -75,8 +75,8 @@
<article id="director-of-education-services" class="job"> <article id="director-of-education-services" class="job">
<div class="inner-wrapper"> <div class="inner-wrapper">
<h3><strong>DIRECTOR OF EDUCATIONAL SERVICES</strong></h3> <h3><strong>VICE PRESIDENT/DIRECTOR OF EDUCATIONAL SERVICES</strong></h3>
<p>The edX Director of Education Services reporting to the VP of Engineering and Educational Services is responsible for:</p> <p>The edX VP/Director of Education Services reporting to the VP of Engineering and Educational Services is responsible for:</p>
<ol> <ol>
<li>Delivering 20 new courses in 2013 in collaboration with the partner Universities <li>Delivering 20 new courses in 2013 in collaboration with the partner Universities
<ul> <ul>
...@@ -419,13 +419,76 @@ ...@@ -419,13 +419,76 @@
</div> </div>
</article> </article>
<article id="sales-engineer" class="job">
<div class="inner-wrapper">
<h3><strong>SALES ENGINEER, BUSINESS DEVELOPMENT TEAM</strong></h3>
<p>A great relationship with edX begins long before the first student signs up. We are
looking for some talented, self-motivated people to help set a solid foundation for
our emerging corporate customers, NGO’s, and governmental partners. As the Sales
Engineer you will be expected to provide oversight if requested over more junior
staff. This may include skills development, knowledge transfer, sharing technical
expertise, and review of demos prior to presentation. The Sales Engineer should
have familiarity with instructional design and competency in that area is a plus.</p>
<p>Experience teaching and mentoring, needs assessment and prior management
responsibility, and LMS experience, also a plus. This is a team atmosphere with
many constituencies working to develop a new global perspective regarding higher
education and online learning. Respect and patience, along with knowledge and
understanding of the development process is critical to success in order to maintain
strong bonds between development teams, sales team, and prospect/client
implementation teams. In addition the Sales Engineer may also work with our
xUniversity partners and the affiliated professors joining the edX movement. This
position requires customer facing skills, comfort in demonstrating the product, and
ability to code ‘demos’ as required. Additionally you will be contributing to
proposals, so clear documentation and writing skills are critical. The job will
require travel to client sites around the US upon occasion, and possibly
internationally as well. Job also requires good speaking skills, and a willingness and
ability to communicate clearly and respond quickly to prospect and customer
requests. This is a salaried position and will on occasion require work and
responsiveness to both the edX team and customers ‘after hours’. This position
reports to the VP, Business Development and will be dotted lined to the
development and program management teams.</p>
<p><strong>Responsibilities:</strong></p>
<ul>
<li>Can code demos and evaluate demos of others</li>
<li>Prepare and deliver standard and custom demonstrations</li>
<li>Handle all pre-sales technical issues professionally and efficiently</li>
<li>Maintain in-depth knowledge of products and pending new releases</li>
<li>Maintain a working knowledge of documentation and training</li>
<li>Maintain a working knowledge of workflow systems</li>
<li>Respond to technical questions from universities looking to expand their on-line offerings</li>
<li>Provide feedback to Product Development regarding new features, improving product performance, and eliminating bugs in the product</li>
<li>Prepare Professional Services for efficient onboarding – professionally managing the transition from pre-sales to post-sales</li>
<li>Deliver high-level presentation and associated ‘click-thru’ demonstrations</li>
<li>and be able to customize to prospect’s requirements</li>
<li>Understand and articulate the underlying technology concepts</li>
<li>Understand and articulate how all products components fit together technically as well as how they integrate and work with external technologies and cross functional applications found within clients organizations.</li>
<li>Build relationships with our prospects and universities, to be viewed as a trusted training partner.</li>
</ul>
<p><strong>Qualifications:</strong></p>
<ul>
<li>Minimum of 5 years of experience working closely with relationship based sales organizations, preferably in an educational technology organization.</li>
<li>Excellent interpersonal skills including proven presentation and facilitation skills.</li>
<li>Strong oral and written communication skills.</li>
<li>Flexibility to work on a variety of initiatives; prior startup experience preferred.</li>
<li>Outstanding work ethic, results-oriented, and creative/innovative style.</li>
<li>Proactive, optimistic approach to problem solving.</li>
<li>Commitment to constant personal and organizational improvement.</li>
<li>Willingness to travel to partner sites as needed.</li>
<li>Lean and Agile thinking and training. Experienced in Scrum or kanban.</li>
<li>Bachelors or Master’s in Education, organizational learning, or other related field preferred. But we're all about education, so let us know how you gained what you need to succeed in this role: projects after completing 6.00x or CS50x, Xbox cheevos, on-line guilds led, large scale innovations championed.</li>
</ul>
<p>If you are interested in this position, please send an email to <a href="mailto:jobs@edx.org">jobs@edx.org</a>.</p>
</div>
</article>
</section> </section>
<section class="jobs-sidebar"> <section class="jobs-sidebar">
<h2>Positions</h2> <h2>Positions</h2>
<nav> <nav>
<a href="#director-of-education-services">Director of Education Services</a> <a href="#director-of-education-services">Vice President/Director of Education Services</a>
<a href="#manager-of-training-services">Manager of Training Services</a> <a href="#manager-of-training-services">Manager of Training Services</a>
<a href="#trainer">Trainer</a> <a href="#trainer">Trainer</a>
<a href="#instructional-designer">Instructional Designer</a> <a href="#instructional-designer">Instructional Designer</a>
...@@ -435,6 +498,7 @@ ...@@ -435,6 +498,7 @@
<a href="#software-engineer">Software Engineer</a> <a href="#software-engineer">Software Engineer</a>
<a href="#devops-engineer-systems-administrator">Devops Engineer - Systems Administrator</a> <a href="#devops-engineer-systems-administrator">Devops Engineer - Systems Administrator</a>
<a href="#learning-sciences-engineer">Learning Sciences Engineer</a> <a href="#learning-sciences-engineer">Learning Sciences Engineer</a>
<a href="#sales-engineer">Sales Engineer, Business Development Team</a>
</nav> </nav>
<h2>How to Apply</h2> <h2>How to Apply</h2>
<p>E-mail your resume, cover letter and any other materials to <a href="mailto:jobs@edx.org">jobs@edx.org</a></p> <p>E-mail your resume, cover letter and any other materials to <a href="mailto:jobs@edx.org">jobs@edx.org</a></p>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment