Merge branch 'master' into production

This commit is contained in:
Nick Winter 2015-04-18 17:26:31 -07:00
commit d5117518a4
19 changed files with 52 additions and 76 deletions

View file

@ -100,7 +100,7 @@ module.exports = class Tracker
eventObject["user"] = me.id eventObject["user"] = me.id
dataToSend = JSON.stringify eventObject dataToSend = JSON.stringify eventObject
# console.log dataToSend if debugAnalytics # console.log dataToSend if debugAnalytics
$.post("http://analytics.codecombat.com/analytics", dataToSend).fail -> $.post("#{window.location.protocol or 'http:'}//analytics.codecombat.com/analytics", dataToSend).fail ->
console.error "Analytics post failed!" console.error "Analytics post failed!"
else else
request = @supermodel.addRequestResource 'log_event', { request = @supermodel.addRequestResource 'log_event', {

View file

@ -1,4 +1,6 @@
module.exports.load = (key) -> # Pass false for fromCache to fetch keys that have been stored outside of lscache.
module.exports.load = (key, fromCache=true) ->
return lscache.get key if fromCache
s = localStorage.getItem(key) s = localStorage.getItem(key)
return null unless s return null unless s
try try
@ -8,8 +10,17 @@ module.exports.load = (key) ->
console.warn('error loading from storage', key) console.warn('error loading from storage', key)
return null return null
module.exports.save = (key, value) -> # Pass 0 for expirationInMinutes to persist it as long as possible outside of lscache expiration.
s = JSON.stringify(value) module.exports.save = (key, value, expirationInMinutes) ->
localStorage.setItem(key, s) expirationInMinutes ?= 7 * 24 * 60
if expirationInMinutes
lscache.set key, value, expirationInMinutes
else
localStorage.setItem key, JSON.stringify(value)
module.exports.remove = (key) -> localStorage.removeItem key # Pass false for fromCache to remove keys that have been stored outside of lscache.
module.exports.remove = (key, fromCache=true) ->
if fromCache
lscache.remove key
else
localStorage.removeItem key

View file

@ -70,9 +70,9 @@ module.exports = class World
setThang: (thang) -> setThang: (thang) ->
thang.stateChanged = true thang.stateChanged = true
for old, i in @thangs for old, i in @thangs
console.error 'world trying to set', thang, 'over', old unless old? and thang?
if old.id is thang.id if old.id is thang.id
@thangs[i] = thang @thangs[i] = thang
break
@thangMap[thang.id] = thang @thangMap[thang.id] = thang
thangDialogueSounds: (startFrame=0) -> thangDialogueSounds: (startFrame=0) ->
@ -102,7 +102,9 @@ module.exports = class World
if @realTime and not @countdownFinished if @realTime and not @countdownFinished
@realTimeSpeedFactor = 1 @realTimeSpeedFactor = 1
unless @showsCountdown unless @showsCountdown
if @levelID in ['village-guard', 'thornbush-farm', 'back-to-back', 'ogre-encampment', 'woodland-cleaver', 'shield-rush', 'peasant-protection', 'munchkin-swarm', 'munchkin-harvest', 'swift-dagger', 'shrapnel', 'arcane-ally', 'touch-of-death', 'bonemender'] if @levelID in ['woodland-cleaver', 'village-guard', 'shield-rush']
@realTimeSpeedFactor = 2
else if @levelID in ['thornbush-farm', 'back-to-back', 'ogre-encampment', 'peasant-protection', 'munchkin-swarm', 'munchkin-harvest', 'swift-dagger', 'shrapnel', 'arcane-ally', 'touch-of-death', 'bonemender']
@realTimeSpeedFactor = 3 @realTimeSpeedFactor = 3
if @showsCountdown if @showsCountdown
return setTimeout @finishCountdown(continueLaterFn), REAL_TIME_COUNTDOWN_DELAY return setTimeout @finishCountdown(continueLaterFn), REAL_TIME_COUNTDOWN_DELAY

View file

@ -529,8 +529,6 @@
volume_label: "Volume" volume_label: "Volume"
music_label: "Music" music_label: "Music"
music_description: "Turn background music on/off." music_description: "Turn background music on/off."
autorun_label: "Autorun"
autorun_description: "Control automatic code execution."
editor_config: "Editor Config" editor_config: "Editor Config"
editor_config_title: "Editor Configuration" editor_config_title: "Editor Configuration"
editor_config_level_language_label: "Language for This Level" editor_config_level_language_label: "Language for This Level"

View file

@ -63,10 +63,10 @@ _.extend UserSchema.properties,
githubID: {type: 'integer', title: 'GitHub ID'} githubID: {type: 'integer', title: 'GitHub ID'}
gplusID: c.shortString({title: 'G+ ID'}) gplusID: c.shortString({title: 'G+ ID'})
wizardColor1: c.pct({title: 'Wizard Clothes Color'}) wizardColor1: c.pct({title: 'Wizard Clothes Color'}) # No longer used
volume: c.pct({title: 'Volume'}) volume: c.pct({title: 'Volume'})
music: { type: 'boolean' } music: { type: 'boolean' }
autocastDelay: { type: 'integer' } autocastDelay: { type: 'integer' } # No longer used
lastLevel: { type: 'string' } lastLevel: { type: 'string' }
heroConfig: c.HeroConfigSchema heroConfig: c.HeroConfigSchema

View file

@ -73,7 +73,7 @@ block footer
.fb-like(data-href="https://www.facebook.com/codecombat", data-send="false", data-layout="button_count", data-width="350", data-show-faces="true", data-ref="coco_footer_#{fbRef}") .fb-like(data-href="https://www.facebook.com/codecombat", data-send="false", data-layout="button_count", data-width="350", data-show-faces="true", data-ref="coco_footer_#{fbRef}")
if !isIE if !isIE
a.twitter-follow-button(href="https://twitter.com/CodeCombat", data-show-count="true", data-show-screen-name="false", data-dnt="true", data-align="right", data-i18n="nav.twitter_follow") Follow a.twitter-follow-button(href="https://twitter.com/CodeCombat", data-show-count="true", data-show-screen-name="false", data-dnt="true", data-align="right", data-i18n="nav.twitter_follow") Follow
iframe.github-star-button(src="http://ghbtns.com/github-btn.html?user=codecombat&repo=codecombat&type=watch&count=true", allowtransparency="true", frameborder="0", scrolling="0", width="110", height="20") iframe.github-star-button(src="https://ghbtns.com/github-btn.html?user=codecombat&repo=codecombat&type=watch&count=true", allowtransparency="true", frameborder="0", scrolling="0", width="110", height="20")
#footer-credits #footer-credits
span span

View file

@ -39,4 +39,4 @@ block modal-footer-content
.g-plusone(data-href="http://codecombat.com", data-size="medium") .g-plusone(data-href="http://codecombat.com", data-size="medium")
.fb-like(data-href="https://www.facebook.com/codecombat", data-send="false", data-layout="button_count", data-width="350", data-show-faces="true", data-ref="coco_victory_#{fbRef}") .fb-like(data-href="https://www.facebook.com/codecombat", data-send="false", data-layout="button_count", data-width="350", data-show-faces="true", data-ref="coco_victory_#{fbRef}")
a.twitter-follow-button(href="https://twitter.com/CodeCombat", data-show-count="true", data-show-screen-name="false", data-dnt="true", data-align="right", data-i18n="nav.twitter_follow") Follow a.twitter-follow-button(href="https://twitter.com/CodeCombat", data-show-count="true", data-show-screen-name="false", data-dnt="true", data-align="right", data-i18n="nav.twitter_follow") Follow
iframe.github-star-button(src="http://ghbtns.com/github-btn.html?user=codecombat&repo=codecombat&type=watch&count=true", allowtransparency="true", frameborder="0", scrolling="0", width="110", height="20") iframe.github-star-button(src="https://ghbtns.com/github-btn.html?user=codecombat&repo=codecombat&type=watch&count=true", allowtransparency="true", frameborder="0", scrolling="0", width="110", height="20")

View file

@ -6,6 +6,7 @@
span.glyphicon.glyphicon-remove span.glyphicon.glyphicon-remove
ul#game-menu-nav.nav.nav-pills.nav-stacked ul#game-menu-nav.nav.nav-pills.nav-stacked
if showsChooseHero
li li
a#change-hero-tab a#change-hero-tab
span.glyphicon.glyphicon-user span.glyphicon.glyphicon-user

View file

@ -22,16 +22,6 @@
span(data-i18n="options.music_label") Music span(data-i18n="options.music_label") Music
span.help-block(data-i18n="options.music_description") Turn background music on/off. span.help-block(data-i18n="options.music_description") Turn background music on/off.
.form-group.select-group
label.control-label(for="option-autorun-delay", data-i18n="options.autorun_label") Autorun
select#option-autorun-delay.form-control(name="autorunDelay")
option(value=1000, selected=(autorunDelay === 1000), data-i18n="common.delay_1_sec") 1 second
option(value=3000, selected=(autorunDelay === 3000), data-i18n="common.delay_3_sec") 3 seconds
option(value=5000, selected=(autorunDelay === 5000), data-i18n="common.delay_5_sec") 5 seconds
option(value=90019001, selected=(autorunDelay === 90019001), data-i18n="common.manual") Manual
span.help-block(data-i18n="options.autorun_description") Control automatic code execution.
img.hr(src="/images/pages/play/modal/hr.png", draggable="false") img.hr(src="/images/pages/play/modal/hr.png", draggable="false")
h3(data-i18n="options.editor_config_title") Editor Configuration h3(data-i18n="options.editor_config_title") Editor Configuration

View file

@ -51,5 +51,5 @@ module.exports = class HomeView extends RootView
me.set 'hourOfCode', true me.set 'hourOfCode', true
me.patch() me.patch()
# We may also insert the tracking pixel for everyone on the CampaignView so as to count directly-linked visitors. # We may also insert the tracking pixel for everyone on the CampaignView so as to count directly-linked visitors.
$('body').append($('<img src="http://code.org/api/hour/begin_codecombat.png" style="visibility: hidden;">')) $('body').append($('<img src="https://code.org/api/hour/begin_codecombat.png" style="visibility: hidden;">'))
application.tracker?.trackEvent 'Hour of Code Begin' application.tracker?.trackEvent 'Hour of Code Begin'

View file

@ -37,6 +37,9 @@ module.exports = class LadderPlayModal extends ModalView
aceConfig.language = @$el.find('#tome-language').val() aceConfig.language = @$el.find('#tome-language').val()
me.set 'aceConfig', aceConfig me.set 'aceConfig', aceConfig
me.patch() me.patch()
if @session
@session.set 'codeLanguage', aceConfig.language
@session.patch()
# PART 1: Load challengers from the db unless some are in the matches # PART 1: Load challengers from the db unless some are in the matches
startLoadingChallengersMaybe: -> startLoadingChallengersMaybe: ->
@ -88,6 +91,7 @@ module.exports = class LadderPlayModal extends ModalView
ctx.teamID = @team ctx.teamID = @team
ctx.otherTeamID = @otherTeam ctx.otherTeamID = @otherTeam
ctx.tutorialLevelExists = @tutorialLevelExists ctx.tutorialLevelExists = @tutorialLevelExists
ctx.language = @session?.get('codeLanguage') ? me.get('aceConfig')?.language ? 'python'
ctx.languages = [ ctx.languages = [
{id: 'python', name: 'Python'} {id: 'python', name: 'Python'}
{id: 'javascript', name: 'JavaScript'} {id: 'javascript', name: 'JavaScript'}

View file

@ -108,7 +108,7 @@ module.exports = class CampaignView extends RootView
# 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. # 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'))) elapsed = (new Date() - new Date(me.get('dateCreated')))
if not trackedHourOfCode and not me.get('hourOfCode') and elapsed < 5 * 60 * 1000 if not trackedHourOfCode and not me.get('hourOfCode') and elapsed < 5 * 60 * 1000
$('body').append($('<img src="http://code.org/api/hour/begin_codecombat.png" style="visibility: hidden;">')) $('body').append($('<img src="https://code.org/api/hour/begin_codecombat.png" style="visibility: hidden;">'))
trackedHourOfCode = true trackedHourOfCode = true
@requiresSubscription = not me.isPremium() @requiresSubscription = not me.isPremium()

View file

@ -352,6 +352,7 @@ module.exports = class PlayLevelView extends RootView
@realTimeMultiplayerContinueGame @options.realTimeMultiplayerSessionID @realTimeMultiplayerContinueGame @options.realTimeMultiplayerSessionID
# TODO: Is it possible to create a Mongoose ObjectId for 'ls', instead of the string returned from get()? # TODO: Is it possible to create a Mongoose ObjectId for 'ls', instead of the string returned from get()?
application.tracker?.trackEvent 'Started Level', category:'Play Level', levelID: @levelID, ls: @session?.get('_id') unless @observing application.tracker?.trackEvent 'Started Level', category:'Play Level', levelID: @levelID, ls: @session?.get('_id') unless @observing
$(window).trigger 'resize'
playAmbientSound: -> playAmbientSound: ->
return if @destroyed return if @destroyed

View file

@ -47,10 +47,7 @@ module.exports = class CastButtonView extends CocoView
afterRender: -> afterRender: ->
super() super()
@castButton = $('.cast-button', @$el) @castButton = $('.cast-button', @$el)
@castOptions = $('.autocast-delays', @$el) spell.view?.createOnCodeChangeHandlers() for spellKey, spell of @spells
#delay = me.get('autocastDelay') # No more autocast
delay = 90019001
@setAutocastDelay delay
if @options.level.get('hidesSubmitUntilRun') or @options.level.get('hidesRealTimePlayback') if @options.level.get('hidesSubmitUntilRun') or @options.level.get('hidesRealTimePlayback')
@$el.find('.submit-button').hide() # Hide Submit for the first few until they run it once. @$el.find('.submit-button').hide() # Hide Submit for the first few until they run it once.
if @options.session.get('state')?.complete and @options.level.get 'hidesRealTimePlayback' if @options.session.get('state')?.complete and @options.level.get 'hidesRealTimePlayback'
@ -150,17 +147,6 @@ module.exports = class CastButtonView extends CocoView
waitTime = moment().add(timeUntilResubmit, 'ms').fromNow() waitTime = moment().add(timeUntilResubmit, 'ms').fromNow()
submitAgainLabel.text waitTime submitAgainLabel.text waitTime
setAutocastDelay: (delay) ->
#console.log 'Set autocast delay to', delay
return unless delay
delay = 90019001 # No more autocast
@autocastDelay = delay = parseInt delay
me.set('autocastDelay', delay)
me.patch()
spell.view?.setAutocastDelay delay for spellKey, spell of @spells
@castOptions.find('a').each ->
$(@).toggleClass('selected', parseInt($(@).attr('data-delay')) is delay)
onJoinedRealTimeMultiplayerGame: (e) -> onJoinedRealTimeMultiplayerGame: (e) ->
@inRealTimeMultiplayerSession = true @inRealTimeMultiplayerSession = true

View file

@ -76,6 +76,7 @@ module.exports = class SpellView extends CocoView
@createACE() @createACE()
@createACEShortcuts() @createACEShortcuts()
@fillACE() @fillACE()
@createOnCodeChangeHandlers()
@lockDefaultCode() @lockDefaultCode()
if @session.get('multiplayer') if @session.get('multiplayer')
@createFirepad() @createFirepad()
@ -613,11 +614,7 @@ module.exports = class SpellView extends CocoView
Backbone.Mediator.publish 'tome:spell-loaded', spell: @spell Backbone.Mediator.publish 'tome:spell-loaded', spell: @spell
@updateLines() @updateLines()
recompileIfNeeded: =>
@recompile() if @recompileNeeded
recompile: (cast=true, realTime=false) -> recompile: (cast=true, realTime=false) ->
@setRecompileNeeded false
hasChanged = @spell.source isnt @getSource() hasChanged = @spell.source isnt @getSource()
if hasChanged if hasChanged
@spell.transpile @getSource() @spell.transpile @getSource()
@ -640,17 +637,9 @@ module.exports = class SpellView extends CocoView
catch error catch error
console.warn 'Error resizing ACE after an update:', error console.warn 'Error resizing ACE after an update:', error
# Called from CastButtonView initially and whenever the delay is changed
setAutocastDelay: (@autocastDelay) ->
@createOnCodeChangeHandlers()
createOnCodeChangeHandlers: -> createOnCodeChangeHandlers: ->
@aceDoc.removeListener 'change', @onCodeChangeMetaHandler if @onCodeChangeMetaHandler @aceDoc.removeListener 'change', @onCodeChangeMetaHandler if @onCodeChangeMetaHandler
autocastDelay = @autocastDelay ? 3000 onSignificantChange = []
onSignificantChange = [
_.debounce @setRecompileNeeded, autocastDelay - 100
@currentAutocastHandler = _.debounce @recompileIfNeeded, autocastDelay
]
onAnyChange = [ onAnyChange = [
_.debounce @updateAether, 500 _.debounce @updateAether, 500
_.debounce @notifyEditingEnded, 1000 _.debounce @notifyEditingEnded, 1000
@ -671,10 +660,7 @@ module.exports = class SpellView extends CocoView
callback() for callback in onAnyChange # Then these callback() for callback in onAnyChange # Then these
@aceDoc.on 'change', @onCodeChangeMetaHandler @aceDoc.on 'change', @onCodeChangeMetaHandler
setRecompileNeeded: (@recompileNeeded) => onCursorActivity: => # Used to refresh autocast delay; doesn't do anything at the moment.
onCursorActivity: =>
@currentAutocastHandler?()
# Design for a simpler system? # Design for a simpler system?
# * Keep Aether linting, debounced, on any significant change # * Keep Aether linting, debounced, on any significant change
@ -801,7 +787,7 @@ module.exports = class SpellView extends CocoView
@userCodeProblem.save() @userCodeProblem.save()
null null
# Autocast: # Autocast (preload the world in the background):
# Goes immediately if the code is a) changed and b) complete/valid and c) the cursor is at beginning or end of a line # Goes immediately if the code is a) changed and b) complete/valid and c) the cursor is at beginning or end of a line
# We originally thought it would: # We originally thought it would:
# - Go after specified delay if a) and b) but not c) # - Go after specified delay if a) and b) but not c)
@ -817,10 +803,7 @@ module.exports = class SpellView extends CocoView
incompleteThis = /^(s|se|sel|self|t|th|thi|this)$/.test currentLine.trim() incompleteThis = /^(s|se|sel|self|t|th|thi|this)$/.test currentLine.trim()
#console.log "finished=#{valid and (endOfLine or beginningOfLine) and not incompleteThis}", valid, endOfLine, beginningOfLine, incompleteThis, cursorPosition, currentLine.length, aether, new Date() - 0, currentLine #console.log "finished=#{valid and (endOfLine or beginningOfLine) and not incompleteThis}", valid, endOfLine, beginningOfLine, incompleteThis, cursorPosition, currentLine.length, aether, new Date() - 0, currentLine
if valid and (endOfLine or beginningOfLine) and not incompleteThis if valid and (endOfLine or beginningOfLine) and not incompleteThis
if @autocastDelay > 60000
@preload() @preload()
else
@recompile()
singleLineCommentRegex: -> singleLineCommentRegex: ->
if @_singleLineCommentRegex if @_singleLineCommentRegex
@ -836,7 +819,10 @@ module.exports = class SpellView extends CocoView
preload: -> preload: ->
# Send this code over to the God for preloading, but don't change the cast state. # Send this code over to the God for preloading, but don't change the cast state.
return if @spell.source.indexOf 'while' # If they're working with while-loops, it's more likely to be an incomplete infinite loop, so don't preload. #console.log 'preload?', @spell.source.indexOf('while'), @spell.source.length, @spellThang?.castAether?.metrics?.statementsExecuted
return if @spell.source.indexOf('while') isnt -1 # If they're working with while-loops, it's more likely to be an incomplete infinite loop, so don't preload.
return if @spell.source.length > 500 # Only preload on really short methods
return if @spellThang?.castAether?.metrics?.statementsExecuted > 2000 # Don't preload if they are running significant amounts of user code
oldSource = @spell.source oldSource = @spell.source
oldSpellThangAethers = {} oldSpellThangAethers = {}
for thangID, spellThang of @spell.thangs for thangID, spellThang of @spell.thangs

View file

@ -1,7 +1,7 @@
# There's one TomeView per Level. It has: # There's one TomeView per Level. It has:
# - a CastButtonView, which has # - a CastButtonView, which has
# - a cast button # - a cast button
# - an autocast settings options button # - a submit/done button
# - for each spell (programmableMethod): # - for each spell (programmableMethod):
# - a Spell, which has # - a Spell, which has
# - a list of Thangs that share that Spell, with one aether per Thang per Spell # - a list of Thangs that share that Spell, with one aether per Thang per Spell

View file

@ -43,6 +43,7 @@ module.exports = class GameMenuModal extends ModalView
'guide': 'list' 'guide': 'list'
'save-load': 'floppy-disk' 'save-load': 'floppy-disk'
'multiplayer': 'globe' 'multiplayer': 'globe'
context.showsChooseHero = @options.levelID not in ['zero-sum']
context context
afterRender: -> afterRender: ->

View file

@ -20,7 +20,6 @@ module.exports = class OptionsView extends CocoView
events: events:
'change #option-music': 'updateMusic' 'change #option-music': 'updateMusic'
'change #option-autorun-delay': 'updateAutorun'
'change #option-key-bindings': 'updateInvisibles' 'change #option-key-bindings': 'updateInvisibles'
'change #option-key-bindings': 'updateKeyBindings' 'change #option-key-bindings': 'updateKeyBindings'
'change #option-indent-guides': 'updateIndentGuides' 'change #option-indent-guides': 'updateIndentGuides'
@ -44,7 +43,6 @@ module.exports = class OptionsView extends CocoView
@aceConfig = _.defaults @aceConfig, @defaultConfig @aceConfig = _.defaults @aceConfig, @defaultConfig
c.aceConfig = @aceConfig c.aceConfig = @aceConfig
c.music = me.get('music', true) c.music = me.get('music', true)
c.autorunDelay = me.get('autocastDelay') ? 5000
c c
afterRender: -> afterRender: ->
@ -80,9 +78,6 @@ module.exports = class OptionsView extends CocoView
updateMusic: -> updateMusic: ->
me.set 'music', @$el.find('#option-music').prop('checked') me.set 'music', @$el.find('#option-music').prop('checked')
updateAutorun: ->
me.set 'autocastDelay', parseInt(@$el.find('#option-autorun-delay').val())
updateInvisibles: -> updateInvisibles: ->
@aceConfig.invisibles = @$el.find('#option-invisibles').prop('checked') @aceConfig.invisibles = @$el.find('#option-invisibles').prop('checked')

View file

@ -47,7 +47,8 @@
"modernizr": "~2.8.3", "modernizr": "~2.8.3",
"backfire": "~0.3.0", "backfire": "~0.3.0",
"fastclick": "~1.0.3", "fastclick": "~1.0.3",
"three.js": "*" "three.js": "*",
"lscache": "~1.0.5"
}, },
"overrides": { "overrides": {
"backbone": { "backbone": {