mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-04-29 23:43:51 -04:00
Merge branch 'master' into production
This commit is contained in:
commit
04f63c7768
11 changed files with 76 additions and 71 deletions
app
lib/world
models
schemas/subscriptions
templates/editor/campaign
views
editor
play
scripts/devSetup
|
@ -73,7 +73,7 @@ module.exports = class Thang
|
||||||
for [prop, type] in props
|
for [prop, type] in props
|
||||||
unless type in ThangState.trackedPropertyTypes
|
unless type in ThangState.trackedPropertyTypes
|
||||||
# How should errors for busted Components work? We can't recover from this and run the world.
|
# How should errors for busted Components work? We can't recover from this and run the world.
|
||||||
throw new Error "Type #{type} for property #{prop} is not a trackable property type: #{trackedPropertyTypes}"
|
throw new Error "Type #{type} for property #{prop} is not a trackable property type: #{ThangState.trackedPropertyTypes}"
|
||||||
oldPropIndex = @trackedPropertiesKeys.indexOf prop
|
oldPropIndex = @trackedPropertiesKeys.indexOf prop
|
||||||
if oldPropIndex is -1
|
if oldPropIndex is -1
|
||||||
@trackedPropertiesKeys.push prop
|
@trackedPropertiesKeys.push prop
|
||||||
|
|
|
@ -9,7 +9,6 @@ module.exports = class Level extends CocoModel
|
||||||
@levels:
|
@levels:
|
||||||
'dungeons-of-kithgard': '5411cb3769152f1707be029c'
|
'dungeons-of-kithgard': '5411cb3769152f1707be029c'
|
||||||
'defense-of-plainswood': '541b67f71ccc8eaae19f3c62'
|
'defense-of-plainswood': '541b67f71ccc8eaae19f3c62'
|
||||||
'the-mighty-sand-yak': '5480b9d01bf0b10000711c5f'
|
|
||||||
urlRoot: '/db/level'
|
urlRoot: '/db/level'
|
||||||
|
|
||||||
serialize: (supermodel, session, otherSession, cached=false) ->
|
serialize: (supermodel, session, otherSession, cached=false) ->
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
c = require 'schemas/schemas'
|
c = require 'schemas/schemas'
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
|
'editor:campaign-analytics-modal-closed': c.object {title: 'Campaign editor analytics modal closed'},
|
||||||
|
targetLevelSlug: {type: 'string'}
|
||||||
|
|
||||||
'editor:save-new-version': c.object {title: 'Save New Version', description: 'Published when a version gets saved', required: ['major', 'commitMessage']},
|
'editor:save-new-version': c.object {title: 'Save New Version', description: 'Published when a version gets saved', required: ['major', 'commitMessage']},
|
||||||
major: {type: 'boolean'}
|
major: {type: 'boolean'}
|
||||||
commitMessage: {type: 'string'}
|
commitMessage: {type: 'string'}
|
||||||
|
|
|
@ -7,6 +7,7 @@ block modal-header-content
|
||||||
input.form-control#input-startday(type='text', style='width:100px;', value=campaignCompletions.startDay)
|
input.form-control#input-startday(type='text', style='width:100px;', value=campaignCompletions.startDay)
|
||||||
input.form-control#input-endday(type='text', style='width:100px;', value=campaignCompletions.endDay)
|
input.form-control#input-endday(type='text', style='width:100px;', value=campaignCompletions.endDay)
|
||||||
button.btn.btn-default.btn-sm#reload-button(style='margin-left:10px;') Reload
|
button.btn.btn-default.btn-sm#reload-button(style='margin-left:10px;') Reload
|
||||||
|
div(style='font-size:10px') Double-click row to open level details.
|
||||||
|
|
||||||
block modal-body-content
|
block modal-body-content
|
||||||
if campaignCompletions && campaignCompletions.levels
|
if campaignCompletions && campaignCompletions.levels
|
||||||
|
@ -23,7 +24,7 @@ block modal-body-content
|
||||||
td Completion %
|
td Completion %
|
||||||
tbody
|
tbody
|
||||||
- for (var i = 0; i < campaignCompletions.levels.length; i++)
|
- for (var i = 0; i < campaignCompletions.levels.length; i++)
|
||||||
tr
|
tr.level(data-level-slug=campaignCompletions.levels[i].level)
|
||||||
td.level-name-container= campaignCompletions.levels[i].level
|
td.level-name-container= campaignCompletions.levels[i].level
|
||||||
span.level-name-background(style="width:#{campaignCompletions.levels[i].usersRemaining || 0}%;")
|
span.level-name-background(style="width:#{campaignCompletions.levels[i].usersRemaining || 0}%;")
|
||||||
td= campaignCompletions.levels[i].started
|
td= campaignCompletions.levels[i].started
|
||||||
|
|
|
@ -13,6 +13,7 @@ module.exports = class CampaignAnalyticsModal extends ModalView
|
||||||
|
|
||||||
events:
|
events:
|
||||||
'click #reload-button': 'onClickReloadButton'
|
'click #reload-button': 'onClickReloadButton'
|
||||||
|
'dblclick .level': 'onDblClickLevel'
|
||||||
|
|
||||||
constructor: (options, @campaignHandle, @campaignCompletions) ->
|
constructor: (options, @campaignHandle, @campaignCompletions) ->
|
||||||
super options
|
super options
|
||||||
|
@ -29,6 +30,20 @@ module.exports = class CampaignAnalyticsModal extends ModalView
|
||||||
$("#input-endday").datepicker dateFormat: "yy-mm-dd"
|
$("#input-endday").datepicker dateFormat: "yy-mm-dd"
|
||||||
@addCompletionLineGraphs()
|
@addCompletionLineGraphs()
|
||||||
|
|
||||||
|
onClickReloadButton: () =>
|
||||||
|
startDay = $('#input-startday').val()
|
||||||
|
endDay = $('#input-endday').val()
|
||||||
|
delete @campaignCompletions.levels
|
||||||
|
@campaignCompletions.startDay = startDay
|
||||||
|
@campaignCompletions.endDay = endDay
|
||||||
|
@render()
|
||||||
|
@getCampaignAnalytics startDay, endDay
|
||||||
|
|
||||||
|
onDblClickLevel: (e) ->
|
||||||
|
row = $(e.target).parents('.level')
|
||||||
|
Backbone.Mediator.publish 'editor:campaign-analytics-modal-closed', targetLevelSlug: row.data 'level-slug'
|
||||||
|
@hide()
|
||||||
|
|
||||||
addCompletionLineGraphs: ->
|
addCompletionLineGraphs: ->
|
||||||
return unless @campaignCompletions.levels
|
return unless @campaignCompletions.levels
|
||||||
for level in @campaignCompletions.levels
|
for level in @campaignCompletions.levels
|
||||||
|
@ -38,57 +53,43 @@ module.exports = class CampaignAnalyticsModal extends ModalView
|
||||||
days.push
|
days.push
|
||||||
day: day
|
day: day
|
||||||
rate: level['days'][day].finished / level['days'][day].started
|
rate: level['days'][day].finished / level['days'][day].started
|
||||||
|
count: level['days'][day].started
|
||||||
days.sort (a, b) -> a.day - b.day
|
days.sort (a, b) -> a.day - b.day
|
||||||
data = []
|
data = []
|
||||||
for i in [0...days.length]
|
for i in [0...days.length]
|
||||||
data.push
|
data.push
|
||||||
x: i
|
x: i
|
||||||
y: days[i].rate
|
y: days[i].rate
|
||||||
|
c: days[i].count
|
||||||
@addLineGraph '#background' + level.level, data
|
@addLineGraph '#background' + level.level, data
|
||||||
|
|
||||||
addLineGraph: (containerSelector, lineData, lineColor='green', min=0, max=1.0) ->
|
addLineGraph: (containerSelector, lineData, lineColor='green', min=0, max=1.0) ->
|
||||||
# Add a line chart to the given container
|
# Add a line chart to the given container
|
||||||
|
# Adjust stroke-weight based on segment count: width 0.3 to 3.0 for counts roughly 100 to 10000
|
||||||
# TODO: Move this to a utility library
|
# TODO: Move this to a utility library
|
||||||
vis = d3.select(containerSelector)
|
vis = d3.select(containerSelector)
|
||||||
width = $(containerSelector).width()
|
width = $(containerSelector).width()
|
||||||
height = $(containerSelector).height()
|
height = $(containerSelector).height()
|
||||||
xRange = d3.scale.linear().range([0, width]).domain([d3.min(lineData, (d) -> d.x), d3.max(lineData, (d) -> d.x)])
|
xRange = d3.scale.linear().range([0, width]).domain([d3.min(lineData, (d) -> d.x), d3.max(lineData, (d) -> d.x)])
|
||||||
yRange = d3.scale.linear().range([height, 0]).domain([min, max])
|
yRange = d3.scale.linear().range([height, 0]).domain([min, max])
|
||||||
xAxis = d3.svg.axis()
|
lines = []
|
||||||
.scale(xRange)
|
for i in [0...lineData.length-1]
|
||||||
.tickSize(5)
|
lines.push
|
||||||
.tickSubdivide(true)
|
x1: xRange(lineData[i].x)
|
||||||
yAxis = d3.svg.axis()
|
y1: yRange(lineData[i].y)
|
||||||
.scale(yRange)
|
x2: xRange(lineData[i + 1].x)
|
||||||
.tickSize(5)
|
y2: yRange(lineData[i + 1].y)
|
||||||
.orient('left')
|
strokeWidth: Math.min(3, Math.max(0.3, Math.log(lineData[i].c/10)/2))
|
||||||
.tickSubdivide(true)
|
vis.selectAll('.line')
|
||||||
vis.append('svg:g')
|
.data(lines)
|
||||||
.attr('class', 'x axis')
|
.enter()
|
||||||
.attr('transform', 'translate(0,' + height + ')')
|
.append("line")
|
||||||
.call(xAxis)
|
.attr("x1", (d) -> d.x1)
|
||||||
vis.append('svg:g')
|
.attr("y1", (d) -> d.y1)
|
||||||
.attr('class', 'y axis')
|
.attr("x2", (d) -> d.x2)
|
||||||
.attr('transform', 'translate(0,0)')
|
.attr("y2", (d) -> d.y2)
|
||||||
.call(yAxis)
|
.style("stroke-width", (d) -> d.strokeWidth)
|
||||||
lineFunc = d3.svg.line()
|
.style("stroke", lineColor)
|
||||||
.x((d) -> xRange(d.x))
|
|
||||||
.y((d) -> yRange(d.y))
|
|
||||||
.interpolate('linear')
|
|
||||||
vis.append('svg:path')
|
|
||||||
.attr('d', lineFunc(lineData))
|
|
||||||
.attr('stroke', lineColor)
|
|
||||||
.attr('stroke-width', 1)
|
|
||||||
.attr('fill', 'none')
|
|
||||||
|
|
||||||
onClickReloadButton: () =>
|
|
||||||
startDay = $('#input-startday').val()
|
|
||||||
endDay = $('#input-endday').val()
|
|
||||||
delete @campaignCompletions.levels
|
|
||||||
@campaignCompletions.startDay = startDay
|
|
||||||
@campaignCompletions.endDay = endDay
|
|
||||||
@render()
|
|
||||||
@getCampaignAnalytics startDay, endDay
|
|
||||||
|
|
||||||
getCampaignAnalytics: (startDay, endDay) =>
|
getCampaignAnalytics: (startDay, endDay) =>
|
||||||
if startDay?
|
if startDay?
|
||||||
|
|
|
@ -24,6 +24,9 @@ module.exports = class CampaignEditorView extends RootView
|
||||||
'click #analytics-button': 'onClickAnalyticsButton'
|
'click #analytics-button': 'onClickAnalyticsButton'
|
||||||
'click #save-button': 'onClickSaveButton'
|
'click #save-button': 'onClickSaveButton'
|
||||||
|
|
||||||
|
subscriptions:
|
||||||
|
'editor:campaign-analytics-modal-closed' : 'onAnalyticsModalClosed'
|
||||||
|
|
||||||
constructor: (options, @campaignHandle) ->
|
constructor: (options, @campaignHandle) ->
|
||||||
super(options)
|
super(options)
|
||||||
@campaign = new Campaign({_id:@campaignHandle})
|
@campaign = new Campaign({_id:@campaignHandle})
|
||||||
|
@ -140,6 +143,13 @@ module.exports = class CampaignEditorView extends RootView
|
||||||
onClickAnalyticsButton: ->
|
onClickAnalyticsButton: ->
|
||||||
@openModalView new CampaignAnalyticsModal {}, @campaignHandle, @campaignAnalytics
|
@openModalView new CampaignAnalyticsModal {}, @campaignHandle, @campaignAnalytics
|
||||||
|
|
||||||
|
onAnalyticsModalClosed: (options) ->
|
||||||
|
if options.targetLevelSlug? and @treema.childrenTreemas?.levels?.childrenTreemas?
|
||||||
|
for original, level of @treema.childrenTreemas.levels.childrenTreemas
|
||||||
|
if level.data?.slug is options.targetLevelSlug
|
||||||
|
@openCampaignLevelView @supermodel.getModelByOriginal Level, original
|
||||||
|
break
|
||||||
|
|
||||||
onClickSaveButton: ->
|
onClickSaveButton: ->
|
||||||
@toSave.set @toSave.filter (m) -> m.hasLocalChanges()
|
@toSave.set @toSave.filter (m) -> m.hasLocalChanges()
|
||||||
@openModalView new SaveCampaignModal({}, @toSave)
|
@openModalView new SaveCampaignModal({}, @toSave)
|
||||||
|
|
|
@ -200,5 +200,5 @@ module.exports = class LevelEditView extends RootView
|
||||||
|
|
||||||
incrementBuildTime: =>
|
incrementBuildTime: =>
|
||||||
return if application.userIsIdle
|
return if application.userIsIdle
|
||||||
@levelBuildTime ?= @level.get('buildTime')
|
@levelBuildTime ?= @level.get('buildTime') ? 0
|
||||||
++@levelBuildTime
|
++@levelBuildTime
|
||||||
|
|
|
@ -138,6 +138,7 @@ module.exports = class CampaignView extends RootView
|
||||||
level.locked = not me.ownsLevel level.original
|
level.locked = not me.ownsLevel level.original
|
||||||
level.locked = false if @levelStatusMap[level.slug] in ['started', 'complete']
|
level.locked = false if @levelStatusMap[level.slug] in ['started', 'complete']
|
||||||
level.locked = false if @editorMode
|
level.locked = false if @editorMode
|
||||||
|
level.locked = false if @campaign.get('name') is 'Auditions'
|
||||||
level.disabled = true if level.adminOnly and @levelStatusMap[level.slug] not in ['started', 'complete']
|
level.disabled = true if level.adminOnly and @levelStatusMap[level.slug] not in ['started', 'complete']
|
||||||
level.color = 'rgb(255, 80, 60)'
|
level.color = 'rgb(255, 80, 60)'
|
||||||
if level.requiresSubscription
|
if level.requiresSubscription
|
||||||
|
|
|
@ -88,7 +88,7 @@ module.exports = class HeroVictoryModal extends ModalView
|
||||||
me.fetch cache: false unless me.loading
|
me.fetch cache: false unless me.loading
|
||||||
|
|
||||||
@readyToContinue = true if not @achievements.models.length
|
@readyToContinue = true if not @achievements.models.length
|
||||||
|
|
||||||
# have to use a something resource because addModelResource doesn't handle models being upserted/fetched via POST like we're doing here
|
# have to use a something resource because addModelResource doesn't handle models being upserted/fetched via POST like we're doing here
|
||||||
@newEarnedAchievementsResource = @supermodel.addSomethingResource('earned achievements') if @newEarnedAchievements.length
|
@newEarnedAchievementsResource = @supermodel.addSomethingResource('earned achievements') if @newEarnedAchievements.length
|
||||||
|
|
||||||
|
@ -314,7 +314,7 @@ module.exports = class HeroVictoryModal extends ModalView
|
||||||
AudioPlayer.playSound name, 1
|
AudioPlayer.playSound name, 1
|
||||||
|
|
||||||
getNextLevelCampaign: ->
|
getNextLevelCampaign: ->
|
||||||
{'kithgard-gates': 'forest', 'siege-of-stonehold': 'desert'}[@level.get('slug')] or @level.get 'campaign' # Much easier to just keep this updated than to dynamically figure it out.
|
{'kithgard-gates': 'forest', 'siege-of-stonehold': 'desert', 'clash-of-clones': 'mountain'}[@level.get('slug')] or @level.get 'campaign' # Much easier to just keep this updated than to dynamically figure it out.
|
||||||
|
|
||||||
getNextLevelLink: ->
|
getNextLevelLink: ->
|
||||||
link = '/play'
|
link = '/play'
|
||||||
|
|
|
@ -126,7 +126,7 @@ exports.config =
|
||||||
'vendor/scripts/jasmine-html.js'
|
'vendor/scripts/jasmine-html.js'
|
||||||
'vendor/scripts/jasmine-boot.js'
|
'vendor/scripts/jasmine-boot.js'
|
||||||
'vendor/scripts/jasmine-mock-ajax.js'
|
'vendor/scripts/jasmine-mock-ajax.js'
|
||||||
|
|
||||||
# vendor.js ordering
|
# vendor.js ordering
|
||||||
'bower_components/jquery/dist/jquery.js'
|
'bower_components/jquery/dist/jquery.js'
|
||||||
'bower_components/lodash/dist/lodash.js'
|
'bower_components/lodash/dist/lodash.js'
|
||||||
|
|
|
@ -101,35 +101,6 @@ class LinuxSetup(SetupFactory):
|
||||||
|
|
||||||
def distroSetup(self):
|
def distroSetup(self):
|
||||||
distro = self.detectDistro()
|
distro = self.detectDistro()
|
||||||
if distro == "arch":
|
|
||||||
print("Arch Linux detected. Would you like to install \n"
|
|
||||||
"NodeJS and MongoDB via pacman? [y/N]")
|
|
||||||
if raw_input().lower() in ["y", "yes"]:
|
|
||||||
try:
|
|
||||||
subprocess.check_call(["pacman", "-S",
|
|
||||||
"nodejs", "mongodb",
|
|
||||||
"--noconfirm"])
|
|
||||||
except subprocess.CalledProcessError as err:
|
|
||||||
print("Installation failed. Retry, Continue, or "
|
|
||||||
"Abort? [r/c/A]")
|
|
||||||
answer = raw_input().lower()
|
|
||||||
if answer in ["r", "retry"]:
|
|
||||||
return(self.distroSetup())
|
|
||||||
elif answer in ["c", "continue"]:
|
|
||||||
return()
|
|
||||||
else:
|
|
||||||
exit(1)
|
|
||||||
else:
|
|
||||||
#try:
|
|
||||||
#print("Enabling and starting MongoDB in systemd.")
|
|
||||||
#subprocess.check_call(["systemctl", "enable",
|
|
||||||
# "mongodb.service"])
|
|
||||||
#subprocess.check_call(["systemctl", "start",
|
|
||||||
# "mongodb.service"])
|
|
||||||
#print("Node and Mongo installed. Continuing.")
|
|
||||||
#except subprocess.CalledProcessError as err:
|
|
||||||
#print("Mongo failed to start. Aborting")
|
|
||||||
#exit(1)
|
|
||||||
if distro == "ubuntu":
|
if distro == "ubuntu":
|
||||||
print("Ubuntu installation detected. Would you like to install \n"
|
print("Ubuntu installation detected. Would you like to install \n"
|
||||||
"NodeJS and MongoDB via apt-get? [y/N]")
|
"NodeJS and MongoDB via apt-get? [y/N]")
|
||||||
|
@ -180,3 +151,22 @@ class LinuxSetup(SetupFactory):
|
||||||
#except subprocess.CalledProcessError as err:
|
#except subprocess.CalledProcessError as err:
|
||||||
#print("Mongo failed to start. Aborting.")
|
#print("Mongo failed to start. Aborting.")
|
||||||
#exit(1)
|
#exit(1)
|
||||||
|
if distro == "arch":
|
||||||
|
print("Arch Linux detected. Would you like to install \n"
|
||||||
|
"NodeJS and MongoDB via pacman? [y/N]")
|
||||||
|
if raw_input().lower() in ["y", "yes"]:
|
||||||
|
try:
|
||||||
|
subprocess.check_call(["pacman", "-S",
|
||||||
|
"nodejs", "mongodb",
|
||||||
|
"--noconfirm"])
|
||||||
|
except subprocess.CalledProcessError as err:
|
||||||
|
print("Installation failed. Retry, Continue, or "
|
||||||
|
"Abort? [r/c/A]")
|
||||||
|
answer = raw_input().lower()
|
||||||
|
if answer in ["r", "retry"]:
|
||||||
|
return(self.distroSetup())
|
||||||
|
elif answer in ["c", "continue"]:
|
||||||
|
return()
|
||||||
|
else:
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue