mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-25 00:28:31 -05:00
385 lines
14 KiB
CoffeeScript
385 lines
14 KiB
CoffeeScript
RootView = require 'views/core/RootView'
|
|
Campaign = require 'models/Campaign'
|
|
Level = require 'models/Level'
|
|
Achievement = require 'models/Achievement'
|
|
ThangType = require 'models/ThangType'
|
|
CampaignView = require 'views/play/CampaignView'
|
|
CocoCollection = require 'collections/CocoCollection'
|
|
treemaExt = require 'core/treema-ext'
|
|
utils = require 'core/utils'
|
|
SaveCampaignModal = require './SaveCampaignModal'
|
|
RelatedAchievementsCollection = require 'collections/RelatedAchievementsCollection'
|
|
CampaignLevelView = require './CampaignLevelView'
|
|
|
|
achievementProject = ['related', 'rewards', 'name', 'slug']
|
|
thangTypeProject = ['name', 'original']
|
|
|
|
module.exports = class CampaignEditorView extends RootView
|
|
id: "campaign-editor-view"
|
|
template: require 'templates/editor/campaign/campaign-editor-view'
|
|
className: 'editor'
|
|
|
|
events:
|
|
'click #save-button': 'onClickSaveButton'
|
|
|
|
constructor: (options, @campaignHandle) ->
|
|
super(options)
|
|
@campaign = new Campaign({_id:@campaignHandle})
|
|
@supermodel.loadModel(@campaign, 'campaign')
|
|
|
|
@levels = new CocoCollection([], {
|
|
model: Level
|
|
url: "/db/campaign/#{@campaignHandle}/levels"
|
|
project: Campaign.denormalizedLevelProperties
|
|
})
|
|
@supermodel.loadCollection(@levels, 'levels')
|
|
|
|
@achievements = new CocoCollection([], {
|
|
model: Achievement
|
|
url: "/db/campaign/#{@campaignHandle}/achievements"
|
|
project: achievementProject
|
|
})
|
|
@supermodel.loadCollection(@achievements, 'achievements')
|
|
|
|
@toSave = new Backbone.Collection()
|
|
@listenToOnce @campaign ,'sync', @loadThangTypeNames
|
|
@listenToOnce @campaign, 'sync', @onFundamentalLoaded
|
|
@listenToOnce @levels, 'sync', @onFundamentalLoaded
|
|
@listenToOnce @achievements, 'sync', @onFundamentalLoaded
|
|
|
|
_.delay @getCampaignAnalytics, 500
|
|
|
|
loadThangTypeNames: ->
|
|
# Load the names of the ThangTypes that this level's Treema nodes might want to display.
|
|
originals = []
|
|
for level in _.values(@campaign.get('levels'))
|
|
originals = originals.concat(_.values(level.requiredGear)) if level.requiredGear
|
|
originals = originals.concat(_.values(level.restrictedGear)) if level.restrictedGear
|
|
originals = originals.concat(level.allowedHeroes) if level.allowedHeroes
|
|
originals = _.uniq _.flatten originals
|
|
for original in originals
|
|
thangType = new ThangType()
|
|
thangType.setProjection(thangTypeProject)
|
|
thangType.setURL("/db/thang.type/#{original}/version")
|
|
@supermodel.loadModel(thangType, 'thang')
|
|
|
|
onFundamentalLoaded: ->
|
|
# Load any levels which haven't been denormalized into our campaign.
|
|
return unless @campaign.loaded and @levels.loaded and @achievements.loaded
|
|
for level in _.values(@campaign.get('levels'))
|
|
continue if model = @levels.findWhere(original: level.original)
|
|
model = new Level({})
|
|
model.setProjection Campaign.denormalizedLevelProperties
|
|
model.setURL("/db/level/#{level.original}/version")
|
|
@levels.add @supermodel.loadModel(model, 'level').model
|
|
achievements = new RelatedAchievementsCollection level.original
|
|
achievements.setProjection achievementProject
|
|
@supermodel.loadCollection achievements, 'achievements'
|
|
@listenToOnce achievements, 'sync', ->
|
|
@achievements.add(achievements.models)
|
|
|
|
onLoaded: ->
|
|
@toSave.add @campaign if @campaign.hasLocalChanges()
|
|
campaignLevels = $.extend({}, @campaign.get('levels'))
|
|
for level in @levels.models
|
|
levelOriginal = level.get('original')
|
|
campaignLevel = campaignLevels[levelOriginal]
|
|
continue if not campaignLevel
|
|
|
|
$.extend campaignLevel, _.omit(level.attributes, '_id')
|
|
achievements = @achievements.where {'related': levelOriginal}
|
|
rewards = []
|
|
for achievement in achievements
|
|
for rewardType, rewardArray of achievement.get('rewards')
|
|
for reward in rewardArray
|
|
rewardObject = { achievement: achievement.id }
|
|
|
|
if rewardType is 'heroes'
|
|
rewardObject.hero = reward
|
|
thangType = new ThangType({}, {project: thangTypeProject})
|
|
thangType.setURL("/db/thang.type/#{reward}/version")
|
|
@supermodel.loadModel(thangType, 'thang')
|
|
|
|
if rewardType is 'levels'
|
|
rewardObject.level = reward
|
|
if not @levels.findWhere({original: reward})
|
|
level = new Level({}, {project: Campaign.denormalizedLevelProperties})
|
|
level.setURL("/db/level/#{reward}/version")
|
|
@supermodel.loadModel(level, 'level')
|
|
|
|
if rewardType is 'items'
|
|
rewardObject.item = reward
|
|
thangType = new ThangType({}, {project: thangTypeProject})
|
|
thangType.setURL("/db/thang.type/#{reward}/version")
|
|
@supermodel.loadModel(thangType, 'thang')
|
|
|
|
rewards.push rewardObject
|
|
campaignLevel.rewards = rewards
|
|
delete campaignLevel.unlocks
|
|
campaignLevel.campaign = @campaign.get 'slug'
|
|
campaignLevels[levelOriginal] = campaignLevel
|
|
|
|
@campaign.set('levels', campaignLevels)
|
|
|
|
for level in _.values campaignLevels
|
|
model = @levels.findWhere {original: level.original}
|
|
model.set key, level[key] for key in Campaign.denormalizedLevelProperties
|
|
@toSave.add model if model.hasLocalChanges()
|
|
@updateRewardsForLevel model, level.rewards
|
|
|
|
super()
|
|
|
|
getRenderData: ->
|
|
c = super()
|
|
c.campaign = @campaign
|
|
c.campaignCompletions = @campaignCompletions
|
|
c
|
|
|
|
onClickSaveButton: ->
|
|
@toSave.set @toSave.filter (m) -> m.hasLocalChanges()
|
|
@openModalView new SaveCampaignModal({}, @toSave)
|
|
|
|
afterRender: ->
|
|
super()
|
|
treemaOptions =
|
|
schema: Campaign.schema
|
|
data: $.extend({}, @campaign.attributes)
|
|
filePath: "db/campaign/#{@campaign.get('_id')}"
|
|
callbacks:
|
|
change: @onTreemaChanged
|
|
select: @onTreemaSelectionChanged
|
|
dblclick: @onTreemaDoubleClicked
|
|
nodeClasses:
|
|
levels: LevelsNode
|
|
level: LevelNode
|
|
campaigns: CampaignsNode
|
|
campaign: CampaignNode
|
|
achievement: AchievementNode
|
|
supermodel: @supermodel
|
|
|
|
@treema = @$el.find('#campaign-treema').treema treemaOptions
|
|
@treema.build()
|
|
@treema.open()
|
|
@treema.childrenTreemas.levels?.open()
|
|
|
|
@campaignView = new CampaignView({editorMode: true, supermodel: @supermodel}, @campaignHandle)
|
|
@campaignView.highlightElement = _.noop # make it stop
|
|
@listenTo @campaignView, 'level-moved', @onCampaignLevelMoved
|
|
@listenTo @campaignView, 'adjacent-campaign-moved', @onAdjacentCampaignMoved
|
|
@listenTo @campaignView, 'level-clicked', @onCampaignLevelClicked
|
|
@listenTo @campaignView, 'level-double-clicked', @onCampaignLevelDoubleClicked
|
|
@insertSubView @campaignView
|
|
|
|
onTreemaChanged: (e, nodes) =>
|
|
for node in nodes
|
|
path = node.getPath()
|
|
if _.string.startsWith path, '/levels/'
|
|
parts = path.split('/')
|
|
original = parts[2]
|
|
level = @supermodel.getModelByOriginal Level, original
|
|
campaignLevel = @treema.get "/levels/#{original}"
|
|
|
|
@updateRewardsForLevel level, campaignLevel.rewards
|
|
|
|
level.set key, campaignLevel[key] for key in Campaign.denormalizedLevelProperties
|
|
@toSave.add level if level.hasLocalChanges()
|
|
|
|
@toSave.add @campaign
|
|
@campaign.set key, value for key, value of @treema.data
|
|
@campaignView.setCampaign(@campaign)
|
|
|
|
onTreemaDoubleClicked: (e, node) =>
|
|
path = node.getPath()
|
|
return unless _.string.startsWith path, '/levels/'
|
|
original = path.split('/')[2]
|
|
@openCampaignLevelView @supermodel.getModelByOriginal Level, original
|
|
|
|
onCampaignLevelMoved: (e) ->
|
|
path = "levels/#{e.levelOriginal}/position"
|
|
@treema.set path, e.position
|
|
|
|
onAdjacentCampaignMoved: (e) ->
|
|
path = "adjacentCampaigns/#{e.campaignID}/position"
|
|
@treema.set path, e.position
|
|
|
|
onCampaignLevelClicked: (levelOriginal) ->
|
|
return unless levelTreema = @treema.childrenTreemas?.levels?.childrenTreemas?[levelOriginal]
|
|
if key.ctrl or key.command
|
|
url = "/editor/level/#{levelTreema.data.slug}"
|
|
window.open url, '_blank'
|
|
levelTreema.select()
|
|
#levelTreema.open()
|
|
|
|
onCampaignLevelDoubleClicked: (levelOriginal) ->
|
|
@openCampaignLevelView @supermodel.getModelByOriginal Level, levelOriginal
|
|
|
|
openCampaignLevelView: (level) ->
|
|
@insertSubView campaignLevelView = new CampaignLevelView({}, level)
|
|
@listenToOnce campaignLevelView, 'hidden', => @$el.find('#campaign-view').show()
|
|
@$el.find('#campaign-view').hide()
|
|
|
|
updateRewardsForLevel: (level, rewards) ->
|
|
achievements = @supermodel.getModels(Achievement)
|
|
achievements = (a for a in achievements when a.get('related') is level.get('original'))
|
|
for achievement in achievements
|
|
rewardSubset = (r for r in rewards when r.achievement is achievement.id)
|
|
oldRewards = achievement.get 'rewards'
|
|
newRewards = {}
|
|
|
|
heroes = _.compact((r.hero for r in rewardSubset))
|
|
newRewards.heroes = heroes if heroes.length
|
|
|
|
items = _.compact((r.item for r in rewardSubset))
|
|
newRewards.items = items if items.length
|
|
|
|
levels = _.compact((r.level for r in rewardSubset))
|
|
newRewards.levels = levels if levels.length
|
|
|
|
newRewards.gems = oldRewards.gems if oldRewards.gems
|
|
achievement.set 'rewards', newRewards
|
|
if achievement.hasLocalChanges()
|
|
@toSave.add achievement
|
|
|
|
getCampaignAnalytics: =>
|
|
# Fetch last 14 days of campaign analytics
|
|
|
|
startDay = utils.getUTCDay -14
|
|
endDay = utils.getUTCDay -1
|
|
|
|
success = (data) =>
|
|
return if @destroyed
|
|
mapFn = (item) ->
|
|
item.completionRate = (item.finished / item.started * 100).toFixed(2)
|
|
item
|
|
@campaignCompletions = levels: _.map data, mapFn, @
|
|
sortedLevels = _.cloneDeep @campaignCompletions.levels
|
|
sortedLevels = _.filter sortedLevels, ((a) -> a.completionRate > 1.0), @
|
|
sortedLevels.sort (a, b) -> b.completionRate - a.completionRate
|
|
@campaignCompletions.top3 = _.pluck sortedLevels[0..2], 'level'
|
|
sortedLevels.sort (a, b) -> a.completionRate - b.completionRate
|
|
@campaignCompletions.bottom3 = _.pluck sortedLevels[0..2], 'level'
|
|
@campaignCompletions.startDay = "#{startDay[0..3]}-#{startDay[4..5]}-#{startDay[6..7]}"
|
|
@campaignCompletions.endDay = "#{endDay[0..3]}-#{endDay[4..5]}-#{endDay[6..7]}"
|
|
@getCampaignAveragePlaytimes()
|
|
|
|
# TODO: Why do we need this url dash?
|
|
request = @supermodel.addRequestResource 'campaign_completions', {
|
|
url: '/db/analytics_perday/-/campaign_completions'
|
|
data: {startDay: startDay, slug: @campaignHandle}
|
|
method: 'POST'
|
|
success: success
|
|
}, 0
|
|
request.load()
|
|
|
|
getCampaignAveragePlaytimes: =>
|
|
# Fetch last 14 days of level average playtimes
|
|
success = (data) =>
|
|
return if @destroyed
|
|
levelAverages = {}
|
|
for item in data
|
|
levelAverages[item.level] ?= []
|
|
levelAverages[item.level].push item.average
|
|
for level in @campaignCompletions.levels
|
|
if levelAverages[level.level]
|
|
if levelAverages[level.level].length > 0
|
|
total = _.reduce levelAverages[level.level], ((sum, num) -> sum + num)
|
|
level.averagePlaytime = (total / levelAverages[level.level].length).toFixed(2)
|
|
else
|
|
level.averagePlaytime = 0.0
|
|
@render()
|
|
|
|
levelSlugs = _.pluck @campaignCompletions.levels, 'level'
|
|
startDay = utils.getUTCDay -14
|
|
startDay = "#{startDay[0..3]}-#{startDay[4..5]}-#{startDay[6..7]}"
|
|
request = @supermodel.addRequestResource 'playtime_averages', {
|
|
url: '/db/level/-/playtime_averages'
|
|
data: {startDay: startDay, slugs: levelSlugs}
|
|
method: 'POST'
|
|
success: success
|
|
}, 0
|
|
request.load()
|
|
|
|
|
|
class LevelsNode extends TreemaObjectNode
|
|
valueClass: 'treema-levels'
|
|
@levels: {}
|
|
|
|
buildValueForDisplay: (valEl, data) ->
|
|
@buildValueForDisplaySimply valEl, ''+_.size(data)
|
|
|
|
childPropertiesAvailable: -> @childSource
|
|
|
|
childSource: (req, res) =>
|
|
s = new Backbone.Collection([], {model:Level})
|
|
s.url = '/db/level'
|
|
s.fetch({data: {term:req.term, project: Campaign.denormalizedLevelProperties.join(',')}})
|
|
s.once 'sync', (collection) =>
|
|
for level in collection.models
|
|
LevelsNode.levels[level.get('original')] = level
|
|
@settings.supermodel.registerModel level
|
|
mapped = ({label: r.get('name'), value: r.get('original')} for r in collection.models)
|
|
res(mapped)
|
|
|
|
|
|
class LevelNode extends TreemaObjectNode
|
|
valueClass: 'treema-level'
|
|
buildValueForDisplay: (valEl, data) ->
|
|
name = data.name
|
|
if data.requiresSubscription
|
|
name = "[P] " + name
|
|
|
|
status = ''
|
|
el = 'strong'
|
|
if data.adminOnly
|
|
status += " (disabled)"
|
|
el = 'span'
|
|
else if data.adventurer
|
|
status += " (adventurer)"
|
|
|
|
completion = ''
|
|
if data.tasks
|
|
completion = "#{(t for t in data.tasks when t.complete).length} / #{data.tasks.length}"
|
|
|
|
valEl.append $("<a href='/editor/level/#{_.string.slugify(data.name)}' class='spr'>(e)</a>")
|
|
valEl.append $("<#{el}></#{el}>").addClass('treema-shortened').text name
|
|
if status
|
|
valEl.append $('<em class="spl"></em>').text status
|
|
if completion
|
|
valEl.append $('<span class="completion"></span>').text completion
|
|
|
|
populateData: ->
|
|
return if @data.name?
|
|
data = _.pick LevelsNode.levels[@keyForParent].attributes, Campaign.denormalizedLevelProperties
|
|
_.extend @data, data
|
|
|
|
class CampaignsNode extends TreemaObjectNode
|
|
valueClass: 'treema-campaigns'
|
|
@campaigns: {}
|
|
|
|
buildValueForDisplay: (valEl, data) ->
|
|
@buildValueForDisplaySimply valEl, ''+_.size(data)
|
|
|
|
childPropertiesAvailable: -> @childSource
|
|
|
|
childSource: (req, res) =>
|
|
s = new Backbone.Collection([], {model:Campaign})
|
|
s.url = '/db/campaign'
|
|
s.fetch({data: {term:req.term, project: Campaign.denormalizedCampaignProperties}})
|
|
s.once 'sync', (collection) ->
|
|
CampaignsNode.campaigns[campaign.id] = campaign for campaign in collection.models
|
|
mapped = ({label: r.get('name'), value: r.id} for r in collection.models)
|
|
res(mapped)
|
|
|
|
|
|
class CampaignNode extends TreemaObjectNode
|
|
valueClass: 'treema-campaign'
|
|
buildValueForDisplay: (valEl, data) ->
|
|
@buildValueForDisplaySimply valEl, data.name
|
|
|
|
populateData: ->
|
|
return if @data.name?
|
|
data = _.pick CampaignsNode.campaigns[@keyForParent].attributes, Campaign.denormalizedCampaignProperties
|
|
_.extend @data, data
|
|
|
|
class AchievementNode extends treemaExt.IDReferenceNode
|
|
buildSearchURL: (term) -> "#{@url}?term=#{term}&project=#{achievementProject.join(',')}"
|