diff --git a/lua/wikis/commons/Standings/Table.lua b/lua/wikis/commons/Standings/Table.lua index d02b9a366b9..d23c6f11285 100644 --- a/lua/wikis/commons/Standings/Table.lua +++ b/lua/wikis/commons/Standings/Table.lua @@ -37,7 +37,7 @@ local StandingsTable = {} ---@field startingPoints number? ---@param frame Frame ----@return Widget +---@return Renderable function StandingsTable.fromTemplate(frame) local args = Arguments.getArgs(frame) local tableType = args.tabletype diff --git a/lua/wikis/commons/Widget/Standings/Ffa.lua b/lua/wikis/commons/Widget/Standings/Ffa.lua index 782388c8860..ef019ad995b 100644 --- a/lua/wikis/commons/Widget/Standings/Ffa.lua +++ b/lua/wikis/commons/Widget/Standings/Ffa.lua @@ -8,13 +8,12 @@ local Lua = require('Module:Lua') local Array = Lua.import('Module:Array') -local Class = Lua.import('Module:Class') local Logic = Lua.import('Module:Logic') local String = Lua.import('Module:StringUtils') local WidgetUtil = Lua.import('Module:Widget/Util') -local Widget = Lua.import('Module:Widget') -local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local Component = Lua.import('Module:Widget/Component') +local Html = Lua.import('Module:Widget/Html') local Label = Lua.import('Module:Widget/Basic/Label') local RoundSelector = Lua.import('Module:Widget/Standings/RoundSelector') local PlacementChange = Lua.import('Module:Widget/Standings/PlacementChange') @@ -29,21 +28,16 @@ local STATUS_TO_DISPLAY = { nc = '-', } ----@class StandingsFfaWidgetProps ----@field standings StandingsModel +local Helpers = {} ----@class StandingsFfaWidget: Widget ----@operator call(StandingsFfaWidgetProps): StandingsFfaWidget ----@field props StandingsFfaWidgetProps -local StandingsFfaWidget = Class.new(Widget) - ----@return Widget? -function StandingsFfaWidget:render() - if not self.props.standings then +---@param props {standings?: StandingsModel} +---@return Renderable? +local function StandingsFfa(props) + local standings = props.standings + if not standings then return end - local standings = self.props.standings local activeRounds = (Array.maxBy( Array.filter(standings.rounds, function(round) return round.started end), function (round) return round.round end @@ -53,7 +47,7 @@ function StandingsFfaWidget:render() classes = {'standings-ffa'}, columns = WidgetUtil.collect( {align = 'center'}, - self:_showRoundColumns() and {align = 'center'} or nil, + Helpers._showRoundColumns(standings) and {align = 'center'} or nil, {align = 'left'}, Array.map(standings.tiebreakers, function(tiebreaker) if not tiebreaker.title then @@ -61,16 +55,16 @@ function StandingsFfaWidget:render() end return {align = 'center'} end), - self:_showRoundColumns() and Array.rep({align = 'center'}, #standings.rounds) or nil + Helpers._showRoundColumns(standings) and Array.rep({align = 'center'}, #standings.rounds) or nil ), title = String.nilIfEmpty(standings.title), children = WidgetUtil.collect( - self:_headerRow(), + Helpers._headerRow(standings), Array.map(standings.rounds, function (round) if round.round > activeRounds then return end - return self:_createRoundBody(round) + return Helpers._createRoundBody(standings, round) end) ), striped = false @@ -80,13 +74,13 @@ function StandingsFfaWidget:render() return standingsTable end - local hasFutureRounds = self:_hasFutureRounds() + local hasFutureRounds = Helpers._hasFutureRounds(standings) - return HtmlWidgets.Div{ + return Html.Div{ classes = {'standings-ffa-wrapper', 'toggle-area', 'toggle-area-' .. activeRounds}, attributes = {['data-toggle-area'] = activeRounds}, children = WidgetUtil.collect( - activeRounds > 0 and HtmlWidgets.Div{ + activeRounds > 0 and Html.Div{ classes = {'standings-ffa-controls'}, children = { RoundSelector{ @@ -105,26 +99,25 @@ function StandingsFfaWidget:render() end ---@private +---@param standings StandingsModel ---@return boolean -function StandingsFfaWidget:_hasFutureRounds() - local standings = self.props.standings +function Helpers._hasFutureRounds(standings) return not standings.rounds[#standings.rounds].started end ---@private +---@param standings StandingsModel ---@return boolean -function StandingsFfaWidget:_showRoundColumns() - local standings = self.props.standings +function Helpers._showRoundColumns(standings) return #standings.rounds > 1 end ---@private ----@return Widget -function StandingsFfaWidget:_headerRow() - local standings = self.props.standings - +---@param standings StandingsModel +---@return Renderable +function Helpers._headerRow(standings) ---@param text string? - ---@return Widget + ---@return Renderable local makeHeaderCell = function(text) return TableWidgets.CellHeader{children = text} end @@ -132,7 +125,7 @@ function StandingsFfaWidget:_headerRow() return TableWidgets.TableHeader{children = { TableWidgets.Row{children = WidgetUtil.collect( makeHeaderCell('#'), - self:_showRoundColumns() and makeHeaderCell() or nil, + Helpers._showRoundColumns(standings) and makeHeaderCell() or nil, makeHeaderCell('Participant'), Array.map(standings.tiebreakers, function(tiebreaker) if not tiebreaker.title then @@ -140,7 +133,7 @@ function StandingsFfaWidget:_headerRow() end return makeHeaderCell(tiebreaker.title) end), - self:_showRoundColumns() and Array.map(standings.rounds, function(round) + Helpers._showRoundColumns(standings) and Array.map(standings.rounds, function(round) return TableWidgets.CellHeader{ classes = {'standings-ffa-detail'}, children = round.title, @@ -151,10 +144,10 @@ function StandingsFfaWidget:_headerRow() end ---@private +---@param standings StandingsModel ---@param round StandingsRound ----@return Widget -function StandingsFfaWidget:_createRoundBody(round) - local standings = self.props.standings +---@return Renderable +function Helpers._createRoundBody(standings, round) return TableWidgets.TableBody{children = Array.map(round.opponents, function (slot) return TableWidgets.Row{ attributes = { @@ -169,7 +162,7 @@ function StandingsFfaWidget:_createRoundBody(round) }, labelScheme = 'placement', }}, - self:_showRoundColumns() and TableWidgets.Cell{ + Helpers._showRoundColumns(standings) and TableWidgets.Cell{ children = PlacementChange{change = slot.positionChangeFromPreviousRound} } or nil, TableWidgets.Cell{children = OpponentDisplay.BlockOpponent{ @@ -187,7 +180,7 @@ function StandingsFfaWidget:_createRoundBody(round) children = slot.tiebreakerValues[tiebreaker.id] and slot.tiebreakerValues[tiebreaker.id].display or '' } end), - self:_showRoundColumns() and Array.map(standings.rounds, function (columnRound) + Helpers._showRoundColumns(standings) and Array.map(standings.rounds, function (columnRound) local text if columnRound.round <= round.round then local opponent = Array.find(columnRound.opponents, function(columnSlot) @@ -212,4 +205,4 @@ function StandingsFfaWidget:_createRoundBody(round) end)} end -return StandingsFfaWidget +return Component.component(StandingsFfa) diff --git a/lua/wikis/commons/Widget/Standings/MatchOverview.lua b/lua/wikis/commons/Widget/Standings/MatchOverview.lua index 27e98586fa4..5820bca5c3d 100644 --- a/lua/wikis/commons/Widget/Standings/MatchOverview.lua +++ b/lua/wikis/commons/Widget/Standings/MatchOverview.lua @@ -8,29 +8,19 @@ local Lua = require('Module:Lua') local Array = Lua.import('Module:Array') -local Class = Lua.import('Module:Class') -local FnUtil = Lua.import('Module:FnUtil') -local Widget = Lua.import('Module:Widget') -local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local Component = Lua.import('Module:Widget/Component') +local HtmlWidgets = Lua.import('Module:Widget/Html') local Label = Lua.import('Module:Widget/Basic/Label') local WidgetUtil = Lua.import('Module:Widget/Util') local OpponentDisplay = Lua.import('Module:OpponentDisplay/Custom') ----@class MatchOverviewWidgetProps ----@field match MatchGroupUtilMatch ----@field showOpponent integer - ----@class MatchOverviewWidget: Widget ----@operator call(MatchOverviewWidgetProps): MatchOverviewWidget ----@field props MatchOverviewWidgetProps -local MatchOverviewWidget = Class.new(Widget) - ----@return Widget? -function MatchOverviewWidget:render() - local match = self.props.match - local opponentIndexToShow = tonumber(self.props.showOpponent) +---@param props {match: MatchGroupUtilMatch, showOpponent: integer} +---@return Renderable? +local function MatchOverviewWidget(props) + local match = props.match + local opponentIndexToShow = tonumber(props.showOpponent) if not match or not opponentIndexToShow or #match.opponents ~= 2 then return end @@ -45,13 +35,37 @@ function MatchOverviewWidget:render() return end + local phase = match.phase + local resultType + + if match.phase ~= 'finished' then + resultType = 'default' + elseif match.winner == opponentIndexToShow then + resultType = 'loss' + elseif match.winner == 0 then + resultType = 'draw' + else + resultType = 'win' + end + return HtmlWidgets.Div{ classes = {'standings-match-overview'}, children = WidgetUtil.collect( - self:_createResultDisplay( - OpponentDisplay.InlineScore(leftOpponent), - OpponentDisplay.InlineScore(opponentToShow) - ), + phase ~= 'upcoming' and Label{ + labelScheme = 'standings-result', + labelType = 'result-' .. resultType, + children = { + HtmlWidgets.Span{ + css = resultType == 'win' and {['font-weight'] = 'bold'} or nil, + children = OpponentDisplay.InlineScore(leftOpponent) + }, + HtmlWidgets.Span{children = ':'}, + HtmlWidgets.Span{ + css = resultType == 'loss' and {['font-weight'] = 'bold'} or nil, + children = OpponentDisplay.InlineScore(opponentToShow) + } + } + } or nil, OpponentDisplay.InlineOpponent{ opponent = opponentToShow, overflow = 'ellipsis', @@ -61,54 +75,4 @@ function MatchOverviewWidget:render() } end ----@private ----@param self MatchOverviewWidget ----@return string -MatchOverviewWidget._getMatchResultType = FnUtil.memoize(function (self) - local match = self.props.match - local opponentIndexToShow = tonumber(self.props.showOpponent) - - if match.phase == 'ongoing' then - return 'default' - elseif match.winner == opponentIndexToShow then - return 'loss' - elseif match.winner == 0 then - return 'draw' - end - return 'win' -end) - ----@private ----@param leftScore string ----@param rightScore string ----@return Widget[] -function MatchOverviewWidget:_createScoreContainer(leftScore, rightScore) - local resultType = self:_getMatchResultType() - return { - HtmlWidgets.Span{ - css = resultType == 'win' and {['font-weight'] = 'bold'} or nil, - children = leftScore - }, - HtmlWidgets.Span{children = ':'}, - HtmlWidgets.Span{ - css = resultType == 'loss' and {['font-weight'] = 'bold'} or nil, - children = rightScore - } - } -end - ----@private ----@return VNode? -function MatchOverviewWidget:_createResultDisplay(leftScore, rightScore) - if self.props.match.phase == 'upcoming' then - return - end - local resultType = self:_getMatchResultType() - return Label{ - labelScheme = 'standings-result', - labelType = 'result-' .. resultType, - children = self:_createScoreContainer(leftScore, rightScore) - } -end - -return MatchOverviewWidget +return Component.component(MatchOverviewWidget) diff --git a/lua/wikis/commons/Widget/Standings/PlacementChange.lua b/lua/wikis/commons/Widget/Standings/PlacementChange.lua index e6449757671..aab17faa2e4 100644 --- a/lua/wikis/commons/Widget/Standings/PlacementChange.lua +++ b/lua/wikis/commons/Widget/Standings/PlacementChange.lua @@ -7,10 +7,8 @@ local Lua = require('Module:Lua') -local Class = Lua.import('Module:Class') - -local Widget = Lua.import('Module:Widget') -local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local Component = Lua.import('Module:Widget/Component') +local HtmlWidgets = Lua.import('Module:Widget/Html') local IconFa = Lua.import('Module:Widget/Image/Icon/Fontawesome') local PLACEMENT_MOVE_DOUBLE_UP = IconFa{iconName = 'rankup_double'} @@ -19,39 +17,15 @@ local PLACEMENT_MOVE_NEUTRAL = IconFa{iconName = 'rankneutral'} local PLACEMENT_MOVE_DOWN = IconFa{iconName = 'rankdown'} local PLACEMENT_MOVE_DOUBLE_DOWN = IconFa{iconName = 'rankdown_double'} ----@class PlacementChangeWidgetProps ----@field change integer ----@field emphasisThreshold integer - ----@class PlacementChangeWidget: Widget ----@operator call(PlacementChangeWidgetProps): PlacementChangeWidget ----@field props PlacementChangeWidgetProps -local PlacementChangeWidget = Class.new(Widget) -PlacementChangeWidget.defaultProps = { +local defaultProps = { change = 0, emphasisThreshold = 5, } ----@return Widget? -function PlacementChangeWidget:render() - local change = self.props.change - - return HtmlWidgets.Span{ - classes = { - 'standings-position-indicator', - 'movement-' .. PlacementChangeWidget._getMovementType(change) - }, - children = { - self:_getIndicator(), - change ~= 0 and HtmlWidgets.Span{children = math.abs(change)} or nil - }, - } -end - ---@private ---@param change integer ---@return string -function PlacementChangeWidget._getMovementType(change) +local function getMovementType(change) if change == 0 then return 'neutral' elseif change > 0 then @@ -62,19 +36,35 @@ function PlacementChangeWidget._getMovementType(change) end ---@private ----@return Widget -function PlacementChangeWidget:_getIndicator() - local change = self.props.change - +---@return Renderable +local function getIndicator(change, threshold) if change == 0 then return PLACEMENT_MOVE_NEUTRAL end - local changeEmphasized = math.abs(change) >= self.props.emphasisThreshold + local changeEmphasized = math.abs(change) >= threshold if change > 0 then return changeEmphasized and PLACEMENT_MOVE_DOUBLE_UP or PLACEMENT_MOVE_UP end return changeEmphasized and PLACEMENT_MOVE_DOUBLE_DOWN or PLACEMENT_MOVE_DOWN end -return PlacementChangeWidget +---@param props {change: integer?, emphasisThreshold: integer?} +---@return Renderable? +local function PlacementChangeWidget(props) + ---@cast props {change: integer, emphasisThreshold: integer} + local change = props.change + + return HtmlWidgets.Span{ + classes = { + 'standings-position-indicator', + 'movement-' .. getMovementType(change) + }, + children = { + getIndicator(change, props.emphasisThreshold), + change ~= 0 and HtmlWidgets.Span{children = math.abs(change)} or nil + }, + } +end + +return Component.component(PlacementChangeWidget, defaultProps) diff --git a/lua/wikis/commons/Widget/Standings/RoundSelector.lua b/lua/wikis/commons/Widget/Standings/RoundSelector.lua index 4f5edd35d77..4ef790fd7e6 100644 --- a/lua/wikis/commons/Widget/Standings/RoundSelector.lua +++ b/lua/wikis/commons/Widget/Standings/RoundSelector.lua @@ -8,30 +8,31 @@ local Lua = require('Module:Lua') local Array = Lua.import('Module:Array') -local Class = Lua.import('Module:Class') -local Widget = Lua.import('Module:Widget') -local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local Component = Lua.import('Module:Widget/Component') +local HtmlWidgets = Lua.import('Module:Widget/Html') local Button = Lua.import('Module:Widget/Basic/Button') ----@class RoundSelectorWidgetProps ----@field rounds integer? ----@field hasEnded boolean? - ----@class RoundSelectorWidget: Widget ----@operator call(RoundSelectorWidgetProps): RoundSelectorWidget ----@field props RoundSelectorWidgetProps -local RoundSelectorWidget = Class.new(Widget) +---@private +---@return string +local function finalRoundTitle(hasEnded, rounds) + if not hasEnded then + return 'Current' + else + return 'Round ' .. tostring(rounds) + end +end ----@return Widget? -function RoundSelectorWidget:render() - if not self.props.rounds or self.props.rounds <= 1 then +---@param props {rounds: integer?, hasEnded: boolean?} +---@return Renderable? +local function RoundSelectorWidget(props) + if not props.rounds or props.rounds <= 1 then return end - local roundTitles = Array.map(Array.range(1, self.props.rounds), function (round) - if round == self.props.rounds then - return self:_finalRoundTitle() + local roundTitles = Array.map(Array.range(1, props.rounds), function (round) + if round == props.rounds then + return finalRoundTitle(props.hasEnded, props.rounds) else return 'Round ' .. round end @@ -52,7 +53,7 @@ function RoundSelectorWidget:render() css = {float = 'left'}, children = { Button{ - children = self:_finalRoundTitle(), + children = finalRoundTitle(props.hasEnded, props.rounds), variant = 'primary', size = 'sm', classes = {'dropdown-box-button'}, @@ -66,14 +67,4 @@ function RoundSelectorWidget:render() } end ----@private ----@return string -function RoundSelectorWidget:_finalRoundTitle() - if not self.props.hasEnded then - return 'Current' - else - return 'Round ' .. tostring(self.props.rounds) - end -end - -return RoundSelectorWidget +return Component.component(RoundSelectorWidget) diff --git a/lua/wikis/commons/Widget/Standings/Swiss.lua b/lua/wikis/commons/Widget/Standings/Swiss.lua index bb8b66fe1fc..ba0888a6ba9 100644 --- a/lua/wikis/commons/Widget/Standings/Swiss.lua +++ b/lua/wikis/commons/Widget/Standings/Swiss.lua @@ -8,11 +8,10 @@ local Lua = require('Module:Lua') local Array = Lua.import('Module:Array') -local Class = Lua.import('Module:Class') local Logic = Lua.import('Module:Logic') local WidgetUtil = Lua.import('Module:Widget/Util') -local Widget = Lua.import('Module:Widget') +local Component = Lua.import('Module:Widget/Component') local Label = Lua.import('Module:Widget/Basic/Label') local MatchOverview = Lua.import('Module:Widget/Standings/MatchOverview') local TableWidgets = Lua.import('Module:Widget/Table2/All') @@ -20,33 +19,28 @@ local TableWidgets = Lua.import('Module:Widget/Table2/All') local Opponent = Lua.import('Module:Opponent/Custom') local OpponentDisplay = Lua.import('Module:OpponentDisplay/Custom') ----@class StandingsSwissWidgetProps ----@field standings StandingsModel +local Helpers = {} ----@class StandingsSwissWidget: Widget ----@operator call(StandingsSwissWidgetProps): StandingsSwissWidget ----@field props StandingsSwissWidgetProps -local StandingsSwissWidget = Class.new(Widget) - ----@return Widget? -function StandingsSwissWidget:render() - if not self.props.standings then +---@param props {standings?: StandingsModel} +---@return Renderable? +local function StandingsSwiss(props) + local standings = props.standings + if not standings then return end - local standings = self.props.standings local lastRound = standings.rounds[#standings.rounds] return TableWidgets.Table{ classes = {'standings-swiss'}, title = Logic.nilIfEmpty(standings.title), - columns = self:_buildColumnDefinitions(), + columns = Helpers.buildColumnDefinitions(standings), children = WidgetUtil.collect( -- Column Header - self:_headerRow(), + Helpers.headerRow(standings), -- Rows TableWidgets.TableBody{children = Array.map(lastRound.opponents, function(slot) - return self:_createRow(slot) + return Helpers.createRow(standings, slot) end)} ), striped = false @@ -54,9 +48,9 @@ function StandingsSwissWidget:render() end ---@private +---@param standings StandingsModel ---@return table[] -function StandingsSwissWidget:_buildColumnDefinitions() - local standings = self.props.standings +function Helpers.buildColumnDefinitions(standings) return WidgetUtil.collect( {align = 'left'}, {align = 'left'}, @@ -71,12 +65,11 @@ function StandingsSwissWidget:_buildColumnDefinitions() end ---@private ----@return Widget -function StandingsSwissWidget:_headerRow() - local standings = self.props.standings - +---@param standings StandingsModel +---@return Renderable +function Helpers.headerRow(standings) ---@param text string? - ---@return Widget + ---@return Renderable local makeHeaderCell = function(text) return TableWidgets.CellHeader{children = text} end @@ -99,10 +92,10 @@ function StandingsSwissWidget:_headerRow() end ---@private +---@param standings StandingsModel ---@param slot StandingsEntryModel ----@return Widget -function StandingsSwissWidget:_createRow(slot) - local standings = self.props.standings +---@return Renderable +function Helpers.createRow(standings, slot) return TableWidgets.Row{ attributes = {['data-position-status'] = slot.positionStatus}, children = WidgetUtil.collect( @@ -158,4 +151,4 @@ function StandingsSwissWidget:_createRow(slot) } end -return StandingsSwissWidget +return Component.component(StandingsSwiss)