2015-03-31 16:28:57 -04:00
|
|
|
RootView = require 'views/core/RootView'
|
|
|
|
template = require 'templates/clans/clan-details'
|
2015-04-10 17:33:16 -04:00
|
|
|
app = require 'core/application'
|
|
|
|
AuthModal = require 'views/core/AuthModal'
|
2015-04-02 20:00:28 -04:00
|
|
|
CocoCollection = require 'collections/CocoCollection'
|
2015-04-28 12:20:37 -04:00
|
|
|
Campaign = require 'models/Campaign'
|
2015-04-01 14:56:48 -04:00
|
|
|
Clan = require 'models/Clan'
|
2015-04-03 14:05:37 -04:00
|
|
|
EarnedAchievement = require 'models/EarnedAchievement'
|
2015-04-03 18:52:16 -04:00
|
|
|
LevelSession = require 'models/LevelSession'
|
2015-04-10 17:33:16 -04:00
|
|
|
SubscribeModal = require 'views/core/SubscribeModal'
|
2015-04-03 19:44:35 -04:00
|
|
|
ThangType = require 'models/ThangType'
|
2015-04-02 20:00:28 -04:00
|
|
|
User = require 'models/User'
|
2015-03-31 16:28:57 -04:00
|
|
|
|
2015-04-10 17:33:16 -04:00
|
|
|
# TODO: Add message for clan not found
|
2015-04-28 12:20:37 -04:00
|
|
|
# TODO: Progress visual for premium levels?
|
|
|
|
# TODO: Add expanded level names toggle
|
|
|
|
# TODO: Only need campaign data if clan is private
|
2015-04-01 19:00:39 -04:00
|
|
|
|
2015-03-31 16:28:57 -04:00
|
|
|
module.exports = class ClanDetailsView extends RootView
|
|
|
|
id: 'clan-details-view'
|
|
|
|
template: template
|
|
|
|
|
2015-04-01 19:00:39 -04:00
|
|
|
events:
|
2015-04-02 14:44:18 -04:00
|
|
|
'click .delete-clan-btn': 'onDeleteClan'
|
2015-04-15 14:09:43 -04:00
|
|
|
'click .edit-description-save-btn': 'onEditDescriptionSave'
|
|
|
|
'click .edit-name-save-btn': 'onEditNameSave'
|
2015-04-01 19:00:39 -04:00
|
|
|
'click .join-clan-btn': 'onJoinClan'
|
|
|
|
'click .leave-clan-btn': 'onLeaveClan'
|
2015-04-28 12:20:37 -04:00
|
|
|
'click .progress-level-cell': 'onClickLevel'
|
2015-04-02 14:01:37 -04:00
|
|
|
'click .remove-member-btn': 'onRemoveMember'
|
2015-04-28 12:20:37 -04:00
|
|
|
'mouseenter .progress-level-cell': 'onMouseEnterPoint'
|
|
|
|
'mouseleave .progress-level-cell': 'onMouseLeavePoint'
|
2015-04-01 19:00:39 -04:00
|
|
|
|
2015-03-31 16:28:57 -04:00
|
|
|
constructor: (options, @clanID) ->
|
|
|
|
super options
|
2015-04-03 12:52:25 -04:00
|
|
|
@initData()
|
2015-03-31 16:28:57 -04:00
|
|
|
|
2015-04-02 20:00:28 -04:00
|
|
|
destroy: ->
|
|
|
|
@stopListening?()
|
|
|
|
|
2015-04-03 12:52:25 -04:00
|
|
|
initData: ->
|
2015-04-03 14:05:37 -04:00
|
|
|
@stats = {}
|
|
|
|
|
2015-04-28 12:20:37 -04:00
|
|
|
@campaigns = new CocoCollection([], { url: "/db/campaign", model: Campaign, comparator:'_id' })
|
2015-04-03 12:52:25 -04:00
|
|
|
@clan = new Clan _id: @clanID
|
2015-04-24 17:21:57 -04:00
|
|
|
@members = new CocoCollection([], { url: "/db/clan/#{@clanID}/members", model: User, comparator: 'nameLower' })
|
2015-04-03 14:05:37 -04:00
|
|
|
@memberAchievements = new CocoCollection([], { url: "/db/clan/#{@clanID}/member_achievements", model: EarnedAchievement, comparator:'_id' })
|
2015-04-16 11:03:02 -04:00
|
|
|
# MemberSessions: only loads creatorName, levelName, codeLanguage, submittedCodeLanguage for each session
|
2015-04-03 18:52:16 -04:00
|
|
|
@memberSessions = new CocoCollection([], { url: "/db/clan/#{@clanID}/member_sessions", model: LevelSession, comparator:'_id' })
|
2015-04-03 14:05:37 -04:00
|
|
|
|
2015-04-03 12:52:25 -04:00
|
|
|
@listenTo me, 'sync', => @render?()
|
2015-04-28 12:20:37 -04:00
|
|
|
@listenTo @campaigns, 'sync', @onCampaignSync
|
2015-04-03 18:52:16 -04:00
|
|
|
@listenTo @clan, 'sync', @onClanSync
|
|
|
|
@listenTo @members, 'sync', @onMembersSync
|
|
|
|
@listenTo @memberAchievements, 'sync', @onMemberAchievementsSync
|
|
|
|
@listenTo @memberSessions, 'sync', @onMemberSessionsSync
|
|
|
|
|
2015-04-28 12:20:37 -04:00
|
|
|
@supermodel.loadModel @campaigns, 'clan', cache: false
|
2015-04-03 18:52:16 -04:00
|
|
|
@supermodel.loadModel @clan, 'clan', cache: false
|
|
|
|
@supermodel.loadCollection(@members, 'members', {cache: false})
|
|
|
|
@supermodel.loadCollection(@memberAchievements, 'member_achievements', {cache: false})
|
|
|
|
@supermodel.loadCollection(@memberSessions, 'member_sessions', {cache: false})
|
2015-04-03 12:52:25 -04:00
|
|
|
|
2015-04-03 19:44:35 -04:00
|
|
|
getRenderData: ->
|
|
|
|
context = super()
|
2015-04-28 12:20:37 -04:00
|
|
|
context.campaignLevelProgressions = @campaignLevelProgressions ? []
|
2015-04-03 19:44:35 -04:00
|
|
|
context.clan = @clan
|
|
|
|
if application.isProduction()
|
|
|
|
context.joinClanLink = "https://codecombat.com/clans/#{@clanID}"
|
|
|
|
else
|
|
|
|
context.joinClanLink = "http://localhost:3000/clans/#{@clanID}"
|
|
|
|
context.owner = @owner
|
|
|
|
context.memberAchievementsMap = @memberAchievementsMap
|
|
|
|
context.memberLanguageMap = @memberLanguageMap
|
2015-04-28 12:20:37 -04:00
|
|
|
context.memberLevelStateMap = @memberLevelMap ? {}
|
2015-04-16 18:26:14 -04:00
|
|
|
context.memberMaxLevelCount = @memberMaxLevelCount
|
2015-04-03 19:44:35 -04:00
|
|
|
context.members = @members?.models
|
|
|
|
context.isOwner = @clan.get('ownerID') is me.id
|
|
|
|
context.isMember = @clanID in (me.get('clans') ? [])
|
|
|
|
context.stats = @stats
|
2015-04-28 12:20:37 -04:00
|
|
|
|
|
|
|
# Find last campaign level for each user
|
|
|
|
lastUserCampaignLevelMap = {}
|
|
|
|
maxLastUserCampaignLevel = 0
|
|
|
|
if @campaigns.loaded
|
|
|
|
for campaign in @campaigns.models
|
|
|
|
campaignID = campaign.id
|
|
|
|
lastLevelIndex = 0
|
|
|
|
for levelID, level of campaign.get('levels')
|
|
|
|
levelSlug = level.slug
|
|
|
|
for member in context.members
|
|
|
|
if context.memberLevelStateMap[member.id]?[levelSlug]
|
|
|
|
lastUserCampaignLevelMap[member.id] ?= {}
|
|
|
|
lastUserCampaignLevelMap[member.id][campaignID] ?= {}
|
|
|
|
lastUserCampaignLevelMap[member.id][campaignID] =
|
|
|
|
levelSlug: levelSlug
|
|
|
|
index: lastLevelIndex
|
|
|
|
maxLastUserCampaignLevel = lastLevelIndex if lastLevelIndex > maxLastUserCampaignLevel
|
|
|
|
lastLevelIndex++
|
|
|
|
|
|
|
|
context.lastUserCampaignLevelMap = lastUserCampaignLevelMap
|
|
|
|
context.showExpandedProgress = maxLastUserCampaignLevel <= 30
|
2015-04-03 19:44:35 -04:00
|
|
|
context
|
|
|
|
|
|
|
|
afterRender: ->
|
|
|
|
super()
|
|
|
|
@updateHeroIcons()
|
|
|
|
|
2015-04-03 14:05:37 -04:00
|
|
|
refreshData: ->
|
|
|
|
me.fetch cache: false
|
|
|
|
@members.fetch cache: false
|
|
|
|
@memberAchievements.fetch cache: false
|
2015-04-16 18:26:14 -04:00
|
|
|
@memberSessions.fetch cache: false
|
2015-04-03 14:05:37 -04:00
|
|
|
|
2015-04-03 19:44:35 -04:00
|
|
|
updateHeroIcons: ->
|
|
|
|
return unless @members?.models?
|
|
|
|
for member in @members.models
|
|
|
|
continue unless hero = member.get('heroConfig')?.thangType
|
|
|
|
for slug, original of ThangType.heroes when original is hero
|
|
|
|
@$el.find(".player-hero-icon[data-memberID=#{member.id}]").removeClass('.player-hero-icon').addClass('player-hero-icon ' + slug)
|
|
|
|
|
2015-04-28 12:20:37 -04:00
|
|
|
onCampaignSync: ->
|
|
|
|
return unless @campaigns.loaded
|
|
|
|
@campaignLevelProgressions = []
|
|
|
|
for campaign in @campaigns.models
|
|
|
|
continue if campaign.get('slug') is 'auditions'
|
|
|
|
campaignLevelProgression =
|
|
|
|
ID: campaign.id
|
|
|
|
slug: campaign.get('slug')
|
|
|
|
name: campaign.get('name')
|
|
|
|
levels: []
|
|
|
|
# TODO: Where do these proper names come from?
|
|
|
|
campaignLevelProgression.name = switch
|
|
|
|
when campaignLevelProgression.slug is 'dungeon' then 'Kithgard Dungeon'
|
|
|
|
when campaignLevelProgression.slug is 'forest' then 'Backwoods Forest'
|
|
|
|
when campaignLevelProgression.slug is 'desert' then 'Sarven Desert'
|
|
|
|
when campaignLevelProgression.slug is 'mountain' then 'Cloudrip Mountain'
|
|
|
|
else campaignLevelProgression.name
|
|
|
|
for levelID, level of campaign.get('levels')
|
|
|
|
campaignLevelProgression.levels.push
|
|
|
|
ID: levelID
|
|
|
|
slug: level.slug
|
|
|
|
name: level.name
|
|
|
|
@campaignLevelProgressions.push campaignLevelProgression
|
|
|
|
@render?()
|
|
|
|
|
2015-04-03 18:52:16 -04:00
|
|
|
onClanSync: ->
|
|
|
|
unless @owner?
|
|
|
|
@owner = new User _id: @clan.get('ownerID')
|
|
|
|
@listenTo @owner, 'sync', => @render?()
|
|
|
|
@supermodel.loadModel @owner, 'owner', cache: false
|
|
|
|
@render?()
|
|
|
|
|
|
|
|
onMembersSync: ->
|
|
|
|
@stats.averageLevel = Math.round(@members.reduce(((sum, member) -> sum + member.level()), 0) / @members.length)
|
|
|
|
@render?()
|
|
|
|
|
|
|
|
onMemberAchievementsSync: ->
|
|
|
|
@memberAchievementsMap = {}
|
|
|
|
for achievement in @memberAchievements.models
|
|
|
|
user = achievement.get('user')
|
|
|
|
@memberAchievementsMap[user] ?= []
|
|
|
|
@memberAchievementsMap[user].push achievement
|
2015-04-06 13:19:35 -04:00
|
|
|
for user of @memberAchievementsMap
|
|
|
|
@memberAchievementsMap[user].sort (a, b) -> b.id.localeCompare(a.id)
|
2015-04-20 19:20:49 -04:00
|
|
|
@stats.averageAchievements = Math.round(@memberAchievements.models.length / Object.keys(@memberAchievementsMap).length)
|
2015-04-03 18:52:16 -04:00
|
|
|
@render?()
|
|
|
|
|
|
|
|
onMemberSessionsSync: ->
|
2015-04-28 12:20:37 -04:00
|
|
|
@memberLevelMap = {}
|
2015-04-16 18:26:14 -04:00
|
|
|
memberSessions = {}
|
2015-04-03 18:52:16 -04:00
|
|
|
for levelSession in @memberSessions.models
|
2015-04-28 12:20:37 -04:00
|
|
|
continue if levelSession.isMultiplayer()
|
2015-04-03 18:52:16 -04:00
|
|
|
user = levelSession.get('creator')
|
2015-04-28 12:20:37 -04:00
|
|
|
levelSlug = levelSession.get('levelID')
|
|
|
|
@memberLevelMap[user] ?= {}
|
|
|
|
@memberLevelMap[user][levelSlug] ?= {}
|
|
|
|
levelInfo =
|
|
|
|
level: levelSession.get('levelName')
|
|
|
|
levelID: levelSession.get('levelID')
|
|
|
|
changed: new Date(levelSession.get('changed')).toLocaleString()
|
|
|
|
playtime: levelSession.get('playtime')
|
|
|
|
sessionID: levelSession.id
|
|
|
|
@memberLevelMap[user][levelSlug].levelInfo = levelInfo
|
|
|
|
if levelSession.get('state')?.complete is true
|
|
|
|
@memberLevelMap[user][levelSlug].state = 'complete'
|
2015-04-16 18:26:14 -04:00
|
|
|
memberSessions[user] ?= []
|
|
|
|
memberSessions[user].push levelSession
|
2015-04-28 12:20:37 -04:00
|
|
|
else
|
|
|
|
@memberLevelMap[user][levelSlug].state = 'started'
|
2015-04-16 18:26:14 -04:00
|
|
|
@memberMaxLevelCount = 0
|
2015-04-03 18:52:16 -04:00
|
|
|
@memberLanguageMap = {}
|
2015-04-16 18:26:14 -04:00
|
|
|
for user of memberSessions
|
2015-04-03 18:52:16 -04:00
|
|
|
languageCounts = {}
|
2015-04-16 18:26:14 -04:00
|
|
|
for levelSession in memberSessions[user]
|
2015-04-03 18:52:16 -04:00
|
|
|
language = levelSession.get('codeLanguage') or levelSession.get('submittedCodeLanguage')
|
|
|
|
languageCounts[language] = (languageCounts[language] or 0) + 1 if language
|
2015-04-16 18:26:14 -04:00
|
|
|
@memberMaxLevelCount = memberSessions[user].length if @memberMaxLevelCount < memberSessions[user].length
|
2015-04-03 18:52:16 -04:00
|
|
|
mostUsedCount = 0
|
|
|
|
for language, count of languageCounts
|
|
|
|
if count > mostUsedCount
|
|
|
|
mostUsedCount = count
|
|
|
|
@memberLanguageMap[user] = language
|
|
|
|
@render?()
|
|
|
|
|
2015-04-16 18:26:14 -04:00
|
|
|
onMouseEnterPoint: (e) ->
|
2015-04-28 12:20:37 -04:00
|
|
|
$('.level-popup-container').hide()
|
2015-04-16 18:26:14 -04:00
|
|
|
container = $(e.target).find('.level-popup-container').show()
|
|
|
|
margin = 20
|
|
|
|
offset = $(e.target).offset()
|
|
|
|
scrollTop = $(e.target).offsetParent().scrollTop()
|
|
|
|
height = container.outerHeight()
|
|
|
|
container.css('left', offset.left + e.offsetX)
|
|
|
|
container.css('top', offset.top + scrollTop - height - margin)
|
|
|
|
|
|
|
|
onMouseLeavePoint: (e) ->
|
|
|
|
$(e.target).find('.level-popup-container').hide()
|
|
|
|
|
2015-04-23 17:31:21 -04:00
|
|
|
onClickLevel: (e) ->
|
|
|
|
levelInfo = $(e.target).data 'level-info'
|
|
|
|
return unless levelInfo?.levelID? and levelInfo?.sessionID?
|
|
|
|
url = "/play/level/#{levelInfo.levelID}?session=#{levelInfo.sessionID}&observing=true"
|
|
|
|
window.open url, '_blank'
|
|
|
|
|
2015-04-02 14:44:18 -04:00
|
|
|
onDeleteClan: (e) ->
|
|
|
|
return @openModalView(new AuthModal()) if me.isAnonymous()
|
2015-04-21 16:41:31 -04:00
|
|
|
return unless window.confirm("Delete Clan?")
|
2015-04-02 14:44:18 -04:00
|
|
|
options =
|
|
|
|
url: "/db/clan/#{@clanID}"
|
|
|
|
method: 'DELETE'
|
|
|
|
error: (model, response, options) =>
|
|
|
|
console.error 'Error joining clan', response
|
|
|
|
success: (model, response, options) =>
|
|
|
|
app.router.navigate "/clans"
|
|
|
|
window.location.reload()
|
|
|
|
@supermodel.addRequestResource( 'delete_clan', options).load()
|
|
|
|
|
2015-04-15 14:09:43 -04:00
|
|
|
onEditDescriptionSave: (e) ->
|
|
|
|
description = $('.edit-description-input').val()
|
|
|
|
@clan.set 'description', description
|
|
|
|
@clan.patch()
|
|
|
|
$('#editDescriptionModal').modal('hide')
|
|
|
|
|
|
|
|
onEditNameSave: (e) ->
|
|
|
|
if name = $('.edit-name-input').val()
|
|
|
|
@clan.set 'name', name
|
|
|
|
@clan.patch()
|
|
|
|
$('#editNameModal').modal('hide')
|
|
|
|
|
2015-04-01 19:00:39 -04:00
|
|
|
onJoinClan: (e) ->
|
|
|
|
return @openModalView(new AuthModal()) if me.isAnonymous()
|
2015-04-10 17:33:16 -04:00
|
|
|
return unless @clan.loaded
|
2015-04-20 17:16:44 -04:00
|
|
|
if @clan.get('type') is 'private' and not me.isPremium()
|
|
|
|
@openModalView new SubscribeModal()
|
|
|
|
window.tracker?.trackEvent 'Show subscription modal', category: 'Subscription', label: 'join clan'
|
|
|
|
return
|
2015-04-02 14:44:18 -04:00
|
|
|
options =
|
|
|
|
url: "/db/clan/#{@clanID}/join"
|
|
|
|
method: 'PUT'
|
|
|
|
error: (model, response, options) =>
|
|
|
|
console.error 'Error joining clan', response
|
2015-04-03 14:05:37 -04:00
|
|
|
success: (model, response, options) => @refreshData()
|
2015-04-02 14:44:18 -04:00
|
|
|
@supermodel.addRequestResource( 'join_clan', options).load()
|
2015-04-01 19:00:39 -04:00
|
|
|
|
|
|
|
onLeaveClan: (e) ->
|
2015-04-02 14:44:18 -04:00
|
|
|
options =
|
|
|
|
url: "/db/clan/#{@clanID}/leave"
|
|
|
|
method: 'PUT'
|
|
|
|
error: (model, response, options) =>
|
|
|
|
console.error 'Error leaving clan', response
|
2015-04-03 14:05:37 -04:00
|
|
|
success: (model, response, options) => @refreshData()
|
2015-04-02 14:44:18 -04:00
|
|
|
@supermodel.addRequestResource( 'leave_clan', options).load()
|
2015-04-02 14:01:37 -04:00
|
|
|
|
|
|
|
onRemoveMember: (e) ->
|
2015-04-23 17:33:13 -04:00
|
|
|
return unless window.confirm("Remove Hero?")
|
2015-04-02 14:01:37 -04:00
|
|
|
if memberID = $(e.target).data('id')
|
|
|
|
options =
|
|
|
|
url: "/db/clan/#{@clanID}/remove/#{memberID}"
|
|
|
|
method: 'PUT'
|
|
|
|
error: (model, response, options) =>
|
|
|
|
console.error 'Error removing clan member', response
|
2015-04-03 14:05:37 -04:00
|
|
|
success: (model, response, options) => @refreshData()
|
2015-04-02 14:01:37 -04:00
|
|
|
@supermodel.addRequestResource( 'remove_member', options).load()
|
|
|
|
else
|
|
|
|
console.error "No member ID attached to remove button."
|