Remove progress tab from course details view

This commit is contained in:
Matt Lott 2015-12-03 16:20:53 -08:00
parent 52cb638a6c
commit c610ce6ffd
3 changed files with 33 additions and 488 deletions
app
styles/courses
templates/courses
views/courses

View file

@ -1,94 +1,11 @@
#course-details-view
#invite-emails-textarea
width: 50%
.progress-cell
padding: 2px
padding-bottom: 10px
.progress-popup-container
display: none
position: absolute
padding: 10px
border: 1px solid black
z-index: 3
background-color: blanchedalmond
font-size: 10pt
.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-complete
background-color: lightgreen
.progress-concept-cell-started
background-color: lightyellow
.progress-concept-completion-container
font-size: 10pt
.progress-concepts-label
color: #317EAC
font-size: 12pt
.available-courses-title
font-size: 20px
font-weight: bold
margin-top: 8px
margin-bottom: 4px
margin: 4px
.progress-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
.progress-concepts-container
width: 100%
.progress-condensed-cell
width: 100%
.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-complete
background-color: lightgreen
.progress-key-started
background-color: lightyellow
.progress-expand-checkbox
margin-left: 14px
.progress-expand-label
font-weight: normal
font-size: 14px
.progress-level-cell
.concept
display: inline-block
white-space: nowrap
font-size: 12px
@ -97,50 +14,6 @@
margin: 0px
padding: 2px
.progress-level-cell-complete
cursor: pointer
background-color: lightgreen
.progress-level-cell-started
cursor: pointer
background-color: lightyellow
.progess-levels-label
color: #317EAC
font-size: 12pt
font-weight: bold
margin-top: 8px
.progress-member-cell
width: 150px
.progress-member-header
cursor: pointer
display: inline-block
padding: 2px
.progress-stats-container
font-size: 12pt
td
padding-right: 8px
.progress-summary-container
font-size: 14pt
#settingsModal .modal-dialog
background-color: white
font-size: 14pt
.settings-description-input
width: 100%
.settings-language-select
width: 200px
display: inline
.settings-name-input
width: 50%
.jumbotron
form
margin-top: -40px

View file

@ -115,238 +115,33 @@ block content
em COMING SOON
p We are hard at work making more courses for you!
if !me.isAnonymous()
div.well.well-sm(role='tabpanel')
ul.nav.nav-pills(role='tablist')
if view.teacherMode
li.active(role='presentation')
a(href='#progress', aria-controls='progress', role='tab', data-toggle='tab', data-i18n="courses.progress")
li(role='presentation')
a(href='#levels', aria-controls='levels', role='tab', data-toggle='tab', data-i18n="nav.play")
else
li.active(role='presentation')
a(href='#levels', aria-controls='levels', role='tab', data-toggle='tab', data-i18n="nav.play")
li(role='presentation')
a(href='#progress', aria-controls='progress', role='tab', data-toggle='tab', data-i18n="courses.progress")
.tab-content
if view.teacherMode
.tab-pane.active#progress(role='tabpanel')
+progress-tab
.tab-pane#levels(role='tabpanel')
+levels-tab
else
.tab-pane.active#levels(role='tabpanel')
+levels-tab
.tab-pane#progress(role='tabpanel')
+progress-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(data-i18n="courses.stats")
table.progress-stats-container
tr
td(data-i18n="courses.total_students")
td
if courseInstance
div #{courseInstance.get('members').length}
if instanceStats
tr
td(data-i18n="courses.average_time")
if instanceStats.averageLevelPlaytime > 0
td= moment.duration(instanceStats.averageLevelPlaytime, "seconds").humanize()
else
td 0
tr
td(data-i18n="courses.total_time")
if instanceStats.totalPlayTime > 0
td= moment.duration(instanceStats.totalPlayTime, "seconds").humanize()
else
td 0
tr
td(data-i18n="courses.average_levels")
td #{instanceStats.averageLevelsCompleted.toFixed(2)}
tr
td(data-i18n="courses.total_levels")
td= instanceStats.totalLevelsCompleted
tr
td(data-i18n="courses.furthest_level")
td= instanceStats.furthestLevelCompleted.replace('Course: ', '')
mixin progress-summary-concepts
h3(data-i18n="courses.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
.available-courses-title Available Levels
table.table.table-striped.table-condensed
thead
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(data-i18n="courses.students")
table.table.table-condensed
thead
tr
th
span.progress-member-header.spr(data-i18n="clans.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(data-i18n="clans.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(data-i18n="clans.complete_1")
span.progress-key.progress-key-started(data-i18n="clans.started_1")
if showExpandedProgress
span.progress-key(data-i18n="clans.not_started_1")
input.progress-expand-checkbox(type='checkbox')
span.spl.progress-expand-label(data-i18n="courses.expand_details")
tbody
each memberID in sortedMembers
tr
td.progress-member-cell
+progress-members-individual(memberID)
td.progress-cell
if showExpandedProgress
.progress-concepts-label(data-i18n="courses.concepts")
+progress-members-concepts(memberID)
.progess-levels-label(data-i18n="nav.play")
+progress-members-levels-expanded(memberID)
else
table
tbody
tr
td.progress-concepts-label(data-i18n="courses.concepts")
td.progress-condensed-cell
+progress-members-concepts(memberID)
tr
td.progess-levels-label(data-i18n="nav.play")
td.progress-condensed-cell
+progress-members-levels-condensed(memberID)
mixin progress-members-individual(memberID)
- var name = memberUserMap[memberID] ? memberUserMap[memberID].get('name') : 'Anoner'
strong= name || 'Anoner'
if memberStats && memberStats[memberID]
div
span #{memberStats[memberID].totalLevelsCompleted}
span.spl(data-i18n="courses.levels")
div
span.spr(data-i18n="courses.played")
span #{moment.duration(memberStats[memberID].totalPlayTime, "seconds").humanize()}
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'
span.progress-level-cell.progress-level-cell-complete(data-level-id=levelID, data-level-slug=level.slug, data-user-id=memberID) #{i + 1}
span.spl= level.name.replace('Course: ', '')
+progress-members-popup-completed(i, level, (view.userLevelSessionMap[memberID] || {})[levelID])
else if userLevelStateMap[memberID][levelID] === 'started'
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: ', '')}
+progress-members-popup-started(i, level, (view.userLevelSessionMap[memberID] || {})[levelID])
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'
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}
+progress-members-popup-completed(i, level, (view.userLevelSessionMap[memberID] || {})[levelID])
else if userLevelStateMap[memberID][levelID] === 'started'
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}
+progress-members-popup-started(i, level, (view.userLevelSessionMap[memberID] || {})[levelID])
else
break
- i++
mixin progress-members-popup-completed(i, level, session)
.progress-popup-container
h3 #{i + 1}. #{level.name.replace('Course: ', '')}
p
span.spr(data-i18n="courses.play_time")
span #{moment.duration(session.get('playtime'), "seconds").humanize()}
p
span.spr(data-i18n="courses.completed")
span #{moment(session.get('changed')).format('MMMM Do YYYY, h:mm:ss a')}
if view.teacherMode || me.isAdmin()
strong(data-i18n="clans.view_solution")
mixin progress-members-popup-started(i, level, session)
.progress-popup-container
h3 #{i + 1}. #{level.name.replace('Course: ', '')}
p
span.spr(data-i18n="courses.play_time")
span #{moment.duration(session.get('playtime'), "seconds").humanize()}
p
span.spr(data-i18n="clans.last_played")
span #{moment(session.get('changed')).format('MMMM Do YYYY, h:mm:ss a')}
if view.teacherMode || me.isAdmin()
strong(data-i18n="clans.view_attempt")
mixin levels-tab
table.table.table-striped.table-condensed
thead
tr
th
th(data-i18n="clans.status")
th(data-i18n="resources.level")
th(data-i18n="courses.concepts")
tbody
if campaign
- var lastLevelCompleted = true;
- var levelCount = 0;
each level, levelID in campaign.get('levels')
tr
td
if lastLevelCompleted || view.teacherMode
- var i18n = level.type === 'course-ladder' ? 'play.compete' : 'home.play';
button.btn.btn-success.btn-play-level(data-level-slug=level.slug, data-i18n=i18n, data-level-id=levelID)
td
if userLevelStateMap[me.id]
div= userLevelStateMap[me.id][levelID]
- lastLevelCompleted = userLevelStateMap[me.id][levelID] === 'complete'
else
- lastLevelCompleted = false
td= ++levelCount + '. ' + 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)
th
th(data-i18n="clans.status")
th(data-i18n="resources.level")
th(data-i18n="courses.concepts")
tbody
if campaign
- var lastLevelCompleted = true;
- var levelCount = 0;
each level, levelID in campaign.get('levels')
tr
td
if lastLevelCompleted || view.teacherMode
- var i18n = level.type === 'course-ladder' ? 'play.compete' : 'home.play';
button.btn.btn-success.btn-play-level(data-level-slug=level.slug, data-i18n=i18n, data-level-id=levelID)
td
if userLevelStateMap[me.id]
div= userLevelStateMap[me.id][levelID]
- lastLevelCompleted = userLevelStateMap[me.id][levelID] === 'complete'
else
- lastLevelCompleted = false
td= ++levelCount + '. ' + level.name.replace('Course: ', '')
td
if levelConceptMap[levelID]
each concept in course.get('concepts')
if levelConceptMap[levelID][concept]
span.spr.concept(data-i18n="concepts." + concept)

View file

@ -7,8 +7,6 @@ LevelSession = require 'models/LevelSession'
RootView = require 'views/core/RootView'
template = require 'templates/courses/course-details'
User = require 'models/User'
utils = require 'core/utils'
Prepaid = require 'models/Prepaid'
storage = require 'core/storage'
autoplayedOnce = false
@ -21,14 +19,8 @@ module.exports = class CourseDetailsView extends RootView
memberSort: 'nameAsc'
events:
'change .progress-expand-checkbox': 'onCheckExpandedProgress'
'click .btn-play-level': 'onClickPlayLevel'
'click .btn-select-instance': 'onClickSelectInstance'
'click .progress-member-header': 'onClickMemberHeader'
'click .progress-header': 'onClickProgressHeader'
'click .progress-level-cell': 'onClickProgressLevelCell'
'mouseenter .progress-level-cell': 'onMouseEnterPoint'
'mouseleave .progress-level-cell': 'onMouseLeavePoint'
'submit #school-form': 'onSubmitSchoolForm'
constructor: (options, @courseID, @courseInstanceID) ->
@ -38,7 +30,6 @@ module.exports = class CourseDetailsView extends RootView
@classroom = new Classroom()
@course = @supermodel.getModel(Course, @courseID) or new Course _id: @courseID
@listenTo @course, 'sync', @onCourseSync
@prepaid = new Prepaid()
if @course.loaded
@onCourseSync()
else
@ -47,23 +38,13 @@ module.exports = class CourseDetailsView extends RootView
getRenderData: ->
context = super()
context.campaign = @campaign
context.conceptsCompleted = @conceptsCompleted ? {}
context.course = @course if @course?.loaded
context.courseInstance = @courseInstance if @courseInstance?.loaded
context.courseInstances = @courseInstances?.models ? []
context.instanceStats = @instanceStats
context.levelConceptMap = @levelConceptMap ? {}
context.memberSort = @memberSort
context.memberStats = @memberStats
context.memberUserMap = @memberUserMap ? {}
context.noCourseInstance = @noCourseInstance
context.noCourseInstanceSelected = @noCourseInstanceSelected
context.pricePerSeat = @course.get('pricePerSeat')
context.showExpandedProgress = @showExpandedProgress
context.sortedMembers = @sortedMembers ? []
context.userConceptStateMap = @userConceptStateMap ? {}
context.userLevelStateMap = @userLevelStateMap ? {}
context.document = document
context.promptForSchool = @courseComplete and not me.isAnonymous() and not me.get('schoolName') and not storage.load('no-school')
context
@ -155,23 +136,8 @@ module.exports = class CourseDetailsView extends RootView
@levelSessions = new CocoCollection([], { url: "/db/course_instance/#{@courseInstance.id}/level_sessions", model: LevelSession, comparator: '_id' })
@listenToOnce @levelSessions, 'sync', @onLevelSessionsSync
@supermodel.loadCollection @levelSessions, 'level_sessions', cache: false
@members = new CocoCollection([], { url: "/db/course_instance/#{@courseInstance.id}/members", model: User, comparator: 'nameLower' })
@listenToOnce @members, 'sync', @onMembersSync
@supermodel.loadCollection @members, 'members', cache: false
@owner = new User({_id: @courseInstance.get('ownerID')})
@supermodel.loadModel @owner, 'user'
if @teacherMode and prepaidID = @courseInstance.get('prepaidID')
@prepaid = @supermodel.getModel(Prepaid, prepaidID) or new Prepaid _id: prepaidID
@listenTo @prepaid, 'sync', @onPrepaidSync
if @prepaid.loaded
@onPrepaidSync()
else
@supermodel.loadModel @prepaid, 'prepaid'
@render()
onPrepaidSync: ->
return if @destroyed
# TODO: why do we rerender here? Template doesn't use prepaid.
@render()
onLevelSessionsSync: ->
@ -180,7 +146,6 @@ module.exports = class CourseDetailsView extends RootView
@instanceStats = averageLevelsCompleted: 0, furthestLevelCompleted: '', totalLevelsCompleted: 0, totalPlayTime: 0
@memberStats = {}
@userConceptStateMap = {}
@userLevelSessionMap = {}
@userLevelStateMap = {}
levelStateMap = {}
for levelSession in @levelSessions.models
@ -211,9 +176,6 @@ module.exports = class CourseDetailsView extends RootView
for concept of @levelConceptMap[levelID]
@userConceptStateMap[userID][concept] = state
@userLevelSessionMap[userID] ?= {}
@userLevelSessionMap[userID][levelID] = levelSession
@userLevelStateMap[userID] ?= {}
@userLevelStateMap[userID][levelID] = state
@ -240,15 +202,6 @@ module.exports = class CourseDetailsView extends RootView
autoplayedOnce = true
@$el.find('button.btn-play-level').click()
onMembersSync: ->
return if @destroyed
# console.log 'onMembersSync'
@memberUserMap = {}
for user in @members.models
@memberUserMap[user.id] = user
@sortMembers()
@render()
onAllCoursesSync: ->
@findNextCourseInstance()
@ -265,22 +218,6 @@ module.exports = class CourseDetailsView extends RootView
else
@loadAllCourses()
onCheckExpandedProgress: (e) ->
@showExpandedProgress = $('.progress-expand-checkbox').prop('checked')
# TODO: why does render reset the checkbox to be unchecked?
@render()
$('.progress-expand-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) ->
levelSlug = $(e.target).data('level-slug')
levelID = $(e.target).data('level-id')
@ -306,66 +243,6 @@ module.exports = class CourseDetailsView extends RootView
@noCourseInstanceSelected = false
@loadCourseInstance(courseInstanceID)
onClickProgressLevelCell: (e) ->
return unless @teacherMode or me.isAdmin()
levelID = $(e.currentTarget).data('level-id')
levelSlug = $(e.currentTarget).data('level-slug')
userID = $(e.currentTarget).data('user-id')
return unless levelID and levelSlug and userID
route = @getLevelURL levelSlug
if @userLevelSessionMap[userID]?[levelID]
route += "&session=#{@userLevelSessionMap[userID][levelID].id}&observing=true"
Backbone.Mediator.publish 'router:navigate', {
route: route
viewClass: 'views/play/level/PlayLevelView'
viewArgs: [{supermodel: @supermodel}, levelSlug]
}
onMouseEnterPoint: (e) ->
$('.progress-popup-container').hide()
container = $(e.target).find('.progress-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('.progress-popup-container').hide()
sortMembers: ->
# Progress sort precedence: most completed concepts, most started concepts, most levels, name sort
return unless @campaign and @courseInstance and @memberUserMap
@sortedMembers = @courseInstance.get('members')
switch @memberSort
when "nameDesc"
@sortedMembers.sort (a, b) =>
aName = @memberUserMap[a]?.get('name') ? 'Anoner'
bName = @memberUserMap[b]?.get('name') ? 'Anoner'
bName.localeCompare(aName)
when "progressAsc"
@sortedMembers.sort (a, b) =>
for levelID, level of @campaign.get('levels')
if @userLevelStateMap[a]?[levelID] isnt 'complete' and @userLevelStateMap[b]?[levelID] is 'complete'
return -1
else if @userLevelStateMap[a]?[levelID] is 'complete' and @userLevelStateMap[b]?[levelID] isnt 'complete'
return 1
0
when "progressDesc"
@sortedMembers.sort (a, b) =>
for levelID, level of @campaign.get('levels')
if @userLevelStateMap[a]?[levelID] isnt 'complete' and @userLevelStateMap[b]?[levelID] is 'complete'
return 1
else if @userLevelStateMap[a]?[levelID] is 'complete' and @userLevelStateMap[b]?[levelID] isnt 'complete'
return -1
0
else
@sortedMembers.sort (a, b) =>
aName = @memberUserMap[a]?.get('name') ? 'Anoner'
bName = @memberUserMap[b]?.get('name') ? 'Anoner'
aName.localeCompare(bName)
getOwnerName: ->
return if @owner.isNew()
if @owner.get('firstName') and @owner.get('lastName')