LoadingScreen -> level intro behavior

This commit is contained in:
Nick Winter 2015-11-10 15:22:09 -08:00
parent 4497334e6d
commit 2259076800
12 changed files with 240 additions and 107 deletions

View file

@ -248,3 +248,39 @@ module.exports.getPrepaidCodeAmount = getPrepaidCodeAmount = (price=999, users=0
return 0 unless users > 0 and months > 0
total = price * users * months
total
module.exports.filterMarkdownCodeLanguages = (text) ->
currentLanguage = me.get('aceConfig')?.language or 'python'
excludedLanguages = _.without ['javascript', 'python', 'coffeescript', 'clojure', 'lua', 'io'], currentLanguage
exclusionRegex = new RegExp "```(#{excludedLanguages.join('|')})\n[^`]+```\n?", 'gm'
text.replace exclusionRegex, ''
module.exports.aceEditModes = aceEditModes =
'javascript': 'ace/mode/javascript'
'coffeescript': 'ace/mode/coffee'
'python': 'ace/mode/python'
'clojure': 'ace/mode/clojure'
'lua': 'ace/mode/lua'
'io': 'ace/mode/text'
module.exports.initializeACE = (el, codeLanguage) ->
contents = $(el).text().trim()
editor = ace.edit el
editor.setOptions maxLines: Infinity
editor.setReadOnly true
editor.setTheme 'ace/theme/textmate'
editor.setShowPrintMargin false
editor.setShowFoldWidgets false
editor.setHighlightActiveLine false
editor.setHighlightActiveLine false
editor.setBehavioursEnabled false
editor.renderer.setShowGutter false
editor.setValue contents
editor.clearSelection()
session = editor.getSession()
session.setUseWorker false
session.setMode aceEditModes[codeLanguage]
session.setWrapLimitRange null
session.setUseWrapMode true
session.setNewLineMode 'unix'
return editor

View file

@ -79,15 +79,15 @@ _.extend LevelSessionSchema.properties,
currentScriptOffset:
type: 'number'
selected:
selected: # Not tracked any more, delete with old level types
type: [
'null'
'string'
]
playing:
type: 'boolean' # Not tracked any more
type: 'boolean' # Not tracked any more, delete with old level types
frame:
type: 'number' # Not tracked any more
type: 'number' # Not tracked any more, delete with old level types
thangs: # ... what is this? Is this used?
type: 'object'
additionalProperties:

View file

@ -72,7 +72,13 @@ $level-resize-transition-time: 0.5s
width: 55%
position: relative
overflow: hidden
@include transition($level-resize-transition-time ease-out)
@include transition(all $level-resize-transition-time ease-out, z-index 1.2s linear)
z-index: 0
&.preview-overlay
z-index: 20
#goals-view
visibility: hidden
canvas#webgl-surface
background-color: #333

View file

@ -8,24 +8,48 @@
background-position: top $backgroundPosition
background-size: contain
$UNVEIL_TIME: 1.2s
#level-loading-view
width: 100%
height: 100%
position: absolute
z-index: 20
$UNVEIL_TIME: 1.2s
&.unveiled
pointer-events: none
.loading-details
&.preview-screen
background-color: rgba(0, 0, 0, 0.5)
.left-wing, .right-wing
width: 100%
height: 100%
position: absolute
pointer-events: none
.left-wing
@include wing-background('/images/level/loading_left_wing_1920.jpg', right)
@media screen and ( max-width: 1366px )
@include wing-background('/images/level/loading_left_wing_1366.jpg', right)
left: -50%
@include transition(all $UNVEIL_TIME ease)
.right-wing
@include wing-background('/images/level/loading_right_wing_1920.jpg', left)
@media screen and ( max-width: 1366px )
@include wing-background('/images/level/loading_right_wing_1366.jpg', left)
right: -50%
@include transition(all $UNVEIL_TIME ease)
#loading-details
position: absolute
top: 86px
left: 50%
right: 50%
$WIDTH: 450px
width: $WIDTH
height: 450px
margin-left: (-$WIDTH / 2)
margin-right: (-$WIDTH / 2)
z-index: 100
background: transparent url(/images/level/code_editor_background.png) no-repeat
background-size: 100% 100%
@ -34,9 +58,22 @@
padding: 80px 80px 40px 80px
text-align: center
// http://matthewlein.com/ceaser/ Bounce down a bit, then snap up.
@include transition(top $UNVEIL_TIME cubic-bezier(0.285, -0.595, 0.670, -0.600))
@include transition($UNVEIL_TIME cubic-bezier(0.285, -0.595, 0.670, -0.600))
font-family: 'Open Sans Condensed'
&.preview
top: 0
right: 0
margin-right: 0
width: 45%
height: auto
pointer-events: all
@include transition($UNVEIL_TIME ease-in-out)
padding: 80px 70px 40px 50px
.progress-or-start-container.intro-footer
bottom: 30px
.level-loading-goals
text-align: left
@ -49,12 +86,21 @@
font-size: 20px
color: black
.intro-doc
text-align: left
font-size: 16px
overflow: scroll
img
max-width: 100%
.progress-or-start-container
position: absolute
bottom: 95px
width: 325px
height: 80px
left: 48px
right: 77px
@include transition(bottom $UNVEIL_TIME ease-out)
.load-progress
width: 100%
@ -131,21 +177,7 @@
width: 401px
color: #666
.left-wing, .right-wing
width: 100%
height: 100%
position: absolute
.left-wing
@include wing-background('/images/level/loading_left_wing_1920.jpg', right)
@media screen and ( max-width: 1366px )
@include wing-background('/images/level/loading_left_wing_1366.jpg', right)
left: -50%
@include transition(all $UNVEIL_TIME ease)
.right-wing
@include wing-background('/images/level/loading_right_wing_1920.jpg', left)
@media screen and ( max-width: 1366px )
@include wing-background('/images/level/loading_right_wing_1366.jpg', left)
right: -50%
@include transition(all $UNVEIL_TIME ease)
&.preview #tip-wrapper
left: 48px
right: 77px
width: auto

View file

@ -52,3 +52,14 @@
border-image: url(/images/level/code_toolbar_submit_button_zazz_pressed.png) 14 20 20 20 fill round
padding: 2px 0 0 2px
color: white
#guide-view
pre.ace_editor
padding: 2px 4px
border-radius: 4px
background-color: #f9f2f4
font-size: 12px
font-family: Monaco, Menlo, Ubuntu Mono, Consolas, "source-code-pro", monospace !important
.ace_cursor, .ace_bracket
display: none

View file

@ -2,7 +2,7 @@
.right-wing
.loading-details.loading-container
#loading-details.loading-container
.level-loading-goals.secret
.goals-title(data-i18n="play_level.goals") Goals
@ -10,6 +10,8 @@
.errors
.intro-doc
.progress-or-start-container
button.start-level-button.btn.btn-lg.btn-success.btn-illustrated.header-font.needsclick(data-i18n="play_level.loading_start") Start Level

View file

@ -7,6 +7,7 @@ module.exports = class ArchmageView extends ContributeClassView
contributorClassName: 'archmage'
contributors: [
{id: '547acbb2af18b03c0563fdb3', name: 'David Liu', github: 'trotod'}
{id: '52ccfc9bd3eb6b5a4100b60d', name: 'Glen De Cauwsemaecker', github: 'GlenDC'}
{id: '52bfc3ecb7ec628868001297', name: 'Tom Steinbrecher', github: 'TomSteinbrecher'}
{id: '5272806093680c5817033f73', name: 'Sébastien Moratinos', github: 'smoratinos'}

View file

@ -14,6 +14,7 @@ module.exports = class LevelLoadingView extends CocoView
subscriptions:
'level:loaded': 'onLevelLoaded' # If Level loads after level loading view.
'level:session-loaded': 'onSessionLoaded'
'level:subscription-required': 'onSubscriptionRequired' # If they'd need a subscription to start playing.
'level:course-membership-required': 'onCourseMembershipRequired' # If they'd need a subscription to start playing.
'subscribe-modal:subscribed': 'onSubscribed'
@ -44,6 +45,14 @@ module.exports = class LevelLoadingView extends CocoView
onLevelLoaded: (e) ->
@level = e.level
@prepareGoals()
@prepareTip()
@prepareIntro()
onSessionLoaded: (e) ->
@session = e.session if e.session.get('creator') is me.id
prepareGoals: ->
goalContainer = @$el.find('.level-loading-goals')
goalList = goalContainer.find('ul')
goalCount = 0
@ -55,57 +64,120 @@ module.exports = class LevelLoadingView extends CocoView
goalContainer.removeClass('secret')
if goalCount is 1
goalContainer.find('.panel-heading').text $.i18n.t 'play_level.goal' # Not plural
prepareTip: ->
tip = @$el.find('.tip')
if @level.get('loadingTip')
loadingTip = utils.i18n @level.attributes, 'loadingTip'
tip.text(loadingTip)
tip.removeClass('secret')
prepareIntro: ->
@docs = @level.get('documentation') ? {}
specific = @docs.specificArticles or []
return unless @intro = _.find specific, name: 'Intro'
@intro.html = marked utils.filterMarkdownCodeLanguages(utils.i18n(@intro, 'body'))
@intro.name = utils.i18n @intro, 'name'
showReady: ->
return if @shownReady
@shownReady = true
_.delay @finishShowingReady, 1500 # Let any blocking JS hog the main thread before we show that we're done.
_.delay @finishShowingReady, 100 # Let any blocking JS hog the main thread before we show that we're done.
finishShowingReady: =>
return if @destroyed
if @options.autoUnveil
if @options.autoUnveil or (@session?.get('state').complete and not @getQueryVariable('intro'))
@startUnveiling()
@unveil()
@unveil true
else
@playSound 'level_loaded', 0.75 # old: loading_ready
@$el.find('.progress').hide()
@$el.find('.start-level-button').show()
@unveil false
startUnveiling: (e) ->
@playSound 'menu-button-click'
@unveiling = true
Backbone.Mediator.publish 'level:loading-view-unveiling', {}
_.delay @onClickStartLevel, 1000 # If they never mouse-up for the click (or a modal shows up and interrupts the click), do it anyway.
onClickStartLevel: (e) =>
return if @destroyed
@unveil()
@unveil true
onEnterPressed: (e) ->
return unless @shownReady and not @$el.hasClass 'unveiled'
return unless @shownReady and not @unveiled
@startUnveiling()
@onClickStartLevel()
unveil: ->
return if @$el.hasClass 'unveiled'
@$el.addClass 'unveiled'
loadingDetails = @$el.find('.loading-details')
duration = parseFloat loadingDetails.css 'transition-duration'
loadingDetails.css 'top', -loadingDetails.outerHeight(true)
unveil: (full) ->
return if @destroyed or @unveiled
@unveiled = full
@$loadingDetails = @$el.find('#loading-details')
duration = parseFloat(@$loadingDetails.css 'transition-duration') * 1000
unless @$el.hasClass 'unveiled'
@$el.addClass 'unveiled'
@unveilWings duration
if full
@unveilLoadingFull()
_.delay @onUnveilEnded, duration
else
@unveilLoadingPreview duration
unveilLoadingFull: ->
# Get rid of the loading details screen entirely--the level is totally ready.
unless @unveiling
Backbone.Mediator.publish 'level:loading-view-unveiling', {}
@unveiling = true
if @$el.hasClass 'preview-screen'
@$loadingDetails.css 'right', -@$loadingDetails.outerWidth(true)
else
@$loadingDetails.css 'top', -@$loadingDetails.outerHeight(true)
@$el.removeClass 'preview-screen'
$('#canvas-wrapper').removeClass 'preview-overlay'
unveilLoadingPreview: (duration) ->
# Move the loading details screen over the code editor to preview the level.
return if @$el.hasClass 'preview-screen'
$('#canvas-wrapper').addClass 'preview-overlay'
@$el.addClass('preview-screen')
@$loadingDetails.addClass('preview')
@resize()
@onWindowResize = _.debounce @onWindowResize, 700 # Wait a bit for other views to resize before we resize
$(window).on 'resize', @onWindowResize
if @intro
@$el.find('.progress-or-start-container').addClass('intro-footer')
@$el.find('#tip-wrapper').remove()
_.delay @unveilIntro, duration
resize: ->
maxHeight = $('#page-container').outerHeight(true)
minHeight = $('#code-area').outerHeight(true)
@$el.css height: maxHeight
@$loadingDetails.css minHeight: minHeight, maxHeight: maxHeight
$intro = @$el.find('.intro-doc')
$intro.css maxHeight: minHeight - $intro.offset().top - @$el.find('.progress-or-start-container').outerHeight() - 30 - 20
unveilWings: (duration) ->
@playSound 'loading-view-unveil', 0.5
@$el.find('.left-wing').css left: '-100%', backgroundPosition: 'right -400px top 0'
@$el.find('.right-wing').css right: '-100%', backgroundPosition: 'left -400px top 0'
@playSound 'loading-view-unveil', 0.5
_.delay @onUnveilEnded, duration * 1000
$('#level-footer-background').detach().appendTo('#page-container').slideDown(duration * 1000)
$('#level-footer-background').detach().appendTo('#page-container').slideDown(duration)
unveilIntro: =>
return if @destroyed or not @intro or @unveiled
@$el.find('.intro-doc').html @intro.html
@resize()
onUnveilEnded: =>
return if @destroyed
Backbone.Mediator.publish 'level:loading-view-unveiled', view: @
onWindowResize: (e) =>
return if @destroyed
@$loadingDetails.css transition: 'none'
@resize()
onSubscriptionRequired: (e) ->
@$el.find('.level-loading-goals, .tip, .load-progress').hide()
@$el.find('.subscription-required').show()
@ -120,3 +192,7 @@ module.exports = class LevelLoadingView extends CocoView
onSubscribed: ->
document.location.reload()
destroy: ->
$(window).off 'resize', @onWindowResize
super()

View file

@ -155,7 +155,7 @@ module.exports = class PlayLevelView extends RootView
afterRender: ->
super()
window.onPlayLevelViewLoaded? @ # still a hack
@insertSubView @loadingView = new LevelLoadingView autoUnveil: @options.autoUnveil or @observing, level: @levelLoader?.level ? @level # May not have @level loaded yet
@insertSubView @loadingView = new LevelLoadingView autoUnveil: @options.autoUnveil or @observing, level: @levelLoader?.level ? @level, session: @levelLoader?.session ? @session # May not have @level loaded yet
@$el.find('#level-done-button').hide()
$('body').addClass('is-playing')
$('body').bind('touchmove', false) if @isIPadApp()
@ -177,7 +177,6 @@ module.exports = class PlayLevelView extends RootView
@initVolume()
@listenTo(@session, 'change:multiplayer', @onMultiplayerChanged)
@originalSessionState = $.extend(true, {}, @session.get('state'))
@register()
@controlBar.setBus(@bus)
@initScriptManager()
@ -341,14 +340,16 @@ module.exports = class PlayLevelView extends RootView
if window.currentModal and not window.currentModal.destroyed and window.currentModal.constructor isnt VictoryModal
return Backbone.Mediator.subscribeOnce 'modal:closed', @onLevelStarted, @
@surface.showLevel()
Backbone.Mediator.publish 'level:set-time', time: 0
if @isEditorPreview or @observing
@loadingView.startUnveiling()
@loadingView.unveil()
@loadingView.unveil true
onLoadingViewUnveiling: (e) ->
@restoreSessionState()
@selectHero()
onLoadingViewUnveiled: (e) ->
Backbone.Mediator.publish 'level:set-playing', playing: true
@loadingView.$el.remove()
@removeSubView @loadingView
@loadingView = null
@ -372,21 +373,11 @@ module.exports = class PlayLevelView extends RootView
@ambientSound = createjs.Sound.play src, loop: -1, volume: 0.1
createjs.Tween.get(@ambientSound).to({volume: 1.0}, 10000)
restoreSessionState: ->
return if @alreadyLoadedState
@alreadyLoadedState = true
state = @originalSessionState
if not @level or @level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder']
Backbone.Mediator.publish 'level:suppress-selection-sounds', suppress: true
Backbone.Mediator.publish 'tome:select-primary-sprite', {}
Backbone.Mediator.publish 'level:suppress-selection-sounds', suppress: false
@surface.focusOnHero()
Backbone.Mediator.publish 'level:set-time', time: 0
Backbone.Mediator.publish 'level:set-playing', playing: true
else
if state.selected
# TODO: Should also restore selected spell here by saving spellName
Backbone.Mediator.publish 'level:select-sprite', thangID: state.selected, spellName: null
selectHero: ->
Backbone.Mediator.publish 'level:suppress-selection-sounds', suppress: true
Backbone.Mediator.publish 'tome:select-primary-sprite', {}
Backbone.Mediator.publish 'level:suppress-selection-sounds', suppress: false
@surface.focusOnHero()
# callbacks

View file

@ -3,7 +3,7 @@ template = require 'templates/play/level/tome/spell_palette_entry'
{me} = require 'core/auth'
filters = require 'lib/image_filter'
DocFormatter = require './DocFormatter'
SpellView = require 'views/play/level/tome/SpellView'
utils = require 'core/utils'
module.exports = class SpellPaletteEntryView extends CocoView
tagName: 'div' # Could also try <code> instead of <div>, but would need to adjust colors
@ -59,26 +59,8 @@ module.exports = class SpellPaletteEntryView extends CocoView
@aceEditors = []
aceEditors = @aceEditors
popover?.$tip?.find('.docs-ace').each ->
contents = $(@).text()
editor = ace.edit @
editor.setOptions maxLines: Infinity
editor.setReadOnly true
editor.setTheme 'ace/theme/textmate'
editor.setShowPrintMargin false
editor.setShowFoldWidgets false
editor.setHighlightActiveLine false
editor.setHighlightActiveLine false
editor.setBehavioursEnabled false
editor.renderer.setShowGutter false
editor.setValue contents
editor.clearSelection()
session = editor.getSession()
session.setUseWorker false
session.setMode SpellView.editModes[codeLanguage]
session.setWrapLimitRange null
session.setUseWrapMode true
session.setNewLineMode 'unix'
aceEditors.push editor
aceEditor = utils.initializeACE @, codeLanguage
aceEditors.push aceEditor
onMouseEnter: (e) ->
# Make sure the doc has the updated Thang so it can regenerate its prop value

View file

@ -9,6 +9,7 @@ SpellDebugView = require './SpellDebugView'
SpellToolbarView = require './SpellToolbarView'
LevelComponent = require 'models/LevelComponent'
UserCodeProblem = require 'models/UserCodeProblem'
utils = require 'core/utils'
module.exports = class SpellView extends CocoView
id: 'spell-view'
@ -18,14 +19,6 @@ module.exports = class SpellView extends CocoView
eventsSuppressed: true
writable: true
@editModes:
'javascript': 'ace/mode/javascript'
'coffeescript': 'ace/mode/coffee'
'python': 'ace/mode/python'
'clojure': 'ace/mode/clojure'
'lua': 'ace/mode/lua'
'io': 'ace/mode/text'
keyBindings:
'default': null
'vim': 'ace/keyboard/vim'
@ -93,7 +86,7 @@ module.exports = class SpellView extends CocoView
@aceSession = @ace.getSession()
@aceDoc = @aceSession.getDocument()
@aceSession.setUseWorker false
@aceSession.setMode SpellView.editModes[@spell.language]
@aceSession.setMode utils.aceEditModes[@spell.language]
@aceSession.setWrapLimitRange null
@aceSession.setUseWrapMode true
@aceSession.setNewLineMode 'unix'
@ -479,7 +472,7 @@ module.exports = class SpellView extends CocoView
# window.zatannaInstance = @zatanna # For debugging. Make sure to not leave active when committing.
# window.snippetEntries = snippetEntries
lang = SpellView.editModes[e.language].substr 'ace/mode/'.length
lang = utils.aceEditModes[e.language].substr 'ace/mode/'.length
@zatanna.addSnippets snippetEntries, lang
@editorLang = lang
@ -1138,8 +1131,8 @@ module.exports = class SpellView extends CocoView
onChangeLanguage: (e) ->
return unless @spell.canWrite()
@aceSession.setMode SpellView.editModes[e.language]
@zatanna?.set 'language', SpellView.editModes[e.language].substr('ace/mode/')
@aceSession.setMode utils.aceEditModes[e.language]
@zatanna?.set 'language', utils.aceEditModes[e.language].substr('ace/mode/')
wasDefault = @getSource() is @spell.originalSource
@spell.setLanguage e.language
@reloadCode true if wasDefault

View file

@ -4,8 +4,6 @@ Article = require 'models/Article'
SubscribeModal = require 'views/core/SubscribeModal'
utils = require 'core/utils'
# let's implement this once we have the docs database schema set up
module.exports = class LevelGuideView extends CocoView
template: template
id: 'guide-view'
@ -41,10 +39,10 @@ module.exports = class LevelGuideView extends CocoView
@docs = specific.concat(general)
@docs = $.extend(true, [], @docs)
@docs = [@docs[0]] if @firstOnly and @docs[0]
doc.html = marked(@filterCodeLanguages(utils.i18n(doc, 'body'))) for doc in @docs
doc.html = marked(utils.filterMarkdownCodeLanguages(utils.i18n(doc, 'body'))) for doc in @docs
doc.name = (utils.i18n doc, 'name') for doc in @docs
doc.slug = _.string.slugify(doc.name) for doc in @docs
super()
super options
destroy: ->
if @vimeoListenerAttached
@ -52,6 +50,7 @@ module.exports = class LevelGuideView extends CocoView
window.removeEventListener('message', @onMessageReceived, false)
else
window.detachEvent('onmessage', @onMessageReceived, false)
oldEditor.destroy() for oldEditor in @aceEditors ? []
super()
getRenderData: ->
@ -70,13 +69,17 @@ module.exports = class LevelGuideView extends CocoView
@$el.find('.nav-tabs li:first').addClass('active')
@$el.find('.tab-content .tab-pane:first').addClass('active')
@$el.find('.nav-tabs a').click(@clickTab)
@configureACEEditors()
@playSound 'guide-open'
filterCodeLanguages: (text) ->
currentLanguage = me.get('aceConfig')?.language or 'python'
excludedLanguages = _.without ['javascript', 'python', 'coffeescript', 'clojure', 'lua', 'io'], currentLanguage
exclusionRegex = new RegExp "```(#{excludedLanguages.join('|')})\n[^`]+```\n?", 'gm'
text.replace exclusionRegex, ''
configureACEEditors: ->
oldEditor.destroy() for oldEditor in @aceEditors ? []
@aceEditors = []
aceEditors = @aceEditors
codeLanguage = me.get('aceConfig')?.language or 'python'
@$el.find('pre').each ->
aceEditor = utils.initializeACE @, codeLanguage
aceEditors.push aceEditor
clickSubscribe: (e) ->
level = @levelSlug # Save ref to level slug