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: 'change .expand-progress-checkbox': 'onExpandedProgressCheckbox' '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: -> @showExpandedProgress = false @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 or @showExpandedProgress 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') onExpandedProgressCheckbox: (e) -> @showExpandedProgress = $('.expand-progress-checkbox').prop('checked') # TODO: why does render reset the checkbox to be unchecked? @render?() $('.expand-progress-checkbox').attr('checked', @showExpandedProgress) 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."