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 @getCampaignDropOffs, 1000 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.campaignDropOffs = @campaignDropOffs 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 getCampaignDropOffs: => # Fetch last 7 days of campaign drop-off rates startDay = new Date() startDay.setDate(startDay.getUTCDate() - 6) startDay = startDay.getUTCFullYear() + '-' + (startDay.getUTCMonth() + 1) + '-' + startDay.getUTCDate() success = (data) => return if @destroyed # API returns all the campaign data currently @campaignDropOffs = data[@campaignHandle] mapFn = (item) -> item.startDropRate = (item.startDropped / item.started * 100).toFixed(2) item.finishDropRate = (item.finishDropped / item.finished * 100).toFixed(2) item @campaignDropOffs.levels = _.map @campaignDropOffs.levels, mapFn, @ @campaignDropOffs.startDay = startDay @render() # TODO: Why do we need this url dash? request = @supermodel.addRequestResource 'campaign_drop_offs', { url: '/db/analytics_log_event/-/campaign_drop_offs' data: {startDay: startDay, slugs: [@campaignHandle]} 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 $("(e)") valEl.append $("<#{el}>").addClass('treema-shortened').text name if status valEl.append $('').text status if completion valEl.append $('').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(',')}"