Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create participant table Component #3346

Merged
merged 32 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
b7b613b
low hanging fruits
hjpalpha Oct 2, 2023
c6dc99c
Add input reading and storage
hjpalpha Oct 2, 2023
08d703b
commons display
hjpalpha Oct 2, 2023
052236c
manual faction count input reading (sc2)
hjpalpha Oct 2, 2023
d314291
add sc2 display
hjpalpha Oct 2, 2023
559ecec
Add note and dq support - move opp sorting into readSection
hjpalpha Oct 3, 2023
92ba384
adjust lpdb instead of only add extradata
hjpalpha Oct 3, 2023
27e7736
kick unused
hjpalpha Oct 3, 2023
ddf71c6
typos
hjpalpha Oct 3, 2023
a131583
add importFromMatchGroupSpec support
hjpalpha Oct 3, 2023
0b9a9eb
shut up linter
hjpalpha Oct 3, 2023
23ad73d
typo and shut up anno warnings
hjpalpha Oct 3, 2023
8c99ee5
typo
hjpalpha Oct 3, 2023
6cef5ab
another typo
hjpalpha Oct 3, 2023
fcfba4d
fix some issues
hjpalpha Oct 3, 2023
efc2d30
disable race icons for players in sc special version
hjpalpha Oct 3, 2023
2b8296b
adjust display
hjpalpha Oct 3, 2023
6418ebb
adjust default width
hjpalpha Oct 3, 2023
9a246a5
remove redundant
hjpalpha Oct 3, 2023
f48e766
fix count calculations (ignore dq players for them)
hjpalpha Oct 4, 2023
4ec362a
Merge branch 'main' into participantTable
hjpalpha Oct 4, 2023
f5104f8
return `self` instead of `ParticipantTable`
hjpalpha Oct 4, 2023
80a9ffa
improve starcraft custom
hjpalpha Oct 5, 2023
65cea7d
fix unknown column issues
hjpalpha Oct 5, 2023
cbc4ef4
Update components/participant_table/commons/participant_table_import.lua
hjpalpha Oct 5, 2023
566e9bf
Update components/participant_table/commons/starcraft_starcraft2/part…
hjpalpha Oct 5, 2023
71dbcd8
order params
hjpalpha Oct 5, 2023
276b926
Update components/participant_table/commons/starcraft_starcraft2/part…
hjpalpha Oct 5, 2023
b5ab90f
Update participant_table_import.lua
hjpalpha Oct 5, 2023
2352eee
enable storage on sc(2) without date condition
hjpalpha Oct 9, 2023
759962d
Update components/participant_table/commons/participant_table_base.lua
hjpalpha Oct 12, 2023
504372c
kick `/dev`
hjpalpha Oct 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
399 changes: 399 additions & 0 deletions components/participant_table/commons/participant_table_base.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,399 @@
---
-- @Liquipedia
-- wiki=commons
-- page=Module:ParticipantTable/Base
--
-- Please see https://github.com/Liquipedia/Lua-Modules to contribute
--

--can not name it `Module:ParticipantTable` due to that already existing on some wikis

local Arguments = require('Module:Arguments')
local Array = require('Module:Array')
local Class = require('Module:Class')
local DateExt = require('Module:Date/Ext')
local Json = require('Module:Json/dev')
local Logic = require('Module:Logic')
local Lua = require('Module:Lua')
local Namespace = require('Module:Namespace')
local PageVariableNamespace = require('Module:PageVariableNamespace')
local String = require('Module:StringUtils')
local Table = require('Module:Table')
local Template = require('Module:Template')
local Variables = require('Module:Variables')

local OpponentLibraries = require('Module:OpponentLibraries')
local Opponent = OpponentLibraries.Opponent
local OpponentDisplay = OpponentLibraries.OpponentDisplay

local Import = Lua.import('Module:ParticipantTable/Import', {requireDevIfEnabled = true})
local PlayerExt = Lua.import('Module:Player/Ext', {requireDevIfEnabled = true})
local TournamentStructure = Lua.import('Module:TournamentStructure', {requireDevIfEnabled = true})

local pageVars = PageVariableNamespace('ParticipantTable')

---@class ParticipantTable
---@operator call(Frame): ParticipantTable
---@field args table
---@field config ParticipantTableConfig
---@field display Html?
---@field sections ParticipantTableSection[]
local ParticipantTable = Class.new(
function(self, frame)
self.args = Arguments.getArgs(frame)
end)

---@param frame Frame
---@return Html?
function ParticipantTable.run(frame)
return ParticipantTable(frame):read():store():create()
end

---@return self
function ParticipantTable:read()
self.config = self.readConfig(self.args)
self:readSections()

return self
end

---@class ParticipantTableConfig
---@field lpdbPrefix string
---@field noStorage boolean
---@field matchGroupSpec table?
---@field syncPlayers boolean
---@field showCountBySection boolean
---@field onlyNotable boolean
---@field colSpan number
---@field resolveDate string
---@field sortPlayers boolean sort players within an opponent
---@field showTeams boolean
---@field title string
---@field columnWidth number? width of the column in px
---@field importOnlyQualified boolean?
---@field display boolean
---@field count number?
hjpalpha marked this conversation as resolved.
Show resolved Hide resolved

---@param args table
---@param parentConfig ParticipantTableConfig?
---@return ParticipantTableConfig
function ParticipantTable.readConfig(args, parentConfig)
parentConfig = parentConfig or {}
local config = {
lpdbPrefix = args.lpdbPrefix or parentConfig.lpdbPrefix or Variables.varDefault('lpdbPrefix') or '',
noStorage = Logic.readBool(args.noStorage or parentConfig.noStorage or
Variables.varDefault('disable_LPDB_storage') or not Namespace.isMain()),
matchGroupSpec = TournamentStructure.readMatchGroupsSpec(args),
syncPlayers = Logic.nilOr(Logic.readBoolOrNil(args.syncPlayers), parentConfig.syncPlayers, true),
showCountBySection = Logic.readBool(args.showCountBySection or parentConfig.showCountBySection),
count = tonumber(args.count),
colSpan = tonumber(args.colspan) or parentConfig.colSpan or 4,
onlyNotable = Logic.readBool(args.onlyNotable or parentConfig.onlyNotable),
resolveDate = args.date or parentConfig.resolveDate or DateExt.getContextualDate(),
sortPlayers = Logic.readBool(args.sortPlayers or parentConfig.sortPlayers),
showTeams = not Logic.readBool(args.disable_teams),
title = args.title,
importOnlyQualified = Logic.readBool(args.onlyQualified),
display = not Logic.readBool(args.hidden)
}

config.columnWidth = tonumber(args.entrywidth) or config.showTeams and 212 or 156

return config
end

function ParticipantTable:readSections()
self.sections = {}
Array.forEach(self:fetchSectionsArgs(), function(sectionArgs)
self:readSection(sectionArgs)
end)
end

---@return table[]
function ParticipantTable:fetchSectionsArgs()
local args = self.args

pageVars:set('stashArgs', '1')

-- make sure that all sections stashArgs
for _, potentialSection in pairs(args) do
ParticipantTable._stashArgs(potentialSection)
end

-- retrieve sectionsArgs
local sectionsArgs = Template.retrieveReturnValues('ParticipantTable')
pageVars:delete('stashArgs')

--case no sections: use whole table as first section
if Table.isEmpty(sectionsArgs) then
return {args}
end

return sectionsArgs
end

---access the args so it stashes
---@param potentialSection string
---@return string
function ParticipantTable._stashArgs(potentialSection)
return potentialSection
end


---@class ParticipantTableSection
---@field config ParticipantTableConfig
---@field entries ParticipantTableEntry

---@param args table
function ParticipantTable:readSection(args)
local config = self.readConfig(args, self.config)
local section = {config = config}

local keys = Array.filter(Array.extractKeys(args), function(key)
return String.contains(key, '^%d+$') or
String.contains(key, '^p%d+$') or
String.contains(key, '^player%d+$')
end)

local entriesByName = {}

--need custom sort so it doesn't compare number with string
local sortKeys = function(tbl, key1, key2) return tostring(key1) < tostring(key2) end

for _, key in Table.iter.spairs(keys, sortKeys) do
local entry = self:readEntry(args, key, config)
if entriesByName[entry.name] then
error('Duplicate Input "|' .. key .. '=' .. args[key] .. '"')
end

entriesByName[entry.name] = entry
end

section.entries = Array.map(Import.importFromMatchGroupSpec(config, entriesByName), function(entry)
entry.opponent = Opponent.resolve(entry.opponent, config.resolveDate, {syncPlayer = config.syncPlayers})
return entry
end)

Array.sortInPlaceBy(section.entries, function(entry) return entry.name:lower() end)

table.insert(self.sections, section)
end

---@class ParticipantTableEntry
---@field opponent standardOpponent
---@field name string
---@field note string?
---@field dq boolean

---@param sectionArgs table
---@param key string|number
---@param config any
---@return ParticipantTableEntry
function ParticipantTable:readEntry(sectionArgs, key, config)
--if not a json assume it is a solo opponent
local opponentArgs = Json.parseIfTable(sectionArgs[key]) or Logic.isNumeric(key) and {
type = Opponent.solo,
name = sectionArgs[key],
} or {
type = Opponent.solo,
name = sectionArgs[key],
link = sectionArgs[key .. 'link'],
flag = sectionArgs[key .. 'flag'],
team = sectionArgs[key .. 'team'],
}

assert(Opponent.isType(opponentArgs.type) and opponentArgs.type ~= Opponent.team,
'Missing or unsupported opponent type for "' .. sectionArgs[key] .. '"')

local opponent = Opponent.readOpponentArgs(opponentArgs)

if config.sortPlayers and opponent.players then
table.sort(opponent.players, function (player1, player2)
local name1 = (player1.displayName or player1.name):lower()
local name2 = (player2.displayName or player2.name):lower()
return name1 < name2
end)
end

return {
dq = Logic.readBool(opponentArgs.dq or sectionArgs[key .. 'dq']),
note = opponentArgs.note or sectionArgs[key .. 'note'],
opponent = opponent,
name = Opponent.toName(opponent),
}
end

---@return self
function ParticipantTable:store()
if self.config.noStorage then return self end

local lpdbTournamentData = {
tournament = Variables.varDefault('tournament_name'),
parent = Variables.varDefault('tournament_parent'),
series = Variables.varDefault('tournament_series'),
shortname = Variables.varDefault('tournament_tickername'),
hjpalpha marked this conversation as resolved.
Show resolved Hide resolved
startdate = Variables.varDefault('tournament_startdate'),
mode = Variables.varDefault('tournament_mode'),
type = Variables.varDefault('tournament_type'),
liquipediatier = Variables.varDefault('tournament_liquipediatier'),
liquipediatiertype = Variables.varDefault('tournament_liquipediatiertype'),
publishertier = Variables.varDefault('tournament_publishertier'),
icon = Variables.varDefault('tournament_icon'),
icondark = Variables.varDefault('tournament_icondark'),
game = Variables.varDefault('tournament_game'),
prizepoolindex = tonumber(Variables.varDefault('prizepool_index')) or 0,
}

local placements = self:getPlacements()

Array.forEach(self.sections, function(section) Array.forEach(section.entries, function(entry)
local lpdbData = Opponent.toLpdbStruct(entry.opponent)

if placements[lpdbData.opponentname] or section.config.noStorage or
Opponent.isTbd(entry.opponent) or Opponent.isEmpty(entry.opponent) then return end


lpdbData = Table.merge(lpdbTournamentData, lpdbData, {date = section.config.resolveDate, extradata = {}})

ParticipantTable:adjustLpdbData(lpdbData, entry, section.config)

mw.ext.LiquipediaDB.lpdb_placement(self:objectName(lpdbData), Json.stringifySubTables(lpdbData))
end) end)

return self
end

---Get placements already set on the page from prize pools
---@return table<string, true>
function ParticipantTable:getPlacements()
local placements = {}
Array.forEach(mw.ext.LiquipediaDB.lpdb('placement', {
limit = 5000,
conditions = '[[placement::!]] AND [[pagename::' .. string.gsub(mw.title.getCurrentTitle().text, ' ', '_') .. ']]',
}), function(placement) placements[placement.opponentname] = true end)

return placements
end

---@param lpdbData table
---@return string
function ParticipantTable:objectName(lpdbData)
return 'ranking_' .. self.config.lpdbPrefix .. '_' .. lpdbData.prizepoolindex .. '_' .. lpdbData.opponentname
end

---@param lpdbData table
---@param entry ParticipantTableEntry
---@param config ParticipantTableConfig
function ParticipantTable:adjustLpdbData(lpdbData, entry, config)
end

---@return Html?
function ParticipantTable:create()
local config = self.config

if not config.display then return end

self.display = mw.html.create('div')
:addClass('participantTable')
:css('max-width', '100%!important')
:css('width', (config.colSpan * config.columnWidth + 1) .. 'px')
:node(mw.html.create('div'):addClass('participantTable-title'):wikitext(config.title or 'Participants'))

Array.forEach(self.sections, function(section) self:displaySection(section) end)

return self.display
end

---@return Html
function ParticipantTable.newSectionNode()
return mw.html.create('div'):addClass('participantTable-row')
end

---@param section ParticipantTableSection
function ParticipantTable:displaySection(section)
local entries = section.config.onlyNotable and self.filterOnlyNotables(section.entries) or section.entries

local sectionEntryCount = #Array.filter(entries, function(entry) return not entry.dq end)

self.display:node(self.newSectionNode():node(self.sectionTitle(section, sectionEntryCount)))

if Table.isEmpty(section.entries) then
self.display:node(self.newSectionNode():node(self:tbd()))
return
end

local sectionNode = ParticipantTable.newSectionNode()

Array.forEach(entries, function(entry, entryIndex)
sectionNode:node(self:displayEntry(entry):css('width', self.config.columnWidth .. 'px'))
end)

local nextColumn = (#entries % self.config.colSpan) + 1

Array.forEach(Array.range(nextColumn, self.config.colSpan),
function() sectionNode:node(self:empty()) end)

self.display:node(sectionNode)
end

---@return Html
function ParticipantTable:tbd()
return mw.html.create('div')
:addClass('participantTable-tbd')
:wikitext('To be determined')
end

---@return Html
function ParticipantTable:empty()
return mw.html.create('div'):addClass('participantTable-entry participantTable-empty')
end

---@param section ParticipantTableSection
---@param amountOfEntries number
---@return Html?
function ParticipantTable.sectionTitle(section, amountOfEntries)
if Logic.isEmpty(section.config.title) then return end

local title = mw.html.create('div'):addClass('participantTable-title'):wikitext(section.config.title)

if not section.config.showCountBySection then return title end

return title:tag('i'):wikitext(' (' .. (section.config.count or amountOfEntries) .. ')'):done()
end

---@param entry ParticipantTableEntry
---@param additionalProps table?
---@return Html
function ParticipantTable:displayEntry(entry, additionalProps)
additionalProps = additionalProps or {}

return mw.html.create('div')
:addClass('participantTable-entry brkts-opponent-hover')
:attr('aria-label', entry.name)
:node(OpponentDisplay.BlockOpponent(Table.merge(additionalProps, {
dq = entry.dq,
note = entry.note,
showPlayerTeam = self.config.showTeams,
opponent = entry.opponent,
})))
end

---@param entries ParticipantTableEntry[]
---@return ParticipantTableEntry[]
function ParticipantTable.filterOnlyNotables(entries)
return Array.filter(entries, function(entry) return ParticipantTable.isNotable(entry) end)
end

---@param entry ParticipantTableEntry
---@return boolean
function ParticipantTable.isNotable(entry)
return Array.any(entry.opponent.players or {}, ParticipantTable.isNotablePlayer)
end

---@param player standardPlayer
---@return boolean
function ParticipantTable.isNotablePlayer(player)
return Logic.isNotEmpty(player.pageName) and PlayerExt.fetchPlayerFlag(player.pageName) ~= nil
end

return ParticipantTable
Loading
Loading