codecombat/app/views/clans/ClanDetailsView.coffee
Matt Lott a995fb938b Update clans UI
Update private clans dashboard to show level progression in order, and
visually indicate which levels are not completed/started/completed.
Update private clans blurb and screenshot.
Subscribe prompt on make private clan checkbox.
2015-04-28 09:22:58 -07:00

285 lines
11 KiB
CoffeeScript

RootView = require 'views/core/RootView'
template = require 'templates/clans/clan-details'
app = require 'core/application'
AuthModal = require 'views/core/AuthModal'
CocoCollection = require 'collections/CocoCollection'
Campaign = require 'models/Campaign'
Clan = require 'models/Clan'
EarnedAchievement = require 'models/EarnedAchievement'
LevelSession = require 'models/LevelSession'
SubscribeModal = require 'views/core/SubscribeModal'
ThangType = require 'models/ThangType'
User = require 'models/User'
# TODO: Add message for clan not found
# TODO: Progress visual for premium levels?
# TODO: Add expanded level names toggle
# TODO: Only need campaign data if clan is private
module.exports = class ClanDetailsView extends RootView
id: 'clan-details-view'
template: template
events:
'click .delete-clan-btn': 'onDeleteClan'
'click .edit-description-save-btn': 'onEditDescriptionSave'
'click .edit-name-save-btn': 'onEditNameSave'
'click .join-clan-btn': 'onJoinClan'
'click .leave-clan-btn': 'onLeaveClan'
'click .progress-level-cell': 'onClickLevel'
'click .remove-member-btn': 'onRemoveMember'
'mouseenter .progress-level-cell': 'onMouseEnterPoint'
'mouseleave .progress-level-cell': 'onMouseLeavePoint'
constructor: (options, @clanID) ->
super options
@initData()
destroy: ->
@stopListening?()
initData: ->
@stats = {}
@campaigns = new CocoCollection([], { url: "/db/campaign", model: Campaign, comparator:'_id' })
@clan = new Clan _id: @clanID
@members = new CocoCollection([], { url: "/db/clan/#{@clanID}/members", model: User, comparator: 'nameLower' })
@memberAchievements = new CocoCollection([], { url: "/db/clan/#{@clanID}/member_achievements", model: EarnedAchievement, comparator:'_id' })
# MemberSessions: only loads creatorName, levelName, codeLanguage, submittedCodeLanguage for each session
@memberSessions = new CocoCollection([], { url: "/db/clan/#{@clanID}/member_sessions", model: LevelSession, comparator:'_id' })
@listenTo me, 'sync', => @render?()
@listenTo @campaigns, 'sync', @onCampaignSync
@listenTo @clan, 'sync', @onClanSync
@listenTo @members, 'sync', @onMembersSync
@listenTo @memberAchievements, 'sync', @onMemberAchievementsSync
@listenTo @memberSessions, 'sync', @onMemberSessionsSync
@supermodel.loadModel @campaigns, 'clan', cache: false
@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})
getRenderData: ->
context = super()
context.campaignLevelProgressions = @campaignLevelProgressions ? []
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
context.memberLevelStateMap = @memberLevelMap ? {}
context.memberMaxLevelCount = @memberMaxLevelCount
context.members = @members?.models
context.isOwner = @clan.get('ownerID') is me.id
context.isMember = @clanID in (me.get('clans') ? [])
context.stats = @stats
# 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
context
afterRender: ->
super()
@updateHeroIcons()
refreshData: ->
me.fetch cache: false
@members.fetch cache: false
@memberAchievements.fetch cache: false
@memberSessions.fetch cache: false
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)
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?()
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
for user of @memberAchievementsMap
@memberAchievementsMap[user].sort (a, b) -> b.id.localeCompare(a.id)
@stats.averageAchievements = Math.round(@memberAchievements.models.length / Object.keys(@memberAchievementsMap).length)
@render?()
onMemberSessionsSync: ->
@memberLevelMap = {}
memberSessions = {}
for levelSession in @memberSessions.models
continue if levelSession.isMultiplayer()
user = levelSession.get('creator')
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'
memberSessions[user] ?= []
memberSessions[user].push levelSession
else
@memberLevelMap[user][levelSlug].state = 'started'
@memberMaxLevelCount = 0
@memberLanguageMap = {}
for user of memberSessions
languageCounts = {}
for levelSession in memberSessions[user]
language = levelSession.get('codeLanguage') or levelSession.get('submittedCodeLanguage')
languageCounts[language] = (languageCounts[language] or 0) + 1 if language
@memberMaxLevelCount = memberSessions[user].length if @memberMaxLevelCount < memberSessions[user].length
mostUsedCount = 0
for language, count of languageCounts
if count > mostUsedCount
mostUsedCount = count
@memberLanguageMap[user] = language
@render?()
onMouseEnterPoint: (e) ->
$('.level-popup-container').hide()
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()
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'
onDeleteClan: (e) ->
return @openModalView(new AuthModal()) if me.isAnonymous()
return unless window.confirm("Delete Clan?")
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()
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')
onJoinClan: (e) ->
return @openModalView(new AuthModal()) if me.isAnonymous()
return unless @clan.loaded
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
options =
url: "/db/clan/#{@clanID}/join"
method: 'PUT'
error: (model, response, options) =>
console.error 'Error joining clan', response
success: (model, response, options) => @refreshData()
@supermodel.addRequestResource( 'join_clan', options).load()
onLeaveClan: (e) ->
options =
url: "/db/clan/#{@clanID}/leave"
method: 'PUT'
error: (model, response, options) =>
console.error 'Error leaving clan', response
success: (model, response, options) => @refreshData()
@supermodel.addRequestResource( 'leave_clan', options).load()
onRemoveMember: (e) ->
return unless window.confirm("Remove Hero?")
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
success: (model, response, options) => @refreshData()
@supermodel.addRequestResource( 'remove_member', options).load()
else
console.error "No member ID attached to remove button."