diff --git a/javascript/commons/Prizepooltable.js b/javascript/commons/Prizepooltable.js index f7a4a32765b..f84bd25c5e7 100644 --- a/javascript/commons/Prizepooltable.js +++ b/javascript/commons/Prizepooltable.js @@ -5,6 +5,12 @@ liquipedia.prizepooltable = { init: function() { document.querySelectorAll( '.prizepooltable' ).forEach( ( prizepooltable ) => { + // The redesigned (Table2) prize pool wraps its table in + // `.prizepool-table-wrapper` and collapses via general-collapsible, so it + // supplies its own toggle. Skip it here to avoid a duplicate legacy toggle. + if ( prizepooltable.closest( '.prizepool-table-wrapper' ) !== null ) { + return; + } let cutAfter; if ( typeof prizepooltable.dataset.cutafter !== 'undefined' ) { cutAfter = parseInt( prizepooltable.dataset.cutafter ); diff --git a/lua/spec/prize_pool_spec.lua b/lua/spec/prize_pool_spec.lua index a6b871fa86b..a479ad4535b 100644 --- a/lua/spec/prize_pool_spec.lua +++ b/lua/spec/prize_pool_spec.lua @@ -67,8 +67,8 @@ describe('prize pool', function() currency = 'SEK', } }, - {id = 'QUALIFIES1', type = 'QUALIFIES', index = 1, data = {title = 'A Display', link = 'A_Tournament'}}, {id = 'POINTS1', type = 'POINTS', index = 1, data = {title = 'Points', link = 'A Page'}}, + {id = 'QUALIFIES1', type = 'QUALIFIES', index = 1, data = {title = 'A Display', link = 'A_Tournament'}}, {id = 'FREETEXT1', type = 'FREETEXT', index = 1, data = {title = 'A title'}}, }, ppt.prizes diff --git a/lua/spec/snapshots/prize_pool.png b/lua/spec/snapshots/prize_pool.png index 74dcd66d32d..2eb65a2bcb2 100644 Binary files a/lua/spec/snapshots/prize_pool.png and b/lua/spec/snapshots/prize_pool.png differ diff --git a/lua/wikis/commons/Placement.lua b/lua/wikis/commons/Placement.lua index af4636a1386..dd73c54ccab 100644 --- a/lua/wikis/commons/Placement.lua +++ b/lua/wikis/commons/Placement.lua @@ -88,7 +88,6 @@ local prizepoolClasses = { 'background-color-first-place', 'background-color-second-place', 'background-color-third-place', - 'background-color-fourth-place', w = 'bg-win', q = 'bg-win', l = 'bg-lose', diff --git a/lua/wikis/commons/PrizePool.lua b/lua/wikis/commons/PrizePool.lua index 9c3e78a31b5..2b7d963a4f9 100644 --- a/lua/wikis/commons/PrizePool.lua +++ b/lua/wikis/commons/PrizePool.lua @@ -10,7 +10,6 @@ local Lua = require('Module:Lua') local Array = Lua.import('Module:Array') local Class = Lua.import('Module:Class') local Json = Lua.import('Module:Json') -local Operator = Lua.import('Module:Operator') local String = Lua.import('Module:StringUtils') local Import = Lua.import('Module:PrizePool/Import') @@ -20,18 +19,14 @@ local Placement = Lua.import('Module:PrizePool/Placement') local Opponent = Lua.import('Module:Opponent/Custom') local Widgets = Lua.import('Module:Widget/All') -local Div = Widgets.Div -local IconFa = Lua.import('Module:Widget/Image/Icon/Fontawesome') -local TableRow = Widgets.TableRow -local TableCell = Widgets.TableCell +local Span = Widgets.Span +local TableCell = Lua.import('Module:Widget/Table2/All').Cell ---@class PrizePool: BasePrizePool ---@operator call(...): PrizePool ---@field placements PrizePoolPlacement[] local PrizePool = Class.new(BasePrizePool) -local NON_BREAKING_SPACE = ' ' - ---@param args table function PrizePool:readPlacements(args) local currentPlace = 0 @@ -52,16 +47,19 @@ function PrizePool:readPlacements(args) end ---@param placement PrizePoolPlacement ----@return WidgetTableCell +---@return Renderable function PrizePool:placeOrAwardCell(placement) - local placeCell = TableCell{ - children = {placement:getMedal() or '', NON_BREAKING_SPACE, placement:_displayPlace()}, - css = {['font-weight'] = 'bolder'}, + local badgeClass = placement:getBadgeClass() + local placeDisplay = placement:_displayPlace() + local content = badgeClass + and Span{classes = {'prizepooltable-badge', badgeClass}, children = {placeDisplay}} + or placeDisplay + + return TableCell{ + children = {content}, classes = {'prizepooltable-place'}, + rowspan = #placement.opponents, } - placeCell.rowSpan = #placement.opponents - - return placeCell end ---@param placement PrizePoolPlacement @@ -73,61 +71,23 @@ end ---@param placement PrizePoolPlacement ---@return boolean function PrizePool:applyCutAfter(placement) - if placement.placeStart > self.options.cutafter then - return true - end - return false -end - ----@param placement PrizePoolPlacement? ----@param nextPlacement PrizePoolPlacement ----@param rows WidgetTableRow[] -function PrizePool:applyToggleExpand(placement, nextPlacement, rows) - if placement ~= nil - and placement.placeStart <= self.options.cutafter - and placement.placeEnd >= self.options.cutafter - and placement ~= self.placements[#self.placements] - and nextPlacement.placeStart ~= placement.placeStart - and nextPlacement.placeEnd ~= placement.placeEnd then - - table.insert(rows, self:_toggleExpand(placement.placeEnd + 1)) - end + return placement.placeStart > self.options.cutafter end ----@param placeStart number ----@return WidgetTableRow -function PrizePool:_toggleExpand(placeStart) - local placeEnd = self.placements[#self.placements].placeEnd - - if self.options.hideafter < math.huge then - local lastCut = Array.max( - Array.filter(self.placements, function (placement) - return placement.placeEnd <= self.options.hideafter - end), - Operator.property('placeEnd') - ) - placeEnd = lastCut.placeEnd +---@return {opentext: string, closetext: string}? +function PrizePool:_collapseText() + local visible = Array.filter(self.placements, function(placement) + return placement.placeStart <= self.options.hideafter + end) + local lastVisible = visible[#visible] + local firstHidden = Array.find(visible, function(placement) + return placement.placeStart > self.options.cutafter + end) + if not firstHidden or not lastVisible then + return nil end - - local text = 'place ' .. placeStart .. ' to ' .. placeEnd - local expandButton = TableCell{ - children = Div{children = { - text, - ' ', - IconFa{iconName = 'expand'}, - }}, - classes = {'general-collapsible-expand-button'}, - } - local collapseButton = TableCell{ - children = Div{children = { - text, - ' ', - IconFa{iconName = 'collapse'}, - }}, - classes = {'general-collapsible-collapse-button'}, - } - - return TableRow{classes = {'ppt-toggle-expand'}, children = {expandButton, collapseButton}} + local text = 'place ' .. firstHidden.placeStart .. ' to ' .. lastVisible.placeEnd + return {opentext = text, closetext = text} end -- get the lpdbObjectName depending on opponenttype diff --git a/lua/wikis/commons/PrizePool/Award.lua b/lua/wikis/commons/PrizePool/Award.lua index 6eb7b073a65..c3fe42ddb4a 100644 --- a/lua/wikis/commons/PrizePool/Award.lua +++ b/lua/wikis/commons/PrizePool/Award.lua @@ -17,11 +17,7 @@ local Placement = Lua.import('Module:PrizePool/Award/Placement') local Opponent = Lua.import('Module:Opponent/Custom') -local Widgets = Lua.import('Module:Widget/All') -local Div = Widgets.Div -local IconFa = Lua.import('Module:Widget/Image/Icon/Fontawesome') -local TableRow = Widgets.TableRow -local TableCell = Widgets.TableCell +local TableCell = Lua.import('Module:Widget/Table2/All').Cell --- @class AwardPrizePool: BasePrizePool --- @operator call(...): AwardPrizePool @@ -50,58 +46,24 @@ function AwardPrizePool:readPlacements(args) end ---@param placement AwardPlacement ----@return WidgetTableCell +---@return Renderable function AwardPrizePool:placeOrAwardCell(placement) - local awardCell = TableCell{ + return TableCell{ children = {placement.award}, - css = {['font-weight'] = 'bolder'}, classes = {'prizepooltable-place'}, + rowspan = #placement.opponents, } - awardCell.rowSpan = #placement.opponents - - return awardCell end ---@param placement AwardPlacement ---@return boolean function AwardPrizePool:applyCutAfter(placement) - if (placement.previousTotalNumberOfParticipants + 1) > self.options.cutafter then - return true - end - return false + return (placement.previousTotalNumberOfParticipants + 1) > self.options.cutafter end ----@param placement AwardPlacement? ----@param nextPlacement AwardPlacement ----@param rows WidgetTableRow[] -function AwardPrizePool:applyToggleExpand(placement, nextPlacement, rows) - if placement ~= nil - and (placement.previousTotalNumberOfParticipants + 1) <= self.options.cutafter - and placement.currentTotalNumberOfParticipants >= self.options.cutafter - and placement ~= self.placements[#self.placements] then - - table.insert(rows, self:_toggleExpand()) - end -end - ----@return WidgetTableRow -function AwardPrizePool:_toggleExpand() - local expandButton = TableCell{ - children = Div{children = { - 'Show more Awards ', - IconFa{iconName = 'expand'}, - }}, - classes = {'general-collapsible-expand-button'}, - } - local collapseButton = TableCell{ - children = Div{children = { - 'Show less Awards ', - IconFa{iconName = 'collapse'}, - }}, - classes = {'general-collapsible-collapse-button'}, - } - - return TableRow{classes = {'ppt-toggle-expand'}, children = {expandButton, collapseButton}} +---@return {opentext: string, closetext: string}? +function AwardPrizePool:_collapseText() + return {opentext = 'Show more Awards', closetext = 'Show less Awards'} end -- Get the lpdbObjectName depending on opponenttype diff --git a/lua/wikis/commons/PrizePool/Base.lua b/lua/wikis/commons/PrizePool/Base.lua index 680786f4314..81d40140de5 100644 --- a/lua/wikis/commons/PrizePool/Base.lua +++ b/lua/wikis/commons/PrizePool/Base.lua @@ -28,13 +28,14 @@ local LpdbInjector = Lua.import('Module:Lpdb/Injector') local Opponent = Lua.import('Module:Opponent/Custom') local OpponentDisplay = Lua.import('Module:OpponentDisplay/Custom') -local Widgets = Lua.import('Module:Widget/All') local HtmlWidgets = Lua.import('Module:Widget/Html/All') -local WidgetTable = Widgets.TableOld -local TableRow = Widgets.TableRow -local TableCell = Widgets.TableCell +local TableWidgets = Lua.import('Module:Widget/Table2/All') +local TableCell = TableWidgets.Cell +local TableCellHeader = TableWidgets.CellHeader +local TableRow = TableWidgets.Row local Div = HtmlWidgets.Div local Span = HtmlWidgets.Span +local ChevronToggle = Lua.import('Module:Widget/GeneralCollapsible/ChevronToggle') local WidgetUtil = Lua.import('Module:Widget/Util') local pageVars = PageVariableNamespace('PrizePool') @@ -64,6 +65,10 @@ local PRIZE_TYPE_POINTS = 'POINTS' local PRIZE_TYPE_PERCENTAGE = 'PERCENT' local PRIZE_TYPE_FREETEXT = 'FREETEXT' +-- Alignment for the fixed (non-prize) columns; prize columns carry `align` on their prize type. +local PLACE_COLUMN_ALIGN = 'center' +local OPPONENT_COLUMN_ALIGN = 'left' + BasePrizePool.config = { showBaseCurrency = { default = false @@ -153,10 +158,11 @@ BasePrizePool.config = { BasePrizePool.prizeTypes = { [PRIZE_TYPE_BASE_CURRENCY] = { sortOrder = 10, + align = 'right', headerDisplay = function (data) local currencyText = Currency.display(BASE_CURRENCY) - return TableCell{children = {currencyText}} + return TableCellHeader{children = {currencyText}} end, row = BASE_CURRENCY:lower() .. 'prize', @@ -174,6 +180,7 @@ BasePrizePool.prizeTypes = { }, [PRIZE_TYPE_LOCAL_CURRENCY] = { sortOrder = 20, + align = 'right', header = 'localcurrency', headerParse = function (prizePool, input, context, index) @@ -195,7 +202,7 @@ BasePrizePool.prizeTypes = { } end, headerDisplay = function (data) - return TableCell{children = {Currency.display(data.currency)}} + return TableCellHeader{children = {Currency.display(data.currency)}} end, row = 'localprize', @@ -226,6 +233,7 @@ BasePrizePool.prizeTypes = { }, [PRIZE_TYPE_PERCENTAGE] = { sortOrder = 30, + align = 'right', header = 'percentage', headerParse = function (prizePool, input, context, index) @@ -233,7 +241,7 @@ BasePrizePool.prizeTypes = { return {title = 'Percentage'} end, headerDisplay = function (data) - return TableCell{children = {data.title}} + return TableCellHeader{children = {data.title}} end, row = 'percentage', @@ -252,7 +260,8 @@ BasePrizePool.prizeTypes = { end, }, [PRIZE_TYPE_QUALIFIES] = { - sortOrder = 40, + sortOrder = 50, + align = 'left', header = 'qualifies', headerParse = function (prizePool, input, context, index) @@ -271,7 +280,7 @@ BasePrizePool.prizeTypes = { } end, headerDisplay = function (data) - return TableCell{children = {'Qualifies To'}} + return TableCellHeader{children = {'Qualifies To'}} end, row = 'qualified', @@ -305,7 +314,8 @@ BasePrizePool.prizeTypes = { mergeDisplayColumns = true, }, [PRIZE_TYPE_POINTS] = { - sortOrder = 50, + sortOrder = 40, + align = 'right', header = 'points', headerParse = function (prizePool, input, context, index) @@ -344,7 +354,7 @@ BasePrizePool.prizeTypes = { table.insert(headerDisplay, text) end - return TableCell{children = {table.concat(headerDisplay)}} + return TableCellHeader{children = headerDisplay} end, row = 'points', @@ -359,13 +369,14 @@ BasePrizePool.prizeTypes = { }, [PRIZE_TYPE_FREETEXT] = { sortOrder = 60, + align = 'left', header = 'freetext', headerParse = function (prizePool, input, context, index) return {title = input} end, headerDisplay = function (data) - return TableCell{children = {data.title}} + return TableCellHeader{children = {data.title}} end, row = 'freetext', @@ -587,126 +598,163 @@ end ---@param isAward boolean? ---@return Widget function BasePrizePool:_buildTable(isAward) - local headerRow = self:_buildHeader(isAward) + local bodyRows = self:_buildRows() + local hasCutRows = Array.any(self.placements, function(placement) + return not self:applyHideAfter(placement) and self:applyCutAfter(placement) + end) + local toggle = hasCutRows and self:_collapseToggle() or nil + + return TableWidgets.Table{ + classes = WidgetUtil.collect( + 'prizepool-table-wrapper', + toggle and 'general-collapsible' or nil, + toggle and 'collapsed' or nil + ), + tableClasses = { + 'prizepooltable', + 'prizepooltable-' .. (isAward and 'award' or 'placement'), + }, + footer = toggle, + children = { + TableWidgets.TableHeader{children = {self:_buildHeader(isAward)}}, + TableWidgets.TableBody{children = bodyRows}, + }, + } +end +---@return Renderable? +function BasePrizePool:_collapseToggle() + local collapseText = self:_collapseText() + if not collapseText then + return nil + end return Div{ - css = {['overflow-x'] = 'auto'}, - children = {WidgetTable{ - classes = { - 'collapsed', - 'general-collapsible', - 'prizepooltable', - 'prizepooltable-' .. (isAward and 'award' or 'placement') - }, - css = {width = 'max-content'}, - columns = headerRow:getCellCount(), - children = WidgetUtil.collect(headerRow, unpack(self:_buildRows())) + classes = {'prizepooltable-toggle'}, + children = {ChevronToggle{ + expandText = collapseText.opentext, + collapseText = collapseText.closetext, + size = 'sm', }}, } end ---@param isAward boolean? ----@return WidgetTableRow +---@return Renderable function BasePrizePool:_buildHeader(isAward) - local children = {} - - table.insert(children, TableCell{children = {isAward and 'Award' or 'Place'}, css = {['min-width'] = '80px'}}) + local children = { + TableCellHeader{children = {isAward and 'Award' or 'Place'}, align = PLACE_COLUMN_ALIGN}, + TableCellHeader{children = {'Participant'}, classes = {'prizepooltable-col-team'}, align = OPPONENT_COLUMN_ALIGN}, + } local previousOfType = {} for _, prize in ipairs(self.prizes) do local prizeTypeData = self.prizeTypes[prize.type] - if not prizeTypeData.mergeDisplayColumns or not previousOfType[prize.type] then local cell = prizeTypeData.headerDisplay(prize.data) + cell.props.align = cell.props.align or prizeTypeData.align table.insert(children, cell) previousOfType[prize.type] = cell end end - table.insert(children, TableCell{children = {'Participant'}, classes = {'prizepooltable-col-team'}}) - - return TableRow{classes = {'prizepooltable-header'}, css = {['font-weight'] = 'bold'}, children = children} + return TableRow{classes = {'prizepooltable-header'}, children = children} end ----@return WidgetTableRow[] +---@return Renderable[] function BasePrizePool:_buildRows() local rows = {} - local previousPlacement = nil for _, placement in ipairs(self.placements) do - local previousOpponent = {} - if self:applyHideAfter(placement) then break end - self:applyToggleExpand(previousPlacement, placement, rows) - - local cells = {} - table.insert(cells, self:placeOrAwardCell(placement)) + local opponents = placement.opponents + local placeCell = self:placeOrAwardCell(placement) + local backgroundClass = placement:getBackground() - for _, opponent in ipairs(placement.opponents) do - local previousOfPrizeType = {} - local prizeCells = Array.map(self.prizes, function (prize) - local prizeTypeData = self.prizeTypes[prize.type] - local reward = opponent.prizeRewards[prize.id] or placement.prizeRewards[prize.id] - - local cell = reward and prizeTypeData.rowDisplay(prize.data, reward) or TableCell{} - - -- Update the previous column of this type in the same row - local lastCellOfType = previousOfPrizeType[prize.type] - if lastCellOfType and prizeTypeData.mergeDisplayColumns then - - if Table.isNotEmpty(lastCellOfType.props.children) and Table.isNotEmpty(cell.props.children) then - table.insert(lastCellOfType.props.children, tostring(mw.html.create('hr'):css('width', '100%'))) - end - - Array.extendWith(lastCellOfType.props.children, cell.props.children) - lastCellOfType.css['flex-direction'] = 'column' + -- Build the prize-cell matrix: prizeMatrix[opponentIndex] = {cell, …} in display-column order. + local prizeMatrix = Array.map(opponents, function(opponent) + return self:_opponentPrizeCells(placement, opponent) + end) - return nil + -- Vertically merge consecutive-equal prize cells per column: the first cell of + -- each run spans the run, the rest are dropped (tracked by cell identity). + local numCols = prizeMatrix[1] and #prizeMatrix[1] or 0 + local omittedCells = {} + for col = 1, numCols do + local columnCells = Array.map(prizeMatrix, function(row) return row[col] end) + local runs = Array.groupAdjacentBy(columnCells, function(cell) + return cell.props.children + end, Table.deepEquals) + Array.forEach(runs, function(run) + if #run <= 1 then + return end - - previousOfPrizeType[prize.type] = cell - return cell + run[1].props.rowspan = #run + Array.forEach(Array.sub(run, 2), function(cell) + omittedCells[cell] = true + end) end) + end - Array.forEach(prizeCells, function (prizeCell, columnIndex) - local lastInColumn = previousOpponent[columnIndex] - - ---@cast prizeCell -nil - if Table.isEmpty(prizeCell.props.children) then - prizeCell = BasePrizePool._emptyCell() - end - - if lastInColumn and Table.deepEquals(lastInColumn.props.children, prizeCell.props.children) then - lastInColumn.rowSpan = (lastInColumn.rowSpan or 1) + 1 - else - previousOpponent[columnIndex] = prizeCell - table.insert(cells, prizeCell) - end + local isCut = self:applyCutAfter(placement) + Array.forEach(opponents, function(opponent, opponentIndex) + local opponentCell = TableCell{ + children = {OpponentDisplay.BlockOpponent{ + opponent = opponent.opponentData, + showPlayerTeam = true, + }}, + classes = {'prizepooltable-col-team'}, + align = OPPONENT_COLUMN_ALIGN, + nowrap = false, + } + local prizeCells = Array.filter(prizeMatrix[opponentIndex], function(cell) + return not omittedCells[cell] end) - local opponentDisplay = tostring(OpponentDisplay.BlockOpponent{ - opponent = opponent.opponentData, - showPlayerTeam = true, + table.insert(rows, TableRow{ + children = WidgetUtil.collect( + opponentIndex == 1 and placeCell or nil, + opponentCell, + prizeCells + ), + classes = WidgetUtil.collect(backgroundClass, isCut and 'ppt-hide-on-collapse' or nil), }) - local opponentCss = {['justify-content'] = 'start'} + end) + end - table.insert(cells, TableCell{children = {opponentDisplay}, css = opponentCss}) - end - local classes = {placement:getBackground()} - if self:applyCutAfter(placement) then - table.insert(classes, 'ppt-hide-on-collapse') - end - local row = TableRow{children = cells, classes = classes} + return rows +end - table.insert(rows, row) +---@private +---@param placement BasePlacement +---@param opponent BasePlacementOpponent +---@return Renderable[] +function BasePrizePool:_opponentPrizeCells(placement, opponent) + local previousOfPrizeType = {} + local prizeCells = Array.map(self.prizes, function(prize) + local prizeTypeData = self.prizeTypes[prize.type] + local reward = opponent.prizeRewards[prize.id] or placement.prizeRewards[prize.id] + local cell = reward and prizeTypeData.rowDisplay(prize.data, reward) or TableCell{} + cell.props.align = cell.props.align or prizeTypeData.align + + local lastCellOfType = previousOfPrizeType[prize.type] + if lastCellOfType and prizeTypeData.mergeDisplayColumns then + if Table.isNotEmpty(lastCellOfType.props.children) and Table.isNotEmpty(cell.props.children) then + table.insert(lastCellOfType.props.children, tostring(mw.html.create('hr'):css('width', '100%'))) + end + Array.extendWith(lastCellOfType.props.children, cell.props.children) + return nil + end - previousPlacement = placement - end + previousOfPrizeType[prize.type] = cell + return cell + end) - return rows + return Array.map(prizeCells, function(cell) + return Table.isEmpty(cell.props.children) and BasePrizePool._emptyCell() or cell + end) end ---@protected @@ -722,19 +770,20 @@ function BasePrizePool:applyHideAfter(placement) return false end +--- Whether a placement's rows are hidden behind the collapse toggle. +--- Child classes override this; the default keeps every row visible. ---@protected ---@param placement BasePlacement ---@return boolean function BasePrizePool:applyCutAfter(placement) - error('Function applyCutAfter needs to be implemented by a child class of "Module:PrizePool/Base"') + return false end +---Open/close labels for the collapse toggle. Child classes override. ---@protected ----@param placement BasePlacement? ----@param nextPlacement BasePlacement ----@param row WidgetTableRow -function BasePrizePool:applyToggleExpand(placement, nextPlacement, row) - error('Function applyToggleExpand needs to be implemented by a child class of "Module:PrizePool/Base"') +---@return {opentext: string, closetext: string}? +function BasePrizePool:_collapseText() + return nil end ---@return string @@ -815,7 +864,7 @@ function BasePrizePool:_hasBaseCurrency() end --- Creates an empty table cell ----@return WidgetTableCell +---@return Renderable function BasePrizePool._emptyCell() return TableCell{children = {DASH}} end diff --git a/lua/wikis/commons/PrizePool/Placement.lua b/lua/wikis/commons/PrizePool/Placement.lua index 3e11a0935b8..e46d1099cae 100644 --- a/lua/wikis/commons/PrizePool/Placement.lua +++ b/lua/wikis/commons/PrizePool/Placement.lua @@ -12,7 +12,6 @@ local Array = Lua.import('Module:Array') local Class = Lua.import('Module:Class') local Logic = Lua.import('Module:Logic') local Medals = Lua.import('Module:Medals') -local Ordinal = Lua.import('Module:Ordinal') local PlacementInfo = Lua.import('Module:Placement') local String = Lua.import('Module:StringUtils') local Table = Lua.import('Module:Table') @@ -315,12 +314,11 @@ function Placement:_displayPlace() end end - local start = Ordinal.toOrdinal(self.placeStart) if self.placeEnd > self.placeStart then - return start .. DASH .. Ordinal.toOrdinal(self.placeEnd) + return self.placeStart .. DASH .. self.placeEnd end - return start + return tostring(self.placeStart) end ---@return string? @@ -334,6 +332,16 @@ function Placement:getBackground() return PlacementInfo.getBgClass{placement = self.placeStart} end +---Returns the placement-badge color class for top-3 placements, else nil. +---Colored by the top of the range (placeStart). +---@return string? +function Placement:getBadgeClass() + if self:hasSpecialStatus() or self.placeStart > 3 then + return + end + return PlacementInfo.raw(self.placeStart).backgroundClass +end + ---@return string? function Placement:getMedal() if self:hasSpecialStatus() then diff --git a/lua/wikis/commons/Widget/GeneralCollapsible/ChevronToggle.lua b/lua/wikis/commons/Widget/GeneralCollapsible/ChevronToggle.lua index 062c0981c51..b2e909f0395 100644 --- a/lua/wikis/commons/Widget/GeneralCollapsible/ChevronToggle.lua +++ b/lua/wikis/commons/Widget/GeneralCollapsible/ChevronToggle.lua @@ -11,30 +11,38 @@ local Component = Lua.import('Module:Widget/Component') local Html = Lua.import('Module:Widget/Html') local Button = Lua.import('Module:Widget/Basic/Button') local Icon = Lua.import('Module:Widget/Image/Icon/Fontawesome') +local WidgetUtil = Lua.import('Module:Widget/Util') local Span = Html.Span ----@return HtmlNode -local function ChevronToggle() - local expandButton = Button{ - classes = {'general-collapsible-expand-button'}, - children = Span{ - children = { - Icon{iconName = 'expand'}, - }, - }, - size = 'xs', - variant = 'icon', - } - local collapseButton = Button{ - classes = {'general-collapsible-collapse-button'}, +---@param class string +---@param text (string|Widget)? +---@param iconName string +---@param size 'xs'|'sm'|'md'|'lg' +---@return Widget +local function chevronButton(class, text, iconName, size) + return Button{ + classes = {class}, children = Span{ - children = { - Icon{iconName = 'collapse'}, - }, + children = WidgetUtil.collect(text, text and ' ' or nil, Icon{iconName = iconName}), }, - size = 'xs', - variant = 'icon', + size = size, + -- Ghost (borderless, text-friendly) when a label is shown; icon-only otherwise. + variant = text and 'ghost' or 'icon', } +end + +---@class ChevronToggleProps +---@field expandText (string|Widget)? optional label shown next to the expand chevron +---@field collapseText (string|Widget)? optional label shown next to the collapse chevron +---@field size ('xs'|'sm'|'md'|'lg')? button size, defaults to 'xs' + +---@param props ChevronToggleProps? +---@return HtmlNode +local function ChevronToggle(props) + props = props or {} + local size = props.size or 'xs' + local expandButton = chevronButton('general-collapsible-expand-button', props.expandText, 'expand', size) + local collapseButton = chevronButton('general-collapsible-collapse-button', props.collapseText, 'collapse', size) return Span{ classes = {'general-collapsible-default-toggle'}, diff --git a/lua/wikis/commons/Widget/Table2/Cell.lua b/lua/wikis/commons/Widget/Table2/Cell.lua index 7ca1fec3633..51748499df1 100644 --- a/lua/wikis/commons/Widget/Table2/Cell.lua +++ b/lua/wikis/commons/Widget/Table2/Cell.lua @@ -43,7 +43,7 @@ local function Table2Cell(props, context) props.align, props.nowrap, props.shrink, - props.attributes + ColumnUtil.buildAttributes(props) ), classes = props.classes, children = children, diff --git a/lua/wikis/commons/Widget/Table2/CellHeader.lua b/lua/wikis/commons/Widget/Table2/CellHeader.lua index 4230ed2f10d..6493d57ed88 100644 --- a/lua/wikis/commons/Widget/Table2/CellHeader.lua +++ b/lua/wikis/commons/Widget/Table2/CellHeader.lua @@ -52,7 +52,7 @@ local function Table2CellHeader(props, context) -- Skip context lookups and property merging if there are no column definitions if #columns == 0 then local align = props.align - local attributes = props.attributes or {} + local attributes = ColumnUtil.buildAttributes(props) if align == 'right' or align == 'center' then attributes['data-align'] = align end diff --git a/stylesheets/commons/Prizepooltable.scss b/stylesheets/commons/Prizepooltable.scss index 7aebf9e98e4..c66a01a4fdd 100644 --- a/stylesheets/commons/Prizepooltable.scss +++ b/stylesheets/commons/Prizepooltable.scss @@ -1,10 +1,28 @@ +$prizepool-badge-size: 1.5rem; +$prizepool-accent-width: 0.25rem; + /******************************************************************************* -Template(s): Prize pool tables -Author(s): FO-nTTaX +Shared prize-pool custom properties + +`--prize-pool-*` are consumed by legacy tables here AND by Colours.scss +(`.background-color-first-place` / `.bg-first` / `.bg-p1` resolve `--prize-pool-gold`), +so they must stay defined even though the redesigned table no longer uses them. +`--prizepool-place-*` are the redesign tokens for the Table2 placement table. *******************************************************************************/ :root { --prize-pool-background-color: transparent; + + // Reuse the standard gold/silver/bronze placement tokens so top-3 badges match. + --prizepool-place-1-accent: var( --clr-semantic-gold-40 ); + --prizepool-place-2-accent: var( --clr-semantic-silver-40 ); + --prizepool-place-3-accent: var( --clr-semantic-bronze-40 ); + + // Faint full-row tint per place, built from the accent var so the dark-theme + // accents and surface flip through the cascade — no separate dark overrides. + --prizepool-place-1-tint: color-mix( in srgb, var( --prizepool-place-1-accent ) 15%, var( --clr-background ) ); + --prizepool-place-2-tint: color-mix( in srgb, var( --prizepool-place-2-accent ) 15%, var( --clr-background ) ); + --prizepool-place-3-tint: color-mix( in srgb, var( --prizepool-place-3-accent ) 15%, var( --clr-background ) ); } .theme--light { @@ -25,17 +43,27 @@ Author(s): FO-nTTaX --prize-pool-silver: var( --clr-secondary-39 ); --prize-pool-bronze: var( --clr-semantic-bronze-20 ); --prize-pool-copper: var( --clr-semantic-copper-20 ); + + --prizepool-place-1-accent: var( --clr-semantic-gold-30 ); + --prizepool-place-2-accent: var( --clr-semantic-silver-40 ); + --prizepool-place-3-accent: var( --clr-semantic-bronze-30 ); } +/******************************************************************************* +Legacy / shared prize pool table styles +Template(s): Prize pool tables — Author(s): FO-nTTaX + +Still relied on by non-redesigned consumers that share the `prizepooltable` +class and the global `bg-*` / `background-color-*-place` placement classes +(MvpTable, SeriesMedalStats, StageWinnings, TournamentPlayerInformation, +TournamentsListing, css-grid tables). Do not remove without auditing those. +*******************************************************************************/ + table.prizepooltable:not( .collapsed ) .prizepooltableshow, table.prizepooltable.collapsed .prizepooltablehide { display: none; } -.prizepooltabletoggle { - cursor: pointer; -} - .prizepooltable-col-player, .prizepooltable-col-team { min-width: 150px; @@ -55,6 +83,9 @@ table.prizepooltable.collapsed .prizepooltablehide { } } +// Legacy data-cutafter collapse (mvp / medal / winnings tables, which share the +// `prizepooltable` class and Prizepooltable.js). The JS injects the toggle before +// the (cutAfter + 2)-th row, so rows from (cutAfter + 3) onward are hidden. @for $i from 0 through 32 { html.client-js .prizepooltable.collapsed[ data-cutafter="#{$i}" ] tr:nth-child( n + #{ $i + 3 } ) { display: none; @@ -272,8 +303,7 @@ div.background-color-highlight { } /******************************************************************************* -Template: ( Standardized ) prize pool tables -Author: Rathoz +Legacy ( standardized ) prize pool tables — Author: Rathoz *******************************************************************************/ .collapsed > .ppt-hide-on-collapse { @@ -285,6 +315,12 @@ Author: Rathoz display: none !important; } +.prizepooltabletoggle { + cursor: pointer; +} + +// Legacy css-grid table widget (Module:Widget/Table/Old), still used by +// non-prizepool modules (e.g. infoboxes). Prizepool no longer renders via this. .csstable-widget { border-right: 1px solid var( --table-border-color, #bbbbbb ); border-bottom: 1px solid var( --table-border-color, #bbbbbb ); @@ -316,8 +352,176 @@ Author: Rathoz } /******************************************************************************* -Template: ( Standardized ) prize pool section -Author: iMarbot +Table2-based placement / award prize pool table ( redesign ) + +Scoped to `.prizepooltable-placement` / `.prizepool-table-wrapper` so it owns the +redesigned table only and does not affect the other Table2 tables that merely +share the `prizepooltable` class. +*******************************************************************************/ + +.prizepool-table-wrapper { + .table2__container { + overflow-x: auto; + scrollbar-width: none; + + @supports not ( scrollbar-width: none ) { + &::-webkit-scrollbar { + display: none; + } + } + } +} + +.prizepooltable { + .prizepooltable-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: $prizepool-badge-size; + height: $prizepool-badge-size; + padding: 0 0.25rem; + border-radius: 0.25rem; + font-weight: bold; + line-height: 1; + font-size: 0.75rem; + text-shadow: 0 1px 1px rgba( 0, 0, 0, 0.5 ); + color: #ffffff; + + &.placement-1 { + background-color: var( --prizepool-place-1-accent ); + } + + &.placement-2 { + background-color: var( --prizepool-place-2-accent ); + } + + &.placement-3 { + background-color: var( --prizepool-place-3-accent ); + } + } + + &.prizepooltable-placement .prizepooltable-place { + text-align: center; + font-weight: bold; + color: var( --clr-secondary-25 ); + + // Override the legacy `… > td.prizepooltable-place` metal fill so the row + // tint + accent bar show through on the redesigned table instead. + background-color: transparent; + + .theme--dark & { + color: var( --clr-secondary-100 ); + } + } + + // Top-3 rows: left accent bar (coloured, with the row tint, in the theme block below). + &.prizepooltable-placement { + tr.table2__row--body.background-color-first-place, + tr.table2__row--body.background-color-second-place, + tr.table2__row--body.background-color-third-place { + .prizepooltable-place { + position: relative; + + &::before { + content: ""; + position: absolute; + inset: 0 auto 0 0; + width: $prizepool-accent-width; + } + } + } + } + + // Top-3 row tint + accent bar. The Table2 zebra rule is `:where()`-wrapped, so + // these plain selectors out-specify it without a theme/`.table2__table` qualifier; + // `:hover` keeps the tint on hover. `border-bottom: 0` drops the legacy per-place + // coloured row border. + &.prizepooltable-placement { + tr.table2__row--body.background-color-first-place { + border-bottom: 0; + + &, + &:hover { + background-color: var( --prizepool-place-1-tint ); + } + + .prizepooltable-place::before { + background-color: var( --prizepool-place-1-accent ); + } + } + + tr.table2__row--body.background-color-second-place { + border-bottom: 0; + + &, + &:hover { + background-color: var( --prizepool-place-2-tint ); + } + + .prizepooltable-place::before { + background-color: var( --prizepool-place-2-accent ); + } + } + + tr.table2__row--body.background-color-third-place { + border-bottom: 0; + + &, + &:hover { + background-color: var( --prizepool-place-3-tint ); + } + + .prizepooltable-place::before { + background-color: var( --prizepool-place-3-accent ); + } + } + } + + tr.table2__row--body.bg-win .prizepooltable-place { + background-color: var( --clr-forest-background-color, inherit ); + } + + tr.table2__row--body.bg-lose .prizepooltable-place { + background-color: var( --clr-cinnabar-background-color, inherit ); + } + + tr.table2__row--body.bg-dq .prizepooltable-place { + background-color: var( --clr-sapphire-background-color, inherit ); + } +} + +// Footer toggle for the redesigned (general-collapsible) table. +.prizepooltable-toggle { + text-align: center; + font-weight: bold; + cursor: pointer; + color: var( --clr-brand-30, #0a558f ); + + .theme--dark & { + color: var( --clr-brand-80, #a7cdf1 ); + } + + a { + color: inherit; + } +} + +.general-collapsible.collapsed .prizepooltable-toggle .general-collapsible-collapse-button, +.general-collapsible:not( .collapsed ) .prizepooltable-toggle .general-collapsible-expand-button { + display: none; +} + +html:not( .client-js ) .prizepooltable-toggle { + display: none; +} + +// Gated on `client-js` so non-JS readers see all rows (the toggle can't expand them). +html.client-js .prizepool-table-wrapper.collapsed .ppt-hide-on-collapse { + display: none; +} + +/******************************************************************************* +( Standardized ) prize pool section — Author: iMarbot *******************************************************************************/ .prizepool-section-wrapper { @@ -329,6 +533,7 @@ Author: iMarbot .prizepool-section-tables { display: flex; flex-flow: row wrap; + align-items: flex-start; gap: 1em; @media ( max-width: 600px ) { diff --git a/stylesheets/commons/Table2.scss b/stylesheets/commons/Table2.scss index 596710bdb1a..dd78fcca872 100644 --- a/stylesheets/commons/Table2.scss +++ b/stylesheets/commons/Table2.scss @@ -109,7 +109,9 @@ $table2-cell-padding-x: 0.75rem; } tr.table2__row--body { - &.table2__row--even { + // `:where()` keeps the zebra tint at low specificity so consumers (e.g. the + // prize-pool placement tints) can override it without a specificity arms race. + &:where( .table2__row--even ) { background-color: var( --clr-on-surface-light-primary-4 ); .theme--dark & {