diff --git a/app/locale/pt-BR.coffee b/app/locale/pt-BR.coffee index 894744ce8..f70030543 100644 --- a/app/locale/pt-BR.coffee +++ b/app/locale/pt-BR.coffee @@ -1347,7 +1347,7 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription: no_changes: "Sem Alterações" temp: - ace_of_coders_tournament: "Novo: jogue no torneir Era dos Programadores agora!" + ace_of_coders_tournament: "Novo: jogue no torneio Ás dos Programadores agora!" multiplayer: multiplayer_title: "Configurações de Multijogador" # We'll be changing this around significantly soon. Until then, it's not important to translate. diff --git a/app/styles/courses/courses.sass b/app/styles/courses/courses.sass index 30c9d4192..390d0fcea 100644 --- a/app/styles/courses/courses.sass +++ b/app/styles/courses/courses.sass @@ -1,5 +1,25 @@ #courses-view + .logged_out + font-size: 24px + + .signup-button + background: red + color: white + font-size: 18px + font-variant: small-caps + line-height: 27px + text-transform: uppercase + margin-right: 20px + + .login-button + background: white + color: black + font-size: 18px + font-variant: small-caps + line-height: 27px + text-transform: uppercase + .center text-align: center diff --git a/app/templates/courses/courses.jade b/app/templates/courses/courses.jade index 425585e4b..8fa7f8960 100644 --- a/app/templates/courses/courses.jade +++ b/app/templates/courses/courses.jade @@ -10,7 +10,9 @@ block content if state === 'enrolling' .alert.alert-info Enrolling in course.. else if state === 'ppc_logged_out' - .alert.alert-success Log in or create an account to join this course. + .alert.alert-danger.logged_out Create account or log in to join this course. + button.btn.btn-sm.btn-primary.header-font.signup-button(data-i18n="login.sign_up") + button.btn.btn-sm.btn-default.header-font.login-button(data-i18n="login.log_in") else if state === 'unknown_error' .alert.alert-danger.alert-dismissible= stateMessage @@ -36,7 +38,7 @@ block content - i++ mixin hoc-landing - h1.center Welcome Hour of Code! + h1.center Welcome to CodeCombat's Hour of Code! br .container-fluid .row @@ -51,7 +53,7 @@ mixin student-main mixin teacher-hoc button.btn.btn-warning.btn-student(data-i18n="courses.students_click") - h1.center Welcome Hour of Code! + h1.center Welcome to CodeCombat's Hour of Code! p strong How to use CodeCombat with your students: ol diff --git a/app/templates/home-view.jade b/app/templates/home-view.jade index 6b68e131b..86b57617c 100644 --- a/app/templates/home-view.jade +++ b/app/templates/home-view.jade @@ -36,11 +36,11 @@ block outer_content block extra_footer_content - if explainHourOfCode + if explainsHourOfCode // Does not show up unless lang is en-US. div.hour-of-code-explanation | The 'Hour of Code' is a nationwide initiative by a(href="http://csedweek.org") Computer Science Education Week | and a(href="http://code.org") Code.org - | to introduce millions of students to one hour of computer science and computer programming. \ No newline at end of file + | to introduce millions of students to one hour of computer science and computer programming. diff --git a/app/views/HomeView.coffee b/app/views/HomeView.coffee index 48410c487..3a3208754 100644 --- a/app/views/HomeView.coffee +++ b/app/views/HomeView.coffee @@ -46,6 +46,8 @@ module.exports = class HomeView extends RootView @$el.addClass 'hour-of-code' if @explainsHourOfCode setUpHourOfCode: -> + # All this HoC stuff is for the 2014-2015 year. 2015-2016 year lands at /hoc instead (the courses view). + # TODO: get rid of all this sometime in November 2015 when code.org/learn updates to the new version for Hour of Code tutorials. elapsed = (new Date() - new Date(me.get('dateCreated'))) if elapsed < 5 * 60 * 1000 me.set 'hourOfCode', true diff --git a/app/views/courses/CourseDetailsView.coffee b/app/views/courses/CourseDetailsView.coffee index fcaae13a4..384195746 100644 --- a/app/views/courses/CourseDetailsView.coffee +++ b/app/views/courses/CourseDetailsView.coffee @@ -9,6 +9,8 @@ User = require 'models/User' utils = require 'core/utils' Prepaid = require 'models/Prepaid' +autoplayedOnce = false + module.exports = class CourseDetailsView extends RootView id: 'course-details-view' template: template @@ -64,7 +66,7 @@ module.exports = class CourseDetailsView extends RootView onCourseSync: -> # console.log 'onCourseSync' - if me.isAnonymous() + if me.isAnonymous() and not me.get('hourOfCode') @noCourseInstance = true @render?() return @@ -117,7 +119,7 @@ module.exports = class CourseDetailsView extends RootView onCourseInstanceSync: -> # console.log 'onCourseInstanceSync' - @adminMode = true if @courseInstance.get('ownerID') is me.id + @adminMode = true if @courseInstance.get('ownerID') is me.id and @courseInstance.get('name') isnt 'Single Player' @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 @@ -180,6 +182,11 @@ module.exports = class CourseDetailsView extends RootView @conceptsCompleted[concept]++ @render?() + # If we just joined a single-player course for Hour of Code, we automatically play. + if @instanceStats.totalLevelsCompleted is 0 and @instanceStats.totalPlayTime is 0 and @courseInstance.get('members').length is 1 and me.get('hourOfCode') and not @adminMode and not autoplayedOnce + autoplayedOnce = true + @$el.find('button.btn-play-level').click() + onMembersSync: -> # console.log 'onMembersSync' @memberUserMap = {} diff --git a/app/views/courses/CoursesView.coffee b/app/views/courses/CoursesView.coffee index 2ebd5a53c..b69bd8148 100644 --- a/app/views/courses/CoursesView.coffee +++ b/app/views/courses/CoursesView.coffee @@ -17,15 +17,14 @@ module.exports = class CoursesView extends RootView 'click .btn-buy': 'onClickBuy' 'click .btn-enroll': 'onClickEnroll' 'click .btn-enter': 'onClickEnter' - 'click .btn-hoc-student-continue': 'onClickHocStudentContinue' + 'click .btn-hoc-student-continue': 'onClickHOCStudentContinue' 'click .btn-student': 'onClickStudent' 'click .btn-teacher': 'onClickTeacher' constructor: (options) -> super(options) + @setUpHourOfCode() @praise = utils.getCoursePraise() - @hocLandingPage = Backbone.history.getFragment()?.indexOf('hoc') >= 0 - @hocMode = utils.getQueryVariable('hoc', false) @studentMode = Backbone.history.getFragment()?.indexOf('courses/students') >= 0 @courses = new CocoCollection([], { url: "/db/course", model: Course}) @supermodel.loadCollection(@courses, 'courses') @@ -39,6 +38,21 @@ module.exports = class CoursesView extends RootView @studentMode = true @courseEnroll(prepaidCode) + setUpHourOfCode: -> + # If we are coming in at /hoc, then we show the landing page. + # If we have ?hoc=true (for the step after the landing page), then we show any HoC-specific instructions. + # If we haven't tracked this player as an hourOfCode player yet, and it's a new account, we do that now. + @hocLandingPage = Backbone.history.getFragment()?.indexOf('hoc') >= 0 + @hocMode = utils.getQueryVariable('hoc', false) + elapsed = new Date() - new Date(me.get('dateCreated')) + if not me.get('hourOfCode') and (@hocLandingPage or @hocMode) and elapsed < 5 * 60 * 1000 + 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' + if me.get('hourOfCode') and elapsed < 24 * 60 * 60 * 1000 + @hocMode = true # If they really just arrived, make sure we're still in hocMode even if they lost ?hoc=true. + getRenderData: -> context = super() context.courses = @courses.models ? [] @@ -102,10 +116,12 @@ module.exports = class CoursesView extends RootView navigationEvent = route: route, viewClass: viewClass, viewArgs: viewArgs Backbone.Mediator.publish 'router:navigate', navigationEvent - onClickHocStudentContinue: (e) -> + onClickHOCStudentContinue: (e) -> $('.continue-dialog').modal('hide') - return @openModalView new AuthModal() if me.isAnonymous() - courseID = $(e.target).data('course-id') + if e + courseID = $(e.target).data('course-id') + else + courseID = '560f1a9f22961295f9427742' @state = 'enrolling' @stateMessage = undefined @@ -117,6 +133,7 @@ module.exports = class CoursesView extends RootView name: 'Single Player' seats: 9999 courseID: courseID + hourOfCode: true jqxhr = $.post('/db/course_instance/-/create', data) jqxhr.done (data, textStatus, jqXHR) => application.tracker?.trackEvent 'Finished HoC student course creation', {courseID: courseID} @@ -145,6 +162,10 @@ module.exports = class CoursesView extends RootView @render?() onClickStudent: (e) -> + if @supermodel.finished() and @hocLandingPage + # Automatically enroll in first course + @onClickHOCStudentContinue() + return route = "/courses/students" route += "?hoc=true" if @hocLandingPage or @hocMode viewClass = require 'views/courses/CoursesView' diff --git a/app/views/play/CampaignView.coffee b/app/views/play/CampaignView.coffee index d74e22041..23e821347 100644 --- a/app/views/play/CampaignView.coffee +++ b/app/views/play/CampaignView.coffee @@ -106,6 +106,7 @@ module.exports = class CampaignView extends RootView window.tracker?.trackEvent 'Loaded World Map', category: 'World Map', label: @terrain # If it's a new player who didn't appear to come from Hour of Code, we register her here without setting the hourOfCode property. + # TODO: get rid of all this sometime in November 2015 when code.org/learn updates to the new version for Hour of Code tutorials. elapsed = (new Date() - new Date(me.get('dateCreated'))) if not trackedHourOfCode and not me.get('hourOfCode') and elapsed < 5 * 60 * 1000 $('body').append($('<img src="https://code.org/api/hour/begin_codecombat.png" style="visibility: hidden;">')) diff --git a/app/views/play/level/modal/HeroVictoryModal.coffee b/app/views/play/level/modal/HeroVictoryModal.coffee index db572661a..bb8d7c865 100644 --- a/app/views/play/level/modal/HeroVictoryModal.coffee +++ b/app/views/play/level/modal/HeroVictoryModal.coffee @@ -189,11 +189,13 @@ module.exports = class HeroVictoryModal extends ModalView elapsed = (new Date() - new Date(me.get('dateCreated'))) isHourOfCode = me.get('hourOfCode') or elapsed < 120 * 60 * 1000 # Later we should only check me.get('hourOfCode'), but for now so much traffic comes in that we just assume it. + # TODO: get rid of said assumption sometime in November 2015 when code.org/learn updates to the new version for Hour of Code tutorials. if isHourOfCode # Show the Hour of Code "I'm Done" tracking pixel after they played for 20 minutes - enough = elapsed >= 20 * 60 * 1000 + lastLevel = @level.get('slug') is 'course-kithgard-gates' + enough = elapsed >= 20 * 60 * 1000 or lastLevel tooMuch = elapsed > 120 * 60 * 1000 - showDone = elapsed >= 30 * 60 * 1000 and not tooMuch + showDone = (elapsed >= 30 * 60 * 1000 and not tooMuch) or lastLevel if enough and not tooMuch and not me.get('hourOfCodeComplete') $('body').append($('<img src="http://code.org/api/hour/finish_codecombat.png" style="visibility: hidden;">')) me.set 'hourOfCodeComplete', true # Note that this will track even for players who don't have hourOfCode set. diff --git a/server/courses/course_instance_handler.coffee b/server/courses/course_instance_handler.coffee index 7ae5dcf52..96d139a82 100644 --- a/server/courses/course_instance_handler.coffee +++ b/server/courses/course_instance_handler.coffee @@ -38,7 +38,8 @@ CourseInstanceHandler = class CourseInstanceHandler extends Handler super arguments... createAPI: (req, res) -> - return @sendUnauthorizedError(res) if not req.user? or req.user?.isAnonymous() + return @sendUnauthorizedError(res) if not req.user? + return @sendUnauthorizedError(res) if req.user.isAnonymous() and not (req.body.hourOfCode and req.body.courseID is '560f1a9f22961295f9427742') # Required Input seats = req.body.seats