diff --git a/app/initialize.coffee b/app/initialize.coffee index e076f2ec3..7670088db 100644 --- a/app/initialize.coffee +++ b/app/initialize.coffee @@ -1,14 +1,24 @@ app = require 'application' +auth = require 'lib/auth' -$ -> +init = -> app.initialize() Backbone.history.start({ pushState: true }) handleNormalUrls() - + treemaExt = require 'treema-ext' treemaExt.setup() filepicker.setKey('AvlkNoldcTOU4PvKi2Xm7z') +$ -> + # Make sure we're "logged in" first. + if auth.me.id + init() + else + Backbone.Mediator.subscribeOnce 'me:synced', init + +window.init = init + handleNormalUrls = -> # http://artsy.github.com/blog/2012/06/25/replacing-hashbang-routes-with-pushstate/ $(document).on "click", "a[href^='/']", (event) -> diff --git a/app/lib/LoadingScreen.coffee b/app/lib/LoadingScreen.coffee index b3993a435..aff6f53e6 100644 --- a/app/lib/LoadingScreen.coffee +++ b/app/lib/LoadingScreen.coffee @@ -96,4 +96,4 @@ module.exports = class LoadingScreen extends CocoClass destroy: -> @stage.canvas = null - super() \ No newline at end of file + super() \ No newline at end of file diff --git a/app/lib/sprites/SpriteBuilder.coffee b/app/lib/sprites/SpriteBuilder.coffee index babf5178a..65ecaaf0b 100644 --- a/app/lib/sprites/SpriteBuilder.coffee +++ b/app/lib/sprites/SpriteBuilder.coffee @@ -141,8 +141,8 @@ module.exports = class SpriteBuilder return unless shapes.length colors = @initColorMap(shapes) @adjustHuesForColorMap(colors, config.hue) - @adjustValueForColorMap(colors, 1, config.lightness) - @adjustValueForColorMap(colors, 2, config.saturation) + @adjustValueForColorMap(colors, 1, config.saturation) + @adjustValueForColorMap(colors, 2, config.lightness) @applyColorMap(shapes, colors) initColorMap: (shapes) -> diff --git a/app/lib/surface/CastingScreen.coffee b/app/lib/surface/CastingScreen.coffee new file mode 100644 index 000000000..8b00a1df8 --- /dev/null +++ b/app/lib/surface/CastingScreen.coffee @@ -0,0 +1,48 @@ +CocoClass = require 'lib/CocoClass' + +module.exports = class CastingScreen extends CocoClass + subscriptions: + 'tome:cast-spells': 'onCastingBegins' + 'god:new-world-created': 'onCastingEnds' + + constructor: (options) -> + super() + options ?= {} + @camera = options.camera + @layer = options.layer + console.error @toString(), "needs a camera." unless @camera + console.error @toString(), "needs a layer." unless @layer + @build() + + onCastingBegins: -> + @show() + + onCastingEnds: -> + @hide() + + toString: -> "" + + build: -> + @dimLayer = new createjs.Container() + @dimLayer.mouseEnabled = @dimLayer.mouseChildren = false + @dimLayer.layerIndex = -11 + @dimLayer.addChild @dimScreen = new createjs.Shape() + @dimScreen.graphics.beginFill("rgba(0,0,0,0.5)").rect 0, 0, @camera.canvasWidth, @camera.canvasHeight + @dimLayer.cache 0, 0, @camera.canvasWidth, @camera.canvasHeight + @dimLayer.alpha = 0 + @layer.addChild @dimLayer + + show: -> + return if @on + @on = true + + @dimLayer.alpha = 0 + createjs.Tween.removeTweens @dimLayer + createjs.Tween.get(@dimLayer).to({alpha:1}, 500) + + hide: -> + return unless @on + @on = false + + createjs.Tween.removeTweens @dimLayer + createjs.Tween.get(@dimLayer).to({alpha:0}, 500) diff --git a/app/lib/surface/CocoSprite.coffee b/app/lib/surface/CocoSprite.coffee index 1232ffe48..29ee32371 100644 --- a/app/lib/surface/CocoSprite.coffee +++ b/app/lib/surface/CocoSprite.coffee @@ -65,13 +65,13 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass @age = 0 @displayObject = new createjs.Container() if @thangType.get('actions') - @onThangTypeLoaded() + @setupSprite() else @stillLoading = true @thangType.fetch() - @thangType.once 'sync', @onThangTypeLoaded, @ + @thangType.once 'sync', @setupSprite, @ - onThangTypeLoaded: -> + setupSprite: -> @stillLoading = false @actions = @thangType.getActions() @buildFromSpriteSheet @buildSpriteSheet() @@ -101,6 +101,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass # temp, until these are re-exported with perspective if @options.camera and @thangType.get('name') in ['Dungeon Floor', 'Indoor Floor', 'Grass', 'Goal Trigger', 'Obstacle'] sprite.scaleY *= @options.camera.y2x + @displayObject.removeChild(@imageObject) if @imageObject @imageObject = sprite @displayObject.addChild(sprite) @addHealthBar() @@ -157,6 +158,14 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass @hiding = false @updateAlpha() + stop: -> + @imageObject?.stop?() + mark.stop() for name, mark of @marks + + play: -> + @imageObject?.play?() + mark.play() for name, mark of @marks + update: -> # Gets the sprite to reflect what the current state of the thangs and surface are return if @stillLoading @@ -197,8 +206,11 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass if @thang.width isnt @lastThangWidth or @thang.height isnt @lastThangHeight [@lastThangWidth, @lastThangHeight] = [@thang.width, @thang.height] bounds = @imageObject.getBounds() - @imageObject.scaleX = @thang.width * Camera.PPM / bounds.width * @thangType.get('scale') ? 1 - @imageObject.scaleY = @thang.height * Camera.PPM * @options.camera.y2x / bounds.height * @thangType.get('scale') ? 1 + @imageObject.scaleX = @thang.width * Camera.PPM / bounds.width + @imageObject.scaleY = @thang.height * Camera.PPM * @options.camera.y2x / bounds.height + unless @thang.spriteName is 'Beam' + @imageObject.scaleX *= @thangType.get('scale') ? 1 + @imageObject.scaleY *= @thangType.get('scale') ? 1 return scaleX = if @getActionProp 'flipX' then -1 else 1 scaleY = if @getActionProp 'flipY' then -1 else 1 diff --git a/app/lib/surface/Label.coffee b/app/lib/surface/Label.coffee index d7caa0c47..3b7a37de6 100644 --- a/app/lib/surface/Label.coffee +++ b/app/lib/surface/Label.coffee @@ -54,17 +54,19 @@ module.exports = class Label extends CocoClass buildLabelOptions: -> o = {} st = {dialogue: 'D', say: 'S', name: 'N'}[@style] - o.marginX = {D: 5, S: 2, N: 3}[st] - o.marginY = {D: 6, S: 2, N: 3}[st] + o.marginX = {D: 5, S: 6, N: 3}[st] + o.marginY = {D: 6, S: 4, N: 3}[st] + o.fontWeight = {D: "bold", S: "bold", N: "bold"}[st] o.shadow = {D: false, S: true, N: true}[st] - o.fontSize = {D: 25, S: 19, N: 14}[st] + o.shadowColor = {D: "#FFF", S: "#000", N: "#FFF"}[st] + o.fontSize = {D: 25, S: 12, N: 12}[st] fontFamily = {D: "Arial", S: "Arial", N: "Arial"}[st] - o.fontDescriptor = "#{o.fontSize}px #{fontFamily}" - o.fontColor = {D: "#000", S: "#000", N: "#00a"}[st] - o.backgroundFillColor = {D: "white", S: "rgba(255, 255, 255, 0.2)", N: "rgba(255, 255, 255, 0.5)"}[st] - o.backgroundStrokeColor = {D: "black", S: "rgba(0, 0, 0, 0.2)", N: "rgba(0, 0, 0, 0.0)"}[st] + o.fontDescriptor = "#{o.fontWeight} #{o.fontSize}px #{fontFamily}" + o.fontColor = {D: "#000", S: "#FFF", N: "#00a"}[st] + o.backgroundFillColor = {D: "white", S: "rgba(0, 0, 0, 0.4)", N: "rgba(255, 255, 255, 0.5)"}[st] + o.backgroundStrokeColor = {D: "black", S: "rgba(0, 0, 0, .6)", N: "rgba(0, 0, 0, 0.0)"}[st] o.backgroundStrokeStyle = {D: 2, S: 1, N: 1}[st] - o.backgroundBorderRadius = {D: 10, S: 5, N: 3}[st] + o.backgroundBorderRadius = {D: 10, S: 3, N: 3}[st] o.layerPriority = {D: 10, S: 5, N: 5}[st] maxWidth = {D: 300, S: 300, N: 180}[st] maxWidth = Math.max @camera.canvasWidth / 2 - 100, maxWidth # Does this do anything? @@ -79,7 +81,7 @@ module.exports = class Label extends CocoClass label.lineHeight = o.fontSize + 2 label.x = o.marginX label.y = o.marginY - label.shadow = new createjs.Shadow "#FFF", 1, 1, 0 if o.shadow + label.shadow = new createjs.Shadow o.shadowColor, 1, 1, 0 if o.shadow label.layerPriority = o.layerPriority label.name = "Sprite Label - #{@style}" o.textHeight = label.getMeasuredHeight() diff --git a/app/lib/surface/Mark.coffee b/app/lib/surface/Mark.coffee index 313531ef1..c012b537c 100644 --- a/app/lib/surface/Mark.coffee +++ b/app/lib/surface/Mark.coffee @@ -48,7 +48,7 @@ module.exports = class Mark extends CocoClass build: -> unless @mark if @name is 'bounds' then @buildBounds() - else if @name is 'shadow' then @buildRadius() + else if @name is 'shadow' then @buildShadow() else if @name is 'debug' then @buildDebug() else if @thangType then @buildSprite() else console.error "Don't know how to build mark for", @name @@ -87,21 +87,31 @@ module.exports = class Mark extends CocoClass @lastWidth = @sprite.thang.width @lastHeight = @sprite.thang.height - buildRadius: -> - # TODO: make this not just a shadow - # TODO: draw boxes and ellipses for non-circular Thangs - diameter = @sprite.thangType.get('shadow') ? @sprite.thang?.width + 0.5 - diameter *= Camera.PPM + buildShadow: -> + width = (@sprite.thang?.width ? 0) + 0.5 + height = (@sprite.thang?.height ? 0) + 0.5 + longest = Math.max width, height + actualLongest = @sprite.thangType.get('shadow') ? longest + width = width * actualLongest / longest + height = height * actualLongest / longest + width *= Camera.PPM + height *= Camera.PPM * @camera.y2x # TODO: doesn't work with rotation @mark = new createjs.Shape() @mark.mouseEnabled = false @mark.graphics.beginFill "black" - @mark.graphics.drawEllipse 0, 0, diameter, diameter * @camera.y2x + if @sprite.thang.shape in ['ellipsoid', 'disc'] + @mark.graphics.drawEllipse 0, 0, width, height + else + @mark.graphics.drawRect 0, 0, width, height @mark.graphics.endFill() - @mark.regX = diameter / 2 - @mark.regY = diameter / 2 * @camera.y2x + @mark.regX = width / 2 + @mark.regY = height / 2 @mark.layerIndex = 10 #@mark.cache 0, 0, diameter, diameter # not actually faster than simple ellipse draw + buildRadius: -> + return # not implemented + buildDebug: -> @mark = new createjs.Shape() PX = 3 @@ -152,7 +162,7 @@ module.exports = class Mark extends CocoClass @mark.y += offset.y updateRotation: -> - if @name is 'debug' + if @name is 'debug' or (@name is 'shadow' and @sprite.thang?.shape in ["rectangle", "box"]) @mark.rotation = @sprite.thang.rotation * 180 / Math.PI updateScale: -> @@ -174,3 +184,6 @@ module.exports = class Mark extends CocoClass @mark.scaleX = @mark.scaleY = Math.min 1, scale if @name in ['selection', 'target', 'repair'] @mark.scaleY *= @camera.y2x # code applies perspective + + stop: -> @markSprite?.stop() + play: -> @markSprite?.play() diff --git a/app/lib/surface/SpriteBoss.coffee b/app/lib/surface/SpriteBoss.coffee index f6acd4da2..768efcc99 100644 --- a/app/lib/surface/SpriteBoss.coffee +++ b/app/lib/surface/SpriteBoss.coffee @@ -20,6 +20,7 @@ module.exports = class SpriteBoss extends CocoClass 'level-lock-select': 'onSetLockSelect' 'level:restarted': 'onLevelRestarted' 'god:new-world-created': 'onNewWorld' + 'tome:cast-spells': 'onCastSpells' constructor: (@options) -> super() @@ -205,6 +206,14 @@ module.exports = class SpriteBoss extends CocoClass onNewWorld: (e) -> @world = @options.world = e.world + sprite.imageObject.play() for thangID, sprite of @sprites + @selectionMark?.play() + @targetMark?.play() + + onCastSpells: -> + sprite.imageObject.stop() for thangID, sprite of @sprites + @selectionMark?.stop() + @targetMark?.stop() # Selection diff --git a/app/lib/surface/Surface.coffee b/app/lib/surface/Surface.coffee index 0b558b9a1..f42720101 100644 --- a/app/lib/surface/Surface.coffee +++ b/app/lib/surface/Surface.coffee @@ -8,6 +8,7 @@ CameraBorder = require './CameraBorder' Layer = require './Layer' Letterbox = require './Letterbox' Dimmer = require './Dimmer' +CastingScreen = require './CastingScreen' DebugDisplay = require './DebugDisplay' CoordinateDisplay = require './CoordinateDisplay' SpriteBoss = require './SpriteBoss' @@ -88,6 +89,7 @@ module.exports = Surface = class Surface extends CocoClass @spriteBoss.destroy() @chooser?.destroy() @dimmer?.destroy() + @castingScreen?.destroy() @stage.clear() @musicPlayer?.destroy() @stage.removeAllChildren() @@ -224,7 +226,7 @@ module.exports = Surface = class Surface extends CocoClass @currentFrame = actualCurrentFrame # TODO: are these needed, or perhaps do they duplicate things? - @spriteBoss.update() + @spriteBoss.update true @onFrameChanged() getCurrentFrame: -> @@ -299,11 +301,18 @@ module.exports = Surface = class Surface extends CocoClass @lastFrame = @currentFrame onCastSpells: (event) -> + @casting = true + @wasPlayingWhenCastingBegan = @playing + Backbone.Mediator.publish 'level-set-playing', { playing: false } + createjs.Tween.removeTweens(@surfaceLayer) createjs.Tween.get(@surfaceLayer).to({alpha:0.9}, 1000, createjs.Ease.getPowOut(4.0)) onNewWorld: (event) -> return unless event.world.name is @world.name + @casting = false + Backbone.Mediator.publish 'level-set-playing', { playing: @wasPlayingWhenCastingBegan } + fastForwardTo = null if @playing fastForwardTo = Math.min event.world.firstChangedFrame, @currentFrame @@ -340,6 +349,7 @@ module.exports = Surface = class Surface extends CocoClass @surfaceLayer.addChild @cameraBorder = new CameraBorder bounds: @camera.bounds @screenLayer.addChild new Letterbox canvasWidth: canvasWidth, canvasHeight: canvasHeight @spriteBoss = new SpriteBoss camera: @camera, surfaceLayer: @surfaceLayer, surfaceTextLayer: @surfaceTextLayer, world: @world, thangTypes: @options.thangTypes, choosing: @options.choosing, navigateToSelection: @options.navigateToSelection, showInvisible: @options.showInvisible + @castingScreen ?= new CastingScreen camera: @camera, layer: @screenLayer @stage.enableMouseOver(10) @stage.addEventListener 'stagemousemove', @onMouseMove @stage.addEventListener 'stagemousedown', @onMouseDown @@ -497,7 +507,7 @@ module.exports = Surface = class Surface extends CocoClass updateState: (frameChanged) -> # world state must have been restored in @updateSpriteSounds @camera.updateZoom() - @spriteBoss.update frameChanged + @spriteBoss.update frameChanged unless @casting @dimmer?.setSprites @spriteBoss.sprites drawCurrentFrame: (e) -> @@ -508,6 +518,7 @@ module.exports = Surface = class Surface extends CocoClass updatePaths: -> return unless @options.paths + return if @casting @hidePaths() selectedThang = @spriteBoss.selectedSprite?.thang return if @world.showPaths is 'never' diff --git a/app/lib/surface/WizardSprite.coffee b/app/lib/surface/WizardSprite.coffee index b5f6e68ff..9f56b5c8a 100644 --- a/app/lib/surface/WizardSprite.coffee +++ b/app/lib/surface/WizardSprite.coffee @@ -24,7 +24,7 @@ module.exports = class WizardSprite extends IndieSprite constructor: (thangType, options) -> if options?.isSelf - options.colorConfig = me.get('wizard')?.colorConfig or {} + options.colorConfig = _.cloneDeep(me.get('wizard')?.colorConfig) or {} super thangType, options @isSelf = options.isSelf @targetPos = @thang.pos @@ -59,7 +59,12 @@ module.exports = class WizardSprite extends IndieSprite onMeSynced: (e) -> return unless @isSelf @setNameLabel me.displayName() if @displayObject.visible # not if we hid the wiz - @setColorHue me.get('wizardColor1') + newColorConfig = me.get('wizard')?.colorConfig or {} + shouldUpdate = not _.isEqual(newColorConfig, @options.colorConfig) + @options.colorConfig = _.cloneDeep(newColorConfig) + if shouldUpdate + @setupSprite() + @playAction(@currentAction) onSpriteSelected: (e) -> return unless @isSelf diff --git a/app/lib/world/world.coffee b/app/lib/world/world.coffee index 98f04b529..87d6e3d9a 100644 --- a/app/lib/world/world.coffee +++ b/app/lib/world/world.coffee @@ -55,7 +55,7 @@ module.exports = class World @thangMap[thang.id] = thang thangDialogueSounds: -> - if @frames.length < @totalFrames then worldShouldBeOverBeforeGrabbingDialogue + if @frames.length < @totalFrames then throw new Error("World should be over before grabbing dialogue") [sounds, seen] = [[], {}] for frame in @frames for thangID, state of frame.thangStateMap @@ -245,7 +245,7 @@ module.exports = class World serialize: -> # Code hotspot; optimize it - if @frames.length < @totalFrames then worldShouldBeOverBeforeSerialization + if @frames.length < @totalFrames then throw new Error("World Should Be Over Before Serialization") [transferableObjects, nontransferableObjects] = [0, 0] o = {name: @name, totalFrames: @totalFrames, maxTotalFrames: @maxTotalFrames, frameRate: @frameRate, dt: @dt, victory: @victory, userCodeMap: {}, trackedProperties: {}} o.trackedProperties[prop] = @[prop] for prop in @trackedProperties or [] diff --git a/app/styles/account/settings.sass b/app/styles/account/settings.sass index 4eccfb708..8751e59ef 100644 --- a/app/styles/account/settings.sass +++ b/app/styles/account/settings.sass @@ -37,44 +37,4 @@ font-size: 12px .form - max-width: 600px - -#wizard-settings-tab-view - #color-settings - float: left - width: 600px - margin-left: 30px - - canvas - float: left - border: 2px solid black - margin: 20px - - .color-group - clear: both - padding-bottom: 10px - margin-bottom: 10px - border-bottom: 1px solid gray - - .name-cell - float: left - width: 100px - padding-top: 2px - - input - margin-right: 10px - position: relative - top: -3px - - .checkbox-cell - float: left - width: 40px - - .slider-cell - margin-bottom: 10px - float: left - width: 120px - - .selector - width: 100px - + max-width: 600px \ No newline at end of file diff --git a/app/styles/account/wizard-settings.sass b/app/styles/account/wizard-settings.sass new file mode 100644 index 000000000..6abd733df --- /dev/null +++ b/app/styles/account/wizard-settings.sass @@ -0,0 +1,42 @@ +#wizard-settings-view + h3#loading + text-align: center + + #color-settings + float: left + width: 600px + margin-left: 30px + + canvas + float: left + border: 2px solid black + margin: 20px + + .color-group + clear: both + padding-bottom: 10px + margin-bottom: 10px + border-bottom: 1px solid gray + + .name-cell + float: left + width: 100px + padding-top: 2px + + input + margin-right: 10px + position: relative + top: -3px + + .checkbox-cell + float: left + width: 40px + + .slider-cell + margin-bottom: 10px + float: left + width: 120px + + .selector + width: 100px + diff --git a/app/styles/home.sass b/app/styles/home.sass index 44652acdd..f8944cd3b 100644 --- a/app/styles/home.sass +++ b/app/styles/home.sass @@ -24,9 +24,9 @@ width: 300px height: 80px - //@include transition(color .10s linear) // buggy in chrome, coloring doesn't get the right edge of the word + @include transition(color .10s linear) - &:hover a, &:active a + &:hover button, &:active button color: #8090AA canvas diff --git a/app/styles/modal/wizard_settings.sass b/app/styles/modal/wizard_settings.sass index a219949e6..09c4bc10e 100644 --- a/app/styles/modal/wizard_settings.sass +++ b/app/styles/modal/wizard_settings.sass @@ -1,20 +1,17 @@ #wizard-settings-modal color: black - background: white - width: 400px + #wizard-settings-view #color-settings + width: 480px + canvas - border: 1px solid black - float: left - - .settings - width: 225px - margin: 10px - display: inline-block + margin: 0px auto 20px + display: block + float: none + background: white - .selector - margin: 10px 5px - - button - margin-left: 10px - float: right \ No newline at end of file + .wizard-name-line + text-align: center + margin-bottom: 10px + label + margin-right: 10px \ No newline at end of file diff --git a/app/styles/play/ladder.sass b/app/styles/play/ladder.sass index 8608768f8..027e755db 100644 --- a/app/styles/play/ladder.sass +++ b/app/styles/play/ladder.sass @@ -19,4 +19,6 @@ text-overflow: ellipsis white-space: nowrap overflow: hidden - \ No newline at end of file + + #must-log-in button + margin-right: 10px \ No newline at end of file diff --git a/app/styles/play/ladder/team.sass b/app/styles/play/ladder/team.sass index 2368ebce0..ca4cc9676 100644 --- a/app/styles/play/ladder/team.sass +++ b/app/styles/play/ladder/team.sass @@ -5,3 +5,8 @@ #competitors-column .well font-size: 18px padding: 7px + + #your-score + margin-top: 20px + text-align: center + font-size: 20px \ No newline at end of file diff --git a/app/styles/play/level.sass b/app/styles/play/level.sass index bac54bb41..fb93d93a1 100644 --- a/app/styles/play/level.sass +++ b/app/styles/play/level.sass @@ -132,9 +132,21 @@ height: 100% width: 2% - .footer:not(:hover) - @include opacity(0.6) + .footer + @media screen and (min-aspect-ratio: 17/10) + display: none -@media screen and (min-aspect-ratio: 17/10) - #level-view .footer - display: none + &:not(:hover) + @include opacity(0.6) + + .hour-of-code-explanation + margin-top: 5px + color: white + font-size: 12px + + &:not(:hover) + @include opacity(0.75) + + a + color: white + text-decoration: underline diff --git a/app/styles/play/level/tome/spell.sass b/app/styles/play/level/tome/spell.sass index d5dfa5cc8..9066b4af5 100644 --- a/app/styles/play/level/tome/spell.sass +++ b/app/styles/play/level/tome/spell.sass @@ -77,7 +77,7 @@ .executed background-color: rgba(245, 255, 6, 0.18) .problem-marker-info - background-color: rgba(96, 63, 84, 0.25) + background-color: rgba(196, 163, 184, 0.25) .problem-marker-warning background-color: rgba(100, 65, 20, 0.25) .problem-marker-error diff --git a/app/styles/play/level/tome/spell_palette_entry.sass b/app/styles/play/level/tome/spell_palette_entry.sass index f2ec74182..5e8ee3a0f 100644 --- a/app/styles/play/level/tome/spell_palette_entry.sass +++ b/app/styles/play/level/tome/spell_palette_entry.sass @@ -9,16 +9,18 @@ border: 1px solid transparent cursor: pointer @include user-select(all) + ::selection + background: transparent &:hover - border: 1px solid #BFF + border: 1px solid #000000 &.pinned - background-color: darken(#BFF, 20%) + background-color: darken(#FFFFFF, 25%) // Pulling these colors from the most relevant textmate-theme classes &.function - color: #0000A2 + color: #0066FF &.object color: rgb(6, 150, 14) &.string diff --git a/app/templates/account/settings.jade b/app/templates/account/settings.jade index 483b35f88..bae178865 100644 --- a/app/templates/account/settings.jade +++ b/app/templates/account/settings.jade @@ -57,7 +57,7 @@ block content a(href="http://en.gravatar.com/profiles/edit/?noclose#your-images", target="_blank", data-i18n="account_settings.gravatar_add_more_photos") Add more photos to your Gravatar account to access them here. #wizard-pane.tab-pane - #wizard-settings-tab-view + #wizard-settings-view #password-pane.tab-pane p diff --git a/app/templates/account/wizard_settings_tab.jade b/app/templates/account/wizard_settings.jade similarity index 98% rename from app/templates/account/wizard_settings_tab.jade rename to app/templates/account/wizard_settings.jade index faea52b89..138a8c970 100644 --- a/app/templates/account/wizard_settings_tab.jade +++ b/app/templates/account/wizard_settings.jade @@ -8,7 +8,7 @@ canvas#tinting-display(width=200, height=200).img-rounded span(data-i18n='wizard_settings.' + group.dasherized)= group.humanized div.sliders div.slider-cell - label(for=group.humanized+"_hue", data-i18n="wizard_settigs.hue") Hue + label(for=group.humanized+"_hue", data-i18n="wizard_settings.hue") Hue .selector(id=group.humanized+"_hue", name=group.name+'.hue', data-key='hue') div.slider-cell label(for=group.humanized+"_saturation", data-i18n="wizard_settings.saturation") Saturation diff --git a/app/templates/modal/wizard_settings.jade b/app/templates/modal/wizard_settings.jade index 7a789dce4..21ec05549 100644 --- a/app/templates/modal/wizard_settings.jade +++ b/app/templates/modal/wizard_settings.jade @@ -4,19 +4,17 @@ block modal-header-content h3(data-i18n="wizard_settings.title") Wizard Settings block modal-body-content - h4(data-i18n="wizard_settings.customize_avatar") Customize Your Avatar - - canvas(width="120px", height="150px") - .settings - .form-vertical.form - .form-group - label.control-label(for="name") - | Name - button.btn.btn-mini.btn-primary#random-name Random - input#wizard-settings-name(name="name", type="text", value="#{me.get('name')||''}") - .form-group - label.control-label(for="wizardColor1") Hat Color - .selector#wizard-settings-color-1 + div.wizard-name-line.form-group + label.control-label(for="name") + | Name + input#wizard-settings-name(name="name", type="text", value="#{me.get('name')||''}") + + #wizard-settings-view + +block modal-body-wait-content + h3 Saving... +.progress.progress-striped.active + .progress-bar block modal-footer-content - button.btn.btn-primary.btn-large#wizard-settings-done(type="button", data-dismiss="modal", aria-hidden="true") Done + button.btn.btn-primary.btn-large#wizard-settings-done(type="button") Done diff --git a/app/templates/play/ladder.jade b/app/templates/play/ladder.jade index 0a2b72e40..14a46c52a 100644 --- a/app/templates/play/ladder.jade +++ b/app/templates/play/ladder.jade @@ -6,47 +6,58 @@ block content div#level-description !{description} - a(href="http://www.youtube.com/watch?v=IFvfZiJGDsw&list=HL1392928835&feature=mh_lolz").intro-button.btn.btn-primary.btn-lg Watch the Video - - a(href="/play/level/ladder-tutorial").intro-button.btn.btn-primary.btn-lg Play the Tutorial + if !me.get('anonymous') + a(href="http://www.youtube.com/watch?v=IFvfZiJGDsw&list=HL1392928835&feature=mh_lolz").intro-button.btn.btn-primary.btn-lg Watch the Video + + a(href="/play/level/ladder-tutorial").intro-button.btn.btn-primary.btn-lg Play the Tutorial hr - div#columns.row - for team in teams - div.column.col-md-6 - a(href="/play/ladder/#{levelID}/team/#{team.id}", style="background-color: #{team.primaryColor}").play-button.btn.btn-danger.btn-block.btn-lg - span Play As - span= team.name + + if me.get('anonymous') + div#must-log-in + p + strong Please log in first before playing a ladder game. + button.btn.btn-primary(data-toggle="coco-modal", data-target="modal/login", data-i18n="login.log_in") Log In + button.btn.btn-primary(data-toggle="coco-modal", data-target="modal/signup", data-i18n="login.sign_up") Create Account - table.table.table-bordered.table-condensed.table-hover - //(style="background-color: #{team.bgColor}") - tr - th(colspan=3, style="color: #{team.primaryColor}") - span= team.name - span Leaderboard - tr - th Score - th.name-col-cell Name - th - for session in team.leaderboard.topPlayers.models - - var myRow = session.get('creator') == me.id - tr(class=myRow ? "success" : "") - td.score-cell= session.get('totalScore').toFixed(2) - td.name-col-cell= session.get('creatorName') || "Anonymous" - td - if(!myRow) - a(href="/play/level/#{level.get('slug') || level.id}/?team=#{team.otherTeam}&opponent=#{session.id}") Battle as #{team.otherTeam}! - else - a(href="/play/ladder/#{levelID}/team/#{team.id}") View your #{team.id} matches. - - unless me.attributes.anonymous - hr - button.btn.btn-warning.btn-lg.highlight#simulate-button(style="margin-bottom:10px;") Simulate Games! - p(id="simulation-status-text", style="display:inline; margin-left:10px;") - if simulationStatus - | #{simulationStatus} - else - | By simulating games you can get your game ranked faster! - if me.isAdmin() - button.btn.btn-danger.btn-lg.highlight#simulate-all-button(style="margin-bottom:10px; float: right;") RESET AND SIMULATE GAMES \ No newline at end of file + else + div#columns.row + for team in teams + div.column.col-md-6 + a(href="/play/ladder/#{levelID}/team/#{team.id}", style="background-color: #{team.primaryColor}").play-button.btn.btn-danger.btn-block.btn-lg + span Play As + span= team.name + + table.table.table-bordered.table-condensed.table-hover + //(style="background-color: #{team.bgColor}") + tr + th(colspan=3, style="color: #{team.primaryColor}") + span= team.name + span Leaderboard + tr + th Score + th.name-col-cell Name + th + + for session in team.leaderboard.topPlayers.models + - var myRow = session.get('creator') == me.id + tr(class=myRow ? "success" : "") + td.score-cell= session.get('totalScore').toFixed(2) + td.name-col-cell= session.get('creatorName') || "Anonymous" + td + if(!myRow) + a(href="/play/level/#{level.get('slug') || level.id}/?team=#{team.otherTeam}&opponent=#{session.id}") Battle as #{team.otherTeam}! + else + a(href="/play/ladder/#{levelID}/team/#{team.id}") View your #{team.id} matches. + + unless me.attributes.anonymous + hr + button.btn.btn-warning.btn-lg.highlight#simulate-button(style="margin-bottom:10px;") Simulate Games! + p(id="simulation-status-text", style="display:inline; margin-left:10px;") + if simulationStatus + | #{simulationStatus} + else + | By simulating games you can get your game ranked faster! + if me.isAdmin() + button.btn.btn-danger.btn-lg.highlight#simulate-all-button(style="margin-bottom:10px; float: right;") RESET AND SIMULATE GAMES \ No newline at end of file diff --git a/app/templates/play/ladder/team.jade b/app/templates/play/ladder/team.jade index 8b478371e..22bfd5404 100644 --- a/app/templates/play/ladder/team.jade +++ b/app/templates/play/ladder/team.jade @@ -15,7 +15,14 @@ block content p | After your first submission, your code will also continuously run against other players as they rank themselves. - + + if matches.length + p#your-score + span Your Current Score: + span + strong= score + + div#columns.row div#matches-column.col-md-6 h3.pull-left Ranked Games diff --git a/app/templates/play/level.jade b/app/templates/play/level.jade index 80b295e1a..a247ce1b6 100644 --- a/app/templates/play/level.jade +++ b/app/templates/play/level.jade @@ -27,3 +27,11 @@ .content p(class='footer-link-text') a(title='Send CodeCombat a message', tabindex=-1, data-toggle="coco-modal", data-target="modal/contact", 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/thang_avatar.jade b/app/templates/play/level/thang_avatar.jade index a37e5c311..03be3d62e 100644 --- a/app/templates/play/level/thang_avatar.jade +++ b/app/templates/play/level/thang_avatar.jade @@ -1,6 +1,7 @@ .thang-avatar-wrapper(class="team-" + (thang.team || "neutral")) //canvas(width=100, height=100, title=thang.id + " - " + thang.team) - img.img-responsive(src=avatarURL, title=thang.id + " - " + thang.team) + - var title = thang.id + " - " + thang.team + (thang.type ? ' - type: "' + thang.type + '"' : '') + img.img-responsive(src=avatarURL, title=title) .badge.problems .badge.shared-thangs if includeName diff --git a/app/views/account/settings_view.coffee b/app/views/account/settings_view.coffee index bb028c0cf..031435b5f 100644 --- a/app/views/account/settings_view.coffee +++ b/app/views/account/settings_view.coffee @@ -4,7 +4,7 @@ template = require 'templates/account/settings' forms = require('lib/forms') User = require('models/User') -WizardSettingsTabView = require './wizard_settings_tab_view' +WizardSettingsView = require './wizard_settings_view' module.exports = class SettingsView extends View id: 'account-settings-view' @@ -26,11 +26,12 @@ module.exports = class SettingsView extends View refreshPicturePane: => h = $(@template(@getRenderData())) - new_pane = $('#picture-pane', h) - old_pane = $('#picture-pane') - active = old_pane.hasClass('active') - old_pane.replaceWith(new_pane) - new_pane.addClass('active') if active + newPane = $('#picture-pane', h) + oldPane = $('#picture-pane') + active = oldPane.hasClass('active') + oldPane.replaceWith(newPane) + newPane.i18n() + newPane.addClass('active') if active afterRender: -> super() @@ -46,9 +47,9 @@ module.exports = class SettingsView extends View @chooseTab(location.hash.replace('#','')) @updateWizardColor() - wizardSettingsTabView = new WizardSettingsTabView() - wizardSettingsTabView.on 'change', @save, @ - @insertSubView wizardSettingsTabView + WizardSettingsView = new WizardSettingsView() + WizardSettingsView.on 'change', @save, @ + @insertSubView WizardSettingsView chooseTab: (category) -> id = "##{category}-pane" diff --git a/app/views/account/wizard_settings_tab_view.coffee b/app/views/account/wizard_settings_view.coffee similarity index 90% rename from app/views/account/wizard_settings_tab_view.coffee rename to app/views/account/wizard_settings_view.coffee index ea0462761..b9a73b4c6 100644 --- a/app/views/account/wizard_settings_tab_view.coffee +++ b/app/views/account/wizard_settings_view.coffee @@ -1,12 +1,13 @@ -RootView = require 'views/kinds/RootView' -template = require 'templates/account/wizard_settings_tab' +CocoView = require 'views/kinds/CocoView' +template = require 'templates/account/wizard_settings' {me} = require('lib/auth') ThangType = require 'models/ThangType' SpriteBuilder = require 'lib/sprites/SpriteBuilder' -module.exports = class WizardSettingsTabView extends RootView - id: 'wizard-settings-tab-view' +module.exports = class WizardSettingsView extends CocoView + id: 'wizard-settings-view' template: template + startsLoading: true events: 'change .color-group-checkbox': (e) -> @@ -25,6 +26,7 @@ module.exports = class WizardSettingsTabView extends RootView @wizardThangType.once 'sync', @initCanvas, @ initCanvas: -> + @startsLoading = false @render() @spriteBuilder = new SpriteBuilder(@wizardThangType) @initStage() @@ -44,6 +46,7 @@ module.exports = class WizardSettingsTabView extends RootView c afterRender: -> + return if @startsLoading wizardSettings = me.get('wizard') or {} wizardSettings.colorConfig ?= {} @@ -82,8 +85,6 @@ module.exports = class WizardSettingsTabView extends RootView initStage: -> @stage = new createjs.Stage(@$el.find('canvas')[0]) @updateMovieClip() - createjs.Ticker.setFPS 20 - createjs.Ticker.addEventListener("tick", @stage) updateMovieClip: -> return unless @wizardThangType.loaded @@ -103,7 +104,4 @@ module.exports = class WizardSettingsTabView extends RootView @movieClip.regX = reg.x @movieClip.regY = reg.y @stage.addChild @movieClip - @stage.update() - - destroy: -> - @stage?.removeAllEventListeners() + @stage.update() \ No newline at end of file diff --git a/app/views/editor/level/thangs_tab_view.coffee b/app/views/editor/level/thangs_tab_view.coffee index afc46cf14..33d703b77 100644 --- a/app/views/editor/level/thangs_tab_view.coffee +++ b/app/views/editor/level/thangs_tab_view.coffee @@ -48,15 +48,10 @@ module.exports = class ThangsTabView extends View 'click #extant-thangs-filter button': 'onFilterExtantThangs' shortcuts: - 'esc': -> @selectAddThang() - - onFilterExtantThangs: (e) -> - button = $(e.target).closest('button') - button.button('toggle') - val = button.val() - @thangsTreema.$el.removeClass(@lastHideClass) if @lastHideClass - @thangsTreema.$el.addClass(@lastHideClass = "hide-except-#{val}") if val - + 'esc': 'selectAddThang' + 'delete, del, backspace': 'deleteSelectedExtantThang' + 'left': -> @moveAddThangSelection -1 + 'right': -> @moveAddThangSelection 1 constructor: (options) -> super options @@ -102,12 +97,12 @@ module.exports = class ThangsTabView extends View $('#thangs-list').bind 'mousewheel', @preventBodyScrollingInThangList @$el.find('#extant-thangs-filter button:first').button('toggle') - # TODO: move these into the shortcuts list - key 'left', _.bind @moveAddThangSelection, @, -1 - key 'right', _.bind @moveAddThangSelection, @, 1 - key 'delete, del, backspace', @deleteSelectedExtantThang - key 'f', => Backbone.Mediator.publish('level-set-debug', debug: not @surface.debug) - key 'g', => Backbone.Mediator.publish('level-set-grid', grid: not @surface.gridShowing()) + onFilterExtantThangs: (e) -> + button = $(e.target).closest('button') + button.button('toggle') + val = button.val() + @thangsTreema.$el.removeClass(@lastHideClass) if @lastHideClass + @thangsTreema.$el.addClass(@lastHideClass = "hide-except-#{val}") if val preventBodyScrollingInThangList: (e) -> @scrollTop += (if e.deltaY < 0 then 1 else -1) * 30 diff --git a/app/views/kinds/CocoView.coffee b/app/views/kinds/CocoView.coffee index 081bd0076..a4b70ee2a 100644 --- a/app/views/kinds/CocoView.coffee +++ b/app/views/kinds/CocoView.coffee @@ -129,7 +129,7 @@ module.exports = class CocoView extends Backbone.View # Loading RootViews showLoading: ($el=@$el) -> - $el.find('>').hide() + $el.find('>').addClass('hidden') $el.append($('
') .append('

Loading

') .append('
')) @@ -138,7 +138,7 @@ module.exports = class CocoView extends Backbone.View hideLoading: -> return unless @_lastLoading? @_lastLoading.find('.loading-screen').remove() - @_lastLoading.find('>').show() + @_lastLoading.find('>').removeClass('hidden') @_lastLoading = null # Loading ModalViews diff --git a/app/views/kinds/RootView.coffee b/app/views/kinds/RootView.coffee index d29023af1..6dbc36be0 100644 --- a/app/views/kinds/RootView.coffee +++ b/app/views/kinds/RootView.coffee @@ -22,8 +22,8 @@ module.exports = class RootView extends CocoView logoutUser($('#login-email').val()) showWizardSettingsModal: -> - WizardSettingsView = require('views/modal/wizard_settings_modal') - subview = new WizardSettingsView {} + WizardSettingsModal = require('views/modal/wizard_settings_modal') + subview = new WizardSettingsModal {} @openModalView subview showLoading: ($el) -> diff --git a/app/views/modal/wizard_settings_modal.coffee b/app/views/modal/wizard_settings_modal.coffee index 61a2b53ff..794509521 100644 --- a/app/views/modal/wizard_settings_modal.coffee +++ b/app/views/modal/wizard_settings_modal.coffee @@ -2,79 +2,44 @@ View = require 'views/kinds/ModalView' template = require 'templates/modal/wizard_settings' WizardSprite = require 'lib/surface/WizardSprite' ThangType = require 'models/ThangType' +{me} = require 'lib/auth' +forms = require('lib/forms') -module.exports = class WizardSettingsView extends View +module.exports = class WizardSettingsModal extends View id: "wizard-settings-modal" template: template closesOnClickOutside: false events: 'change #wizard-settings-name': 'onNameChange' - 'click #random-name': 'onRandomNameClick' - 'click #wizard-settings-done': 'saveSettings' - - render: -> - me.set('name', @randomName()) if not me.get('name') - super() - - onRandomNameClick: => - $('#wizard-settings-name').val(@randomName()) - @saveSettings() - - randomName: -> - return NameGenerator.getName(7, 9) + 'click #wizard-settings-done': 'onWizardSettingsDone' afterRender: -> - super() - @colorSlider = $( "#wizard-settings-color-1", @$el).slider({ animate: "fast" }) - @colorSlider.slider('value', me.get('wizardColor1')*100) - @colorSlider.on('slide',@onSliderChange) - @colorSlider.on('slidechange',@onSliderChange) - @stage = new createjs.Stage($('canvas', @$el)[0]) - @saveChanges = _.debounce(@saveChanges, 1000) + WizardSettingsView = require 'views/account/wizard_settings_view' + view = new WizardSettingsView() + @insertSubView view - wizOriginal = "52a00d55cf1818f2be00000b" - url = "/db/thang_type/#{wizOriginal}/version" - @wizardType = new ThangType() - @wizardType.url = -> url - @wizardType.fetch() - @wizardType.once 'sync', @initCanvas - - initCanvas: => - spriteOptions = thangID: "Config Wizard", resolutionFactor: 3 - @wizardSprite = new WizardSprite @wizardType, spriteOptions - @wizardSprite.setColorHue(me.get('wizardColor1')) - @wizardDisplayObject = @wizardSprite.displayObject - @wizardDisplayObject.x = 10 - @wizardDisplayObject.y = 15 - @wizardDisplayObject.scaleX = @wizardDisplayObject.scaleY = 3.0 - @stage.addChild(@wizardDisplayObject) - @updateSpriteColor() - @stage.update() - - onSliderChange: => - @updateSpriteColor() - @saveSettings() - - getColorHue: -> - @colorSlider.slider('value') / 100 - - updateSpriteColor: -> - colorHue = @getColorHue() - @wizardSprite.setColorHue(colorHue) - @stage.update() - - onNameChange: => - @saveSettings() - - saveSettings: -> + onNameChange: -> me.set('name', $('#wizard-settings-name').val()) - me.set('wizardColor1', @getColorHue()) - @saveChanges() - saveChanges: -> - me.save() + onWizardSettingsDone: -> + forms.clearFormAlerts(@$el) + res = me.validate() + if res? + forms.applyErrorsToForm(@$el, res) + return - destroy: -> - @wizardSprite?.destroy() - super() + res = me.save() + return unless res + save = $('#save-button', @$el).text($.i18n.t('common.saving', defaultValue: 'Saving...')) + .addClass('btn-info').show().removeClass('btn-danger') + + res.error => + errors = JSON.parse(res.responseText) + forms.applyErrorsToForm(@$el, errors) + @disableModalInProgress(@$el) + res.success (model, response, options) => + @hide() + + @enableModalInProgress(@$el) + me.save() \ No newline at end of file diff --git a/app/views/play/ladder/team_view.coffee b/app/views/play/ladder/team_view.coffee index 138f62c19..32adcbc24 100644 --- a/app/views/play/ladder/team_view.coffee +++ b/app/views/play/ladder/team_view.coffee @@ -90,6 +90,7 @@ module.exports = class LadderTeamView extends RootView ctx.matches = (convertMatch(match) for match in @session.get('matches') or []) ctx.matches.reverse() + ctx.score = (@session.get('totalScore') or 10).toFixed(2) ctx afterRender: -> diff --git a/app/views/play/level/playback_view.coffee b/app/views/play/level/playback_view.coffee index 26e8454c1..4e54c609b 100644 --- a/app/views/play/level/playback_view.coffee +++ b/app/views/play/level/playback_view.coffee @@ -36,8 +36,8 @@ module.exports = class PlaybackView extends View shortcuts: '⌘+p, p, ctrl+p': 'onTogglePlay' - '[': 'onScrubBack' - ']': 'onScrubForward' + '⌘+[, ctrl+[': 'onScrubBack' + '⌘+], ctrl+]': 'onScrubForward' constructor: -> super(arguments...) @@ -169,7 +169,7 @@ module.exports = class PlaybackView extends View if @clickingSlider @clickingSlider = false @wasPlaying = false - @onSetPlaying {playing: false} + Backbone.Mediator.publish 'level-set-playing', {playing: false} @$el.find('.scrubber-handle').effect('bounce', {times: 2}) ) diff --git a/app/views/play/level/tome/cast_button_view.coffee b/app/views/play/level/tome/cast_button_view.coffee index 2a3cf5644..3264d9283 100644 --- a/app/views/play/level/tome/cast_button_view.coffee +++ b/app/views/play/level/tome/cast_button_view.coffee @@ -17,7 +17,7 @@ module.exports = class CastButtonView extends View @spells = options.spells @levelID = options.levelID isMac = navigator.platform.toUpperCase().indexOf('MAC') isnt -1 - @castShortcut = "⇧↩" + @castShortcut = "⇧↵" @castShortcutVerbose = "Shift+Enter" getRenderData: (context={}) -> @@ -35,7 +35,7 @@ module.exports = class CastButtonView extends View # TODO: use a User setting instead of localStorage delay = localStorage.getItem 'autocastDelay' delay ?= 5000 - if @levelID is 'project-dota' + if @levelID in ['project-dota', 'brawlwood', 'ladder-tutorial'] delay = 90019001 @setAutocastDelay delay diff --git a/app/views/play/level/tome/spell_debug_view.coffee b/app/views/play/level/tome/spell_debug_view.coffee index 2b08611aa..f59b84501 100644 --- a/app/views/play/level/tome/spell_debug_view.coffee +++ b/app/views/play/level/tome/spell_debug_view.coffee @@ -21,6 +21,10 @@ module.exports = class DebugView extends View @ace = options.ace @thang = options.thang @variableStates = {} + @globals = {Math: Math, _: _} # ... add more as documented + for className, klass of serializedClasses + @globals[className] = klass + @onMouseMove = _.throttle @onMouseMove, 25 afterRender: -> super() @@ -30,10 +34,11 @@ module.exports = class DebugView extends View @update() onMouseMove: (e) => + return if @destroyed pos = e.getDocumentPosition() endOfDoc = pos.row is @ace.getSession().getDocument().getLength() - 1 it = new TokenIterator e.editor.session, pos.row, pos.column - isIdentifier = (t) -> t and (t.type is 'identifier' or t.value is 'this') + isIdentifier = (t) => t and (t.type is 'identifier' or t.value is 'this' or @globals[t.value]) while it.getCurrentTokenRow() is pos.row and not isIdentifier(token = it.getCurrentToken()) it.stepBackward() break unless token @@ -52,7 +57,7 @@ module.exports = class DebugView extends View token = prev start = it.getCurrentTokenColumn() chain.unshift token.value - if token and (token.value of @variableStates or token.value is "this") + if token and (token.value of @variableStates or token.value is "this" or @globals[token.value]) @variableChain = chain offsetX = e.domEvent.offsetX ? e.clientX - $(e.domEvent.target).offset().left offsetY = e.domEvent.offsetY ? e.clientY - $(e.domEvent.target).offset().top @@ -76,6 +81,10 @@ module.exports = class DebugView extends View @$el.show().css(@pos) else @$el.hide() + if @variableChain?.length is 2 + Backbone.Mediator.publish 'tome:spell-debug-property-hovered', property: @variableChain[1], owner: @variableChain[0] + else + Backbone.Mediator.publish 'tome:spell-debug-property-hovered', property: null @updateMarker() updateMarker: -> @@ -92,7 +101,7 @@ module.exports = class DebugView extends View return "" if value is @thang and depth if depth is 2 if value.constructor?.className is "Thang" - value = "<#{value.spriteName} - #{value.id}, #{if value.pos then value.pos.toString() else 'non-physical'}>" + value = "<#{value.type or value.spriteName} - #{value.id}, #{if value.pos then value.pos.toString() else 'non-physical'}>" else value = value.toString() return value @@ -124,8 +133,11 @@ module.exports = class DebugView extends View for prop, i in chain if prop is "this" value = @thang + else if i is 0 + value = @variableStates[prop] + if typeof value is "undefined" then value = @globals[prop] else - value = (if i is 0 then @variableStates else value)[prop] + value = value[prop] keys.push prop break unless value if theClass = serializedClasses[value.CN] diff --git a/app/views/play/level/tome/spell_list_tab_entry_view.coffee b/app/views/play/level/tome/spell_list_tab_entry_view.coffee index 897241cc2..b2732f9cf 100644 --- a/app/views/play/level/tome/spell_list_tab_entry_view.coffee +++ b/app/views/play/level/tome/spell_list_tab_entry_view.coffee @@ -74,7 +74,7 @@ module.exports = class SpellListTabEntryView extends SpellListEntryView formatPopover: (doc) -> content = popoverTemplate doc: doc, marked: marked, argumentExamples: (arg.example or arg.default or arg.name for arg in doc.args ? []) owner = @thang - content = content.replace /#{spriteName}/g, @thang.spriteName # No quotes like we'd get with @formatValue + content = content.replace /#{spriteName}/g, @thang.type ? @thang.spriteName # Prefer type, and excluded the quotes we'd get with @formatValue content.replace /\#\{(.*?)\}/g, (s, properties) => @formatValue downTheChain(owner, properties.split('.')) formatValue: (v) -> @@ -101,12 +101,15 @@ module.exports = class SpellListTabEntryView extends SpellListEntryView onClick: (e) -> # Don't call super onDropdownClick: (e) -> + return unless @controlsEnabled Backbone.Mediator.publish 'tome:toggle-spell-list' onCodeReload: -> + return unless @controlsEnabled Backbone.Mediator.publish "tome:reload-code", spell: @spell onBeautifyClick: -> + return unless @controlsEnabled Backbone.Mediator.publish "spell-beautify", spell: @spell updateReloadButton: -> diff --git a/app/views/play/level/tome/spell_palette_entry_view.coffee b/app/views/play/level/tome/spell_palette_entry_view.coffee index d6b2907ff..770f71e0a 100644 --- a/app/views/play/level/tome/spell_palette_entry_view.coffee +++ b/app/views/play/level/tome/spell_palette_entry_view.coffee @@ -66,6 +66,7 @@ module.exports = class SpellPaletteEntryView extends View 'surface:frame-changed': "onFrameChanged" 'tome:palette-hovered': "onPaletteHovered" 'tome:palette-pin-toggled': "onPalettePinToggled" + 'tome:spell-debug-property-hovered': 'onSpellDebugPropertyHovered' events: 'mouseenter': 'onMouseEnter' @@ -83,7 +84,11 @@ module.exports = class SpellPaletteEntryView extends View @doc.shortName = @doc.shorterName = @doc.title = @doc.name else @doc.owner ?= 'this' - suffix = if @doc.type is 'function' then '()' else '' + suffix = '' + if @doc.type is 'function' + argNames = (arg.name for arg in @doc.args ? []).join(', ') + argNames = '...' if argNames.length > 6 + suffix = "(#{argNames})" @doc.shortName = "#{@doc.owner}.#{@doc.name}#{suffix};" if @doc.owner is 'this' or options.tabbify @doc.shorterName = "#{@doc.name}#{suffix}" @@ -184,6 +189,16 @@ module.exports = class SpellPaletteEntryView extends View return if e.entry is @ @otherPopoverPinned = e.pinned + onSpellDebugPropertyHovered: (e) -> + matched = e.property is @doc.name and e.owner is @doc.owner + if matched and not @debugHovered + @debugHovered = true + @togglePinned() unless @popoverPinned + else if @debugHovered and not matched + @debugHovered = false + @togglePinned() if @popoverPinned + null + destroy: -> $('.popover.pinned').remove() if @popoverPinned # @$el.popover('destroy') doesn't work @togglePinned() if @popoverPinned diff --git a/app/views/play/level/tome/spell_palette_view.coffee b/app/views/play/level/tome/spell_palette_view.coffee index 6e25157a1..e045d1842 100644 --- a/app/views/play/level/tome/spell_palette_view.coffee +++ b/app/views/play/level/tome/spell_palette_view.coffee @@ -46,24 +46,42 @@ module.exports = class SpellPaletteView extends View for doc in (lc.get('propertyDocumentation') ? []) allDocs[doc.name] ?= [] allDocs[doc.name].push doc + if doc.type is 'snippet' then doc.owner = 'snippets' #allDocs[doc.name] = doc for doc in (lc.get('propertyDocumentation') ? []) for lc in lcs - props = _.sortBy @thang.programmableProperties ? [] - snippets = _.sortBy @thang.programmableSnippets ? [] - shortenize = props.length + snippets.length > 6 - tabbify = props.length + snippets.length >= 10 + propStorage = + 'this': 'programmableProperties' + more: 'moreProgrammableProperties' + Math: 'programmableMathProperties' + Vector: 'programmableVectorProperties' + snippets: 'programmableSnippets' + count = 0 + propGroups = {} + for owner, storage of propStorage + added = propGroups[owner] = _.sortBy(@thang[storage] ? []).slice() + count += added.length + + shortenize = count > 6 + tabbify = count >= 10 @entries = [] - for type, props of {props: props.slice(), snippets: snippets.slice()} + for owner, props of propGroups for prop in props - doc = allDocs[prop]?.shift() ? prop # Add one doc per instance of the prop name (this is super gimp) - @entries.push @addEntry(doc, shortenize, tabbify, type is 'snippets') + doc = _.find (allDocs[prop] ? []), (doc) -> + return true if doc.owner is owner + return (owner is 'this' or owner is 'more') and (not doc.owner? or doc.owner is 'this') + console.log 'could not find doc for', prop, 'from', allDocs[prop], 'for', owner, 'of', propGroups unless doc + doc ?= prop + @entries.push @addEntry(doc, shortenize, tabbify, owner is 'snippets') + groupForEntry = (entry) -> + return 'more' if entry.doc.owner is 'this' and entry.doc.name in propGroups.more + entry.doc.owner @entries = _.sortBy @entries, (entry) -> - order = ['this', 'Math', 'Vector', 'snippets'] - index = order.indexOf entry.doc.owner + order = ['this', 'more', 'Math', 'Vector', 'snippets'] + index = order.indexOf groupForEntry entry index = String.fromCharCode if index is -1 then order.length else index index += entry.doc.name if tabbify and _.find @entries, ((entry) -> entry.doc.owner isnt 'this') - @entryGroups = _.groupBy @entries, (entry) -> entry.doc.owner + @entryGroups = _.groupBy @entries, groupForEntry else defaultGroup = $.i18n.t("play_level.tome_available_spells", defaultValue: "Available Spells") @entryGroups = {} diff --git a/app/views/play/level/tome/spell_view.coffee b/app/views/play/level/tome/spell_view.coffee index e6714feab..f12ea380c 100644 --- a/app/views/play/level/tome/spell_view.coffee +++ b/app/views/play/level/tome/spell_view.coffee @@ -326,7 +326,10 @@ module.exports = class SpellView extends View isCast = not _.isEmpty(aether.metrics) or _.some aether.problems.errors, {type: 'runtime'} @problems = [] annotations = [] + seenProblemKeys = {} for aetherProblem, problemIndex in aether.getAllProblems() + continue if aetherProblem.userInfo.key of seenProblemKeys + seenProblemKeys[aetherProblem.userInfo.key] = true @problems.push problem = new Problem aether, aetherProblem, @ace, isCast and problemIndex is 0, isCast annotations.push problem.annotation if problem.annotation @aceSession.setAnnotations annotations @@ -452,11 +455,11 @@ module.exports = class SpellView extends View markerRange.end.detach() @aceSession.removeMarker markerRange.id @markerRanges = [] - @debugView.setVariableStates {} @aceSession.removeGutterDecoration row, 'executing' for row in [0 ... @aceSession.getLength()] $(@ace.container).find('.ace_gutter-cell.executing').removeClass('executing') if not executed.length or (@spell.name is "plan" and @spellThang.castAether.metrics.statementsExecuted < 20) @toolbarView?.toggleFlow false + @debugView.setVariableStates {} return lastExecuted = _.last executed @toolbarView?.toggleFlow true @@ -464,6 +467,7 @@ module.exports = class SpellView extends View @toolbarView?.setCallState states[currentCallIndex], statementIndex, currentCallIndex, @spellThang.castAether.metrics marked = {} lastExecuted = lastExecuted[0 .. @toolbarView.statementIndex] if @toolbarView?.statementIndex? + gotVariableStates = false for state, i in lastExecuted [start, end] = state.range clazz = if i is lastExecuted.length - 1 then 'executing' else 'executed' @@ -473,6 +477,7 @@ module.exports = class SpellView extends View markerType = "fullLine" else @debugView.setVariableStates state.variables + gotVariableStates = true markerType = "text" markerRange = new Range start.row, start.col, end.row, end.col markerRange.start = @aceDoc.createAnchor markerRange.start @@ -480,6 +485,7 @@ module.exports = class SpellView extends View markerRange.id = @aceSession.addMarker markerRange, clazz, markerType @markerRanges.push markerRange @aceSession.addGutterDecoration start.row, clazz if clazz is 'executing' + @debugView.setVariableStates {} unless gotVariableStates null highlightComments: -> diff --git a/app/views/play/level/tome/thang_list_entry_view.coffee b/app/views/play/level/tome/thang_list_entry_view.coffee index 3dabc5492..f5e44c183 100644 --- a/app/views/play/level/tome/thang_list_entry_view.coffee +++ b/app/views/play/level/tome/thang_list_entry_view.coffee @@ -21,6 +21,7 @@ module.exports = class ThangListEntryView extends View 'surface:frame-changed': "onFrameChanged" 'level-set-letterbox': 'onSetLetterbox' 'tome:thang-list-entry-popover-shown': 'onThangListEntryPopoverShown' + 'surface:coordinates-shown': 'onSurfaceCoordinatesShown' events: 'click': 'onClick' @@ -86,38 +87,47 @@ module.exports = class ThangListEntryView extends View onMouseEnter: (e) -> return unless @controlsEnabled and @spells.length - @showSpells() + @clearTimeouts() + @showSpellsTimeout = _.delay @showSpells, 100 onMouseLeave: (e) -> return unless @controlsEnabled and @spells.length - clearTimeout @hideSpellsTimeout if @hideSpellsTimeout + @clearTimeouts() @hideSpellsTimeout = _.delay @hideSpells, 100 + clearTimeouts: -> + clearTimeout @showSpellsTimeout if @showSpellsTimeout + clearTimeout @hideSpellsTimeout if @hideSpellsTimeout + @showSpellsTimeout = @hideSpellsTimeout = null + onThangListEntryPopoverShown: (e) -> # I couldn't figure out how to get the mouseenter / mouseleave to always work, so this is a fallback # to hide our popover is another Thang's popover gets shown. return if e.entry is @ @hideSpells() + onSurfaceCoordinatesShown: (e) -> + # Definitely aren't hovering over this. + @hideSpells() + showSpells: => + @clearTimeouts() @sortSpells() @$el.data('bs.popover').options.content = @getSpellListHTML() @$el.popover('setContent').popover('show') @$el.parent().parent().parent().i18n() - clearTimeout @hideSpellsTimeout if @hideSpellsTimeout - @hideSpellsTimeout = null @popover = @$el.parent().parent().parent().find('.popover') @popover.off 'mouseenter mouseleave' - @popover.mouseenter (e) => @onMouseEnter() - @popover.mouseleave (e) => @onMouseLeave() + @popover.mouseenter (e) => @showSpells() if @controlsEnabled + @popover.mouseleave (e) => @hideSpells() thangID = @thang.id @popover.find('code').click (e) -> Backbone.Mediator.publish "level-select-sprite", thangID: thangID, spellName: $(@).data 'spell-name' Backbone.Mediator.publish 'tome:thang-list-entry-popover-shown', entry: @ hideSpells: => + @clearTimeouts() @$el.popover('hide') - @hideSpellsTimeout = null getSpellListHTML: -> spellsPopoverTemplate {spells: @spells} @@ -129,13 +139,16 @@ module.exports = class ThangListEntryView extends View onSetLetterbox: (e) -> if e.on then @reasonsToBeDisabled.letterbox = true else delete @reasonsToBeDisabled.letterbox @updateControls() + onDisableControls: (e) -> return if e.controls and not ('surface' in e.controls) # disable selection? @reasonsToBeDisabled.controls = true @updateControls() + onEnableControls: (e) -> delete @reasonsToBeDisabled.controls @updateControls() + updateControls: -> enabled = _.keys(@reasonsToBeDisabled).length is 0 return if enabled is @controlsEnabled diff --git a/app/views/play/level/tome/tome_view.coffee b/app/views/play/level/tome/tome_view.coffee index 151341e7c..5b525ad39 100644 --- a/app/views/play/level/tome/tome_view.coffee +++ b/app/views/play/level/tome/tome_view.coffee @@ -151,9 +151,15 @@ module.exports = class TomeView extends View @cast() cast: -> - for spellKey, spell of @spells - for thangID, spellThang of spell.thangs - spellThang.aether.options.includeFlow = spellThang.aether.originalOptions.includeFlow = spellThang is @spellView?.spellThang + if @options.levelID is 'project-dota' + # For performance reasons, only includeFlow on the currently Thang. + for spellKey, spell of @spells + for thangID, spellThang of spell.thangs + hadFlow = Boolean spellThang.aether.options.includeFlow + willHaveFlow = spellThang is @spellView?.spellThang + spellThang.aether.options.includeFlow = spellThang.aether.originalOptions.includeFlow = willHaveFlow + spellThang.aether.transpile spell.source unless hadFlow is willHaveFlow + #console.log "set includeFlow to", spellThang.aether.options.includeFlow, "for", thangID, "of", spellKey Backbone.Mediator.publish 'tome:cast-spells', spells: @spells onToggleSpellList: (e) -> diff --git a/app/views/play/level_view.coffee b/app/views/play/level_view.coffee index bfa3a0437..453e4f7c7 100644 --- a/app/views/play/level_view.coffee +++ b/app/views/play/level_view.coffee @@ -116,6 +116,10 @@ module.exports = class PlayLevelView extends View getRenderData: -> c = super() c.world = @world + if me.get('hourOfCode') and me.lang() 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: -> @@ -194,7 +198,7 @@ module.exports = class PlayLevelView extends View afterInsert: -> super() -# @showWizardSettingsModal() if not me.get('name') + @showWizardSettingsModal() if not me.get('name') # callbacks diff --git a/bin/coco-dev-server b/bin/coco-dev-server index e6cc1813c..c76923672 100755 --- a/bin/coco-dev-server +++ b/bin/coco-dev-server @@ -8,5 +8,5 @@ current_directory = os.path.dirname(os.path.realpath(sys.argv[0])) coco_path = os.getenv("COCO_DIR",os.path.join(current_directory,os.pardir)) nodemon_path = coco_path + os.sep + "node_modules" + os.sep + ".bin" + os.sep + "nodemon" -call(nodemon_path + " . --ext \".coffee|.js\" --watch server --watch app.js --watch server_config.js",shell=True,cwd=coco_path) +call(nodemon_path + " . --ext \".coffee|.js\" --watch server --watch app.js --watch server_config.js --watch server_setup.coffee",shell=True,cwd=coco_path) diff --git a/server/queues/scoring.coffee b/server/queues/scoring.coffee index 49fef561c..4592ec74c 100644 --- a/server/queues/scoring.coffee +++ b/server/queues/scoring.coffee @@ -23,6 +23,25 @@ connectToScoringQueue = -> if error? then throw new Error "There was an error registering the scoring queue: #{error}" scoringTaskQueue = data log.info "Connected to scoring task queue!" + +module.exports.addPairwiseTaskToQueue = (req, res) -> + taskPair = req.body.sessions + #unless isUserAdmin req then return errors.forbidden res, "You do not have the permissions to submit that game to the leaderboard" + #fetch both sessions + LevelSession.findOne(_id:taskPair[0]).lean().exec (err, firstSession) => + if err? then return errors.serverError res, "There was an error fetching the first session in the pair" + LevelSession.find(_id:taskPair[1]).exec (err, secondSession) => + if err? then return errors.serverError res, "There was an error fetching the second session" + try + taskPairs = generateTaskPairs(secondSession, firstSession) + catch e + if e then return errors.serverError res, "There was an error generating the task pairs" + + sendEachTaskPairToTheQueue taskPairs, (taskPairError) -> + if taskPairError? then return errors.serverError res, "There was an error sending the task pairs to the queue" + + sendResponseObject req, res, {"message":"All task pairs were succesfully sent to the queue"} + module.exports.createNewTask = (req, res) -> requestSessionID = req.body.session diff --git a/server/routes/auth.coffee b/server/routes/auth.coffee index 5f050dd06..e5d3f9367 100644 --- a/server/routes/auth.coffee +++ b/server/routes/auth.coffee @@ -5,6 +5,7 @@ UserHandler = require('../users/user_handler') config = require '../../server_config' errors = require '../commons/errors' mail = require '../commons/mail' +languages = require '../routes/languages' module.exports.setup = (app) -> authentication.serializeUser((user, done) -> done(null, user._id)) @@ -43,10 +44,36 @@ module.exports.setup = (app) -> ) app.get('/auth/whoami', (req, res) -> - res.setHeader('Content-Type', 'text/json'); + if req.user + sendSelf(req, res) + else + user = new User({anonymous:true}) + user.set 'testGroupNumber', Math.floor(Math.random() * 256) # also in app/lib/auth + user.set 'preferredLanguage', languages.languageCodeFromAcceptedLanguages req.acceptedLanguages + makeNext = (req, res) -> -> sendSelf(req, res) + next = makeNext(req, res) + loginUser(req, res, user, false, next) + ) + + sendSelf = (req, res) -> + res.setHeader('Content-Type', 'text/json') res.send(UserHandler.formatEntity(req, req.user)) res.end() - ) + + loginUser = (req, res, user, send=true, next=null) -> + user.save((err) -> + if err + return @sendDatabaseError(res, err) + + req.logIn(user, (err) -> + if err + return @sendDatabaseError(res, err) + + if send + return @sendSuccess(res, user) + next() if next + ) + ) app.post('/auth/logout', (req, res) -> req.logout() diff --git a/server/routes/queue.coffee b/server/routes/queue.coffee index f8bfa0613..85ee0f8e3 100644 --- a/server/routes/queue.coffee +++ b/server/routes/queue.coffee @@ -6,9 +6,13 @@ scoringQueue = require '../queues/scoring' module.exports.setup = (app) -> scoringQueue.setup() + #app.post '/queue/scoring/pairwise', (req, res) -> + # handler = loadQueueHandler 'scoring' + # handler.addPairwiseTaskToQueue req, res + app.all '/queue/*', (req, res) -> setResponseHeaderToJSONContentType res - + queueName = getQueueNameFromPath req.path try handler = loadQueueHandler queueName @@ -23,6 +27,7 @@ module.exports.setup = (app) -> catch error log.error error sendQueueError req, res, error + setResponseHeaderToJSONContentType = (res) -> res.setHeader('Content-Type', 'application/json') diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee index af806a968..c755a7c33 100644 --- a/server/users/user_handler.coffee +++ b/server/users/user_handler.coffee @@ -3,7 +3,6 @@ crypto = require 'crypto' request = require 'request' User = require './User' Handler = require '../commons/Handler' -languages = require '../routes/languages' mongoose = require 'mongoose' config = require '../../server_config' errors = require '../commons/errors' @@ -171,29 +170,4 @@ UserHandler = class UserHandler extends Handler res.redirect(document?.get('photoURL') or '/images/generic-wizard-icon.png') res.end() -module.exports = new UserHandler() - -module.exports.setupMiddleware = (app) -> - app.use (req, res, next) -> - if req.user - next() - else - user = new User({anonymous:true}) - user.set 'testGroupNumber', Math.floor(Math.random() * 256) # also in app/lib/auth - user.set 'preferredLanguage', languages.languageCodeFromAcceptedLanguages req.acceptedLanguages - loginUser(req, res, user, false, next) - -loginUser = (req, res, user, send=true, next=null) -> - user.save((err) -> - if err - return @sendDatabaseError(res, err) - - req.logIn(user, (err) -> - if err - return @sendDatabaseError(res, err) - - if send - return @sendSuccess(res, user) - next() if next - ) - ) +module.exports = new UserHandler() \ No newline at end of file diff --git a/server_setup.coffee b/server_setup.coffee index d0afd259d..9f94da179 100644 --- a/server_setup.coffee +++ b/server_setup.coffee @@ -40,9 +40,6 @@ setupOneSecondDelayMiddlware = (app) -> if(config.slow_down) app.use((req, res, next) -> setTimeout((-> next()), 1000)) -setupUserMiddleware = (app) -> - user.setupMiddleware(app) - setupMiddlewareToSendOldBrowserWarningWhenPlayersViewLevelDirectly = (app) -> isOldBrowser = (req) -> # https://github.com/biggora/express-useragent/blob/master/lib/express-useragent.js @@ -66,7 +63,6 @@ exports.setupMiddleware = (app) -> setupExpressMiddleware app setupPassportMiddleware app setupOneSecondDelayMiddlware app - setupUserMiddleware app ###Routing function implementations###