2015-09-03 14:04:40 -04:00
|
|
|
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;')
|
|
|
|
|
2015-09-23 19:27:45 -04:00
|
|
|
if (noCourseInstance || noCourseInstanceSelected) && course
|
|
|
|
h1= course.get('name')
|
|
|
|
if noCourseInstance
|
|
|
|
p You are not enrolled in this course.
|
|
|
|
p
|
|
|
|
span.spr Please visit the
|
|
|
|
a.spr(href="/courses") courses
|
|
|
|
span page to enroll.
|
|
|
|
else if noCourseInstanceSelected
|
|
|
|
p Select one of your classes
|
|
|
|
.container-fluid
|
|
|
|
.row
|
|
|
|
.col-md-6
|
|
|
|
select.form-control.select-instance
|
|
|
|
each courseInstance in courseInstances
|
|
|
|
if courseInstance.get('name')
|
|
|
|
option(value="#{courseInstance.id}")= courseInstance.get('name')
|
|
|
|
else
|
|
|
|
option(value="#{courseInstance.id}") *unnamed*
|
|
|
|
.col-md-6
|
|
|
|
button.btn.btn-success.btn-select-instance Select
|
2015-09-13 01:01:59 -04:00
|
|
|
else if !course || !courseInstance
|
|
|
|
h1 Loading...
|
2015-09-03 14:04:40 -04:00
|
|
|
else
|
2015-09-13 01:01:59 -04:00
|
|
|
h1= courseInstance.get('name') || 'Unnamed Class'
|
|
|
|
small.spl (#{course.get('name')})
|
2015-09-03 14:04:40 -04:00
|
|
|
|
|
|
|
p
|
2015-09-13 01:01:59 -04:00
|
|
|
if courseInstance.get('description')
|
|
|
|
each line in courseInstance.get('description').split('\n')
|
|
|
|
div= line
|
|
|
|
if adminMode && courseInstance
|
|
|
|
+settings-dialog
|
|
|
|
p
|
|
|
|
button.btn.btn-xs(data-toggle='modal', data-target='#settingsModal') edit class settings
|
|
|
|
|
|
|
|
div.well.well-sm(role='tabpanel')
|
|
|
|
ul.nav.nav-pills(role='tablist')
|
|
|
|
li.active(role='presentation')
|
|
|
|
a(href='#progress', aria-controls='progress', role='tab', data-toggle='tab') Class Progress
|
|
|
|
if adminMode
|
|
|
|
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
|
|
|
|
.tab-pane.active#progress(role='tabpanel')
|
|
|
|
+progress-tab
|
|
|
|
if adminMode
|
|
|
|
.tab-pane#invite(role='tabpanel')
|
|
|
|
+invite-tab
|
|
|
|
.tab-pane#levels(role='tabpanel')
|
|
|
|
+levels-tab
|
|
|
|
|
|
|
|
mixin progress-tab
|
|
|
|
.container-fluid.progress-summary-container
|
|
|
|
.row
|
|
|
|
.col-md-6
|
|
|
|
+progress-summary-stats
|
|
|
|
.col-md-6
|
|
|
|
+progress-summary-concepts
|
|
|
|
+progress-members
|
|
|
|
|
|
|
|
mixin progress-summary-stats
|
|
|
|
h3 Statistics
|
|
|
|
table.progress-stats-container
|
|
|
|
tr
|
|
|
|
td Total students:
|
|
|
|
td
|
|
|
|
if courseInstance
|
|
|
|
div #{courseInstance.get('members').length}
|
2015-09-24 10:28:43 -04:00
|
|
|
if instanceStats
|
|
|
|
tr
|
|
|
|
td Average level play time:
|
|
|
|
if instanceStats.averageLevelPlaytime > 0
|
|
|
|
td= moment.duration(instanceStats.averageLevelPlaytime, "seconds").humanize()
|
|
|
|
else
|
|
|
|
td 0
|
|
|
|
tr
|
|
|
|
td Total play time:
|
|
|
|
if instanceStats.totalPlayTime > 0
|
|
|
|
td= moment.duration(instanceStats.totalPlayTime, "seconds").humanize()
|
|
|
|
else
|
|
|
|
td 0
|
|
|
|
tr
|
|
|
|
td Average levels completed:
|
|
|
|
td #{instanceStats.averageLevelsCompleted.toFixed(2)}
|
|
|
|
tr
|
|
|
|
td Total levels completed:
|
|
|
|
td= instanceStats.totalLevelsCompleted
|
|
|
|
tr
|
|
|
|
td Furthest level completed:
|
|
|
|
td= instanceStats.furthestLevelCompleted.replace('Course: ', '')
|
2015-09-13 01:01:59 -04:00
|
|
|
|
|
|
|
mixin progress-summary-concepts
|
|
|
|
h3 Concepts Covered
|
|
|
|
if course && courseInstance && conceptsCompleted
|
|
|
|
table.progress-concepts-container
|
|
|
|
each concept in course.get('concepts')
|
|
|
|
- var conceptCompletion = Math.round(parseFloat(conceptsCompleted[concept]) / courseInstance.get('members').length * 100)
|
|
|
|
if isNaN(conceptCompletion)
|
|
|
|
- conceptCompletion = 0
|
|
|
|
tr
|
|
|
|
td.progress-concept-completion-container
|
|
|
|
span.progress-concept-summary(style="width:#{conceptCompletion}%;")
|
|
|
|
span.spr(data-i18n="concepts." + concept)
|
|
|
|
span - #{conceptCompletion}%
|
|
|
|
|
|
|
|
mixin progress-members
|
|
|
|
h3 Students
|
|
|
|
table.table.table-condensed
|
|
|
|
thead
|
|
|
|
tr
|
|
|
|
th
|
|
|
|
span.progress-member-header.spr Name
|
|
|
|
if memberSort === 'nameAsc'
|
|
|
|
span.progress-member-header.glyphicon.glyphicon-chevron-up
|
|
|
|
else if memberSort === 'nameDesc'
|
|
|
|
span.progress-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.progress-expand-checkbox(type='checkbox')
|
|
|
|
span.spl.progress-expand-label Expand details
|
|
|
|
tbody
|
|
|
|
each memberID in sortedMembers
|
|
|
|
tr
|
|
|
|
td.progress-member-cell
|
|
|
|
+progress-members-individual(memberID)
|
|
|
|
td.progress-cell
|
|
|
|
if showExpandedProgress
|
|
|
|
.progress-concepts-label Concepts
|
|
|
|
+progress-members-concepts(memberID)
|
|
|
|
.progess-levels-label Levels
|
|
|
|
+progress-members-levels-expanded(memberID)
|
|
|
|
else
|
|
|
|
table
|
|
|
|
tbody
|
|
|
|
tr
|
|
|
|
td.progress-concepts-label Concepts
|
|
|
|
td.progress-condensed-cell
|
|
|
|
+progress-members-concepts(memberID)
|
|
|
|
tr
|
|
|
|
td.progess-levels-label Levels
|
|
|
|
td.progress-condensed-cell
|
|
|
|
+progress-members-levels-condensed(memberID)
|
|
|
|
|
|
|
|
mixin progress-members-individual(memberID)
|
|
|
|
- var name = memberUserMap[memberID] ? memberUserMap[memberID].get('name') : 'Anoner'
|
|
|
|
a(href="/user/#{memberID}")= name || 'Anoner'
|
2015-09-24 10:28:43 -04:00
|
|
|
if memberStats && memberStats[memberID]
|
|
|
|
div #{memberStats[memberID].totalLevelsCompleted} levels
|
|
|
|
div Played #{moment.duration(memberStats[memberID].totalPlayTime, "seconds").humanize()}
|
2015-09-13 01:01:59 -04:00
|
|
|
|
|
|
|
mixin progress-members-concepts(memberID)
|
|
|
|
if course && userLevelStateMap[memberID]
|
|
|
|
each concept in course.get('concepts')
|
|
|
|
if userConceptStateMap[memberID][concept] === 'complete'
|
|
|
|
span.spr.progress-concept-cell.progress-concept-cell-complete(data-i18n="concepts." + concept)
|
|
|
|
else if userConceptStateMap[memberID][concept] === 'started'
|
|
|
|
span.spr.progress-concept-cell.progress-concept-cell-started(data-i18n="concepts." + concept)
|
|
|
|
else if showExpandedProgress
|
|
|
|
span.spr.progress-concept-cell.progress-concept-cell-not-started(data-i18n="concepts." + concept)
|
|
|
|
|
|
|
|
mixin progress-members-levels-expanded(memberID)
|
|
|
|
if campaign && userLevelStateMap[memberID]
|
|
|
|
- var i = 0
|
|
|
|
each level, levelID in campaign.get('levels')
|
|
|
|
if userLevelStateMap[memberID][levelID] === 'complete'
|
2015-09-24 17:48:54 -04:00
|
|
|
span.progress-level-cell.progress-level-cell-complete(data-level-id=levelID, data-level-slug=level.slug, data-user-id=memberID) #{i + 1}
|
2015-09-13 01:01:59 -04:00
|
|
|
span.spl= level.name.replace('Course: ', '')
|
|
|
|
+progress-members-popup-completed(i, level)
|
|
|
|
else if userLevelStateMap[memberID][levelID] === 'started'
|
2015-09-24 17:48:54 -04:00
|
|
|
span.progress-level-cell.progress-level-cell-started(data-level-id=levelID, data-level-slug=level.slug, data-user-id=memberID) #{i + 1} #{level.name.replace('Course: ', '')}
|
2015-09-13 01:01:59 -04:00
|
|
|
+progress-members-popup-started(i, level)
|
|
|
|
else
|
|
|
|
span.progress-level-cell #{i + 1} #{level.name.replace('Course: ', '')}
|
|
|
|
- i++
|
|
|
|
|
|
|
|
mixin progress-members-levels-condensed(memberID)
|
|
|
|
if campaign && userLevelStateMap[memberID]
|
|
|
|
- var numLevels = Object.keys(campaign.get('levels')).length
|
|
|
|
- var levelCellWidth = 100.00
|
|
|
|
if numLevels > 0
|
|
|
|
levelCellWidth = 100.00 / numLevels
|
|
|
|
- var i = 0
|
|
|
|
each level, levelID in campaign.get('levels')
|
|
|
|
if userLevelStateMap[memberID][levelID] === 'complete'
|
2015-09-24 17:48:54 -04:00
|
|
|
span.progress-level-cell.progress-level-cell-complete(style="width:#{levelCellWidth}%;", data-level-id=levelID, data-level-slug=level.slug, data-user-id=memberID) #{i + 1}
|
2015-09-13 01:01:59 -04:00
|
|
|
+progress-members-popup-completed(i, level)
|
|
|
|
else if userLevelStateMap[memberID][levelID] === 'started'
|
2015-09-24 17:48:54 -04:00
|
|
|
span.progress-level-cell.progress-level-cell-started(style="width:#{levelCellWidth}%;", data-level-id=levelID, data-level-slug=level.slug, data-user-id=memberID) #{i + 1}
|
2015-09-13 01:01:59 -04:00
|
|
|
+progress-members-popup-started(i, level)
|
|
|
|
else
|
|
|
|
break
|
|
|
|
- i++
|
|
|
|
|
|
|
|
mixin progress-members-popup-completed(i, level)
|
|
|
|
.progress-popup-container
|
|
|
|
h3 #{i + 1}. #{level.name.replace('Course: ', '')}
|
2015-09-24 17:48:54 -04:00
|
|
|
p Play time: #{moment.duration(level.playtime, "seconds").humanize()}
|
|
|
|
p Completed: #{moment(level.changed).format('MMMM Do YYYY, h:mm:ss a')}
|
|
|
|
if adminMode
|
|
|
|
strong Click to view solution.
|
2015-09-13 01:01:59 -04:00
|
|
|
|
|
|
|
mixin progress-members-popup-started(i, level)
|
|
|
|
.progress-popup-container
|
|
|
|
h3 #{i + 1}. #{level.name.replace('Course: ', '')}
|
2015-09-24 17:48:54 -04:00
|
|
|
p Play time: #{moment.duration(level.playtime, "seconds").humanize()}
|
|
|
|
p Last played: #{moment(level.changed).format('MMMM Do YYYY, h:mm:ss a')}
|
|
|
|
if adminMode
|
|
|
|
strong Click to view solution.
|
2015-09-13 01:01:59 -04:00
|
|
|
|
|
|
|
mixin invite-tab
|
|
|
|
p Invite students to join this class.
|
|
|
|
p TODO: Student unlock code
|
|
|
|
p TODO: Class capacity
|
|
|
|
textarea.invite-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
|
|
|
|
|
|
|
|
mixin levels-tab
|
|
|
|
table.table.table-striped.table-condensed
|
|
|
|
thead
|
|
|
|
tr
|
|
|
|
th
|
|
|
|
th Status
|
|
|
|
th Level
|
|
|
|
th Concepts
|
|
|
|
tbody
|
|
|
|
if campaign
|
|
|
|
each level, levelID in campaign.get('levels')
|
|
|
|
tr
|
|
|
|
td
|
|
|
|
button.btn.btn-success.btn-play-level(data-level-slug=level.slug) Play
|
|
|
|
td
|
|
|
|
if userLevelStateMap[me.id]
|
|
|
|
div= userLevelStateMap[me.id][levelID]
|
|
|
|
td= level.name.replace('Course: ', '')
|
|
|
|
td
|
|
|
|
if levelConceptMap[levelID]
|
|
|
|
each concept in course.get('concepts')
|
|
|
|
if levelConceptMap[levelID][concept]
|
|
|
|
span.spr.progress-level-cell.progress-level-cell-not-started(data-i18n="concepts." + concept)
|
|
|
|
|
|
|
|
mixin settings-dialog
|
|
|
|
.modal#settingsModal
|
|
|
|
.modal-dialog
|
|
|
|
.modal-header
|
|
|
|
button.close(data-dismiss='modal')
|
|
|
|
span ×
|
|
|
|
h3.modal-title Edit Class Settings
|
|
|
|
.modal-body
|
|
|
|
p
|
|
|
|
strong Title
|
|
|
|
p
|
|
|
|
input.settings-name-input(type='text', value="#{courseInstance.get('name') || ''}")
|
|
|
|
p
|
|
|
|
strong Description
|
|
|
|
p
|
|
|
|
textarea.settings-description-input(rows=2)= courseInstance.get('description')
|
|
|
|
p Select programming languages available to the class:
|
|
|
|
p
|
|
|
|
select.form-control.settings-language-select
|
|
|
|
option(value="Python") Python
|
|
|
|
option(value="JavaScript") JavaScript
|
|
|
|
option(value="All Languages") All Languages
|
|
|
|
p
|
|
|
|
input.settings-public-progress(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")
|