From 2d2a9ad681d112c29be57bc84d67523d3de12dec Mon Sep 17 00:00:00 2001 From: Nick Winter Date: Thu, 27 Nov 2014 09:44:08 -0800 Subject: [PATCH] Updated Hour of Code visitor counting. --- app/Router.coffee | 4 +-- app/styles/home.sass | 12 +++++++ .../play/level/modal/hero-victory-modal.sass | 17 ++++++++++ app/templates/base.jade | 2 ++ app/templates/home.jade | 11 +++++- app/templates/play/level.jade | 8 ----- .../play/level/modal/hero-victory-modal.jade | 9 +++++ app/views/FrontView.coffee | 34 ------------------- app/views/HomeView.coffee | 17 ++++++++++ app/views/play/WorldMapView.coffee | 8 +++++ app/views/play/level/PlayLevelView.coffee | 20 +++-------- .../play/level/modal/HeroVictoryModal.coffee | 19 ++++++++++- 12 files changed, 98 insertions(+), 63 deletions(-) delete mode 100644 app/views/FrontView.coffee diff --git a/app/Router.coffee b/app/Router.coffee index 07ddec112..1dd9e7a3b 100644 --- a/app/Router.coffee +++ b/app/Router.coffee @@ -15,8 +15,7 @@ module.exports = class CocoRouter extends Backbone.Router @initializeSocialMediaServices = _.once @initializeSocialMediaServices routes: - '': go('HomeView') # This will go somewhere deprecated when FrontView is done. - 'front': go('FrontView') # This will become '' when it is done. + '': go('HomeView') 'about': go('AboutView') @@ -85,7 +84,6 @@ module.exports = class CocoRouter extends Backbone.Router 'play-old': go('play/MainPlayView') # This used to be 'play'. 'play': go('play/WorldMapView') - 'play-hero': go('play/WorldMapView') # Legacy URL for /play; leave up until start of 2015, I guess. 'play/ladder/:levelID': go('play/ladder/LadderView') 'play/ladder': go('play/ladder/MainLadderView') 'play/level/:levelID': go('play/level/PlayLevelView') diff --git a/app/styles/home.sass b/app/styles/home.sass index 203e3ffe0..0092035f6 100644 --- a/app/styles/home.sass +++ b/app/styles/home.sass @@ -70,3 +70,15 @@ .alert top: 213px border: 5px solid darkred + + &.hour-of-code + #site-footer + background-color: rgb(70, 58, 44) + height: 185px + + .hour-of-code-explanation + color: #9e8777 + text-align: center + + a + color: lighten(#0b63bc, 10%) diff --git a/app/styles/play/level/modal/hero-victory-modal.sass b/app/styles/play/level/modal/hero-victory-modal.sass index 4e4c8b523..2ca7c5c2b 100644 --- a/app/styles/play/level/modal/hero-victory-modal.sass +++ b/app/styles/play/level/modal/hero-victory-modal.sass @@ -231,6 +231,23 @@ width: calc(33.333333% - 10px) margin: 5px + .hour-of-code-done + clear: both + padding-top: 10px + + strong + color: white + display: block + margin-bottom: 10px + font-weight: normal + + .image-link + float: right + margin-left: 10px + + .text-link + color: lighten(#0b63bc, 10%) + html.no-borderimage #hero-victory-modal diff --git a/app/templates/base.jade b/app/templates/base.jade index 4892d7148..53a4f0732 100644 --- a/app/templates/base.jade +++ b/app/templates/base.jade @@ -86,3 +86,5 @@ block footer a(href="http://www.fullyillustrated.com/") Fully Illustrated a.firebase-bade(href="https://www.firebase.com/") img(src="/images/pages/base/firebase.png", alt="Powered by Firebase") + + block extra_footer_content diff --git a/app/templates/home.jade b/app/templates/home.jade index d28401a6a..304791189 100644 --- a/app/templates/home.jade +++ b/app/templates/home.jade @@ -22,4 +22,13 @@ block outer_content strong(data-i18n="home.old_browser") Uh oh, your browser is too old to run CodeCombat. Sorry! br span(data-i18n="home.old_browser_suffix") You can try anyway, but it probably won't work. - \ No newline at end of file + +block extra_footer_content + if true || explainHourOfCode + // 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 diff --git a/app/templates/play/level.jade b/app/templates/play/level.jade index 3c5b8d968..095b4434c 100644 --- a/app/templates/play/level.jade +++ b/app/templates/play/level.jade @@ -37,11 +37,3 @@ #play-footer p(class='footer-link-text') a(title='Send CodeCombat a message', tabindex=-1, data-toggle="coco-modal", data-target="modal/ContactModal", data-i18n="nav.contact") Contact - if explainHourOfCode - // 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 diff --git a/app/templates/play/level/modal/hero-victory-modal.jade b/app/templates/play/level/modal/hero-victory-modal.jade index 6a7559e8b..5302dc502 100644 --- a/app/templates/play/level/modal/hero-victory-modal.jade +++ b/app/templates/play/level/modal/hero-victory-modal.jade @@ -69,3 +69,12 @@ block modal-footer-content button.btn.btn-primary.return-to-ladder-button(data-href="/play/ladder/#{level.get('slug')}#my-matches", data-dismiss="modal", data-i18n="play_level.victory_return_to_ladder") Return to Ladder else button.btn.btn-success.world-map-button.next-level-button.hide#continue-button(data-i18n="play_level.victory_play_continue") Continue + + if showHourOfCodeDoneButton + .hour-of-code-done + hr + a.image-link(href="http://code.org/api/hour/finish") + img(src="/images/level/csedweek-logo-final-small.jpg", alt="CS Ed Week Hour of Code", title="I'm finished with my Hour of Code", width=80) + strong(data-i18n="play_level.victory_hour_of_code_done") Are You Done? + a.text-link(href="http://code.org/api/hour/finish") + span(data-i18n="play_level.victory_hour_of_code_done_yes") Yes, I'm finished with my Hour of Code! diff --git a/app/views/FrontView.coffee b/app/views/FrontView.coffee deleted file mode 100644 index c137b4afd..000000000 --- a/app/views/FrontView.coffee +++ /dev/null @@ -1,34 +0,0 @@ -RootView = require 'views/kinds/RootView' -template = require 'templates/front-view' -{me} = require '/lib/auth' -ModalView = require 'views/kinds/ModalView' - -module.exports = class FrontView extends RootView - id: 'front-view' - template: template - - events: - 'click .platform-ios a': 'onIOSClicked' - - getRenderData: -> - c = super() - if $.browser - majorVersion = $.browser.versionNumber - c.isOldBrowser = true if $.browser.mozilla && majorVersion < 21 - c.isOldBrowser = true if $.browser.chrome && majorVersion < 17 - c.isOldBrowser = true if $.browser.safari && majorVersion < 6 - else - console.warn 'no more jquery browser version...' - c - - afterRender: -> - super() - - onIOSClicked: (e) -> - header = 'Sorry, the iPad app isn\'t ready yet' - body = ''' -

We are working on it!

-

For now, try playing on the web, and totally sign up (with emails enabled) so you can be the first to hear when it is ready.

- ''' - notImplementedModal = new ModalView headerContent: header, bodyContent: body - @openModalView notImplementedModal diff --git a/app/views/HomeView.coffee b/app/views/HomeView.coffee index e8773a24e..18ff30fa6 100644 --- a/app/views/HomeView.coffee +++ b/app/views/HomeView.coffee @@ -16,6 +16,12 @@ module.exports = class HomeView extends RootView constructor: -> super() window.tracker?.trackEvent 'Homepage', Action: 'Loaded' + if not me.get('hourOfCode') and @getQueryVariable 'hour_of_code' + @setUpHourOfCode() + elapsed = (new Date() - new Date(me.get('dateCreated'))) + if me.get('hourOfCode') and elapsed < 86400 * 1000 and me.get('preferredLanguage', true) is 'en-US' + # Show the Hour of Code footer explanation in English until it's been more than a day + @explainsHourOfCode = true getRenderData: -> c = super() @@ -28,6 +34,7 @@ module.exports = class HomeView extends RootView console.warn 'no more jquery browser version...' c.isEnglish = (me.get('preferredLanguage') or 'en').startsWith 'en' c.languageName = me.get('preferredLanguage') + c.explainsHourOfCode = @explainsHourOfCode c onClickBeginnerCampaign: (e) -> @@ -39,7 +46,17 @@ module.exports = class HomeView extends RootView afterInsert: -> super(arguments...) + @$el.addClass 'hour-of-code' if @explainsHourOfCode if me.isAdmin() and me.get('slug') is 'nick' LevelSetupManager = require 'lib/LevelSetupManager' setupManager = new LevelSetupManager levelID: 'dungeons-of-kithgard', hadEverChosenHero: true, parent: @ setupManager.open() + + setUpHourOfCode: -> + elapsed = (new Date() - new Date(me.get('dateCreated'))) + if elapsed < 5 * 60 * 1000 + me.set 'hourOfCode', true + me.patch() + # We may also insert the tracking pixel for everyone on the WorldMapView so as to count directly-linked visitors. + $('body').append($('')) + application.tracker?.trackEvent 'Hour of Code Begin', {} diff --git a/app/views/play/WorldMapView.coffee b/app/views/play/WorldMapView.coffee index 79e6727ca..50c3f68ed 100644 --- a/app/views/play/WorldMapView.coffee +++ b/app/views/play/WorldMapView.coffee @@ -10,6 +10,8 @@ MusicPlayer = require 'lib/surface/MusicPlayer' storage = require 'lib/storage' AuthModal = require 'views/modal/AuthModal' +trackedHourOfCode = false + class LevelSessionsCollection extends CocoCollection url: '' model: LevelSession @@ -70,6 +72,12 @@ module.exports = class WorldMapView extends RootView @listenTo me, 'change:spent', -> @renderSelectors('#gems-count') window.tracker?.trackEvent 'World Map', Action: 'Loaded', ['Google Analytics'] + # 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. + elapsed = (new Date() - new Date(me.get('dateCreated'))) + if not trackedHourOfCode and not me.get('hourOfCode') and elapsed < 5 * 60 * 1000 + $('body').append($('')) + trackedHourOfCode = true + destroy: -> @setupManager?.destroy() $(window).off 'resize', @onWindowResize diff --git a/app/views/play/level/PlayLevelView.coffee b/app/views/play/level/PlayLevelView.coffee index 6f7e7b44a..e9e26ad55 100644 --- a/app/views/play/level/PlayLevelView.coffee +++ b/app/views/play/level/PlayLevelView.coffee @@ -93,8 +93,6 @@ module.exports = class PlayLevelView extends RootView constructor: (options, @levelID) -> console.profile?() if PROFILE_ME super options - if not me.get('hourOfCode') and @getQueryVariable 'hour_of_code' - @setUpHourOfCode() @isEditorPreview = @getQueryVariable 'dev' @sessionID = @getQueryVariable 'session' @@ -114,12 +112,6 @@ module.exports = class PlayLevelView extends RootView @load() application.tracker?.trackEvent 'Started Level Load', level: @levelID, label: @levelID, ['Google Analytics'] - setUpHourOfCode: -> - me.set 'hourOfCode', true - me.patch() - $('body').append($('')) - application.tracker?.trackEvent 'Hour of Code Begin', {} - setLevel: (@level, givenSupermodel) -> @supermodel.models = givenSupermodel.models @supermodel.collections = givenSupermodel.collections @@ -152,10 +144,6 @@ module.exports = class PlayLevelView extends RootView getRenderData: -> c = super() c.world = @world - if me.get('hourOfCode') and me.get('preferredLanguage', true) is 'en-US' - # Show the Hour of Code footer explanation until it's been more than a day - elapsed = (new Date() - new Date(me.get('dateCreated'))) - c.explainHourOfCode = elapsed < 86400 * 1000 c afterRender: -> @@ -606,7 +594,7 @@ module.exports = class PlayLevelView extends RootView # Current real-time multiplayer session # Internal multiplayer create/joined/left events # - # Real-time state variables. + # Real-time state variables. # Each Ref is Firebase reference, and may have a matching Data suffixed variable with the latest data received. # @realTimePlayerRef - User's real-time multiplayer player for this level # @realTimePlayerGameRef - User's current real-time multiplayer player game session @@ -727,7 +715,7 @@ module.exports = class PlayLevelView extends RootView @realTimeSessionRef = new Firebase "#{@multiplayerFireHost}multiplayer_level_sessions/#{@levelID}/#{e.realTimeSessionID}" @realTimePlayersRef = @realTimeSessionRef.child 'players' - + # Look for opponent @realTimeSessionRef.once 'value', (multiplayerSessionSnapshot) => if @realTimeSessionData = multiplayerSessionSnapshot.val() @@ -751,7 +739,7 @@ module.exports = class PlayLevelView extends RootView console.error 'Could not lookup multiplayer session data.' @realTimeSessionRef.on 'value', @onRealTimeSessionChanged - @realTimePlayerGameRef = @realTimeSessionRef.child "players/#{me.id}" + @realTimePlayerGameRef = @realTimeSessionRef.child "players/#{me.id}" # TODO: Follow up in MultiplayerView to see if double joins can be avoided # else @@ -817,7 +805,7 @@ module.exports = class PlayLevelView extends RootView unless @realTimeSessionRef? console.error 'Real-time multiplayer cast without multiplayer session.' return - unless @realTimeSessionData? + unless @realTimeSessionData? console.error 'Real-time multiplayer cast without multiplayer data.' return unless @realTimePlayersData? diff --git a/app/views/play/level/modal/HeroVictoryModal.coffee b/app/views/play/level/modal/HeroVictoryModal.coffee index 84e77ef26..1e20eb431 100644 --- a/app/views/play/level/modal/HeroVictoryModal.coffee +++ b/app/views/play/level/modal/HeroVictoryModal.coffee @@ -80,7 +80,7 @@ module.exports = class HeroVictoryModal extends ModalView @readyToContinue = true @updateSavingProgressStatus() me.fetch() unless me.loading - + @readyToContinue = true if not @achievements.models.length getRenderData: -> @@ -117,6 +117,23 @@ module.exports = class HeroVictoryModal extends ModalView {key: 'continue', link: @continueLevelLink, 'choice-explicit': 'next_level', 'choice-implicit': 'just_right'} {key: 'more_practice', link: @morePracticeLevelLink, 'choice-explicit': 'more_practice', 'choice-implicit': 'too_hard'} ] + + 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. + if isHourOfCode + # Show the Hour of Code "I'm Done" tracking pixel after they played for 20 minutes + enough = elapsed >= 20 * 60 * 1000 + tooMuch = elapsed > 120 * 60 * 1000 + showDone = elapsed >= 30 * 60 * 1000 and not tooMuch + if enough and not tooMuch and not me.get('hourOfCodeComplete') + $('body').append($('')) + me.set 'hourOfCodeComplete', true # Note that this will track even for players who don't have hourOfCode set. + me.patch() + window.tracker?.trackEvent 'Hour of Code Finish', {} + # Show the "I'm done" button between 30 - 120 minutes if they definitely came from Hour of Code + c.showHourOfCodeDoneButton = me.get('hourOfCode') and showDone + return c afterRender: ->