Merge branch 'master' into production

This commit is contained in:
Nick Winter 2015-12-04 12:06:48 -08:00
commit 0c8a5dabb0
30 changed files with 85 additions and 1440 deletions

View file

@ -26,12 +26,13 @@ module.exports = class LevelSetupManager extends CocoClass
levelURL = "/db/level/#{@options.levelID}"
@level = new Level().setURL levelURL
@level = @supermodel.loadModel(@level, 'level').model
onLevelSync = ->
return if @destroyed
if @waitingToLoadModals
@waitingToLoadModals = false
@loadModals()
onLevelSync.call @ if @level.loaded
if @level.loaded then @onLevelSync() else @listenToOnce @level, 'sync', @onLevelSync
onLevelSync: ->
return if @destroyed
if @waitingToLoadModals
@waitingToLoadModals = false
@loadModals()
loadSession: ->
sessionURL = "/db/level/#{@options.levelID}/session"
@ -104,6 +105,7 @@ module.exports = class LevelSetupManager extends CocoClass
lastHeroesPurchased = me.get('purchased')?.heroes ? []
@options.parent.openModalView(firstModal)
@trigger 'open'
# @inventoryModal.onShown() # replace?
#- Modal events

View file

@ -1,149 +0,0 @@
#course-details-mock-view
.section-selector
margin-bottom: 0px
.concept-completion-container
font-size: 10pt
.summary-container
font-size: 14pt
.statistics-container
font-size: 12pt
td
padding-right: 8px
.table-concepts-summary
width: 100%
.concept-summary
width: 100%
background-color: white
cursor: default
display: inline-block
white-space: nowrap
font-size: 9pt
font-weight: normal
border: 1px solid gray
margin: 0px
padding: 2px
background-color: white
#editSettingsModal .modal-dialog
background-color: white
font-size: 14pt
.edit-description-input
width: 100%
.edit-name-input
width: 50%
.member-header
cursor: pointer
display: inline-block
padding: 2px
.textarea-emails
width: 50%
.select-language
width: 200px
display: inline
.select-session
width: 300px
display: inline
.progress-header
margin-right: 14px
cursor: pointer
.progress-key
cursor: default
display: inline-block
white-space: nowrap
font-size: 12px
line-height: 12px
font-weight: normal
border: 1px solid gray
margin: 0px
padding: 2px
.progress-key-started
background-color: lightgreen
.progress-key-complete
background-color: lightgray
.expand-progress-checkbox
margin-left: 14px
.expand-progress-label
font-weight: normal
font-size: 14px
.student-cell
width: 150px
.progress-cell
padding: 2px
padding-bottom: 10px
.level-popup-container
display: none
position: absolute
padding: 10px
border: 1px solid black
z-index: 3
background-color: blanchedalmond
font-size: 10pt
.level-progression-concepts
color: #317EAC
font-size: 12pt
font-weight: bold
margin-top: 8px
margin-bottom: 4px
.level-progression-levels
color: #317EAC
font-size: 12pt
font-weight: bold
margin-top: 8px
.progress-level-cell
display: inline-block
white-space: nowrap
font-size: 12px
line-height: 12px
border: 1px solid gray
margin: 0px
padding: 2px
.progress-level-cell-started
cursor: pointer
background-color: lightgreen
.progress-level-cell-complete
cursor: pointer
background-color: lightgray
.progress-concept-cell
display: inline-block
white-space: nowrap
font-size: 12px
line-height: 12px
border: 1px solid gray
margin: 0px
padding: 2px
.progress-concept-cell-started
background-color: lightgreen
.progress-concept-cell-complete
background-color: lightgray
.condense-progress
width: 100%

View file

@ -1,14 +0,0 @@
#course-enroll-mock-view
.btn-buy
margin: 20px 0px
.center
text-align: center
.enroll-container
margin: 5% 20%
width: 60%
.session-name
width: 300px

View file

@ -1,31 +0,0 @@
#course-info-view
.btn-enroll
margin-top: 20px
.center
text-align: center
.caption-text
font-size: 14px
.concepts-container
width: 200px
.contact-container
margin-top: 20px
text-align: center
.info-container
margin: 0% 10%
font-size: 18px
.monitoring-img-container
margin-top: 10px
.praise-quote
font-size: 24px
font-style: italic
.progress-container
font-size: 20px

View file

@ -1,71 +0,0 @@
#courses-mock-view
.center
text-align: center
.code-input
width: 100%
.course-image
width: 100%
.course-panel
margin: 20px
.faq-blurb
font-size: 14px
.row-pick-class
display: none
#continueModal .modal-dialog
background-color: white
max-width: 400px
.instruction-label
font-size: 14pt
.or
margin-bottom: 20px
font-size: 14pt
.btn-enroll
margin-top: 20px
.center
text-align: center
.concepts-container
width: 200px
.contact-container
margin-top: 20px
text-align: center
.info-container
margin: 0% 10%
font-size: 18px
.monitoring-img-container
margin-top: 10px
.praise-caption
font-size: 14px
.praise-quote
font-size: 20px
font-style: italic
.progress-container
font-size: 20px
.img-quote
height: 160px
.popover
z-index: 1050
min-width: 400px
h3
background: transparent
border: 0
font-size: 30px

View file

@ -1,14 +0,0 @@
#purchase-courses-view
font-size: 18px
.enrollments-info
width: 300px
#students-input
width: 100px
font-size: 30px
text-align: center
.uppercase
text-transform: uppercase

View file

@ -1,4 +1,4 @@
- var completed = session && session.get('state').complete;
- var completed = session && session.get('state') && session.get('state').complete;
h3 #{i}. #{level.name.replace('Course: ', '')}
if session
p

View file

@ -90,7 +90,7 @@ block content
- var session = sessionMap[levelID];
a(href=view.getLevelURL(level, course, courseInstance, session))
- var content = view.levelPopoverContent(level, session, i);
if session && session.get('state').complete
if session && session.get('state') && session.get('state').complete
.progress-bar.progress-bar-success(style=css, data-content=content, data-toggle='popover')= i
else if session
.progress-bar.progress-bar-warning(style=css, data-content=content, data-toggle='popover')= i

View file

@ -1,6 +1,13 @@
extends /templates/base
block content
h2
if view.teacherMode
a(href="/courses/teachers") Back to my classrooms
else
a(href="/courses") Back to my courses
hr
if (noCourseInstance || noCourseInstanceSelected) && course
h1= course.get('name')
@ -36,7 +43,7 @@ block content
else
span(data-i18n='courses.unnamed_class')
if !view.owner.isNew() && view.getOwnerName()
if !view.owner.isNew() && view.getOwnerName() && courseInstance.get('name') != 'Single Player'
span.spl.spr - Teacher:
//a(href="/user/#{view.owner.id}") // Don't link to profiles until we improve them
span

View file

@ -34,7 +34,7 @@ block content
p
strong Hi adventurer, welcome back!
p
a#continue-playing-btn.btn.btn-success.btn-lg(href=view.continuePlayingLink()) Continue Playing
button#continue-playing-btn.btn.btn-success.btn-lg Continue Playing
p
em.spr
span.spr Last Played:

View file

@ -1,271 +0,0 @@
extends /templates/base
block content
//- DO NOT localize / i18n
div
span *UNDER CONSTRUCTION, send feedback to
a.spl(href='mailto:team@codecombat.com') team@codecombat.com
div
input.student-mode-checkbox(type='checkbox', checked=studentMode)
span.spl Student view
div(style='border-bottom: 1px solid black;')
.modal#editSettingsModal
.modal-dialog
.modal-header
button.close(data-dismiss='modal')
span ×
h3.modal-title Edit Class Settings
.modal-body
p
strong Title
p
input.edit-name-input(type='text', value="#{instance.name}")
p
strong Description
p
textarea.edit-description-input(rows=2)= instance.description
p Select programming languages available to the class:
p
select.form-control.select-language
option(value="Python") Python
option(value="JavaScript") JavaScript
option(value="All Languages") All Languages
p
input(type='checkbox', checked)
span.spl Show student progress to everyone in the class
.modal-footer
button.btn.btn-save-settings(data-i18n="common.save_changes")
h1= instance.name
small.spl (#{course.title})
p
if instance.description
each line in instance.description.split('\n')
div= line
if !studentMode
p
button.btn.btn-xs.btn-edit-settings(data-toggle='modal', data-target='#editSettingsModal') edit class settings
div.well.well-sm.section-selector(role='tabpanel')
ul.nav.nav-pills(role='tablist')
if studentMode
li.active(role='presentation')
a(href='#levels', aria-controls='levels', role='tab', data-toggle='tab') Levels
li(role='presentation')
a(href='#progress', aria-controls='progress', role='tab', data-toggle='tab') Class
else
li.active(role='presentation')
a(href='#progress', aria-controls='progress', role='tab', data-toggle='tab') Class
li(role='presentation')
a(href='#invite', aria-controls='invite', role='tab', data-toggle='tab') Add Students
li(role='presentation')
a(href='#levels', aria-controls='levels', role='tab', data-toggle='tab') Levels
.tab-content
if studentMode
.tab-pane.active#levels(role='tabpanel')
+levels-tab
.tab-pane#progress(role='tabpanel')
+progress-tab
else
.tab-pane.active#progress(role='tabpanel')
+progress-tab
.tab-pane#invite(role='tabpanel')
br
p Invite students to join this class.
if course.title !== 'Introduction to Computer Science'
p Student unlock code: #{instance.code}
p Class capacity: 34/50
textarea.textarea-emails(rows=3, placeholder="Enter student emails to invite, one per line")
div(style='margin-top:10px;')
button.btn.btn-success.btn-invite Send Invites
.tab-pane#levels(role='tabpanel')
+levels-tab
mixin progress-tab
if instance.students
.container-fluid.summary-container
.row
.col-md-6
h3 Statistics
table.statistics-container
tr
td Total students:
td #{instance.students.length}
tr
td Average level play time:
td= moment.duration(stats.averageLevelPlaytime, "seconds").humanize()
tr
td Total play time:
td= moment.duration(stats.totalPlayTime, "seconds").humanize()
tr
td Average levels completed:
td #{stats.averageLevelsCompleted}
tr
td Total levels completed:
td #{stats.totalLevelsCompleted}
tr
td Furthest level completed:
td #{stats.lastLevelCompleted}
.col-md-6
h3 Concepts Covered
table.table-concepts-summary
each concept in courseConcepts
- var conceptCompletion = Math.round(parseFloat(conceptsCompleted[concept]) / instance.students.length * 100)
if isNaN(conceptCompletion)
- conceptCompletion = 0
tr
td.concept-completion-container
span.concept-summary(style="width:#{conceptCompletion}%;")
span.concept-completed-foreground(data-i18n="concepts." + concept)
span.spl - #{conceptCompletion}%
h3 Students
table.table.table-condensed
thead
tr
th
span.member-header.spr Name
if memberSort === 'nameAsc'
span.member-header.glyphicon.glyphicon-chevron-up
else if memberSort === 'nameDesc'
span.member-header.glyphicon.glyphicon-chevron-down
th
span.progress-header.spr Progress
if memberSort === 'progressAsc'
span.progress-header.glyphicon.glyphicon-chevron-up
else if memberSort === 'progressDesc'
span.progress-header.glyphicon.glyphicon-chevron-down
else
span(style='padding-left:16px;')
span.progress-key.progress-key-complete complete
span.progress-key.progress-key-started started
span.progress-key not started
input.expand-progress-checkbox(type='checkbox')
span.spl.expand-progress-label Expand details
tbody
each student in instance.students
tr
td.student-cell
a= student
div #{stats[student].levelsCompleted} levels completed
div #{moment.duration(stats[student].secondsPlayed, 'seconds').humanize()} played
div Played #{moment().subtract(stats[student].secondsLastPlayed, 'seconds').fromNow()}
td.progress-cell
if showExpandedProgress
.level-progression-concepts Concepts
each concept in courseConcepts
if userConceptsMap[student] && userConceptsMap[student][concept] === 'complete'
span.spr.progress-concept-cell.progress-concept-cell-complete(data-i18n="concepts." + concept)
else if userConceptsMap[student] && userConceptsMap[student][concept] === 'started'
span.spr.progress-concept-cell.progress-concept-cell-started(data-i18n="concepts." + concept)
else
span.spr.progress-concept-cell.progress-concept-cell-not-started(data-i18n="concepts." + concept)
.level-progression-levels Levels
- var i = 0
each level in course.levels
if userLevelStateMap[student][level] === 'complete'
span.progress-level-cell.progress-level-cell-complete #{i + 1}
span.spl= level.replace('Course: ', '')
.level-popup-container
h3 #{i + 1}. #{level.replace('Course: ', '')}
p
- var playTime = Math.round(Math.random() * 600)
span Time to solve
span : #{moment.duration(playTime, "seconds").humanize()}
p
- var completionDate = new Date()
- completionDate.setUTCDate(completionDate.getUTCDate() - Math.round(Math.random() * 60))
span Completed on
span : #{moment(completionDate).format('MMMM Do YYYY, h:mm:ss a')}
strong(data-i18n="clans.view_solution") Click to view solution.
else if userLevelStateMap[student][level] === 'started'
span.progress-level-cell.progress-level-cell-started #{i + 1} #{level.replace('Course: ', '')}
.level-popup-container
h3 #{i + 1}. #{level.replace('Course: ', '')}
p
- var completionDate = new Date()
- completionDate.setUTCDate(completionDate.getUTCDate() - Math.round(Math.random() * 60))
span Last played on
span : #{moment(completionDate).format('MMMM Do YYYY, h:mm:ss a')}
strong(data-i18n="clans.view_solution") Click to view solution.
else
span.progress-level-cell.level-progression-level-not-started #{i + 1} #{level.replace('Course: ', '')}
- i++
else
//- Condensed view
table
tbody
tr
td
.level-progression-concepts(style='margin:0px;') Concepts
td.condense-progress
each concept in courseConcepts
if userConceptsMap[student] && userConceptsMap[student][concept] === 'complete'
span.spr.progress-concept-cell.progress-concept-cell-complete(data-i18n="concepts." + concept)
else if userConceptsMap[student] && userConceptsMap[student][concept] === 'started'
span.spr.progress-concept-cell.progress-concept-cell-started(data-i18n="concepts." + concept)
else
break
tr
td
.level-progression-levels(style='margin:0px;') Levels
td.condense-progress
- var levelCellWidth = 100.00 / course.levels.length
- var i = 0
each level in course.levels
if userLevelStateMap[student][level] === 'complete'
span.progress-level-cell.progress-level-cell-complete(style="width:#{levelCellWidth}%;") #{i + 1}
.level-popup-container
h3 #{i + 1}. #{level.replace('Course: ', '')}
p
- var playTime = Math.round(Math.random() * 600)
span Time to solve
span : #{moment.duration(playTime, "seconds").humanize()}
p
- var completionDate = new Date()
- completionDate.setUTCDate(completionDate.getUTCDate() - Math.round(Math.random() * 60))
span Completed on
span : #{moment(completionDate).format('MMMM Do YYYY, h:mm:ss a')}
strong(data-i18n="clans.view_solution") Click to view solution.
else if userLevelStateMap[student][level] === 'started'
span.progress-level-cell.progress-level-cell-started(style="width:#{levelCellWidth}%;") #{i + 1}
.level-popup-container
h3 #{i + 1}. #{level.replace('Course: ', '')}
p
- var completionDate = new Date()
- completionDate.setUTCDate(completionDate.getUTCDate() - Math.round(Math.random() * 60))
span Last played on
span : #{moment(completionDate).format('MMMM Do YYYY, h:mm:ss a')}
strong(data-i18n="clans.view_solution") Click to view solution.
else
break
- i++
mixin levels-tab
br
table.table.table-striped.table-condensed
thead
tr
th
th Status
th Level
th Concepts
tbody
- var student = instance.students[0]
each level in course.levels
tr
td
button.btn.btn-success.btn-play-level(data-level=level) Play
td= userLevelStateMap[student][level]
td= level.replace('Course: ', '')
td
each concept in courseConcepts
if levelConceptsMap[level] && levelConceptsMap[level][concept]
span.spr.progress-level-cell.level-progression-level-not-started(data-i18n="concepts." + concept)

View file

@ -1,53 +0,0 @@
extends /templates/base
block content
//- DO NOT localize / i18n
div
span *UNDER CONSTRUCTION, send feedback to
a.spl(href='mailto:team@codecombat.com') team@codecombat.com
div(style='border-bottom: 1px solid black')
.well.well-lg.enroll-container
h1.center Buy Course
h3 1. Course
p Select 'All Courses' for a 50% discount!
.form-group
select.form-control.course-select
each course in courses
option(value="#{course.title}")= course.title
option(value="All Courses") All Courses
h3 2. Number of students
p Enter the number of students you need for this class.
input.input-quantity(type='text', value="#{quantity}")
h3 3. Name your class
p This will be displayed on the course page for you and your students. It can be changed later.
input.session-name(type='text', placeholder="Mrs. Smith's 4th Period")
h3 4. Buy
p
span.spr You are purchasing a license for
strong.spr #{selectedCourseTitle}
span.spr for
strong #{quantity} students
| .
p After purchase you will receive an unlock code to distribute to your students, which they can use to enroll in your class.
p.center
if price > 0
button.btn.btn-success.btn-lg.btn-buy $#{price}
else
button.btn.btn-success.btn-lg.btn-buy FREE
h3 Free trial for teachers!
p
span.spr Please fill out our
a(href='/teachers/freetrial', data-i18n="teachers.teacher_subs_2")
span.spl to get individual access to all courses for evalutaion purposes.
h3 Questions?
p
span Please contact
a.spl(href='mailto:team@codecombat.com') team@codecombat.com

View file

@ -1,78 +0,0 @@
extends /templates/base
block content
//- DO NOT localize / i18n
div
span *UNDER CONSTRUCTION, send feedback to
a.spl(href='mailto:team@codecombat.com') team@codecombat.com
div(style='border-bottom: 1px solid black; margin-bottom: 20px')
.info-container
h1.center= course.title
p.center.gameplay-img-container
img(src="#{course.image}" width='800')
p= course.description
h1.center Learn More in Less Time
p
span.spr Your students will learn
strong.spr more computer science
span.spr material in
strong.spr less time
span with CodeCombat!
p
span.spr In about
strong.spr #{course.duration} hours
span.spr your students will work through
strong.spr #{course.levels.length} lessons
span and cover these high-level topics:
p
ul
each topic in course.topics
li= topic
h1.center No Experience Necesssary
p
strong.spr No outside experience
span is needed for students to complete this course. They will pick up where the left off from the previous CodeCombat course.
p
strong.spr Teachers do not need programming experience
span to administer this course. Your students will learn on their own.
p
span.spr There are built-in
strong.spr help videos, level guides, tool tips
span to explain everything to your students.
p
span.spr Use
strong.spr student progress monitoring
span to match up stronger students with those that need a little extra help!
h1.center Monitor Student Progress
p.center.monitoring-img-container
img(src='/images/pages/clans/dashboard_preview.png' width='700')
div.center.caption-text TODO: Add caption
p.progress-container
ul
li
strong Track concepts
span.spl learned by each student
li Track levels completed for each student
li
span See your students'
strong.spl solutions
li Sort students by name or progress
h1.center Teachers Love CodeCombat!
p
div.praise-quote "#{praise.quote}"
div.caption-text - #{praise.source}
p.center
button.btn.btn-info.btn-lg.btn-enroll(data-course-id="#{courseID}") Enroll Now
p.contact-container
span For more information, please contact
a.spl(href='mailto:team@codecombat.com') team@codecombat.com

View file

@ -1,128 +0,0 @@
extends /templates/base
block content
//- DO NOT localize / i18n
div(style='border-bottom: 1px solid black')
span *UNDER CONSTRUCTION, send feedback to
a.spl(href='mailto:team@codecombat.com') team@codecombat.com
.modal#continueModal
.modal-dialog
.modal-header
button.close(data-dismiss='modal')
span ×
h3.modal-title Loading...
.modal-body
.container-fluid
.row.button-row.row-pick-class
.col-md-12
.well.well-sm
p
div.instruction-label Pick from your current classes
.container-fluid
.row
.col-md-8
select.form-control.select-session
each inst in instances
option= inst.name
.col-md-4
button.btn.btn-success.btn-enter Enter
.row.button-row.center.row-pick-class
.col-md-12
div.or Or
if studentMode
.row.button-row
.col-md-12
.well.well-sm
p
div.instruction-label Enter an unlock code
.container-fluid
.row
.col-md-8
input.code-input(type='text', placeholder="Enter unlock code")
.col-md-4
button.btn.btn-success.btn-enroll Enroll
else
.row.button-row.center
.col-md-12
button.btn.btn-success.btn-lg.btn-buy Buy this course
br
if !studentMode
button.btn.btn-warning.btn-student Students Click Here
else
button.btn.btn-warning.btn-teacher Teachers Click Here
h1.center Courses on CodeCombat
if !studentMode
.info-container
p Courses are designed to introduce computer science concepts using CodeCombat's fun and engaging environment. CodeCombat levels are organized around key topics to encourage progressive learning, over the course of 5 hours.
.container-fluid
.row
.col-md-6
ul
li Learn more in less time
li No coding experience necesssary
li Easily monitor student progress
p Purchase a course for your entire class. It's easy to sign up your students!
p.faq-blurb
span.spr See the courses
a.spr.courses-faq FAQ
span for more information.
.col-md-6
img.img-quote(src="/images/pages/courses/coco_complab.png")
p
.well.well-sm
div.praise-quote "#{praise.quote}"
div.praise-caption - #{praise.source}
h2.center Choose Your Course:
.container-fluid
- var i = 0
while i < courses.length
.row
+course-block(courses[i], i)
- i++
if i < courses.length
+course-block(courses[i], i)
- i++
mixin course-block(course, courseID)
.col-md-6
.well.panel.course-panel(class=course.unlocked ? 'panel-success' : 'panel-info')
.panel-heading
.panel-title
span.spr #{course.title}
strong #{course.unlocked ? '[ enrolled ]' : ''}
.panel-body
.container-fluid
.row
.col-md-12
p
img.course-image(src="#{course.image}")
.row.button-row
.col-md-6
strong Topics
ul
each topic in course.topics
li= topic
strong Hours of content: #{course.duration}
.col-md-6.center(style='margin-top: 40px;')
if !studentMode
if course.unlocked
button.btn.btn-lg.btn-success.btn-continue(data-toggle='modal', data-target="#continueModal", data-course-title="#{course.title}", data-course-id="#{courseID}") #{course.unlocked ? 'Continue' : 'Enter'}
else if course.title === 'Introduction to Computer Science'
button.btn.btn-lg.btn-success.btn-buy(data-course-id="#{courseID}") Get FREE course
else
button.btn.btn-lg.btn-success.btn-buy(data-course-id="#{courseID}") Buy course
else
if course.unlocked
a.btn.btn-lg.btn-success.btn-continue(href="/courses/mock1/#{courseID}?student=true") Continue
else
button.btn.btn-lg.btn-success.btn-continue(data-toggle='modal', data-target="#continueModal", data-course-title="#{course.title}", data-course-id="#{courseID}") Enter

View file

@ -28,6 +28,8 @@ if campaign
div(style="left: #{level.position.x}%; bottom: #{level.position.y}%", class="level-shadow" + (level.next ? " next" : "") + " " + (levelStatusMap[level.slug] || ""))
.level-info-container(data-level-slug=level.slug, data-level-path=level.levelPath || 'level', data-level-name=level.name)
- var playCount = levelPlayCountMap[level.slug]
.progress.progress-striped.active.hide
.progress-bar(style="width: 100%")
div(class="level-info " + (levelStatusMap[level.slug] || "") + (level.requiresSubscription ? " premium" : ""))
.level-status
h3= i18n(level, 'name') + (level.disabled ? " (Coming soon!)" : (level.locked ? " (Locked)" : ""))

View file

@ -13,7 +13,15 @@ module.exports = class HomeView extends RootView
window.tracker?.trackEvent 'Homepage Loaded', category: 'Homepage'
if @getQueryVariable 'hour_of_code'
application.router.navigate "/hoc", trigger: true
if @justPlaysCourses()
isHourOfCodeWeek = true # Temporary: default to /hoc flow during the main event week
if isHourOfCodeWeek and (@isNewPlayer() or (@justPlaysCourses() and me.isAnonymous()))
# Go/return straight to playing single-player HoC course on Play click
@playURL = '/hoc?go=true'
@alternatePlayURL = '/play'
@alternatePlayText = 'home.play_campaign_version'
else if @justPlaysCourses()
# Save players who might be in a classroom from getting into the campaign
@playURL = '/courses'
@alternatePlayURL = '/play'
@alternatePlayText = 'home.play_campaign_version'
@ -44,3 +52,6 @@ module.exports = class HomeView extends RootView
justPlaysCourses: ->
# This heuristic could be better, but currently we don't add to me.get('courseInstances') for single-player anonymous intro courses, so they have to beat a level without choosing a hero.
return me.get('stats')?.gamesCompleted and not me.get('heroConfig')
isNewPlayer: ->
not me.get('stats')?.gamesCompleted and not me.get('heroConfig')

View file

@ -89,7 +89,7 @@ module.exports = class SubscriptionView extends RootView
onClickRecipientsSubscribe: (e) ->
emails = @$el.find('.recipient-emails').val().split('\n')
valid = @emailValidator.validateEmails(emails, =>@render?())
@recipientSubs.startSubscribe(emails) if valid
@recipientSubs.startSubscribe(emails) if valid
onClickRecipientUnsubscribe: (e) ->
$(e.target).addClass('hide')
@ -108,8 +108,8 @@ class EmailValidator
validateEmails: (emails, render) ->
@lastEmails = emails.join('\n')
#taken from http://www.regular-expressions.info/email.html
emailRegex = /[A-z0-9._%+-]+@[A-z0-9.-]+\.[A-z]{2,4}/
#taken from http://www.regular-expressions.info/email.html
emailRegex = /[A-z0-9._%+-]+@[A-z0-9.-]+\.[A-z]{2,63}/
@validEmails = (email for email in emails when emailRegex.test(email.trim().toLowerCase()))
return @emailsInvalid(render) if @validEmails.length < emails.length
return @emailsValid(render)

View file

@ -79,7 +79,7 @@ module.exports = class AuthModal extends ModalView
emailCheck: ->
email = $('#email', @$el).val()
filter = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i # https://news.ycombinator.com/item?id=5763990
filter = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,63}$/i # https://news.ycombinator.com/item?id=5763990
unless filter.test(email)
forms.setErrorToProperty @$el, 'email', 'Please enter a valid email address', true
return false

View file

@ -4,21 +4,21 @@ template = require 'templates/courses/choose-language-modal'
module.exports = class ChooseLanguageModal extends ModalView
id: 'choose-language-modal'
template: template
events:
'click .lang-choice-btn': 'onClickLanguageChoiceButton'
initialize: (options) ->
options ?= {}
@logoutFirst = options.logoutFirst
onClickLanguageChoiceButton: (e) ->
@chosenLanguage = $(e.target).data('language')
@chosenLanguage = $(e.target).closest('.lang-choice-btn').data('language')
if @logoutFirst
@logoutUser()
else
@saveLanguageSetting()
logoutUser: ->
$.ajax({
method: 'POST'
@ -45,7 +45,8 @@ module.exports = class ChooseLanguageModal extends ModalView
@listenToOnce me, 'sync', @onLanguageSettingSaved
else
@onLanguageSettingSaved()
onLanguageSettingSaved: ->
@trigger('set-language')
window.tracker?.trackEvent 'Chose language', category: 'Courses', label: @chosenLanguage
@hide()

View file

@ -19,7 +19,7 @@ module.exports = class ClassroomView extends RootView
id: 'classroom-view'
template: template
teacherMode: false
events:
'click #edit-class-details-link': 'onClickEditClassDetailsLink'
'click #activate-licenses-btn': 'onClickActivateLicensesButton'
@ -57,7 +57,7 @@ module.exports = class ClassroomView extends RootView
@listenToOnce sessions, 'sync', (sessions) ->
@sessions.add(sessions.slice())
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}
@ -83,7 +83,7 @@ module.exports = class ClassroomView extends RootView
campaign = @campaigns.get(campaignID)
courseInstance.sessions.campaign = campaign
super()
afterRender: ->
@$('[data-toggle="popover"]').popover({
html: true
@ -131,7 +131,7 @@ module.exports = class ClassroomView extends RootView
classStats: ->
stats = {}
playtime = 0
total = 0
for session in @sessions.models
@ -140,8 +140,8 @@ module.exports = class ClassroomView extends RootView
total += 1
stats.averagePlaytime = if playtime and total then moment.duration(playtime / total, "seconds").humanize() else 0
stats.totalPlaytime = if playtime then moment.duration(playtime, "seconds").humanize() else 0
completeSessions = @sessions.filter (s) -> s.get('state').complete
completeSessions = @sessions.filter (s) -> s.get('state')?.complete
stats.averageLevelsComplete = if @users.size() then (_.size(completeSessions) / @users.size()).toFixed(1) else 'N/A'
stats.totalLevelsComplete = _.size(completeSessions)
return stats
@ -158,7 +158,7 @@ module.exports = class ClassroomView extends RootView
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})
@ -175,7 +175,7 @@ module.exports = class ClassroomView extends RootView
})
@openModalView(modal)
modal.once 'remove-student', @onStudentRemoved, @
onStudentRemoved: (e) ->
@users.remove(e.user)
@render()
@ -193,4 +193,4 @@ module.exports = class ClassroomView extends RootView
getLevelURL: (level, course, courseInstance, session) ->
return null unless @teacherMode and _.all(arguments)
"/play/level/#{level.slug}?course=#{course.id}&course-instance=#{courseInstance.id}&session=#{session.id}&observing=true"
"/play/level/#{level.slug}?course=#{course.id}&course-instance=#{courseInstance.id}&session=#{session.id}&observing=true"

View file

@ -18,7 +18,7 @@ module.exports = class HourOfCodeView extends RootView
template: template
events:
'click #student-btn': 'onClickStudentButton'
'click #continue-playing-btn': 'onClickContinuePlayingButton'
'click #start-new-game-btn': 'onClickStartNewGameButton'
'click #log-in-btn': 'onClickLogInButton'
'click #log-out-link': 'onClickLogOutLink'
@ -52,7 +52,7 @@ module.exports = class HourOfCodeView extends RootView
project: 'name,slug'
}
})
setUpHourOfCode: ->
# If we haven't tracked this player as an hourOfCode player yet, and it's a new account, we do that now.
elapsed = new Date() - new Date(me.get('dateCreated'))
@ -60,16 +60,26 @@ module.exports = class HourOfCodeView extends RootView
me.set('hourOfCode', true)
me.patch()
$('body').append($('<img src="https://code.org/api/hour/begin_codecombat.png" style="visibility: hidden;">'))
application.tracker?.trackEvent 'Hour of Code Begin'
window.tracker?.trackEvent 'Hour of Code Begin'
onClickContinuePlayingButton: ->
url = @continuePlayingLink()
window.tracker?.trackEvent 'HoC continue playing ', category: 'HoC', label: url
app.router.navigate(url, { trigger: true })
afterRender: ->
super()
@onClickStartNewGameButton() if @getQueryVariable('go') and not @lastLevel
onClickStartNewGameButton: ->
# user without hour of code course instance, creates one, starts playing
# User without hour of code course instance, creates one, starts playing
modal = new ChooseLanguageModal({
logoutFirst: @hourOfCodeCourseInstance?
})
@openModalView(modal)
@listenToOnce modal, 'set-language', @startHourOfCodePlay
window.tracker?.trackEvent 'Start New Game', category: 'HoC', label: 'HoC Start New Game'
continuePlayingLink: ->
ci = @hourOfCodeCourseInstance
"/play/level/#{@lastLevel.get('slug')}?course=#{ci.get('courseID')}&course-instance=#{ci.id}"
@ -87,11 +97,13 @@ module.exports = class HourOfCodeView extends RootView
modal = new StudentLogInModal()
@openModalView(modal)
modal.on 'want-to-create-account', @onWantToCreateAccount, @
window.tracker?.trackEvent 'Started Login', category: 'HoC', label: 'HoC Login'
onWantToCreateAccount: ->
modal = new StudentSignUpModal()
@openModalView(modal)
window.tracker?.trackEvent 'Started Signup', category: 'HoC', label: 'HoC Sign Up'
onClickLogOutLink: ->
auth.logoutUser()
window.tracker?.trackEvent 'Log Out', category: 'HoC', label: 'HoC Log Out'
auth.logoutUser()

View file

@ -7,7 +7,7 @@ User = require 'models/User'
module.exports = class StudentSignInModal extends ModalView
id: 'student-log-in-modal'
template: template
events:
'click #log-in-btn': 'onClickLogInButton'
'submit form': 'onSubmitForm'
@ -16,11 +16,13 @@ module.exports = class StudentSignInModal extends ModalView
onSubmitForm: (e) ->
e.preventDefault()
@login()
onClickLogInButton: ->
@login()
login: ->
# TODO: doesn't track failed login
window.tracker?.trackEvent 'Finished Login', category: 'Courses', label: 'Courses Student Login'
data = forms.formToObject @$el
@enableModalInProgress(@$el)
auth.loginUser data, (jqxhr) =>
@ -39,4 +41,4 @@ module.exports = class StudentSignInModal extends ModalView
afterInsert: ->
super()
_.delay (=> @$('input:visible:first').focus()), 500
_.delay (=> @$('input:visible:first').focus()), 500

View file

@ -23,7 +23,7 @@ module.exports = class StudentSignUpModal extends ModalView
afterInsert: ->
super()
_.delay (=> @$('input:visible:first').focus()), 500
onClickSkipLink: ->
@trigger 'click-skip-link' # defer to view that opened this modal
@hide?()
@ -37,7 +37,7 @@ module.exports = class StudentSignUpModal extends ModalView
emailCheck: ->
email = @$('#email').val()
filter = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i # https://news.ycombinator.com/item?id=5763990
filter = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,63}$/i # https://news.ycombinator.com/item?id=5763990
unless filter.test(email)
@$('#errors-alert').text($.i18n.t('share_progress_modal.email_invalid')).removeClass('hide')
return false
@ -71,7 +71,8 @@ module.exports = class StudentSignUpModal extends ModalView
data.emails ?= {}
data.emails.generalNews ?= {}
data.emails.generalNews.enabled = false
window.tracker?.trackEvent 'Finished Student Signup', label: 'CodeCombat'
# TODO: Doesn't handle failed user creation. Double posts when placed in onCreateUserSuccess.
window.tracker?.trackEvent 'Finished Student Signup', category: 'Courses', label: 'Courses Student Signup'
@enableModalInProgress(@$el)
user = new User(data)
user.notyErrors = false

View file

@ -1,205 +0,0 @@
app = require 'core/application'
utils = require 'core/utils'
RootView = require 'views/core/RootView'
template = require 'templates/courses/mock1/course-details'
CocoCollection = require 'collections/CocoCollection'
Campaign = require 'models/Campaign'
module.exports = class CourseDetailsView extends RootView
id: 'course-details-mock-view'
template: template
events:
'change .expand-progress-checkbox': 'onExpandedProgressCheckbox'
'change .student-mode-checkbox': 'onChangeStudent'
'click .btn-play-level': 'onClickPlayLevel'
'click .btn-save-settings': 'onClickSaveSettings'
'click .member-header': 'onClickMemberHeader'
'click .progress-header': 'onClickProgressHeader'
'mouseenter .progress-level-cell': 'onMouseEnterPoint'
'mouseleave .progress-level-cell': 'onMouseLeavePoint'
constructor: (options, @courseID=0, @instanceID=0) ->
super options
@studentMode = utils.getQueryVariable('student', false) or options.studentMode
@initData()
destroy: ->
@stopListening?()
getRenderData: ->
context = super()
context.conceptsProgression = @conceptsProgression ? []
context.course = @course ? {}
context.courseConcepts = @courseConcepts ? []
context.instance = @instances?[@currentInstanceIndex] ? {}
context.instances = @instances ? []
context.levelConceptsMap = @levelConceptsMap ? {}
context.maxLastStartedIndex = @maxLastStartedIndex ? 0
context.memberSort = @memberSort
context.userConceptsMap = @userConceptsMap ? {}
context.userLevelStateMap = @userLevelStateMap ? {}
context.showExpandedProgress = @showExpandedProgress
context.stats = @stats
context.studentMode = @studentMode ? false
conceptsCompleted = {}
for user of context.userConceptsMap
for concept of context.userConceptsMap[user]
conceptsCompleted[concept] ?= 0
conceptsCompleted[concept]++
context.conceptsCompleted = conceptsCompleted
context
initData: ->
@memberSort = 'nameAsc'
mockData = require 'views/courses/mock1/CoursesMockData'
@course = mockData.courses[@courseID]
@currentInstanceIndex = @instanceID
@instances = mockData.instances
@updateLevelMaps()
@campaigns = new CocoCollection([], { url: "/db/campaign", model: Campaign, comparator:'_id' })
@listenTo @campaigns, 'sync', @onCampaignSync
@supermodel.loadModel @campaigns, 'clan', cache: false
updateLevelMaps: ->
@levelMap = {}
@levelMap[level] = true for level in @course.levels
@userLevelStateMap = {}
@stats =
averageLevelPlaytime: _.random(30, 240)
averageLevelsCompleted: _.random(1, @course.levels.length)
students: {}
@maxLastStartedIndex = -1
for student in @instances?[@currentInstanceIndex].students
@userLevelStateMap[student] = {}
lastCompletedIndex = _.random(-1, @course.levels.length)
for i in [0..lastCompletedIndex]
@userLevelStateMap[student][@course.levels[i]] = 'complete'
lastStartedIndex = lastCompletedIndex + 1
@userLevelStateMap[student][@course.levels[lastStartedIndex]] = 'started'
@maxLastStartedIndex = lastStartedIndex if lastStartedIndex > @maxLastStartedIndex
@stats[student] ?= {}
@stats[student].levelsCompleted = 0
@stats[student].levelsCompleted++ for level in @course.levels when @userLevelStateMap[student][level] is 'complete'
@stats[student].secondsPlayed = Math.round(Math.random() * 1000 * (@stats[student].levelsCompleted + 1))
@stats[student].secondsLastPlayed = Math.round(Math.random() * 100000)
@sortMembers()
@stats.totalPlayTime = @instances?[@currentInstanceIndex].students?.length * @stats.averageLevelPlaytime ? 0
@stats.totalLevelsCompleted = @instances?[@currentInstanceIndex].students?.length * @stats.averageLevelsCompleted ? 0
@stats.totalPlayTime = @instances?[@currentInstanceIndex].students?.length * @stats.averageLevelPlaytime ? 0
@stats.lastLevelCompleted = @course.levels[0] ? @course.levels[@course.levels.length - 1]
sortMembers: ->
# Progress sort precedence: most completed concepts, most started concepts, most levels, name sort
instance = @instances?[@currentInstanceIndex] ? {}
return if _.isEmpty(instance)
switch @memberSort
when "nameDesc"
instance.students.sort (a, b) -> b.localeCompare(a)
when "progressAsc"
instance.students.sort (a, b) =>
for level in @course.levels
if @userLevelStateMap[a][level] isnt 'complete' and @userLevelStateMap[b][level] is 'complete'
return -1
else if @userLevelStateMap[a][level] is 'complete' and @userLevelStateMap[b][level] isnt 'complete'
return 1
0
when "progressDesc"
instance.students.sort (a, b) =>
for level in @course.levels
if @userLevelStateMap[a][level] isnt 'complete' and @userLevelStateMap[b][level] is 'complete'
return 1
else if @userLevelStateMap[a][level] is 'complete' and @userLevelStateMap[b][level] isnt 'complete'
return -1
0
else
instance.students.sort (a, b) -> a.localeCompare(b)
onCampaignSync: ->
return unless @campaigns.loaded
@conceptsProgression = []
@courseConcepts = []
@levelConceptsMap = {}
@levelNameSlugMap = {}
@userConceptsMap = {}
# Update course levels if course has a specific campaign
for campaign in @campaigns.models when campaign.get('slug') is @course.campaign
@course.levels = []
for levelID, level of campaign.get('levels')
if campaign.get('slug') is @course.campaign
@course.levels.push level.name
@updateLevelMaps()
for campaign in @campaigns.models
continue if campaign.get('slug') is 'auditions'
for levelID, level of campaign.get('levels')
@levelNameSlugMap[level.name] = level.slug
if level.concepts?
for concept in level.concepts
@conceptsProgression.push concept unless concept in @conceptsProgression
continue unless @levelMap[level.name]
@courseConcepts.push concept unless concept in @courseConcepts
@levelConceptsMap[level.name] ?= {}
@levelConceptsMap[level.name][concept] = true
for student in @instances?[@currentInstanceIndex].students
@userConceptsMap[student] ?= {}
if @userLevelStateMap[student][level.name] is 'complete'
@userConceptsMap[student][concept] = 'complete'
else if @userLevelStateMap[student][level.name] is 'started'
@userConceptsMap[student][concept] ?= 'started'
@courseConcepts.sort (a, b) => if @conceptsProgression.indexOf(a) < @conceptsProgression.indexOf(b) then -1 else 1
@render?()
onChangeStudent: (e) ->
@studentMode = $('.student-mode-checkbox').prop('checked')
@render?()
$('.student-mode-checkbox').attr('checked', @studentMode)
onExpandedProgressCheckbox: (e) ->
@showExpandedProgress = $('.expand-progress-checkbox').prop('checked')
# TODO: why does render reset the checkbox to be unchecked?
@render?()
$('.expand-progress-checkbox').attr('checked', @showExpandedProgress)
onClickMemberHeader: (e) ->
@memberSort = if @memberSort is 'nameAsc' then 'nameDesc' else 'nameAsc'
@sortMembers()
@render?()
onClickProgressHeader: (e) ->
@memberSort = if @memberSort is 'progressAsc' then 'progressDesc' else 'progressAsc'
@sortMembers()
@render?()
onClickPlayLevel: (e) ->
levelName = $(e.target).data('level')
levelSlug = @levelNameSlugMap[levelName]
Backbone.Mediator.publish 'router:navigate', {
route: "/play/level/#{levelSlug}"
viewClass: 'views/play/level/PlayLevelView'
viewArgs: [{}, levelSlug]
}
onClickSaveSettings: (e) ->
if name = $('.edit-name-input').val()
@instances[@currentInstanceIndex].name = name
description = $('.edit-description-input').val()
@instances[@currentInstanceIndex].description = description
$('#editSettingsModal').modal('hide')
@render?()
onMouseEnterPoint: (e) ->
$('.level-popup-container').hide()
container = $(e.target).find('.level-popup-container').show()
margin = 20
offset = $(e.target).offset()
scrollTop = $('#page-container').scrollTop()
height = container.outerHeight()
container.css('left', offset.left + e.offsetX)
container.css('top', offset.top + scrollTop - height - margin)
onMouseLeavePoint: (e) ->
$(e.target).find('.level-popup-container').hide()

View file

@ -1,63 +0,0 @@
app = require 'core/application'
RootView = require 'views/core/RootView'
template = require 'templates/courses/mock1/course-enroll'
module.exports = class CourseEnrollView extends RootView
id: 'course-enroll-mock-view'
template: template
events:
'click .btn-buy': 'onClickBuy'
'change .course-select': 'onChangeCourse'
'change .input-quantity': 'onQuantityChange'
constructor: (options, @courseID=0) ->
super options
@initData()
getRenderData: ->
context = super()
context.courses = @courses ? {}
context.courseID = @courseID
context.price = @price ? 0
context.quantity = @quantity
context.selectedCourseTitle = @selectedCourseTitle
context
afterRender: ->
super()
@$el.find('.course-select').val(@selectedCourseTitle)
initData: ->
mockData = require 'views/courses/mock1/CoursesMockData'
@courses = mockData.courses
@selectedCourseTitle = @courses[@courseID]?.title
@quantity = 20
@updatePrice()
onClickBuy: (e) ->
if @selectedCourseTitle is 'All Courses'
app.router.navigate "/courses/mock1/0"
else
for course, i in @courses when course.title is @selectedCourseTitle
app.router.navigate "/courses/mock1/#{i}"
break
window.location.reload()
onChangeCourse: (e) ->
@selectedCourseTitle = $(e.target).val()
@updatePrice()
@render?()
onQuantityChange: (e) ->
@quantity = $(e.target).val() ? 20
@updatePrice()
@render?()
updatePrice: ->
if @selectedCourseTitle is 'All Courses'
@price = (@courses.length - 1) * @quantity * 2
else if @selectedCourseTitle is 'Introduction to Computer Science'
@price = 0
else
@price = @quantity * 4

View file

@ -1,31 +0,0 @@
app = require 'core/application'
RootView = require 'views/core/RootView'
template = require 'templates/courses/mock1/course-info'
module.exports = class CourseInfoView extends RootView
id: 'course-info-view'
template: template
events:
'click .btn-enroll': 'onClickEnroll'
constructor: (options, @courseID) ->
super options
@initData()
getRenderData: ->
context = super()
context.course = @course ? {}
context.courseID = @courseID
context.praise = @praise
context
initData: ->
mockData = require 'views/courses/mock1/CoursesMockData'
@course = mockData.courses[@courseID]
@praise = mockData.praise[_.random(0, mockData.praise.length - 1)]
onClickEnroll: (e) ->
courseID = $(e.target).data('course-id')
app.router.navigate "/courses/mock1/#{courseID}/enroll"
window.location.reload()

View file

@ -1,170 +0,0 @@
data = {}
data.concepts = [
'Advanced Strings'
'Algorithms'
'Arithmetic'
'Arrays'
'Basic Syntax'
'Boolean Logic'
'Break Statements'
'Classes'
'For Loops'
'Functions'
'If Statements'
'Input Handling'
'Math Operations'
'Object Literals'
'Strings'
'Variables'
'Vectors'
'While Loops'
]
data.courses = [
{
title: 'Introduction to Computer Science'
description: 'Learn basic syntax, while loops, and the CodeCombat learning environment.'
topics: ['Basic Syntax', 'Strings', 'Loops']
duration: 1
levels: ['Dungeons of Kithgard', 'Gems in the Deep', 'Shadow Guard', 'Kounter Kithwise', 'Crawlways of Kithgard', 'Enemy Mine', 'Illusory Interruption', 'Forgetful Gemsmith', 'Signs and Portents', 'Favorable Odds', 'True Names', 'The Prisoner', 'Banefire', 'The Raised Sword', 'Haunted Kithmaze', 'Riddling Kithmaze', 'Descending Further', 'The Second Kithmaze', 'Dread Door', 'Cupboards of Kithgard', 'Hack and Dash']
campaign: 'intro'
image: '/images/pages/courses/101_info.png'
},
{
title: 'Computer Science 2'
description: 'Introduce Arguments, Variables, If Statements, and Arithmetic.'
topics: ['Arguments', 'Variables', 'If Statements', 'Arithmetic']
duration: 5
levels: ['Known Enemy', 'Master of Names', 'Lowly Kithmen', 'Closing the Distance', 'Tactical Strike', 'The Final Kithmaze', 'The Gauntlet', 'Radiant Aura', 'Kithgard Gates', 'Destroying Angel', 'Deadly Dungeon Rescue', 'Kithgard Brawl', 'Cavern Survival', 'Breakout', 'Attack Wisely!', 'Kithgard Mastery', 'Kithgard Apprentice', 'Long Kithmaze', 'Boom! and Bust', 'Defense of Plainswood', 'Winding Trail', 'Thumb Biter', 'Gems or Death', 'Backwoods Ambush', 'Patrol Buster', 'Endangered Burl', 'Village Guard', 'Thornbush Farm', 'Back to Back', 'Ogre Encampment', 'Woodland Cleaver', 'Shield Rush', 'Peasant Protection', 'Munchkin Swarm']
image: '/images/pages/courses/102_info.png'
},
{
title: 'Computer Science 3'
description: 'Learn how to handle input.'
topics: ['If Statements', 'Arithmetic', 'Input Handling']
duration: 5
levels: ['Munchkin Harvest', 'Swift Dagger', 'Shrapnel', 'Arcane Ally', 'Touch of Death', 'Bonemender', 'Coinucopia', 'Copper Meadows', 'Drop the Flag', 'Deadly Pursuit', 'Rich Forager', 'Siege of Stonehold', 'Multiplayer Treasure Grove', 'Dueling Grounds', 'Backwoods Brawl', 'Backwoods Treasure', 'Range Finder', 'Stillness in Motion', 'The Agrippa Defense', 'Storming the Towers of Areth', 'Hold the Forest Pass', 'Hold for Reinforcements', 'Storming the Farmhouse', 'Wild Horses', 'Boulder Woods', 'Unfair Support', 'Tactical Timing', 'Apocalypse', 'Doom Glade', 'Defend the Garrison', 'Lost Viking', 'Forest Flower Grove', 'The Dunes', 'The Mighty Sand Yak', 'Oasis', 'Sarven Road', 'Sarven Gaps', 'Thunderhooves', 'Medical Attention', 'The Great Yak Stampede', 'Minesweeper', 'Sarven Sentry', 'Keeping Time']
image: '/images/pages/courses/103_info.png'
},
{
title: 'Computer Science 4'
description: 'Time to tackle arrays and some pvp stuff.'
topics: ['Loops', 'Break Statements', 'Arrays']
duration: 5
levels: ['Hoarding Gold', 'Decoy Drill', 'Yakstraction', 'Sarven Brawl', 'Desert Combat', 'Dust', 'Sarven Rescue', 'Sacred Statue', 'Mirage Maker', 'Sarven Savior', 'Odd Sandstorm', 'Lurkers', 'Preferential Treatment', 'Sarven Shepherd', 'Shine Getter', 'The Trials', 'Mad Maxer', 'Mad Maxer Strikes Back', 'Mad Maxer Sells Out', 'Mad Maxer Gets Greedy', 'Mad Maxer: Redemption', 'Sarven Treasure', 'Harrowland', 'Sarven Siege', 'Clash of Clones', 'Sand Snakes', 'Crag Tag']
image: '/images/pages/courses/104_info.png'
},
{
title: 'Computer Science 5'
description: 'Time to tackle arrays and some PVP.'
topics: ['Break Statements', 'Arrays', 'Object Literals']
duration: 5
levels: ['Slalom', 'Black Diamond', 'Treasure Cave', 'Ogre Gorge Gouger', 'Dance-Off', 'Alpine Rally', 'Cloudrip Commander', 'Mountain Mercenaries']
image: '/images/pages/courses/105_info.png'
},
{
title: 'Computer Science 6'
description: 'For loops!'
topics: ['Break Statements', 'Object Literals', 'For loops']
duration: 5
levels: ['Timber Guard', 'Hunting Party', 'Zoo Keeper', 'Cloudrip Brawl', 'Cloudrip Treasure', 'Cloudrip Siege', 'Noble Sacrifice', 'Zero Sum', 'Borrowed Sword', 'Protect and Serve']
image: '/images/pages/courses/106_info.png'
},
{
title: 'Computer Science 7'
description: 'Functions!'
topics: ['Object Literals', 'For loops', 'Functions']
duration: 5
levels: ['Vital Powers', 'Timber Turncoat', 'Restless Dead', 'Ring Bearer', 'The Two Flowers', 'The Geometry of Flowers', 'Mountain Flower Grove', 'Hunters and Prey', 'Library Tactician']
image: '/images/pages/courses/107_info.png'
},
{
title: 'Computer Science 8'
description: 'Maths.'
topics: ['For loops', 'Functions', 'Math Operations']
duration: 5
levels: ['Steelclaw Gap', 'Pesky Yaks', 'Mixed Unit Tactics', 'Sowing Fire', 'Reaping Fire', 'Toil and Trouble', 'What in Carnation', 'Misty Island Mine', 'Raiders of the Long Dark', 'Grim Determination', 'Deadly Discs', "Summit's Gate"]
image: '/images/pages/courses/107_info.png'
},
{
title: 'Computer Science 9'
description: 'Vectors and strings.'
topics: ['Vectors', 'Advanced Strings']
duration: 5
levels: ['Circle Walking', 'Skating Away', 'Kelvintaph Crusader', 'Kelvintaph Burgler', 'Ice Soccer', 'Razorfray']
image: '/images/pages/courses/107_info.png'
}
]
getStudents = ->
students = ['Jill', 'Billy', 'Sarah', 'Tom', 'June', 'Bob', 'Kristin', 'Samantha', 'Eric']
_.shuffle(students).slice(_.random(0, 5))
data.instances = [
{
name: "Mr. Smith's First Period"
description: "Homework due on Friday."
code: 'b2KF7'
students: getStudents()
},
{
name: "Mr. Smith's Second Period"
description: "Test class description"
code: 'b2KF7'
students: getStudents()
},
{
name: "Summer Camp 2015"
description: "You should have received an email with extra credit homework."
code: 'b2KF7'
students: getStudents()
},
{
name: "Maple High 4th"
code: 'b2KF7'
students: getStudents()
},
{
name: "Test class name one"
description: "Test class description"
code: 'b2KF7'
students: getStudents()
}
]
data.praise = [
{
quote: "The kids love it."
source: "Leo Joseph Tran, Athlos Leadership Academy"
},
{
quote: "My students have been using the site for a couple of weeks and they love it."
source: "Scott Hatfield, Computer Applications Teacher, School Technology Coordinator, Eastside Middle School"
},
{
quote: "Thanks for the captivating site. My eighth graders love it."
source: "Janet Cook, Ansbach Middle/High School"
},
{
quote: "My students have started working on CodeCombat and love it! I love that they are learning coding and problem solving skills without them even knowing it!!"
source: "Kristin Huff, Special Education Teacher, Webb City School District"
},
{
quote: "I recently introduced Code Combat to a few of my fifth graders and they are loving it!"
source: "Shauna Hamman, Fifth Grade Teacher, Four Peaks Elementary School"
},
{
quote: "Overall I think it's a fantastic service. Variables, arrays, loops, all covered in very fun and imaginative ways. Every kid who has tried it is a fan."
source: "Aibinder Andrew, Technology Teacher"
},
{
quote: "I love what you have created. The kids are so engaged."
source: "Desmond Smith, 4KS Academy"
},
{
quote: "My students love the website and I hope on having content structured around it in the near future."
source: "Michael Leonard, Science Teacher, Clearwater Central Catholic High School"
}
]
module.exports = data

View file

@ -1,119 +0,0 @@
app = require 'core/application'
utils = require 'core/utils'
RootView = require 'views/core/RootView'
template = require 'templates/courses/mock1/courses'
module.exports = class CoursesView extends RootView
id: 'courses-mock-view'
template: template
events:
'click .btn-buy': 'onClickBuy'
'click .btn-continue': 'onClickContinue'
'click .btn-enroll': 'onClickEnroll'
'click .btn-enter': 'onClickEnter'
'click .btn-student': 'onClickStudent'
'click .btn-teacher': 'onClickTeacher'
'hidden.bs.modal #continueModal': 'onHideContinueModal'
constructor: (options) ->
super options
@studentMode = utils.getQueryVariable('student', false) or options.studentMode
@initData()
getRenderData: ->
context = super()
context.courses = @courses ? []
context.instances = @instances ? []
context.praise = @praise
context.studentMode = @studentMode
context
afterRender: ->
super()
@setupCoursesFAQPopover()
initData: ->
mockData = require 'views/courses/mock1/CoursesMockData'
@courses = mockData.courses
for course, i in @courses
if _.random(0, 2) > 0
course.unlocked = true
else
break
@instances = mockData.instances
@praise = mockData.praise[_.random(0, mockData.praise.length - 1)]
setupCoursesFAQPopover: ->
popoverTitle = "<h3>Courses FAQ<button type='button' class='close' onclick='$(&#39;.courses-faq&#39;).popover(&#39;hide&#39;);'>&times;</button></h3>"
popoverContent = "<p><strong>Q:</strong> What's the difference between these courses and the single player game?</p>"
popoverContent += "<p><strong>A:</strong> The single player game is designed for individuals, while the courses are designed for classes.</p>"
popoverContent += "<p>The single player game has items, gems, hero selection, leveling up, and in-app purchases. Courses have classroom management features and streamlined student-focused level pacing.</p>"
@$el.find('.courses-faq').popover(
animation: true
html: true
placement: 'top'
trigger: 'click'
title: popoverTitle
content: popoverContent
container: @$el
).on 'shown.bs.popover', =>
application.tracker?.trackEvent 'Subscription payment methods hover'
onClickBuy: (e) ->
courseID = $(e.target).data('course-id') ? 0
app.router.navigate("/courses/mock1/enroll/#{courseID}")
window.location.reload()
onClickContinue: (e) ->
courseID = $(e.target).data('course-id')
courseTitle = $(e.target).data('course-title')
$('#continueModal').find('.modal-title').text(courseTitle)
$('#continueModal').find('.btn-buy').data('course-id', courseID)
$('#continueModal').find('.btn-enroll').data('course-id', courseID)
$('#continueModal').find('.btn-enter').data('course-id', courseID)
$('#continueModal .row-pick-class').show() if @courses[courseID]?.unlocked
if @courses[courseID]?.unlocked
$('#continueModal .btn-buy').prop('innerText', 'Start new class')
else if courseTitle is 'Introduction to Computer Science'
$('#continueModal .btn-buy').prop('innerText', 'Get this FREE course!')
else
$('#continueModal .btn-buy').prop('innerText', 'Buy this course')
onClickEnroll: (e) ->
$('#continueModal').modal('hide')
courseID = $(e.target).data('course-id')
instanceID = _.random(0, @instances.length - 1)
viewClass = require 'views/courses/mock1/CourseDetailsView'
viewArgs = [{studentMode: @studentMode}, courseID, instanceID]
navigationEvent = route: "/courses/mock1/#{courseID}", viewClass: viewClass, viewArgs: viewArgs
Backbone.Mediator.publish 'router:navigate', navigationEvent
onClickEnter: (e) ->
$('#continueModal').modal('hide')
courseID = $(e.target).data('course-id')
instanceName = $('.select-session').val()
for val, index in @instances when val.name is instanceName
instanceID = index
viewClass = require 'views/courses/mock1/CourseDetailsView'
viewArgs = [{}, courseID, instanceID]
navigationEvent = route: "/courses/mock1/#{courseID}", viewClass: viewClass, viewArgs: viewArgs
Backbone.Mediator.publish 'router:navigate', navigationEvent
onClickStudent: (e) ->
route = "/courses/mock1?student=true"
viewClass = require 'views/courses/mock1/CoursesView'
viewArgs = [studentMode: true]
navigationEvent = route: route, viewClass: viewClass, viewArgs: viewArgs
Backbone.Mediator.publish 'router:navigate', navigationEvent
onClickTeacher: (e) ->
route = "/courses/mock1?student=false"
viewClass = require 'views/courses/mock1/CoursesView'
viewArgs = [studentMode: false]
navigationEvent = route: route, viewClass: viewClass, viewArgs: viewArgs
Backbone.Mediator.publish 'router:navigate', navigationEvent
onHideContinueModal: (e) ->
$('#continueModal .row-pick-class').hide()

View file

@ -499,8 +499,11 @@ module.exports = class CampaignView extends RootView
levelSlug = levelElement.data 'level-slug'
session = @preloadedSession if @preloadedSession?.loaded and @preloadedSession.levelSlug is levelSlug
@setupManager = new LevelSetupManager supermodel: @supermodel, levelID: levelSlug, levelPath: levelElement.data('level-path'), levelName: levelElement.data('level-name'), hadEverChosenHero: @hadEverChosenHero, parent: @, session: session
@$levelInfo.find('.level-info, .progress').toggleClass('hide')
@listenToOnce @setupManager, 'open', ->
@$levelInfo.find('.level-info, .progress').toggleClass('hide')
@$levelInfo?.hide()
@setupManager.open()
@$levelInfo?.hide()
onClickViewSolutions: (e) ->
levelElement = $(e.target).parents('.level-info-container')

View file

@ -124,6 +124,7 @@ ClassroomHandler = class ClassroomHandler extends Handler
return @sendDatabaseError(res, err) if err
return @sendSuccess(res, (@formatEntity(req, classroom) for classroom in classrooms))
else if code = req.query.code.toLowerCase()
code = code.toLowerCase()
Classroom.findOne {code: code}, (err, classroom) =>
return @sendDatabaseError(res, err) if err
return @sendNotFoundError(res) unless classroom