mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-23 23:58:02 -05:00
Update course details page
This commit is contained in:
parent
921fe7f807
commit
b68e5e209b
5 changed files with 593 additions and 39 deletions
|
@ -1,7 +1,142 @@
|
|||
#course-details-view
|
||||
|
||||
.edit-description-input
|
||||
.invite-emails
|
||||
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: lightgray
|
||||
|
||||
.progress-concept-cell-started
|
||||
background-color: lightgreen
|
||||
|
||||
.progress-concept-completion-container
|
||||
font-size: 10pt
|
||||
|
||||
.progress-concepts-label
|
||||
color: #317EAC
|
||||
font-size: 12pt
|
||||
font-weight: bold
|
||||
margin-top: 8px
|
||||
margin-bottom: 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%
|
||||
|
||||
.edit-name-input
|
||||
.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: lightgray
|
||||
|
||||
.progress-key-started
|
||||
background-color: lightgreen
|
||||
|
||||
.progress-expand-checkbox
|
||||
margin-left: 14px
|
||||
|
||||
.progress-expand-label
|
||||
font-weight: normal
|
||||
font-size: 14px
|
||||
|
||||
.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-complete
|
||||
cursor: pointer
|
||||
background-color: lightgray
|
||||
|
||||
.progress-level-cell-started
|
||||
cursor: pointer
|
||||
background-color: lightgreen
|
||||
|
||||
.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%
|
||||
|
|
|
@ -9,23 +9,254 @@ block content
|
|||
a.spl(href='mailto:team@codecombat.com') team@codecombat.com
|
||||
div(style='border-bottom: 1px solid black;')
|
||||
|
||||
h1(style='text-align: center;') Course
|
||||
if course
|
||||
div= course.get('name')
|
||||
div= course.get('description')
|
||||
div= course.get('campaignID')
|
||||
div= course.get('concepts')
|
||||
if me.isAnonymous()
|
||||
h1 TODO: logged out
|
||||
else if !course || !courseInstance
|
||||
h1 Loading...
|
||||
else
|
||||
div No course found.
|
||||
h1= courseInstance.get('name') || 'Unnamed Class'
|
||||
small.spl (#{course.get('name')})
|
||||
|
||||
h1(style='text-align: center;') Class
|
||||
if courseInstance
|
||||
p
|
||||
div= courseInstance.get('name') || 'Class Name'
|
||||
div= courseInstance.get('description')
|
||||
div= courseInstance.get('courseID')
|
||||
div= courseInstance.get('ownerID')
|
||||
div= courseInstance.get('members')
|
||||
div= courseInstance.get('prepaidID')
|
||||
else
|
||||
p No classes found.
|
||||
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}
|
||||
tr
|
||||
td Average level play time:
|
||||
td TODO
|
||||
tr
|
||||
td Total play time:
|
||||
td TODO
|
||||
tr
|
||||
td Average levels completed:
|
||||
td TODO
|
||||
tr
|
||||
td Total levels completed:
|
||||
td TODO
|
||||
tr
|
||||
td Furthest level completed:
|
||||
td TODO
|
||||
|
||||
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'
|
||||
div TODO: levels completed
|
||||
div TODO: total time played
|
||||
div TODO: last played
|
||||
|
||||
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 #{i + 1}
|
||||
span.spl= level.name.replace('Course: ', '')
|
||||
+progress-members-popup-completed(i, level)
|
||||
else if userLevelStateMap[memberID][levelID] === 'started'
|
||||
span.progress-level-cell.progress-level-cell-started #{i + 1} #{level.name.replace('Course: ', '')}
|
||||
+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'
|
||||
span.progress-level-cell.progress-level-cell-complete(style="width:#{levelCellWidth}%;") #{i + 1}
|
||||
+progress-members-popup-completed(i, level)
|
||||
else if userLevelStateMap[memberID][levelID] === 'started'
|
||||
span.progress-level-cell.progress-level-cell-started(style="width:#{levelCellWidth}%;") #{i + 1}
|
||||
+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: ', '')}
|
||||
p TODO: Time to solve
|
||||
p TODO: Completed on
|
||||
strong Click to view solution.
|
||||
|
||||
mixin progress-members-popup-started(i, level)
|
||||
.progress-popup-container
|
||||
h3 #{i + 1}. #{level.name.replace('Course: ', '')}
|
||||
p TODO: last played on
|
||||
strong Click to view solution.
|
||||
|
||||
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")
|
||||
|
|
|
@ -51,7 +51,6 @@ module.exports = class ClanDetailsView extends RootView
|
|||
@clan = new Clan _id: @clanID
|
||||
@members = new CocoCollection([], { url: "/db/clan/#{@clanID}/members", model: User, comparator: 'nameLower' })
|
||||
@memberAchievements = new CocoCollection([], { url: "/db/clan/#{@clanID}/member_achievements", model: EarnedAchievement, comparator:'_id' })
|
||||
# MemberSessions: only loads creatorName, levelName, codeLanguage, submittedCodeLanguage for each session
|
||||
@memberSessions = new CocoCollection([], { url: "/db/clan/#{@clanID}/member_sessions", model: LevelSession, comparator:'_id' })
|
||||
|
||||
@listenTo me, 'sync', => @render?()
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
RootView = require 'views/core/RootView'
|
||||
template = require 'templates/courses/course-details'
|
||||
Campaign = require 'models/Campaign'
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
Course = require 'models/Course'
|
||||
CourseInstance = require 'models/CourseInstance'
|
||||
LevelSession = require 'models/LevelSession'
|
||||
RootView = require 'views/core/RootView'
|
||||
template = require 'templates/courses/course-details'
|
||||
User = require 'models/User'
|
||||
utils = require 'core/utils'
|
||||
|
||||
# TODO: logged out experience
|
||||
# TODO: no course instances
|
||||
|
@ -12,27 +16,186 @@ module.exports = class CourseDetailsView extends RootView
|
|||
id: 'course-details-view'
|
||||
template: template
|
||||
|
||||
events:
|
||||
'change .progress-expand-checkbox': 'onCheckExpandedProgress'
|
||||
'click .btn-play-level': 'onClickPlayLevel'
|
||||
'click .btn-save-settings': 'onClickSaveSettings'
|
||||
'click .progress-member-header': 'onClickMemberHeader'
|
||||
'click .progress-header': 'onClickProgressHeader'
|
||||
'mouseenter .progress-level-cell': 'onMouseEnterPoint'
|
||||
'mouseleave .progress-level-cell': 'onMouseLeavePoint'
|
||||
|
||||
constructor: (options, @courseID) ->
|
||||
super options
|
||||
@courseInstanceID = options.courseInstanceID
|
||||
@course = new Course _id: @courseID
|
||||
@supermodel.loadModel @course, 'course', cache: false
|
||||
if @courseInstanceID
|
||||
@courseInstance = new CourseInstance _id: @courseInstanceID
|
||||
@supermodel.loadModel @courseInstance, 'course_instance', cache: false
|
||||
else if !me.isAnonymous()
|
||||
@courseInstances = new CocoCollection([], { url: "/db/user/#{me.id}/course_instances", model: CourseInstance})
|
||||
@listenToOnce @courseInstances, 'sync', @onCourseInstancesLoaded
|
||||
@supermodel.loadCollection(@courseInstances, 'course_instances')
|
||||
@courseInstanceID = utils.getQueryVariable('ciid', false) or options.courseInstanceID
|
||||
@adminMode = me.isAdmin()
|
||||
@memberSort = 'nameAsc'
|
||||
unless me.isAnonymous()
|
||||
@course = new Course _id: @courseID
|
||||
@listenTo @course, 'sync', @onCourseSync
|
||||
@supermodel.loadModel @course, 'course', cache: false
|
||||
|
||||
getRenderData: ->
|
||||
context = super()
|
||||
context.course = @course
|
||||
context.courseInstance = @courseInstance
|
||||
context.adminMode = @adminMode ? false
|
||||
context.campaign = @campaign
|
||||
context.conceptsCompleted = @conceptsCompleted ? {}
|
||||
context.course = @course if @course?.loaded
|
||||
context.courseInstance = @courseInstance if @courseInstance?.loaded
|
||||
context.levelConceptMap = @levelConceptMap ? {}
|
||||
context.memberSort = @memberSort
|
||||
context.memberUserMap = @memberUserMap ? {}
|
||||
context.showExpandedProgress = @showExpandedProgress
|
||||
context.sortedMembers = @sortedMembers ? []
|
||||
context.userConceptStateMap = @userConceptStateMap ? {}
|
||||
context.userLevelStateMap = @userLevelStateMap ? {}
|
||||
context
|
||||
|
||||
onCourseInstancesLoaded: ->
|
||||
onCourseSync: ->
|
||||
# console.log 'onCourseSync'
|
||||
return if @campaign?
|
||||
@campaign = new Campaign _id: @course.get('campaignID')
|
||||
@listenTo @campaign, 'sync', @onCampaignSync
|
||||
@supermodel.loadModel @campaign, 'campaign', cache: false
|
||||
@render?()
|
||||
|
||||
onCampaignSync: ->
|
||||
# console.log 'onCampaignSync'
|
||||
if @courseInstanceID
|
||||
@loadCourseInstance(@courseInstanceID)
|
||||
else if !me.isAnonymous()
|
||||
@courseInstances = new CocoCollection([], { url: "/db/user/#{me.id}/course_instances", model: CourseInstance})
|
||||
@listenToOnce @courseInstances, 'sync', @onCourseInstancesSync
|
||||
@supermodel.loadCollection(@courseInstances, 'course_instances')
|
||||
@levelConceptMap = {}
|
||||
for levelID, level of @campaign.get('levels')
|
||||
@levelConceptMap[levelID] ?= {}
|
||||
for concept in level.concepts
|
||||
@levelConceptMap[levelID][concept] = true
|
||||
@render?()
|
||||
|
||||
loadCourseInstance: (courseInstanceID) ->
|
||||
# console.log 'loadCourseInstance'
|
||||
return if @courseInstance?
|
||||
@courseInstance = new CourseInstance _id: courseInstanceID
|
||||
@listenTo @courseInstance, 'sync', @onCourseInstanceSync
|
||||
@supermodel.loadModel @courseInstance, 'course_instance', cache: false
|
||||
|
||||
onCourseInstancesSync: ->
|
||||
# console.log 'onCourseInstancesSync'
|
||||
if @courseInstances.models.length is 1
|
||||
@courseInstance = @courseInstances.models[0]
|
||||
@loadCourseInstance(@courseInstances.models[0].id)
|
||||
else if @courseInstances.models.length > 0
|
||||
@courseInstance = @courseInstances.models[0]
|
||||
@loadCourseInstance(@courseInstances.models[0].id)
|
||||
|
||||
onCourseInstanceSync: ->
|
||||
console.log 'onCourseInstanceSync', @courseInstance.get('description')
|
||||
@adminMode = true if @courseInstance.get('ownerID') is me.id
|
||||
@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
|
||||
@render?()
|
||||
|
||||
onLevelSessionsSync: ->
|
||||
# console.log 'onLevelSessionsSync'
|
||||
@userConceptStateMap = {}
|
||||
@userLevelStateMap = {}
|
||||
for levelSession in @levelSessions.models
|
||||
userID = levelSession.get('creator')
|
||||
levelID = levelSession.get('level').original
|
||||
@userConceptStateMap[userID] ?= {}
|
||||
@userLevelStateMap[userID] ?= {}
|
||||
state = if levelSession.get('state')?.complete then 'complete' else 'started'
|
||||
@userLevelStateMap[userID][levelID] = state
|
||||
for concept of @levelConceptMap[levelID]
|
||||
@userConceptStateMap[userID][concept] = state
|
||||
@conceptsCompleted = {}
|
||||
for userID, conceptStateMap of @userConceptStateMap
|
||||
for concept, state of conceptStateMap
|
||||
@conceptsCompleted[concept] ?= 0
|
||||
@conceptsCompleted[concept]++
|
||||
@render?()
|
||||
|
||||
onMembersSync: ->
|
||||
# console.log 'onMembersSync'
|
||||
@memberUserMap = {}
|
||||
for user in @members.models
|
||||
@memberUserMap[user.id] = user
|
||||
@sortMembers()
|
||||
@render?()
|
||||
|
||||
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')
|
||||
Backbone.Mediator.publish 'router:navigate', {
|
||||
route: "/play/level/#{levelSlug}"
|
||||
viewClass: 'views/play/level/PlayLevelView'
|
||||
viewArgs: [{}, levelSlug]
|
||||
}
|
||||
|
||||
onClickSaveSettings: (e) ->
|
||||
return unless @courseInstance
|
||||
if name = $('.settings-name-input').val()
|
||||
@courseInstance.set('name', name)
|
||||
description = $('.settings-description-input').val()
|
||||
console.log 'onClickSaveSettings', description
|
||||
@courseInstance.set('description', description)
|
||||
@courseInstance.patch()
|
||||
$('#settingsModal').modal('hide')
|
||||
|
||||
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()
|
||||
|
||||
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) => @memberUserMap[b]?.get('name').localeCompare(@memberUserMap[a]?.get('name'))
|
||||
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) => @memberUserMap[a]?.get('name').localeCompare(@memberUserMap[b]?.get('name'))
|
||||
|
|
|
@ -3,7 +3,11 @@ Handler = require '../commons/Handler'
|
|||
{getCoursesPrice} = require '../../app/core/utils'
|
||||
Course = require './Course'
|
||||
CourseInstance = require './CourseInstance'
|
||||
LevelSession = require '../levels/sessions/LevelSession'
|
||||
LevelSessionHandler = require '../levels/sessions/level_session_handler'
|
||||
Prepaid = require '../prepaids/Prepaid'
|
||||
User = require '../users/User'
|
||||
UserHandler = require '../users/user_handler'
|
||||
|
||||
CourseInstanceHandler = class CourseInstanceHandler extends Handler
|
||||
modelClass: CourseInstance
|
||||
|
@ -14,15 +18,18 @@ CourseInstanceHandler = class CourseInstanceHandler extends Handler
|
|||
console.warn "Course error: #{user.get('slug')} (#{user._id}): '#{msg}'"
|
||||
|
||||
hasAccess: (req) ->
|
||||
req.method is 'GET' or req.user?.isAdmin()
|
||||
req.method in @allowedMethods or req.user?.isAdmin()
|
||||
|
||||
hasAccessToDocument: (req, document, method=null) ->
|
||||
return true if _.find document?.get('members'), (a) -> a.equals(req.user?.get('_id'))
|
||||
return true if document?.get('ownerID')?.equals(req.user?.get('_id'))
|
||||
return true if req.method is 'GET' and _.find document?.get('members'), (a) -> a.equals(req.user?.get('_id'))
|
||||
req.user?.isAdmin()
|
||||
|
||||
getByRelationship: (req, res, args...) ->
|
||||
relationship = args[1]
|
||||
return @createAPI(req, res) if relationship is 'create'
|
||||
return @getLevelSessionsAPI(req, res, args[0]) if args[1] is 'level_sessions'
|
||||
return @getMembersAPI(req, res, args[0]) if args[1] is 'members'
|
||||
super arguments...
|
||||
|
||||
createAPI: (req, res) ->
|
||||
|
@ -94,5 +101,24 @@ CourseInstanceHandler = class CourseInstanceHandler extends Handler
|
|||
Course.find {}, (err, documents) =>
|
||||
done(err, documents)
|
||||
|
||||
getLevelSessionsAPI: (req, res, courseInstanceID) ->
|
||||
CourseInstance.findById courseInstanceID, (err, courseInstance) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendNotFoundError(res) unless courseInstance
|
||||
memberIDs = _.map courseInstance.get('members') ? [], (memberID) -> memberID.toHexString?() or memberID
|
||||
LevelSession.find {creator: {$in: memberIDs}}, (err, documents) =>
|
||||
return @sendDatabaseError(res, err) if err?
|
||||
cleandocs = (LevelSessionHandler.formatEntity(req, doc) for doc in documents)
|
||||
@sendSuccess(res, cleandocs)
|
||||
|
||||
getMembersAPI: (req, res, courseInstanceID) ->
|
||||
CourseInstance.findById courseInstanceID, (err, courseInstance) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendNotFoundError(res) unless courseInstance
|
||||
memberIDs = courseInstance.get('members') ? []
|
||||
User.find {_id: {$in: memberIDs}}, (err, users) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
cleandocs = (UserHandler.formatEntity(req, doc) for doc in users)
|
||||
@sendSuccess(res, cleandocs)
|
||||
|
||||
module.exports = new CourseInstanceHandler()
|
||||
|
|
Loading…
Reference in a new issue