Merge branch 'master' into production
This commit is contained in:
commit
1d51b063c4
13 changed files with 137 additions and 65 deletions
app
assets/docs
styles
templates
views
server/classrooms
BIN
app/assets/docs/HourofCodeGettingStartedGuide.pdf
Normal file
BIN
app/assets/docs/HourofCodeGettingStartedGuide.pdf
Normal file
Binary file not shown.
|
@ -37,7 +37,8 @@
|
||||||
margin-bottom: 20px
|
margin-bottom: 20px
|
||||||
|
|
||||||
img.media-object
|
img.media-object
|
||||||
width: 300px
|
width: 100%
|
||||||
|
margin-bottom: 5px
|
||||||
|
|
||||||
.edit-classroom-small
|
.edit-classroom-small
|
||||||
cursor: pointer
|
cursor: pointer
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
#teachers-view
|
#teachers-view
|
||||||
|
|
||||||
|
font-size: 16px
|
||||||
|
|
||||||
.bigger-text
|
.bigger-text
|
||||||
font-size: 18px
|
font-size: 18px
|
||||||
|
|
||||||
|
.faq-question
|
||||||
|
font-size: 18px
|
||||||
|
font-weight: bold
|
||||||
|
margin: 20px 0px 10px 0px
|
||||||
|
|
||||||
.uppercase
|
.uppercase
|
||||||
text-transform: uppercase
|
text-transform: uppercase
|
||||||
|
|
||||||
|
@ -16,3 +23,6 @@
|
||||||
|
|
||||||
.text-center
|
.text-center
|
||||||
text-align: center
|
text-align: center
|
||||||
|
|
||||||
|
.title
|
||||||
|
font-size: 20px
|
||||||
|
|
|
@ -53,7 +53,7 @@ block content
|
||||||
.progress
|
.progress
|
||||||
.progress-bar(style='width:'+stats.levels.pctDone)
|
.progress-bar(style='width:'+stats.levels.pctDone)
|
||||||
else if paidFor
|
else if paidFor
|
||||||
button.enable-btn.btn.btn-info.btn-sm(data-user-id=user.id, data-course-instance-id=courseInstance.id) Enable
|
button.enable-btn.btn.btn-info.btn-sm(data-user-id=user.id, data-course-instance-cid=courseInstance.cid) Enable
|
||||||
|
|
||||||
if !paidFor
|
if !paidFor
|
||||||
.text-center
|
.text-center
|
||||||
|
|
|
@ -244,7 +244,7 @@ mixin progress-members
|
||||||
|
|
||||||
mixin progress-members-individual(memberID)
|
mixin progress-members-individual(memberID)
|
||||||
- var name = memberUserMap[memberID] ? memberUserMap[memberID].get('name') : 'Anoner'
|
- var name = memberUserMap[memberID] ? memberUserMap[memberID].get('name') : 'Anoner'
|
||||||
a(href="/user/#{memberID}")= name || 'Anoner'
|
strong= name || 'Anoner'
|
||||||
if memberStats && memberStats[memberID]
|
if memberStats && memberStats[memberID]
|
||||||
div
|
div
|
||||||
span #{memberStats[memberID].totalLevelsCompleted}
|
span #{memberStats[memberID].totalLevelsCompleted}
|
||||||
|
|
|
@ -8,6 +8,33 @@ block content
|
||||||
else
|
else
|
||||||
.welcome Welcome, #{me.get('name')}!
|
.welcome Welcome, #{me.get('name')}!
|
||||||
|
|
||||||
|
.container-fluid
|
||||||
|
.row
|
||||||
|
.col-md-2
|
||||||
|
.col-md-8
|
||||||
|
.well
|
||||||
|
.text-center
|
||||||
|
strong.uppercase Getting Started with Courses
|
||||||
|
br
|
||||||
|
.text-center
|
||||||
|
a.btn.btn-info(href='/docs/HourofCodeGettingStartedGuide.pdf') Download Getting Started Guide [PDF]
|
||||||
|
br
|
||||||
|
ol
|
||||||
|
li Create a new class by clicking the green "Create New Class" button below.
|
||||||
|
li Once you've created a class, click the blue "Add Students" button.
|
||||||
|
li You'll see student's progress below as they sign up and join your class.
|
||||||
|
br
|
||||||
|
.text-center
|
||||||
|
strong Additional Resources
|
||||||
|
ul
|
||||||
|
li
|
||||||
|
span.spr Complete our
|
||||||
|
a.spr(href='/teachers/freetrial') Teacher Survey
|
||||||
|
span to get 15 more hours of content for FREE for 2 months.
|
||||||
|
li
|
||||||
|
span.spr Visit our
|
||||||
|
a.spr(href='http://discourse.codecombat.com/c/teachers') Teacher Forums
|
||||||
|
span to connect to fellow educators who are using CodeCombat.
|
||||||
|
|
||||||
.section-header Your Classes
|
.section-header Your Classes
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,25 @@
|
||||||
extends /templates/base
|
extends /templates/base
|
||||||
|
|
||||||
block content
|
block content
|
||||||
|
|
||||||
p.bigger-text
|
|
||||||
strong Teachers
|
|
||||||
span - share CodeCombat's Hour of Code with your class!
|
|
||||||
|
|
||||||
.container-fluid
|
.container-fluid
|
||||||
.row
|
.row
|
||||||
.col-md-6
|
.col-md-6
|
||||||
.bigger-text
|
.pull-left
|
||||||
|
a(href="/") <- to main CodeCombat website
|
||||||
|
.col-md-6
|
||||||
|
.pull-right
|
||||||
|
a(href="/hoc") Students, click here!
|
||||||
|
.row
|
||||||
|
.col-md-12
|
||||||
|
br
|
||||||
|
.text-center
|
||||||
|
.title
|
||||||
|
strong.spr Teachers
|
||||||
|
span - share CodeCombat's Hour of Code with your class!
|
||||||
|
.row
|
||||||
|
.col-md-4
|
||||||
|
.col-md-4
|
||||||
|
p
|
||||||
div In just one hour, students will learn:
|
div In just one hour, students will learn:
|
||||||
ul
|
ul
|
||||||
li basic Python syntex
|
li basic Python syntex
|
||||||
|
@ -17,19 +27,7 @@ block content
|
||||||
li strings
|
li strings
|
||||||
li while loops
|
li while loops
|
||||||
li variables
|
li variables
|
||||||
.col-md-5
|
br
|
||||||
.well
|
|
||||||
strong.uppercase These easy steps to get started:
|
|
||||||
ol
|
|
||||||
if me.isAnonymous()
|
|
||||||
li
|
|
||||||
a.spr.link-register Register
|
|
||||||
span for a free teacher account to manage classes and monitor student progress
|
|
||||||
else
|
|
||||||
li Register (you've already done this)
|
|
||||||
li
|
|
||||||
a.spr(href='/courses/teachers') Create a class
|
|
||||||
span and invite students via email, unique passcode, or URL.
|
|
||||||
|
|
||||||
if me.isAnonymous()
|
if me.isAnonymous()
|
||||||
.text-center
|
.text-center
|
||||||
|
@ -37,38 +35,24 @@ block content
|
||||||
.text-center
|
.text-center
|
||||||
button.btn.btn-lg.btn-primary.uppercase.btn-login-account Log into Teacher Account
|
button.btn.btn-lg.btn-primary.uppercase.btn-login-account Log into Teacher Account
|
||||||
|
|
||||||
h2 Free Trial for Teachers!
|
br
|
||||||
p
|
br
|
||||||
strong.spr Hour of Code Special!
|
h2.text-center Hour of Code FAQ
|
||||||
span Complete the survey by December 31st and enroll all your students in the paid courses for 2 months.
|
|
||||||
p(data-i18n="teachers.teacher_subs_0")
|
|
||||||
p
|
|
||||||
span.spr(data-i18n="teachers.teacher_subs_1")
|
|
||||||
a(href='/teachers/freetrial', data-i18n="teachers.teacher_subs_2")
|
|
||||||
span.spl(data-i18n="teachers.teacher_subs_3")
|
|
||||||
|
|
||||||
h2 FAQ
|
.container-fluid
|
||||||
|
.row
|
||||||
|
.col-md-2
|
||||||
|
.col-md-8
|
||||||
|
.faq-question(data-i18n="teachers.who_for_title")
|
||||||
|
p(data-i18n="teachers.who_for_1")
|
||||||
|
p(data-i18n="teachers.who_for_2")
|
||||||
|
|
||||||
h3(data-i18n="teachers.who_for_title")
|
.faq-question Does it cost anything to play Hour of Code?
|
||||||
p(data-i18n="teachers.who_for_1")
|
p No! The first hour of CodeCombat is completely free.
|
||||||
p(data-i18n="teachers.who_for_2")
|
p Teachers, please see the free trial information above for further details.
|
||||||
|
|
||||||
h3 Does it cost anything to play Hour of Code?
|
.faq-question(data-i18n="teachers.more_info_title")
|
||||||
p No! The first hour of CodeCombat is completely free.
|
p
|
||||||
p Teachers, please see the free trial information above for further details.
|
span.spr(data-i18n="teachers.more_info_1")
|
||||||
|
a(href='http://discourse.codecombat.com/c/teachers', data-i18n="teachers.more_info_2")
|
||||||
//- h3 How much are the paid courses?
|
span.spl(data-i18n="teachers.more_info_3")
|
||||||
//- p
|
|
||||||
//- span.spr(data-i18n="teachers.free_3")
|
|
||||||
//- a(href='/courses', data-i18n="teachers.free_4")
|
|
||||||
//- span(data-i18n="teachers.free_5")
|
|
||||||
//- p(data-i18n="teachers.free_6")
|
|
||||||
//- p
|
|
||||||
//- span.spr For more details, please email
|
|
||||||
//- a(href='mailto:team@codecombat.com') team@codecombat.com
|
|
||||||
|
|
||||||
h3(data-i18n="teachers.more_info_title")
|
|
||||||
p
|
|
||||||
span.spr(data-i18n="teachers.more_info_1")
|
|
||||||
a(href='http://discourse.codecombat.com/c/teachers', data-i18n="teachers.more_info_2")
|
|
||||||
span.spl(data-i18n="teachers.more_info_3")
|
|
||||||
|
|
|
@ -11,6 +11,12 @@ module.exports = class TeachersView extends RootView
|
||||||
'click .btn-login-account': 'onClickLogin'
|
'click .btn-login-account': 'onClickLogin'
|
||||||
'click .link-register': 'onClickSignup'
|
'click .link-register': 'onClickSignup'
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
|
super()
|
||||||
|
unless me.isAnonymous()
|
||||||
|
_.defer ->
|
||||||
|
application.router.navigate "/courses/teachers", trigger: true
|
||||||
|
|
||||||
onClickLogin: (e) ->
|
onClickLogin: (e) ->
|
||||||
@openModalView new AuthModal(mode: 'login') if me.get('anonymous')
|
@openModalView new AuthModal(mode: 'login') if me.get('anonymous')
|
||||||
|
|
||||||
|
|
|
@ -50,10 +50,22 @@ module.exports = class ClassroomView extends RootView
|
||||||
@supermodel.loadCollection(sessions, 'sessions')
|
@supermodel.loadCollection(sessions, 'sessions')
|
||||||
courseInstance.sessions = sessions
|
courseInstance.sessions = sessions
|
||||||
sessions.courseInstance = courseInstance
|
sessions.courseInstance = courseInstance
|
||||||
|
courseInstance.sessionsByUser = {}
|
||||||
@listenToOnce sessions, 'sync', (sessions) ->
|
@listenToOnce sessions, 'sync', (sessions) ->
|
||||||
@sessions.add(sessions.slice())
|
@sessions.add(sessions.slice())
|
||||||
sessions.courseInstance.sessionsByUser = sessions.groupBy('creator')
|
sessions.courseInstance.sessionsByUser = sessions.groupBy('creator')
|
||||||
|
|
||||||
|
# generate course instance JIT, in the meantime have models w/out equivalents in the db
|
||||||
|
for course in @courses.models
|
||||||
|
query = {courseID: course.id, classroomID: @classroom.id}
|
||||||
|
courseInstance = @courseInstances.findWhere(query)
|
||||||
|
if not courseInstance
|
||||||
|
courseInstance = new CourseInstance(query)
|
||||||
|
@courseInstances.add(courseInstance)
|
||||||
|
courseInstance.sessions = new CocoCollection([], {model: LevelSession})
|
||||||
|
sessions.courseInstance = courseInstance
|
||||||
|
courseInstance.sessionsByUser = {}
|
||||||
|
|
||||||
onLoaded: ->
|
onLoaded: ->
|
||||||
userSessions = @sessions.groupBy('creator')
|
userSessions = @sessions.groupBy('creator')
|
||||||
for user in @users.models
|
for user in @users.models
|
||||||
|
@ -105,11 +117,20 @@ module.exports = class ClassroomView extends RootView
|
||||||
@openModalView(modal)
|
@openModalView(modal)
|
||||||
|
|
||||||
onClickEnableButton: (e) ->
|
onClickEnableButton: (e) ->
|
||||||
courseInstance = @courseInstances.get($(e.target).data('course-instance-id'))
|
courseInstance = @courseInstances.get($(e.target).data('course-instance-cid'))
|
||||||
userID = $(e.target).data('user-id')
|
userID = $(e.target).data('user-id')
|
||||||
courseInstance.addMember(userID)
|
|
||||||
$(e.target).attr('disabled', true)
|
$(e.target).attr('disabled', true)
|
||||||
@listenToOnce courseInstance, 'sync', @render
|
|
||||||
|
onCourseInstanceCreated = =>
|
||||||
|
courseInstance.addMember(userID)
|
||||||
|
@listenToOnce courseInstance, 'sync', @render
|
||||||
|
|
||||||
|
if courseInstance.isNew()
|
||||||
|
# adding the first student to this course, so generate the course instance for it
|
||||||
|
courseInstance.save(null, {validate: false})
|
||||||
|
courseInstance.once 'sync', onCourseInstanceCreated
|
||||||
|
else
|
||||||
|
onCourseInstanceCreated()
|
||||||
|
|
||||||
onClickRemoveStudentLink: (e) ->
|
onClickRemoveStudentLink: (e) ->
|
||||||
user = @users.get($(e.target).closest('a').data('user-id'))
|
user = @users.get($(e.target).closest('a').data('user-id'))
|
||||||
|
|
|
@ -7,7 +7,7 @@ module.exports = class InviteToClassroomModal extends ModalView
|
||||||
|
|
||||||
events:
|
events:
|
||||||
'click #send-invites-btn': 'onClickSendInvitesButton'
|
'click #send-invites-btn': 'onClickSendInvitesButton'
|
||||||
'click #copy-url-btn': 'onClickCopyURLButton'
|
'click #copy-url-btn, #join-url-input': 'copyURL'
|
||||||
|
|
||||||
initialize: (options) ->
|
initialize: (options) ->
|
||||||
@classroom = options.classroom
|
@classroom = options.classroom
|
||||||
|
@ -34,7 +34,7 @@ module.exports = class InviteToClassroomModal extends ModalView
|
||||||
@$('#invite-emails-success-alert').removeClass('hide')
|
@$('#invite-emails-success-alert').removeClass('hide')
|
||||||
})
|
})
|
||||||
|
|
||||||
onClickCopyURLButton: ->
|
copyURL: ->
|
||||||
@$('#join-url-input').val(@joinURL).select()
|
@$('#join-url-input').val(@joinURL).select()
|
||||||
try
|
try
|
||||||
document.execCommand('copy')
|
document.execCommand('copy')
|
||||||
|
|
|
@ -26,7 +26,7 @@ module.exports = class PurchaseCoursesView extends RootView
|
||||||
getPrice: -> @pricePerStudent * @numberOfStudents
|
getPrice: -> @pricePerStudent * @numberOfStudents
|
||||||
|
|
||||||
onInputStudentsInput: ->
|
onInputStudentsInput: ->
|
||||||
@numberOfStudents = parseInt(@$('#students-input').val()) or 0
|
@numberOfStudents = Math.max(parseInt(@$('#students-input').val()) or 0, 0)
|
||||||
@updatePrice()
|
@updatePrice()
|
||||||
|
|
||||||
updatePrice: ->
|
updatePrice: ->
|
||||||
|
@ -54,7 +54,6 @@ module.exports = class PurchaseCoursesView extends RootView
|
||||||
onStripeReceivedToken: (e) ->
|
onStripeReceivedToken: (e) ->
|
||||||
@state = 'purchasing'
|
@state = 'purchasing'
|
||||||
@render?()
|
@render?()
|
||||||
console.log 'e', e
|
|
||||||
|
|
||||||
data =
|
data =
|
||||||
maxRedeemers: @numberOfStudents
|
maxRedeemers: @numberOfStudents
|
||||||
|
|
|
@ -60,7 +60,9 @@ module.exports = class TeacherCoursesView extends RootView
|
||||||
# TODO: how to get new classroom from modal?
|
# TODO: how to get new classroom from modal?
|
||||||
@classrooms.add(modal.classroom)
|
@classrooms.add(modal.classroom)
|
||||||
# TODO: will this definitely fire after modal saves new classroom?
|
# TODO: will this definitely fire after modal saves new classroom?
|
||||||
@listenToOnce modal.classroom, 'sync', @render
|
@listenToOnce modal.classroom, 'sync', ->
|
||||||
|
@addFreeCourseInstances()
|
||||||
|
@render()
|
||||||
|
|
||||||
onClickEditClassroomSmall: (e) ->
|
onClickEditClassroomSmall: (e) ->
|
||||||
classroomID = $(e.target).data('classroom-id')
|
classroomID = $(e.target).data('classroom-id')
|
||||||
|
@ -68,3 +70,26 @@ module.exports = class TeacherCoursesView extends RootView
|
||||||
modal = new ClassroomSettingsModal({classroom: classroom})
|
modal = new ClassroomSettingsModal({classroom: classroom})
|
||||||
@openModalView(modal)
|
@openModalView(modal)
|
||||||
@listenToOnce modal, 'hide', @render
|
@listenToOnce modal, 'hide', @render
|
||||||
|
|
||||||
|
onLoaded: ->
|
||||||
|
super()
|
||||||
|
@addFreeCourseInstances()
|
||||||
|
|
||||||
|
addFreeCourseInstances: ->
|
||||||
|
# so that when students join the classroom, they can automatically get free courses
|
||||||
|
# non-free courses are generated when the teacher first adds a student to them
|
||||||
|
for classroom in @classrooms.models
|
||||||
|
for course in @courses.models
|
||||||
|
continue if not course.get('free')
|
||||||
|
courseInstance = @courseInstances.findWhere({classroomID: classroom.id, courseID: course.id})
|
||||||
|
if not courseInstance
|
||||||
|
courseInstance = new CourseInstance({
|
||||||
|
classroomID: classroom.id
|
||||||
|
courseID: course.id
|
||||||
|
})
|
||||||
|
# TODO: figure out a better way to get around triggering validation errors for properties
|
||||||
|
# that the server will end up filling in, like an empty members array, ownerID
|
||||||
|
courseInstance.save(null, {validate: false})
|
||||||
|
@courseInstances.add(courseInstance)
|
||||||
|
@listenToOnce courseInstance, 'sync', @addFreeCourseInstances
|
||||||
|
return
|
|
@ -106,8 +106,7 @@ ClassroomHandler = class ClassroomHandler extends Handler
|
||||||
address: email
|
address: email
|
||||||
email_data:
|
email_data:
|
||||||
class_name: classroom.get('name')
|
class_name: classroom.get('name')
|
||||||
# TODO: join_link
|
join_link: "https://codecombat.com/courses?_cc=" + (classroom.get('codeCamel') or classroom.get('code'))
|
||||||
join_link: "https://codecombat.com/courses/students?_cc=" + (classroom.get('codeCamel') or classroom.get('code'))
|
|
||||||
sendwithus.api.send context, _.noop
|
sendwithus.api.send context, _.noop
|
||||||
return @sendSuccess(res, {})
|
return @sendSuccess(res, {})
|
||||||
|
|
||||||
|
|
Reference in a new issue