diff --git a/app/lib/LevelSetupManager.coffee b/app/lib/LevelSetupManager.coffee index 5e4fb6ad9..e5acfdbd1 100644 --- a/app/lib/LevelSetupManager.coffee +++ b/app/lib/LevelSetupManager.coffee @@ -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 diff --git a/app/styles/courses/mock1/course-details.sass b/app/styles/courses/mock1/course-details.sass deleted file mode 100644 index 2f909406f..000000000 --- a/app/styles/courses/mock1/course-details.sass +++ /dev/null @@ -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% diff --git a/app/styles/courses/mock1/course-enroll.sass b/app/styles/courses/mock1/course-enroll.sass deleted file mode 100644 index a8ddddc4f..000000000 --- a/app/styles/courses/mock1/course-enroll.sass +++ /dev/null @@ -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 diff --git a/app/styles/courses/mock1/course-info.sass b/app/styles/courses/mock1/course-info.sass deleted file mode 100644 index b518f0de1..000000000 --- a/app/styles/courses/mock1/course-info.sass +++ /dev/null @@ -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 diff --git a/app/styles/courses/mock1/courses.sass b/app/styles/courses/mock1/courses.sass deleted file mode 100644 index 20457a5fe..000000000 --- a/app/styles/courses/mock1/courses.sass +++ /dev/null @@ -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 diff --git a/app/styles/courses/mock1/purchase-courses-view.sass b/app/styles/courses/mock1/purchase-courses-view.sass deleted file mode 100644 index aaf4ce96d..000000000 --- a/app/styles/courses/mock1/purchase-courses-view.sass +++ /dev/null @@ -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 diff --git a/app/templates/courses/classroom-level-popover.jade b/app/templates/courses/classroom-level-popover.jade index 02e143780..f80389ad6 100644 --- a/app/templates/courses/classroom-level-popover.jade +++ b/app/templates/courses/classroom-level-popover.jade @@ -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 diff --git a/app/templates/courses/classroom-view.jade b/app/templates/courses/classroom-view.jade index 65f4df653..e088e7815 100644 --- a/app/templates/courses/classroom-view.jade +++ b/app/templates/courses/classroom-view.jade @@ -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 diff --git a/app/templates/courses/course-details.jade b/app/templates/courses/course-details.jade index d246e232d..fe6a3a6bf 100644 --- a/app/templates/courses/course-details.jade +++ b/app/templates/courses/course-details.jade @@ -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 diff --git a/app/templates/courses/hour-of-code-view.jade b/app/templates/courses/hour-of-code-view.jade index 0bdeb07e8..1ed8bcfef 100644 --- a/app/templates/courses/hour-of-code-view.jade +++ b/app/templates/courses/hour-of-code-view.jade @@ -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: diff --git a/app/templates/courses/mock1/course-details.jade b/app/templates/courses/mock1/course-details.jade deleted file mode 100644 index eabf11070..000000000 --- a/app/templates/courses/mock1/course-details.jade +++ /dev/null @@ -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) diff --git a/app/templates/courses/mock1/course-enroll.jade b/app/templates/courses/mock1/course-enroll.jade deleted file mode 100644 index aeca1df5a..000000000 --- a/app/templates/courses/mock1/course-enroll.jade +++ /dev/null @@ -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 diff --git a/app/templates/courses/mock1/course-info.jade b/app/templates/courses/mock1/course-info.jade deleted file mode 100644 index ffecb75df..000000000 --- a/app/templates/courses/mock1/course-info.jade +++ /dev/null @@ -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 diff --git a/app/templates/courses/mock1/courses.jade b/app/templates/courses/mock1/courses.jade deleted file mode 100644 index f26304e00..000000000 --- a/app/templates/courses/mock1/courses.jade +++ /dev/null @@ -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 diff --git a/app/templates/play/campaign-view.jade b/app/templates/play/campaign-view.jade index abb280818..21becd68e 100644 --- a/app/templates/play/campaign-view.jade +++ b/app/templates/play/campaign-view.jade @@ -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)" : "")) diff --git a/app/views/HomeView.coffee b/app/views/HomeView.coffee index 2fd1f1af5..377202c93 100644 --- a/app/views/HomeView.coffee +++ b/app/views/HomeView.coffee @@ -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') diff --git a/app/views/account/SubscriptionView.coffee b/app/views/account/SubscriptionView.coffee index de796e5e2..41ace9c30 100644 --- a/app/views/account/SubscriptionView.coffee +++ b/app/views/account/SubscriptionView.coffee @@ -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) diff --git a/app/views/core/AuthModal.coffee b/app/views/core/AuthModal.coffee index 10494a36f..e7e73f0e7 100644 --- a/app/views/core/AuthModal.coffee +++ b/app/views/core/AuthModal.coffee @@ -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 diff --git a/app/views/courses/ChooseLanguageModal.coffee b/app/views/courses/ChooseLanguageModal.coffee index 7d8fe3f26..549582105 100644 --- a/app/views/courses/ChooseLanguageModal.coffee +++ b/app/views/courses/ChooseLanguageModal.coffee @@ -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() diff --git a/app/views/courses/ClassroomView.coffee b/app/views/courses/ClassroomView.coffee index 7bfafc27b..011bcfac5 100644 --- a/app/views/courses/ClassroomView.coffee +++ b/app/views/courses/ClassroomView.coffee @@ -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" \ No newline at end of file + "/play/level/#{level.slug}?course=#{course.id}&course-instance=#{courseInstance.id}&session=#{session.id}&observing=true" diff --git a/app/views/courses/HourOfCodeView.coffee b/app/views/courses/HourOfCodeView.coffee index e40daf131..5cd213da4 100644 --- a/app/views/courses/HourOfCodeView.coffee +++ b/app/views/courses/HourOfCodeView.coffee @@ -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() diff --git a/app/views/courses/StudentLogInModal.coffee b/app/views/courses/StudentLogInModal.coffee index 5baf986f6..ffebbab8e 100644 --- a/app/views/courses/StudentLogInModal.coffee +++ b/app/views/courses/StudentLogInModal.coffee @@ -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 \ No newline at end of file + _.delay (=> @$('input:visible:first').focus()), 500 diff --git a/app/views/courses/StudentSignUpModal.coffee b/app/views/courses/StudentSignUpModal.coffee index 0c25cb030..3b7c03e12 100644 --- a/app/views/courses/StudentSignUpModal.coffee +++ b/app/views/courses/StudentSignUpModal.coffee @@ -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 diff --git a/app/views/courses/mock1/CourseDetailsView.coffee b/app/views/courses/mock1/CourseDetailsView.coffee deleted file mode 100644 index 90006c2c0..000000000 --- a/app/views/courses/mock1/CourseDetailsView.coffee +++ /dev/null @@ -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() diff --git a/app/views/courses/mock1/CourseEnrollView.coffee b/app/views/courses/mock1/CourseEnrollView.coffee deleted file mode 100644 index 139e878d2..000000000 --- a/app/views/courses/mock1/CourseEnrollView.coffee +++ /dev/null @@ -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 diff --git a/app/views/courses/mock1/CourseInfoView.coffee b/app/views/courses/mock1/CourseInfoView.coffee deleted file mode 100644 index e047c0883..000000000 --- a/app/views/courses/mock1/CourseInfoView.coffee +++ /dev/null @@ -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() diff --git a/app/views/courses/mock1/CoursesMockData.coffee b/app/views/courses/mock1/CoursesMockData.coffee deleted file mode 100644 index 26d58fd47..000000000 --- a/app/views/courses/mock1/CoursesMockData.coffee +++ /dev/null @@ -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 diff --git a/app/views/courses/mock1/CoursesView.coffee b/app/views/courses/mock1/CoursesView.coffee deleted file mode 100644 index 7a498d0c3..000000000 --- a/app/views/courses/mock1/CoursesView.coffee +++ /dev/null @@ -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='$('.courses-faq').popover('hide');'>×</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() diff --git a/app/views/play/CampaignView.coffee b/app/views/play/CampaignView.coffee index 792b7a15a..4492a23a7 100644 --- a/app/views/play/CampaignView.coffee +++ b/app/views/play/CampaignView.coffee @@ -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') diff --git a/server/classrooms/classroom_handler.coffee b/server/classrooms/classroom_handler.coffee index ec45aaf7d..10df981a9 100644 --- a/server/classrooms/classroom_handler.coffee +++ b/server/classrooms/classroom_handler.coffee @@ -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