To edit the documentation or categories for this module, click here.
local util_args = require('Module:ArgsUtil')
local util_cargo = require('Module:CargoUtil')
local util_esports = require('Module:EsportsUtil')
local util_table = require('Module:TableUtil')
local util_text = require('Module:TextUtil')
local util_vars = require('Module:VarsUtil')
local bracket_wiki = require('Module:Bracket/Wiki') -- wiki localization per game
local m_team = require('Module:Team')
local ROWS_PER_TEAM = 6
local ROWS_PER_TITLE = 2
local ROWS_PER_HLINE = 1
local ROUNDWIDTH = 12
local LINEWIDTH = '3em'
local SCOREWIDTH = 2
local sep = '%s*,%s*'
local h = {}
function h.processArgs(tpl_args)
-- format tpl_args
local args = {}
for k, v in pairs(tpl_args) do
if type(k) ~= 'string' then
-- pass
elseif k:find('R%d+M%d+_.*_') then
local r, m, val, team = k:match('R(%d+)M(%d+)_(.*)_(%d+)')
r = tonumber(r)
m = tonumber(m)
h.initializeMatch(args, r, m)
if val == 'team' then
args[r][m]['team' .. team][val] = m_team.teamlinkname(v)
else
args[r][m]['team' .. team][val] = v
end
elseif k:find('R%d+M%d+_.*') then
local r, m, val = k:match('R(%d+)M(%d+)_(.*)')
r = tonumber(r)
m = tonumber(m)
h.initializeMatch(args, r, m)
args[r][m][val] = v
elseif k:find('R%d+_') then
local r, val = k:match('R(%d+)_(.*)')
r = tonumber(r)
h.initializeMatch(args, r)
args[r][val] = v
else
args[k] = v
end
end
return args
end
function h.initializeMatch(args, r, m)
if not args[r] then
args[r] = {}
end
if not args[r][m] and m then
args[r][m] = { team1 = {}, team2 = {} }
end
end
function h.processSettings(settings, args)
-- in theory this could be done in the settings module before returning but
-- this way the code is a bit more hidden from users editing stuff
-- and also this makes the settings module closer to a read-only table that you
-- import (and clone) here which i guess is nice?
-- tbh im not sure if this was the right way to do it tho
for r, col in ipairs(settings) do
local m = #col.matches
while m >= 1 do
-- need to iterate backwards bc we'll delete third-place matches if hidden
local match = col.matches[m]
local lines = col.lines and col.lines[m]
if lines and lines.reseed then
lines.class = lines.class:format(args.reseed or 'reseeding')
end
if match.argtoshow then
if not util_args.castAsBool(args[match.argtoshow]) then
if col.matches[m+1] then
col.matches[m+1].above = (col.matches[m+1].above or 0) + (match.above or 0) + 6
end
table.remove(col.matches,m)
end
end
m = m - 1
end
end
end
-- cargo
function h.addCargoData(args, settings)
local overviewPage = util_esports.getOverviewPage(args.page)
local data = h.doCargoQuery(overviewPage)
if not next(data) then
return
end
local processed = h.processCargoData(data)
h.addProcessedToArgs(args, settings, processed, overviewPage)
end
function h.doCargoQuery(page)
local query = {
tables = 'MatchSchedule',
fields = {
'Team1',
'Team2',
'Team1Final',
'Team2Final',
'Winner',
'FF',
'Team1Score',
'Team2Score',
'Tab',
'N_MatchInTab',
'UniqueMatch'
},
where = ('OverviewPage="%s"'):format(page),
types = {
-- keep winner as a string since that's what's expected from args sigh
Team1Score = 'number',
Team2Score = 'number',
FF = 'number'
}
}
return util_cargo.queryAndCast(query)
end
function h.processCargoData(data)
local processed = {}
for _, row in ipairs(data) do
h.sortFF(row)
processed[('%s_%s'):format(row.Tab,row.N_MatchInTab)] = {
winner = row.Winner,
team1 = { score = row.Team1Score, team = row.Team1, teamfinal = row.Team1Final },
team2 = { score = row.Team2Score, team = row.Team2, teamfinal = row.Team2Final },
}
end
return processed
end
function h.sortFF(row)
if row.FF == 1 then
row.Team1Score = 'FF'
row.Team2Score = 'W'
elseif row.FF == 2 then
row.Team1Score = 'W'
row.Team2Score = 'FF'
end
end
function h.addProcessedToArgs(args, settings, processed, overviewPage)
for r, col in ipairs(settings) do
h.initializeMatch(args, r)
local title = args[r] and args[r].title or col.matches.title or ''
for m, _ in ipairs(col.matches) do
h.initializeMatch(args, r, m)
local argmatch = args[r] and args[r][m]
if argmatch and argmatch.cargomatch then
h.addMatchCargoToMatch(argmatch, processed[argmatch.cargomatch])
else
local uniquematch = ('%s_%s'):format(
title,
m
)
if not argmatch then
h.initializeMatch(args, r, m)
argmatch = args[r][m]
end
h.addMatchCargoToMatch(argmatch, processed[uniquematch])
end
end
end
end
function h.addMatchCargoToMatch(argMatch, cargoDataMatch)
if not cargoDataMatch then
return
end
-- allow arg data to overwrite cargo data always if applicable
argMatch.winner = argMatch.winner or cargoDataMatch.winner
for _, team in ipairs({ 'team1', 'team2' }) do
for k, v in pairs(cargoDataMatch[team]) do
argMatch[team][k] = argMatch[team][k] or v
end
end
end
-- print
function h.makeOutput(args, settings)
local output = mw.html.create()
local bracketN = util_vars.setGlobalIndex('BracketToggler')
if settings.togglers then
local togglers = h.makeTogglerButtons(settings.togglers, bracketN)
local tblRound1 = output:tag('div')
:addClass(h.allToggleClass(bracketN))
:addClass(h.roundToggleClass(bracketN, 1))
h.printBracket(args, settings, tblRound1, togglers)
for i, toggle in ipairs(settings.togglers) do
h.fixColumnLabelsForToggle(settings, toggle.bracket, i)
table.remove(args, 1)
h.processSettings(toggle.bracket, args)
local tbl = output:tag('div')
:addClass(h.allToggleClass(bracketN))
:addClass(h.roundToggleClass(bracketN, i + 1))
:addClass('toggle-section-hidden')
table.remove(togglers, 1)
h.printBracket(args, toggle.bracket, tbl, togglers)
end
else
h.printBracket(args, settings, output:tag('div'), {})
end
return output
end
function h.allToggleClass(n, isAttr)
local dot = isAttr and '.' or ''
return ('%sbracket-toggle-allrounds-%s'):format(dot, n)
end
function h.roundToggleClass(n, i, isAttr)
local dot = isAttr and '.' or ''
return ('%sbracket-toggle-round-%s-%s'):format(dot, n, i)
end
function h.fixColumnLabelsForToggle(settings, bracket, i)
for k, col in ipairs(bracket) do
col.matches.title = settings[k + i].matches.title
end
end
function h.makeTogglerButtons(togglers, n)
local tbl = {}
tbl[1] = h.makeToggler(n, 1)
for i, _ in ipairs(togglers) do
if i == #togglers then
tbl[#tbl+1] = h.makeLastToggler(n)
else
-- first add 1 because we already did 1 from the default bracket
tbl[#tbl+1] = h.makeToggler(n, i + 1)
end
end
return tbl
end
function h.makeToggler(n, i)
local div = mw.html.create('div')
:addClass('bracket-toggler')
:wikitext('[')
div:tag('span')
:addClass('alwaysactive-toggler')
:attr('data-toggler-hide', h.allToggleClass(n, true))
:attr('data-toggler-show', h.roundToggleClass(n, i + 1, true))
:wikitext('x')
div:wikitext(']')
return div
end
function h.makeLastToggler(n)
local div = mw.html.create('div')
:addClass('bracket-toggler')
div:tag('span')
:addClass('alwaysactive-toggler')
:attr('data-toggler-hide', h.allToggleClass(n, true))
:attr('data-toggler-show', h.roundToggleClass(n, 1, true))
:wikitext('<<')
return div
end
function h.printBracket(args, settings, tbl, togglers)
tbl:addClass('bracket-grid')
:css({
['grid-template-columns'] = h.getGTC(settings, args),
['grid-template-rows'] = h.getGTR(settings, args.notitle)
})
for round, col in ipairs(settings) do
h.addLinesColumn(tbl, col.lines, round, not args.notitle)
h.addMatchesColumn(tbl, args, col.matches, round, not args.notitle, togglers[round])
end
return tbl
end
function h.getGTC(settings, args)
local scores = {}
for round, col in ipairs(settings) do
scores[round] = args[round] and tonumber(args[round].extendedscore or '') or col.extendedscore or 1
end
local firstcol = settings[1].lines and next(settings[1].lines)
local firstwidth = firstcol and LINEWIDTH or '0'
return h.getCustomGTC(scores, args.roundwidth, firstwidth)
end
function h.getCustomGTC(scores, roundwidth, firstwidth)
if roundwidth then
roundwidth = tonumber(roundwidth:gsub('em','') or '')
else
roundwidth = ROUNDWIDTH
end
for k, v in ipairs(scores) do
scores[k] = (SCOREWIDTH * (v - 1) + roundwidth) .. 'em'
end
return firstwidth .. ' ' .. table.concat(scores, ' 3em ')
end
function h.getGTR(settings, notitle)
local max = 0
for _, col in ipairs(settings) do
local total = 0
for _, match in ipairs(col.matches) do
total = total + (match.above or 0)
if match.display == 'match' then
total = total + ROWS_PER_TEAM
elseif match.display == 'hline' then
total = total + ROWS_PER_HLINE
end
end
if total > max then
max = total
end
end
if not notitle then max = max + ROWS_PER_TITLE end
return ('repeat(%s,1fr)'):format(max)
end
function h.addLinesColumn(tbl, lineData, r, addtitle)
local roundname = 'round' .. (r - 1)
if not lineData then
return
end
for m, row in ipairs(lineData) do
if m == 1 and addtitle then
h.addBracketLine(tbl, roundname, row, 2)
else
h.addBracketLine(tbl, roundname, row, 0)
end
end
return
end
function h.addBracketLine(tbl, roundname, linerow, extra)
if linerow.above + extra > 0 then
tbl:tag('div')
:addClass('bracket-line')
:addClass(roundname)
:cssText(('grid-row:span %s;'):format(linerow.above + extra))
end
tbl:tag('div')
:addClass('bracket-line')
:addClass(linerow.class)
:addClass(roundname)
:cssText(('grid-row:span %s;'):format(linerow.height))
return
end
function h.addMatchesColumn(tbl, args, data, r, addtitle, toggler)
local roundname = 'round' .. r
if addtitle then
local title = args[r] and args[r].title or data.title or ''
h.makeTitle(tbl, roundname, title, toggler)
end
for m, row in ipairs(data) do
local game = args[r] and args[r][m] or { team1 = {}, team2 = {} }
if row.above then
h.addSpacer(tbl, roundname, row.above)
end
if row.display == 'match' then
h.makeMatch(tbl, game, roundname, not args.nolabels and row.label, args.teamstyle)
elseif row.display == 'hline' then
h.makeHorizontalCell(tbl, roundname)
end
end
return
end
function h.makeTitle(tbl, roundname, text, toggler)
local outerdiv = tbl:tag('div')
:addClass('bracket-grid-header')
:addClass(roundname)
local innerdiv = outerdiv:tag('div')
:addClass('bracket-header-content')
:wikitext(text)
if toggler then
innerdiv:node(toggler)
end
end
function h.makeHorizontalCell(tbl, roundname)
tbl:tag('div')
:addClass('bracket-spacer')
:addClass('horizontal')
:addClass(roundname)
return
end
function h.makeMatch(tbl, game, roundname, label, teamstyle)
if game.label then label = game.label end
h.addSpacer(tbl, roundname, nil, label)
h.makeTeam(tbl, roundname, game, game.team1, '1', teamstyle)
h.makeTeam(tbl, roundname, game, game.team2, '2', teamstyle)
h.addSpacer(tbl, roundname)
return
end
function h.addSpacer(tbl, roundname, n, label)
local div = tbl:tag('div')
:addClass('bracket-spacer')
:addClass(roundname)
if label then
div:wikitext(label)
end
if n then
div:cssText(('grid-row:span %s;'):format(n))
end
return
end
function h.makeTeam(tbl, roundname, game, data, n, teamstyle)
local isWinner = game.winner == n
local isbye = util_args.castAsBool(data.bye)
local line = tbl:tag('div')
:addClass('bracket-team')
:addClass(roundname)
:addClass(game.class)
util_esports.addTeamHighlighter(line, data.teamfinal or data.playerlink or data.player or data.team)
if isWinner then
line:addClass('bracket-winner')
end
local team = line:tag('div')
:addClass('bracket-team-name')
if data.free then
team:wikitext(data.free)
elseif isbye then
team:wikitext('BYE')
line:addClass('bracket-bye')
else
bracket_wiki.teamDisplay(team, data, teamstyle)
end
h.makeScore(line, data.score, isbye, game.winners, n)
end
function h.makeScore(line, score, isbye, winners, n)
local tbl = util_text.split(tostring(score or ''),sep)
tbl_win = winners and util_text.split(winners, sep) or {}
for k, v in ipairs(tbl) do
local div = line:tag('div')
:addClass('bracket-team-points')
:wikitext(v or (isbye and '-') or '')
if tbl_win[k] == n then
div:addClass('bracket-score-winner')
elseif tbl_win[k] then
div:addClass('bracket-score-loser')
end
end
end
local p = {}
function p.main(frame)
local tpl_args = util_args.merge(true)
-- use require instead of loadData so that we can use next() and #
local settings = require('Module:Bracket/'.. tpl_args[1])
local args = h.processArgs(tpl_args)
h.processSettings(settings, args)
if util_args.castAsBool(args.cargo) then
h.addCargoData(args, settings)
end
return h.makeOutput(args, settings)
end
return p