Merge pull request #88 from kotarou3/tournaments

Tournaments
This commit is contained in:
Guangcong Luo
2013-12-08 23:13:24 -08:00
6 changed files with 806 additions and 3 deletions

View File

@@ -103,6 +103,7 @@
<script src="//play.pokemonshowdown.com/js/lib/html-sanitizer-minified.js?"></script>
<script src="//play.pokemonshowdown.com/js/lib/lodash.compat.js?"></script>
<script src="//play.pokemonshowdown.com/js/lib/backbone.js?"></script>
<script src="//play.pokemonshowdown.com/js/lib/d3.v3.min.js"></script>
<script>
document.getElementById('loading-message').innerHTML += ' DONE<br />Loading data...';
@@ -124,6 +125,7 @@
<script src="//play.pokemonshowdown.com/js/client-teambuilder.js?"></script>
<script src="//play.pokemonshowdown.com/js/client-ladder.js?"></script>
<script src="//play.pokemonshowdown.com/js/client-chat.js?"></script>
<script src="//play.pokemonshowdown.com/js/client-chat-tournament.js"></script>
<script src="//play.pokemonshowdown.com/js/client-battle.js?"></script>
<script src="//play.pokemonshowdown.com/js/client-rooms.js?"></script>
<script src="//play.pokemonshowdown.com/js/storage.js?"></script>

View File

@@ -0,0 +1,574 @@
(function($) {
function arrayToPhrase(array, finalSeparator) {
if (array.length <= 1)
return array.join();
finalSeparator = finalSeparator || "and";
return array.slice(0, -1).join(", ") + " " + finalSeparator + " " + array.slice(-1)[0];
}
function clampPosition(element, position) {
var $element = $(element);
var elementWidth = $element.width();
var parentWidth = $element.parent().width();
if (parentWidth >= elementWidth)
position.left = parentWidth / 2 - elementWidth / 2;
else if (position.left < parentWidth - elementWidth)
position.left = parentWidth - elementWidth;
else if (position.left > 0)
position.left = 0;
var elementHeight = $element.height();
var parentHeight = $element.parent().height();
if (parentHeight >= elementHeight)
position.top = parentHeight / 2 - elementHeight / 2;
else if (position.top < parentHeight - elementHeight)
position.top = parentHeight - elementHeight;
else if (position.top > 0)
position.top = 0;
}
function makeDraggable(element, position) {
var $element = $(element);
position = position || {};
$element.css({
'user-select': 'none',
cursor: 'default',
position: 'absolute'
});
if (!('left' in position) || position.isDefault) {
position.left = $element.parent().width() / 2 - $element.width() / 2;
position.top = 0;
position.isDefault = true;
}
clampPosition(element, position);
$element.css({
left: position.left,
top: position.top
});
var isMouseDown = false;
var innerX = 0;
var innerY = 0;
$element.on('mousedown', function (e) {
innerX = e.pageX - this.offsetLeft;
innerY = e.pageY - this.offsetTop;
isMouseDown = true;
});
$(document).on('mousemove', function (e) {
if (isMouseDown) {
position.left = e.pageX - innerX;
position.top = e.pageY - innerY;
delete position.isDefault;
clampPosition(element, position);
$element.css({
left: position.left,
top: position.top
});
}
}).on('mouseup', function () {
isMouseDown = false;
});
}
var TournamentBox = this.TournamentBox = (function () {
function TournamentBox(room, $wrapper) {
this.room = room;
this.$wrapper = $wrapper;
$wrapper.html(
'<div class="tournament-title"><span class="tournament-format"></span> <span class="tournament-generator"></span> Tournament</div>' +
'<div class="tournament-box">' +
'<div class="tournament-bracket"></div>' +
'<div class="tournament-tools">' +
'<button class="button tournament-join">Join</button><button class="button tournament-leave">Leave</button>' +
'<div class="tournament-nomatches">There are currently no new matches available for you. Please wait for some other battles to end.</div>' +
'<div class="tournament-challenge">' +
'<span class="tournament-challenge-user"></span>' +
'<span class="tournament-challenge-team"></span>' +
'<button class="button tournament-challenge-challenge">Challenge</button>' +
'</div>' +
'<div class="tournament-challengeby"></div>' +
'<div class="tournament-challenging"></div>' +
'<div class="tournament-challenged">' +
'<div class="tournament-challenged-message"></div>' +
'<span class="tournament-challenge-team"></span>' +
'<button class="button tournament-challenge-accept">Accept</button>' +
'</div>' +
'</div>' +
'</div>');
this.$title = $wrapper.find('.tournament-title');
this.$format = $wrapper.find('.tournament-format');
this.$generator = $wrapper.find('.tournament-generator');
this.$box = $wrapper.find('.tournament-box');
this.$bracket = $wrapper.find('.tournament-bracket');
this.$tools = $wrapper.find('.tournament-tools');
this.$join = $wrapper.find('.tournament-join');
this.$leave = $wrapper.find('.tournament-leave');
this.$noMatches = $wrapper.find('.tournament-nomatches');
this.$challenge = $wrapper.find('.tournament-challenge');
this.$challengeUser = $wrapper.find('.tournament-challenge-user');
this.$challengeTeam = $wrapper.find('.tournament-challenge-team');
this.$challengeChallenge = $wrapper.find('.tournament-challenge-challenge');
this.$challengeBy = $wrapper.find('.tournament-challengeby');
this.$challenging = $wrapper.find('.tournament-challenging');
this.$challenged = $wrapper.find('.tournament-challenged');
this.$challengedMessage = $wrapper.find('.tournament-challenged-message');
this.$challengeAccept = $wrapper.find('.tournament-challenge-accept');
this.isActive = false;
this.info = null;
this.isJoined = false;
this.bracketData = null;
this.savedBracketPosition = {};
this.challenges = null;
this.challengeBys = null;
var self = this;
this.$title.on('click', function() {
self.toggleBoxVisibility();
});
this.$box.on('transitionend webkitTransitionEnd oTransitionEnd otransitionend', function() {
if (self.$box.hasClass('active'))
self.$box.css('transition', 'none');
if (!self.isActive)
self.$wrapper.removeClass('active');
});
this.$join.on('click', function() {
self.room.send('/tournament join');
});
this.$leave.on('click', function() {
self.room.send('/tournament leave');
});
this.$challengeChallenge.on('click', function() {
var team = Storage.teams[self.$challengeTeam.children().val()];
self.room.send('/utm ' + JSON.stringify(team ? team.team : null));
self.room.send('/tournament challenge ' + self.$challengeUser.children().val());
});
this.$challengeAccept.on('click', function() {
var team = Storage.teams[self.$challengeTeam.children().val()];
self.room.send('/utm ' + JSON.stringify(team ? team.team : null));
self.room.send('/tournament acceptchallenge');
});
}
TournamentBox.prototype.updateLayout = function () {
this.$box.css('max-height', this.$box.hasClass('active') ? this.$box[0].scrollHeight: '');
if (this.$bracket.hasClass('tournament-bracket-overflowing')) {
clampPosition(this.$bracket.children(), this.savedBracketPosition);
this.$bracket.children().css({
left: this.savedBracketPosition.left,
top: this.savedBracketPosition.top
});
} else {
if (this.$bracket[0].offsetHeight < this.$bracket[0].scrollHeight ||
this.$bracket[0].offsetWidth < this.$bracket[0].scrollWidth) {
this.$bracket.addClass('tournament-bracket-overflowing');
makeDraggable(this.$bracket.children().first(), this.savedBracketPosition);
}
}
};
TournamentBox.prototype.setBoxVisibility = function (isVisible) {
if (isVisible) {
if (this.$box.hasClass('active'))
return;
this.$box.addClass('active');
} else {
if (!this.$box.hasClass('active'))
return;
this.$box.removeClass('active');
}
this.$box.css('transition', '');
this.$box.css('max-height', this.$box.hasClass('active') ? this.$box[0].scrollHeight: '');
};
TournamentBox.prototype.toggleBoxVisibility = function () {
this.setBoxVisibility(!this.$box.hasClass('active'));
};
TournamentBox.prototype.parseMessage = function (data, isBroadcast) {
if (Config.server.id !== "battletower") return true;
if (isBroadcast) {
// TODO
return true;
} else {
var cmd = data.shift().toLowerCase();
if (!this.isActive && cmd !== 'end') {
this.$wrapper.addClass("active");
this.$box.addClass("active");
this.isActive = true;
}
switch (cmd) {
case 'create':
var format = BattleFormats[data[0]].name;
var type = data[1];
this.room.$chat.append("<div class=\"notice tournament-message-create\">A " + format + " " + Tools.escapeHTML(type) + " Tournament has been created.</div>");
this.room.notifyOnce("Tournament created", "Room: " + this.room.title + "\nFormat: " + format + "\nType: " + type, 'tournament-create');
break;
case 'join':
this.room.$chat.append("<div class=\"notice tournament-message-join\">" + Tools.escapeHTML(data[0]) + " has joined the tournament</div>");
break;
case 'leave':
this.room.$chat.append("<div class=\"notice tournament-message-leave\">" + Tools.escapeHTML(data[0]) + " has left the tournament</div>");
break;
case 'start':
this.room.$chat.append("<div class=\"notice tournament-message-start\">The tournament has started!</div>");
break;
case 'disqualify':
this.room.$chat.append("<div class=\"notice tournament-message-disqualify\">" + Tools.escapeHTML(data[0]) + " has been disqualified from the tournament</div>");
break;
case 'update':
this.$bracket.removeClass('tournament-bracket-overflowing');
this.$tools.find('.active').andSelf().removeClass('active');
if (this.info && this.info.isStarted)
this.$noMatches.addClass('active');
this.isJoined = false;
this.challenges = null;
this.challengeBys = null;
break;
case 'info':
this.info = JSON.parse(data.join('|'));
this.$format.text(BattleFormats[this.info.format].name);
this.$generator.text(this.info.generator);
break;
case 'isjoined':
this.isJoined = true;
break;
case 'bracketdata':
// The 'isjoined' packet isn't a guaranteed packet to all users, so that packet's
// handling is placed in the immediate next guaranteed packet, here
if (!this.info.isStarted)
if (this.isJoined)
this.$leave.addClass('active');
else
this.$join.addClass('active');
if (!this.info.isStarted || this.isJoined)
this.$tools.addClass('active');
this.bracketData = JSON.parse(data.join('|'));
this.$bracket.empty()
var $bracket = this.generateBracket(this.bracketData);
if ($bracket) {
this.$bracket.append($bracket);
this.updateLayout();
}
break;
case 'challenges':
this.$noMatches.removeClass('active');
this.challenges = data[0].split(',');
this.$challengeUser.html(this.renderChallengeUsers());
this.$challengeTeam.html(app.rooms[''].renderTeams(this.info.format));
this.$challengeTeam.children().data('type', 'challengeTeam');
this.$challengeTeam.children().attr('name', 'tournamentButton');
this.$challenge.addClass("active");
this.setBoxVisibility(true);
this.room.notifyOnce("Tournament challenges available", "Room: " + this.room.title, 'tournament-challenges');
break;
case 'challengebys':
this.$noMatches.removeClass('active');
this.challengeBys = data[0].split(',');
this.$challengeBy.text((this.challenges ? "Or" : "Please") + " wait for " + arrayToPhrase(this.challengeBys, "or") + " to challenge you.");
this.$challengeBy.addClass("active");
break;
case 'challenging':
this.$noMatches.removeClass('active');
this.$challenging.text("Challenging " + data[0] + "...").addClass("active");
break;
case 'challenged':
this.$noMatches.removeClass('active');
this.$challengedMessage.text(data[0] + " has challenged you.");
this.$challengeTeam.html(app.rooms[''].renderTeams(this.info.format));
this.$challengeTeam.children().data('type', 'challengeTeam');
this.$challengeTeam.children().attr('name', 'tournamentButton');
this.$challenged.addClass("active");
this.setBoxVisibility(true);
this.room.notifyOnce("Tournament challenge from " + data[0], "Room: " + this.room.title, 'tournament-challenged');
break;
case 'battlestart':
this.room.$chat.append('<div class="notice tournament-message-battlestart"><a href="' + app.root + toRoomid(data[2]).toLowerCase() + '" class="ilink">' +
"A tournament battle between " + Tools.escapeHTML(data[0]) + " and " + Tools.escapeHTML(data[1]) + " has started." +
'</a></div>');
break;
case 'battleend':
var result = "drawn";
if (data[2] === 'win')
result = "won";
else if (data[2] === 'loss')
result = "lost";
this.room.$chat.append('<div class="notice tournament-message-battleend">' +
Tools.escapeHTML(data[0]) + " has " + result + " the match " + Tools.escapeHTML(data[3].split(',').join(' - ')) + " against " + Tools.escapeHTML(data[1]) +
(data[4] ? " but the tournament does not support drawing, so it did not count" : "") +
'</div>');
break;
case 'end':
var endData = JSON.parse(data[0]);
var $bracket = this.generateBracket(endData.bracketData);
if ($bracket) {
var $bracketMessage = $('<div class="notice tournament-message-end-bracket"></div>').append($bracket);
this.room.$chat.append($bracketMessage);
if ($bracketMessage[0].offsetHeight < $bracketMessage[0].scrollHeight ||
$bracketMessage[0].offsetWidth < $bracketMessage[0].scrollWidth) {
$bracketMessage.addClass('tournament-message-end-bracket-overflowing');
makeDraggable($bracket);
}
}
this.room.$chat.append("<div class=\"notice tournament-message-end-winner\">Congratulations to " + Tools.escapeHTML(arrayToPhrase(endData.results[0])) + " for winning the tournament!</div>");
if (endData.results[1])
this.room.$chat.append("<div class=\"notice tournament-message-end-runnerup\">Runner-up" + (endData.results[1].length > 1 ? "s" : "") +": " + Tools.escapeHTML(arrayToPhrase(endData.results[1])) + "</div>");
// Fallthrough
case 'forceend':
this.isActive = false;
this.info = null;
this.bracketData = null;
this.savedBracketPosition = {};
this.$box.removeClass("active");
this.$box.css('transition', '');
if (cmd === 'forceend')
this.room.$chat.append("<div class=\"notice tournament-message-forceend\">The tournament was forcibly ended.</div>");
break;
default:
return true;
}
this.$box.css('max-height', this.$box.hasClass('active') ? this.$box[0].scrollHeight : '');
}
};
TournamentBox.prototype.generateBracket = function (data) {
if (data.type === 'tree') {
if (!data.rootNode)
return;
var nodeSize = {
width: 150, height: 20,
radius: 5,
separationX: 30, separationY: 15
};
var $div = $('<div class="tournament-bracket-tree"></div>');
var nodesByDepth = [];
var stack = [{node: data.rootNode, depth: 0}];
while (stack.length > 0) {
var frame = stack.pop();
if (!nodesByDepth[frame.depth])
nodesByDepth.push(0);
++nodesByDepth[frame.depth];
frame.node.children.forEach(function (child) {
stack.push({node: child, depth: frame.depth + 1});
});
}
var maxDepth = nodesByDepth.length;
var maxWidth = 0;
nodesByDepth.forEach(function (nodes) {
if (nodes > maxWidth)
maxWidth = nodes;
});
nodeSize.realWidth = nodeSize.width + nodeSize.radius * 2;
nodeSize.realHeight = nodeSize.height + nodeSize.radius * 2;
nodeSize.smallRealHeight = nodeSize.height / 2 + nodeSize.radius * 2;
var size = {
width: nodeSize.realWidth * maxDepth + nodeSize.separationX * maxDepth,
height: nodeSize.realHeight * (maxWidth + 0.5) + nodeSize.separationY * maxWidth
};
var tree = d3.layout.tree()
.size([size.height, size.width - nodeSize.realWidth - nodeSize.separationX])
.separation(function () { return 1; })
.children(function (node) {
return node.children.length === 0 ? null : node.children;
});
var nodes = tree.nodes(data.rootNode);
var links = tree.links(nodes);
var layoutRoot = d3.select($div[0])
.append('svg:svg').attr('width', size.width).attr('height', size.height)
.append('svg:g')
.attr('transform', 'translate(' + (-(nodeSize.realWidth + nodeSize.separationX) / 2) + ',0)');
var link = d3.svg.diagonal()
.source(function (link) {
return {x: link.source.x, y: link.source.y + nodeSize.realWidth / 2};
})
.target(function (link) {
return {x: link.target.x, y: link.target.y - nodeSize.realWidth / 2};
})
.projection(function (link) {
return [size.width - link.y, link.x];
});
layoutRoot.selectAll('path.tournament-bracket-tree-link').data(links).enter()
.append('svg:path')
.attr('d', link)
.classed('tournament-bracket-tree-link', true)
.classed('tournament-bracket-tree-link-active', function (link) {
return link.source.state === 'finished' && link.source.team === link.target.team;
});
var nodeGroup = layoutRoot.selectAll('g.tournament-bracket-tree-node').data(nodes).enter()
.append('svg:g').classed('tournament-bracket-tree-node', true).attr('transform', function (node) {
return 'translate(' + (size.width - node.y) + ',' + node.x + ')';
});
nodeGroup.append('svg:rect')
.attr('rx', nodeSize.radius)
.attr('x', -nodeSize.realWidth / 2).attr('width', nodeSize.realWidth)
.each(function (node) {
var elem = d3.select(this);
if (node.children.length === 0)
elem.attr('y', -nodeSize.smallRealHeight / 2).attr('height', nodeSize.smallRealHeight);
else
elem.attr('y', -nodeSize.realHeight / 2).attr('height', nodeSize.realHeight);
});
nodeGroup.each(function (node) {
var elem = d3.select(this);
if (node.children.length === 0) {
elem.classed('tournament-bracket-tree-node-team', true);
elem.append('svg:text').text(node.team || "Unavailable");
} else {
elem.classed('tournament-bracket-tree-node-match', true);
elem.classed('tournament-bracket-tree-node-match-' + node.state, true);
if (node.state === 'unavailable')
elem.append('svg:text').text("Unavailable");
else {
var teams = elem.append('svg:text').attr('y', -nodeSize.realHeight / 5).classed('tournament-bracket-tree-node-match-teams', true);
var teamA = teams.append('svg:tspan').classed('tournament-bracket-tree-node-match-team', true).text(node.children[0].team);
teams.append('svg:tspan').text(" vs ");
var teamB = teams.append('svg:tspan').classed('tournament-bracket-tree-node-match-team', true).text(node.children[1].team);
var score = elem.append('svg:text').attr('y', nodeSize.realHeight / 5);
if (node.state === 'available')
score.text("Waiting");
else if (node.state === 'inprogress')
score.append('svg:a').attr('xlink:href', app.root + toRoomid(node.room).toLowerCase()).classed('ilink', true).text("In-progress");
else if (node.state === 'finished') {
if (node.result === 'win') {
teamA.classed('tournament-bracket-tree-node-match-team-win', true);
teamB.classed('tournament-bracket-tree-node-match-team-loss', true);
} else if (node.result === 'loss') {
teamA.classed('tournament-bracket-tree-node-match-team-loss', true);
teamB.classed('tournament-bracket-tree-node-match-team-win', true);
} else {
teamA.classed('tournament-bracket-tree-node-match-team-draw', true);
teamB.classed('tournament-bracket-tree-node-match-team-draw', true);
}
elem.classed('tournament-bracket-tree-node-match-result-' + node.result, true);
score.text(node.score.join(" - "));
}
}
}
if (node.parent && node.parent.state === 'finished')
if (node.parent.result === 'draw')
elem.classed('tournament-bracket-tree-node-draw', true);
else if (node.team === node.parent.team)
elem.classed('tournament-bracket-tree-node-win', true);
else
elem.classed('tournament-bracket-tree-node-loss', true);
});
return $div;
} else if (data.type === 'table') {
if (data.tableContents.length === 0)
return;
var $table = $('<table class="tournament-bracket-table"></table>');
var $colHeaders = $('<tr><td class="empty"></td></tr>');
$table.append($colHeaders);
data.tableHeaders.cols.forEach(function (name) {
$colHeaders.append($('<th></th>').text(name));
});
data.tableHeaders.rows.forEach(function (name, r) {
var $row = $('<tr></tr>');
$table.append($row);
$row.append($('<th></th>').text(name));
data.tableContents[r].forEach(function (cell) {
var $cell = $('<td></td>');
$row.append($cell);
if (!cell) {
$cell.addClass('tournament-bracket-table-cell-null');
return;
}
$cell.addClass('tournament-bracket-table-cell-' + cell.state);
if (cell.state === 'unavailable')
$cell.text("Unavailable");
else if (cell.state === 'available')
$cell.text("Waiting");
else if (cell.state === "inprogress")
$cell.html('<a href="' + app.root + toRoomid(cell.room).toLowerCase() + '" class="ilink">In-progress</a>');
else if (cell.state === 'finished') {
$cell.addClass('tournament-bracket-table-cell-result-' + cell.result);
$cell.text(cell.score.join(" - "));
}
});
$row.append($('<th class="tournament-bracket-row-score"></th>').text(data.scores[r]));
});
return $table;
}
},
TournamentBox.prototype.renderChallengeUsers = function () {
return '<button class="select" value="' + toId(this.challenges[0]) + '" name="tournamentButton" data-type="challengeUser">' + Tools.escapeHTML(this.challenges[0]) + '</button>';
};
TournamentBox.prototype.challengeUser = function (user, button) {
app.addPopup(UserPopup, {user: user, users: this.challenges, sourceEl: button});
};
TournamentBox.prototype.challengeTeam = function (team, button) {
app.addPopup(TeamPopup, {team: team, format: this.info.format, sourceEl: button});
};
return TournamentBox;
})();
var UserPopup = this.Popup.extend({
initialize: function (data) {
this.$el.html('<ul class="popupmenu">' + data.users.map(function (user) {
return '<li><button name="selectUser" value="' + Tools.escapeQuotes(user) + '"' + (user === data.user ? ' class="sel"' : '') + '>' + Tools.escapeHTML(user) + '</button></li>';
}).join('') + '</ul>');
},
selectUser: function (user) {
this.sourceEl.val(toId(user)).text(user);
this.close();
}
});
}).call(this, jQuery);

View File

@@ -394,7 +394,7 @@
switch (cmd.toLowerCase()) {
case 'challenge':
var targets = target.split(',').map($.trim);
if (!targets[0]) targets[0] = prompt('Who?');
target = toId(targets[0]);
this.challengeData = { userid: target, format: targets[1] || '', team: targets[2] || '' };
@@ -635,7 +635,7 @@
maxWidth: 1024,
isSideRoom: true,
initialize: function() {
var buf = '<ul class="userlist" style="display:none"></ul><div class="chat-log"><div class="inner"></div></div></div><div class="chat-log-add">Connecting...</div>';
var buf = '<ul class="userlist" style="display:none"></ul><div class="tournament-wrapper"></div><div class="chat-log"><div class="inner"></div></div></div><div class="chat-log-add">Connecting...</div>';
this.$el.addClass('ps-room-light').html(buf);
this.$chatAdd = this.$('.chat-log-add');
@@ -643,6 +643,9 @@
this.$chat = this.$('.inner');
this.$chatbox = null;
this.$tournamentWrapper = this.$('.tournament-wrapper');
this.tournamentBox = null;
this.users = {};
this.userCount = {};
@@ -663,12 +666,15 @@
this.$userList.show();
this.$chatFrame.addClass('hasuserlist');
this.$chatAdd.addClass('hasuserlist');
this.$tournamentWrapper.addClass('hasuserlist');
} else {
this.$userList.hide();
this.$chatFrame.removeClass('hasuserlist');
this.$chatAdd.removeClass('hasuserlist');
this.$tournamentWrapper.removeClass('hasuserlist');
}
this.$chatFrame.scrollTop(this.$chat.height());
if (this.tournamentBox) this.tournamentBox.updateLayout();
},
show: function() {
Room.prototype.show.apply(this, arguments);
@@ -818,6 +824,13 @@
});
break;
case 'tournament':
case 'tournaments':
if (!this.tournamentBox) this.tournamentBox = new TournamentBox(this, this.$tournamentWrapper);
if (!this.tournamentBox.parseMessage(row.slice(1), row[0] === 'tournaments'))
break;
// fallthrough in case of unparsed message
case '':
this.$chat.append('<div class="notice">' + Tools.escapeHTML(row.slice(1).join('|')) + '</div>');
break;
@@ -828,6 +841,9 @@
}
}
},
tournamentButton: function(val, button) {
if (this.tournamentBox) this.tournamentBox[$(button).data('type')](val, button);
},
parseUserList: function(userList) {
this.userCount = {};
this.users = {};

33
js/lib/d3.v3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -963,6 +963,181 @@ a.ilink:hover {
font-family: Verdana, Helvetica, Arial, sans-serif;
}
/* Tournaments */
.tournament-wrapper {
display: none;
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2em;
line-height: 2em;
text-align: center;
border-bottom: 1px #aaa solid;
}
.tournament-wrapper.active {
display: block;
}
.tournament-wrapper.active + .chat-log {
top: 2.4em;
}
.tournament-title {
font-weight: bold;
cursor: pointer;
}
.tournament-box {
position: absolute;
top: 2em;
left: 0;
right: 0;
z-index: 1;
line-height: 1em;
border-bottom: 1px #aaa solid;
background-color: rgba(242, 247, 250, 0.85);
max-height: 0;
overflow: hidden;
transition: max-height 0.5s ease-in-out;
-o-transition: max-height 0.5s ease-in-out;
-moz-transition: max-height 0.5s ease-in-out;
-webkit-transition: max-height 0.5s ease-in-out;
}
.tournament-bracket {
max-height: 200px;
padding: 10px;
overflow: hidden;
font-size: 8pt;
}
.tournament-bracket-overflowing {
height: 200px;
padding: 0;
position: relative;
left: 0;
top: 0;
}
.tournament-bracket-table {
margin: 0 auto;
empty-cells: hide;
border-bottom: 1px #aaa solid;
border-collapse: collapse;
}
.tournament-bracket-table td, .tournament-bracket-table th {
border: 1px #aaa solid;
padding: 5px;
}
.tournament-bracket-table td {
white-space: nowrap;
}
.tournament-bracket-table .empty,
.tournament-bracket-table .tournament-bracket-table-cell-null {
border-width: 0;
}
.tournament-bracket-table-cell-result-win {
background-color: rgba(0, 255, 0, 0.2)
}
.tournament-bracket-table-cell-result-draw {
background-color: rgba(255, 255, 0, 0.2)
}
.tournament-bracket-table-cell-result-loss {
background-color: rgba(255, 0, 0, 0.2)
}
.tournament-bracket-tree-link {
fill: none;
stroke: #aaa;
}
.tournament-bracket-tree-link-active {
stroke: #aa0;
stroke-width: 3px;
}
.tournament-bracket-tree-node > rect {
/* Note: Due to SVG limitations, dimensions and border radii are in client-tournament.js (nodeSize) */
fill: rgba(225, 232, 242, 0.7);
stroke: rgba(178, 183, 191, 0.7);
}
.tournament-bracket-tree-node-win > rect {
fill: rgba(0, 255, 0, 0.2);
stroke: rgba(0, 255, 0, 0.5);
}
.tournament-bracket-tree-node-draw > rect {
fill: rgba(255, 255, 0, 0.2);
stroke: rgba(255, 255, 0, 0.5);
}
.tournament-bracket-tree-node-loss > rect {
fill: rgba(255, 0, 0, 0.2);
stroke: rgba(255, 0, 0, 0.5);
}
.tournament-bracket-tree-node > text {
font-size: 0.8em;
text-anchor: middle;
dominant-baseline: central;
}
.tournament-bracket-tree-node > text > a {
fill: #248;
}
.tournament-bracket-tree-node > text > a:hover {
fill: #68c;
}
.tournament-bracket-tree-node-match-team {
font-weight: bold;
}
.tournament-bracket-tree-node-match-team-draw, .tournament-bracket-tree-node-match-team-loss {
fill: #aaa;
}
.tournament-tools {
padding: 10px;
display: none;
border-top: 1px #aaa solid;
}
.tournament-tools.active {
display: block;
}
.tournament-join,
.tournament-leave {
display: none;
}
.tournament-join.active,
.tournament-leave.active {
display: inline;
}
.tournament-nomatches,
.tournament-challenge,
.tournament-challengeby,
.tournament-challenging,
.tournament-challenged {
display: none;
}
.tournament-nomatches.active,
.tournament-challenge.active,
.tournament-challengeby.active,
.tournament-challenging.active,
.tournament-challenged.active {
display: block;
}
div[class^='tournament-message-'], div[class*=' tournament-message-'] {
font-weight: bold;
}
.tournament-message-end-bracket {
font-weight: normal !important;
max-height: 400px;
overflow: hidden;
text-align: center;
}
.tournament-message-end-bracket-overflowing {
height: 400px;
position: relative;
left: 0;
top: 0;
}
/* Messages */
.message-announce {
@@ -1030,7 +1205,8 @@ a.ilink:hover {
overflow-scrolling: touch;
}
.chat-log.hasuserlist,
.chat-log-add.hasuserlist {
.chat-log-add.hasuserlist,
.tournament-wrapper.hasuserlist {
left: 146px;
}
.userlist,

View File

@@ -86,6 +86,7 @@
<script src="js/lib/html-sanitizer-minified.js"></script>
<script src="js/lib/lodash.compat.js"></script>
<script src="js/lib/backbone.js"></script>
<script src="js/lib/d3.v3.min.js"></script>
<script>
document.getElementById('loading-message').innerHTML += ' DONE<br />Loading data...';
@@ -107,6 +108,7 @@
<script src="js/client-teambuilder.js"></script>
<script src="js/client-ladder.js"></script>
<script src="js/client-chat.js"></script>
<script src="js/client-chat-tournament.js"></script>
<script src="js/client-battle.js"></script>
<script src="js/client-rooms.js"></script>
<script src="js/storage.js"></script>