2014-11-28 20:49:41 -05:00
|
|
|
CocoView = require 'views/core/CocoView'
|
2014-03-14 20:06:08 -04:00
|
|
|
template = require 'templates/play/level/level_loading'
|
2014-11-28 20:49:41 -05:00
|
|
|
utils = require 'core/utils'
|
2014-12-18 23:34:59 -05:00
|
|
|
SubscribeModal = require 'views/core/SubscribeModal'
|
2014-03-14 20:06:08 -04:00
|
|
|
|
2014-07-17 20:20:11 -04:00
|
|
|
module.exports = class LevelLoadingView extends CocoView
|
2014-06-30 22:16:26 -04:00
|
|
|
id: 'level-loading-view'
|
2014-03-14 20:06:08 -04:00
|
|
|
template: template
|
|
|
|
|
2014-09-21 17:35:59 -04:00
|
|
|
events:
|
2014-09-21 23:19:27 -04:00
|
|
|
'mousedown .start-level-button': 'startUnveiling' # Split into two for animation smoothness.
|
2014-09-21 17:35:59 -04:00
|
|
|
'click .start-level-button': 'onClickStartLevel'
|
2014-12-03 15:04:12 -05:00
|
|
|
'click .start-subscription-button': 'onClickStartSubscription'
|
2014-09-21 17:35:59 -04:00
|
|
|
|
2014-09-21 18:52:49 -04:00
|
|
|
subscriptions:
|
2014-09-21 23:19:27 -04:00
|
|
|
'level:loaded': 'onLevelLoaded' # If Level loads after level loading view.
|
2015-11-10 18:22:09 -05:00
|
|
|
'level:session-loaded': 'onSessionLoaded'
|
2014-12-03 15:04:12 -05:00
|
|
|
'level:subscription-required': 'onSubscriptionRequired' # If they'd need a subscription to start playing.
|
2015-10-06 15:48:39 -04:00
|
|
|
'level:course-membership-required': 'onCourseMembershipRequired' # If they'd need a subscription to start playing.
|
2014-12-03 15:04:12 -05:00
|
|
|
'subscribe-modal:subscribed': 'onSubscribed'
|
2014-09-21 17:35:59 -04:00
|
|
|
|
2014-11-23 18:24:59 -05:00
|
|
|
shortcuts:
|
|
|
|
'enter': 'onEnterPressed'
|
|
|
|
|
2014-03-17 01:01:21 -04:00
|
|
|
afterRender: ->
|
2014-11-01 14:10:26 -04:00
|
|
|
super()
|
2014-03-17 03:12:11 -04:00
|
|
|
@$el.find('.tip.rare').remove() if _.random(1, 10) < 9
|
|
|
|
tips = @$el.find('.tip').addClass('to-remove')
|
2014-03-17 01:01:21 -04:00
|
|
|
tip = _.sample(tips)
|
2014-11-12 18:00:24 -05:00
|
|
|
$(tip).removeClass('to-remove').addClass('secret')
|
2014-03-17 03:12:11 -04:00
|
|
|
@$el.find('.to-remove').remove()
|
2014-09-21 23:19:27 -04:00
|
|
|
@onLevelLoaded level: @options.level if @options.level?.get('goals') # If Level was already loaded.
|
2014-03-14 20:06:08 -04:00
|
|
|
|
2014-11-01 14:10:26 -04:00
|
|
|
afterInsert: ->
|
|
|
|
super()
|
|
|
|
_.defer =>
|
|
|
|
return if @destroyed
|
|
|
|
# Make sure that we are as tall now as we will be when the canvas wrapper is resized to the right height.
|
2014-11-08 14:51:31 -05:00
|
|
|
currentCanvasHeight = 589
|
2014-11-01 14:10:26 -04:00
|
|
|
canvasAspectRatio = 924 / 589
|
|
|
|
eventualCanvasWidth = $('#canvas-wrapper').outerWidth()
|
2014-11-08 14:51:31 -05:00
|
|
|
eventualCanvasHeight = eventualCanvasWidth / canvasAspectRatio
|
|
|
|
newHeight = Math.max 769, @$el.outerHeight() + eventualCanvasHeight - currentCanvasHeight + 2
|
|
|
|
@$el.addClass('manually-sized').css('height', newHeight)
|
2014-11-01 14:10:26 -04:00
|
|
|
|
2014-09-21 18:52:49 -04:00
|
|
|
onLevelLoaded: (e) ->
|
2015-11-29 15:30:19 -05:00
|
|
|
return if @level
|
2014-09-21 18:52:49 -04:00
|
|
|
@level = e.level
|
2015-11-11 09:42:12 -05:00
|
|
|
@prepareGoals e
|
2015-11-10 18:22:09 -05:00
|
|
|
@prepareTip()
|
|
|
|
@prepareIntro()
|
|
|
|
|
|
|
|
onSessionLoaded: (e) ->
|
2015-11-29 15:30:19 -05:00
|
|
|
return if @session
|
2015-11-10 18:22:09 -05:00
|
|
|
@session = e.session if e.session.get('creator') is me.id
|
|
|
|
|
2015-11-11 09:42:12 -05:00
|
|
|
prepareGoals: (e) ->
|
2014-09-22 01:10:52 -04:00
|
|
|
goalContainer = @$el.find('.level-loading-goals')
|
|
|
|
goalList = goalContainer.find('ul')
|
|
|
|
goalCount = 0
|
2015-01-31 15:23:34 -05:00
|
|
|
for goalID, goal of @level.get('goals') when (not goal.team or goal.team is (e.team or 'humans')) and not goal.hiddenGoal
|
2016-06-24 11:32:50 -04:00
|
|
|
continue if goal.optional and @level.get('type', true) is 'course'
|
2014-09-21 23:49:45 -04:00
|
|
|
name = utils.i18n goal, 'name'
|
2014-11-30 21:02:45 -05:00
|
|
|
goalList.append $('<li>' + name + '</li>')
|
2014-09-22 01:10:52 -04:00
|
|
|
++goalCount
|
|
|
|
if goalCount
|
|
|
|
goalContainer.removeClass('secret')
|
|
|
|
if goalCount is 1
|
|
|
|
goalContainer.find('.panel-heading').text $.i18n.t 'play_level.goal' # Not plural
|
2015-11-10 18:22:09 -05:00
|
|
|
|
|
|
|
prepareTip: ->
|
2014-11-12 18:00:24 -05:00
|
|
|
tip = @$el.find('.tip')
|
|
|
|
if @level.get('loadingTip')
|
|
|
|
loadingTip = utils.i18n @level.attributes, 'loadingTip'
|
|
|
|
tip.text(loadingTip)
|
|
|
|
tip.removeClass('secret')
|
2014-09-21 18:52:49 -04:00
|
|
|
|
2015-11-10 18:22:09 -05:00
|
|
|
prepareIntro: ->
|
|
|
|
@docs = @level.get('documentation') ? {}
|
|
|
|
specific = @docs.specificArticles or []
|
2015-11-10 19:09:21 -05:00
|
|
|
@intro = _.find specific, name: 'Intro'
|
2016-02-17 14:33:50 -05:00
|
|
|
if window.serverConfig.picoCTF
|
|
|
|
@intro ?= body: ''
|
2015-11-10 18:22:09 -05:00
|
|
|
|
2014-03-14 20:06:08 -04:00
|
|
|
showReady: ->
|
2014-05-20 00:53:14 -04:00
|
|
|
return if @shownReady
|
|
|
|
@shownReady = true
|
2015-11-10 18:22:09 -05:00
|
|
|
_.delay @finishShowingReady, 100 # Let any blocking JS hog the main thread before we show that we're done.
|
2014-11-01 14:10:26 -04:00
|
|
|
|
|
|
|
finishShowingReady: =>
|
|
|
|
return if @destroyed
|
2015-11-10 19:09:21 -05:00
|
|
|
showIntro = @getQueryVariable('intro')
|
|
|
|
autoUnveil = not showIntro and (@options.autoUnveil or @session?.get('state').complete)
|
|
|
|
if autoUnveil
|
2014-09-21 23:19:27 -04:00
|
|
|
@startUnveiling()
|
2015-11-10 18:22:09 -05:00
|
|
|
@unveil true
|
2014-09-21 23:19:27 -04:00
|
|
|
else
|
2015-09-09 17:36:05 -04:00
|
|
|
@playSound 'level_loaded', 0.75 # old: loading_ready
|
2014-11-30 21:02:45 -05:00
|
|
|
@$el.find('.progress').hide()
|
|
|
|
@$el.find('.start-level-button').show()
|
2015-11-10 18:22:09 -05:00
|
|
|
@unveil false
|
2014-03-14 20:06:08 -04:00
|
|
|
|
2014-09-21 18:52:49 -04:00
|
|
|
startUnveiling: (e) ->
|
2014-11-26 09:58:23 -05:00
|
|
|
@playSound 'menu-button-click'
|
2015-11-10 18:22:09 -05:00
|
|
|
@unveiling = true
|
2014-09-21 18:52:49 -04:00
|
|
|
Backbone.Mediator.publish 'level:loading-view-unveiling', {}
|
2014-09-23 14:39:56 -04:00
|
|
|
_.delay @onClickStartLevel, 1000 # If they never mouse-up for the click (or a modal shows up and interrupts the click), do it anyway.
|
2014-09-21 18:52:49 -04:00
|
|
|
|
2014-09-23 14:39:56 -04:00
|
|
|
onClickStartLevel: (e) =>
|
|
|
|
return if @destroyed
|
2015-11-10 18:22:09 -05:00
|
|
|
@unveil true
|
2014-03-14 20:06:08 -04:00
|
|
|
|
2014-11-23 18:24:59 -05:00
|
|
|
onEnterPressed: (e) ->
|
2015-11-10 18:22:09 -05:00
|
|
|
return unless @shownReady and not @unveiled
|
2014-11-23 18:24:59 -05:00
|
|
|
@startUnveiling()
|
|
|
|
@onClickStartLevel()
|
|
|
|
|
2015-11-10 18:22:09 -05:00
|
|
|
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
|
2015-11-15 11:23:12 -05:00
|
|
|
if @intro
|
|
|
|
$intro = @$el.find('.intro-doc')
|
|
|
|
$intro.css height: minHeight - $intro.offset().top - @$el.find('.progress-or-start-container').outerHeight() - 30 - 20
|
2015-11-30 18:33:50 -05:00
|
|
|
_.defer -> $intro.find('.nano').nanoScroller alwaysVisible: true
|
2015-11-10 18:22:09 -05:00
|
|
|
|
|
|
|
unveilWings: (duration) ->
|
|
|
|
@playSound 'loading-view-unveil', 0.5
|
2014-03-14 22:44:19 -04:00
|
|
|
@$el.find('.left-wing').css left: '-100%', backgroundPosition: 'right -400px top 0'
|
|
|
|
@$el.find('.right-wing').css right: '-100%', backgroundPosition: 'left -400px top 0'
|
2015-11-10 18:22:09 -05:00
|
|
|
$('#level-footer-background').detach().appendTo('#page-container').slideDown(duration)
|
|
|
|
|
|
|
|
unveilIntro: =>
|
|
|
|
return if @destroyed or not @intro or @unveiled
|
2016-02-17 14:33:50 -05:00
|
|
|
if window.serverConfig.picoCTF and problem = @level.picoCTFProblem
|
|
|
|
html = marked """
|
|
|
|
### #{problem.name}
|
|
|
|
|
|
|
|
#{@intro.body}
|
|
|
|
|
|
|
|
#{problem.description}
|
|
|
|
|
|
|
|
#{problem.category} - #{problem.score} points
|
|
|
|
""", sanitize: false
|
|
|
|
else
|
|
|
|
html = marked utils.filterMarkdownCodeLanguages(utils.i18n(@intro, 'body'))
|
2015-11-15 11:23:12 -05:00
|
|
|
@$el.find('.intro-doc').removeClass('hidden').find('.intro-doc-content').html html
|
2015-11-10 18:22:09 -05:00
|
|
|
@resize()
|
2014-03-14 20:06:08 -04:00
|
|
|
|
|
|
|
onUnveilEnded: =>
|
|
|
|
return if @destroyed
|
2014-04-13 23:31:23 -04:00
|
|
|
Backbone.Mediator.publish 'level:loading-view-unveiled', view: @
|
2014-12-03 15:04:12 -05:00
|
|
|
|
2015-11-10 18:22:09 -05:00
|
|
|
onWindowResize: (e) =>
|
|
|
|
return if @destroyed
|
|
|
|
@$loadingDetails.css transition: 'none'
|
|
|
|
@resize()
|
|
|
|
|
2014-12-03 15:04:12 -05:00
|
|
|
onSubscriptionRequired: (e) ->
|
|
|
|
@$el.find('.level-loading-goals, .tip, .load-progress').hide()
|
|
|
|
@$el.find('.subscription-required').show()
|
|
|
|
|
2015-10-06 15:48:39 -04:00
|
|
|
onCourseMembershipRequired: (e) ->
|
|
|
|
@$el.find('.level-loading-goals, .tip, .load-progress').hide()
|
|
|
|
@$el.find('.course-membership-required').show()
|
|
|
|
|
2016-06-06 22:42:57 -04:00
|
|
|
onLoadError: (resource) ->
|
|
|
|
@$el.find('.level-loading-goals, .tip, .load-progress').hide()
|
|
|
|
@$el.find('.could-not-load').show()
|
|
|
|
|
2014-12-03 15:04:12 -05:00
|
|
|
onClickStartSubscription: (e) ->
|
2014-12-06 12:35:13 -05:00
|
|
|
@openModalView new SubscribeModal()
|
2016-02-09 18:12:01 -05:00
|
|
|
levelSlug = @level?.get('slug') or @options.level?.get('slug')
|
2016-02-17 14:33:50 -05:00
|
|
|
# TODO: Added levelID on 2/9/16. Remove level property and associated AnalyticsLogEvent 'properties.level' index later.
|
2016-02-09 18:12:01 -05:00
|
|
|
window.tracker?.trackEvent 'Show subscription modal', category: 'Subscription', label: 'level loading', level: levelSlug, levelID: levelSlug
|
2014-12-03 15:04:12 -05:00
|
|
|
|
|
|
|
onSubscribed: ->
|
|
|
|
document.location.reload()
|
2015-11-10 18:22:09 -05:00
|
|
|
|
|
|
|
destroy: ->
|
|
|
|
$(window).off 'resize', @onWindowResize
|
|
|
|
super()
|