diff --git a/app/models/Campaign.coffee b/app/models/Campaign.coffee index 51436acd7..631c95193 100644 --- a/app/models/Campaign.coffee +++ b/app/models/Campaign.coffee @@ -11,5 +11,26 @@ module.exports = class Campaign extends CocoModel @denormalizedLevelProperties: _.keys(_.omit(schema.properties.levels.additionalProperties.properties, ['unlocks', 'position', 'rewards'])) @denormalizedCampaignProperties: ['name', 'i18n', 'slug'] - levelsCollection: -> - new CocoCollection(_.values(@get('levels')), {model: Level}) + statsForSessions: (sessions) -> + # common code for crunching stats for a user's progress on a campaign/course + stats = {} + sessions = _.sortBy sessions.models, (s) -> s.get('changed') + levels = _.values(@get('levels')) + levels = (level for level in levels when not _.contains(level.type, 'ladder')) + levelOriginals = _.pluck(levels, 'original') + sessionOriginals = (session.get('level').original for session in sessions when session.get('state').complete) + levelsLeft = _.size(_.difference(levelOriginals, sessionOriginals)) + lastSession = _.last(sessions) + stats.levels = { + size: _.size(levels) + left: levelsLeft + done: levelsLeft is 0 + numDone: _.size(levels) - levelsLeft + pctDone: (100 * (_.size(levels) - levelsLeft) / _.size(levels)).toFixed(1) + '%' + lastPlayed: if lastSession then _.findWhere levels, { original: lastSession.get('level').original } else null + first: _.first(levels) + arena: _.find _.values(@get('levels')), (level) -> _.contains(level.type, 'ladder') + } + sum = (nums) -> _.reduce(nums, (s, num) -> s + num) + stats.playtime = sum((session.get('playtime') or 0 for session in sessions)) + return stats \ No newline at end of file diff --git a/app/styles/courses/courses-view.sass b/app/styles/courses/courses-view.sass index 7454f403f..91b46876a 100644 --- a/app/styles/courses/courses-view.sass +++ b/app/styles/courses/courses-view.sass @@ -29,3 +29,11 @@ .course-instance-entry padding-left: 40px + + .progress-bar + min-width: 15% + + .btn + margin-left: 20px + min-width: 180px + \ No newline at end of file diff --git a/app/templates/courses/courses-view.jade b/app/templates/courses/courses-view.jade index 93890b884..3a9d9240c 100644 --- a/app/templates/courses/courses-view.jade +++ b/app/templates/courses/courses-view.jade @@ -28,8 +28,11 @@ block content else - var showHOCComplete = false; - if false && view.hocCourseInstance - - showHOCComplete = view.hocCourseInstance.sessions.allDone(); + if view.hocCourseInstance + - var course = view.courses.get(view.hocCourseInstance.get('courseID')); + - var campaign = view.campaigns.get(course.get('campaignID')); + - var stats = campaign.statsForSessions(view.hocCourseInstance.sessions); + - showHOCComplete = stats.levels.done; .text-center if !showHOCComplete @@ -43,7 +46,7 @@ block content li Learn even more programming! a.btn.btn-lg.btn-success(href="/play") - if false && view.hocCourseInstance + if view.hocCourseInstance && !view.classrooms.size() h3 Saved Games hr @@ -56,7 +59,7 @@ block content +course-instance-body(view.hocCourseInstance) .clearfix - if view.classrooms.size() + else if view.classrooms.size() h3.text-uppercase My Classes hr @@ -111,29 +114,43 @@ block content mixin course-instance-body(courseInstance) - var course = view.courses.get(courseInstance.get('courseID')); - var campaign = view.campaigns.get(course.get('campaignID')); - - var levels = campaign.levelsCollection(); - - var complete = view.isCampaignComplete(campaign, courseInstance.sessions); - if complete + - var stats = campaign.statsForSessions(courseInstance.sessions); + if stats.levels.done .text-success span.glyphicon.glyphicon-ok span.spl Course Complete! .pull-right - if complete - - var arenaLevel = levels.findWhere({ type: 'course-ladder' }); + if stats.levels.done + - var arenaLevel = stats.levels.arena; if arenaLevel - - var arenaURL = "/play/ladder/"+arenaLevel.get('slug')+"/course/"+courseInstance.id; + - var arenaURL = "/play/ladder/"+arenaLevel.slug+"/course/"+courseInstance.id; a.btn.btn-warning.btn-lg(href=arenaURL) | Play Arena else a.btn.btn-default.btn-lg(disabled=true) Course Complete else if courseInstance.sessions.size() - - var lastSession = courseInstance.sessions.last(); - - var lastLevel = levels.findWhere({original: lastSession.get('level').original}); - - var levelURL = "/play/level/"+lastLevel.get('slug')+"?course="+courseInstance.get('courseID')+"&course-instance="+courseInstance.id; + - var lastLevel = stats.levels.lastPlayed; + - var levelURL = "/play/level/"+lastLevel.slug+"?course="+courseInstance.get('courseID')+"&course-instance="+courseInstance.id; a.btn.btn-success.btn-lg(href=levelURL) | Continue else - - var firstLevel = levels.first(); - - var levelURL = "/play/level/"+firstLevel.get('slug')+"?course="+courseInstance.get('courseID')+"&course-instance="+courseInstance.id; + - var firstLevel = stats.levels.first; + - var levelURL = "/play/level/"+firstLevel.slug+"?course="+courseInstance.get('courseID')+"&course-instance="+courseInstance.id; a.btn.btn-info.btn-lg(href=levelURL) | Start + + div + span Playtime + span.spr : + span= moment.duration(stats.playtime, 'seconds').humanize() + + if stats.levels.lastPlayed + div + span Last Level + span.spr : + span= stats.levels.lastPlayed.name + + .progress + .progress-bar(style="width:"+stats.levels.pctDone)= stats.levels.pctDone + + \ No newline at end of file diff --git a/app/views/courses/CoursesView.coffee b/app/views/courses/CoursesView.coffee index 121d5c362..2bb8236c0 100644 --- a/app/views/courses/CoursesView.coffee +++ b/app/views/courses/CoursesView.coffee @@ -49,19 +49,12 @@ module.exports = class CoursesView extends RootView model: LevelSession }) courseInstance.sessions.comparator = 'changed' - @supermodel.loadCollection(courseInstance.sessions, 'sessions', { data: { project: 'state.complete level.original' }}) + @supermodel.loadCollection(courseInstance.sessions, 'sessions', { data: { project: 'state.complete level.original playtime changed' }}) @hocCourseInstance = @courseInstances.findWhere({hourOfCode: true}) if @hocCourseInstance @courseInstances.remove(@hocCourseInstance) - isCampaignComplete: (campaign, sessions) -> - levels = _.values(campaign.get('levels')) - levels = (level for level in levels when not _.contains(level.type, 'ladder')) - levelOriginals = _.pluck(levels, 'original') - sessionOriginals = (session.get('level').original for session in sessions.models) - return _.size(_.difference(levelOriginals, sessionOriginals)) is 0 - onClickStartNewGameButton: -> @openSignUpModal()