2014-11-28 20:49:41 -05:00
|
|
|
CocoView = require 'views/core/CocoView'
|
2014-11-29 15:46:04 -05:00
|
|
|
template = require 'templates/play/menu/guide-view'
|
2014-10-28 19:30:40 -04:00
|
|
|
Article = require 'models/Article'
|
2015-01-30 13:48:49 -05:00
|
|
|
SubscribeModal = require 'views/core/SubscribeModal'
|
2014-11-28 20:49:41 -05:00
|
|
|
utils = require 'core/utils'
|
2014-08-08 14:36:41 -04:00
|
|
|
|
2014-10-28 19:30:40 -04:00
|
|
|
module.exports = class LevelGuideView extends CocoView
|
|
|
|
template: template
|
2014-08-08 14:36:41 -04:00
|
|
|
id: 'guide-view'
|
|
|
|
className: 'tab-pane'
|
2014-12-18 01:26:23 -05:00
|
|
|
helpVideoHeight: '295'
|
|
|
|
helpVideoWidth: '471'
|
2014-08-08 14:36:41 -04:00
|
|
|
|
2015-01-30 13:48:49 -05:00
|
|
|
events:
|
2015-03-05 11:58:11 -05:00
|
|
|
'click .start-subscription-button': 'clickSubscribe'
|
2015-01-30 13:48:49 -05:00
|
|
|
|
2014-10-28 19:30:40 -04:00
|
|
|
constructor: (options) ->
|
2016-02-17 14:33:50 -05:00
|
|
|
super options
|
2015-01-30 13:48:49 -05:00
|
|
|
@levelSlug = options.level.get('slug')
|
2015-01-15 14:04:48 -05:00
|
|
|
@sessionID = options.session.get('_id')
|
2015-01-30 13:48:49 -05:00
|
|
|
@requiresSubscription = not me.isPremium()
|
2016-07-17 03:53:17 -04:00
|
|
|
@isCourseLevel = options.level.isType('course', 'course-ladder')
|
2016-05-31 13:12:20 -04:00
|
|
|
@helpVideos = if @isCourseLevel then [] else options.level.get('helpVideos') ? []
|
2014-12-18 01:26:23 -05:00
|
|
|
@trackedHelpVideoStart = @trackedHelpVideoFinish = false
|
2014-12-18 02:55:11 -05:00
|
|
|
# A/B Testing video tutorial styles
|
|
|
|
@helpVideosIndex = me.getVideoTutorialStylesIndex(@helpVideos.length)
|
2016-05-31 13:12:20 -04:00
|
|
|
@helpVideo = @helpVideos[@helpVideosIndex] if @helpVideos.length > 0 and not @isCourseLevel
|
|
|
|
@videoLocked = not (@helpVideo?.free or @isCourseLevel) and @requiresSubscription
|
2014-12-28 16:25:20 -05:00
|
|
|
|
2014-10-28 19:30:40 -04:00
|
|
|
@firstOnly = options.firstOnly
|
2016-07-21 14:11:11 -04:00
|
|
|
if window.serverConfig.picoCTF
|
|
|
|
@docs = options?.docs ? options.level.get('documentation') ? {}
|
|
|
|
general = @docs.generalArticles or []
|
|
|
|
specific = @docs.specificArticles or []
|
2014-10-28 19:30:40 -04:00
|
|
|
|
2016-07-21 14:11:11 -04:00
|
|
|
articles = options.supermodel.getModels(Article)
|
|
|
|
articleMap = {}
|
|
|
|
articleMap[article.get('original')] = article for article in articles
|
|
|
|
general = (articleMap[ref.original] for ref in general)
|
|
|
|
general = (article.attributes for article in general when article)
|
2014-10-28 19:30:40 -04:00
|
|
|
|
2016-07-21 14:11:11 -04:00
|
|
|
@docs = specific.concat(general)
|
|
|
|
@docs = $.extend(true, [], @docs)
|
|
|
|
@docs = [@docs[0]] if @firstOnly and @docs[0]
|
|
|
|
@addPicoCTFProblem()
|
|
|
|
doc.html = marked(utils.filterMarkdownCodeLanguages(utils.i18n(doc, 'body'), options.session.get('codeLanguage'))) for doc in @docs
|
|
|
|
doc.slug = _.string.slugify(doc.name) for doc in @docs
|
|
|
|
doc.name = (utils.i18n doc, 'name') for doc in @docs
|
|
|
|
else
|
|
|
|
@docs = []
|
2014-10-28 19:30:40 -04:00
|
|
|
|
2014-12-18 02:55:11 -05:00
|
|
|
destroy: ->
|
|
|
|
if @vimeoListenerAttached
|
|
|
|
if window.addEventListener
|
|
|
|
window.removeEventListener('message', @onMessageReceived, false)
|
|
|
|
else
|
|
|
|
window.detachEvent('onmessage', @onMessageReceived, false)
|
2015-11-10 18:22:09 -05:00
|
|
|
oldEditor.destroy() for oldEditor in @aceEditors ? []
|
2014-12-18 02:55:11 -05:00
|
|
|
super()
|
|
|
|
|
2014-10-28 19:30:40 -04:00
|
|
|
getRenderData: ->
|
|
|
|
c = super()
|
|
|
|
c.docs = @docs
|
2016-05-31 13:12:20 -04:00
|
|
|
c.showVideo = @helpVideos.length > 0 unless @isCourseLevel
|
2015-01-30 13:48:49 -05:00
|
|
|
c.videoLocked = @videoLocked
|
2014-10-28 19:30:40 -04:00
|
|
|
c
|
2014-08-08 14:36:41 -04:00
|
|
|
|
|
|
|
afterRender: ->
|
|
|
|
super()
|
2015-11-30 16:54:13 -05:00
|
|
|
@setupVideoPlayer() unless @videoLocked
|
|
|
|
if @docs.length + @helpVideos.length > 1
|
|
|
|
if @helpVideos.length
|
|
|
|
startingTab = 0
|
|
|
|
else
|
2016-05-31 13:37:30 -04:00
|
|
|
startingTab = _.findIndex @docs, slug: 'overview'
|
2015-11-30 16:54:13 -05:00
|
|
|
startingTab = 0 if startingTab is -1
|
2014-10-28 19:30:40 -04:00
|
|
|
# incredible hackiness. Getting bootstrap tabs to work shouldn't be this complex
|
2015-11-30 16:54:13 -05:00
|
|
|
@$el.find(".nav-tabs li:nth(#{startingTab})").addClass('active')
|
|
|
|
@$el.find(".tab-content .tab-pane:nth(#{startingTab})").addClass('active')
|
2014-10-28 19:30:40 -04:00
|
|
|
@$el.find('.nav-tabs a').click(@clickTab)
|
2015-11-30 16:54:13 -05:00
|
|
|
@$el.addClass 'has-tabs'
|
2015-11-10 18:22:09 -05:00
|
|
|
@configureACEEditors()
|
2014-11-26 09:58:23 -05:00
|
|
|
@playSound 'guide-open'
|
2014-10-28 19:30:40 -04:00
|
|
|
|
2015-11-10 18:22:09 -05:00
|
|
|
configureACEEditors: ->
|
|
|
|
oldEditor.destroy() for oldEditor in @aceEditors ? []
|
|
|
|
@aceEditors = []
|
|
|
|
aceEditors = @aceEditors
|
2016-02-19 13:55:42 -05:00
|
|
|
codeLanguage = @options.session.get('codeLanguage') or me.get('aceConfig')?.language or 'python'
|
2015-11-10 18:22:09 -05:00
|
|
|
@$el.find('pre').each ->
|
|
|
|
aceEditor = utils.initializeACE @, codeLanguage
|
|
|
|
aceEditors.push aceEditor
|
2015-11-04 13:46:16 -05:00
|
|
|
|
2015-03-05 11:58:11 -05:00
|
|
|
clickSubscribe: (e) ->
|
2015-01-30 13:48:49 -05:00
|
|
|
level = @levelSlug # Save ref to level slug
|
|
|
|
@openModalView new SubscribeModal()
|
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: 'help video clicked', level: level, levelID: level
|
2015-01-30 13:48:49 -05:00
|
|
|
|
2014-10-28 19:30:40 -04:00
|
|
|
clickTab: (e) =>
|
|
|
|
@$el.find('li.active').removeClass('active')
|
2014-11-26 09:58:23 -05:00
|
|
|
@playSound 'guide-tab-switch'
|
2014-10-28 19:30:40 -04:00
|
|
|
|
|
|
|
afterInsert: ->
|
|
|
|
super()
|
|
|
|
Backbone.Mediator.publish 'level:docs-shown', {}
|
|
|
|
|
|
|
|
onHidden: ->
|
2016-04-19 19:38:44 -04:00
|
|
|
if @vimeoListenerAttached
|
2016-04-25 17:19:28 -04:00
|
|
|
player = @$('#help-video-player')[0]
|
2016-04-19 19:38:44 -04:00
|
|
|
player.contentWindow.postMessage JSON.stringify(method: 'pause'), '*'
|
2015-02-18 15:35:41 -05:00
|
|
|
createjs?.Sound?.setVolume?(@volume ? ( me.get('volume') ? 1.0))
|
2014-10-28 19:30:40 -04:00
|
|
|
Backbone.Mediator.publish 'level:docs-hidden', {}
|
2014-12-18 01:26:23 -05:00
|
|
|
|
|
|
|
onShown: ->
|
|
|
|
# TODO: Disable sound only when video is playing?
|
|
|
|
@volume ?= me.get('volume') ? 1.0
|
|
|
|
createjs?.Sound?.setVolume(0.0)
|
|
|
|
|
2014-12-18 02:55:11 -05:00
|
|
|
onStartHelpVideo: ->
|
|
|
|
unless @trackedHelpVideoStart
|
2015-01-30 13:48:49 -05:00
|
|
|
window.tracker?.trackEvent 'Start help video', level: @levelSlug, ls: @sessionID, style: @helpVideo?.style
|
2014-12-18 02:55:11 -05:00
|
|
|
@trackedHelpVideoStart = true
|
2014-12-28 16:25:20 -05:00
|
|
|
|
2014-12-18 02:55:11 -05:00
|
|
|
onFinishHelpVideo: ->
|
|
|
|
unless @trackedHelpVideoFinish
|
2015-01-30 13:48:49 -05:00
|
|
|
window.tracker?.trackEvent 'Finish help video', level: @levelSlug, ls: @sessionID, style: @helpVideo?.style
|
2014-12-18 02:55:11 -05:00
|
|
|
@trackedHelpVideoFinish = true
|
|
|
|
|
2014-12-18 01:26:23 -05:00
|
|
|
setupVideoPlayer: () ->
|
2015-01-30 13:48:49 -05:00
|
|
|
return unless @helpVideo
|
2015-01-29 19:45:15 -05:00
|
|
|
# Always use HTTPS
|
|
|
|
# TODO: Not specifying the protocol should work based on Vimeo docs, but breaks postMessage/eventing in practice.
|
2015-01-30 13:48:49 -05:00
|
|
|
url = "https:" + @helpVideo.url.substr @helpVideo.url.indexOf '/'
|
2015-01-29 19:45:15 -05:00
|
|
|
@setupVimeoVideoPlayer url
|
2014-12-18 01:26:23 -05:00
|
|
|
|
|
|
|
setupVimeoVideoPlayer: (helpVideoURL) ->
|
|
|
|
# Setup Vimeo player
|
|
|
|
# https://developer.vimeo.com/player/js-api#universal-with-postmessage
|
|
|
|
|
|
|
|
# Create Vimeo iframe player
|
|
|
|
tag = document.createElement('iframe')
|
|
|
|
tag.id = 'help-video-player'
|
2014-12-18 02:59:59 -05:00
|
|
|
tag.src = helpVideoURL + "?api=1&badge=0&byline=0&portrait=0&title=0"
|
2014-12-18 01:26:23 -05:00
|
|
|
tag.height = @helpVideoHeight
|
|
|
|
tag.width = @helpVideoWidth
|
2015-10-19 01:44:19 -04:00
|
|
|
tag.allowFullscreen = true
|
2016-02-25 20:13:42 -05:00
|
|
|
tag.mozAllowFullscreen = true
|
|
|
|
$tag = $(tag)
|
|
|
|
$tag.attr('webkitallowfullscreen', true) # strong arm Safari into working
|
|
|
|
@$el.find('#help-video-player').replaceWith($tag)
|
2014-12-18 01:26:23 -05:00
|
|
|
|
2014-12-18 02:55:11 -05:00
|
|
|
@onMessageReceived = (e) =>
|
2014-12-18 01:26:23 -05:00
|
|
|
data = JSON.parse(e.data)
|
|
|
|
if data.event is 'ready'
|
|
|
|
# Vimeo player is ready, can now hook up other events
|
|
|
|
# https://developer.vimeo.com/player/js-api#events
|
|
|
|
player = $('#help-video-player')[0]
|
|
|
|
player.contentWindow.postMessage JSON.stringify(method: 'addEventListener', value: 'play'), helpVideoURL
|
|
|
|
player.contentWindow.postMessage JSON.stringify(method: 'addEventListener', value: 'finish'), helpVideoURL
|
|
|
|
else if data.event is 'play'
|
2014-12-18 02:55:11 -05:00
|
|
|
@onStartHelpVideo?()
|
2014-12-18 01:26:23 -05:00
|
|
|
else if data.event is 'finish'
|
2014-12-18 02:55:11 -05:00
|
|
|
@onFinishHelpVideo?()
|
2014-12-18 01:26:23 -05:00
|
|
|
|
|
|
|
# Listen for Vimeo player 'ready'
|
|
|
|
if window.addEventListener
|
2014-12-18 02:55:11 -05:00
|
|
|
window.addEventListener('message', @onMessageReceived, false)
|
2014-12-18 01:26:23 -05:00
|
|
|
else
|
2014-12-18 02:55:11 -05:00
|
|
|
window.attachEvent('onmessage', @onMessageReceived, false)
|
|
|
|
@vimeoListenerAttached = true
|
2016-02-17 14:33:50 -05:00
|
|
|
|
|
|
|
addPicoCTFProblem: ->
|
|
|
|
return unless problem = @options.level.picoCTFProblem
|
|
|
|
@docs = [name: 'Intro', body: '', slug: 'intro'] unless @docs.length
|
|
|
|
for doc in @docs when doc.name in ['Overview', 'Intro']
|
|
|
|
doc.body += """
|
|
|
|
### #{problem.name}
|
|
|
|
|
|
|
|
#{problem.description}
|
|
|
|
|
|
|
|
#{problem.category} - #{problem.score} points
|
|
|
|
|
|
|
|
Hint: #{problem.hints}
|
|
|
|
""".replace /<p>(.*?)<\/p>/gi, '$1'
|