mirror of
https://gitlab.com/Deukhoofd/BattleSim.git
synced 2025-10-28 18:20:04 +00:00
Lots of stuff
This commit is contained in:
14
client/app/js/views/battle_list_view.coffee
Normal file
14
client/app/js/views/battle_list_view.coffee
Normal file
@@ -0,0 +1,14 @@
|
||||
class @BattleListView extends Backbone.View
|
||||
template: JST['battle_list']
|
||||
|
||||
initialize: (attributes) =>
|
||||
@battles = []
|
||||
@render()
|
||||
|
||||
refreshList: =>
|
||||
PokeBattle.primus.send "getBattleList", (battles) =>
|
||||
@battles = _(battles).sortBy((battle) => battle[3])
|
||||
@render()
|
||||
|
||||
render: =>
|
||||
@$el.html @template(battles: @battles)
|
||||
1390
client/app/js/views/battles/battle_view.coffee
Normal file
1390
client/app/js/views/battles/battle_view.coffee
Normal file
File diff suppressed because it is too large
Load Diff
244
client/app/js/views/battles/chat_view.coffee
Normal file
244
client/app/js/views/battles/chat_view.coffee
Normal file
@@ -0,0 +1,244 @@
|
||||
class @ChatView extends Backbone.View
|
||||
template: JST['chat']
|
||||
userListTemplate: JST['user_list']
|
||||
|
||||
events:
|
||||
'click': 'focusChat'
|
||||
'keydown .chat_input': 'handleKeys'
|
||||
'click .chat_input_send': 'sendChat'
|
||||
'scroll_to_bottom': 'scrollToBottom'
|
||||
|
||||
MAX_USERNAME_HISTORY = 10
|
||||
MAX_MESSAGES_LENGTH = 500
|
||||
|
||||
# Takes a `model`, which is a Room instance.
|
||||
initialize: (options) =>
|
||||
{@noisy} = options
|
||||
if @model
|
||||
@listenTo(@model.get('users'), 'add remove reset', @renderUserList)
|
||||
if @noisy
|
||||
@listenTo(@model.get('users'), 'add', @userJoin)
|
||||
@listenTo(@model.get('users'), 'remove', @userLeave)
|
||||
for eventName in Room::EVENTS
|
||||
callback = this[eventName] || throw new Error("ChatView must implement #{eventName}.")
|
||||
@listenTo(@model, eventName, callback)
|
||||
@chatHistory = []
|
||||
@mostRecentNames = []
|
||||
@tabCompleteIndex = -1
|
||||
@tabCompleteNames = []
|
||||
|
||||
# Sets the channel topic
|
||||
setTopic: (topic) =>
|
||||
topic = @sanitize(topic)
|
||||
@rawMessage("<div class='alert alert-info'><b>Topic:</b> #{topic}</div>")
|
||||
|
||||
render: =>
|
||||
@$el.html @template()
|
||||
if @model
|
||||
@$el.removeClass('without_spectators')
|
||||
@$el.removeClass('without_chat_input')
|
||||
@renderUserList()
|
||||
this
|
||||
|
||||
renderUserList: =>
|
||||
@$('.user_count').text "Users (#{@model.get('users').length})"
|
||||
@$('.users').html @userListTemplate(userList: @model.get('users').models)
|
||||
this
|
||||
|
||||
getSelectedText: =>
|
||||
text = ""
|
||||
if window.getSelection
|
||||
text = window.getSelection().toString()
|
||||
else if document.selection && document.selection.type != "Control"
|
||||
text = document.selection.createRange().text
|
||||
return text
|
||||
|
||||
focusChat: =>
|
||||
selectedText = @getSelectedText()
|
||||
@$('.chat_input').focus() if selectedText.length == 0
|
||||
|
||||
sendChat: =>
|
||||
$this = @$('.chat_input')
|
||||
message = $this.val()
|
||||
if @model.sendChat(message)
|
||||
@chatHistory.push(message)
|
||||
delete @chatHistoryIndex
|
||||
$this.val('')
|
||||
|
||||
tabComplete: ($input, options = {}) =>
|
||||
cursorIndex = $input.prop('selectionStart')
|
||||
text = $input.val()
|
||||
if @tabCompleteNames.length > 0 && @tabCompleteCursorIndex == cursorIndex
|
||||
if options.reverse
|
||||
@tabCompleteIndex -= 1
|
||||
if @tabCompleteIndex < 0
|
||||
@tabCompleteIndex = @tabCompleteNames.length - 1
|
||||
else
|
||||
@tabCompleteIndex = (@tabCompleteIndex + 1) % @tabCompleteNames.length
|
||||
else
|
||||
delete @tabCompleteCursorIndex
|
||||
pieces = text[0...cursorIndex].split(' ')
|
||||
possibleName = pieces.pop()
|
||||
rest = pieces.join(' ')
|
||||
rest += ' ' if pieces.length > 0 # Append a space if a word exists
|
||||
length = possibleName.length
|
||||
return if length == 0
|
||||
candidates = _.union(@mostRecentNames, @model.get('users').pluck('id'))
|
||||
candidates = candidates.filter (name) ->
|
||||
name[...length].toLowerCase() == possibleName.toLowerCase()
|
||||
return if candidates.length == 0
|
||||
if options.reverse
|
||||
@tabCompleteIndex = candidates.length - 1
|
||||
else
|
||||
@tabCompleteIndex = 0
|
||||
@tabCompleteNames = candidates
|
||||
@tabCompletePrefix = rest
|
||||
@tabCompleteCursorIndex = cursorIndex
|
||||
tabbedName = @tabCompleteNames[@tabCompleteIndex]
|
||||
newPrefix = @tabCompletePrefix + tabbedName
|
||||
newPrefixLength = newPrefix.length
|
||||
$input.val(newPrefix + text[cursorIndex...])
|
||||
$input[0].setSelectionRange(newPrefixLength, newPrefixLength)
|
||||
@tabCompleteCursorIndex = newPrefixLength
|
||||
|
||||
handleKeys: (e) =>
|
||||
$input = $(e.currentTarget)
|
||||
switch e.which
|
||||
when 13 # [Enter]
|
||||
e.preventDefault()
|
||||
@sendChat()
|
||||
when 9 # [Tab]
|
||||
e.preventDefault()
|
||||
@tabComplete($input, reverse: e.shiftKey)
|
||||
when 38 # [Up arrow]
|
||||
e.preventDefault()
|
||||
return if @chatHistory.length == 0
|
||||
if !@chatHistoryIndex?
|
||||
@chatHistoryIndex = @chatHistory.length
|
||||
@chatHistoryText = $input.val()
|
||||
if @chatHistoryIndex > 0
|
||||
@chatHistoryIndex -= 1
|
||||
$input.val(@chatHistory[@chatHistoryIndex])
|
||||
when 40 # [Down arrow]
|
||||
e.preventDefault()
|
||||
return unless @chatHistoryIndex?
|
||||
@chatHistoryIndex += 1
|
||||
if @chatHistoryIndex == @chatHistory.length
|
||||
$input.val(@chatHistoryText)
|
||||
delete @chatHistoryIndex
|
||||
else
|
||||
$input.val(@chatHistory[@chatHistoryIndex])
|
||||
|
||||
userMessage: (username, message) =>
|
||||
user = @model.get('users').get(username)
|
||||
displayName = user?.getDisplayName() || username
|
||||
yourName = PokeBattle.username
|
||||
highlight = (new RegExp("\\b#{yourName}\\b", 'i').test(message))
|
||||
|
||||
# Render the chat message
|
||||
u = "<b class='open_pm fake_link' data-user-id='#{username}'
|
||||
style='color: #{@userColor(username)}'>#{displayName}:</b>"
|
||||
@rawMessage("#{@timestamp()} #{u} #{@sanitize(message)}", {highlight})
|
||||
|
||||
# We might want to run something based on the message, e.g. !pbv from a mod.
|
||||
@handleMessage(user, message)
|
||||
|
||||
# Record last few usernames who chatted
|
||||
index = @mostRecentNames.indexOf(username)
|
||||
@mostRecentNames.splice(index, 1) if index != -1
|
||||
@mostRecentNames.push(username)
|
||||
@mostRecentNames.shift() if @mostRecentNames.length > MAX_USERNAME_HISTORY
|
||||
|
||||
userColor: (username) =>
|
||||
# Same hashing algorithm as in Java
|
||||
hash = 0
|
||||
for c, i in username
|
||||
chr = username.charCodeAt(i)
|
||||
hash = ((hash << 5) - hash) + chr
|
||||
hash |= 0
|
||||
|
||||
h = hash % 360
|
||||
hash /= 360
|
||||
s = (hash % 25) + 75
|
||||
l = 50
|
||||
"hsl(#{h}, #{s}%, #{l}%)"
|
||||
|
||||
handleMessage: (user, message) =>
|
||||
authority = user?.get('authority')
|
||||
printableCommands = ['/pbv', '/data']
|
||||
# TODO: no magic constants. '1' is a regular user.
|
||||
if authority > 1 && message.split(/\s/, 1)[0] in printableCommands
|
||||
PokeBattle.commands.execute(@model, message)
|
||||
|
||||
userJoin: (user) =>
|
||||
@rawMessage("#{@timestamp()} #{user.id} joined!")
|
||||
|
||||
userLeave: (user) =>
|
||||
@rawMessage("#{@timestamp()} #{user.id} left!")
|
||||
|
||||
rawMessage: (message, options = {}) =>
|
||||
wasAtBottom = @isAtBottom()
|
||||
klass = []
|
||||
klass.push('bg-blue') if options.highlight
|
||||
klass.push(options.class) if options.class
|
||||
@print("<p class='chat_message #{klass.join(' ')}'>#{message}</p>")
|
||||
@cleanChat()
|
||||
if wasAtBottom then @scrollToBottom()
|
||||
|
||||
cleanChat: =>
|
||||
$messages = @$('.chat_message')
|
||||
numToRemove = ($messages.length - MAX_MESSAGES_LENGTH)
|
||||
if numToRemove > 0
|
||||
$messages.slice(0, numToRemove).remove()
|
||||
|
||||
announce: (klass, message) =>
|
||||
wasAtBottom = @isAtBottom()
|
||||
message = @linkify(message)
|
||||
@print("<div class='alert alert-#{klass} clearfix'>#{message}</div>")
|
||||
if wasAtBottom then @scrollToBottom()
|
||||
|
||||
print: (message) =>
|
||||
@$('.messages').append(message)
|
||||
|
||||
clear: =>
|
||||
@$('.messages').empty()
|
||||
|
||||
timestamp: =>
|
||||
date = new Date()
|
||||
hours = date.getHours()
|
||||
minutes = date.getMinutes()
|
||||
seconds = date.getSeconds()
|
||||
|
||||
minutes = "00#{minutes}".substr(-2)
|
||||
seconds = "00#{seconds}".substr(-2)
|
||||
"<span class='monospace'>[#{hours}:#{minutes}:#{seconds}]</span>"
|
||||
|
||||
# Escapes all HTML, but also converts links to clickable links.
|
||||
sanitize: (message) =>
|
||||
sanitizedMessage = $('<div/>').text(message).html()
|
||||
@linkify(sanitizedMessage)
|
||||
|
||||
linkify: (message) =>
|
||||
message = URI.withinString message, (url) ->
|
||||
uri = URI(url)
|
||||
[host, path] = [uri.host(), uri.path()]
|
||||
battleRegex = /^\/battles\/([a-fA-F0-9]+)$/i
|
||||
$a = $("<a/>").prop('href', url).prop('target', '_blank').text(url)
|
||||
|
||||
if host == URI(window.location.href).host() && battleRegex.test(path)
|
||||
battleId = path.match(battleRegex)[1]
|
||||
$a.addClass('spectate').attr('data-battle-id', battleId)
|
||||
|
||||
return $a.wrap("<div/>").parent().html()
|
||||
message
|
||||
|
||||
# Returns true if the chat is scrolled to the bottom of the screen.
|
||||
# This also returns true if the messages are hidden.
|
||||
isAtBottom: =>
|
||||
$el = @$('.messages')
|
||||
($el[0].scrollHeight - $el.scrollTop() <= $el.outerHeight())
|
||||
|
||||
scrollToBottom: =>
|
||||
messages = @$('.messages')[0]
|
||||
messages.scrollTop = messages.scrollHeight
|
||||
false
|
||||
259
client/app/js/views/private_messages_view.coffee
Normal file
259
client/app/js/views/private_messages_view.coffee
Normal file
@@ -0,0 +1,259 @@
|
||||
class @PrivateMessagesView extends Backbone.View
|
||||
messageTemplate: JST['private_message']
|
||||
|
||||
events:
|
||||
"keypress .chat_input" : "keyPressEvent"
|
||||
"keyup .chat_input" : "keyUpEvent"
|
||||
"click .challenge_button, .cancel_challenge" : "toggleChallengeEvent"
|
||||
"click .popup_messages" : "focusChatEvent"
|
||||
"click .title_minimize" : "minimizePopupEvent"
|
||||
"click .title_close" : "closePopupEvent"
|
||||
"challenge .popup" : "sendChallengeEvent"
|
||||
"cancelChallenge .popup" : "challengeCanceledEvent"
|
||||
"focus .popup" : "focusPopupEvent"
|
||||
|
||||
initialize: =>
|
||||
@listenTo(@collection, 'open', @createPopup)
|
||||
@listenTo(@collection, 'focus', @focusPopup)
|
||||
@listenTo(@collection, 'receive', @receiveMessage)
|
||||
@listenTo(@collection, 'close', @closePopup)
|
||||
@listenTo(@collection, 'minimize', @minimizePopup)
|
||||
@listenTo(@collection, 'show', @showPopup)
|
||||
@listenTo(@collection, 'openChallenge', @openChallenge)
|
||||
@listenTo(@collection, 'cancelChallenge', @cancelChallenge)
|
||||
@listenTo(@collection, 'closeChallenge', @closeChallenge)
|
||||
@listenTo(@collection, 'focus show', @resetNotifications)
|
||||
|
||||
# @listenTo(PokeBattle.userList, 'add', @notifyJoin)
|
||||
# @listenTo(PokeBattle.userList, 'remove', @notifyLeave)
|
||||
|
||||
createPopup: (message) =>
|
||||
title = id = message.id
|
||||
$html = @$findPopup(id)
|
||||
if @$findPopup(id).length == 0
|
||||
$html = $(@messageTemplate({window, id, title}))
|
||||
|
||||
@$el.append($html)
|
||||
@positionPopup($html, @$(".popup:visible").length - 1)
|
||||
@addLogMessages($html, message.getLog())
|
||||
$html
|
||||
|
||||
focusPopup: (message) =>
|
||||
id = message.id
|
||||
$popup = @$findPopup(id)
|
||||
$popup.find('.chat_input').focus()
|
||||
|
||||
closePopup: (message) =>
|
||||
username = message.id
|
||||
@$findPopup(username).remove()
|
||||
@repositionPopups()
|
||||
|
||||
minimizePopup: (message) =>
|
||||
username = message.id
|
||||
$popup = @$findPopup(username)
|
||||
$popup.addClass('hidden')
|
||||
@repositionPopups()
|
||||
|
||||
showPopup: (message) =>
|
||||
username = message.id
|
||||
$popup = @$findPopup(username)
|
||||
@$el.append($popup)
|
||||
$popup.removeClass('hidden')
|
||||
@scrollToBottom($popup)
|
||||
@repositionPopups()
|
||||
|
||||
addMessage: ($popup, message) =>
|
||||
$messages = $popup.find('.popup_messages')
|
||||
$messages.append(message)
|
||||
|
||||
# todo: make this and receiveMessage construct messages from a common source
|
||||
addLogMessages: ($popup, log) =>
|
||||
messageHtml = ""
|
||||
for {username, message, opts} in log
|
||||
message = _.escape(message)
|
||||
username = "Me" if username == PokeBattle.username
|
||||
if opts.type in [ 'error', 'alert' ]
|
||||
messageHtml += "<p class='grey'>#{message}</p>"
|
||||
else
|
||||
messageHtml += "<p class='grey'><strong>#{username}:</strong> #{message}</p>"
|
||||
|
||||
@addMessage($popup, messageHtml)
|
||||
@scrollToBottom($popup)
|
||||
|
||||
# todo: make this and addLogMessages construct messages from a common source
|
||||
receiveMessage: (messageModel, messageId, username, message, options) =>
|
||||
message = _.escape(message)
|
||||
$popup = @$findOrCreatePopup(messageId)
|
||||
wasAtBottom = @isAtBottom($popup)
|
||||
username = "Me" if username == PokeBattle.username
|
||||
if options.type == 'error'
|
||||
@addMessage($popup, "<p class='red italic'>#{message}</p>")
|
||||
else if options.type == 'alert'
|
||||
@addMessage($popup, "<p class='yellow italic'>#{message}</p>")
|
||||
else
|
||||
if username != "Me" && !$popup.find('.chat_input').is(":focus")
|
||||
$popup.addClass('new_message')
|
||||
PokeBattle.notifyUser(PokeBattle.NotificationTypes.PRIVATE_MESSAGE, username)
|
||||
else
|
||||
@resetNotifications(messageModel)
|
||||
@addMessage($popup, "<p><strong>#{username}:</strong> #{message}</p>")
|
||||
if wasAtBottom then @scrollToBottom($popup)
|
||||
|
||||
openChallenge: (messageId, generation, conditions) =>
|
||||
$popup = @$findOrCreatePopup(messageId)
|
||||
$challenge = @createChallenge($popup, generation, conditions)
|
||||
if generation
|
||||
$challenge.find('.is_not_challenger').addClass('hidden')
|
||||
$challenge.find('.is_challenger').removeClass('hidden')
|
||||
|
||||
cancelChallenge: (messageId) =>
|
||||
$popup = @$findOrCreatePopup(messageId)
|
||||
$challenge = $popup.find('.challenge')
|
||||
$challenge.find('.icon-spinner').addClass('hidden')
|
||||
$challenge.find('.send_challenge, .select').removeClass('disabled')
|
||||
$challenge.find('.challenge_text').text("Challenge")
|
||||
$challenge.find(".cancel_challenge").text('Close')
|
||||
|
||||
closeChallenge: (messageId) =>
|
||||
$popup = @$findOrCreatePopup(messageId)
|
||||
$challenge = $popup.find('.challenge')
|
||||
$challenge.addClass('hidden')
|
||||
$popup.find('.popup_messages').removeClass('small')
|
||||
|
||||
resetNotifications: (message) =>
|
||||
message.set('notifications', 0)
|
||||
|
||||
notifyJoin: (user) =>
|
||||
message = @collection.get(user.id)
|
||||
return unless @isOpen(message)
|
||||
message?.add(user.id, "#{user.id} is now online!", type: "alert")
|
||||
|
||||
notifyLeave: (user) =>
|
||||
message = @collection.get(user.id)
|
||||
return unless @isOpen(message)
|
||||
message?.add(user.id, "#{user.id} is now offline.", type: "alert")
|
||||
|
||||
isOpen: (message) =>
|
||||
message && @$findPopup(message.id).length > 0
|
||||
|
||||
# Returns true if the chat is scrolled to the bottom of the screen.
|
||||
# This also returns true if the messages are hidden.
|
||||
isAtBottom: ($popup) =>
|
||||
$el = $popup.find('.popup_messages')
|
||||
($el[0].scrollHeight - $el.scrollTop() <= $el.outerHeight())
|
||||
|
||||
scrollToBottom: ($popup) =>
|
||||
messages = $popup.find('.popup_messages')[0]
|
||||
return unless messages
|
||||
messages.scrollTop = messages.scrollHeight
|
||||
false
|
||||
|
||||
positionPopup: ($popup, index) =>
|
||||
leftOffset = $('#content').position().left
|
||||
$popup.css(left: leftOffset + index * $popup.outerWidth(true))
|
||||
|
||||
repositionPopups: =>
|
||||
@$(".popup:visible").each (index, self) =>
|
||||
@positionPopup($(self), index)
|
||||
|
||||
$findPopup: (id) =>
|
||||
@$(".popup[data-user-id='#{id}']")
|
||||
|
||||
$findOrCreatePopup: (messageId) =>
|
||||
$popup = @$findPopup(messageId)
|
||||
$popup = @createPopup(@collection.get(messageId)) if $popup.length == 0
|
||||
$popup
|
||||
|
||||
$closestPopup: (target) =>
|
||||
$target = $(target)
|
||||
return $target if $target.hasClass("popup")
|
||||
return $target.closest(".popup")
|
||||
|
||||
messageFromPopup: (target) =>
|
||||
$popup = @$closestPopup(target)
|
||||
message = @collection.get($popup.data('user-id'))
|
||||
return message
|
||||
|
||||
createChallenge: ($popup, generation, conditions) =>
|
||||
$challenge = $popup.find('.challenge')
|
||||
$challenge.html(JST['challenge']())
|
||||
createChallengePane
|
||||
eventName: "challenge"
|
||||
button: $popup.find('.send_challenge')
|
||||
acceptButton: $popup.find('.accept_challenge')
|
||||
rejectButton: $popup.find('.reject_challenge')
|
||||
populate: $popup.find(".challenge_data")
|
||||
generation: generation
|
||||
personId: $popup.data('user-id')
|
||||
defaultClauses: conditions || [
|
||||
Conditions.TEAM_PREVIEW
|
||||
Conditions.PBV_1000
|
||||
Conditions.SLEEP_CLAUSE
|
||||
Conditions.EVASION_CLAUSE
|
||||
Conditions.SPECIES_CLAUSE
|
||||
Conditions.OHKO_CLAUSE
|
||||
Conditions.PRANKSTER_SWAGGER_CLAUSE
|
||||
Conditions.UNRELEASED_BAN
|
||||
]
|
||||
blockedClauses: conditions? || [Conditions.RATED_BATTLE]
|
||||
$popup.find('.popup_messages').addClass('small')
|
||||
$challenge.removeClass('hidden')
|
||||
$challenge
|
||||
|
||||
##########
|
||||
# EVENTS #
|
||||
##########
|
||||
|
||||
keyPressEvent: (e) =>
|
||||
switch e.which
|
||||
when 13 # [ Enter ]
|
||||
$input = $(e.currentTarget)
|
||||
message = @messageFromPopup(e.currentTarget)
|
||||
text = $input.val()
|
||||
return if text.length == 0
|
||||
PokeBattle.primus.send('privateMessage', message.id, text)
|
||||
$input.val('')
|
||||
|
||||
keyUpEvent: (e) =>
|
||||
switch e.which
|
||||
when 27 # [ Esc ]
|
||||
@closePopupEvent(e)
|
||||
|
||||
minimizePopupEvent: (e) =>
|
||||
message = @messageFromPopup(e.currentTarget)
|
||||
message.trigger('minimize', message)
|
||||
|
||||
closePopupEvent: (e) =>
|
||||
message = @messageFromPopup(e.currentTarget)
|
||||
message.trigger('close', message)
|
||||
|
||||
focusChatEvent: (e) =>
|
||||
@$closestPopup(e.currentTarget).find('input').focus()
|
||||
|
||||
toggleChallengeEvent: (e) =>
|
||||
$popup = @$closestPopup(e.currentTarget)
|
||||
$challenge = $popup.find('.challenge')
|
||||
wasAtBottom = @isAtBottom($popup)
|
||||
if $challenge.hasClass("hidden")
|
||||
@createChallenge($popup)
|
||||
else if $challenge.find('.cancel_challenge').text() == 'Cancel'
|
||||
$popup.find('.send_challenge').click()
|
||||
else
|
||||
@closeChallenge(@messageFromPopup($popup))
|
||||
if wasAtBottom then @scrollToBottom($popup)
|
||||
|
||||
sendChallengeEvent: (e) =>
|
||||
$popup = @$closestPopup(e.currentTarget)
|
||||
$challenge = $popup.find('.challenge')
|
||||
$challenge.find(".icon-spinner").removeClass('hidden')
|
||||
$challenge.find(".challenge_text").text('Challenging...')
|
||||
$challenge.find(".cancel_challenge").text('Cancel')
|
||||
|
||||
challengeCanceledEvent: (e) =>
|
||||
message = @messageFromPopup(e.currentTarget)
|
||||
message.trigger('cancelChallenge', message.id)
|
||||
|
||||
focusPopupEvent: (e) =>
|
||||
$popup = @$closestPopup(e.currentTarget)
|
||||
$popup.removeClass('new_message')
|
||||
@resetNotifications(@collection.get($popup.data('user-id')))
|
||||
34
client/app/js/views/replays/replay_view.coffee
Normal file
34
client/app/js/views/replays/replay_view.coffee
Normal file
@@ -0,0 +1,34 @@
|
||||
class @ReplayView extends Backbone.View
|
||||
replayTemplate: JST['replay']
|
||||
|
||||
events:
|
||||
'click .delete-replay': 'deleteReplay'
|
||||
|
||||
render: =>
|
||||
@$el.empty()
|
||||
|
||||
templates = @collection.map((replay) => @replayTemplate({window, replay}))
|
||||
groups = for x in [0...templates.length] by 3
|
||||
_.compact([ templates[x], templates[x + 1], templates[x + 2] ])
|
||||
|
||||
for groupHTML in groups
|
||||
$row = $('<div/>').addClass('row-fluid')
|
||||
$row.append(groupHTML)
|
||||
$row.appendTo(@$el)
|
||||
|
||||
if @collection.length == 0
|
||||
@$el.append($("<p/>").text("You have not saved any replays."))
|
||||
|
||||
this
|
||||
|
||||
deleteReplay: (e) =>
|
||||
return unless confirm("Do you really want to delete this replay?")
|
||||
$target = $(e.currentTarget)
|
||||
$spinner = $target.closest('.clickable-box').find('.show_spinner')
|
||||
|
||||
$spinner.removeClass('hidden')
|
||||
cid = $target.data('cid')
|
||||
replay = @collection.get(cid)
|
||||
replay
|
||||
.destroy()
|
||||
.complete(@render)
|
||||
188
client/app/js/views/sidebar_view.coffee
Normal file
188
client/app/js/views/sidebar_view.coffee
Normal file
@@ -0,0 +1,188 @@
|
||||
class @SidebarView extends Backbone.View
|
||||
template: JST['navigation']
|
||||
|
||||
events:
|
||||
"click .logo" : "focusLobbyEvent"
|
||||
"click .nav_rooms li" : 'focusRoomEvent'
|
||||
"click .nav_battles li" : 'focusBattleEvent'
|
||||
"click .nav_messages li": 'focusMessageEvent'
|
||||
"click .nav_battles .close" : 'leaveRoomEvent'
|
||||
"click .nav_messages .close" : 'closeMessageEvent'
|
||||
"click .nav_teambuilder": 'showTeambuilder'
|
||||
"click .nav_battle_list": 'showBattleList'
|
||||
|
||||
initialize: (attributes) =>
|
||||
@currentWindow = null
|
||||
|
||||
@listenTo(PokeBattle.battles, 'add', @addBattle)
|
||||
@listenTo(PokeBattle.battles, 'remove', @removeBattle)
|
||||
@listenTo(PokeBattle.battles, 'reset', @resetBattles)
|
||||
@listenTo(PokeBattle.battles, 'change:notifications', @renderNotifications)
|
||||
|
||||
@listenTo(PokeBattle.messages, 'open receive', @addMessage)
|
||||
@listenTo(PokeBattle.messages, 'close', @removeMessage)
|
||||
@listenTo(PokeBattle.messages, 'reset', @resetMessages)
|
||||
@listenTo(PokeBattle.messages, 'change:notifications', @renderMessageNotifications)
|
||||
|
||||
@render()
|
||||
|
||||
showTeambuilder: =>
|
||||
@changeWindowTo($("#teambuilder-section"), $(".nav_teambuilder"))
|
||||
|
||||
showBattleList: =>
|
||||
@changeWindowTo($("#battle-list-section"), $(".nav_battle_list"))
|
||||
PokeBattle.battleList.refreshList()
|
||||
|
||||
render: =>
|
||||
@$el.html @template(battles: PokeBattle.battles)
|
||||
|
||||
renderNotifications: (battle) =>
|
||||
$notifications = @$("[data-battle-id='#{battle.id}'] .notifications")
|
||||
|
||||
# We don't want to display notifications if this window is already focused.
|
||||
if @currentWindow.data('battle-id') == battle.id
|
||||
battle.set('notifications', 0, silent: true)
|
||||
$notifications.addClass('hidden')
|
||||
return
|
||||
|
||||
# Show notification count.
|
||||
notificationCount = battle.get('notifications')
|
||||
if notificationCount > 0
|
||||
$notifications.text(notificationCount)
|
||||
$notifications.removeClass('hidden')
|
||||
else
|
||||
$notifications.addClass('hidden')
|
||||
|
||||
addBattle: (battle) =>
|
||||
@$(".header_battles, .nav_battles").removeClass("hidden")
|
||||
$li = $("""<li class="nav_item fake_link" data-battle-id="#{battle.id}">
|
||||
<div class="nav_meta">
|
||||
<div class="notifications hidden">0</div>
|
||||
<div class="close">×</div>
|
||||
</div>#{battle.get('playerIds').join(' VS ')}</li>""")
|
||||
$li.appendTo(@$('.nav_battles'))
|
||||
$li.click()
|
||||
|
||||
removeBattle: (battle) =>
|
||||
$navItems = @$(".nav_item")
|
||||
$battle = @$(".nav_item[data-battle-id='#{battle.id}']")
|
||||
index = $navItems.index($battle)
|
||||
$battle.remove()
|
||||
if PokeBattle.battles.size() == 0
|
||||
@$(".header_battles, .nav_battles").addClass('hidden')
|
||||
PokeBattle.navigation.focusLobby()
|
||||
else
|
||||
$next = $navItems.eq(index).add($navItems.eq(index - 1))
|
||||
$next.first().click()
|
||||
|
||||
resetBattles: (battles) =>
|
||||
for battle in battles
|
||||
@addBattle(battle)
|
||||
|
||||
addMessage: (message) =>
|
||||
# This event can trigger on already opened messages, so we need to verify
|
||||
return if @$(".nav_item[data-message-id='#{message.id}']").length
|
||||
|
||||
@$(".header_messages, .nav_messages").removeClass("hidden")
|
||||
$li = $("""<li class="nav_item fake_link" data-message-id="#{message.id}">
|
||||
<div class="nav_meta">
|
||||
<div class="notifications hidden">0</div>
|
||||
<div class="close">×</div>
|
||||
</div>#{message.id}</li>""")
|
||||
$li.appendTo(@$('.nav_messages'))
|
||||
@renderMessageNotifications(message)
|
||||
|
||||
removeMessage: (message) =>
|
||||
@$(".nav_item[data-message-id='#{message.id}']").remove()
|
||||
|
||||
# If there are no messages, remove the header
|
||||
# Note: We can't check the collection directly since messages are never actually removed from it
|
||||
if @$('.nav_messages li').length == 0
|
||||
@$(".header_messages").addClass("hidden")
|
||||
|
||||
resetMessages: (messages) =>
|
||||
@addMessage(message) for message in messages
|
||||
|
||||
renderMessageNotifications: (message) =>
|
||||
$notifications = @$("[data-message-id='#{message.id}'] .notifications")
|
||||
|
||||
notificationCount = message.get('notifications')
|
||||
if notificationCount > 0
|
||||
$notifications.text(notificationCount)
|
||||
$notifications.removeClass('hidden')
|
||||
else
|
||||
$notifications.addClass('hidden')
|
||||
|
||||
focusLobby: =>
|
||||
# TODO: Clean this up once rooms are implemented
|
||||
# right now it duplicates part of focusRoom()
|
||||
$lobbyLink = @$(".nav_rooms li").first()
|
||||
@resetNotifications($lobbyLink)
|
||||
$room = $('.chat_window')
|
||||
@changeWindowTo($room, $lobbyLink)
|
||||
PokeBattle.router.navigate("")
|
||||
|
||||
leaveRoomEvent: (e) =>
|
||||
$navItem = $(e.currentTarget).closest('.nav_item')
|
||||
battleId = $navItem.data('battle-id')
|
||||
battle = PokeBattle.battles.get(battleId)
|
||||
if battle.isPlaying()
|
||||
return if !confirm("Are you sure you want to forfeit this battle?")
|
||||
battle.forfeit()
|
||||
PokeBattle.battles.remove(battle)
|
||||
false
|
||||
|
||||
closeMessageEvent: (e) =>
|
||||
$navItem = $(e.currentTarget).closest('.nav_item')
|
||||
messageId = $navItem.data('message-id')
|
||||
message = PokeBattle.messages.get(messageId)
|
||||
message.trigger('close', message)
|
||||
|
||||
focusBattleEvent: (e) =>
|
||||
$this = $(e.currentTarget)
|
||||
@resetNotifications($this)
|
||||
battleId = $this.data('battle-id')
|
||||
@changeWindowToBattle(battleId)
|
||||
|
||||
focusLobbyEvent: (e) =>
|
||||
@focusLobby()
|
||||
|
||||
focusRoomEvent: (e) =>
|
||||
$this = $(e.currentTarget)
|
||||
@resetNotifications($this)
|
||||
# TODO: Remove hardcoding once rooms are implemented
|
||||
$room = $('.chat_window')
|
||||
@changeWindowTo($room, $this)
|
||||
PokeBattle.router.navigate("")
|
||||
|
||||
focusMessageEvent: (e) =>
|
||||
$navItem = $(e.currentTarget).closest('.nav_item')
|
||||
messageId = $navItem.data('message-id')
|
||||
message = PokeBattle.messages.get(messageId)
|
||||
message.trigger('show', message)
|
||||
message.trigger('focus', message)
|
||||
|
||||
changeWindowTo: ($toSelector, $navItem) =>
|
||||
# Show window, hide others
|
||||
$mainContent = $('#main-section')
|
||||
$mainContent.children().addClass("hidden")
|
||||
@currentWindow = $toSelector.first()
|
||||
@currentWindow.removeClass("hidden")
|
||||
@currentWindow.find('.chat').trigger('scroll_to_bottom')
|
||||
|
||||
# Add .active to navigation, remove from others
|
||||
@$('.nav_item').removeClass('active')
|
||||
$navItem.addClass('active')
|
||||
|
||||
changeWindowToBattle: (battleId) =>
|
||||
$battle = $(""".battle_window[data-battle-id='#{battleId}']""")
|
||||
$navItem = @$("[data-battle-id='#{battleId}']")
|
||||
@changeWindowTo($battle, $navItem)
|
||||
PokeBattle.router.navigate("battles/#{battleId}")
|
||||
|
||||
resetNotifications: ($link) =>
|
||||
$link = $link.first()
|
||||
$link = $link.closest('li') if $link[0].tagName != 'li'
|
||||
if battleId = $link.data('battle-id')
|
||||
battle = PokeBattle.battles.get(battleId)
|
||||
battle.set('notifications', 0)
|
||||
484
client/app/js/views/teambuilder/pokemon_edit_view.coffee
Normal file
484
client/app/js/views/teambuilder/pokemon_edit_view.coffee
Normal file
@@ -0,0 +1,484 @@
|
||||
isMobileOrAndroid = ->
|
||||
return true if /Mobile/i.test(window.navigator.userAgent)
|
||||
return true if /Android/i.test(window.navigator.userAgent)
|
||||
return false
|
||||
|
||||
# helper which attaches selectize
|
||||
attachSelectize = ($element, options) ->
|
||||
# Block selectize on mobile and all android operating systems (All androids are blocked due to a bug)
|
||||
return if isMobileOrAndroid()
|
||||
$element.selectize(options)
|
||||
|
||||
setSelectizeValue = ($element, value) ->
|
||||
if isMobileOrAndroid()
|
||||
$element.val(value)
|
||||
else
|
||||
$element.each ->
|
||||
@selectize?.setValue(value)
|
||||
|
||||
setSelectizeDisabled = ($element, disabled) ->
|
||||
$element.filter(".selectized").each ->
|
||||
return unless @selectize
|
||||
if disabled then @selectize.disable() else @selectize.enable()
|
||||
|
||||
class @PokemonEditView extends Backbone.View
|
||||
editTemplate: JST['teambuilder/pokemon']
|
||||
speciesTemplate: JST['teambuilder/species']
|
||||
nonStatsTemplate: JST['teambuilder/non_stats']
|
||||
movesTemplate: JST['teambuilder/moves']
|
||||
|
||||
events:
|
||||
'change .sortSpecies': 'changeSort'
|
||||
'change .species_list': 'changeSpecies'
|
||||
'change .selected_nickname': 'changeNickname'
|
||||
'click .selected_shininess': 'changeShiny'
|
||||
'click .selected_happiness': 'changeHappiness'
|
||||
'change .selected-forme': 'changeForme'
|
||||
'change .selected_nature': 'changeNature'
|
||||
'change .selected_ability': 'changeAbility'
|
||||
'change .selected_item': 'changeItem'
|
||||
'change .selected_gender': 'changeGender'
|
||||
'change .selected_level': 'changeLevel'
|
||||
'change .iv-entry': 'changeIv'
|
||||
'focus .ev-entry': 'focusEv'
|
||||
'blur .ev-entry': 'changeEv'
|
||||
'change .ev-entry': 'changeEv'
|
||||
'input .ev-entry[type=range]': 'changeEv' # fix for firefox
|
||||
'change .select-hidden-power': 'changeHiddenPower'
|
||||
'keydown .selected_moves input': 'keydownMoves'
|
||||
'blur .selected_moves input': 'blurMoves'
|
||||
'click .table-moves tbody tr': 'clickMoveName'
|
||||
'mousedown .table-moves': 'preventBlurMoves'
|
||||
'click .move-button': 'clickSelectedMove'
|
||||
'click .move-button .close': 'removeSelectedMove'
|
||||
|
||||
initialize: (attributes={}) =>
|
||||
@onPokemonChange = attributes.onPokemonChange
|
||||
|
||||
setFormat: (format) =>
|
||||
format = Formats[format] || Formats[DEFAULT_FORMAT]
|
||||
@setGeneration(format.generation)
|
||||
# TODO: Set PBV limit based on conditions
|
||||
|
||||
changeSort:(e) =>
|
||||
sort = $(e.currentTarget).val()
|
||||
console.log(sort)
|
||||
if sort =="Default Sort"
|
||||
@sortSpecieslist("Default")
|
||||
else if sort == "Sort by Dexnumber"
|
||||
@sortSpecieslist("id", false)
|
||||
else if sort == "Invert by Dexnumber"
|
||||
@sortSpecieslist("id", true)
|
||||
else if sort == "Sort Alphabetically"
|
||||
@sortSpecieslist("pokename", false)
|
||||
else if sort == "Invert Alphabetically"
|
||||
@sortSpecieslist("pokename", true)
|
||||
|
||||
sortSpecieslist: (option, reverse) =>
|
||||
{MoveData, SpeciesData, ItemData} = @generation
|
||||
if option == "Default"
|
||||
sortedlist = @getSpecies
|
||||
else
|
||||
sortedlist = @sortObject(SpeciesData, option, reverse)
|
||||
@speciesList = (species for species, data of sortedlist)
|
||||
@render()
|
||||
|
||||
sortObject: (data, option, reverse) ->
|
||||
arr = []
|
||||
for key, val of data
|
||||
val.pokename = key
|
||||
arr.push(val)
|
||||
arr = _.sortBy(arr, option)
|
||||
if reverse == true
|
||||
arr.reverse()
|
||||
newobj = {}
|
||||
for thing in arr
|
||||
newobj[thing.pokename] = thing
|
||||
finished = newobj
|
||||
|
||||
setGeneration: (generation) =>
|
||||
@generation = window.Generations[generation.toUpperCase()]
|
||||
{MoveData, SpeciesData, ItemData} = @generation
|
||||
@moveData = MoveData
|
||||
@speciesList = (species for species, data of SpeciesData)
|
||||
# TODO: filter irrelevant items
|
||||
@itemList = (_(itemName for itemName, data of ItemData).sort())
|
||||
|
||||
@render()
|
||||
|
||||
setPokemon: (pokemon) =>
|
||||
# Stop listening for change events on the previously set pokemon
|
||||
@stopListening(@pokemon) if @pokemon
|
||||
|
||||
@pokemon = pokemon
|
||||
@listenTo(pokemon, 'change:level', @renderStats)
|
||||
@listenTo(pokemon, 'change:ivs', @renderStats)
|
||||
@listenTo(pokemon, 'change:evs', @renderStats)
|
||||
@listenTo(pokemon, 'change:nature', @renderStats)
|
||||
@listenTo(pokemon, 'change:hiddenPowerType', @renderStats)
|
||||
@listenTo(pokemon, 'change:shiny', @renderSpecies)
|
||||
|
||||
@renderPokemon()
|
||||
|
||||
setTeamPBV: (pbv) =>
|
||||
@teamPBV = pbv
|
||||
|
||||
changeSpecies: (e) =>
|
||||
return if not @onPokemonChange
|
||||
species = $(e.currentTarget).val()
|
||||
@pokemon = if species
|
||||
new Pokemon(teambuilder: true, species: species)
|
||||
else
|
||||
new NullPokemon()
|
||||
@onPokemonChange(@pokemon)
|
||||
|
||||
changeNickname: (e) =>
|
||||
$input = $(e.currentTarget)
|
||||
@pokemon.set("name", $input.val())
|
||||
|
||||
changeShiny: (e) =>
|
||||
$switch = $(e.currentTarget).toggleClass("selected")
|
||||
@pokemon.set("shiny", $switch.is(".selected"))
|
||||
|
||||
changeHappiness: (e) =>
|
||||
$switch = $(e.currentTarget).toggleClass("selected")
|
||||
happiness = if $switch.is(".selected") then 0 else 100
|
||||
@pokemon.set("happiness", happiness)
|
||||
|
||||
changeForme: (e) =>
|
||||
$forme = $(e.currentTarget)
|
||||
@pokemon.set('forme', $forme.val())
|
||||
# Forme changes may have different abilities, so we have to change this.
|
||||
@pokemon.set('ability', @pokemon.getAbilities()[0])
|
||||
|
||||
changeNature: (e) =>
|
||||
$list = $(e.currentTarget)
|
||||
@pokemon.set("nature", $list.val())
|
||||
|
||||
changeAbility: (e) =>
|
||||
$list = $(e.currentTarget)
|
||||
@pokemon.set("ability", $list.val())
|
||||
|
||||
changeItem: (e) =>
|
||||
$list = $(e.currentTarget)
|
||||
@pokemon.set("item", $list.val())
|
||||
|
||||
changeGender: (e) =>
|
||||
$list = $(e.currentTarget)
|
||||
@pokemon.set("gender", $list.val())
|
||||
|
||||
changeLevel: (e) =>
|
||||
$input = $(e.currentTarget)
|
||||
value = parseInt($input.val(), 10)
|
||||
value = 1120 if isNaN(value) || value > 120
|
||||
value = 1 if value < 1
|
||||
$input.val(value)
|
||||
@pokemon.set("level", value)
|
||||
|
||||
changeIv: (e) =>
|
||||
# todo: make changeIv and changeEv DRY
|
||||
$input = $(e.currentTarget)
|
||||
stat = $input.data("stat")
|
||||
value = parseInt($input.val(), 10)
|
||||
if isNaN(value) || value > 31 || value < 0
|
||||
value = 31
|
||||
|
||||
@pokemon.setIv(stat, value)
|
||||
|
||||
focusEv: (e) =>
|
||||
$input = $(e.currentTarget)
|
||||
return if $input.is("[type=range]")
|
||||
value = parseInt($input.val(), 10)
|
||||
$input.val("") if value == 0
|
||||
|
||||
changeEv: (e) =>
|
||||
# todo: make changeIv and changeEv DRY
|
||||
$input = $(e.currentTarget)
|
||||
stat = $input.data("stat")
|
||||
value = parseInt($input.val(), 10)
|
||||
value = 252 if value > 252
|
||||
value = 0 if isNaN(value) || value < 0
|
||||
|
||||
value = @pokemon.setEv(stat, value)
|
||||
$input.val(value) if not $input.is("[type=range]")
|
||||
|
||||
changeHiddenPower: (e) =>
|
||||
$input = $(e.currentTarget)
|
||||
type = $input.val()
|
||||
@pokemon.set('hiddenPowerType', type.toLowerCase())
|
||||
|
||||
# Prevents the blurMoves event from activating for the duration of
|
||||
# the remaining javascript events. This allows the click event to not fire
|
||||
# the blur event.
|
||||
preventBlurMoves: (e) =>
|
||||
@_preventBlur = true
|
||||
_.defer =>
|
||||
@_preventBlur = false
|
||||
|
||||
blurMoves: (e) =>
|
||||
$input = $(e.currentTarget)
|
||||
if @_preventBlur
|
||||
previousScrollPosition = @$el.scrollTop()
|
||||
$input.focus()
|
||||
e.preventDefault()
|
||||
@$el.scrollTop(previousScrollPosition) # prevent scroll from refocus
|
||||
return
|
||||
|
||||
$selectedMove = @$selectedMove()
|
||||
moveName = $selectedMove.data('move-id')
|
||||
|
||||
# Remove filtering and row selection
|
||||
@filterMovesBy("")
|
||||
$(".table-moves .active").removeClass("active")
|
||||
|
||||
if $input.val().length == 0
|
||||
@recordMoves()
|
||||
else
|
||||
@insertMove($input, moveName)
|
||||
|
||||
clickMoveName: (e) =>
|
||||
$this = $(e.currentTarget)
|
||||
moveName = $this.data('move-id')
|
||||
$moves = @$el.find('.selected_moves')
|
||||
$input = $moves.find('input:focus').first()
|
||||
$input = $moves.find('input').first() if $input.length == 0
|
||||
return if $input.length == 0
|
||||
@insertMove($input, moveName)
|
||||
|
||||
insertMove: ($input, moveName) =>
|
||||
currentScrollPosition = @$el.scrollTop()
|
||||
|
||||
@preventBlurMoves()
|
||||
return if !@buttonify($input, moveName)
|
||||
$moves = @$el.find('.selected_moves')
|
||||
$firstInput = $moves.find('input').first()
|
||||
if $firstInput.length > 0
|
||||
$firstInput.focus()
|
||||
@$el.scrollTop(currentScrollPosition)
|
||||
else
|
||||
@$el.scrollTop(0)
|
||||
@recordMoves()
|
||||
|
||||
recordMoves: =>
|
||||
movesArray = []
|
||||
$moves = @$el.find('.selected_moves')
|
||||
$moves.find('.move-button').each ->
|
||||
moveName = $(this).find("span").text().trim()
|
||||
if moveName != ""
|
||||
movesArray.push(moveName)
|
||||
@pokemon.set("moves", movesArray)
|
||||
|
||||
$selectedMove: =>
|
||||
$table = @$el.find('.table-moves')
|
||||
$allMoves = $table.find('tbody tr')
|
||||
$allMoves.filter('.active').first()
|
||||
|
||||
clickSelectedMove: (e) =>
|
||||
$this = $(e.currentTarget)
|
||||
moveName = $this.find('span').text()
|
||||
$input = $("<input type='text' value='#{moveName}'/>")
|
||||
$this.replaceWith($input)
|
||||
$input.focus().select()
|
||||
|
||||
# Set the current move row to active
|
||||
$(".table-moves tr[data-move-id='#{moveName}']").addClass("active")
|
||||
|
||||
removeSelectedMove: (e) =>
|
||||
$this = $(e.currentTarget).parent()
|
||||
$input = $("<input type='text'/>")
|
||||
$this.replaceWith($input)
|
||||
$input.focus()
|
||||
e.stopPropagation()
|
||||
|
||||
buttonify: ($input, moveName) =>
|
||||
return false if moveName not of @moveData
|
||||
|
||||
# The blur event may have been cancelled, so when removing the input also
|
||||
# remove the filter
|
||||
if $input.is(":focus")
|
||||
@filterMovesBy("")
|
||||
$(".table-moves .active").removeClass("active")
|
||||
|
||||
type = @moveData[moveName].type.toLowerCase()
|
||||
$input.replaceWith("""
|
||||
<div class="button move-button #{type}"><span>#{moveName}</span><div class='close'>×</div></div>
|
||||
""")
|
||||
return true
|
||||
|
||||
keydownMoves: (e) =>
|
||||
$input = $(e.currentTarget)
|
||||
$table = @$el.find('.table-moves')
|
||||
$allMoves = $table.find('tbody tr')
|
||||
switch e.which
|
||||
when 13 # [Enter]; we're selecting the active move.
|
||||
$activeMove = @$selectedMove()
|
||||
$activeMove.click()
|
||||
when 38 # [Up arrow]; selects move above
|
||||
$activeMove = $allMoves.filter('.active').first()
|
||||
$prevMove = $activeMove.prevAll(":visible").first()
|
||||
if $prevMove.length > 0
|
||||
$activeMove.removeClass('active')
|
||||
$prevMove.addClass('active')
|
||||
when 40 # [Down arrow]; selects move below
|
||||
$activeMove = $allMoves.filter('.active').first()
|
||||
$nextMove = $activeMove.nextAll(":visible").first()
|
||||
if $nextMove.length > 0
|
||||
$activeMove.removeClass('active')
|
||||
$nextMove.addClass('active')
|
||||
else
|
||||
# Otherwise we're filtering moves
|
||||
# We defer since $input may not have updated yet
|
||||
_.defer =>
|
||||
return unless $input.is(":focus")
|
||||
moveName = $input.val()
|
||||
@filterMovesBy(moveName)
|
||||
|
||||
filterMovesBy: (moveName) =>
|
||||
moveName = moveName.replace(/\s+|-/g, "")
|
||||
$table = @$el.find('.table-moves')
|
||||
$allMoves = $table.find('tbody tr')
|
||||
moveRegex = new RegExp(moveName, "i")
|
||||
$moves = $allMoves.filter ->
|
||||
$move = $(this)
|
||||
moveName = $move.data('move-search-id')
|
||||
moveRegex.test(moveName)
|
||||
$table.addClass('hidden')
|
||||
$moves.removeClass('hidden')
|
||||
$allMoves.not($moves).addClass('hidden')
|
||||
$allMoves.removeClass('active')
|
||||
$moves.first().addClass('active')
|
||||
$table.removeClass('hidden')
|
||||
|
||||
disableEventsAndExecute: (callback) =>
|
||||
isOutermost = !@_eventsDisabled
|
||||
|
||||
@_eventsDisabled = true
|
||||
@undelegateEvents() if isOutermost # disable events
|
||||
callback()
|
||||
@delegateEvents() if isOutermost
|
||||
@_eventsDisabled = false if isOutermost
|
||||
|
||||
render: =>
|
||||
@$el.html @editTemplate(window: window, speciesList: @speciesList, itemList: @itemList)
|
||||
attachSelectize(@$el.find(".species_list"),
|
||||
render:
|
||||
option: (item, escape) =>
|
||||
pbv = PokeBattle.PBV.determinePBV(@generation, species: item.value)
|
||||
return "<div class='clearfix'>#{item.text}<div class='pbv'>#{pbv}</div></div>"
|
||||
)
|
||||
attachSelectize(@$el.find(".selected_item"))
|
||||
return this
|
||||
|
||||
renderPokemon: =>
|
||||
@renderSpecies()
|
||||
@renderNonStats()
|
||||
@renderStats()
|
||||
@renderMoves()
|
||||
@renderPBV()
|
||||
|
||||
# Disable entering values if this is a NullPokemon
|
||||
$elements = @$el.find("input, select").not(".species input, .species select")
|
||||
$elements.prop("disabled", @pokemon.isNull)
|
||||
setSelectizeDisabled($elements, @pokemon.isNull)
|
||||
|
||||
return this
|
||||
|
||||
renderPBV: =>
|
||||
individualPBV = @pokemon.getPBV()
|
||||
@$(".individual-pbv").text(individualPBV)
|
||||
|
||||
team = @pokemon.getTeam()
|
||||
if team && team.hasPBV()
|
||||
pbv = team.getPBV()
|
||||
maxPBV = team.getMaxPBV()
|
||||
@$(".total-pbv").text(pbv).toggleClass("red", pbv > maxPBV)
|
||||
@$(".max-pbv").text(maxPBV)
|
||||
|
||||
renderSpecies: =>
|
||||
@disableEventsAndExecute =>
|
||||
setSelectizeValue(@$(".species_list"), @pokemon.get("species"))
|
||||
html = if @pokemon.isNull then "" else @speciesTemplate(window: window, pokemon: @pokemon)
|
||||
@$(".species-info").html(html)
|
||||
@$(".selected_shininess").toggleClass("selected", @pokemon.get('shiny') == true)
|
||||
@$(".selected_happiness").toggleClass("selected", @pokemon.get("happiness") == 0)
|
||||
|
||||
renderNonStats: =>
|
||||
$nonStats = @$el.find(".non-stats")
|
||||
|
||||
populateSelect = (searchStr, valueTextPairs, selectedValue) ->
|
||||
$select = $nonStats.find(searchStr).empty()
|
||||
for pair in valueTextPairs
|
||||
value = text = pair
|
||||
if pair instanceof Array
|
||||
value = pair[0]
|
||||
text = pair[1]
|
||||
|
||||
$select.append($("<option>").attr("value", value).text(text))
|
||||
$select.val(selectedValue)
|
||||
|
||||
displayedGenders =
|
||||
F: "Female"
|
||||
M: "Male"
|
||||
Genderless: "Genderless"
|
||||
|
||||
@disableEventsAndExecute =>
|
||||
genders = ([g, displayedGenders[g]] for g in @pokemon.getGenders())
|
||||
$nonStats.find(".selected_nickname").val(@pokemon.get("name"))
|
||||
populateSelect ".selected_ability", @pokemon.getAbilities(), @pokemon.get("ability")
|
||||
populateSelect ".selected_nature", @pokemon.getNatures(), @pokemon.get("nature")
|
||||
setSelectizeValue(@$(".selected_item"), @pokemon.get("item"))
|
||||
populateSelect ".selected_gender", genders, @pokemon.get("gender")
|
||||
$nonStats.find(".selected_level").val(@pokemon.get("level"))
|
||||
|
||||
renderStats: =>
|
||||
pokemon = @pokemon
|
||||
|
||||
@$(".iv-entry").each ->
|
||||
$input = $(this)
|
||||
stat = $input.data("stat")
|
||||
$input.val(pokemon.iv(stat))
|
||||
|
||||
@$(".ev-entry").each ->
|
||||
return if $(this).is(":focus")
|
||||
$input = $(this)
|
||||
stat = $input.data("stat")
|
||||
$input.val(pokemon.ev(stat))
|
||||
|
||||
@$('.base-stat').each ->
|
||||
$this = $(this)
|
||||
stat = $this.data("stat")
|
||||
$this.text(pokemon.base(stat))
|
||||
|
||||
@$('.stat-total').each ->
|
||||
$this = $(this)
|
||||
stat = $this.data("stat")
|
||||
$this.text(pokemon.stat(stat))
|
||||
$this.removeClass('plus-nature minus-nature')
|
||||
|
||||
if pokemon.natureBoost(stat) > 1
|
||||
$this.addClass('plus-nature')
|
||||
$this.text($this.text() + '+')
|
||||
|
||||
if pokemon.natureBoost(stat) < 1
|
||||
$this.addClass('minus-nature')
|
||||
$this.text($this.text() + '-')
|
||||
|
||||
remainingEvs = 508 - @pokemon.getTotalEVs()
|
||||
@$('.remaining-evs-amount')
|
||||
.text(remainingEvs)
|
||||
.toggleClass("over-limit", remainingEvs < 0)
|
||||
|
||||
@$('.select-hidden-power').val(@pokemon.get('hiddenPowerType'))
|
||||
|
||||
renderMoves: =>
|
||||
# TODO: Cache the resultant html
|
||||
$moveSection = @$el.find(".moves-section")
|
||||
if @pokemon.isNull
|
||||
$moveSection.html ""
|
||||
return
|
||||
|
||||
$moveSection.html @movesTemplate(window: window, pokemon: @pokemon)
|
||||
$moveSection.find('.selected_moves input').each (i, el) =>
|
||||
$this = $(el)
|
||||
moveName = $this.val()
|
||||
@buttonify($this, moveName)
|
||||
301
client/app/js/views/teambuilder/teambuilder_view.coffee
Normal file
301
client/app/js/views/teambuilder/teambuilder_view.coffee
Normal file
@@ -0,0 +1,301 @@
|
||||
class @TeambuilderView extends Backbone.View
|
||||
template: JST['teambuilder/main']
|
||||
teamTemplate: JST['teambuilder/team']
|
||||
teamsTemplate: JST['teambuilder/teams']
|
||||
pokemonListTemplate: JST['teambuilder/pokemon_list']
|
||||
|
||||
events:
|
||||
# Team view
|
||||
'click .add-new-team': 'addNewTeamEvent'
|
||||
'click .export-team': 'exportTeam'
|
||||
'click .clone-team': 'cloneTeam'
|
||||
'click .delete-team': 'deleteTeamEvent'
|
||||
'click .go-to-team': 'clickTeam'
|
||||
'click .import-team': 'renderImportTeamModal'
|
||||
|
||||
# Teambuild view
|
||||
'click .change-format-dropdown a': 'changeTeamFormat'
|
||||
'blur .team_name': 'blurTeamName'
|
||||
'keypress .team_name': 'keypressTeamName'
|
||||
'click .go_back': 'goBackToOverview'
|
||||
'click .pokemon_list li': 'clickPokemon'
|
||||
'click .add_pokemon': 'addNewPokemonEvent'
|
||||
'click .save_team': 'saveTeam'
|
||||
|
||||
initialize: (attributes) =>
|
||||
@selectedPokemon = 0
|
||||
@selectedTeam = null
|
||||
|
||||
@render()
|
||||
|
||||
@listenTo(PokeBattle.TeamStore, 'reset', @resetTeams)
|
||||
@listenTo(PokeBattle.TeamStore, 'add', @addNewTeam)
|
||||
@listenTo(PokeBattle.TeamStore, 'remove', @deleteTeam)
|
||||
@listenTo(PokeBattle.TeamStore, 'change:id', @changeTeamId)
|
||||
@listenTo(PokeBattle.TeamStore, 'reset', @renderTeams)
|
||||
@listenTo(PokeBattle.TeamStore, 'saving', @renderSaving)
|
||||
@listenTo(PokeBattle.TeamStore, 'saved', @renderSaved)
|
||||
@listenTo PokeBattle.TeamStore, 'render', (team) =>
|
||||
@renderTeams()
|
||||
if @getSelectedTeam() && team.id == @getSelectedTeam().id
|
||||
@setSelectedTeam(team)
|
||||
|
||||
@pokemonEditView = new PokemonEditView(
|
||||
el: @$('.pokemon_edit')
|
||||
onPokemonChange: (newPokemon) =>
|
||||
team = @getSelectedTeam()
|
||||
team.replace(@selectedPokemon, newPokemon)
|
||||
@renderPBV()
|
||||
)
|
||||
|
||||
clickTeam: (e) =>
|
||||
$team = $(e.currentTarget).closest('.select-team')
|
||||
team = PokeBattle.TeamStore.get($team.data('cid'))
|
||||
@setSelectedTeam(team)
|
||||
|
||||
clickPokemon: (e) =>
|
||||
$listItem = $(e.currentTarget)
|
||||
index = @$('.pokemon_list li').index($listItem)
|
||||
@setSelectedPokemonIndex(index)
|
||||
|
||||
attachEventsToTeam: (team) =>
|
||||
return if team.attachedTeambuildEvents
|
||||
|
||||
@listenTo(team, 'add:pokemon', @renderPokemon)
|
||||
|
||||
# Todo: Make this perform better
|
||||
@listenTo(team, 'change:pokemon[*].species change:pokemon[*].forme', (pokemon) =>
|
||||
@renderPokemonList()
|
||||
@renderPokemon(pokemon)
|
||||
)
|
||||
|
||||
@listenTo(team, 'add:pokemon remove:pokemon', @renderPokemonList)
|
||||
@listenTo(team, 'reset:pokemon', (=> @changeTeam(team)))
|
||||
@listenTo(team, 'change nested-change reset:pokemon add:pokemon remove:pokemon', @dirty)
|
||||
@listenTo(team, 'change:pokemon[*] reset:pokemon add:pokemon remove:pokemon', @renderPBV)
|
||||
|
||||
# A temporary flag to attach until the teambuilder view is refactored
|
||||
team.attachedTeambuildEvents = true
|
||||
|
||||
addEmptyPokemon: (team) =>
|
||||
team.get('pokemon').add(new NullPokemon())
|
||||
|
||||
addNewTeamEvent: (e) =>
|
||||
team = new Team()
|
||||
PokeBattle.TeamStore.add(team)
|
||||
team.save()
|
||||
|
||||
addNewTeam: (team) =>
|
||||
@addEmptyPokemon(team) while team.get('pokemon').length < 6
|
||||
@$('.teambuilder_teams').append @teamTemplate({team, window})
|
||||
@attachEventsToTeam(team)
|
||||
|
||||
resetTeams: (teamStore) =>
|
||||
teamStore.forEach (team) =>
|
||||
@attachEventsToTeam(team)
|
||||
|
||||
cloneTeam: (e) =>
|
||||
$team = $(e.currentTarget).closest('.select-team')
|
||||
cid = $team.data('cid')
|
||||
clone = @getTeam(cid).clone().set("id", null)
|
||||
PokeBattle.TeamStore.add(clone)
|
||||
clone.save()
|
||||
return false
|
||||
|
||||
deleteTeamEvent: (e) =>
|
||||
return false if !confirm("Do you really want to delete this team?")
|
||||
$team = $(e.currentTarget).closest('.select-team')
|
||||
team = @getTeam($team.data('cid'))
|
||||
PokeBattle.TeamStore.remove(team)
|
||||
team.destroy()
|
||||
return false
|
||||
|
||||
deleteTeam: (team) =>
|
||||
@$(".select-team[data-cid=#{team.cid}]").remove()
|
||||
|
||||
changeTeam: (team) =>
|
||||
html = $(@teamTemplate({team, window})).html()
|
||||
@$(".select-team[data-cid=#{team.cid}]").html(html)
|
||||
|
||||
changeTeamId: (team) =>
|
||||
@$(".select-team[data-cid=#{team.cid}]").attr('data-id', team.id)
|
||||
|
||||
exportTeam: (e) =>
|
||||
$team = $(e.currentTarget).closest('.select-team')
|
||||
id = $team.data('id')
|
||||
if not @getTeam(id).hasNonNullPokemon()
|
||||
alert("You cannot export empty teams. Please add some pokemon first.")
|
||||
return false
|
||||
|
||||
teamJSON = @getTeam(id).toNonNullJSON()
|
||||
teamString = PokeBattle.exportTeam(teamJSON.pokemon)
|
||||
|
||||
$modal = PokeBattle.modal('modals/export_team')
|
||||
$modal.find('.exported-team').val(teamString)
|
||||
$modal.find('textarea, input').first().focus().select()
|
||||
return false
|
||||
|
||||
addNewPokemonEvent: =>
|
||||
@addNewPokemon(@getSelectedTeam())
|
||||
|
||||
addNewPokemon: (team) =>
|
||||
@addEmptyPokemon(team)
|
||||
@$('.pokemon_list li').last().click()
|
||||
|
||||
saveTeam: =>
|
||||
clone = @getSelectedTeam()
|
||||
team = PokeBattle.TeamStore.get(clone.id)
|
||||
team.save(clone.toJSON(), silent: true)
|
||||
@resetHeaderButtons()
|
||||
|
||||
changeTeamFormat: (e) =>
|
||||
$link = $(e.currentTarget)
|
||||
format = $link.data('format')
|
||||
team = @getSelectedTeam()
|
||||
if format != team.get('generation')
|
||||
team.set('generation', format)
|
||||
@renderTeam()
|
||||
@dirty() # renderTeam() removes dirty, so call it again
|
||||
|
||||
setSelectedPokemonIndex: (index) =>
|
||||
pokemon = @getSelectedTeam().at(index)
|
||||
@selectedPokemon = index
|
||||
|
||||
# Render the pokemon
|
||||
@pokemonEditView.setPokemon(pokemon)
|
||||
@renderPokemon(pokemon)
|
||||
|
||||
# Set the correct list item to active
|
||||
@$(".navigation li").removeClass("active")
|
||||
@$(".navigation li").eq(index).addClass("active")
|
||||
|
||||
getSelectedPokemon: =>
|
||||
@getSelectedTeam().at(@selectedPokemon)
|
||||
|
||||
setSelectedTeam: (team) =>
|
||||
# Duplicate the team, so that changes don't stick until saved
|
||||
@selectedTeam = team.clone()
|
||||
@selectedTeam.id = team.id
|
||||
@selectedTeam.cid = team.cid
|
||||
@selectedPokemon = 0
|
||||
@attachEventsToTeam(@selectedTeam)
|
||||
@renderTeam()
|
||||
|
||||
getAllTeams: =>
|
||||
PokeBattle.TeamStore.models
|
||||
|
||||
getSelectedTeam: =>
|
||||
@selectedTeam
|
||||
|
||||
getTeam: (idx) =>
|
||||
PokeBattle.TeamStore.get(idx)
|
||||
|
||||
blurTeamName: =>
|
||||
teamName = @$('.team_name').text()
|
||||
@getSelectedTeam().set('name', teamName)
|
||||
|
||||
keypressTeamName: (e) =>
|
||||
if e.which == 13 # [Enter]
|
||||
@$('.team_name').blur()
|
||||
|
||||
goBackToOverview: =>
|
||||
@renderTeams()
|
||||
|
||||
dirty: =>
|
||||
@$('.go_back').text('Discard changes')
|
||||
@$('.save_team').removeClass('disabled')
|
||||
|
||||
resetHeaderButtons: =>
|
||||
@$('.go_back').text('Back')
|
||||
@$('.save_team').addClass('disabled')
|
||||
|
||||
render: =>
|
||||
@$el.html @template(pokemon: @getSelectedTeam(), selected: @selectedPokemon)
|
||||
@renderTeams()
|
||||
|
||||
renderTeams: =>
|
||||
@$('.display_teams').html @teamsTemplate(teams: @getAllTeams(), window: window)
|
||||
@$('.display_teams').removeClass('hidden')
|
||||
@$('.display_pokemon').addClass('hidden')
|
||||
this
|
||||
|
||||
renderTeam: =>
|
||||
team = @getSelectedTeam()
|
||||
@pokemonEditView.setFormat(team.get('generation') || DEFAULT_FORMAT)
|
||||
@resetHeaderButtons()
|
||||
@renderFormat()
|
||||
@renderPokemonList()
|
||||
@setSelectedPokemonIndex(@selectedPokemon)
|
||||
@$('.team_name').text(team.getName())
|
||||
@$('.display_teams').addClass('hidden')
|
||||
@$('.display_pokemon').removeClass('hidden')
|
||||
|
||||
renderPokemonList: =>
|
||||
team = @getSelectedTeam()
|
||||
$pokemon_list = @$(".pokemon_list").empty()
|
||||
$pokemon_list.html @pokemonListTemplate(window: window, pokemonList: team.get('pokemon').models)
|
||||
$pokemon_list.find("li[data-pokemon-index=#{@selectedPokemon}]").addClass("active")
|
||||
|
||||
# NOTE: this isn't be used, and just amounts to hiding the button, however
|
||||
# we may re-enable this functionality in the future
|
||||
# Hide add pokemon if there's 6 pokemon
|
||||
if team.length < 6
|
||||
@$(".add_pokemon").show()
|
||||
else
|
||||
@$(".add_pokemon").hide()
|
||||
|
||||
renderPokemon: (pokemon) =>
|
||||
@pokemonEditView.setPokemon(pokemon)
|
||||
|
||||
renderPBV: (pokemon) =>
|
||||
if pokemon
|
||||
individualPBV = pokemon.getPBV()
|
||||
$listItem = @$(".pokemon_list li[data-pokemon-cid=#{pokemon.cid}]")
|
||||
$listItem.find(".pbv-value").text(individualPBV)
|
||||
|
||||
totalPBV = @getSelectedTeam().getPBV()
|
||||
@pokemonEditView.setTeamPBV(totalPBV)
|
||||
@pokemonEditView.renderPBV()
|
||||
|
||||
renderFormat: =>
|
||||
format = @getSelectedTeam().get("generation")
|
||||
format = DEFAULT_FORMAT if format not of Formats
|
||||
text = @$(".change-format-dropdown a[data-format='#{format}']").text()
|
||||
@$(".current-format").text(text)
|
||||
|
||||
renderImportTeamModal: =>
|
||||
$modal = PokeBattle.modal 'modals/import_team', ($modal) =>
|
||||
$modal.on 'click', '.import-team-submit', (e) =>
|
||||
teamString = $modal.find('.imported-team').val()
|
||||
pokemonJSON = PokeBattle.parseTeam(teamString)
|
||||
errors = @validateImportedTeam(pokemonJSON)
|
||||
if errors.length > 0
|
||||
listErrors = errors.map((e) -> "<li>#{e}</li>").join('')
|
||||
$errors = $modal.find('.form-errors')
|
||||
$errors.html("<ul>#{listErrors}</ul>").removeClass('hidden')
|
||||
else
|
||||
team = new Team(pokemon: pokemonJSON, teambuilder: true)
|
||||
PokeBattle.TeamStore.add(team)
|
||||
team.save()
|
||||
$modal.find('.imported-team').val("")
|
||||
$modal.modal('hide')
|
||||
return false
|
||||
$modal.find('.imported-team').first().focus()
|
||||
|
||||
validateImportedTeam: (json) =>
|
||||
errors = []
|
||||
pokemonSpecies = (pokemon.species for pokemon in json)
|
||||
{SpeciesData} = window.Generations[DEFAULT_GENERATION.toUpperCase()]
|
||||
pokemonSpecies = pokemonSpecies.filter((s) -> s not of SpeciesData)
|
||||
if pokemonSpecies.length > 0
|
||||
errors.push(pokemonSpecies.map((n) -> "#{n} is not a valid Pokemon.")...)
|
||||
return errors
|
||||
return errors
|
||||
|
||||
renderSaving: (team) =>
|
||||
$team = $(".select-team[data-cid='#{team.cid}']")
|
||||
$team.find('.show_spinner').removeClass('hidden')
|
||||
|
||||
renderSaved: (team) =>
|
||||
$team = $(".select-team[data-cid='#{team.cid}']")
|
||||
$team.find('.show_spinner').addClass('hidden')
|
||||
Reference in New Issue
Block a user