Commit 0a47f27f by Julian Arni

Merge branch 'master' into feature/jkarni/foldit-api

parents 26eaf898 49cab68a
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
[run] [run]
data_file = reports/cms/.coverage data_file = reports/cms/.coverage
source = cms,common/djangoapps source = cms,common/djangoapps
omit = cms/envs/*, cms/manage.py omit = cms/envs/*, cms/manage.py, common/djangoapps/terrain/*, common/djangoapps/*/migrations/*
[report] [report]
ignore_errors = True ignore_errors = True
......
...@@ -20,7 +20,8 @@ def i_visit_the_studio_homepage(step): ...@@ -20,7 +20,8 @@ def i_visit_the_studio_homepage(step):
# LETTUCE_SERVER_PORT = 8001 # LETTUCE_SERVER_PORT = 8001
# in your settings.py file. # in your settings.py file.
world.browser.visit(django_url('/')) world.browser.visit(django_url('/'))
assert world.browser.is_element_present_by_css('body.no-header', 10) signin_css = 'a.action-signin'
assert world.browser.is_element_present_by_css(signin_css, 10)
@step('I am logged into Studio$') @step('I am logged into Studio$')
...@@ -113,7 +114,11 @@ def log_into_studio( ...@@ -113,7 +114,11 @@ def log_into_studio(
create_studio_user(uname=uname, email=email, is_staff=is_staff) create_studio_user(uname=uname, email=email, is_staff=is_staff)
world.browser.cookies.delete() world.browser.cookies.delete()
world.browser.visit(django_url('/')) world.browser.visit(django_url('/'))
world.browser.is_element_present_by_css('body.no-header', 10) signin_css = 'a.action-signin'
world.browser.is_element_present_by_css(signin_css, 10)
# click the signin button
css_click(signin_css)
login_form = world.browser.find_by_css('form#login_form') login_form = world.browser.find_by_css('form#login_form')
login_form.find_by_name('email').fill(email) login_form.find_by_name('email').fill(email)
...@@ -127,16 +132,19 @@ def create_a_course(): ...@@ -127,16 +132,19 @@ def create_a_course():
css_click('a.new-course-button') css_click('a.new-course-button')
fill_in_course_info() fill_in_course_info()
css_click('input.new-course-save') css_click('input.new-course-save')
assert_true(world.browser.is_element_present_by_css('a#courseware-tab', 5)) course_title_css = 'span.course-title'
assert_true(world.browser.is_element_present_by_css(course_title_css, 5))
def add_section(name='My Section'): def add_section(name='My Section'):
link_css = 'a.new-courseware-section-button' link_css = 'a.new-courseware-section-button'
css_click(link_css) css_click(link_css)
name_css = '.new-section-name' name_css = 'input.new-section-name'
save_css = '.new-section-name-save' save_css = 'input.new-section-name-save'
css_fill(name_css, name) css_fill(name_css, name)
css_click(save_css) css_click(save_css)
span_css = 'span.section-name-span'
assert_true(world.browser.is_element_present_by_css(span_css, 5))
def add_subsection(name='Subsection One'): def add_subsection(name='Subsection One'):
......
...@@ -34,8 +34,8 @@ def i_click_the_course_link_in_my_courses(step): ...@@ -34,8 +34,8 @@ def i_click_the_course_link_in_my_courses(step):
@step('the Courseware page has loaded in Studio$') @step('the Courseware page has loaded in Studio$')
def courseware_page_has_loaded_in_studio(step): def courseware_page_has_loaded_in_studio(step):
courseware_css = 'a#courseware-tab' course_title_css = 'span.course-title'
assert world.browser.is_element_present_by_css(courseware_css) assert world.browser.is_element_present_by_css(course_title_css)
@step('I see the course listed in My Courses$') @step('I see the course listed in My Courses$')
...@@ -59,4 +59,4 @@ def i_am_on_tab(step, tab_name): ...@@ -59,4 +59,4 @@ def i_am_on_tab(step, tab_name):
@step('I see a link for adding a new section$') @step('I see a link for adding a new section$')
def i_see_new_section_link(step): def i_see_new_section_link(step):
link_css = 'a.new-courseware-section-button' link_css = 'a.new-courseware-section-button'
assert_css_with_text(link_css, 'New Section') assert_css_with_text(link_css, '+ New Section')
...@@ -5,8 +5,8 @@ Feature: Sign in ...@@ -5,8 +5,8 @@ Feature: Sign in
Scenario: Sign up from the homepage Scenario: Sign up from the homepage
Given I visit the Studio homepage Given I visit the Studio homepage
When I click the link with the text "Sign up" When I click the link with the text "Sign Up"
And I fill in the registration form And I fill in the registration form
And I press the "Create My Account" button on the registration form And I press the Create My Account button on the registration form
Then I should see be on the studio home page Then I should see be on the studio home page
And I should see the message "please click on the activation link in your email." And I should see the message "please click on the activation link in your email."
...@@ -11,10 +11,11 @@ def i_fill_in_the_registration_form(step): ...@@ -11,10 +11,11 @@ def i_fill_in_the_registration_form(step):
register_form.find_by_name('terms_of_service').check() register_form.find_by_name('terms_of_service').check()
@step('I press the "([^"]*)" button on the registration form$') @step('I press the Create My Account button on the registration form$')
def i_press_the_button_on_the_registration_form(step, button): def i_press_the_button_on_the_registration_form(step):
register_form = world.browser.find_by_css('form#register_form') register_form = world.browser.find_by_css('form#register_form')
register_form.find_by_value(button).click() submit_css = 'button#submit'
register_form.find_by_css(submit_css).click()
@step('I should see be on the studio home page$') @step('I should see be on the studio home page$')
......
...@@ -5,7 +5,7 @@ from django.test.utils import override_settings ...@@ -5,7 +5,7 @@ from django.test.utils import override_settings
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from path import path from path import path
from tempfile import mkdtemp from tempdir import mkdtemp_clean
import json import json
from fs.osfs import OSFS from fs.osfs import OSFS
import copy import copy
...@@ -194,7 +194,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -194,7 +194,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
import_from_xml(ms, 'common/test/data/', ['full']) import_from_xml(ms, 'common/test/data/', ['full'])
location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012') location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012')
root_dir = path(mkdtemp()) root_dir = path(mkdtemp_clean())
print 'Exporting to tempdir = {0}'.format(root_dir) print 'Exporting to tempdir = {0}'.format(root_dir)
...@@ -214,12 +214,21 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -214,12 +214,21 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
fs = OSFS(root_dir / 'test_export/policies/6.002_Spring_2012') fs = OSFS(root_dir / 'test_export/policies/6.002_Spring_2012')
self.assertTrue(fs.exists('grading_policy.json')) self.assertTrue(fs.exists('grading_policy.json'))
course = ms.get_item(location)
# compare what's on disk compared to what we have in our course # compare what's on disk compared to what we have in our course
with fs.open('grading_policy.json','r') as grading_policy: with fs.open('grading_policy.json','r') as grading_policy:
on_disk = loads(grading_policy.read()) on_disk = loads(grading_policy.read())
course = ms.get_item(location)
self.assertEqual(on_disk, course.definition['data']['grading_policy']) self.assertEqual(on_disk, course.definition['data']['grading_policy'])
#check for policy.json
self.assertTrue(fs.exists('policy.json'))
# compare what's on disk to what we have in the course module
with fs.open('policy.json','r') as course_policy:
on_disk = loads(course_policy.read())
self.assertIn('course/6.002_Spring_2012', on_disk)
self.assertEqual(on_disk['course/6.002_Spring_2012'], course.metadata)
# remove old course # remove old course
delete_course(ms, cs, location) delete_course(ms, cs, location)
...@@ -255,6 +264,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -255,6 +264,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self.assertContains(resp, '/c4x/edX/full/asset/handouts_schematic_tutorial.pdf') self.assertContains(resp, '/c4x/edX/full/asset/handouts_schematic_tutorial.pdf')
class ContentStoreTest(ModuleStoreTestCase): class ContentStoreTest(ModuleStoreTestCase):
""" """
Tests for the CMS ContentStore application. Tests for the CMS ContentStore application.
...@@ -333,7 +343,7 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -333,7 +343,7 @@ class ContentStoreTest(ModuleStoreTestCase):
# Create a course so there is something to view # Create a course so there is something to view
resp = self.client.get(reverse('index')) resp = self.client.get(reverse('index'))
self.assertContains(resp, self.assertContains(resp,
'<h1>My Courses</h1>', '<h1 class="title-1">My Courses</h1>',
status_code=200, status_code=200,
html=True) html=True)
...@@ -369,7 +379,7 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -369,7 +379,7 @@ class ContentStoreTest(ModuleStoreTestCase):
resp = self.client.get(reverse('course_index', kwargs=data)) resp = self.client.get(reverse('course_index', kwargs=data))
self.assertContains(resp, self.assertContains(resp,
'<a href="/MITx/999/course/Robot_Super_Course" class="class-name">Robot Super Course</a>', '<article class="courseware-overview" data-course-id="i4x://MITx/999/course/Robot_Super_Course">',
status_code=200, status_code=200,
html=True) html=True)
...@@ -392,11 +402,11 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -392,11 +402,11 @@ class ContentStoreTest(ModuleStoreTestCase):
def test_capa_module(self): def test_capa_module(self):
"""Test that a problem treats markdown specially.""" """Test that a problem treats markdown specially."""
CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course') course = CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course')
problem_data = { problem_data = {
'parent_location': 'i4x://MITx/999/course/Robot_Super_Course', 'parent_location': 'i4x://MITx/999/course/Robot_Super_Course',
'template': 'i4x://edx/templates/problem/Empty' 'template': 'i4x://edx/templates/problem/Blank_Common_Problem'
} }
resp = self.client.post(reverse('clone_item'), problem_data) resp = self.client.post(reverse('clone_item'), problem_data)
...@@ -413,6 +423,52 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -413,6 +423,52 @@ class ContentStoreTest(ModuleStoreTestCase):
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_metadata_inheritance(self):
import_from_xml(modulestore(), 'common/test/data/', ['full'])
ms = modulestore('direct')
course = ms.get_item(Location(['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None]))
verticals = ms.get_items(['i4x', 'edX', 'full', 'vertical', None, None])
# let's assert on the metadata_inheritance on an existing vertical
for vertical in verticals:
self.assertIn('xqa_key', vertical.metadata)
self.assertEqual(course.metadata['xqa_key'], vertical.metadata['xqa_key'])
self.assertGreater(len(verticals), 0)
new_component_location = Location('i4x', 'edX', 'full', 'html', 'new_component')
source_template_location = Location('i4x', 'edx', 'templates', 'html', 'Blank_HTML_Page')
# crate a new module and add it as a child to a vertical
ms.clone_item(source_template_location, new_component_location)
parent = verticals[0]
ms.update_children(parent.location, parent.definition.get('children', []) + [new_component_location.url()])
# flush the cache
ms.get_cached_metadata_inheritance_tree(new_component_location, -1)
new_module = ms.get_item(new_component_location)
# check for grace period definition which should be defined at the course level
self.assertIn('graceperiod', new_module.metadata)
self.assertEqual(course.metadata['graceperiod'], new_module.metadata['graceperiod'])
#
# now let's define an override at the leaf node level
#
new_module.metadata['graceperiod'] = '1 day'
ms.update_metadata(new_module.location, new_module.metadata)
# flush the cache and refetch
ms.get_cached_metadata_inheritance_tree(new_component_location, -1)
new_module = ms.get_item(new_component_location)
self.assertIn('graceperiod', new_module.metadata)
self.assertEqual('1 day', new_module.metadata['graceperiod'])
class TemplateTestCase(ModuleStoreTestCase): class TemplateTestCase(ModuleStoreTestCase):
def test_template_cleanup(self): def test_template_cleanup(self):
...@@ -420,7 +476,7 @@ class TemplateTestCase(ModuleStoreTestCase): ...@@ -420,7 +476,7 @@ class TemplateTestCase(ModuleStoreTestCase):
# insert a bogus template in the store # insert a bogus template in the store
bogus_template_location = Location('i4x', 'edx', 'templates', 'html', 'bogus') bogus_template_location = Location('i4x', 'edx', 'templates', 'html', 'bogus')
source_template_location = Location('i4x', 'edx', 'templates', 'html', 'Empty') source_template_location = Location('i4x', 'edx', 'templates', 'html', 'Blank_HTML_Page')
ms.clone_item(source_template_location, bogus_template_location) ms.clone_item(source_template_location, bogus_template_location)
......
...@@ -143,10 +143,6 @@ class CourseDetailsViewTest(CourseTestCase): ...@@ -143,10 +143,6 @@ class CourseDetailsViewTest(CourseTestCase):
def test_update_and_fetch(self): def test_update_and_fetch(self):
details = CourseDetails.fetch(self.course_location) details = CourseDetails.fetch(self.course_location)
resp = self.client.get(reverse('course_settings', kwargs={'org': self.course_location.org, 'course': self.course_location.course,
'name': self.course_location.name}))
self.assertContains(resp, '<li><a href="#" class="is-shown" data-section="details">Course Details</a></li>', status_code=200, html=True)
# resp s/b json from here on # resp s/b json from here on
url = reverse('course_settings', kwargs={'org': self.course_location.org, 'course': self.course_location.course, url = reverse('course_settings', kwargs={'org': self.course_location.org, 'course': self.course_location.course,
'name': self.course_location.name, 'section': 'details'}) 'name': self.course_location.name, 'section': 'details'})
......
...@@ -4,7 +4,6 @@ from django.test.client import Client ...@@ -4,7 +4,6 @@ from django.test.client import Client
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from path import path from path import path
from tempfile import mkdtemp
import json import json
from fs.osfs import OSFS from fs.osfs import OSFS
import copy import copy
......
...@@ -59,6 +59,7 @@ from cms.djangoapps.models.settings.course_details import CourseDetails,\ ...@@ -59,6 +59,7 @@ from cms.djangoapps.models.settings.course_details import CourseDetails,\
from cms.djangoapps.models.settings.course_grading import CourseGradingModel from cms.djangoapps.models.settings.course_grading import CourseGradingModel
from cms.djangoapps.contentstore.utils import get_modulestore from cms.djangoapps.contentstore.utils import get_modulestore
from lxml import etree from lxml import etree
from django.shortcuts import redirect
# to install PIL on MacOSX: 'easy_install http://dist.repoze.org/PIL-1.1.6.tar.gz' # to install PIL on MacOSX: 'easy_install http://dist.repoze.org/PIL-1.1.6.tar.gz'
...@@ -81,6 +82,11 @@ def signup(request): ...@@ -81,6 +82,11 @@ def signup(request):
csrf_token = csrf(request)['csrf_token'] csrf_token = csrf(request)['csrf_token']
return render_to_response('signup.html', {'csrf': csrf_token}) return render_to_response('signup.html', {'csrf': csrf_token})
def old_login_redirect(request):
'''
Redirect to the active login url.
'''
return redirect('login', permanent=True)
@ssl_login_shortcut @ssl_login_shortcut
@ensure_csrf_cookie @ensure_csrf_cookie
...@@ -94,6 +100,11 @@ def login_page(request): ...@@ -94,6 +100,11 @@ def login_page(request):
'forgot_password_link': "//{base}/#forgot-password-modal".format(base=settings.LMS_BASE), 'forgot_password_link': "//{base}/#forgot-password-modal".format(base=settings.LMS_BASE),
}) })
def howitworks(request):
if request.user.is_authenticated():
return index(request)
else:
return render_to_response('howitworks.html', {})
# ==== Views for any logged-in user ================================== # ==== Views for any logged-in user ==================================
...@@ -120,7 +131,8 @@ def index(request): ...@@ -120,7 +131,8 @@ def index(request):
reverse('course_index', args=[ reverse('course_index', args=[
course.location.org, course.location.org,
course.location.course, course.location.course,
course.location.name])) course.location.name]),
get_lms_link_for_item(course.location))
for course in courses], for course in courses],
'user': request.user, 'user': request.user,
'disable_course_creation': settings.MITX_FEATURES.get('DISABLE_COURSE_CREATION', False) and not request.user.is_staff 'disable_course_creation': settings.MITX_FEATURES.get('DISABLE_COURSE_CREATION', False) and not request.user.is_staff
...@@ -161,6 +173,8 @@ def course_index(request, org, course, name): ...@@ -161,6 +173,8 @@ def course_index(request, org, course, name):
if not has_access(request.user, location): if not has_access(request.user, location):
raise PermissionDenied() raise PermissionDenied()
lms_link = get_lms_link_for_item(location)
upload_asset_callback_url = reverse('upload_asset', kwargs={ upload_asset_callback_url = reverse('upload_asset', kwargs={
'org': org, 'org': org,
'course': course, 'course': course,
...@@ -173,6 +187,7 @@ def course_index(request, org, course, name): ...@@ -173,6 +187,7 @@ def course_index(request, org, course, name):
return render_to_response('overview.html', { return render_to_response('overview.html', {
'active_tab': 'courseware', 'active_tab': 'courseware',
'context_course': course, 'context_course': course,
'lms_link': lms_link,
'sections': sections, 'sections': sections,
'course_graders': json.dumps(CourseGradingModel.fetch(course.location).graders), 'course_graders': json.dumps(CourseGradingModel.fetch(course.location).graders),
'parent_location': course.location, 'parent_location': course.location,
...@@ -273,7 +288,7 @@ def edit_unit(request, location): ...@@ -273,7 +288,7 @@ def edit_unit(request, location):
template.display_name, template.display_name,
template.location.url(), template.location.url(),
'markdown' in template.metadata, 'markdown' in template.metadata,
template.location.name == 'Empty' 'empty' in template.metadata
)) ))
components = [ components = [
...@@ -730,8 +745,6 @@ def clone_item(request): ...@@ -730,8 +745,6 @@ def clone_item(request):
#@login_required #@login_required
#@ensure_csrf_cookie #@ensure_csrf_cookie
def upload_asset(request, org, course, coursename): def upload_asset(request, org, course, coursename):
''' '''
cdodge: this method allows for POST uploading of files into the course asset library, which will cdodge: this method allows for POST uploading of files into the course asset library, which will
...@@ -796,8 +809,6 @@ def upload_asset(request, org, course, coursename): ...@@ -796,8 +809,6 @@ def upload_asset(request, org, course, coursename):
''' '''
This view will return all CMS users who are editors for the specified course This view will return all CMS users who are editors for the specified course
''' '''
@login_required @login_required
@ensure_csrf_cookie @ensure_csrf_cookie
def manage_users(request, location): def manage_users(request, location):
...@@ -819,7 +830,7 @@ def manage_users(request, location): ...@@ -819,7 +830,7 @@ def manage_users(request, location):
}) })
def create_json_response(errmsg=None): def create_json_response(errmsg = None):
if errmsg is not None: if errmsg is not None:
resp = HttpResponse(json.dumps({'Status': 'Failed', 'ErrMsg': errmsg})) resp = HttpResponse(json.dumps({'Status': 'Failed', 'ErrMsg': errmsg}))
else: else:
...@@ -831,8 +842,6 @@ def create_json_response(errmsg=None): ...@@ -831,8 +842,6 @@ def create_json_response(errmsg=None):
This POST-back view will add a user - specified by email - to the list of editors for This POST-back view will add a user - specified by email - to the list of editors for
the specified course the specified course
''' '''
@expect_json @expect_json
@login_required @login_required
@ensure_csrf_cookie @ensure_csrf_cookie
...@@ -865,8 +874,6 @@ def add_user(request, location): ...@@ -865,8 +874,6 @@ def add_user(request, location):
This POST-back view will remove a user - specified by email - from the list of editors for This POST-back view will remove a user - specified by email - from the list of editors for
the specified course the specified course
''' '''
@expect_json @expect_json
@login_required @login_required
@ensure_csrf_cookie @ensure_csrf_cookie
...@@ -1124,8 +1131,31 @@ def get_course_settings(request, org, course, name): ...@@ -1124,8 +1131,31 @@ def get_course_settings(request, org, course, name):
course_details = CourseDetails.fetch(location) course_details = CourseDetails.fetch(location)
return render_to_response('settings.html', { return render_to_response('settings.html', {
'active_tab': 'settings',
'context_course': course_module, 'context_course': course_module,
'course_location' : location,
'course_details' : json.dumps(course_details, cls=CourseSettingsEncoder)
})
@login_required
@ensure_csrf_cookie
def course_config_graders_page(request, org, course, name):
"""
Send models and views as well as html for editing the course settings to the client.
org, course, name: Attributes of the Location for the item to edit
"""
location = ['i4x', org, course, 'course', name]
# check that logged in user has permissions to this item
if not has_access(request.user, location):
raise PermissionDenied()
course_module = modulestore().get_item(location)
course_details = CourseGradingModel.fetch(location)
return render_to_response('settings_graders.html', {
'context_course': course_module,
'course_location' : location,
'course_details': json.dumps(course_details, cls=CourseSettingsEncoder) 'course_details': json.dumps(course_details, cls=CourseSettingsEncoder)
}) })
......
...@@ -20,7 +20,6 @@ Longer TODO: ...@@ -20,7 +20,6 @@ Longer TODO:
""" """
import sys import sys
import tempfile
import os.path import os.path
import os import os
import lms.envs.common import lms.envs.common
...@@ -59,7 +58,8 @@ sys.path.append(COMMON_ROOT / 'lib') ...@@ -59,7 +58,8 @@ sys.path.append(COMMON_ROOT / 'lib')
############################# WEB CONFIGURATION ############################# ############################# WEB CONFIGURATION #############################
# This is where we stick our compiled template files. # This is where we stick our compiled template files.
MAKO_MODULE_DIR = tempfile.mkdtemp('mako') from tempdir import mkdtemp_clean
MAKO_MODULE_DIR = mkdtemp_clean('mako')
MAKO_TEMPLATES = {} MAKO_TEMPLATES = {}
MAKO_TEMPLATES['main'] = [ MAKO_TEMPLATES['main'] = [
PROJECT_ROOT / 'templates', PROJECT_ROOT / 'templates',
...@@ -74,8 +74,8 @@ TEMPLATE_DIRS = MAKO_TEMPLATES['main'] ...@@ -74,8 +74,8 @@ TEMPLATE_DIRS = MAKO_TEMPLATES['main']
MITX_ROOT_URL = '' MITX_ROOT_URL = ''
LOGIN_REDIRECT_URL = MITX_ROOT_URL + '/login' LOGIN_REDIRECT_URL = MITX_ROOT_URL + '/signin'
LOGIN_URL = MITX_ROOT_URL + '/login' LOGIN_URL = MITX_ROOT_URL + '/signin'
TEMPLATE_CONTEXT_PROCESSORS = ( TEMPLATE_CONTEXT_PROCESSORS = (
......
<li class="input input-existing multi course-grading-assignment-list-item"> <li class="field-group course-grading-assignment-list-item">
<div class="row row-col2"> <div class="field text" id="field-course-grading-assignment-name">
<label for="course-grading-assignment-name">Assignment Type Name:</label> <label for="course-grading-assignment-name">Assignment Type Name</label>
<input type="text" class="long" id="course-grading-assignment-name" value="<%= model.get('type') %>" />
<div class="field"> <span class="tip tip-stacked">e.g. Homework, Midterm Exams</span>
<div class="input course-grading-assignment-name">
<input type="text" class="long"
id="course-grading-assignment-name" value="<%= model.get('type') %>">
<span class="tip tip-stacked">e.g. Homework, Labs, Midterm Exams, Final Exam</span>
</div>
</div> </div>
</div>
<div class="row row-col2">
<label for="course-grading-shortname">Abbreviation:</label>
<div class="field"> <div class="field text" id="field-course-grading-assignment-shortname">
<div class="input course-grading-shortname"> <label for="course-grading-assignment-shortname">Abbreviation:</label>
<input type="text" class="short" <input type="text" class="short" id="course-grading-assignment-shortname" value="<%= model.get('short_label') %>" />
id="course-grading-assignment-shortname" <span class="tip tip-inline">e.g. HW, Midterm</span>
value="<%= model.get('short_label') %>">
<span class="tip tip-inline">e.g. HW, Midterm, Final</span>
</div>
</div>
</div> </div>
<div class="row row-col2"> <div class="field text" id="field-course-grading-assignment-gradeweight">
<label for="course-grading-gradeweight">Weight of Total <label for="course-grading-assignment-gradeweight">Weight of Total Grade</label>
Grade:</label> <input type="text" class="short" id="course-grading-assignment-gradeweight" value = "<%= model.get('weight') %>" />
<div class="field">
<div class="input course-grading-gradeweight">
<input type="text" class="short"
id="course-grading-assignment-gradeweight"
value = "<%= model.get('weight') %>">
<span class="tip tip-inline">e.g. 25%</span> <span class="tip tip-inline">e.g. 25%</span>
</div> </div>
</div>
</div>
<div class="row row-col2"> <div class="field text" id="field-course-grading-assignment-totalassignments">
<label for="course-grading-assignment-totalassignments">Total <label for="course-grading-assignment-totalassignments">Total
Number:</label> Number</label>
<input type="text" class="short" id="course-grading-assignment-totalassignments" value = "<%= model.get('min_count') %>" />
<div class="field">
<div class="input course-grading-totalassignments">
<input type="text" class="short"
id="course-grading-assignment-totalassignments"
value = "<%= model.get('min_count') %>">
<span class="tip tip-inline">total exercises assigned</span> <span class="tip tip-inline">total exercises assigned</span>
</div> </div>
</div>
</div>
<div class="row row-col2"> <div class="field text" id="field-course-grading-assignment-droppable">
<label for="course-grading-assignment-droppable">Number of <label for="course-grading-assignment-droppable">Number of
Droppable:</label> Droppable</label>
<input type="text" class="short" id="course-grading-assignment-droppable" value = "<%= model.get('drop_count') %>" />
<div class="field">
<div class="input course-grading-droppable">
<input type="text" class="short"
id="course-grading-assignment-droppable"
value = "<%= model.get('drop_count') %>">
<span class="tip tip-inline">total exercises that won't be graded</span> <span class="tip tip-inline">total exercises that won't be graded</span>
</div> </div>
<div class="actions">
<a href="#" class="button delete-button standard remove-item remove-grading-data"><span class="delete-icon"></span>Delete</a>
</div> </div>
</div>
<a href="#" class="delete-button standard remove-item remove-grading-data"><span class="delete-icon"></span>Delete</a>
</li> </li>
class CMS.Views.TabsEdit extends Backbone.View class CMS.Views.TabsEdit extends Backbone.View
events:
'click .new-tab': 'addNewTab'
initialize: => initialize: =>
@$('.component').each((idx, element) => @$('.component').each((idx, element) =>
...@@ -13,6 +11,7 @@ class CMS.Views.TabsEdit extends Backbone.View ...@@ -13,6 +11,7 @@ class CMS.Views.TabsEdit extends Backbone.View
) )
) )
@options.mast.find('.new-tab').on('click', @addNewTab)
@$('.components').sortable( @$('.components').sortable(
handle: '.drag-handle' handle: '.drag-handle'
update: @tabMoved update: @tabMoved
......
cms/static/img/html-icon.png

1.37 KB | W: | H:

cms/static/img/html-icon.png

581 Bytes | W: | H:

cms/static/img/html-icon.png
cms/static/img/html-icon.png
cms/static/img/html-icon.png
cms/static/img/html-icon.png
  • 2-up
  • Swipe
  • Onion skin
cms/static/img/large-discussion-icon.png

1.46 KB | W: | H:

cms/static/img/large-discussion-icon.png

737 Bytes | W: | H:

cms/static/img/large-discussion-icon.png
cms/static/img/large-discussion-icon.png
cms/static/img/large-discussion-icon.png
cms/static/img/large-discussion-icon.png
  • 2-up
  • Swipe
  • Onion skin
cms/static/img/large-freeform-icon.png

1.17 KB | W: | H:

cms/static/img/large-freeform-icon.png

412 Bytes | W: | H:

cms/static/img/large-freeform-icon.png
cms/static/img/large-freeform-icon.png
cms/static/img/large-freeform-icon.png
cms/static/img/large-freeform-icon.png
  • 2-up
  • Swipe
  • Onion skin
cms/static/img/large-problem-icon.png

1.49 KB | W: | H:

cms/static/img/large-problem-icon.png

797 Bytes | W: | H:

cms/static/img/large-problem-icon.png
cms/static/img/large-problem-icon.png
cms/static/img/large-problem-icon.png
cms/static/img/large-problem-icon.png
  • 2-up
  • Swipe
  • Onion skin
cms/static/img/large-video-icon.png

994 Bytes | W: | H:

cms/static/img/large-video-icon.png

234 Bytes | W: | H:

cms/static/img/large-video-icon.png
cms/static/img/large-video-icon.png
cms/static/img/large-video-icon.png
cms/static/img/large-video-icon.png
  • 2-up
  • Swipe
  • Onion skin
...@@ -61,19 +61,17 @@ CMS.Models.Settings.CourseDetails = Backbone.Model.extend({ ...@@ -61,19 +61,17 @@ CMS.Models.Settings.CourseDetails = Backbone.Model.extend({
url: function() { url: function() {
var location = this.get('location'); var location = this.get('location');
return '/' + location.get('org') + "/" + location.get('course') + '/settings/' + location.get('name') + '/section/details'; return '/' + location.get('org') + "/" + location.get('course') + '/settings-details/' + location.get('name') + '/section/details';
}, },
_videokey_illegal_chars : /[^a-zA-Z0-9_-]/g, _videokey_illegal_chars : /[^a-zA-Z0-9_-]/g,
save_videosource: function(newsource) { save_videosource: function(newsource) {
// newsource either is <video youtube="speed:key, *"/> or just the "speed:key, *" string // newsource either is <video youtube="speed:key, *"/> or just the "speed:key, *" string
// returns the videosource for the preview which iss the key whose speed is closest to 1 // returns the videosource for the preview which iss the key whose speed is closest to 1
if (_.isEmpty(newsource) && !_.isEmpty(this.get('intro_video'))) this.save({'intro_video': null}, if (_.isEmpty(newsource) && !_.isEmpty(this.get('intro_video'))) this.set({'intro_video': null});
{ error : CMS.ServerError});
// TODO remove all whitespace w/in string // TODO remove all whitespace w/in string
else { else {
if (this.get('intro_video') !== newsource) this.save('intro_video', newsource, if (this.get('intro_video') !== newsource) this.set('intro_video', newsource);
{ error : CMS.ServerError});
} }
return this.videosourceSample(); return this.videosourceSample();
......
...@@ -13,7 +13,8 @@ CMS.Models.Settings.CourseGradingPolicy = Backbone.Model.extend({ ...@@ -13,7 +13,8 @@ CMS.Models.Settings.CourseGradingPolicy = Backbone.Model.extend({
} }
if (attributes['graders']) { if (attributes['graders']) {
var graderCollection; var graderCollection;
if (this.has('graders')) { // interesting race condition: if {parse:true} when newing, then parse called before .attributes created
if (this.attributes && this.has('graders')) {
graderCollection = this.get('graders'); graderCollection = this.get('graders');
graderCollection.reset(attributes.graders); graderCollection.reset(attributes.graders);
} }
...@@ -27,7 +28,7 @@ CMS.Models.Settings.CourseGradingPolicy = Backbone.Model.extend({ ...@@ -27,7 +28,7 @@ CMS.Models.Settings.CourseGradingPolicy = Backbone.Model.extend({
}, },
url : function() { url : function() {
var location = this.get('course_location'); var location = this.get('course_location');
return '/' + location.get('org') + "/" + location.get('course') + '/settings/' + location.get('name') + '/section/grading'; return '/' + location.get('org') + "/" + location.get('course') + '/settings-details/' + location.get('name') + '/section/grading';
}, },
gracePeriodToDate : function() { gracePeriodToDate : function() {
var newDate = new Date(); var newDate = new Date();
...@@ -119,7 +120,7 @@ CMS.Models.Settings.CourseGraderCollection = Backbone.Collection.extend({ ...@@ -119,7 +120,7 @@ CMS.Models.Settings.CourseGraderCollection = Backbone.Collection.extend({
model : CMS.Models.Settings.CourseGrader, model : CMS.Models.Settings.CourseGrader,
course_location : null, // must be set to a Location object course_location : null, // must be set to a Location object
url : function() { url : function() {
return '/' + this.course_location.get('org') + "/" + this.course_location.get('course') + '/grades/' + this.course_location.get('name') + '/'; return '/' + this.course_location.get('org') + "/" + this.course_location.get('course') + '/settings-grading/' + this.course_location.get('name') + '/';
}, },
sumWeights : function() { sumWeights : function() {
return this.reduce(function(subtotal, grader) { return subtotal + grader.get('weight'); }, 0); return this.reduce(function(subtotal, grader) { return subtotal + grader.get('weight'); }, 0);
......
...@@ -3,7 +3,6 @@ CMS.Models.Settings.CourseSettings = Backbone.Model.extend({ ...@@ -3,7 +3,6 @@ CMS.Models.Settings.CourseSettings = Backbone.Model.extend({
// a container for the models representing the n possible tabbed states // a container for the models representing the n possible tabbed states
defaults: { defaults: {
courseLocation: null, courseLocation: null,
// NOTE: keep these sync'd w/ the data-section names in settings-page-menu
details: null, details: null,
faculty: null, faculty: null,
grading: null, grading: null,
......
...@@ -10,7 +10,7 @@ CMS.Views.CourseInfoEdit = Backbone.View.extend({ ...@@ -10,7 +10,7 @@ CMS.Views.CourseInfoEdit = Backbone.View.extend({
render: function() { render: function() {
// instantiate the ClassInfoUpdateView and delegate the proper dom to it // instantiate the ClassInfoUpdateView and delegate the proper dom to it
new CMS.Views.ClassInfoUpdateView({ new CMS.Views.ClassInfoUpdateView({
el: this.$('#course-update-view'), el: $('body.updates'),
collection: this.model.get('updates') collection: this.model.get('updates')
}); });
...@@ -27,10 +27,10 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({ ...@@ -27,10 +27,10 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
// collection is CourseUpdateCollection // collection is CourseUpdateCollection
events: { events: {
"click .new-update-button" : "onNew", "click .new-update-button" : "onNew",
"click .save-button" : "onSave", "click #course-update-view .save-button" : "onSave",
"click .cancel-button" : "onCancel", "click #course-update-view .cancel-button" : "onCancel",
"click .edit-button" : "onEdit", "click .post-actions > .edit-button" : "onEdit",
"click .delete-button" : "onDelete" "click .post-actions > .delete-button" : "onDelete"
}, },
initialize: function() { initialize: function() {
......
CMS.Views.ValidatingView = Backbone.View.extend({
// Intended as an abstract class which catches validation errors on the model and
// decorates the fields. Needs wiring per class, but this initialization shows how
// either have your init call this one or copy the contents
initialize : function() {
this.model.on('error', this.handleValidationError, this);
this.selectorToField = _.invert(this.fieldToSelectorMap);
},
errorTemplate : _.template('<span class="message-error"><%= message %></span>'),
events : {
"blur input" : "clearValidationErrors",
"blur textarea" : "clearValidationErrors"
},
fieldToSelectorMap : {
// Your subclass must populate this w/ all of the model keys and dom selectors
// which may be the subjects of validation errors
},
_cacheValidationErrors : [],
handleValidationError : function(model, error) {
// error triggered either by validation or server error
// error is object w/ fields and error strings
for (var field in error) {
var ele = this.$el.find('#' + this.fieldToSelectorMap[field]);
if (ele.length === 0) {
// check if it might a server error: note a typo in the field name
// or failure to put in a map may cause this to muffle validation errors
if (_.has(error, 'error') && _.has(error, 'responseText')) {
CMS.ServerError(model, error);
return;
}
else continue;
}
this._cacheValidationErrors.push(ele);
if ($(ele).is('div')) {
// put error on the contained inputs
$(ele).find('input, textarea').addClass('error');
}
else $(ele).addClass('error');
$(ele).parent().append(this.errorTemplate({message : error[field]}));
}
},
clearValidationErrors : function() {
// error is object w/ fields and error strings
while (this._cacheValidationErrors.length > 0) {
var ele = this._cacheValidationErrors.pop();
if ($(ele).is('div')) {
// put error on the contained inputs
$(ele).find('input, textarea').removeClass('error');
}
else $(ele).removeClass('error');
$(ele).nextAll('.message-error').remove();
}
},
saveIfChanged : function(event) {
// returns true if the value changed and was thus sent to server
var field = this.selectorToField[event.currentTarget.id];
var currentVal = this.model.get(field);
var newVal = $(event.currentTarget).val();
this.clearValidationErrors(); // curr = new if user reverts manually
if (currentVal != newVal) {
this.model.save(field, newVal);
return true;
}
else return false;
},
// these should perhaps go into a superclass but lack of event hash inheritance demotivates me
inputFocus : function(event) {
$("label[for='" + event.currentTarget.id + "']").addClass("is-focused");
},
inputUnfocus : function(event) {
$("label[for='" + event.currentTarget.id + "']").removeClass("is-focused");
}
});
// Studio - Sign In/Up
// ====================
body.signup, body.signin {
.wrapper-content {
margin: 0;
padding: 0 $baseline;
position: relative;
width: 100%;
}
.content {
@include clearfix();
@include font-size(16);
max-width: $fg-max-width;
min-width: $fg-min-width;
width: flex-grid(12);
margin: 0 auto;
color: $gray-d2;
header {
position: relative;
margin-bottom: $baseline;
border-bottom: 1px solid $gray-l4;
padding-bottom: ($baseline/2);
h1 {
@include font-size(32);
margin: 0;
padding: 0;
font-weight: 600;
}
.action {
@include font-size(13);
position: absolute;
right: 0;
top: 40%;
}
}
.introduction {
@include font-size(14);
margin: 0 0 $baseline 0;
}
}
.content-primary, .content-supplementary {
@include box-sizing(border-box);
float: left;
}
.content-primary {
width: flex-grid(8, 12);
margin-right: flex-gutter();
form {
@include box-sizing(border-box);
@include box-shadow(0 1px 2px $shadow-l1);
@include border-radius(2px);
width: 100%;
border: 1px solid $gray-l2;
padding: $baseline ($baseline*1.5);
background: $white;
.form-actions {
margin-top: $baseline;
.action-primary {
@include blue-button;
@include transition(all .15s);
@include font-size(15);
display:block;
width: 100%;
padding: ($baseline*0.75) ($baseline/2);
font-weight: 600;
text-transform: uppercase;
}
}
.list-input {
margin: 0;
padding: 0;
list-style: none;
.field {
margin: 0 0 ($baseline*0.75) 0;
&:last-child {
margin-bottom: 0;
}
&.required {
label {
font-weight: 600;
}
label:after {
margin-left: ($baseline/4);
content: "*";
}
}
label, input, textarea {
display: block;
}
label {
@include font-size(14);
@include transition(color, 0.15s, ease-in-out);
margin: 0 0 ($baseline/4) 0;
&.is-focused {
color: $blue;
}
}
input, textarea {
@include font-size(16);
height: 100%;
width: 100%;
padding: ($baseline/2);
&.long {
width: 100%;
}
&.short {
width: 25%;
}
::-webkit-input-placeholder {
color: $gray-l4;
}
:-moz-placeholder {
color: $gray-l3;
}
::-moz-placeholder {
color: $gray-l3;
}
:-ms-input-placeholder {
color: $gray-l3;
}
&: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;
}
}
.assets { .uploads {
input.asset-search-input { input.asset-search-input {
float: left; float: left;
width: 260px; width: 260px;
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
} }
} }
&:hover { &:hover, &.active {
@include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset, 0 1px 1px rgba(0, 0, 0, .15)); @include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset, 0 1px 1px rgba(0, 0, 0, .15));
} }
} }
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
background-color: $blue; background-color: $blue;
color: #fff; color: #fff;
&:hover { &:hover, &.active {
background-color: #62aaf5; background-color: #62aaf5;
color: #fff; color: #fff;
} }
...@@ -286,3 +286,10 @@ ...@@ -286,3 +286,10 @@
position: absolute; position: absolute;
width: 1px; 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
...@@ -37,6 +37,11 @@ ...@@ -37,6 +37,11 @@
padding: 34px 0 42px; padding: 34px 0 42px;
border-top: 1px solid #cbd1db; border-top: 1px solid #cbd1db;
&:first-child {
padding-top: 0;
border: none;
}
&.editing { &.editing {
position: relative; position: relative;
z-index: 1001; z-index: 1001;
......
...@@ -498,6 +498,7 @@ input.courseware-unit-search-input { ...@@ -498,6 +498,7 @@ input.courseware-unit-search-input {
} }
&.new-section { &.new-section {
header { header {
height: auto; height: auto;
@include clearfix(); @include clearfix();
...@@ -506,6 +507,15 @@ input.courseware-unit-search-input { ...@@ -506,6 +507,15 @@ input.courseware-unit-search-input {
.expand-collapse-icon { .expand-collapse-icon {
visibility: hidden; visibility: hidden;
} }
.item-details {
padding: 25px 0 0 0;
.section-name {
float: none;
width: 100%;
}
}
} }
} }
......
...@@ -6,19 +6,27 @@ ...@@ -6,19 +6,27 @@
@include box-shadow(0 1px 2px rgba(0, 0, 0, .1)); @include box-shadow(0 1px 2px rgba(0, 0, 0, .1));
li { li {
position: relative;
border-bottom: 1px solid $mediumGrey; border-bottom: 1px solid $mediumGrey;
&:last-child { &:last-child {
border-bottom: none; border-bottom: none;
} }
}
a { .class-link {
z-index: 100;
display: block;
padding: 20px 25px; padding: 20px 25px;
line-height: 1.3; line-height: 1.3;
&:hover { &:hover {
background: $paleYellow; background: $paleYellow;
+ .view-live-button {
opacity: 1.0;
pointer-events: auto;
}
}
} }
} }
...@@ -34,6 +42,22 @@ ...@@ -34,6 +42,22 @@
margin-right: 20px; margin-right: 20px;
color: #3c3c3c; 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 { .new-course {
......
.faded-hr-divider {
@include background-image(linear-gradient(180deg, rgba(200,200,200, 0) 0%,
rgba(200,200,200, 1) 50%,
rgba(200,200,200, 0)));
height: 1px;
width: 100%;
}
.faded-hr-divider-medium {
@include background-image(linear-gradient(180deg, rgba(240,240,240, 0) 0%,
rgba(240,240,240, 1) 50%,
rgba(240,240,240, 0)));
height: 1px;
width: 100%;
}
.faded-hr-divider-light {
@include background-image(linear-gradient(180deg, rgba(255,255,255, 0) 0%,
rgba(255,255,255, 0.8) 50%,
rgba(255,255,255, 0)));
height: 1px;
width: 100%;
}
.faded-vertical-divider {
@include background-image(linear-gradient(90deg, rgba(200,200,200, 0) 0%,
rgba(200,200,200, 1) 50%,
rgba(200,200,200, 0)));
height: 100%;
width: 1px;
}
.faded-vertical-divider-light {
@include background-image(linear-gradient(90deg, rgba(255,255,255, 0) 0%,
rgba(255,255,255, 0.6) 50%,
rgba(255,255,255, 0)));
height: 100%;
width: 1px;
}
.vertical-divider {
@extend .faded-vertical-divider;
position: relative;
&::after {
@extend .faded-vertical-divider-light;
content: "";
display: block;
position: absolute;
left: 1px;
}
}
.horizontal-divider {
border: none;
@extend .faded-hr-divider;
position: relative;
&::after {
@extend .faded-hr-divider-light;
content: "";
display: block;
position: absolute;
top: 1px;
}
}
.fade-right-hr-divider {
@include background-image(linear-gradient(180deg, rgba(200,200,200, 0) 0%,
rgba(200,200,200, 1)));
border: none;
}
.fade-left-hr-divider {
@include background-image(linear-gradient(180deg, rgba(200,200,200, 1) 0%,
rgba(200,200,200, 0)));
border: none;
}
\ No newline at end of file
//studio global footer
.wrapper-footer {
margin: ($baseline*1.5) 0 $baseline 0;
padding: $baseline;
position: relative;
width: 100%;
footer.primary {
@include clearfix();
@include font-size(13);
max-width: $fg-max-width;
min-width: $fg-min-width;
width: flex-grid(12);
margin: 0 auto;
padding-top: $baseline;
border-top: 1px solid $gray-l4;
color: $gray-l2;
.colophon {
width: flex-grid(4, 12);
float: left;
margin-right: flex-gutter(2);
}
.nav-peripheral {
width: flex-grid(6, 12);
float: right;
text-align: right;
.nav-item {
display: inline-block;
margin-right: ($baseline/2);
&:last-child {
margin-right: 0;
}
}
}
a {
color: $gray-l1;
&:hover, &:active {
color: $blue;
}
}
}
}
\ No newline at end of file
body.index { // how it works/not signed in index
> header { .index {
display: none;
&.not-signedin {
.wrapper-header {
margin-bottom: 0;
} }
> h1 { .wrapper-footer {
font-weight: 300; margin: 0;
color: lighten($dark-blue, 40%); border-top: 2px solid $gray-l3;
text-shadow: 0 1px 0 #fff;
-webkit-font-smoothing: antialiased; footer.primary {
max-width: 600px; border: none;
text-align: center; margin-top: 0;
margin: 80px auto 30px; padding-top: 0;
}
} }
section.main-container { .wrapper-content-header, .wrapper-content-features, .wrapper-content-cta {
border-right: 3px;
background: #FFF;
max-width: 600px;
margin: 0 auto;
display: block;
@include box-sizing(border-box); @include box-sizing(border-box);
border: 1px solid lighten( $dark-blue , 30% ); margin: 0;
@include border-radius(3px); padding: 0 $baseline;
overflow: hidden; position: relative;
@include bounce-in-animation(.8s); width: 100%;
}
header { .content {
border-bottom: 1px solid lighten($dark-blue, 50%);
@include linear-gradient(#fff, lighten($dark-blue, 62%));
@include clearfix(); @include clearfix();
@include box-shadow( 0 2px 0 $light-blue, inset 0 -1px 0 #fff); @include font-size(16);
text-shadow: 0 1px 0 #fff; max-width: $fg-max-width;
min-width: $fg-min-width;
width: flex-grid(12);
margin: 0 auto;
color: $gray-d2;
header {
border: none;
padding-bottom: 0;
margin-bottom: 0;
}
h1, h2, h3, h4, h5, h6 {
color: $gray-d3;
}
h2 {
}
h3 {
}
h4 {
}
}
// welcome content
.wrapper-content-header {
@include linear-gradient($blue-l1,$blue,$blue-d1);
padding-bottom: ($baseline*4);
padding-top: ($baseline*4);
}
.content-header {
position: relative;
text-align: center;
color: $white;
h1 { h1 {
font-size: 14px; @include font-size(52);
padding: 8px 20px; float: none;
float: left; margin: 0 0 ($baseline/2) 0;
color: $dark-blue; border-bottom: 1px solid $blue-l1;
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; 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 { 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 {
margin-bottom: 0;
border: none;
padding-bottom: 0;
}
&:nth-child(even) {
.img {
float: right; float: right;
padding: 8px 20px; margin-right: 0;
border-left: 1px solid lighten($dark-blue, 50%); margin-left: flex-gutter();
@include box-shadow( inset -1px 0 0 #fff);
font-weight: bold;
font-size: 22px;
line-height: 1;
color: $dark-blue;
} }
.copy {
float: right;
text-align: right;
} }
ol { .list-proofpoints {
list-style: none;
.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; margin: 0;
padding: 0; padding: 0;
}
.list-actions {
position: relative;
margin-top: -($baseline*1.5);
li { li {
border-bottom: 1px solid lighten($dark-blue, 50%); width: flex-grid(6, 12);
margin: 0 auto;
}
a { .action {
display: block; display: block;
padding: 10px 20px; width: 100%;
text-align: center;
&:hover {
color: $dark-blue;
background: lighten($yellow, 10%);
text-shadow: 0 1px 0 #fff;
} }
.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;
} }
&:last-child { .action-secondary {
border-bottom: none; @include font-size(14);
margin-top: ($baseline/2);
} }
} }
} }
......
...@@ -55,3 +55,15 @@ ...@@ -55,3 +55,15 @@
margin-top: 13px; margin-top: 13px;
} }
} }
// lean modal alternative
#lean_overlay {
position: fixed;
z-index: 10000;
top: 0px;
left: 0px;
display: none;
height: 100%;
width: 100%;
background: $black;
}
\ No newline at end of file
...@@ -55,3 +55,117 @@ table { ...@@ -55,3 +55,117 @@ table {
border-collapse: collapse; border-collapse: collapse;
border-spacing: 0; 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
...@@ -28,7 +28,9 @@ ...@@ -28,7 +28,9 @@
border-radius: 0; border-radius: 0;
&.new-component-item { &.new-component-item {
margin-top: 20px; background: transparent;
border: none;
@include box-shadow(none);
} }
} }
......
.subsection .main-wrapper {
margin: 40px;
}
.subsection .inner-wrapper {
@include clearfix();
}
.subsection-body { .subsection-body {
padding: 32px 40px; padding: 32px 40px;
@include clearfix; @include clearfix;
......
.unit .main-wrapper, .unit .main-wrapper {
.subsection .main-wrapper { @include clearfix();
margin: 40px; margin: 40px;
} }
//Problem Selector tab menu requirements
.js .tabs .tab {
display: none;
}
//end problem selector reqs
.main-column { .main-column {
clear: both; clear: both;
float: left; float: left;
...@@ -58,6 +64,7 @@ ...@@ -58,6 +64,7 @@
margin: 20px 40px; margin: 20px 40px;
.title { .title {
margin: 0 0 15px 0; margin: 0 0 15px 0;
color: $mediumGrey; color: $mediumGrey;
...@@ -67,22 +74,25 @@ ...@@ -67,22 +74,25 @@
} }
&.new-component-item { &.new-component-item {
padding: 20px; margin: 20px 0px;
border: none; border-top: 1px solid $mediumGrey;
border-radius: 3px; box-shadow: 0 2px 1px rgba(182, 182, 182, 0.75) inset;
background: $lightGrey; background-color: $lightGrey;
margin-bottom: 0px;
padding-bottom: 20px;
.new-component-button { .new-component-button {
display: block; display: block;
padding: 20px; padding: 20px;
text-align: center; text-align: center;
color: #6d788b; color: #edf1f5;
} }
h5 { h5 {
margin-bottom: 8px; margin: 20px 0px;
color: #fff; color: #fff;
font-weight: 700; font-weight: 600;
font-size: 18px;
} }
.rendered-component { .rendered-component {
...@@ -92,18 +102,21 @@ ...@@ -92,18 +102,21 @@
} }
.new-component-type { .new-component-type {
a, a,
li { li {
display: inline-block; display: inline-block;
} }
a { a {
border: 1px solid $mediumGrey;
width: 100px; width: 100px;
height: 100px; height: 100px;
margin-right: 10px; color: #fff;
margin-bottom: 10px; margin-right: 15px;
margin-bottom: 20px;
border-radius: 8px; border-radius: 8px;
font-size: 13px; font-size: 15px;
line-height: 14px; line-height: 14px;
text-align: center; text-align: center;
@include box-shadow(0 1px 1px rgba(0, 0, 0, .2), 0 1px 0 rgba(255, 255, 255, .4) inset); @include box-shadow(0 1px 1px rgba(0, 0, 0, .2), 0 1px 0 rgba(255, 255, 255, .4) inset);
...@@ -115,25 +128,40 @@ ...@@ -115,25 +128,40 @@
width: 100%; width: 100%;
padding: 10px; padding: 10px;
@include box-sizing(border-box); @include box-sizing(border-box);
color: #fff;
} }
} }
} }
.new-component-templates { .new-component-templates {
display: none; 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; @include clearfix;
.cancel-button { .cancel-button {
margin: 20px 0px 10px 10px;
@include white-button; @include white-button;
} }
.problem-type-tabs {
display: none;
}
// specific menu types // specific menu types
&.new-component-problem { &.new-component-problem {
padding-bottom:10px;
.ss-icon, .editor-indicator { .ss-icon, .editor-indicator {
display: inline-block; display: inline-block;
} }
.problem-type-tabs {
display: inline-block;
}
} }
} }
...@@ -146,7 +174,6 @@ ...@@ -146,7 +174,6 @@
border: 1px solid $darkGreen; border: 1px solid $darkGreen;
background: tint($green,20%); background: tint($green,20%);
color: #fff; color: #fff;
@include transition(background-color .15s);
&:hover { &:hover {
background: $brightGreen; background: $brightGreen;
...@@ -154,19 +181,81 @@ ...@@ -154,19 +181,81 @@
} }
} }
.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: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 { .new-component-template {
margin-bottom: 20px;
li:last-child {
a { a {
border-radius: 0 0 3px 3px; background: #fff;
border-bottom: 1px solid $darkGreen; 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-top: 0px;
} }
} }
li:nth-child(2) { li:nth-child(2) {
a { a {
border-radius: 3px 3px 0 0; border-radius: 0px;
} }
} }
...@@ -175,18 +264,20 @@ ...@@ -175,18 +264,20 @@
display: block; display: block;
padding: 7px 20px; padding: 7px 20px;
border-bottom: none; border-bottom: none;
font-weight: 300; font-weight: 500;
.name { .name {
float: left; float: left;
.ss-icon { .ss-icon {
@include transition(opacity .15s); @include transition(opacity .15s);
position: relative; display: inline-block;
top: 1px; top: 1px;
font-size: 13px;
margin-right: 5px; margin-right: 5px;
opacity: 0.5; opacity: 0.5;
width: 17;
height: 21px;
vertical-align: middle;
} }
} }
...@@ -204,6 +295,7 @@ ...@@ -204,6 +295,7 @@
} }
&:hover { &:hover {
color: #fff;
.ss-icon { .ss-icon {
opacity: 1.0; opacity: 1.0;
...@@ -217,14 +309,18 @@ ...@@ -217,14 +309,18 @@
// specific editor types // specific editor types
.empty { .empty {
@include box-shadow(0 1px 3px rgba(0,0,0,0.2));
margin-bottom: 10px;
a { a {
border-bottom: 1px solid $darkGreen; line-height: 1.4;
border-radius: 3px; font-weight: 400;
font-weight: 500; background: #fff;
background: $green; color: #3c3c3c;
&:hover {
background: tint($green,30%);
color: #fff;
}
} }
} }
} }
...@@ -233,7 +329,7 @@ ...@@ -233,7 +329,7 @@
text-align: center; text-align: center;
h5 { h5 {
color: $green; color: $darkGreen;
} }
} }
...@@ -507,6 +603,7 @@ ...@@ -507,6 +603,7 @@
.edit-state-draft { .edit-state-draft {
.visibility, .visibility,
.edit-draft-message, .edit-draft-message,
.view-button { .view-button {
display: none; display: none;
......
$gw-column: 80px; $baseline: 20px;
$gw-gutter: 20px;
// grid
$gw-column: ($baseline*3);
$gw-gutter: $baseline;
$fg-column: $gw-column; $fg-column: $gw-column;
$fg-gutter: $gw-gutter; $fg-gutter: $gw-gutter;
$fg-max-columns: 12; $fg-max-columns: 12;
$fg-max-width: 1400px; $fg-max-width: 1280px;
$fg-min-width: 810px; $fg-min-width: 900px;
// type
$sans-serif: 'Open Sans', $verdana; $sans-serif: 'Open Sans', $verdana;
$body-line-height: golden-ratio(.875em, 1); $body-line-height: golden-ratio(.875em, 1);
$error-red: rgb(253, 87, 87);
$white: rgb(255,255,255); // colors - new for re-org
$black: rgb(0,0,0); $black: rgb(0,0,0);
$pink: rgb(182,37,104); $white: rgb(255,255,255);
$error-red: rgb(253, 87, 87);
$gray: rgb(127,127,127);
$gray-l1: tint($gray,20%);
$gray-l2: tint($gray,40%);
$gray-l3: tint($gray,60%);
$gray-l4: tint($gray,80%);
$gray-l5: tint($gray,90%);
$gray-d1: shade($gray,20%);
$gray-d2: shade($gray,40%);
$gray-d3: shade($gray,60%);
$gray-d4: shade($gray,80%);
$blue: rgb(85, 151, 221);
$blue-l1: tint($blue,20%);
$blue-l2: tint($blue,40%);
$blue-l3: tint($blue,60%);
$blue-l4: tint($blue,80%);
$blue-l5: tint($blue,90%);
$blue-d1: shade($blue,20%);
$blue-d2: shade($blue,40%);
$blue-d3: shade($blue,60%);
$blue-d4: shade($blue,80%);
$pink: rgb(183, 37, 103);
$pink-l1: tint($pink,20%);
$pink-l2: tint($pink,40%);
$pink-l3: tint($pink,60%);
$pink-l4: tint($pink,80%);
$pink-l5: tint($pink,90%);
$pink-d1: shade($pink,20%);
$pink-d2: shade($pink,40%);
$pink-d3: shade($pink,60%);
$pink-d4: shade($pink,80%);
$green: rgb(37, 184, 90);
$green-l1: tint($green,20%);
$green-l2: tint($green,40%);
$green-l3: tint($green,60%);
$green-l4: tint($green,80%);
$green-l5: tint($green,90%);
$green-d1: shade($green,20%);
$green-d2: shade($green,40%);
$green-d3: shade($green,60%);
$green-d4: shade($green,80%);
$yellow: rgb(231, 214, 143);
$yellow-l1: tint($yellow,20%);
$yellow-l2: tint($yellow,40%);
$yellow-l3: tint($yellow,60%);
$yellow-l4: tint($yellow,80%);
$yellow-l5: tint($yellow,90%);
$yellow-d1: shade($yellow,20%);
$yellow-d2: shade($yellow,40%);
$yellow-d3: shade($yellow,60%);
$yellow-d4: shade($yellow,80%);
$shadow: rgba(0,0,0,0.2);
$shadow-l1: rgba(0,0,0,0.1);
$shadow-d1: rgba(0,0,0,0.4);
// colors - inherited
$baseFontColor: #3c3c3c; $baseFontColor: #3c3c3c;
$offBlack: #3c3c3c; $offBlack: #3c3c3c;
$black: rgb(0,0,0);
$white: rgb(255,255,255);
$blue: #5597dd;
$orange: #edbd3c; $orange: #edbd3c;
$red: #b20610; $red: #b20610;
$green: #108614; $green: #108614;
......
@import 'bourbon/bourbon'; @import 'bourbon/bourbon';
@import 'bourbon/addons/button';
@import 'vendor/normalize'; @import 'vendor/normalize';
@import 'keyframes'; @import 'keyframes';
...@@ -8,8 +9,10 @@ ...@@ -8,8 +9,10 @@
@import "fonts"; @import "fonts";
@import "variables"; @import "variables";
@import "cms_mixins"; @import "cms_mixins";
@import "extends";
@import "base"; @import "base";
@import "header"; @import "header";
@import "footer";
@import "dashboard"; @import "dashboard";
@import "courseware"; @import "courseware";
@import "subsection"; @import "subsection";
...@@ -26,6 +29,8 @@ ...@@ -26,6 +29,8 @@
@import "modal"; @import "modal";
@import "alerts"; @import "alerts";
@import "login"; @import "login";
@import "account";
@import "index";
@import 'jquery-ui-calendar'; @import 'jquery-ui-calendar';
@import 'content-types'; @import 'content-types';
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<section class="activation"> <section class="activation">
<h1>Account already active!</h1> <h1>Account already active!</h1>
<p> This account has already been activated. <a href="/login">Log in here</a>.</p> <p> This account has already been activated. <a href="/signin">Log in here</a>.</p>
</div> </div>
</section> </section>
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
<section class="tos"> <section class="tos">
<div> <div>
<h1>Activation Complete!</h1> <h1>Activation Complete!</h1>
<p>Thanks for activating your account. <a href="/login">Log in here</a>.</p> <p>Thanks for activating your account. <a href="/signin">Log in here</a>.</p>
</div> </div>
</section> </section>
......
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="bodyclass">assets</%block> <%block name="bodyclass">is-signedin course uploads</%block>
<%block name="title">Courseware Assets</%block> <%block name="title">Uploads &amp; Files</%block>
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
...@@ -33,12 +33,27 @@ ...@@ -33,12 +33,27 @@
</tr> </tr>
</script> </script>
<div class="wrapper-mast wrapper">
<header class="mast has-actions has-subtitle">
<div class="title">
<span class="title-sub">Course Content</span>
<h1 class="title-1">Files &amp; Uploads</h1>
</div>
<nav class="nav-actions">
<h3 class="sr">Page Actions</h3>
<ul>
<li class="nav-item">
<a href="#" class="button upload-button new-button"><i class="ss-icon ss-symbolicons-standard icon icon-create">&#xEB40;</i> Upload New File</a>
</li>
</ul>
</nav>
</header>
</div>
<div class="main-wrapper"> <div class="main-wrapper">
<div class="inner-wrapper"> <div class="inner-wrapper">
<div class="page-actions"> <div class="page-actions">
<a href="#" class="upload-button new-button">
<span class="upload-icon"></span>Upload New Asset
</a>
<input type="text" class="asset-search-input search wip-box" placeholder="search assets" style="display:none"/> <input type="text" class="asset-search-input search wip-box" placeholder="search assets" style="display:none"/>
</div> </div>
<article class="asset-library"> <article class="asset-library">
......
...@@ -5,23 +5,29 @@ ...@@ -5,23 +5,29 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>
<%block name="title"></%block> |
% if context_course:
<% ctx_loc = context_course.location %>
${context_course.display_name} |
% endif
edX Studio
</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="path_prefix" content="${MITX_ROOT_URL}">
<%static:css group='base-style'/> <%static:css group='base-style'/>
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/skins/simple/style.css')}" /> <link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/skins/simple/style.css')}" />
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/sets/wiki/style.css')}" /> <link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/sets/wiki/style.css')}" />
<link rel="stylesheet" type="text/css" href="${static.url('css/vendor/symbolset.ss-standard.css')}" />
<link rel="stylesheet" type="text/css" href="${static.url('css/vendor/symbolset.ss-symbolicons-block.css')}" /> <link rel="stylesheet" type="text/css" href="${static.url('css/vendor/symbolset.ss-symbolicons-block.css')}" />
<link rel="stylesheet" type="text/css" href="${static.url('css/vendor/symbolset.ss-standard.css')}" />
<title><%block name="title"></%block></title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="path_prefix" content="${MITX_ROOT_URL}">
<%block name="header_extras"></%block> <%block name="header_extras"></%block>
</head> </head>
<body class="<%block name='bodyclass'></%block> hide-wip"> <body class="<%block name='bodyclass'></%block> hide-wip">
<%include file="widgets/header.html" args="active_tab=active_tab"/> <%include file="widgets/header.html" />
<%include file="courseware_vendor_js.html"/> <%include file="courseware_vendor_js.html"/>
<script type="text/javascript" src="${static.url('js/vendor/json2.js')}"></script> <script type="text/javascript" src="${static.url('js/vendor/json2.js')}"></script>
...@@ -47,9 +53,9 @@ ...@@ -47,9 +53,9 @@
</script> </script>
<%block name="content"></%block> <%block name="content"></%block>
<%include file="widgets/footer.html" />
<%block name="jsextra"></%block> <%block name="jsextra"></%block>
</body> </body>
</html> </html>
......
<%inherit file="base.html" /> <%inherit file="base.html" />
<%block name="title">Course Manager</%block>
<%include file="widgets/header.html"/> <%include file="widgets/header.html"/>
<%block name="content"> <%block name="content">
......
...@@ -2,8 +2,9 @@ ...@@ -2,8 +2,9 @@
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<!-- TODO decode course # from context_course into title --> <!-- TODO decode course # from context_course into title -->
<%block name="title">Course Info</%block> <%block name="title">Updates</%block>
<%block name="bodyclass">course-info</%block> <%block name="bodyclass">is-signedin course course-info updates</%block>
<%block name="jsextra"> <%block name="jsextra">
<script type="text/javascript" src="${static.url('js/template_loader.js')}"></script> <script type="text/javascript" src="${static.url('js/template_loader.js')}"></script>
...@@ -41,16 +42,38 @@ ...@@ -41,16 +42,38 @@
</%block> </%block>
<%block name="content"> <%block name="content">
<div class="wrapper-mast wrapper">
<header class="mast has-actions has-subtitle">
<div class="title">
<span class="title-sub">Course Content</span>
<h1 class="title-1">Course Updates</h1>
</div>
<nav class="nav-actions">
<h3 class="sr">Page Actions</h3>
<ul>
<li class="nav-item">
<a href="#" class=" button new-button new-update-button"><i class="ss-icon ss-symbolicons-standard icon icon-create">&#x002B;</i> New Update</a>
</li>
</ul>
</nav>
</header>
</div>
<div class="wrapper-content wrapper">
<section class="content">
<div class="introduction">
<p clas="copy">Course updates are announcements or notifications you want to share with your class. Other course authors have used them for important exam/date reminders, change in schedules, and to call out any important steps students need to be aware of.</p>
</div>
</section>
</div>
<div class="main-wrapper"> <div class="main-wrapper">
<div class="inner-wrapper"> <div class="inner-wrapper">
<h1>Course Info</h1>
<div class="course-info-wrapper"> <div class="course-info-wrapper">
<div class="main-column window"> <div class="main-column window">
<article class="course-updates" id="course-update-view"> <article class="course-updates" id="course-update-view">
<h2>Course Updates & News</h2>
<a href="#" class="new-update-button">New Update</a>
<ol class="update-list" id="course-update-list"></ol> <ol class="update-list" id="course-update-list"></ol>
<!-- probably replace w/ a vertical where each element of the vertical is a separate update w/ a date and html field -->
</article> </article>
</div> </div>
<div class="sidebar window course-handouts" id="course-handouts-view"></div> <div class="sidebar window course-handouts" id="course-handouts-view"></div>
......
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="title">Edit Static Page</%block> <%block name="title">Editing Static Page</%block>
<%block name="bodyclass">edit-static-page</%block> <%block name="bodyclass">is-signedin course pages edit-static-page</%block>
<%block name="content"> <%block name="content">
<div class="main-wrapper"> <div class="main-wrapper">
......
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="title">Tabs</%block> <%block name="title">Static Pages</%block>
<%block name="bodyclass">static-pages</%block> <%block name="bodyclass">is-signedin course pages static-pages</%block>
<%block name="jsextra"> <%block name="jsextra">
<script type='text/javascript'> <script type='text/javascript'>
...@@ -9,25 +9,49 @@ ...@@ -9,25 +9,49 @@
el: $('.main-wrapper'), el: $('.main-wrapper'),
model: new CMS.Models.Module({ model: new CMS.Models.Module({
id: '${context_course.location}' id: '${context_course.location}'
}) }),
mast: $('.wrapper-mast')
}); });
</script> </script>
</%block> </%block>
<%block name="content"> <%block name="content">
<div class="main-wrapper"> <div class="wrapper-mast wrapper">
<div class="inner-wrapper"> <header class="mast has-actions has-subtitle">
<article class="unit-body"> <div class="title">
<div class="details"> <span class="title-sub">Course Content</span>
<h2>Here you can add and manage additional pages for your course</h2> <h1 class="title-1">Static Pages</h1>
<p>These pages will be added to the primary navigation menu alongside Courseware, Course Info, Discussion, etc.</p>
</div> </div>
<div class="page-actions"> <nav class="nav-actions">
<a href="#" class="new-button new-tab"> <h3 class="sr">Page Actions</h3>
<span class="plus-icon white"></span>New Page <ul>
</a> <li class="nav-item">
<a href="#" class="button new-button new-tab"><i class="ss-icon ss-symbolicons-standard icon icon-create">&#x002B;</i> New Page</a>
</li>
</ul>
</nav>
</header>
</div>
<div class="wrapper-content wrapper">
<section class="content">
<div class="introduction has-links">
<p class="copy">Static Pages are additional pages that supplement your Courseware. Other course authors have used them to share a syllabus, calendar, handouts, and more.</p>
<nav class="nav-introduction-supplementary">
<ul>
<li class="nav-item">
<a rel="modal" href="#preview-lms-staticpages"><i class="ss-icon ss-symbolicons-block icon icon-information">&#x2753;</i>How do Static Pages look to students in my course?</a>
</li>
</ul>
</nav>
</div> </div>
</section>
</div>
<div class="main-wrapper">
<div class="inner-wrapper">
<article class="unit-body">
<div class="tab-list"> <div class="tab-list">
<ol class='components'> <ol class='components'>
...@@ -43,4 +67,17 @@ ...@@ -43,4 +67,17 @@
</article> </article>
</div> </div>
</div> </div>
<div class="content-modal" id="preview-lms-staticpages">
<h3 class="title">How Static Pages are Used in Your Course</h3>
<figure>
<img src="/static/img/preview-lms-staticpages.png" alt="Preview of how Static Pages are used in your course" />
<figcaption class="description">These pages will be presented in your course's main navigation alongside Courseware, Course Info, Discussion, etc.</figcaption>
</figure>
<a href="#" rel="view" class="action action-modal-close">
<i class="ss-icon ss-symbolicons-block icon-close icon">&#x2421;</i>
<span class="label">close modal</span>
</a>
</div>
</%block> </%block>
\ No newline at end of file
...@@ -7,8 +7,9 @@ ...@@ -7,8 +7,9 @@
%> %>
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="bodyclass">subsection</%block>
<%block name="title">CMS Subsection</%block> <%block name="title">CMS Subsection</%block>
<%block name="bodyclass">is-signedin course subsection</%block>
<%namespace name="units" file="widgets/units.html" /> <%namespace name="units" file="widgets/units.html" />
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
...@@ -97,6 +98,7 @@ ...@@ -97,6 +98,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</%block> </%block>
<%block name="jsextra"> <%block name="jsextra">
......
...@@ -2,10 +2,19 @@ ...@@ -2,10 +2,19 @@
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="title">Export</%block> <%block name="title">Export Course</%block>
<%block name="bodyclass">export</%block> <%block name="bodyclass">is-signedin course tools export</%block>
<%block name="content"> <%block name="content">
<div class="wrapper-mast wrapper">
<header class="mast has-subtitle">
<div class="title">
<span class="title-sub">Tools</span>
<h1 class="title-1">Course Export</h1>
</div>
</header>
</div>
<div class="main-wrapper"> <div class="main-wrapper">
<div class="inner-wrapper"> <div class="inner-wrapper">
<article class="export-overview"> <article class="export-overview">
......
<%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %>
<%block name="title">Welcome</%block>
<%block name="bodyclass">not-signedin index howitworks</%block>
<%block name="content">
<div class="wrapper-content-header wrapper">
<section class="content content-header">
<header>
<h1>Welcome to <span class="logo">edX Studio</span></h1>
<p class="tagline">Studio helps manage your courses online, so you can focus on teaching them</p>
</header>
</section>
</div>
<div class="wrapper-content-features wrapper">
<section class="content content-features">
<header>
<h2 class="sr">Studio's Many Features</h2>
</header>
<ol class="list-features">
<li class="feature">
<figure class="img zoom">
<a rel="modal" href="#hiw-feature1">
<img src="/static/img/thumb-hiw-feature1.png" alt="Studio Helps You Keep Your Courses Organized" />
<figcaption class="sr">Studio Helps You Keep Your Courses Organized</figcaption>
<span class="action-zoom">
<i class="ss-icon ss-symbolicons-block icon icon-zoom">&#xE002;</i>
</span>
</a>
</figure>
<div class="copy">
<h3>Keeping Your Course Organized</h3>
<p>The backbone of your course is how it is organized. Studio offers an <strong>Outline</strong> editor, providing a simple hierarchy and easy drag and drop to help you and your students stay organized.</p>
<ul class="list-proofpoints">
<li class="proofpoint">
<h4 class="title">Simple Organization For Content</h4>
<p>Studio uses a simple hierarchy of <strong>sections</strong> and <strong>subsections</strong> to organize your content.</p>
</li>
<li class="proofpoint">
<h4 class="title">Change Your Mind Anytime</h4>
<p>Draft your outline and build content anywhere. Simple drag and drop tools let your reorganize quickly.</p>
</li>
<li class="proofpoint">
<h4 class="title">Go A Week Or A Semester At A Time</h4>
<p>Build and release <strong>sections</strong> to your students incrementally. You don't have to have it all done at once.</p>
</li>
</ul>
</div>
</li>
<li class="feature">
<figure class="img zoom">
<a rel="modal" href="#hiw-feature2">
<img src="/static/img/thumb-hiw-feature2.png" alt="Learning is More than Just Lectures" />
<figcaption class="sr">Learning is More than Just Lectures</figcaption>
<span class="action-zoom">
<i class="ss-icon ss-symbolicons-block icon icon-zoom">&#xE002;</i>
</span>
</a>
</figure>
<div class="copy">
<h3>Learning is More than Just Lectures</h3>
<p>Studio lets you weave your content together in a way that reinforces learning &mdash; short video lectures interleaved with exercises and more. Insert videos and author a wide variety of exercise types with just a few clicks. </p>
<ul class="list-proofpoints">
<li class="proofpoint">
<h4 class="title">Create Learning Pathways</h4>
<p>Help your students understand a small interactive piece at a time with multimedia, HTML, and exercises.</p>
</li>
<li class="proofpoint">
<h4 class="title">Work Visually, Organize Quickly</h4>
<p>Work visually and see exactly what your students will see. Reorganize all your content with drag and drop.</p>
</li>
<li class="proofpoint">
<h4 class="title">A Broad Library of Problem Types</h4>
<p>It's more than just multiple choice. Studio has nearly a dozen types of problems to challenge your learners.</p>
</li>
</ul>
</div>
</li>
<li class="feature">
<figure class="img zoom">
<a rel="modal" href="#hiw-feature3">
<img src="/static/img/thumb-hiw-feature3.png" alt="Studio Gives You Simple, Fast, and Incremental Publishing. With Friends." />
<figcaption class="sr">Studio Gives You Simple, Fast, and Incremental Publishing. With Friends.</figcaption>
<span class="action-zoom">
<i class="ss-icon ss-symbolicons-block icon icon-zoom">&#xE002;</i>
</span>
</a>
</figure>
<div class="copy">
<h3>Simple, Fast, and Incremental Publishing. With Friends.</h3>
<p>Studio works like web applications you already know, yet understands how you build curriculum. Instant publishing to the web when you want it, incremental release when it makes sense. And with co-authors, you can have a whole team building a course, together.</p>
<ul class="list-proofpoints">
<li class="proofpoint">
<h4 class="title">Instant Changes</h4>
<p>Caught a bug? No problem. When you want, your changes to live when you hit Save.</p>
</li>
<li class="proofpoint">
<h4 class="title">Release-On Date Publishing</h4>
<p>When you've finished a <strong>section</strong>, pick when you want it to go live and Studio takes care of the rest. Build your course incrementally.</p>
</li>
<li class="proofpoint">
<h4 class="title">Work in Teams</h4>
<p>Co-authors have full access to all the same authoring tools. Make your course better through a team effort.</p>
</li>
</ul>
</div>
</li>
</ol>
</section>
</div>
<div class="wrapper-content-cta wrapper">
<section class="content content-cta">
<header>
<h2 class="sr">Sign Up for Studio Today!</h2>
</header>
<ul class="list-actions">
<li>
<a href="${reverse('signup')}" class="action action-primary">Sign Up &amp; Start Making an edX Course</a>
</li>
<li>
<a href="${reverse('login')}" class="action action-secondary">Already have a Studio Account? Sign In</a>
</li>
</ul>
</section>
</div>
<div class="content-modal" id="hiw-feature1">
<h3 class="title">Outlining Your Course</h3>
<figure>
<img src="/static/img/hiw-feature1.png" alt="" />
<figcaption class="description">Simple two-level outline to organize your couse. Drag and drop, and see your course at a glance.</figcaption>
</figure>
<a href="#" rel="view" class="action action-modal-close">
<i class="ss-icon ss-symbolicons-block icon icon-close">&#x2421;</i>
<span class="label">close modal</span>
</a>
</div>
<div class="content-modal" id="hiw-feature2">
<h3 class="title">More than Just Lectures</h3>
<figure>
<img src="/static/img/hiw-feature2.png" alt="" />
<figcaption class="description">Quickly create videos, text snippets, inline discussions, and a variety of problem types.</figcaption>
</figure>
<a href="#" rel="view" class="action action-modal-close">
<i class="ss-icon ss-symbolicons-block icon icon-close">&#x2421;</i>
<span class="label">close modal</span>
</a>
</div>
<div class="content-modal" id="hiw-feature3">
<h3 class="title">Publishing on Date</h3>
<figure>
<img src="/static/img/hiw-feature3.png" alt="" />
<figcaption class="description">Simply set the date of a section or subsection, and Studio will publish it to your students for you.</figcaption>
</figure>
<a href="#" rel="view" class="action action-modal-close">
<i class="ss-icon ss-symbolicons-block icon icon-close">&#x2421;</i>
<span class="label">close modal</span>
</a>
</div>
</%block>
\ No newline at end of file
...@@ -2,10 +2,19 @@ ...@@ -2,10 +2,19 @@
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="title">Import</%block> <%block name="title">Import Course</%block>
<%block name="bodyclass">import</%block> <%block name="bodyclass">is-signedin course tools import</%block>
<%block name="content"> <%block name="content">
<div class="wrapper-mast wrapper">
<header class="mast has-subtitle">
<div class="title">
<span class="title-sub">Tools</span>
<h1 class="title-1">Course Import</h1>
</div>
</header>
</div>
<div class="main-wrapper"> <div class="main-wrapper">
<div class="inner-wrapper"> <div class="inner-wrapper">
<article class="import-overview"> <article class="import-overview">
......
<%inherit file="base.html" /> <%inherit file="base.html" />
<%block name="bodyclass">index</%block>
<%block name="title">Courses</%block> <%block name="title">Courses</%block>
<%block name="bodyclass">is-signedin index dashboard</%block>
<%block name="header_extras"> <%block name="header_extras">
<script type="text/template" id="new-course-template"> <script type="text/template" id="new-course-template">
...@@ -32,31 +33,53 @@ ...@@ -32,31 +33,53 @@
</%block> </%block>
<%block name="content"> <%block name="content">
<div class="wrapper-mast wrapper">
<header class="mast has-actions">
<div class="title">
<h1 class="title-1">My Courses</h1>
</div>
% if user.is_active:
<nav class="nav-actions">
<h3 class="sr">Page Actions</h3>
<ul>
<li class="nav-item">
% if not disable_course_creation:
<a href="#" class="button new-button new-course-button"><i class="ss-icon ss-symbolicons-standard icon icon-create">&#x002B;</i> New Course</a>
% endif
</li>
</ul>
</nav>
% endif
</header>
</div>
<div class="wrapper-content wrapper">
<section class="content">
<div class="introduction">
<p class="copy"><strong>Welcome, ${ user.username }</strong>. Here are all of the courses you are currently authoring in Studio:</p>
</div>
</section>
</div>
<div class="main-wrapper"> <div class="main-wrapper">
<div class="inner-wrapper"> <div class="inner-wrapper">
<h1>My Courses</h1>
<article class="my-classes"> <article class="my-classes">
% if user.is_active: % if user.is_active:
% if not disable_course_creation:
<a href="#" class="new-button new-course-button"><span class="plus-icon white"></span> New Course</a>
%endif
<ul class="class-list"> <ul class="class-list">
%for course, url in courses: %for course, url, lms_link in courses:
<li> <li>
<a 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>
<!--
<span class="detail">Started: 9/21/2012</span>
<span class="detail">Ends: 10/21/2012</span>
-->
</a> </a>
<a href="${lms_link}" rel="external" class="button view-button view-live-button"><i class="ss-icon ss-symbolicons-block icon icon-view">&#xE010;</i>View Live</a>
</li> </li>
%endfor %endfor
</ul> </ul>
% else: % else:
<div class='warn-msg'> <div class='warn-msg'>
<p> <p>
In order to start authoring courses using edX studio, please click on the activation link in your email. In order to start authoring courses using edX Studio, please click on the activation link in your email.
</p> </p>
</div> </div>
% endif % endif
......
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="title">Log in</%block> <%block name="title">Sign In</%block>
<%block name="bodyclass">no-header</%block> <%block name="bodyclass">not-signedin signin</%block>
<%block name="content"> <%block name="content">
<div class="edx-studio-logo-large"></div> <div class="wrapper-content wrapper">
<section class="content">
<article class="log-in-box">
<header> <header>
<h1>Log in to edX studio</h1> <h1 class="title title-1">Sign In to edX Studio</h1>
<a href="${reverse('signup')}" class="action action-signin">Don't have a Studio Account? Sign up!</a>
</header> </header>
<form class="log-in-form" id="login_form" action="login_post" method="post">
<div class="row"> <article class="content-primary" role="main">
<label>Email</label> <form id="login_form" method="post" action="login_post">
<input name="email" type="email" class="email-field" tabindex="1">
</div> <fieldset>
<div class="row"> <legend class="sr">Required Information to Sign In to edX Studio</legend>
<label>Password <a href="${forgot_password_link}" class="forgot-button">Forgot password?</a></label>
<input name="password" type="password" class="password-field" tabindex="2"> <ol class="list-input">
</div> <li class="field text required" id="field-email">
<div class="row form-actions"> <label for="email">Email Address</label>
<input name="submit" type="submit" value="Log In" class="log-in-button" tabindex="3"> <input id="email" type="email" name="email" placeholder="e.g. jane.doe@gmail.com" />
<span class="or">or</span> </li>
<a href="${reverse('signup')}" class="sign-up-button" tabindex="4">Sign up</a>
<li class="field text required" id="field-password">
<a href="${forgot_password_link}" class="action action-forgotpassword" tabindex="-1">Forgot password?</a>
<label for="password">Password</label>
<input id="password" type="password" name="password" />
</li>
</ol>
</fieldset>
<div class="form-actions">
<button type="submit" id="submit" name="submit" class="action action-primary">Sign In to edX Studio</button>
</div> </div>
<!-- no honor code for CMS, but need it because we're using the lms student object -->
<input name="honor_code" type="checkbox" value="true" checked="true" hidden="true">
</form> </form>
</article> </article>
<aside class="content-supplementary" role="complimentary">
<h2 class="sr">Studio Support</h2>
<div class="bit">
<h3 class="title-3">Need Help?</h3>
<p>Having trouble with your account? Use <a href="http://help.edge.edx.org" rel="external">our support center</a> to look over self help steps, find solutions others have found to the same problem, or let us know of your issue.</p>
</div>
</aside>
</section>
</div>
</%block>
<%block name="jsextra">
<script type="text/javascript"> <script type="text/javascript">
(function() { (function() {
function getCookie(name) { function getCookie(name) {
...@@ -51,12 +77,16 @@ ...@@ -51,12 +77,16 @@
submit_data, submit_data,
function(json) { function(json) {
if(json.success) { if(json.success) {
location.href = "${reverse('index')}"; var next = /next=([^&]*)/g.exec(decodeURIComponent(window.location.search));
if (next && next.length > 1) {
location.href = next[1];
}
else location.href = "${reverse('homepage')}";
} else if($('#login_error').length == 0) { } else if($('#login_error').length == 0) {
$('#login_form').prepend('<div id="login_error">' + json.value + '</div>'); $('#login_form').prepend('<div id="login_error" class="message message-status error">' + json.value + '</span></div>');
$('#login_error').slideDown(150); $('#login_error').addClass('is-shown');
} else { } else {
$('#login_error').stop().slideDown(150); $('#login_error').stop().addClass('is-shown');
$('#login_error').html(json.value); $('#login_error').html(json.value);
} }
} }
...@@ -64,5 +94,4 @@ ...@@ -64,5 +94,4 @@
}); });
})(this) })(this)
</script> </script>
</%block> </%block>
\ No newline at end of file
<%inherit file="base.html" /> <%inherit file="base.html" />
<%block name="title">Course Staff Manager</%block> <%block name="title">Course Staff Manager</%block>
<%block name="bodyclass">users</%block> <%block name="bodyclass">is-signedin course users settings team</%block>
<%block name="content"> <%block name="content">
<div class="main-wrapper"> <div class="wrapper-mast wrapper">
<div class="inner-wrapper"> <header class="mast has-actions has-subtitle">
<div class="page-actions"> <div class="title">
<span class="title-sub">Course Settings</span>
<h1 class="title-1">Course Team</h1>
</div>
<nav class="nav-actions">
<h3 class="sr">Page Actions</h3>
<ul>
%if allow_actions: %if allow_actions:
<a href="#" class="new-button new-user-button"> <li class="nav-item">
<span class="plus-icon white"></span>New User <a href="#" class="button new-button new-user-button"><i class="ss-icon ss-symbolicons-standard icon icon-create">&#x002B;</i> New User</a>
</a> </li>
%endif %endif
</div> </ul>
</nav>
</header>
</div>
<div class="main-wrapper">
<div class="inner-wrapper">
<div class="details"> <div class="details">
<p>The following list of users have been designated as course staff. This means that these users will have permissions to modify course content. You may add additional course staff below, if you are the course instructor. Please note that they must have already registered and verified their account.</p> <p>The following list of users have been designated as course staff. This means that these users will have permissions to modify course content. You may add additional course staff below, if you are the course instructor. Please note that they must have already registered and verified their account.</p>
......
...@@ -6,7 +6,8 @@ ...@@ -6,7 +6,8 @@
from datetime import datetime from datetime import datetime
%> %>
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="title">CMS Courseware Overview</%block> <%block name="title">Course Outline</%block>
<%block name="bodyclass">is-signedin course outline</%block>
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<%namespace name="units" file="widgets/units.html" /> <%namespace name="units" file="widgets/units.html" />
...@@ -119,12 +120,32 @@ ...@@ -119,12 +120,32 @@
</div> </div>
</div> </div>
<div class="wrapper-mast wrapper">
<header class="mast has-actions has-subtitle">
<div class="title">
<span class="title-sub">Course Content</span>
<h1 class="title-1">Course Outline</h1>
</div>
<nav class="nav-actions">
<h3 class="sr">Page Actions</h3>
<ul>
<li class="nav-item">
<a href="#" class="toggle-button toggle-button-sections"><i class="ss-icon ss-symbolicons-block icon">up</i> <span class="label">Collapse All Sections</span></a>
</li>
<li class="nav-item">
<a href="#" class="button new-button new-courseware-section-button"><i class="ss-icon ss-symbolicons-standard icon icon-create">&#x002B;</i> New Section</a>
</li>
<li class="nav-item">
<a href="${lms_link}" rel="external" class="button view-button view-live-button"><i class="ss-icon ss-symbolicons-block icon icon-view">&#xE010;</i>View Live</a>
</li>
</ul>
</nav>
</header>
</div>
<div class="main-wrapper"> <div class="main-wrapper">
<div class="inner-wrapper"> <div class="inner-wrapper">
<div class="page-actions">
<a href="#" class="new-button new-courseware-section-button"><span class="plus-icon white"></span> New Section</a>
<a href="#" class="toggle-button toggle-button-sections"><i class="ss-icon ss-symbolicons-block">up</i> <span class="label">Collapse All Sections</span></a>
</div>
<article class="courseware-overview" data-course-id="${context_course.location.url()}"> <article class="courseware-overview" data-course-id="${context_course.location.url()}">
% for section in sections: % for section in sections:
<section class="courseware-section branch" data-id="${section.location}"> <section class="courseware-section branch" data-id="${section.location}">
......
<%inherit file="base.html" />
<%block name="title">Grading</%block>
<%block name="bodyclass">is-signedin course grading settings</%block>
<%namespace name='static' file='static_content.html'/>
<%!
from contentstore import utils
%>
<%block name="jsextra">
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/timepicker/jquery.timepicker.css')}" />
<script src="${static.url('js/vendor/timepicker/jquery.timepicker.js')}"></script>
<script type="text/javascript" src="${static.url('js/template_loader.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/server_error.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/course_relative.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/validating_view.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/settings/course_grading_policy.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/settings/settings_grading_view.js')}"></script>
<script type="text/javascript">
$(document).ready(function(){
$("form :input").focus(function() {
$("label[for='" + this.id + "']").addClass("is-focused");
}).blur(function() {
$("label").removeClass("is-focused");
});
var editor = new CMS.Views.Settings.Grading({
el: $('.settings-grading'),
model : new CMS.Models.Settings.CourseGradingPolicy(${course_details|n},{parse:true})
});
editor.render();
});
</script>
</%block>
<%block name="content">
<div class="wrapper-mast wrapper">
<header class="mast has-subtitle">
<div class="title">
<span class="title-sub">Settings</span>
<h1 class="title-1">Grading</h1>
</div>
</header>
</div>
<div class="wrapper-content wrapper">
<section class="content">
<article class="content-primary" role="main">
<form id="settings_details" class="settings-grading" method="post" action="">
<section class="group-settings grade-range">
<header>
<h2 class="title-2">Overall Grade Range</h2>
<span class="tip">Your overall grading scale for student final grades</span>
</header>
<ol class="list-input">
<li class="field" id="field-course-grading-range">
<div class="grade-controls course-grading-range well">
<a href="#" class="new-grade-button"><span class="plus-icon"></span></a>
<div class="grade-slider">
<div class="grade-bar">
<ol class="increments">
<li class="increment-0">0</li>
<li class="increment-10">10</li>
<li class="increment-20">20</li>
<li class="increment-30">30</li>
<li class="increment-40">40</li>
<li class="increment-50">50</li>
<li class="increment-60">60</li>
<li class="increment-70">70</li>
<li class="increment-80">80</li>
<li class="increment-90">90</li>
<li class="increment-100">100</li>
</ol>
<ol class="grades">
</ol>
</div>
</div>
</div>
</li>
</ol>
</section>
<hr class="divide" />
<section class="group-settings grade-rules">
<header>
<h2 class="title-2">Grading Rules &amp; Policies</h2>
<span class="tip">Deadlines, requirements, and logistics around grading student work</span>
</header>
<ol class="list-input">
<li class="field text" id="field-course-grading-graceperiod">
<label for="course-grading-graceperiod">Grace Period on Deadline:</label>
<input type="text" class="short time" id="course-grading-graceperiod" value="0:00" placeholder="e.g. 10 minutes">
<span class="tip tip-inline">Leeway on due dates</span>
</li>
</ol>
</section>
<hr class="divide" />
<section class="group-settings assignment-types">
<header>
<h2 class="title-2">Assignment Types</h2>
<span class="tip">Categories and labels for any exercises that are gradable</span>
</header>
<ol class="list-input course-grading-assignment-list enum">
</ol>
<div class="actions">
<a href="#" class="new-button new-course-grading-item add-grading-data">
<span class="plus-icon white"></span>New Assignment Type
</a>
</div>
</section>
</form>
</article>
<aside class="content-supplementary" role="complimentary">
<div class="bit">
<h3 class="title-3">How will these settings be used</h3>
<p>Your grading settings will be used to calculate students grades and performance.</p>
<p>Overall grade range will be used in students' final grades, which are calculated by the weighting you determine for each custom assignment type.</p>
</div>
<div class="bit">
% if context_course:
<% ctx_loc = context_course.location %>
<%! from django.core.urlresolvers import reverse %>
<h3 class="title-3">Other Course Settings</h3>
<nav class="nav-related">
<ul>
<li class="nav-item"><a href="${reverse('contentstore.views.get_course_settings', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Details &amp; Schedule</a></li>
<li class="nav-item"><a href="${reverse('manage_users', kwargs=dict(location=ctx_loc))}">Course Team</a></li>
</ul>
</nav>
% endif
</div>
</aside>
</section>
</div>
</%block>
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="title">Sign up</%block> <%block name="title">Sign Up</%block>
<%block name="bodyclass">no-header</%block> <%block name="bodyclass">not-signedin signup</%block>
<%block name="content"> <%block name="content">
<div class="edx-studio-logo-large"></div> <div class="wrapper-content wrapper">
<section class="content">
<article class="sign-up-box">
<header> <header>
<h1>Register for edX studio</h1> <h1 class="title title-1">Sign Up for edX Studio</h1>
<a href="${reverse('login')}" class="action action-signin">Already have a Studio Account? Sign in</a>
</header> </header>
<form id="register_form" method="post">
<div id="register_error" name="register_error"></div> <p class="introduction">Ready to start creating online courses? Sign up below and start creating your first edX course today.</p>
<div class="row">
<label>Email</label> <article class="content-primary" role="main">
<input name="email" type="email"> <form id="register_form" method="post" action="register_post">
</div> <div id="register_error" name="register_error" class="message message-status message-status error">
<div class="row">
<label>Password</label>
<input name="password" type="password">
</div>
<div class="row">
<label>Public Username</label>
<input name="username" type="text">
</div>
<div class="row">
<label>Full Name</label>
<input name="name" type="text">
</div>
<div class="row">
<div class="split">
<label>Your Location</label>
<input name="location" type="text">
</div> </div>
<div class="split">
<label>Preferred Language</label> <fieldset>
<input name="language" type="text"> <legend class="sr">Required Information to Sign Up for edX Studio</legend>
<ol class="list-input">
<li class="field text required" id="field-email">
<label for="email">Email Address</label>
<input id="email" type="email" name="email" placeholder="e.g. jane.doe@gmail.com" />
</li>
<li class="field text required" id="field-password">
<label for="password">Password</label>
<input id="password" type="password" name="password" />
</li>
<li class="field text required" id="field-username">
<label for="username">Public Username</label>
<input id="username" type="text" name="username" placeholder="e.g. janedoe" />
<span class="tip tip-stacked">This will be used in public discussions with your courses and in our edX101 support forums</span>
</li>
<li class="field text required" id="field-name">
<label for="name">Full Name</label>
<input id="name" type="text" name="name" placeholder="e.g. Jane Doe" />
</li>
<li class="field-group">
<div class="field text" id="field-location">
<label for="location">Your Location</label>
<input class="short" id="location" type="text" name="location" />
</div> </div>
<div class="field text" id="field-language">
<label for="language">Preferred Language</label>
<input class="short" id="language" type="text" name="language" />
</div> </div>
<div class="row"> </li>
<label class="terms-of-service">
<input name="terms_of_service" type="checkbox" value="true"> <li class="field checkbox required" id="field-tos">
I agree to the <input id="tos" name="terms_of_service" type="checkbox" value="true" />
<a href="#">Terms of Service</a> <label for="tos">I agree to the Terms of Service</label>
</label> </li>
</ol>
</fieldset>
<div class="form-actions">
<button type="submit" id="submit" name="submit" class="action action-primary">Create My Account & Start Authoring Courses</button>
</div> </div>
<!-- no honor code for CMS, but need it because we're using the lms student object --> <!-- no honor code for CMS, but need it because we're using the lms student object -->
<input name="honor_code" type="checkbox" value="true" checked="true" hidden="true"> <input name="honor_code" type="checkbox" value="true" checked="true" hidden="true">
</form>
</article>
<aside class="content-supplementary" role="complimentary">
<h2 class="sr">Common Studio Questions</h2>
<div class="row form-actions submit"> <div class="bit">
<input name="submit" type="submit" value="Create My Account" class="create-account-button"> <h3 class="title-3">Who is Studio for?</h3>
<p class="enrolled">Already enrolled? <a href="/">Log In.</a></p> <p>Studio is for anyone that wants to create online courses that leverage the global edX platform. Our users are often faculty members, teaching assistants and course staff, and members of instructional technology groups.</p>
</div>
<div class="bit">
<h3 class="title-3">How technically savvy do I need to be to create courses in Studio?</h3>
<p>Studio is designed to be easy to use by almost anyone familiar with common web-based authoring environments (Wordpress, Moodle, etc.). No programming knowledge is required, but for some of the more advanced features, a technical background would be helpful. As always, we are here to help, so don't hesitate to dive right in.</p>
</div> </div>
</form>
</article>
<script type="text/javascript"> <div class="bit">
<h3 class="title-3">I've never authored a course online before. Is there help?</h3>
<p>Absolutely. We have created an online course, edX101, that describes some best practices: from filming video, creating exercises, to the basics of running an online course. Additionally, we're always here to help, just drop us a note.</p>
</div>
</aside>
</section>
</div>
</%block>
<%block name="jsextra">
<script type="text/javascript">
(function() { (function() {
$("form :input").focus(function() {
$("label[for='" + this.id + "']").addClass("is-focused");
}).blur(function() {
$("label").removeClass("is-focused");
});
function getCookie(name) { function getCookie(name) {
return $.cookie(name); return $.cookie(name);
} }
// form validation
function postJSON(url, data, callback) { function postJSON(url, data, callback) {
$.ajax({type:'POST', $.ajax({type:'POST',
url: url, url: url,
...@@ -84,11 +131,11 @@ ...@@ -84,11 +131,11 @@
if(json.success) { if(json.success) {
location.href = "${reverse('index')}"; location.href = "${reverse('index')}";
} else { } else {
$('#register_error').html(json.value).stop().slideDown(150); $('#register_error').html(json.value).stop().addClass('is-shown');
} }
} }
); );
}); });
})(this) })(this)
</script> </script>
</%block> </%block>
\ No newline at end of file
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%namespace name="units" file="widgets/units.html" /> <%namespace name="units" file="widgets/units.html" />
<%block name="bodyclass">unit</%block> <%block name="title">Individual Unit</%block>
<%block name="title">CMS Unit</%block> <%block name="bodyclass">is-signedin course unit</%block>
<%block name="jsextra"> <%block name="jsextra">
<script type='text/javascript'> <script type='text/javascript'>
$(document).ready(function() { $(document).ready(function() {
...@@ -14,11 +15,19 @@ ...@@ -14,11 +15,19 @@
}) })
}); });
$(document).ready(function() {
$('body').addClass('js');
// tabs
$('.tab-group').tabs();
});
$('.new-component-template').each(function(){ $('.new-component-template').each(function(){
$emptyEditor = $(this).find('.empty'); $emptyEditor = $(this).find('.empty');
$(this).prepend($emptyEditor); $(this).prepend($emptyEditor);
}); });
}); });
</script> </script>
</%block> </%block>
...@@ -56,38 +65,66 @@ ...@@ -56,38 +65,66 @@
</div> </div>
% for type, templates in sorted(component_templates.items()): % for type, templates in sorted(component_templates.items()):
<div class="new-component-templates new-component-${type}"> <div class="new-component-templates new-component-${type}">
<h3 class="title">Select <span class="type">${type}</span> component type:</h3> % if type == "problem":
<div class="tab-group tabs">
<ul class="problem-type-tabs nav-tabs">
<li class="current">
<a class="link-tab" href="#tab1">Common Problem Types</a>
</li>
<li>
<a class="link-tab" href="#tab2">Advanced</a>
</li>
</ul>
% endif
<div class="tab current" id="tab1">
<ul class="new-component-template"> <ul class="new-component-template">
% for name, location, has_markdown, is_empty in templates: % for name, location, has_markdown, is_empty in templates:
% if has_markdown or type != "problem":
% if is_empty: % if is_empty:
<li class="editor-md empty"> <li class="editor-md empty">
<a href="#" data-location="${location}"> <a href="#" data-location="${location}">
<span class="name"><i class="ss-icon ss-symbolicons-block">&#xE714;</i> ${name}</span> <span class="name"> ${name}</span>
<span class="editor-indicator">Simple <span class="sr">Editor</span></span>
</a> </a>
</li> </li>
% elif has_markdown: % else:
<li class="editor-md"> <li class="editor-md">
<a href="#" data-location="${location}"> <a href="#" data-location="${location}">
<span class="name"><i class="ss-icon ss-symbolicons-block">&#xE714;</i> ${name}</span> <span class="name"> ${name}</span>
<span class="editor-indicator">Simple <span class="sr">Editor</span></span> </a>
</li>
% endif
% endif
%endfor
</ul>
</div>
% if type == "problem":
<div class="tab" id="tab2">
<ul class="new-component-template">
% for name, location, has_markdown, is_empty in templates:
% if not has_markdown:
% if is_empty:
<li class="editor-manual empty">
<a href="#" data-location="${location}">
<span class="name">${name}</span>
</a> </a>
</li> </li>
% else: % else:
<li class="editor-manual"> <li class="editor-manual">
<a href="#" data-location="${location}"> <a href="#" data-location="${location}">
<span class="name"><i class="ss-icon ss-symbolicons-block">&#x1F527;</i> ${name}</span> <span class="name"> ${name}</span>
<span class="editor-indicator">Advanced <span class="sr">Editor</span></span>
</a> </a>
</li> </li>
% endif % endif
% endif
%endfor % endfor
</ul> </ul>
</div>
</div>
% endif
<a href="#" class="cancel-button">Cancel</a> <a href="#" class="cancel-button">Cancel</a>
</div> </div>
% endfor % endfor
......
<%! from django.core.urlresolvers import reverse %>
<div class="wrapper-footer wrapper">
<footer class="primary" role="contentinfo">
<div class="colophon">
<p>&copy; 2013 <a href="http://www.edx.org" rel="external">edX</a>. All rights reserved.</p>
</div>
<nav class="nav-peripheral">
<ol>
<!-- <li class="nav-item nav-peripheral-tos">
<a href="#">Terms of Service</a>
</li>
<li class="nav-item nav-peripheral-pp">
<a href="#">Privacy Policy</a>
</li> -->
<li class="nav-item nav-peripheral-help">
<a href="http://help.edge.edx.org/" rel="external">edX Studio Help</a>
</li>
<li class="nav-item nav-peripheral-contact">
<a href="https://www.edx.org/contact" rel="external">Contact edX</a>
</li>
% if user.is_authenticated():
<!-- add in zendesk/tender feedback form UI -->
% endif
</ol>
</nav>
</footer>
</div>
\ No newline at end of file
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<% active_tab_class = 'active-tab-' + active_tab if active_tab else '' %> <div class="wrapper-header wrapper">
<header class="primary-header ${active_tab_class}"> <header class="primary" role="banner">
<div class="class">
<div class="inner-wrapper"> <div class="wrapper wrapper-left ">
<div class="left"> <h1 class="branding"><a href="/">edX Studio</a></h1>
% if context_course: % if context_course:
<% ctx_loc = context_course.location %> <% ctx_loc = context_course.location %>
<a href="/" class="home"><span class="small-home-icon"></span></a> <div class="info-course">
<a href="${reverse('course_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" class="class-name">${context_course.display_name}</a> <h2 class="sr">Current Course:</h2>
% endif <a href="${reverse('course_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">
<span class="course-org">${ctx_loc.org}</span><span class="course-number">${ctx_loc.course}</span>
<span class="course-title" title="${context_course.display_name}">${context_course.display_name}</span>
</a>
</div> </div>
<div class="right"> <nav class="nav-course primary nav-dropdown" role="navigation">
<span class="username">${ user.username }</span> <h2 class="sr">${context_course.display_name}'s Navigation:</h2>
% if user.is_authenticated():
<a href="${reverse('logout')}" class="log-out"><span class="log-out-icon"></span></a> <ol>
% else: <li class="nav-item nav-course-courseware">
<a href="${reverse('login')}">Log in</a> <h3 class="title"><span class="label-prefix">Course </span>Content <i class="ss-icon ss-symbolicons-block icon-expand">&#x25BE;</i></h3>
% endif
<div class="wrapper wrapper-nav-sub">
<div class="nav-sub">
<ul>
<li class="nav-item nav-course-courseware-outline"><a href="${reverse('course_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Outline</a></li>
<li class="nav-item nav-course-courseware-updates"><a href="${reverse('course_info', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Updates</a></li>
<li class="nav-item nav-course-courseware-pages"><a href="${reverse('edit_tabs', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, coursename=ctx_loc.name))}">Static Pages</a></li>
<li class="nav-item nav-course-courseware-uploads"><a href="${reverse('asset_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Files &amp; Uploads</a></li>
</ul>
</div>
</div> </div>
</li>
<li class="nav-item nav-course-settings">
<h3 class="title"><span class="label-prefix">Course </span>Settings <i class="ss-icon ss-symbolicons-block icon-expand">&#x25BE;</i></h3>
<div class="wrapper wrapper-nav-sub">
<div class="nav-sub">
<ul>
<li class="nav-item nav-course-settings-schedule"><a href="${reverse('contentstore.views.get_course_settings', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Schedule &amp; Details</a></li>
<li class="nav-item nav-course-settings-grading"><a href="${reverse('contentstore.views.course_config_graders_page', kwargs={'org' : ctx_loc.org, 'course' : ctx_loc.course, 'name': ctx_loc.name})}">Grading</a></li>
<li class="nav-item nav-course-settings-team"><a href="${reverse('manage_users', kwargs=dict(location=ctx_loc))}">Course Team</a></li>
<!-- <li class="nav-item nav-course-settings-advanced"><a href="${reverse('course_settings', kwargs={'org' : ctx_loc.org, 'course' : ctx_loc.course, 'name': ctx_loc.name})}">Advanced Settings</a></li> -->
</ul>
</div> </div>
</div> </div>
<nav class="class-nav-bar"> </li>
% if context_course:
<% ctx_loc = context_course.location %> <li class="nav-item nav-course-tools">
<ul class="class-nav inner-wrapper"> <h3 class="title">Tools <i class="ss-icon ss-symbolicons-block icon-expand">&#x25BE;</i></h3>
<li><a href="${reverse('course_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='courseware-tab'>Courseware</a></li>
<li><a href="${reverse('course_info', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='courseinfo-tab'>Course Info</a></li> <div class="wrapper wrapper-nav-sub">
<li><a href="${reverse('edit_tabs', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, coursename=ctx_loc.name))}" id='pages-tab'>Pages</a></li> <div class="nav-sub">
<li><a href="${reverse('manage_users', kwargs=dict(location=ctx_loc))}" id='users-tab'>Users</a></li> <ul>
<li><a href="${reverse('asset_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='assets-tab'>Assets</a></li> <li class="nav-item nav-course-tools-import"><a href="${reverse('import_course', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Import</a></li>
<li><a href="${reverse('course_settings', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='settings-tab'>Settings</a></li> <li class="nav-item nav-course-tools-export"><a href="${reverse('export_course', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Export</a></li>
<li><a href="${reverse('import_course', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='import-tab'>Import</a></li>
<li><a href="${reverse('export_course', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='export-tab'>Export</a></li>
</ul> </ul>
</div>
</div>
</li>
</ol>
</nav>
% endif % endif
</div>
<div class="wrapper wrapper-right">
% if user.is_authenticated():
<nav class="nav-account nav-is-signedin nav-dropdown">
<h2 class="sr">Currently logged in as:</h2>
<ol>
<li class="nav-item nav-account-username">
<a href="#" class="title">
<span class="account-username">
<i class="ss-icon ss-symbolicons-standard icon-user">&#x1F464;</i>
${ user.username }
</span>
<i class="ss-icon ss-symbolicons-block icon-expand">&#x25BE;</i>
</a>
<div class="wrapper wrapper-nav-sub">
<div class="nav-sub">
<ul>
<li class="nav-item nav-account-dashboard"><a href="/">My Courses</a></li>
<li class="nav-item nav-account-help"><a href="http://help.edge.edx.org/" rel="external">Studio Help</a></li>
<li class="nav-item nav-account-signout"><a class="action action-signout" href="${reverse('logout')}">Sign Out</a></li>
</ul>
</div>
</div>
</li>
</ol>
</nav> </nav>
</header> % else:
<nav class="nav-not-signedin">
<h2 class="sr">You're not currently signed in</h2>
<ol>
<li class="nav-item nav-not-signedin-hiw">
<a href="/">How Studio Works</a>
</li>
<li class="nav-item nav-not-signedin-help">
<a href="http://help.edge.edx.org/" rel="external">Studio Help</a>
</li>
<li class="nav-item nav-not-signedin-signup">
<a class="action action-signup" href="${reverse('signup')}">Sign Up</a>
</li>
<li class="nav-item nav-not-signedin-signin">
<a class="action action-signin" href="${reverse('login')}">Sign In</a>
</li>
</ol>
</nav>
% endif
</div>
</header>
</div>
\ No newline at end of file
<%include file="metadata-edit.html" /> <%include file="metadata-edit.html" />
<section class="problem-editor editor"> <section class="problem-editor editor">
<div class="row"> <div class="row">
%if markdown != '' or data == '<problem>\n</problem>\n': %if enable_markdown:
<div class="editor-bar"> <div class="editor-bar">
<ul class="format-buttons"> <ul class="format-buttons">
<li><a href="#" class="header-button" data-tooltip="Heading 1"><span <li><a href="#" class="header-button" data-tooltip="Heading 1"><span
class="problem-editor-icon heading1"></span></a></li> class="problem-editor-icon heading1"></span></a></li>
<li><a href="#" class="multiple-choice-button" data-tooltip="Multiple Choice"><span <li><a href="#" class="multiple-choice-button" data-tooltip="Multiple Choice"><span
class="problem-editor-icon multiple-choice"></span></a></li> class="problem-editor-icon multiple-choice"></span></a></li>
<li><a href="#" class="checks-button" data-tooltip="Check Multiple"><span <li><a href="#" class="checks-button" data-tooltip="Checkboxes"><span
class="problem-editor-icon checks"></span></a></li> class="problem-editor-icon checks"></span></a></li>
<li><a href="#" class="string-button" data-tooltip="String Response"><span <li><a href="#" class="string-button" data-tooltip="Text Input"><span
class="problem-editor-icon string"></span></a></li> class="problem-editor-icon string"></span></a></li>
<li><a href="#" class="number-button" data-tooltip="Numerical Response"><span <li><a href="#" class="number-button" data-tooltip="Numerical Input"><span
class="problem-editor-icon number"></span></a></li> class="problem-editor-icon number"></span></a></li>
<li><a href="#" class="dropdown-button" data-tooltip="Option Response"><span <li><a href="#" class="dropdown-button" data-tooltip="Dropdown"><span
class="problem-editor-icon dropdown"></span></a></li> class="problem-editor-icon dropdown"></span></a></li>
<li><a href="#" class="explanation-button" data-tooltip="Explanation"><span <li><a href="#" class="explanation-button" data-tooltip="Explanation"><span
class="problem-editor-icon explanation"></span></a></li> class="problem-editor-icon explanation"></span></a></li>
...@@ -56,7 +56,7 @@ ...@@ -56,7 +56,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<h6>Check Multiple</h6> <h6>Checkboxes</h6>
<div class="col sample check-multiple"> <div class="col sample check-multiple">
<img src="/static/img/multi-example.png" /> <img src="/static/img/multi-example.png" />
</div> </div>
...@@ -67,7 +67,7 @@ ...@@ -67,7 +67,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<h6>String Response</h6> <h6>Text Input</h6>
<div class="col sample string-response"> <div class="col sample string-response">
<img src="/static/img/string-example.png" /> <img src="/static/img/string-example.png" />
</div> </div>
...@@ -76,7 +76,7 @@ ...@@ -76,7 +76,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<h6>Numerical Response</h6> <h6>Numerical Input</h6>
<div class="col sample numerical-response"> <div class="col sample numerical-response">
<img src="/static/img/number-example.png" /> <img src="/static/img/number-example.png" />
</div> </div>
...@@ -85,7 +85,7 @@ ...@@ -85,7 +85,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<h6>Option Response</h6> <h6>Dropdown</h6>
<div class="col sample option-reponse"> <div class="col sample option-reponse">
<img src="/static/img/select-example.png" /> <img src="/static/img/select-example.png" />
</div> </div>
......
...@@ -6,7 +6,8 @@ from django.conf.urls import patterns, include, url ...@@ -6,7 +6,8 @@ from django.conf.urls import patterns, include, url
# admin.autodiscover() # admin.autodiscover()
urlpatterns = ('', urlpatterns = ('',
url(r'^$', 'contentstore.views.index', name='index'), url(r'^$', 'contentstore.views.howitworks', name='homepage'),
url(r'^listing', 'contentstore.views.index', name='index'),
url(r'^edit/(?P<location>.*?)$', 'contentstore.views.edit_unit', name='edit_unit'), url(r'^edit/(?P<location>.*?)$', 'contentstore.views.edit_unit', name='edit_unit'),
url(r'^subsection/(?P<location>.*?)$', 'contentstore.views.edit_subsection', name='edit_subsection'), url(r'^subsection/(?P<location>.*?)$', 'contentstore.views.edit_subsection', name='edit_subsection'),
url(r'^preview_component/(?P<location>.*?)$', 'contentstore.views.preview_component', name='preview_component'), url(r'^preview_component/(?P<location>.*?)$', 'contentstore.views.preview_component', name='preview_component'),
...@@ -42,9 +43,10 @@ urlpatterns = ('', ...@@ -42,9 +43,10 @@ urlpatterns = ('',
'contentstore.views.remove_user', name='remove_user'), 'contentstore.views.remove_user', name='remove_user'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/info/(?P<name>[^/]+)$', 'contentstore.views.course_info', name='course_info'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/info/(?P<name>[^/]+)$', 'contentstore.views.course_info', name='course_info'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course_info/updates/(?P<provided_id>.*)$', 'contentstore.views.course_info_updates', name='course_info'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course_info/updates/(?P<provided_id>.*)$', 'contentstore.views.course_info_updates', name='course_info'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/settings/(?P<name>[^/]+)$', 'contentstore.views.get_course_settings', name='course_settings'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/settings-details/(?P<name>[^/]+)$', 'contentstore.views.get_course_settings', name='course_settings'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/settings/(?P<name>[^/]+)/section/(?P<section>[^/]+).*$', 'contentstore.views.course_settings_updates', name='course_settings'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/settings-grading/(?P<name>[^/]+)$', 'contentstore.views.course_config_graders_page', name='course_settings'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/grades/(?P<name>[^/]+)/(?P<grader_index>.*)$', 'contentstore.views.course_grader_updates', name='course_settings'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/settings-details/(?P<name>[^/]+)/section/(?P<section>[^/]+).*$', 'contentstore.views.course_settings_updates', name='course_settings'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/settings-grading/(?P<name>[^/]+)/(?P<grader_index>.*)$', 'contentstore.views.course_grader_updates', name='course_settings'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/(?P<category>[^/]+)/(?P<name>[^/]+)/gradeas.*$', 'contentstore.views.assignment_type_update', name='assignment_type_update'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/(?P<category>[^/]+)/(?P<name>[^/]+)/gradeas.*$', 'contentstore.views.assignment_type_update', name='assignment_type_update'),
...@@ -76,13 +78,15 @@ urlpatterns = ('', ...@@ -76,13 +78,15 @@ urlpatterns = ('',
# User creation and updating views # User creation and updating views
urlpatterns += ( urlpatterns += (
url(r'^howitworks$', 'contentstore.views.howitworks', name='howitworks'),
url(r'^signup$', 'contentstore.views.signup', name='signup'), url(r'^signup$', 'contentstore.views.signup', name='signup'),
url(r'^create_account$', 'student.views.create_account'), url(r'^create_account$', 'student.views.create_account'),
url(r'^activate/(?P<key>[^/]*)$', 'student.views.activate_account', name='activate'), url(r'^activate/(?P<key>[^/]*)$', 'student.views.activate_account', name='activate'),
# form page # form page
url(r'^login$', 'contentstore.views.login_page', name='login'), url(r'^login$', 'contentstore.views.old_login_redirect', name='old_login'),
url(r'^signin$', 'contentstore.views.login_page', name='login'),
# ajax view that actually does the work # ajax view that actually does the work
url(r'^login_post$', 'student.views.login_user', name='login_post'), url(r'^login_post$', 'student.views.login_user', name='login_post'),
......
...@@ -9,6 +9,7 @@ from django.template.loaders.app_directories import Loader as AppDirectoriesLoad ...@@ -9,6 +9,7 @@ from django.template.loaders.app_directories import Loader as AppDirectoriesLoad
from mitxmako.template import Template from mitxmako.template import Template
import mitxmako.middleware import mitxmako.middleware
import tempdir
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -30,7 +31,7 @@ class MakoLoader(object): ...@@ -30,7 +31,7 @@ class MakoLoader(object):
if module_directory is None: if module_directory is None:
log.warning("For more caching of mako templates, set the MAKO_MODULE_DIR in settings!") log.warning("For more caching of mako templates, set the MAKO_MODULE_DIR in settings!")
module_directory = tempfile.mkdtemp() module_directory = tempdir.mkdtemp_clean()
self.module_directory = module_directory self.module_directory = module_directory
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
# limitations under the License. # limitations under the License.
from mako.lookup import TemplateLookup from mako.lookup import TemplateLookup
import tempfile import tempdir
from django.template import RequestContext from django.template import RequestContext
from django.conf import settings from django.conf import settings
...@@ -29,7 +29,7 @@ class MakoMiddleware(object): ...@@ -29,7 +29,7 @@ class MakoMiddleware(object):
module_directory = getattr(settings, 'MAKO_MODULE_DIR', None) module_directory = getattr(settings, 'MAKO_MODULE_DIR', None)
if module_directory is None: if module_directory is None:
module_directory = tempfile.mkdtemp() module_directory = tempdir.mkdtemp_clean()
for location in template_locations: for location in template_locations:
lookup[location] = TemplateLookup(directories=template_locations[location], lookup[location] = TemplateLookup(directories=template_locations[location],
......
...@@ -7,6 +7,7 @@ import logging ...@@ -7,6 +7,7 @@ import logging
import os import os
from tempfile import mkdtemp from tempfile import mkdtemp
import cStringIO import cStringIO
import shutil
import sys import sys
from django.test import TestCase from django.test import TestCase
...@@ -143,23 +144,18 @@ class PearsonTestCase(TestCase): ...@@ -143,23 +144,18 @@ class PearsonTestCase(TestCase):
''' '''
Base class for tests running Pearson-related commands Base class for tests running Pearson-related commands
''' '''
import_dir = mkdtemp(prefix="import")
export_dir = mkdtemp(prefix="export")
def assertErrorContains(self, error_message, expected): def assertErrorContains(self, error_message, expected):
self.assertTrue(error_message.find(expected) >= 0, 'error message "{}" did not contain "{}"'.format(error_message, expected)) self.assertTrue(error_message.find(expected) >= 0, 'error message "{}" did not contain "{}"'.format(error_message, expected))
def tearDown(self): def setUp(self):
def delete_temp_dir(dirname): self.import_dir = mkdtemp(prefix="import")
if os.path.exists(dirname): self.addCleanup(shutil.rmtree, self.import_dir)
for filename in os.listdir(dirname): self.export_dir = mkdtemp(prefix="export")
os.remove(os.path.join(dirname, filename)) self.addCleanup(shutil.rmtree, self.export_dir)
os.rmdir(dirname)
# clean up after any test data was dumped to temp directory
delete_temp_dir(self.import_dir)
delete_temp_dir(self.export_dir)
def tearDown(self):
pass
# and clean up the database: # and clean up the database:
# TestCenterUser.objects.all().delete() # TestCenterUser.objects.all().delete()
# TestCenterRegistration.objects.all().delete() # TestCenterRegistration.objects.all().delete()
......
...@@ -11,8 +11,9 @@ from django.core.management import call_command ...@@ -11,8 +11,9 @@ from django.core.management import call_command
@before.harvest @before.harvest
def initial_setup(server): def initial_setup(server):
# Launch firefox # Launch the browser app (choose one of these below)
world.browser = Browser('chrome') world.browser = Browser('chrome')
# world.browser = Browser('firefox')
@before.each_scenario @before.each_scenario
......
"""Make temporary directories nicely."""
import atexit
import os.path
import shutil
import tempfile
def mkdtemp_clean(suffix="", prefix="tmp", dir=None):
"""Just like mkdtemp, but the directory will be deleted when the process ends."""
the_dir = tempfile.mkdtemp(suffix=suffix, prefix=prefix, dir=dir)
atexit.register(cleanup_tempdir, the_dir)
return the_dir
def cleanup_tempdir(the_dir):
"""Called on process exit to remove a temp directory."""
if os.path.exists(the_dir):
shutil.rmtree(the_dir)
...@@ -703,15 +703,15 @@ class CapaDescriptor(RawDescriptor): ...@@ -703,15 +703,15 @@ class CapaDescriptor(RawDescriptor):
def get_context(self): def get_context(self):
_context = RawDescriptor.get_context(self) _context = RawDescriptor.get_context(self)
_context.update({'markdown': self.metadata.get('markdown', '')}) _context.update({'markdown': self.metadata.get('markdown', ''),
'enable_markdown' : 'markdown' in self.metadata})
return _context return _context
@property @property
def editable_metadata_fields(self): def editable_metadata_fields(self):
"""Remove metadata from the editable fields since it has its own editor""" """Remove any metadata from the editable fields which have their own editor or shouldn't be edited by user."""
subset = super(CapaDescriptor, self).editable_metadata_fields subset = [field for field in super(CapaDescriptor,self).editable_metadata_fields
if 'markdown' in subset: if field not in ['markdown', 'empty']]
subset.remove('markdown')
return subset return subset
......
import copy
from fs.errors import ResourceNotFoundError
import itertools
import json import json
import logging import logging
from lxml import etree from lxml import etree
from lxml.html import rewrite_links
from path import path
import os
import sys
from pkg_resources import resource_string from pkg_resources import resource_string
from .capa_module import only_one, ComplexEncoder
from .editing_module import EditingDescriptor from .editing_module import EditingDescriptor
from .html_checker import check_html
from progress import Progress
from .stringify import stringify_children
from .x_module import XModule from .x_module import XModule
from .xml_module import XmlDescriptor from .xml_module import XmlDescriptor
from xmodule.modulestore import Location from xmodule.open_ended_grading_classes.combined_open_ended_modulev1 import CombinedOpenEndedV1Module, CombinedOpenEndedV1Descriptor
from combined_open_ended_modulev1 import CombinedOpenEndedV1Module, CombinedOpenEndedV1Descriptor
log = logging.getLogger("mitx.courseware") log = logging.getLogger("mitx.courseware")
...@@ -120,11 +108,13 @@ class CombinedOpenEndedModule(XModule): ...@@ -120,11 +108,13 @@ class CombinedOpenEndedModule(XModule):
instance_state = {} instance_state = {}
self.version = self.metadata.get('version', DEFAULT_VERSION) self.version = self.metadata.get('version', DEFAULT_VERSION)
version_error_string = "Version of combined open ended module {0} is not correct. Going with version {1}"
if not isinstance(self.version, basestring): if not isinstance(self.version, basestring):
try: try:
self.version = str(self.version) self.version = str(self.version)
except: except:
log.error("Version {0} is not correct. Going with version {1}".format(self.version, DEFAULT_VERSION)) #This is a dev_facing_error
log.info(version_error_string.format(self.version, DEFAULT_VERSION))
self.version = DEFAULT_VERSION self.version = DEFAULT_VERSION
versions = [i[0] for i in VERSION_TUPLES] versions = [i[0] for i in VERSION_TUPLES]
...@@ -134,7 +124,8 @@ class CombinedOpenEndedModule(XModule): ...@@ -134,7 +124,8 @@ class CombinedOpenEndedModule(XModule):
try: try:
version_index = versions.index(self.version) version_index = versions.index(self.version)
except: except:
log.error("Version {0} is not correct. Going with version {1}".format(self.version, DEFAULT_VERSION)) #This is a dev_facing_error
log.error(version_error_string.format(self.version, DEFAULT_VERSION))
self.version = DEFAULT_VERSION self.version = DEFAULT_VERSION
version_index = versions.index(self.version) version_index = versions.index(self.version)
......
import logging
from lxml import etree
log = logging.getLogger(__name__)
class RubricParsingError(Exception):
def __init__(self, msg):
self.msg = msg
class CombinedOpenEndedRubric(object):
def __init__ (self, system, view_only = False):
self.has_score = False
self.view_only = view_only
self.system = system
def render_rubric(self, rubric_xml):
'''
render_rubric: takes in an xml string and outputs the corresponding
html for that xml, given the type of rubric we're generating
Input:
rubric_xml: an string that has not been parsed into xml that
represents this particular rubric
Output:
html: the html that corresponds to the xml given
'''
success = False
try:
rubric_categories = self.extract_categories(rubric_xml)
max_scores = map((lambda cat: cat['options'][-1]['points']), rubric_categories)
max_score = max(max_scores)
html = self.system.render_template('open_ended_rubric.html',
{'categories': rubric_categories,
'has_score': self.has_score,
'view_only': self.view_only,
'max_score': max_score})
success = True
except:
error_message = "[render_rubric] Could not parse the rubric with xml: {0}".format(rubric_xml)
log.error(error_message)
raise RubricParsingError(error_message)
return success, html
def check_if_rubric_is_parseable(self, rubric_string, location, max_score_allowed, max_score):
success, rubric_feedback = self.render_rubric(rubric_string)
if not success:
error_message = "Could not parse rubric : {0} for location {1}".format(rubric_string, location.url())
log.error(error_message)
raise RubricParsingError(error_message)
rubric_categories = self.extract_categories(rubric_string)
total = 0
for category in rubric_categories:
total = total + len(category['options']) - 1
if len(category['options']) > (max_score_allowed + 1):
error_message = "Number of score points in rubric {0} higher than the max allowed, which is {1}".format(
len(category['options']), max_score_allowed)
log.error(error_message)
raise RubricParsingError(error_message)
if total != max_score:
error_msg = "The max score {0} for problem {1} does not match the total number of points in the rubric {2}".format(
max_score, location, total)
log.error(error_msg)
raise RubricParsingError(error_msg)
def extract_categories(self, element):
'''
Contstruct a list of categories such that the structure looks like:
[ { category: "Category 1 Name",
options: [{text: "Option 1 Name", points: 0}, {text:"Option 2 Name", points: 5}]
},
{ category: "Category 2 Name",
options: [{text: "Option 1 Name", points: 0},
{text: "Option 2 Name", points: 1},
{text: "Option 3 Name", points: 2]}]
'''
if isinstance(element, basestring):
element = etree.fromstring(element)
categories = []
for category in element:
if category.tag != 'category':
raise RubricParsingError("[extract_categories] Expected a <category> tag: got {0} instead".format(category.tag))
else:
categories.append(self.extract_category(category))
return categories
def extract_category(self, category):
'''
construct an individual category
{category: "Category 1 Name",
options: [{text: "Option 1 text", points: 1},
{text: "Option 2 text", points: 2}]}
all sorting and auto-point generation occurs in this function
'''
descriptionxml = category[0]
optionsxml = category[1:]
scorexml = category[1]
score = None
if scorexml.tag == 'score':
score_text = scorexml.text
optionsxml = category[2:]
score = int(score_text)
self.has_score = True
# if we are missing the score tag and we are expecting one
elif self.has_score:
raise RubricParsingError("[extract_category] Category {0} is missing a score".format(descriptionxml.text))
# parse description
if descriptionxml.tag != 'description':
raise RubricParsingError("[extract_category]: expected description tag, got {0} instead".format(descriptionxml.tag))
description = descriptionxml.text
cur_points = 0
options = []
autonumbering = True
# parse options
for option in optionsxml:
if option.tag != 'option':
raise RubricParsingError("[extract_category]: expected option tag, got {0} instead".format(option.tag))
else:
pointstr = option.get("points")
if pointstr:
autonumbering = False
# try to parse this into an int
try:
points = int(pointstr)
except ValueError:
raise RubricParsingError("[extract_category]: expected points to have int, got {0} instead".format(pointstr))
elif autonumbering:
# use the generated one if we're in the right mode
points = cur_points
cur_points = cur_points + 1
else:
raise Exception("[extract_category]: missing points attribute. Cannot continue to auto-create points values after a points value is explicitly defined.")
selected = score == points
optiontext = option.text
options.append({'text': option.text, 'points': points, 'selected': selected})
# sort and check for duplicates
options = sorted(options, key=lambda option: option['points'])
CombinedOpenEndedRubric.validate_options(options)
return {'description': description, 'options': options}
@staticmethod
def validate_options(options):
'''
Validates a set of options. This can and should be extended to filter out other bad edge cases
'''
if len(options) == 0:
raise RubricParsingError("[extract_category]: no options associated with this category")
if len(options) == 1:
return
prev = options[0]['points']
for option in options[1:]:
if prev == option['points']:
raise RubricParsingError("[extract_category]: found duplicate point values between two different options")
else:
prev = option['points']
...@@ -24,14 +24,11 @@ section.combined-open-ended { ...@@ -24,14 +24,11 @@ section.combined-open-ended {
@include clearfix; @include clearfix;
.status-container .status-container
{ {
float:right; padding-bottom: 5px;
width:40%;
} }
.item-container .item-container
{ {
float:left; padding-bottom: 10px;
width: 53%;
padding-bottom: 50px;
} }
.result-container .result-container
...@@ -46,14 +43,26 @@ section.combined-open-ended { ...@@ -46,14 +43,26 @@ section.combined-open-ended {
} }
} }
section.legend-container {
.legenditem {
background-color : #d4d4d4;
font-size: .9em;
padding: 2px;
display: inline;
width: 20%;
}
margin-bottom: 5px;
}
section.combined-open-ended-status { section.combined-open-ended-status {
.statusitem { .statusitem {
background-color: #FAFAFA;
color: #2C2C2C; color: #2C2C2C;
font-family: monospace; background-color : #d4d4d4;
font-size: 1em; font-size: .9em;
padding: 10px; padding: 2px;
display: inline;
width: 20%;
.show-results { .show-results {
margin-top: .3em; margin-top: .3em;
text-align:right; text-align:right;
...@@ -64,7 +73,7 @@ section.combined-open-ended-status { ...@@ -64,7 +73,7 @@ section.combined-open-ended-status {
} }
.statusitem-current { .statusitem-current {
background-color: #d4d4d4; background-color: #B2B2B2;
color: #222; color: #222;
} }
...@@ -98,8 +107,29 @@ section.combined-open-ended-status { ...@@ -98,8 +107,29 @@ section.combined-open-ended-status {
} }
} }
div.result-container { div.combined-rubric-container {
ul.rubric-list{
list-style-type: none;
padding:0;
margin:0;
li {
&.rubric-list-item{
margin-bottom: 2px;
padding: 0px;
}
}
}
span.rubric-category {
font-size: .9em;
}
padding-bottom: 5px;
padding-top: 10px;
}
div.result-container {
padding-top: 10px;
padding-bottom: 5px;
.evaluation { .evaluation {
p { p {
...@@ -113,9 +143,8 @@ div.result-container { ...@@ -113,9 +143,8 @@ div.result-container {
} }
.evaluation-response { .evaluation-response {
margin-bottom: 10px; margin-bottom: 2px;
header { header {
text-align: right;
a { a {
font-size: .85em; font-size: .85em;
} }
...@@ -198,20 +227,6 @@ div.result-container { ...@@ -198,20 +227,6 @@ div.result-container {
} }
} }
.result-correct {
background: url('../images/correct-icon.png') left 20px no-repeat;
.result-actual-output {
color: #090;
}
}
.result-incorrect {
background: url('../images/incorrect-icon.png') left 20px no-repeat;
.result-actual-output {
color: #B00;
}
}
.markup-text{ .markup-text{
margin: 5px; margin: 5px;
padding: 20px 0px 15px 50px; padding: 20px 0px 15px 50px;
...@@ -229,6 +244,16 @@ div.result-container { ...@@ -229,6 +244,16 @@ div.result-container {
} }
} }
} }
.rubric-result-container {
.rubric-result {
font-size: .9em;
padding: 2px;
display: inline-table;
}
padding: 2px;
margin: 0px;
display : inline;
}
} }
...@@ -404,7 +429,7 @@ section.open-ended-child { ...@@ -404,7 +429,7 @@ section.open-ended-child {
div.short-form-response { div.short-form-response {
background: #F6F6F6; background: #F6F6F6;
border: 1px solid #ddd; border: 1px solid #ddd;
margin-bottom: 20px; margin-bottom: 0px;
overflow-y: auto; overflow-y: auto;
height: 200px; height: 200px;
@include clearfix; @include clearfix;
...@@ -478,6 +503,18 @@ section.open-ended-child { ...@@ -478,6 +503,18 @@ section.open-ended-child {
margin-left: .75rem; margin-left: .75rem;
} }
ul.rubric-list{
list-style-type: none;
padding:0;
margin:0;
li {
&.rubric-list-item{
margin-bottom: 0px;
padding: 0px;
}
}
}
ol { ol {
list-style: decimal outside none; list-style: decimal outside none;
margin-bottom: lh(); margin-bottom: lh();
...@@ -503,9 +540,8 @@ section.open-ended-child { ...@@ -503,9 +540,8 @@ section.open-ended-child {
} }
li { li {
line-height: 1.4em; margin-bottom: 0px;
margin-bottom: lh(.5); padding: 0px;
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
......
...@@ -49,10 +49,18 @@ p { ...@@ -49,10 +49,18 @@ p {
em, i { em, i {
font-style: italic; font-style: italic;
span {
font-style: italic;
}
} }
strong, b { strong, b {
font-weight: bold; font-weight: bold;
span {
font-weight: bold;
}
} }
p + p, ul + p, ol + p { p + p, ul + p, ol + p {
......
...@@ -172,6 +172,13 @@ class HtmlDescriptor(XmlDescriptor, EditingDescriptor): ...@@ -172,6 +172,13 @@ class HtmlDescriptor(XmlDescriptor, EditingDescriptor):
elt.set("filename", relname) elt.set("filename", relname)
return elt return elt
@property
def editable_metadata_fields(self):
"""Remove any metadata from the editable fields which have their own editor or shouldn't be edited by user."""
subset = [field for field in super(HtmlDescriptor,self).editable_metadata_fields
if field not in ['empty']]
return subset
class AboutDescriptor(HtmlDescriptor): class AboutDescriptor(HtmlDescriptor):
""" """
......
...@@ -4,11 +4,11 @@ class @Rubric ...@@ -4,11 +4,11 @@ class @Rubric
# finds the scores for each rubric category # finds the scores for each rubric category
@get_score_list: () => @get_score_list: () =>
# find the number of categories: # find the number of categories:
num_categories = $('table.rubric tr').length num_categories = $('.rubric-category').length
score_lst = [] score_lst = []
# get the score for each one # get the score for each one
for i in [0..(num_categories-2)] for i in [0..(num_categories-1)]
score = $("input[name='score-selection-#{i}']:checked").val() score = $("input[name='score-selection-#{i}']:checked").val()
score_lst.push(score) score_lst.push(score)
...@@ -23,9 +23,8 @@ class @Rubric ...@@ -23,9 +23,8 @@ class @Rubric
@check_complete: () -> @check_complete: () ->
# check to see whether or not any categories have not been scored # check to see whether or not any categories have not been scored
num_categories = $('table.rubric tr').length num_categories = $('.rubric-category').length
# -2 because we want to skip the header for i in [0..(num_categories-1)]
for i in [0..(num_categories-2)]
score = $("input[name='score-selection-#{i}']:checked").val() score = $("input[name='score-selection-#{i}']:checked").val()
if score == undefined if score == undefined
return false return false
...@@ -52,22 +51,30 @@ class @CombinedOpenEnded ...@@ -52,22 +51,30 @@ class @CombinedOpenEnded
@reset_button.click @reset @reset_button.click @reset
@next_problem_button = @$('.next-step-button') @next_problem_button = @$('.next-step-button')
@next_problem_button.click @next_problem @next_problem_button.click @next_problem
@status_container = @$('.status-elements')
@show_results_button=@$('.show-results-button') @show_results_button=@$('.show-results-button')
@show_results_button.click @show_results @show_results_button.click @show_results
@question_header = @$('.question-header')
@question_header.click @collapse_question
# valid states: 'initial', 'assessing', 'post_assessment', 'done' # valid states: 'initial', 'assessing', 'post_assessment', 'done'
Collapsible.setCollapsibles(@el) Collapsible.setCollapsibles(@el)
@submit_evaluation_button = $('.submit-evaluation-button') @submit_evaluation_button = $('.submit-evaluation-button')
@submit_evaluation_button.click @message_post @submit_evaluation_button.click @message_post
@results_container = $('.result-container') @results_container = $('.result-container')
@combined_rubric_container = $('.combined-rubric-container')
@legend_container= $('.legend-container')
@show_legend_current()
# Where to put the rubric once we load it # Where to put the rubric once we load it
@el = $(element).find('section.open-ended-child') @el = $(element).find('section.open-ended-child')
@errors_area = @$('.error') @errors_area = @$('.error')
@answer_area = @$('textarea.answer') @answer_area = @$('textarea.answer')
@prompt_container = @$('.prompt')
@rubric_wrapper = @$('.rubric-wrapper') @rubric_wrapper = @$('.rubric-wrapper')
@hint_wrapper = @$('.hint-wrapper') @hint_wrapper = @$('.hint-wrapper')
@message_wrapper = @$('.message-wrapper') @message_wrapper = @$('.message-wrapper')
...@@ -82,11 +89,22 @@ class @CombinedOpenEnded ...@@ -82,11 +89,22 @@ class @CombinedOpenEnded
@can_upload_files = false @can_upload_files = false
@open_ended_child= @$('.open-ended-child') @open_ended_child= @$('.open-ended-child')
@out_of_sync_message = 'The problem state got out of sync. Try reloading the page.'
if @task_number>1
@prompt_hide()
else if @task_number==1 and @child_state!='initial'
@prompt_hide()
@find_assessment_elements() @find_assessment_elements()
@find_hint_elements() @find_hint_elements()
@rebind() @rebind()
if @task_number>1
@show_combined_rubric_current()
@show_results_current()
# locally scoped jquery. # locally scoped jquery.
$: (selector) -> $: (selector) ->
$(selector, @el) $(selector, @el)
...@@ -102,7 +120,7 @@ class @CombinedOpenEnded ...@@ -102,7 +120,7 @@ class @CombinedOpenEnded
Collapsible.setCollapsibles(@results_container) Collapsible.setCollapsibles(@results_container)
show_results: (event) => show_results: (event) =>
status_item = $(event.target).parent().parent() status_item = $(event.target).parent()
status_number = status_item.data('status-number') status_number = status_item.data('status-number')
data = {'task_number' : status_number} data = {'task_number' : status_number}
$.postWithPrefix "#{@ajax_url}/get_results", data, (response) => $.postWithPrefix "#{@ajax_url}/get_results", data, (response) =>
...@@ -115,6 +133,27 @@ class @CombinedOpenEnded ...@@ -115,6 +133,27 @@ class @CombinedOpenEnded
else else
@gentle_alert response.error @gentle_alert response.error
show_combined_rubric_current: () =>
data = {}
$.postWithPrefix "#{@ajax_url}/get_combined_rubric", data, (response) =>
if response.success
@combined_rubric_container.after(response.html).remove()
@combined_rubric_container= $('div.combined_rubric_container')
show_status_current: () =>
data = {}
$.postWithPrefix "#{@ajax_url}/get_status", data, (response) =>
if response.success
@status_container.after(response.html).remove()
@status_container= $('.status-elements')
show_legend_current: () =>
data = {}
$.postWithPrefix "#{@ajax_url}/get_legend", data, (response) =>
if response.success
@legend_container.after(response.html).remove()
@legend_container= $('.legend-container')
message_post: (event)=> message_post: (event)=>
Logger.log 'message_post', @answers Logger.log 'message_post', @answers
external_grader_message=$(event.target).parent().parent().parent() external_grader_message=$(event.target).parent().parent().parent()
...@@ -156,6 +195,11 @@ class @CombinedOpenEnded ...@@ -156,6 +195,11 @@ class @CombinedOpenEnded
@next_problem_button.hide() @next_problem_button.hide()
@hide_file_upload() @hide_file_upload()
@hint_area.attr('disabled', false) @hint_area.attr('disabled', false)
if @task_number>1 or @child_state!='initial'
@show_status_current()
if @task_number==1 and @child_state=='assessing'
@prompt_hide()
if @child_state == 'done' if @child_state == 'done'
@rubric_wrapper.hide() @rubric_wrapper.hide()
if @child_type=="openended" if @child_type=="openended"
...@@ -251,13 +295,14 @@ class @CombinedOpenEnded ...@@ -251,13 +295,14 @@ class @CombinedOpenEnded
$.ajaxWithPrefix("#{@ajax_url}/save_answer",settings) $.ajaxWithPrefix("#{@ajax_url}/save_answer",settings)
else else
@errors_area.html('Problem state got out of sync. Try reloading the page.') @errors_area.html(@out_of_sync_message)
save_assessment: (event) => save_assessment: (event) =>
event.preventDefault() event.preventDefault()
if @child_state == 'assessing' && Rubric.check_complete() if @child_state == 'assessing' && Rubric.check_complete()
checked_assessment = Rubric.get_total_score() checked_assessment = Rubric.get_total_score()
data = {'assessment' : checked_assessment} score_list = Rubric.get_score_list()
data = {'assessment' : checked_assessment, 'score_list' : score_list}
$.postWithPrefix "#{@ajax_url}/save_assessment", data, (response) => $.postWithPrefix "#{@ajax_url}/save_assessment", data, (response) =>
if response.success if response.success
@child_state = response.state @child_state = response.state
...@@ -267,13 +312,12 @@ class @CombinedOpenEnded ...@@ -267,13 +312,12 @@ class @CombinedOpenEnded
@find_hint_elements() @find_hint_elements()
else if @child_state == 'done' else if @child_state == 'done'
@rubric_wrapper.hide() @rubric_wrapper.hide()
@message_wrapper.html(response.message_html)
@rebind() @rebind()
else else
@errors_area.html(response.error) @errors_area.html(response.error)
else else
@errors_area.html('Problem state got out of sync. Try reloading the page.') @errors_area.html(@out_of_sync_message)
save_hint: (event) => save_hint: (event) =>
event.preventDefault() event.preventDefault()
...@@ -288,7 +332,7 @@ class @CombinedOpenEnded ...@@ -288,7 +332,7 @@ class @CombinedOpenEnded
else else
@errors_area.html(response.error) @errors_area.html(response.error)
else else
@errors_area.html('Problem state got out of sync. Try reloading the page.') @errors_area.html(@out_of_sync_message)
skip_post_assessment: => skip_post_assessment: =>
if @child_state == 'post_assessment' if @child_state == 'post_assessment'
...@@ -300,7 +344,7 @@ class @CombinedOpenEnded ...@@ -300,7 +344,7 @@ class @CombinedOpenEnded
else else
@errors_area.html(response.error) @errors_area.html(response.error)
else else
@errors_area.html('Problem state got out of sync. Try reloading the page.') @errors_area.html(@out_of_sync_message)
reset: (event) => reset: (event) =>
event.preventDefault() event.preventDefault()
...@@ -320,7 +364,7 @@ class @CombinedOpenEnded ...@@ -320,7 +364,7 @@ class @CombinedOpenEnded
else else
@errors_area.html(response.error) @errors_area.html(response.error)
else else
@errors_area.html('Problem state got out of sync. Try reloading the page.') @errors_area.html(@out_of_sync_message)
next_problem: => next_problem: =>
if @child_state == 'done' if @child_state == 'done'
...@@ -343,7 +387,7 @@ class @CombinedOpenEnded ...@@ -343,7 +387,7 @@ class @CombinedOpenEnded
else else
@errors_area.html(response.error) @errors_area.html(response.error)
else else
@errors_area.html('Problem state got out of sync. Try reloading the page.') @errors_area.html(@out_of_sync_message)
gentle_alert: (msg) => gentle_alert: (msg) =>
if @el.find('.open-ended-alert').length if @el.find('.open-ended-alert').length
...@@ -367,8 +411,8 @@ class @CombinedOpenEnded ...@@ -367,8 +411,8 @@ class @CombinedOpenEnded
window.queuePollerID = window.setTimeout(@poll, 10000) window.queuePollerID = window.setTimeout(@poll, 10000)
setup_file_upload: => setup_file_upload: =>
if window.File and window.FileReader and window.FileList and window.Blob
if @accept_file_upload == "True" if @accept_file_upload == "True"
if window.File and window.FileReader and window.FileList and window.Blob
@can_upload_files = true @can_upload_files = true
@file_upload_area.html('<input type="file" class="file-upload-box">') @file_upload_area.html('<input type="file" class="file-upload-box">')
@file_upload_area.show() @file_upload_area.show()
...@@ -390,3 +434,26 @@ class @CombinedOpenEnded ...@@ -390,3 +434,26 @@ class @CombinedOpenEnded
# wrap this so that it can be mocked # wrap this so that it can be mocked
reload: -> reload: ->
location.reload() location.reload()
collapse_question: () =>
@prompt_container.slideToggle()
@prompt_container.toggleClass('open')
if @question_header.text() == "(Hide)"
new_text = "(Show)"
else
new_text = "(Hide)"
@question_header.text(new_text)
prompt_show: () =>
if @prompt_container.is(":hidden")==true
@prompt_container.slideToggle()
@prompt_container.toggleClass('open')
@question_header.text("(Hide)")
prompt_hide: () =>
if @prompt_container.is(":visible")==true
@prompt_container.slideToggle()
@prompt_container.toggleClass('open')
@question_header.text("(Show)")
...@@ -175,17 +175,23 @@ class @PeerGradingProblem ...@@ -175,17 +175,23 @@ class @PeerGradingProblem
@prompt_container = $('.prompt-container') @prompt_container = $('.prompt-container')
@rubric_container = $('.rubric-container') @rubric_container = $('.rubric-container')
@flag_student_container = $('.flag-student-container') @flag_student_container = $('.flag-student-container')
@answer_unknown_container = $('.answer-unknown-container')
@calibration_panel = $('.calibration-panel') @calibration_panel = $('.calibration-panel')
@grading_panel = $('.grading-panel') @grading_panel = $('.grading-panel')
@content_panel = $('.content-panel') @content_panel = $('.content-panel')
@grading_message = $('.grading-message') @grading_message = $('.grading-message')
@grading_message.hide() @grading_message.hide()
@question_header = $('.question-header')
@question_header.click @collapse_question
@grading_wrapper =$('.grading-wrapper') @grading_wrapper =$('.grading-wrapper')
@calibration_feedback_panel = $('.calibration-feedback') @calibration_feedback_panel = $('.calibration-feedback')
@interstitial_page = $('.interstitial-page') @interstitial_page = $('.interstitial-page')
@interstitial_page.hide() @interstitial_page.hide()
@calibration_interstitial_page = $('.calibration-interstitial-page')
@calibration_interstitial_page.hide()
@error_container = $('.error-container') @error_container = $('.error-container')
@submission_key_input = $("input[name='submission-key']") @submission_key_input = $("input[name='submission-key']")
...@@ -201,7 +207,10 @@ class @PeerGradingProblem ...@@ -201,7 +207,10 @@ class @PeerGradingProblem
@action_button = $('.action-button') @action_button = $('.action-button')
@calibration_feedback_button = $('.calibration-feedback-button') @calibration_feedback_button = $('.calibration-feedback-button')
@interstitial_page_button = $('.interstitial-page-button') @interstitial_page_button = $('.interstitial-page-button')
@calibration_interstitial_page_button = $('.calibration-interstitial-page-button')
@flag_student_checkbox = $('.flag-checkbox') @flag_student_checkbox = $('.flag-checkbox')
@answer_unknown_checkbox = $('.answer-unknown-checkbox')
@collapse_question()
Collapsible.setCollapsibles(@content_panel) Collapsible.setCollapsibles(@content_panel)
...@@ -210,12 +219,21 @@ class @PeerGradingProblem ...@@ -210,12 +219,21 @@ class @PeerGradingProblem
@calibration_feedback_button.click => @calibration_feedback_button.click =>
@calibration_feedback_panel.hide() @calibration_feedback_panel.hide()
@grading_wrapper.show() @grading_wrapper.show()
@gentle_alert "Calibration essay saved. Fetched the next essay."
@is_calibrated_check() @is_calibrated_check()
@interstitial_page_button.click => @interstitial_page_button.click =>
@interstitial_page.hide() @interstitial_page.hide()
@is_calibrated_check() @is_calibrated_check()
@calibration_interstitial_page_button.click =>
@calibration_interstitial_page.hide()
@is_calibrated_check()
@calibration_feedback_button.hide()
@calibration_feedback_panel.hide()
@error_container.hide()
@is_calibrated_check() @is_calibrated_check()
...@@ -233,6 +251,9 @@ class @PeerGradingProblem ...@@ -233,6 +251,9 @@ class @PeerGradingProblem
fetch_submission_essay: () => fetch_submission_essay: () =>
@backend.post('get_next_submission', {location: @location}, @render_submission) @backend.post('get_next_submission', {location: @location}, @render_submission)
gentle_alert: (msg) =>
@grading_message.fadeIn()
@grading_message.html("<p>" + msg + "</p>")
construct_data: () -> construct_data: () ->
data = data =
...@@ -243,6 +264,7 @@ class @PeerGradingProblem ...@@ -243,6 +264,7 @@ class @PeerGradingProblem
submission_key: @submission_key_input.val() submission_key: @submission_key_input.val()
feedback: @feedback_area.val() feedback: @feedback_area.val()
submission_flagged: @flag_student_checkbox.is(':checked') submission_flagged: @flag_student_checkbox.is(':checked')
answer_unknown: @answer_unknown_checkbox.is(':checked')
return data return data
...@@ -273,6 +295,9 @@ class @PeerGradingProblem ...@@ -273,6 +295,9 @@ class @PeerGradingProblem
else if response.calibrated and @calibration == true else if response.calibrated and @calibration == true
@calibration = false @calibration = false
@render_interstitial_page() @render_interstitial_page()
else if not response.calibrated and @calibration==null
@calibration=true
@render_calibration_interstitial_page()
else else
@calibration = true @calibration = true
@fetch_calibration_essay() @fetch_calibration_essay()
...@@ -296,7 +321,7 @@ class @PeerGradingProblem ...@@ -296,7 +321,7 @@ class @PeerGradingProblem
if response.success if response.success
@is_calibrated_check() @is_calibrated_check()
@grading_message.fadeIn() @grading_message.fadeIn()
@grading_message.html("<p>Grade sent successfully.</p>") @grading_message.html("<p>Successfully saved your feedback. Fetched the next essay.</p>")
else else
if response.error if response.error
@render_error(response.error) @render_error(response.error)
...@@ -308,6 +333,7 @@ class @PeerGradingProblem ...@@ -308,6 +333,7 @@ class @PeerGradingProblem
# check to see whether or not any categories have not been scored # check to see whether or not any categories have not been scored
if Rubric.check_complete() if Rubric.check_complete()
# show button if we have scores for all categories # show button if we have scores for all categories
@grading_message.hide()
@show_submit_button() @show_submit_button()
@grade = Rubric.get_total_score() @grade = Rubric.get_total_score()
...@@ -323,7 +349,7 @@ class @PeerGradingProblem ...@@ -323,7 +349,7 @@ class @PeerGradingProblem
if response.success if response.success
# load in all the data # load in all the data
@submission_container.html("<h3>Training Essay</h3>") @submission_container.html("")
@render_submission_data(response) @render_submission_data(response)
# TODO: indicate that we're in calibration mode # TODO: indicate that we're in calibration mode
@calibration_panel.addClass('current-state') @calibration_panel.addClass('current-state')
...@@ -337,6 +363,9 @@ class @PeerGradingProblem ...@@ -337,6 +363,9 @@ class @PeerGradingProblem
@calibration_panel.find('.grading-text').hide() @calibration_panel.find('.grading-text').hide()
@grading_panel.find('.grading-text').hide() @grading_panel.find('.grading-text').hide()
@flag_student_container.hide() @flag_student_container.hide()
@answer_unknown_container.hide()
@feedback_area.val("")
@submit_button.unbind('click') @submit_button.unbind('click')
@submit_button.click @submit_calibration_essay @submit_button.click @submit_calibration_essay
...@@ -350,7 +379,7 @@ class @PeerGradingProblem ...@@ -350,7 +379,7 @@ class @PeerGradingProblem
render_submission: (response) => render_submission: (response) =>
if response.success if response.success
@submit_button.hide() @submit_button.hide()
@submission_container.html("<h3>Submitted Essay</h3>") @submission_container.html("")
@render_submission_data(response) @render_submission_data(response)
@calibration_panel.removeClass('current-state') @calibration_panel.removeClass('current-state')
...@@ -364,6 +393,8 @@ class @PeerGradingProblem ...@@ -364,6 +393,8 @@ class @PeerGradingProblem
@calibration_panel.find('.grading-text').show() @calibration_panel.find('.grading-text').show()
@grading_panel.find('.grading-text').show() @grading_panel.find('.grading-text').show()
@flag_student_container.show() @flag_student_container.show()
@answer_unknown_container.show()
@feedback_area.val("")
@submit_button.unbind('click') @submit_button.unbind('click')
@submit_button.click @submit_grade @submit_button.click @submit_grade
...@@ -408,18 +439,25 @@ class @PeerGradingProblem ...@@ -408,18 +439,25 @@ class @PeerGradingProblem
actual_score = parseInt(response.actual_score) actual_score = parseInt(response.actual_score)
if score == actual_score if score == actual_score
calibration_wrapper.append("<p>Congratulations! Your score matches the actual score!</p>") calibration_wrapper.append("<p>Your score matches the actual score!</p>")
else else
calibration_wrapper.append("<p>Please try to understand the grading critera better to be more accurate next time.</p>") calibration_wrapper.append("<p>You may want to review the rubric again.</p>")
# disable score selection and submission from the grading interface # disable score selection and submission from the grading interface
$("input[name='score-selection']").attr('disabled', true) $("input[name='score-selection']").attr('disabled', true)
@submit_button.hide() @submit_button.hide()
@calibration_feedback_button.show()
render_interstitial_page: () => render_interstitial_page: () =>
@content_panel.hide() @content_panel.hide()
@grading_message.hide()
@interstitial_page.show() @interstitial_page.show()
render_calibration_interstitial_page: () =>
@content_panel.hide()
@action_button.hide()
@calibration_interstitial_page.show()
render_error: (error_message) => render_error: (error_message) =>
@error_container.show() @error_container.show()
@calibration_feedback_panel.hide() @calibration_feedback_panel.hide()
...@@ -433,3 +471,12 @@ class @PeerGradingProblem ...@@ -433,3 +471,12 @@ class @PeerGradingProblem
setup_score_selection: (max_score) => setup_score_selection: (max_score) =>
# And now hook up an event handler again # And now hook up an event handler again
$("input[class='score-selection']").change @graded_callback $("input[class='score-selection']").change @graded_callback
collapse_question: () =>
@prompt_container.slideToggle()
@prompt_container.toggleClass('open')
if @question_header.text() == "(Hide)"
new_text = "(Show)"
else
new_text = "(Hide)"
@question_header.text(new_text)
...@@ -221,6 +221,15 @@ this.HTML5Video = (function () { ...@@ -221,6 +221,15 @@ this.HTML5Video = (function () {
// and end playing at the specified end time. After it was paused, or when a seek operation happeded, // and end playing at the specified end time. After it was paused, or when a seek operation happeded,
// the starting time and ending time will reset to the beginning and the end of the video respectively. // the starting time and ending time will reset to the beginning and the end of the video respectively.
this.video.addEventListener('canplay', function () { this.video.addEventListener('canplay', function () {
// Because firefox triggers 'canplay' event every time when 'currentTime' property
// changes, we must make sure that this block of code runs only once. Otherwise,
// this will be an endless loop ('currentTime' property is changed below).
//
// Chrome is immune to this behavior.
if (_this.playerState !== HTML5Video.PlayerState.UNSTARTED) {
return;
}
_this.playerState = HTML5Video.PlayerState.PAUSED; _this.playerState = HTML5Video.PlayerState.PAUSED;
if (_this.start > _this.video.duration) { if (_this.start > _this.video.duration) {
......
...@@ -44,5 +44,6 @@ class MakoModuleDescriptor(XModuleDescriptor): ...@@ -44,5 +44,6 @@ class MakoModuleDescriptor(XModuleDescriptor):
# cdodge: encapsulate a means to expose "editable" metadata fields (i.e. not internal system metadata) # cdodge: encapsulate a means to expose "editable" metadata fields (i.e. not internal system metadata)
@property @property
def editable_metadata_fields(self): def editable_metadata_fields(self):
subset = [name for name in self.metadata.keys() if name not in self.system_metadata_fields] subset = [name for name in self.metadata.keys() if name not in self.system_metadata_fields and
name not in self._inherited_metadata]
return subset return subset
import pymongo import pymongo
import sys import sys
import logging import logging
import copy
from bson.son import SON from bson.son import SON
from fs.osfs import OSFS from fs.osfs import OSFS
from itertools import repeat from itertools import repeat
from path import path from path import path
from datetime import datetime, timedelta
from importlib import import_module from importlib import import_module
from xmodule.errortracker import null_error_tracker, exc_info_to_str from xmodule.errortracker import null_error_tracker, exc_info_to_str
...@@ -27,9 +29,11 @@ class CachingDescriptorSystem(MakoDescriptorSystem): ...@@ -27,9 +29,11 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
""" """
A system that has a cache of module json that it will use to load modules A system that has a cache of module json that it will use to load modules
from, with a backup of calling to the underlying modulestore for more data from, with a backup of calling to the underlying modulestore for more data
TODO (cdodge) when the 'split module store' work has been completed we can remove all
references to metadata_inheritance_tree
""" """
def __init__(self, modulestore, module_data, default_class, resources_fs, def __init__(self, modulestore, module_data, default_class, resources_fs,
error_tracker, render_template): error_tracker, render_template, metadata_inheritance_tree = None):
""" """
modulestore: the module store that can be used to retrieve additional modules modulestore: the module store that can be used to retrieve additional modules
...@@ -54,6 +58,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem): ...@@ -54,6 +58,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
# cdodge: other Systems have a course_id attribute defined. To keep things consistent, let's # cdodge: other Systems have a course_id attribute defined. To keep things consistent, let's
# define an attribute here as well, even though it's None # define an attribute here as well, even though it's None
self.course_id = None self.course_id = None
self.metadata_inheritance_tree = metadata_inheritance_tree
def load_item(self, location): def load_item(self, location):
location = Location(location) location = Location(location)
...@@ -61,11 +66,13 @@ class CachingDescriptorSystem(MakoDescriptorSystem): ...@@ -61,11 +66,13 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
if json_data is None: if json_data is None:
return self.modulestore.get_item(location) return self.modulestore.get_item(location)
else: else:
# TODO (vshnayder): metadata inheritance is somewhat broken because mongo, doesn't # load the module and apply the inherited metadata
# always load an entire course. We're punting on this until after launch, and then
# will build a proper course policy framework.
try: try:
return XModuleDescriptor.load_from_json(json_data, self, self.default_class) module = XModuleDescriptor.load_from_json(json_data, self, self.default_class)
if self.metadata_inheritance_tree is not None:
metadata_to_inherit = self.metadata_inheritance_tree.get('parent_metadata', {}).get(location.url(),{})
module.inherit_metadata(metadata_to_inherit)
return module
except: except:
return ErrorDescriptor.from_json( return ErrorDescriptor.from_json(
json_data, json_data,
...@@ -142,6 +149,82 @@ class MongoModuleStore(ModuleStoreBase): ...@@ -142,6 +149,82 @@ class MongoModuleStore(ModuleStoreBase):
self.fs_root = path(fs_root) self.fs_root = path(fs_root)
self.error_tracker = error_tracker self.error_tracker = error_tracker
self.render_template = render_template self.render_template = render_template
self.metadata_inheritance_cache = {}
def get_metadata_inheritance_tree(self, location):
'''
TODO (cdodge) This method can be deleted when the 'split module store' work has been completed
'''
# get all collections in the course, this query should not return any leaf nodes
query = { '_id.org' : location.org,
'_id.course' : location.course,
'_id.revision' : None,
'definition.children':{'$ne': []}
}
# we just want the Location, children, and metadata
record_filter = {'_id':1,'definition.children':1,'metadata':1}
# call out to the DB
resultset = self.collection.find(query, record_filter)
results_by_url = {}
root = None
# now go through the results and order them by the location url
for result in resultset:
location = Location(result['_id'])
results_by_url[location.url()] = result
if location.category == 'course':
root = location.url()
# now traverse the tree and compute down the inherited metadata
metadata_to_inherit = {}
def _compute_inherited_metadata(url):
my_metadata = results_by_url[url]['metadata']
for key in my_metadata.keys():
if key not in XModuleDescriptor.inheritable_metadata:
del my_metadata[key]
results_by_url[url]['metadata'] = my_metadata
# go through all the children and recurse, but only if we have
# in the result set. Remember results will not contain leaf nodes
for child in results_by_url[url].get('definition',{}).get('children',[]):
if child in results_by_url:
new_child_metadata = copy.deepcopy(my_metadata)
new_child_metadata.update(results_by_url[child]['metadata'])
results_by_url[child]['metadata'] = new_child_metadata
metadata_to_inherit[child] = new_child_metadata
_compute_inherited_metadata(child)
else:
# this is likely a leaf node, so let's record what metadata we need to inherit
metadata_to_inherit[child] = my_metadata
if root is not None:
_compute_inherited_metadata(root)
cache = {'parent_metadata': metadata_to_inherit,
'timestamp' : datetime.now()}
return cache
def get_cached_metadata_inheritance_tree(self, location, max_age_allowed):
'''
TODO (cdodge) This method can be deleted when the 'split module store' work has been completed
'''
cache_name = '{0}/{1}'.format(location.org, location.course)
cache = self.metadata_inheritance_cache.get(cache_name,{'parent_metadata': {},
'timestamp': datetime.now() - timedelta(hours=1)})
age = (datetime.now() - cache['timestamp'])
if age.seconds >= max_age_allowed:
logging.debug('loading entire inheritance tree for {0}'.format(cache_name))
cache = self.get_metadata_inheritance_tree(location)
self.metadata_inheritance_cache[cache_name] = cache
return cache
def _clean_item_data(self, item): def _clean_item_data(self, item):
""" """
...@@ -196,6 +279,8 @@ class MongoModuleStore(ModuleStoreBase): ...@@ -196,6 +279,8 @@ class MongoModuleStore(ModuleStoreBase):
resource_fs = OSFS(root) resource_fs = OSFS(root)
# TODO (cdodge): When the 'split module store' work has been completed, we should remove
# the 'metadata_inheritance_tree' parameter
system = CachingDescriptorSystem( system = CachingDescriptorSystem(
self, self,
data_cache, data_cache,
...@@ -203,6 +288,7 @@ class MongoModuleStore(ModuleStoreBase): ...@@ -203,6 +288,7 @@ class MongoModuleStore(ModuleStoreBase):
resource_fs, resource_fs,
self.error_tracker, self.error_tracker,
self.render_template, self.render_template,
metadata_inheritance_tree = self.get_cached_metadata_inheritance_tree(Location(item['location']), 60)
) )
return system.load_item(item['location']) return system.load_item(item['location'])
...@@ -261,11 +347,11 @@ class MongoModuleStore(ModuleStoreBase): ...@@ -261,11 +347,11 @@ class MongoModuleStore(ModuleStoreBase):
descendents of the queried modules for more efficient results later descendents of the queried modules for more efficient results later
in the request. The depth is counted in the number of in the request. The depth is counted in the number of
calls to get_children() to cache. None indicates to cache all descendents. calls to get_children() to cache. None indicates to cache all descendents.
""" """
location = Location.ensure_fully_specified(location) location = Location.ensure_fully_specified(location)
item = self._find_one(location) item = self._find_one(location)
return self._load_items([item], depth)[0] module = self._load_items([item], depth)[0]
return module
def get_instance(self, course_id, location, depth=0): def get_instance(self, course_id, location, depth=0):
""" """
...@@ -285,7 +371,8 @@ class MongoModuleStore(ModuleStoreBase): ...@@ -285,7 +371,8 @@ class MongoModuleStore(ModuleStoreBase):
sort=[('revision', pymongo.ASCENDING)], sort=[('revision', pymongo.ASCENDING)],
) )
return self._load_items(list(items), depth) modules = self._load_items(list(items), depth)
return modules
def clone_item(self, source, location): def clone_item(self, source, location):
""" """
...@@ -313,7 +400,7 @@ class MongoModuleStore(ModuleStoreBase): ...@@ -313,7 +400,7 @@ class MongoModuleStore(ModuleStoreBase):
raise DuplicateItemError(location) raise DuplicateItemError(location)
def get_course_for_item(self, location): def get_course_for_item(self, location, depth=0):
''' '''
VS[compat] VS[compat]
cdodge: for a given Xmodule, return the course that it belongs to cdodge: for a given Xmodule, return the course that it belongs to
...@@ -327,7 +414,7 @@ class MongoModuleStore(ModuleStoreBase): ...@@ -327,7 +414,7 @@ class MongoModuleStore(ModuleStoreBase):
# know the 'name' parameter in this context, so we have # know the 'name' parameter in this context, so we have
# to assume there's only one item in this query even though we are not specifying a name # to assume there's only one item in this query even though we are not specifying a name
course_search_location = ['i4x', location.org, location.course, 'course', None] course_search_location = ['i4x', location.org, location.course, 'course', None]
courses = self.get_items(course_search_location) courses = self.get_items(course_search_location, depth=depth)
# make sure we found exactly one match on this above course search # make sure we found exactly one match on this above course search
found_cnt = len(courses) found_cnt = len(courses)
......
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