Adding easy observing of leaderboard matches (except top 5).

This commit is contained in:
Nick Winter 2015-01-31 10:04:02 -08:00
parent c5e47fda50
commit c977ecc16f
15 changed files with 91 additions and 53 deletions

View file

@ -88,6 +88,8 @@ module.exports = class LevelLoader extends CocoClass
@listenToOnce @opponentSession, 'sync', @loadDependenciesForSession @listenToOnce @opponentSession, 'sync', @loadDependenciesForSession
loadDependenciesForSession: (session) -> loadDependenciesForSession: (session) ->
if me.id isnt session.get 'creator'
session.patch = session.save = -> console.error "Not saving session, since we didn't create it."
if session is @session if session is @session
codeLanguage = session.get('codeLanguage') or me.get('aceConfig')?.language or 'python' codeLanguage = session.get('codeLanguage') or me.get('aceConfig')?.language or 'python'
modulePath = "vendor/aether-#{codeLanguage}" modulePath = "vendor/aether-#{codeLanguage}"
@ -136,6 +138,7 @@ module.exports = class LevelLoader extends CocoClass
@onWorldNecessitiesLoaded() @onWorldNecessitiesLoaded()
addSessionBrowserInfo: (session) -> addSessionBrowserInfo: (session) ->
return unless me.id is session.get 'creator'
return unless $.browser? return unless $.browser?
browser = {} browser = {}
browser['desktop'] = $.browser.desktop if $.browser.desktop browser['desktop'] = $.browser.desktop if $.browser.desktop

View file

@ -342,7 +342,7 @@ _.extend LevelSchema.properties,
type: 'string', links: [{rel: 'db', href: '/db/thang.type/{($)}/version'}], format: 'latest-version-original-reference' type: 'string', links: [{rel: 'db', href: '/db/thang.type/{($)}/version'}], format: 'latest-version-original-reference'
}} }}
campaign: c.shortString title: 'Campaign', description: 'Which campaign this level is part of (like "desert").', format: 'hidden' # Automatically set by campaign editor. campaign: c.shortString title: 'Campaign', description: 'Which campaign this level is part of (like "desert").', format: 'hidden' # Automatically set by campaign editor.
scoreTypes: c.array {title: 'Score Types', description: 'What metric to show leaderboards for.'}, scoreTypes: c.array {title: 'Score Types', description: 'What metric to show leaderboards for.', uniqueItems: true},
c.shortString(title: 'Score Type', 'enum': ['time', 'damage-taken', 'damage-dealt', 'gold-collected', 'difficulty']) # TODO: good version of LoC; total gear value. c.shortString(title: 'Score Type', 'enum': ['time', 'damage-taken', 'damage-dealt', 'gold-collected', 'difficulty']) # TODO: good version of LoC; total gear value.

View file

@ -16,17 +16,21 @@
th th
text-align: center text-align: center
.rank-cell tbody
font-weight: bold tr.viewable
cursor: pointer
.name-col-cell .rank-cell
max-width: 150px font-weight: bold
white-space: nowrap
overflow: hidden
text-overflow: ellipsis
.hero-portrait-cell, .code-language-cell .name-col-cell
background: transparent url(/images/common/code_languages/javascript_small.png) no-repeat center center max-width: 150px
padding: 0 9px white-space: nowrap
background-size: 30px 30px overflow: hidden
height: 30px text-overflow: ellipsis
.hero-portrait-cell, .code-language-cell
background: transparent url(/images/common/code_languages/javascript_small.png) no-repeat center center
background-size: 30px 30px
height: 30px
width: 32px

View file

@ -9,7 +9,7 @@
.glyphicon.glyphicon-play .glyphicon.glyphicon-play
span(data-i18n="nav.play").home-text Levels span(data-i18n="nav.play").home-text Levels
if isMultiplayerLevel if isMultiplayerLevel && !observing
.multiplayer-area-container .multiplayer-area-container
.multiplayer-area .multiplayer-area
.multiplayer-label(data-i18n="play_level.control_bar_multiplayer") .multiplayer-label(data-i18n="play_level.control_bar_multiplayer")
@ -28,17 +28,19 @@ else
.buttons-area .buttons-area
button.btn.btn-inverse#game-menu-button(title="Show game menu") if !observing
.hamburger button.btn.btn-inverse#game-menu-button(title="Show game menu")
span.icon-bar .hamburger
span.icon-bar span.icon-bar
span.icon-bar span.icon-bar
span.game-menu-text(data-i18n="play_level.game_menu") Game Menu span.icon-bar
span.game-menu-text(data-i18n="play_level.game_menu") Game Menu
if spectateGame if spectateGame
button.btn.btn-xs.btn-inverse.banner#next-game-button(title="Next Game", data-i18n="play_level.next-game") Next game! button.btn.btn-xs.btn-inverse.banner#next-game-button(title="Next Game", data-i18n="play_level.next-game") Next game!
button.btn.btn-xs.btn-primary.banner#level-done-button(data-i18n="play_level.done") Done if !observing
button.btn.btn-xs.btn-primary.banner#level-done-button(data-i18n="play_level.done") Done
if me.get('anonymous') if me.get('anonymous')
button.btn.btn-xs.btn-primary.banner#control-bar-sign-up-button(data-toggle='coco-modal', data-target='core/AuthModal', data-i18n="signup.sign_up") button.btn.btn-xs.btn-primary.banner#control-bar-sign-up-button(data-toggle='coco-modal', data-target='core/AuthModal', data-i18n="signup.sign_up")

View file

@ -1,9 +1,10 @@
button.btn.btn-lg.btn-illustrated.cast-button(title=castVerbose) button.btn.btn-lg.btn-illustrated.cast-button(title=castVerbose)
span(data-i18n="play_level.tome_run_button_ran") Ran span(data-i18n="play_level.tome_run_button_ran") Ran
button.btn.btn-lg.btn-illustrated.submit-button(title=castRealTimeVerbose) if !observing
span(data-i18n="play_level.tome_submit_button") Submit button.btn.btn-lg.btn-illustrated.submit-button(title=castRealTimeVerbose)
span.spl.secret.submit-again-time span(data-i18n="play_level.tome_submit_button") Submit
span.spl.secret.submit-again-time
button.btn.btn-lg.btn-illustrated.done-button.secret
span(data-i18n="play_level.done") Done button.btn.btn-lg.btn-illustrated.done-button.secret
span(data-i18n="play_level.done") Done

View file

@ -11,16 +11,23 @@ if topScores
th(colspan=4, data-i18n="general.player") th(colspan=4, data-i18n="general.player")
th(data-i18n="general.score") th(data-i18n="general.score")
th(data-i18n="general.when") th(data-i18n="general.when")
th
tbody tbody
for row, rank in topScores for row, rank in topScores
- var isMyRow = row.creator == me.id - var isMyRow = row.creator == me.id
tr(class=isMyRow ? "success" : "", data-player-id=row.creator, data-session-id=row.id) - var viewable = rank >= 5 || me.isAdmin();
tr(class=isMyRow ? "success" : "" + (viewable ? " viewable" : ""), data-player-id=row.creator, data-session-id=row.session, title=viewable ? "View solution" : "Can't view top 5 solutions")
td.rank-cell= rank + 1 td.rank-cell= rank + 1
td.code-language-cell(style="background-image: url(/images/common/code_languages/#{row.codeLanguage}_small.png)" title=_.string.capitalize(row.codeLanguage)) td.code-language-cell(style="background-image: url(/images/common/code_languages/#{row.codeLanguage}_small.png)" title=_.string.capitalize(row.codeLanguage))
td.hero-portrait-cell(style="background-image: url(/file/db/thang.type/#{row.hero}/portrait.png)") td.hero-portrait-cell(style="background-image: url(/file/db/thang.type/#{row.hero}/portrait.png)")
td.name-col-cell= row.creatorName || "Anonymous" td.name-col-cell= row.creatorName || "Anonymous"
td.score-cell= row.score td.score-cell= row.score
td.ago-cell= row.ago td.ago-cell= row.ago
td.viewable-cell
if viewable
.glyphicon.glyphicon-eye-open
else
.glyphicon.glyphicon-eye-close
else if loading else if loading
h3(data-i18n="common.loading") h3(data-i18n="common.loading")
else else

View file

@ -206,7 +206,7 @@ module.exports = class CampaignView extends RootView
for nextLevelOriginal in level.nextLevels ? [] for nextLevelOriginal in level.nextLevels ? []
if nextLevel = _.find(@campaign.renderedLevels, original: nextLevelOriginal) if nextLevel = _.find(@campaign.renderedLevels, original: nextLevelOriginal)
@createLine level.position, nextLevel.position @createLine level.position, nextLevel.position
@showLeaderboard @options.justBeatLevel?.get('slug') if @options.showLeaderboard #or true @showLeaderboard @options.justBeatLevel?.get('slug') if @options.showLeaderboard or true
@applyCampaignStyles() @applyCampaignStyles()
@testParticles() @testParticles()
@ -220,7 +220,7 @@ module.exports = class CampaignView extends RootView
@openModalView authModal @openModalView authModal
showLeaderboard: (levelSlug) -> showLeaderboard: (levelSlug) ->
#levelSlug ?= 'keeping-time' # Testing: show Keeping Time levelSlug ?= 'siege-of-stonehold' # Testing
leaderboardModal = new LeaderboardModal supermodel: @supermodel, levelSlug: levelSlug leaderboardModal = new LeaderboardModal supermodel: @supermodel, levelSlug: levelSlug
@openModalView leaderboardModal @openModalView leaderboardModal

View file

@ -33,6 +33,7 @@ module.exports = class ControlBarView extends CocoView
@level = options.level @level = options.level
@levelID = @level.get('slug') @levelID = @level.get('slug')
@spectateGame = options.spectateGame ? false @spectateGame = options.spectateGame ? false
@observing = options.session.get('creator') isnt me.id
super options super options
if @level.get('type') in ['hero-ladder'] and me.isAdmin() if @level.get('type') in ['hero-ladder'] and me.isAdmin()
@isMultiplayerLevel = true @isMultiplayerLevel = true
@ -66,6 +67,7 @@ module.exports = class ControlBarView extends CocoView
c.difficultyTitle = "#{$.i18n.t 'play.level_difficulty'}#{c.levelDifficulty}" c.difficultyTitle = "#{$.i18n.t 'play.level_difficulty'}#{c.levelDifficulty}"
@lastDifficulty = c.levelDifficulty @lastDifficulty = c.levelDifficulty
c.spectateGame = @spectateGame c.spectateGame = @spectateGame
c.observing = @observing
@homeViewArgs = [{supermodel: if @hasReceivedMemoryWarning then null else @supermodel}] @homeViewArgs = [{supermodel: if @hasReceivedMemoryWarning then null else @supermodel}]
if @level.get('type', true) in ['ladder', 'ladder-tutorial', 'hero-ladder'] if @level.get('type', true) in ['ladder', 'ladder-tutorial', 'hero-ladder']
levelID = @level.get('slug').replace /\-tutorial$/, '' levelID = @level.get('slug').replace /\-tutorial$/, ''

View file

@ -95,6 +95,7 @@ module.exports = class PlayLevelView extends RootView
@isEditorPreview = @getQueryVariable 'dev' @isEditorPreview = @getQueryVariable 'dev'
@sessionID = @getQueryVariable 'session' @sessionID = @getQueryVariable 'session'
@observing = @getQueryVariable 'observing'
@opponentSessionID = @getQueryVariable('opponent') @opponentSessionID = @getQueryVariable('opponent')
@opponentSessionID ?= @options.opponent @opponentSessionID ?= @options.opponent
@ -109,7 +110,7 @@ module.exports = class PlayLevelView extends RootView
setTimeout f, 100 setTimeout f, 100
else else
@load() @load()
application.tracker?.trackEvent 'Started Level Load', category: 'Play Level', level: @levelID, label: @levelID, ['Google Analytics'] application.tracker?.trackEvent 'Started Level Load', category: 'Play Level', level: @levelID, label: @levelID, ['Google Analytics'] unless @observing
setLevel: (@level, givenSupermodel) -> setLevel: (@level, givenSupermodel) ->
@supermodel.models = givenSupermodel.models @supermodel.models = givenSupermodel.models
@ -134,8 +135,9 @@ module.exports = class PlayLevelView extends RootView
@loadEndTime = new Date() @loadEndTime = new Date()
loadDuration = @loadEndTime - @loadStartTime loadDuration = @loadEndTime - @loadStartTime
console.debug "Level unveiled after #{(loadDuration / 1000).toFixed(2)}s" console.debug "Level unveiled after #{(loadDuration / 1000).toFixed(2)}s"
application.tracker?.trackEvent 'Finished Level Load', category: 'Play Level', label: @levelID, level: @levelID, loadDuration: loadDuration, ['Google Analytics'] unless @observing
application.tracker?.trackTiming loadDuration, 'Level Load Time', @levelID, @levelID application.tracker?.trackEvent 'Finished Level Load', category: 'Play Level', label: @levelID, level: @levelID, loadDuration: loadDuration, ['Google Analytics']
application.tracker?.trackTiming loadDuration, 'Level Load Time', @levelID, @levelID
# CocoView overridden methods ############################################### # CocoView overridden methods ###############################################
@ -147,7 +149,7 @@ module.exports = class PlayLevelView extends RootView
afterRender: -> afterRender: ->
super() super()
window.onPlayLevelViewLoaded? @ # still a hack window.onPlayLevelViewLoaded? @ # still a hack
@insertSubView @loadingView = new LevelLoadingView autoUnveil: @options.autoUnveil, level: @levelLoader?.level ? @level # May not have @level loaded yet @insertSubView @loadingView = new LevelLoadingView autoUnveil: @options.autoUnveil or @observing, level: @levelLoader?.level ? @level # May not have @level loaded yet
@$el.find('#level-done-button').hide() @$el.find('#level-done-button').hide()
$('body').addClass('is-playing') $('body').addClass('is-playing')
$('body').bind('touchmove', false) if @isIPadApp() $('body').bind('touchmove', false) if @isIPadApp()
@ -233,7 +235,7 @@ module.exports = class PlayLevelView extends RootView
@god.setGoalManager @goalManager @god.setGoalManager @goalManager
insertSubviews: -> insertSubviews: ->
@insertSubView @tome = new TomeView levelID: @levelID, session: @session, otherSession: @otherSession, thangs: @world.thangs, supermodel: @supermodel, level: @level @insertSubView @tome = new TomeView levelID: @levelID, session: @session, otherSession: @otherSession, thangs: @world.thangs, supermodel: @supermodel, level: @level, observing: @observing
@insertSubView new LevelPlaybackView session: @session, level: @level @insertSubView new LevelPlaybackView session: @session, level: @level
@insertSubView new GoalsView {} @insertSubView new GoalsView {}
@insertSubView new LevelFlagsView levelID: @levelID, world: @world if @$el.hasClass 'flags' @insertSubView new LevelFlagsView levelID: @levelID, world: @world if @$el.hasClass 'flags'
@ -283,7 +285,7 @@ module.exports = class PlayLevelView extends RootView
return unless @levelLoader.progress() is 1 # double check, since closing the guide may trigger this early return unless @levelLoader.progress() is 1 # double check, since closing the guide may trigger this early
# Save latest level played. # Save latest level played.
if not (@levelLoader.level.get('type') in ['ladder', 'ladder-tutorial']) if not @observing and not (@levelLoader.level.get('type') in ['ladder', 'ladder-tutorial'])
me.set('lastLevel', @levelID) me.set('lastLevel', @levelID)
me.save() me.save()
application.tracker?.identify() application.tracker?.identify()
@ -321,7 +323,7 @@ module.exports = class PlayLevelView extends RootView
if @otherSession and not (@level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop']) if @otherSession and not (@level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop'])
# TODO: colorize name and cloud by team, colorize wizard by user's color config # TODO: colorize name and cloud by team, colorize wizard by user's color config
@surface.createOpponentWizard id: @otherSession.get('creator'), name: @otherSession.get('creatorName'), team: @otherSession.get('team'), levelSlug: @level.get('slug'), codeLanguage: @otherSession.get('submittedCodeLanguage') @surface.createOpponentWizard id: @otherSession.get('creator'), name: @otherSession.get('creatorName'), team: @otherSession.get('team'), levelSlug: @level.get('slug'), codeLanguage: @otherSession.get('submittedCodeLanguage')
if @isEditorPreview if @isEditorPreview or @observing
@loadingView.startUnveiling() @loadingView.startUnveiling()
@loadingView.unveil() @loadingView.unveil()
@ -337,7 +339,7 @@ module.exports = class PlayLevelView extends RootView
Backbone.Mediator.publish 'playback:real-time-playback-waiting', {} Backbone.Mediator.publish 'playback:real-time-playback-waiting', {}
@realTimeMultiplayerContinueGame @options.realTimeMultiplayerSessionID @realTimeMultiplayerContinueGame @options.realTimeMultiplayerSessionID
# TODO: Is it possible to create a Mongoose ObjectId for 'ls', instead of the string returned from get()? # TODO: Is it possible to create a Mongoose ObjectId for 'ls', instead of the string returned from get()?
application.tracker?.trackEvent 'Started Level', category:'Play Level', levelID: @levelID, ls: @session?.get('_id') application.tracker?.trackEvent 'Started Level', category:'Play Level', levelID: @levelID, ls: @session?.get('_id') unless @observing
playAmbientSound: -> playAmbientSound: ->
return if @destroyed return if @destroyed
@ -414,7 +416,7 @@ module.exports = class PlayLevelView extends RootView
return if @victorySeen return if @victorySeen
@victorySeen = true @victorySeen = true
victoryTime = (new Date()) - @loadEndTime victoryTime = (new Date()) - @loadEndTime
if victoryTime > 10 * 1000 # Don't track it if we're reloading an already-beaten level if not @observing and victoryTime > 10 * 1000 # Don't track it if we're reloading an already-beaten level
application.tracker?.trackEvent 'Saw Victory', application.tracker?.trackEvent 'Saw Victory',
category: 'Play Level' category: 'Play Level'
level: @level.get('name') level: @level.get('name')
@ -436,12 +438,12 @@ module.exports = class PlayLevelView extends RootView
@tome.reloadAllCode() @tome.reloadAllCode()
Backbone.Mediator.publish 'level:restarted', {} Backbone.Mediator.publish 'level:restarted', {}
$('#level-done-button', @$el).hide() $('#level-done-button', @$el).hide()
application.tracker?.trackEvent 'Confirmed Restart', category: 'Play Level', level: @level.get('name'), label: @level.get('name') application.tracker?.trackEvent 'Confirmed Restart', category: 'Play Level', level: @level.get('name'), label: @level.get('name') unless @observing
onInfiniteLoop: (e) -> onInfiniteLoop: (e) ->
return unless e.firstWorld return unless e.firstWorld
@openModalView new InfiniteLoopModal() @openModalView new InfiniteLoopModal()
application.tracker?.trackEvent 'Saw Initial Infinite Loop', category: 'Play Level', level: @level.get('name'), label: @level.get('name') application.tracker?.trackEvent 'Saw Initial Infinite Loop', category: 'Play Level', level: @level.get('name'), label: @level.get('name') unless @observing
onHighlightDOM: (e) -> @highlightElement e.selector, delay: e.delay, sides: e.sides, offset: e.offset, rotation: e.rotation onHighlightDOM: (e) -> @highlightElement e.selector, delay: e.delay, sides: e.sides, offset: e.offset, rotation: e.rotation

View file

@ -27,6 +27,7 @@ module.exports = class CastButtonView extends CocoView
@spells = options.spells @spells = options.spells
@castShortcut = '⇧↵' @castShortcut = '⇧↵'
@updateReplayabilityInterval = setInterval @updateReplayability, 1000 @updateReplayabilityInterval = setInterval @updateReplayability, 1000
@observing = options.session.get('creator') isnt me.id
destroy: -> destroy: ->
clearInterval @updateReplayabilityInterval clearInterval @updateReplayabilityInterval
@ -40,6 +41,7 @@ module.exports = class CastButtonView extends CocoView
castRealTimeShortcutVerbose = (if @isMac() then 'Cmd' else 'Ctrl') + '+' + castShortcutVerbose castRealTimeShortcutVerbose = (if @isMac() then 'Cmd' else 'Ctrl') + '+' + castShortcutVerbose
context.castVerbose = castShortcutVerbose + ': ' + $.i18n.t('keyboard_shortcuts.run_code') context.castVerbose = castShortcutVerbose + ': ' + $.i18n.t('keyboard_shortcuts.run_code')
context.castRealTimeVerbose = castRealTimeShortcutVerbose + ': ' + $.i18n.t('keyboard_shortcuts.run_real_time') context.castRealTimeVerbose = castRealTimeShortcutVerbose + ': ' + $.i18n.t('keyboard_shortcuts.run_real_time')
context.observing = @observing
context context
afterRender: -> afterRender: ->

View file

@ -15,6 +15,7 @@ module.exports = class Spell
@otherSession = options.otherSession @otherSession = options.otherSession
@spectateView = options.spectateView @spectateView = options.spectateView
@spectateOpponentCodeLanguage = options.spectateOpponentCodeLanguage @spectateOpponentCodeLanguage = options.spectateOpponentCodeLanguage
@observing = options.observing
@supermodel = options.supermodel @supermodel = options.supermodel
@skipProtectAPI = options.skipProtectAPI @skipProtectAPI = options.skipProtectAPI
@worker = options.worker @worker = options.worker
@ -189,6 +190,7 @@ module.exports = class Spell
return true if @spectateView # Use transpiled code for both teams if we're just spectating. return true if @spectateView # Use transpiled code for both teams if we're just spectating.
return true if @isEnemySpell() # Use transpiled for enemy spells. return true if @isEnemySpell() # Use transpiled for enemy spells.
# Players without permissions can't view the raw code. # Players without permissions can't view the raw code.
return false if @observing and @levelType is 'hero'
return true if @session.get('creator') isnt me.id and not (me.isAdmin() or 'employer' in me.get('permissions', true)) return true if @session.get('creator') isnt me.id and not (me.isAdmin() or 'employer' in me.get('permissions', true))
false false

View file

@ -69,6 +69,7 @@ module.exports = class SpellView extends CocoView
@writable = false unless me.team in @spell.permissions.readwrite # TODO: make this do anything @writable = false unless me.team in @spell.permissions.readwrite # TODO: make this do anything
@highlightCurrentLine = _.throttle @highlightCurrentLine, 100 @highlightCurrentLine = _.throttle @highlightCurrentLine, 100
$(window).on 'resize', @onWindowResize $(window).on 'resize', @onWindowResize
@observing = @session.get('creator') isnt me.id
afterRender: -> afterRender: ->
super() super()
@ -119,14 +120,15 @@ module.exports = class SpellView extends CocoView
name: 'run-code' name: 'run-code'
bindKey: {win: 'Shift-Enter|Ctrl-Enter', mac: 'Shift-Enter|Command-Enter|Ctrl-Enter'} bindKey: {win: 'Shift-Enter|Ctrl-Enter', mac: 'Shift-Enter|Command-Enter|Ctrl-Enter'}
exec: -> Backbone.Mediator.publish 'tome:manual-cast', {} exec: -> Backbone.Mediator.publish 'tome:manual-cast', {}
addCommand unless @observing
name: 'run-code-real-time' addCommand
bindKey: {win: 'Ctrl-Shift-Enter', mac: 'Command-Shift-Enter|Ctrl-Shift-Enter'} name: 'run-code-real-time'
exec: => bindKey: {win: 'Ctrl-Shift-Enter', mac: 'Command-Shift-Enter|Ctrl-Shift-Enter'}
if @options.level.get('replayable') and (timeUntilResubmit = @session.timeUntilResubmit()) > 0 exec: =>
Backbone.Mediator.publish 'tome:manual-cast-denied', timeUntilResubmit: timeUntilResubmit if @options.level.get('replayable') and (timeUntilResubmit = @session.timeUntilResubmit()) > 0
else Backbone.Mediator.publish 'tome:manual-cast-denied', timeUntilResubmit: timeUntilResubmit
Backbone.Mediator.publish 'tome:manual-cast', {realTime: true} else
Backbone.Mediator.publish 'tome:manual-cast', {realTime: true}
addCommand addCommand
name: 'no-op' name: 'no-op'
bindKey: {win: 'Ctrl-S', mac: 'Command-S|Ctrl-S'} bindKey: {win: 'Ctrl-S', mac: 'Command-S|Ctrl-S'}

View file

@ -134,6 +134,7 @@ module.exports = class TomeView extends CocoView
language: language language: language
spectateView: @options.spectateView spectateView: @options.spectateView
spectateOpponentCodeLanguage: @options.spectateOpponentCodeLanguage spectateOpponentCodeLanguage: @options.spectateOpponentCodeLanguage
observing: @options.observing
levelID: @options.levelID levelID: @options.levelID
level: @options.level level: @options.level

View file

@ -15,7 +15,8 @@ module.exports = class LeaderboardTabView extends CocoView
template: template template: template
className: 'leaderboard-tab-view' className: 'leaderboard-tab-view'
events: {} events:
'click tbody tr.viewable': 'onClickRow'
constructor: (options) -> constructor: (options) ->
super options super options
@ -67,3 +68,8 @@ module.exports = class LeaderboardTabView extends CocoView
@hasShown = true @hasShown = true
topScores = new TopScoresCollection @level, @scoreType, @timespan topScores = new TopScoresCollection @level, @scoreType, @timespan
@sessions = @supermodel.loadCollection(topScores, 'sessions', null, 0).model @sessions = @supermodel.loadCollection(topScores, 'sessions', null, 0).model
onClickRow: (e) ->
sessionID = $(e.target).closest('tr').data 'session-id'
url = "/play/level/#{@level.get('slug')}?session=#{sessionID}&observing=true"
window.open url, '_blank'

View file

@ -15,7 +15,10 @@ class LevelSessionHandler extends Handler
formatEntity: (req, document) -> formatEntity: (req, document) ->
documentObject = super(req, document) documentObject = super(req, document)
if req.user?.isAdmin() or req.user?.id is document.creator or ('employer' in (req.user?.get('permissions') ? [])) if req.user?.isAdmin() or
req.user?.id is document.creator or
('employer' in (req.user?.get('permissions') ? [])) or
!document.submittedCode # TODO: only allow leaderboard access to non-top-5 solutions
return documentObject return documentObject
else else
return _.omit documentObject, @privateProperties return _.omit documentObject, @privateProperties
@ -47,8 +50,9 @@ class LevelSessionHandler extends Handler
@sendSuccess res, documents @sendSuccess res, documents
hasAccessToDocument: (req, document, method=null) -> hasAccessToDocument: (req, document, method=null) ->
return true if req.method is 'GET' and document.get('submitted') get = (method ? req.method).toLowerCase() is 'get'
return true if ('employer' in (req.user?.get('permissions') ? [])) and (method ? req.method).toLowerCase() is 'get' return true if get and document.get('submitted')
return true if get and ('employer' in (req.user?.get('permissions') ? []))
super(arguments...) super(arguments...)
getCodeLanguageCounts: (req, res) -> getCodeLanguageCounts: (req, res) ->