From 839a15c1a8768de6dcce0f4895413da8c116c8a9 Mon Sep 17 00:00:00 2001 From: SyntacticSalt Date: Sun, 1 Mar 2026 18:39:54 +0100 Subject: [PATCH 1/4] Add initial draft with storage+fetch --- lua/wikis/counterstrike/VRSStandings.lua | 215 +++++++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 lua/wikis/counterstrike/VRSStandings.lua diff --git a/lua/wikis/counterstrike/VRSStandings.lua b/lua/wikis/counterstrike/VRSStandings.lua new file mode 100644 index 00000000000..65fa13183ce --- /dev/null +++ b/lua/wikis/counterstrike/VRSStandings.lua @@ -0,0 +1,215 @@ +--- +-- @Liquipedia +-- page=Module:Widget/VRSStandings.lua +-- +-- Please see https://github.com/Liquipedia/Lua-Modules to contribute +-- + +local Lua = require('Module:Lua') + +local Array = Lua.import('Module:Array') +local Class = Lua.import('Module:Class') +local Date = Lua.import('Module:Date/Ext') +local FnUtil = Lua.import('Module:FnUtil') +local Json = Lua.import('Module:Json') +local Logic = Lua.import('Module:Logic') +local Lpdb = Lua.import('Module:Lpdb') +local MathUtil = Lua.import('Module:MathUtil') +local Operator = Lua.import('Module:Operator') +local Opponent = Lua.import('Module:Opponent') +local PlayerDisplay = Lua.import('Module:Player/Display') +local OpponentDisplay = Lua.import('Module:OpponentDisplay') +local Table = Lua.import('Module:Table') + +local TableWidgets = Lua.import('Module:Widget/Table2/All') +local Widget = Lua.import('Module:Widget') +local WidgetUtil = Lua.import('Module:Widget/Util') +local HtmlWidgets = Lua.import('Module:Widget/Html/All') + +local Condition = Lua.import('Module:Condition') +local BooleanOperator = Condition.BooleanOperator +local Comparator = Condition.Comparator + + +local DATAPOINT_TYPE = 'vrs_ranking' + +---@class VRSStandings: Widget +---@operator call(table): VRSStandings +---@field props table +local VRSStandings = Class.new(Widget) +VRSStandings.defaultProps = { + title = 'VRS Standings', +} + +---@return Widget? +function VRSStandings:render() + local standings, settings = self:_parse() + + local headerRow = TableWidgets.TableHeader{children = { + TableWidgets.Row{children = WidgetUtil.collect( + TableWidgets.CellHeader{children = {'Rank'}}, + TableWidgets.CellHeader{children = {'Points'}}, + TableWidgets.CellHeader{children = {HtmlWidgets.Span{children = 'Team', css = {['text-align'] = 'center', display = 'block'}}}}, + TableWidgets.CellHeader{children = {HtmlWidgets.Span{children = 'Roster', css = {['text-align'] = 'center', display = 'block'}}}} + )} + }} + + local title = HtmlWidgets.Div { + children = { + HtmlWidgets.Div { + children = { + HtmlWidgets.B { children = 'Unofficial VRS' }, + HtmlWidgets.Span { children = 'Last updated: ' .. settings.updated } + }, + classes = { 'ranking-table__top-row-text' } + }, + HtmlWidgets.Div { + children = { + HtmlWidgets.Span { children = 'Data by Liquipedia' }, + }, + classes = { 'ranking-table__top-row-logo-container' } + } + }, + classes = { 'ranking-table__top-row' }, + } + + return TableWidgets.Table{ + title = title, + sortable = false, + columns = WidgetUtil.collect( + { + align = 'center', + sortType = 'number' + }, + { + align = 'right', + sortType = 'number', + }, + { + align = 'left' + }, + { + align = 'left' + } + ), + children = { + headerRow, + TableWidgets.TableBody{children = Array.map(standings, VRSStandings._row)} + }, + } +end + +---@private +---@return {place: number, points: number, opponent: standardOpponent}[] +---@return {title: string, updated: string, shouldStore: boolean, shouldFetch: boolean} +function VRSStandings:_parse() + local props = self.props + local settings = { + title = props.title, + updated = Date.toYmdInUtc(Date.parseIsoDate(props.updated) or os.date('%F')), + shouldFetch = Logic.readBool(props.shouldFetch), + fetchLimit = tonumber(props.fetchLimit) + } + + ---@type {points: number, opponent: standardOpponent}[] + local standings = {} + + if settings.shouldFetch then + standings = self._fetch(settings.updated, settings.fetchLimit) + else + Table.iter.forEachPair(self.props, function(key, value) + if not string.match(key, '^%d+$') then + return + end + + local data = Json.parse(value) + local opponent = Opponent.readOpponentArgs(Table.merge(data, { + type = Opponent.team, + })) + + -- Remove template from data to not confuse it with first player + data[1] = nil + opponent.players = Array.map(Array.range(1, 5), FnUtil.curry(Opponent.readPlayerArgs, data)) + + table.insert(standings, { + place = tonumber(key), + points = tonumber(data.points), + opponent = opponent + }) + end) + + self._store(settings.updated, standings) + end + + Array.sortInPlaceBy(standings, Operator.property('place')) + + return standings, settings +end + +---@private +---@param standing {place: number, points: number, opponent: standardOpponent} +---@return Widget +function VRSStandings._row(standing) + return TableWidgets.Row{children = WidgetUtil.collect( + TableWidgets.Cell{ + children = standing.place, + }, + TableWidgets.Cell{ + children = MathUtil.formatRounded{value = standing.points, precision = 1} + }, + TableWidgets.Cell{ + children = OpponentDisplay.InlineTeamContainer{ + template = standing.opponent.template + } + }, + TableWidgets.Cell{ + children = Array.map(standing.opponent.players, function(player) + return HtmlWidgets.Span{ + css = { + display = "inline-block", + width = "160px" + }, + children = PlayerDisplay.InlinePlayer({player = player}) + } + end), + } + )} +end + +---@private +---@param updated string +---@param standings {place: number, points: number, opponent: standardOpponent}[] +function VRSStandings._store(updated, standings) + if Lpdb.isStorageDisabled() then + return + end + local dataPoint = Lpdb.DataPoint:new{ + objectname = 'vrs_' .. updated, + type = DATAPOINT_TYPE, + name = 'Inofficial VRS (' .. updated .. ')', + date = updated, + extradata = standings + } + dataPoint:save() +end + +---@private +---@param updated string +---@param fetchLimit integer +---@return {place: number, points: number, opponent: standardOpponent}[] +function VRSStandings._fetch(updated, fetchLimit) + local data = mw.ext.LiquipediaDB.lpdb('datapoint', { + conditions = Condition.Tree(BooleanOperator.all):add{ + Condition.Node(Condition.ColumnName('type'), Comparator.eq, DATAPOINT_TYPE), + Condition.Node(Condition.ColumnName('date'), Comparator.eq, updated), + Condition.Node(Condition.ColumnName('namespace'), Comparator.eq, 2), -- TODO: Remove before release + }:toString(), + query = 'extradata', + limit = 1, + }) + + assert(data[1], 'No VRS data found') + return Array.sub(data[1].extradata, 1, fetchLimit) +end + +return VRSStandings From b14ef39867d51022350e664e3dbeb8b6877b2b39 Mon Sep 17 00:00:00 2001 From: SyntacticSalt Date: Sun, 1 Mar 2026 19:21:22 +0100 Subject: [PATCH 2/4] Remove header css --- lua/wikis/counterstrike/VRSStandings.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lua/wikis/counterstrike/VRSStandings.lua b/lua/wikis/counterstrike/VRSStandings.lua index 65fa13183ce..dd46e3345b5 100644 --- a/lua/wikis/counterstrike/VRSStandings.lua +++ b/lua/wikis/counterstrike/VRSStandings.lua @@ -47,10 +47,10 @@ function VRSStandings:render() local headerRow = TableWidgets.TableHeader{children = { TableWidgets.Row{children = WidgetUtil.collect( - TableWidgets.CellHeader{children = {'Rank'}}, - TableWidgets.CellHeader{children = {'Points'}}, - TableWidgets.CellHeader{children = {HtmlWidgets.Span{children = 'Team', css = {['text-align'] = 'center', display = 'block'}}}}, - TableWidgets.CellHeader{children = {HtmlWidgets.Span{children = 'Roster', css = {['text-align'] = 'center', display = 'block'}}}} + TableWidgets.CellHeader{children = 'Rank'}, + TableWidgets.CellHeader{children = 'Points'}, + TableWidgets.CellHeader{children = 'Team'}, + TableWidgets.CellHeader{children = 'Roster'} )} }} From e10fa9fb1de9a0299914a9409130cac1d8c72240 Mon Sep 17 00:00:00 2001 From: SyntacticSalt Date: Sun, 1 Mar 2026 19:36:30 +0100 Subject: [PATCH 3/4] Rank alignment --- lua/wikis/counterstrike/VRSStandings.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/wikis/counterstrike/VRSStandings.lua b/lua/wikis/counterstrike/VRSStandings.lua index dd46e3345b5..5087722cab7 100644 --- a/lua/wikis/counterstrike/VRSStandings.lua +++ b/lua/wikis/counterstrike/VRSStandings.lua @@ -78,7 +78,7 @@ function VRSStandings:render() sortable = false, columns = WidgetUtil.collect( { - align = 'center', + align = 'right', sortType = 'number' }, { From ebd81338076149ea44534066d6fd3afad6dbc066 Mon Sep 17 00:00:00 2001 From: SyntacticSalt Date: Sun, 1 Mar 2026 19:36:50 +0100 Subject: [PATCH 4/4] tabs not spaces --- lua/wikis/counterstrike/VRSStandings.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lua/wikis/counterstrike/VRSStandings.lua b/lua/wikis/counterstrike/VRSStandings.lua index 5087722cab7..664e62c3352 100644 --- a/lua/wikis/counterstrike/VRSStandings.lua +++ b/lua/wikis/counterstrike/VRSStandings.lua @@ -79,18 +79,18 @@ function VRSStandings:render() columns = WidgetUtil.collect( { align = 'right', - sortType = 'number' + sortType = 'number' }, { align = 'right', sortType = 'number', }, - { - align = 'left' - }, - { - align = 'left' - } + { + align = 'left' + }, + { + align = 'left' + } ), children = { headerRow,