diff --git a/lua/spec/prize_pool_spec.lua b/lua/spec/prize_pool_spec.lua index a6b871fa86b..341e6e3bbc7 100644 --- a/lua/spec/prize_pool_spec.lua +++ b/lua/spec/prize_pool_spec.lua @@ -44,7 +44,15 @@ describe('prize pool', function() assert.are_same( { - {id = 'BASE_CURRENCY1', type = 'BASE_CURRENCY', index = 1, data = {roundPrecision = 3}}, + { + id = 'BASE_CURRENCY1', + type = 'BASE_CURRENCY', + index = 1, + data = { + roundPrecision = 3, + currency = 'USD', + } + }, { id = 'LOCAL_CURRENCY1', type = 'LOCAL_CURRENCY', diff --git a/lua/spec/snapshots/prize_pool.png b/lua/spec/snapshots/prize_pool.png index 74dcd66d32d..91ca9bb6aba 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/PrizePool.lua b/lua/wikis/commons/PrizePool.lua index 9c3e78a31b5..881d84ab858 100644 --- a/lua/wikis/commons/PrizePool.lua +++ b/lua/wikis/commons/PrizePool.lua @@ -19,19 +19,17 @@ 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 ChevronToggle = Lua.import('Module:Widget/GeneralCollapsible/ChevronToggle') +local Html = Lua.import('Module:Widget/Html') +local Div = Html.Div +local Label = Lua.import('Module:Widget/Basic/Label') +local PrizePoolCell = Lua.import('Module:Widget/PrizePool/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 +50,17 @@ function PrizePool:readPlacements(args) end ---@param placement PrizePoolPlacement ----@return WidgetTableCell +---@return VNode function PrizePool:placeOrAwardCell(placement) - local placeCell = TableCell{ - children = {placement:getMedal() or '', NON_BREAKING_SPACE, placement:_displayPlace()}, - css = {['font-weight'] = 'bolder'}, - classes = {'prizepooltable-place'}, + local placementDisplay = placement:_lpdbValue() + + return PrizePoolCell{ + children = Label{ + labelScheme = 'prize-pool-placement', + children = placementDisplay + }, + fullHeight = true, } - placeCell.rowSpan = #placement.opponents - - return placeCell end ---@param placement PrizePoolPlacement @@ -79,24 +78,14 @@ function PrizePool:applyCutAfter(placement) 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)) +---@return VNode? +function PrizePool:getCollapsibleToggle() + local placeStart = Array.find(self.placements, function (placement) + return placement.placeStart > self.options.cutafter + end) + if not placeStart then + return end -end - ----@param placeStart number ----@return WidgetTableRow -function PrizePool:_toggleExpand(placeStart) local placeEnd = self.placements[#self.placements].placeEnd if self.options.hideafter < math.huge then @@ -109,25 +98,16 @@ function PrizePool:_toggleExpand(placeStart) placeEnd = lastCut.placeEnd 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{ + local text = 'place ' .. placeStart.placeStart .. ' to ' .. placeEnd + + return Div{ + classes = {'prize-pool-toggle'}, + attributes = {['data-collapsible-click-region'] = 'true'}, children = Div{children = { text, - ' ', - IconFa{iconName = 'collapse'}, - }}, - classes = {'general-collapsible-collapse-button'}, + ChevronToggle{} + }} } - - return TableRow{classes = {'ppt-toggle-expand'}, children = {expandButton, collapseButton}} 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..0b2bd6f57c1 100644 --- a/lua/wikis/commons/PrizePool/Award.lua +++ b/lua/wikis/commons/PrizePool/Award.lua @@ -20,11 +20,11 @@ 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 PrizePoolCell = Lua.import('Module:Widget/PrizePool/Cell') --- @class AwardPrizePool: BasePrizePool --- @operator call(...): AwardPrizePool +--- @field placements AwardPlacement[] local AwardPrizePool = Class.new(BasePrizePool) ---@param args table @@ -52,12 +52,12 @@ end ---@param placement AwardPlacement ---@return WidgetTableCell function AwardPrizePool:placeOrAwardCell(placement) - local awardCell = TableCell{ + local awardCell = PrizePoolCell{ children = {placement.award}, css = {['font-weight'] = 'bolder'}, classes = {'prizepooltable-place'}, + height = #placement.opponents, } - awardCell.rowSpan = #placement.opponents return awardCell end @@ -71,37 +71,31 @@ function AwardPrizePool:applyCutAfter(placement) return false 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 = { +---@return Renderable +function AwardPrizePool:getCollapsibleToggle() + local expandButton = Div{ + children = { 'Show more Awards ', IconFa{iconName = 'expand'}, - }}, + }, classes = {'general-collapsible-expand-button'}, } - local collapseButton = TableCell{ - children = Div{children = { + local collapseButton = Div{ + children = { 'Show less Awards ', IconFa{iconName = 'collapse'}, - }}, + }, classes = {'general-collapsible-collapse-button'}, } - return TableRow{classes = {'ppt-toggle-expand'}, children = {expandButton, collapseButton}} + return Div{ + classes = {'prize-pool-toggle'}, + attributes = {['data-collapsible-click-region'] = 'true'}, + children = Div{ + classes = {'general-collapsible-default-toggle'}, + children = {expandButton, collapseButton} + } + } end -- Get the lpdbObjectName depending on opponenttype diff --git a/lua/wikis/commons/PrizePool/Award/Placement.lua b/lua/wikis/commons/PrizePool/Award/Placement.lua index b64c29a90e2..1402c4fda88 100644 --- a/lua/wikis/commons/PrizePool/Award/Placement.lua +++ b/lua/wikis/commons/PrizePool/Award/Placement.lua @@ -121,6 +121,7 @@ function AwardPlacement:_getLpdbData(...) end -- No BG for awards +---@return string? function AwardPlacement:getBackground() return end diff --git a/lua/wikis/commons/PrizePool/Base.lua b/lua/wikis/commons/PrizePool/Base.lua index 680786f4314..a336712b313 100644 --- a/lua/wikis/commons/PrizePool/Base.lua +++ b/lua/wikis/commons/PrizePool/Base.lua @@ -28,11 +28,10 @@ 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 PrizePoolTable = Lua.import('Module:Widget/PrizePool/Table') +local PrizePoolRow = Lua.import('Module:Widget/PrizePool/Row') +local PrizePoolCell = Lua.import('Module:Widget/PrizePool/Cell') local Div = HtmlWidgets.Div local Span = HtmlWidgets.Span local WidgetUtil = Lua.import('Module:Widget/Util') @@ -43,6 +42,7 @@ local pageVars = PageVariableNamespace('PrizePool') --- @operator call(...): BasePrizePool --- @field options table --- @field _lpdbInjector LpdbInjector? +--- @field placements BasePlacement[] local BasePrizePool = Class.new(function(self, ...) self:init(...) end) ---@class BasePrizePoolPrize @@ -52,7 +52,6 @@ local BasePrizePool = Class.new(function(self, ...) self:init(...) end) ---@field data table local LANG = mw.language.getContentLanguage() -local DASH = '-' local NON_BREAKING_SPACE = ' ' local BASE_CURRENCY = 'USD' local EXCHANGE_SUMMARY_PRECISION = 5 @@ -156,7 +155,12 @@ BasePrizePool.prizeTypes = { headerDisplay = function (data) local currencyText = Currency.display(BASE_CURRENCY) - return TableCell{children = {currencyText}} + return currencyText + end, + headerParse = function (prizePool, input, context, index) + return { + currency = BASE_CURRENCY, + } end, row = BASE_CURRENCY:lower() .. 'prize', @@ -165,10 +169,9 @@ BasePrizePool.prizeTypes = { end, rowDisplay = function (headerData, data) if data > 0 then - return TableCell{children = { - Currency.display(BASE_CURRENCY, data, - {formatValue = true, formatPrecision = headerData.roundPrecision, displayCurrencyCode = false}) - }} + return Currency.display(BASE_CURRENCY, data, { + formatValue = true, formatPrecision = headerData.roundPrecision, displayCurrencyCode = false + }) end end, }, @@ -195,7 +198,7 @@ BasePrizePool.prizeTypes = { } end, headerDisplay = function (data) - return TableCell{children = {Currency.display(data.currency)}} + return Currency.display(data.currency) end, row = 'localprize', @@ -204,10 +207,9 @@ BasePrizePool.prizeTypes = { end, rowDisplay = function (headerData, data) if data > 0 then - return TableCell{children = { - Currency.display(headerData.currency, data, - {formatValue = true, formatPrecision = headerData.roundPrecision, displayCurrencyCode = false}) - }} + return Currency.display(headerData.currency, data, { + formatValue = true, formatPrecision = headerData.roundPrecision, displayCurrencyCode = false + }) end end, @@ -233,7 +235,7 @@ BasePrizePool.prizeTypes = { return {title = 'Percentage'} end, headerDisplay = function (data) - return TableCell{children = {data.title}} + return PrizePoolCell{children = {data.title}} end, row = 'percentage', @@ -247,7 +249,7 @@ BasePrizePool.prizeTypes = { end, rowDisplay = function (headerData, data) if String.isNotEmpty(data) then - return TableCell{children = {data .. '%'}} + return data .. '%' end end, }, @@ -271,7 +273,7 @@ BasePrizePool.prizeTypes = { } end, headerDisplay = function (data) - return TableCell{children = {'Qualifies To'}} + return 'Qualifies To' end, row = 'qualified', @@ -299,7 +301,7 @@ BasePrizePool.prizeTypes = { table.insert(content, '[[' .. headerData.link .. ']]') end - return TableCell{children = {Div{children = content}}} + return table.concat(content) end, mergeDisplayColumns = true, @@ -344,7 +346,7 @@ BasePrizePool.prizeTypes = { table.insert(headerDisplay, text) end - return TableCell{children = {table.concat(headerDisplay)}} + return headerDisplay end, row = 'points', @@ -353,7 +355,7 @@ BasePrizePool.prizeTypes = { end, rowDisplay = function (headerData, data) if data > 0 then - return TableCell{children = {LANG:formatNum(data)}} + return LANG:formatNum(data) end end, }, @@ -365,7 +367,7 @@ BasePrizePool.prizeTypes = { return {title = input} end, headerDisplay = function (data) - return TableCell{children = {data.title}} + return data.title end, row = 'freetext', @@ -374,7 +376,7 @@ BasePrizePool.prizeTypes = { end, rowDisplay = function (headerData, data) if String.isNotEmpty(data) then - return TableCell{children = {data}} + return data end end, } @@ -421,7 +423,10 @@ function BasePrizePool:create() if self:_hasBaseCurrency() then self:setConfig('showBaseCurrency', true) - self:addPrize(PRIZE_TYPE_BASE_CURRENCY, 1, {roundPrecision = self.options.currencyRoundPrecision}) + self:addPrize(PRIZE_TYPE_BASE_CURRENCY, 1, { + currency = BASE_CURRENCY, + roundPrecision = self.options.currencyRoundPrecision + }) if self.options.autoExchange then local canConvertCurrency = function(prize) @@ -589,128 +594,199 @@ end function BasePrizePool:_buildTable(isAward) local headerRow = self:_buildHeader(isAward) - 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())) - }}, + local rows, collapsedRows = self:_buildRows() + + return PrizePoolTable{ + prizeTypes = #self.prizes, + currencies = Array.map(self.prizes, function (prize) + return prize.data.currency + end), + header = headerRow, + toggle = self:getCollapsibleToggle(), + displayedRows = rows, + collapsedRows = collapsedRows, } end ---@param isAward boolean? ----@return WidgetTableRow +---@return VNode function BasePrizePool:_buildHeader(isAward) - local children = {} - - table.insert(children, TableCell{children = {isAward and 'Award' or 'Place'}, css = {['min-width'] = '80px'}}) + local children = { + PrizePoolCell{children = {isAward and 'Award' or '#'}}, + PrizePoolCell{children = {'Participant'}}, + } local previousOfType = {} + + local currencyIndex = 1 + + local nCurrencies = Array.reduce( + self.prizes, + ---@param currencyCount integer + ---@param prize BasePrizePoolPrize + ---@return integer + function (currencyCount, prize) + if Logic.isEmpty(prize.data.currency) then + return currencyCount + end + return currencyCount + 1 + end, + 0 + ) + 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) - table.insert(children, cell) - previousOfType[prize.type] = cell + local header = prizeTypeData.headerDisplay(prize.data) + if nCurrencies > 1 and prize.data.currency then + table.insert(children, PrizePoolCell{ + classes = { + currencyIndex and ( + currencyIndex == 1 and 'toggle-area-content-active' or 'toggle-area-content-inactive' + ) or nil + }, + attributes = { + ['data-toggle-area-content'] = currencyIndex, + }, + children = header, + }) + currencyIndex = currencyIndex + 1 + previousOfType[prize.type] = true + elseif Logic.isNotEmpty(header) then + table.insert(children, PrizePoolCell{children = header}) + previousOfType[prize.type] = true + end end end - table.insert(children, TableCell{children = {'Participant'}, classes = {'prizepooltable-col-team'}}) - - return TableRow{classes = {'prizepooltable-header'}, css = {['font-weight'] = 'bold'}, children = children} + return PrizePoolRow{ + classes = {'prize-pool-table-header-row'}, + children = children, + } end ----@return WidgetTableRow[] +---@return VNode[], VNode[] function BasePrizePool:_buildRows() local rows = {} - local previousPlacement = nil + local collapsedRows = {} 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)) - - 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 + local cells = { + self:placeOrAwardCell(placement), + PrizePoolCell{ + classes = {'opponent-cell'}, + children = Array.map(placement.opponents, function (opponent) + return OpponentDisplay.BlockOpponent{ + opponent = opponent.opponentData, + showPlayerTeam = true, + } + end) + } + } - 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 + ---@type BasePrizePoolPrize[][] + local groupedPrizes = {} - Array.extendWith(lastCellOfType.props.children, cell.props.children) - lastCellOfType.css['flex-direction'] = 'column' + local currentCurrencyIndex = 1 + local currencyIndices = {} - return nil + Array.forEach(self.prizes, function (prize, index) + if index == 1 or prize.type ~= groupedPrizes[#groupedPrizes][1].type then + if prize.data.currency and currencyIndices[prize.data.currency] == nil then + currencyIndices[prize.data.currency:lower()] = currentCurrencyIndex + currentCurrencyIndex = currentCurrencyIndex + 1 end + table.insert(groupedPrizes, {prize}) + return + end + local prizeType = self.prizeTypes[prize.type] + if prizeType.mergeDisplayColumns then + table.insert(groupedPrizes[#groupedPrizes], prize) + else + if prize.data.currency and currencyIndices[prize.data.currency] == nil then + currencyIndices[prize.data.currency:lower()] = currentCurrencyIndex + currentCurrencyIndex = currentCurrencyIndex + 1 + end + table.insert(groupedPrizes, {prize}) + end + end) - previousOfPrizeType[prize.type] = cell - return cell - end) - - Array.forEach(prizeCells, function (prizeCell, columnIndex) - local lastInColumn = previousOpponent[columnIndex] + if Table.size(currencyIndices) == 1 then + currencyIndices = {} + end - ---@cast prizeCell -nil - if Table.isEmpty(prizeCell.props.children) then - prizeCell = BasePrizePool._emptyCell() - end + Array.forEach( + groupedPrizes, + function (prizes) + local prizeType = prizes[1].type + local prizeTypeData = self.prizeTypes[prizeType] + local currency = (prizes[1].data.currency or ''):lower() + local prizeDisplay = Array.map(placement.opponents, function (opponent) + ---@type string[] + local rewards = Array.map(prizes, function (prize) + local reward = opponent.prizeRewards[prize.id] or placement.prizeRewards[prize.id] + return reward and prizeTypeData.rowDisplay(prize.data, reward) or nil + end) + return rewards + end) + + local children = { + PrizePoolCell{ + height = 1, + children = Array.interleave(prizeDisplay[1], HtmlWidgets.Hr{}) + } + } - 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) + for i = 2, #prizeDisplay do + if Array.equals(prizeDisplay[i - 1], prizeDisplay[i]) then + children[#children].props.height = children[#children].props.height + 1 + else + table.insert(children, PrizePoolCell{ + height = 1, + children = Array.interleave(prizeDisplay[i], HtmlWidgets.Hr{}) + }) + end end - end) + local currencyIndex = currency and currencyIndices[currency] or nil + table.insert(cells, PrizePoolCell{ + classes = { + 'prize-cell', + currencyIndex and ( + currencyIndex == 1 and 'toggle-area-content-active' or 'toggle-area-content-inactive' + ) or nil + }, + attributes = { + ['data-toggle-area-content'] = currencyIndex, + }, + children = children + }) + end + ) - local opponentDisplay = tostring(OpponentDisplay.BlockOpponent{ - opponent = opponent.opponentData, - showPlayerTeam = true, - }) - local opponentCss = {['justify-content'] = 'start'} + local row = PrizePoolRow{ + height = #placement.opponents, + children = cells, + placement = placement:getPlacement() + } - 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') + table.insert(collapsedRows, row) + else + table.insert(rows, row) end - local row = TableRow{children = cells, classes = classes} - - table.insert(rows, row) - - previousPlacement = placement end - return rows + return rows, collapsedRows end ---@protected ---@param placement BasePlacement +---@return Renderable function BasePrizePool:placeOrAwardCell(placement) error('Function placeOrAwardCell needs to be implemented by a child class of "Module:PrizePool/Base"') end @@ -730,11 +806,9 @@ function BasePrizePool:applyCutAfter(placement) end ---@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 Renderable? +function BasePrizePool:getCollapsibleToggle() + error('BasePrizePool:getCollapsibleToggle() cannot be called directly and must be overridden.') end ---@return string @@ -814,12 +888,6 @@ function BasePrizePool:_hasBaseCurrency() end)) end ---- Creates an empty table cell ----@return WidgetTableCell -function BasePrizePool._emptyCell() - return TableCell{children = {DASH}} -end - --- Remove all non-numeric characters from an input and changes it to a number. -- Most commonly used on money inputs, as they often contain , or . ---@param input number|string diff --git a/lua/wikis/commons/PrizePool/Placement.lua b/lua/wikis/commons/PrizePool/Placement.lua index 3e11a0935b8..28e53835c59 100644 --- a/lua/wikis/commons/PrizePool/Placement.lua +++ b/lua/wikis/commons/PrizePool/Placement.lua @@ -13,7 +13,6 @@ 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') @@ -323,15 +322,9 @@ function Placement:_displayPlace() return start end ----@return string? -function Placement:getBackground() - for statusName, status in pairs(Placement.specialStatuses) do - if status.active(self.args) then - return PlacementInfo.getBgClass{placement = statusName:lower()} - end - end - - return PlacementInfo.getBgClass{placement = self.placeStart} +---@return number? +function Placement:getPlacement() + return self.placeStart end ---@return string? diff --git a/lua/wikis/commons/PrizePool/Placement/Base.lua b/lua/wikis/commons/PrizePool/Placement/Base.lua index 0d15ed82051..93a7a1ccdc9 100644 --- a/lua/wikis/commons/PrizePool/Placement/Base.lua +++ b/lua/wikis/commons/PrizePool/Placement/Base.lua @@ -242,4 +242,14 @@ function BasePlacement._isValidDateFormat(date) return date:match('%d%d%d%d%-%d%d%-%d%d') and true or false end +---@return string? +function BasePlacement:getBackground() + error('BasePlacement:getBackground() cannot be called directly and must be overridden.') +end + +---@return number? +function BasePlacement:getPlacement() + return +end + return BasePlacement diff --git a/lua/wikis/commons/Widget/ContentSwitch.lua b/lua/wikis/commons/Widget/ContentSwitch.lua index ba93ca9dbd2..d21fbe9d103 100644 --- a/lua/wikis/commons/Widget/ContentSwitch.lua +++ b/lua/wikis/commons/Widget/ContentSwitch.lua @@ -8,10 +8,10 @@ local Lua = require('Module:Lua') local Array = Lua.import('Module:Array') -local Logic = Lua.import('Module:Logic') local Component = Lua.import('Module:Widget/Component') local Html = Lua.import('Module:Widget/Html') +local SwitchPill = Lua.import('Module:Widget/ContentSwitch/Pill') local Div = Html.Div ---@class ContentSwitchTab @@ -19,15 +19,8 @@ local Div = Html.Div ---@field value? string ---@field content? Renderable|Renderable[] ----@class ContentSwitchParameters ----@field tabs ContentSwitchTab[] ----@field variant? 'themed'|'generic' ----@field defaultActive? integer ----@field switchGroup string +---@class ContentSwitchProps: ContentSwitchPillProps ---@field classes? string[] ----@field size? 'extrasmall'|'small'|'medium' ----@field storeValue? boolean ----@field css? table local defaultProps = { variant = 'generic', @@ -36,35 +29,16 @@ local defaultProps = { storeValue = true, } ----@param props ContentSwitchParameters +---@param props ContentSwitchProps ---@return Renderable|Renderable[] local function ContentSwitch(props) local tabs = assert(props.tabs, 'ContentSwitch requires at least the tabs property to be set') - local variant = props.variant local defaultActive = props.defaultActive - local switchGroup = assert(Logic.nilIfEmpty(props.switchGroup), 'ContentSwitch: missing \'switchGroup\' property') if #tabs < 2 then return (tabs[1] or {}).content end - local tabOptions = Array.map(tabs, function(tab, index) - local isActive = index == defaultActive - local classes = {'switch-pill-option', 'toggle-area-button'} - if isActive then - table.insert(classes, 'switch-pill-option-active') - end - - return Div{ - classes = classes, - attributes = { - ['data-toggle-area-btn'] = tostring(index), - ['data-switch-value'] = tab.value or tostring(index), - }, - children = Logic.emptyOr(tab.label, tostring(index)), - } - end) - local contentAreas = Array.map(tabs, function(tab, index) local isActive = index == defaultActive return Div{ @@ -76,35 +50,11 @@ local function ContentSwitch(props) } end) - local switchPillClasses = {'switch-pill'} - if variant == 'generic' then - table.insert(switchPillClasses, 'switch-pill-generic') - end - if props.size == 'small' then - table.insert(switchPillClasses, 'switch-pill-small') - elseif props.size == 'extrasmall' then - table.insert(switchPillClasses, 'switch-pill-extrasmall') - end - - return Div{ classes = {'toggle-area', 'toggle-area-' .. tostring(defaultActive)}, attributes = {['data-toggle-area'] = tostring(defaultActive)}, children = { - Div{ - classes = {'switch-pill-container'}, - css = props.css, - children = { - Div{ - classes = switchPillClasses, - attributes = { - ['data-switch-group'] = switchGroup, - ['data-store-value'] = Logic.readBool(props.storeValue) and 'true' or nil, - }, - children = tabOptions, - }, - }, - }, + SwitchPill(props), Div{ classes = {'content-switch-content-container'}, children = contentAreas, diff --git a/lua/wikis/commons/Widget/ContentSwitch/Pill.lua b/lua/wikis/commons/Widget/ContentSwitch/Pill.lua new file mode 100644 index 00000000000..19a0ff100c4 --- /dev/null +++ b/lua/wikis/commons/Widget/ContentSwitch/Pill.lua @@ -0,0 +1,77 @@ +--- +-- @Liquipedia +-- page=Module:Widget/ContentSwitch/Pill +-- +-- Please see https://github.com/Liquipedia/Lua-Modules to contribute +-- + +local Lua = require('Module:Lua') + +local Array = Lua.import('Module:Array') +local Logic = Lua.import('Module:Logic') + +local Component = Lua.import('Module:Widget/Component') +local Html = Lua.import('Module:Widget/Html') +local Div = Html.Div + +---@class ContentSwitchPillProps +---@field tabs ContentSwitchTab[] +---@field variant? 'themed'|'generic' +---@field defaultActive? integer +---@field switchGroup string +---@field size? 'extrasmall'|'small'|'medium' +---@field storeValue? boolean +---@field css? table + +---@param props ContentSwitchPillProps +---@return Renderable|Renderable[] +local function ContentSwitchPill(props) + local tabs = assert(props.tabs, 'ContentSwitch requires at least the tabs property to be set') + local variant = props.variant + local defaultActive = props.defaultActive + local switchGroup = assert(Logic.nilIfEmpty(props.switchGroup), 'ContentSwitch: missing \'switchGroup\' property') + + local tabOptions = Array.map(tabs, function(tab, index) + local isActive = index == defaultActive + local classes = {'switch-pill-option', 'toggle-area-button'} + if isActive then + table.insert(classes, 'switch-pill-option-active') + end + + return Div{ + classes = classes, + attributes = { + ['data-toggle-area-btn'] = tostring(index), + ['data-switch-value'] = tab.value or tostring(index), + }, + children = Logic.emptyOr(tab.label, tostring(index)), + } + end) + + local switchPillClasses = {'switch-pill'} + if variant == 'generic' then + table.insert(switchPillClasses, 'switch-pill-generic') + end + if props.size == 'small' then + table.insert(switchPillClasses, 'switch-pill-small') + elseif props.size == 'extrasmall' then + table.insert(switchPillClasses, 'switch-pill-extrasmall') + end + + return Div{ + classes = {'switch-pill-container'}, + css = props.css, + children = { + Div{ + classes = switchPillClasses, + attributes = { + ['data-switch-group'] = switchGroup, + ['data-store-value'] = Logic.readBool(props.storeValue) and 'true' or nil, + }, + children = tabOptions, + }, + }, + } +end + +return Component.component(ContentSwitchPill) diff --git a/lua/wikis/commons/Widget/PrizePool/Cell.lua b/lua/wikis/commons/Widget/PrizePool/Cell.lua new file mode 100644 index 00000000000..fbc1ef2636f --- /dev/null +++ b/lua/wikis/commons/Widget/PrizePool/Cell.lua @@ -0,0 +1,36 @@ +--- +-- @Liquipedia +-- page=Module:Widget/PrizePool/Cell +-- +-- Please see https://github.com/Liquipedia/Lua-Modules to contribute +-- + +local Lua = require('Module:Lua') + +local Array = Lua.import('Module:Array') + +local Component = Lua.import('Module:Widget/Component') +local Html = Lua.import('Module:Widget/Html') + +---@class PrizePoolCellProps:HtmlNodeProps +---@field fullHeight boolean? +---@field height integer? + +---@param props PrizePoolCellProps +---@return VNode +local function PrizePoolCell(props) + return Html.Div{ + attributes = props.attributes, + classes = Array.extend( + 'prize-pool-table-cell', + props.classes, + props.fullHeight and 'full-height' or nil + ), + css = { + ['--prize-pool-cell-height'] = props.height + }, + children = props.children, + } +end + +return Component.component(PrizePoolCell) diff --git a/lua/wikis/commons/Widget/PrizePool/Row.lua b/lua/wikis/commons/Widget/PrizePool/Row.lua new file mode 100644 index 00000000000..cfffa59c407 --- /dev/null +++ b/lua/wikis/commons/Widget/PrizePool/Row.lua @@ -0,0 +1,39 @@ +--- +-- @Liquipedia +-- page=Module:Widget/PrizePool/Table +-- +-- Please see https://github.com/Liquipedia/Lua-Modules to contribute +-- + +local Lua = require('Module:Lua') + +local Array = Lua.import('Module:Array') + +local Component = Lua.import('Module:Widget/Component') +local Html = Lua.import('Module:Widget/Html') + +---@class PrizePoolRowProps: HtmlNodeProps +---@field placement string|number? +---@field height integer? + +---@param props PrizePoolRowProps +---@return VNode +local function PrizePoolRow(props) + local css = props.css or {} + css['--prize-pool-row-height'] = props.height + + local attributes = props.attributes or {} + attributes['data-placement'] = props.placement + + return Html.Div{ + classes = Array.extend( + 'prize-pool-table-row', + props.classes + ), + attributes = attributes, + css = css, + children = props.children + } +end + +return Component.component(PrizePoolRow) diff --git a/lua/wikis/commons/Widget/PrizePool/Table.lua b/lua/wikis/commons/Widget/PrizePool/Table.lua new file mode 100644 index 00000000000..48a20ccb6cc --- /dev/null +++ b/lua/wikis/commons/Widget/PrizePool/Table.lua @@ -0,0 +1,91 @@ +--- +-- @Liquipedia +-- page=Module:Widget/PrizePool/Table +-- +-- Please see https://github.com/Liquipedia/Lua-Modules to contribute +-- + +local Lua = require('Module:Lua') + +local Array = Lua.import('Module:Array') +local Currency = Lua.import('Module:Currency') +local Logic = Lua.import('Module:Logic') +local Table = Lua.import('Module:Table') + +local Component = Lua.import('Module:Widget/Component') +local Html = Lua.import('Module:Widget/Html') +local SwitchPill = Lua.import('Module:Widget/ContentSwitch/Pill') +local WidgetUtil = Lua.import('Module:Widget/Util') + +---@class PrizePoolTableProps +---@field header Renderable +---@field prizeTypes integer +---@field displayedRows Renderable[] +---@field toggle Renderable? +---@field collapsedRows Renderable[]? +---@field currencies string[]? + +---@param props PrizePoolTableProps +---@return VNode +local function PrizePoolTable(props) + local isCollapsed = Table.isNotEmpty(props.collapsedRows) + + local prizePoolTable = Html.Div{ + classes = {'prize-pool-table'}, + css = { + ['--prize-pool-columns'] = props.prizeTypes + 2 + }, + children = { + props.header, + Html.Div{ + classes = Array.extend( + 'prize-pool-table-body', + 'content-switch-content-container', + isCollapsed and { + 'general-collapsible', + 'collapsed', + } or nil + ), + children = WidgetUtil.collect( + props.displayedRows, + Table.isNotEmpty(props.collapsedRows) and { + props.toggle, + Html.Div{ + classes = {'should-collapse'}, + children = props.collapsedRows + } + } or nil + ) + } + } + } + + if Logic.isEmpty(props.currencies) or #props.currencies < 2 then + return prizePoolTable + end + + return Html.Div{ + classes = { + 'prize-pool-table-container', + 'toggle-area', + 'toggle-area-1', + }, + attributes = {['data-toggle-area'] = 1}, + children = { + SwitchPill{ + switchGroup = 'prize-pool-currency', + storeValue = true, + defaultActive = 1, + tabs = Array.map(props.currencies, function (currency) + return { + label = Currency.display(currency), + value = currency:lower(), + } + end) + }, + prizePoolTable + } + } +end + +return Component.component(PrizePoolTable) diff --git a/stylesheets/commons/Prizepooltable.scss b/stylesheets/commons/Prizepooltable.scss index 7aebf9e98e4..cf00e47f474 100644 --- a/stylesheets/commons/Prizepooltable.scss +++ b/stylesheets/commons/Prizepooltable.scss @@ -337,3 +337,183 @@ Author: iMarbot } } } + +/******************************************************************************* +Template: Prize Pool Table +*******************************************************************************/ + +.prize-pool-table { + display: grid; + grid-template-columns: repeat( var( --prize-pool-columns ), auto ); + overflow-x: auto; + height: fit-content; + border-radius: 0.5rem; + border: 1px solid var( --clr-on-surface-light-primary-12 ); + background-color: var( --clr-background, #ffffff ); + color: var( --clr-secondary-16 ); + color-scheme: light; + + .theme--dark & { + background-color: var( --clr-background, #121212 ); + border-color: var( --clr-on-surface-dark-primary-12 ); + color: var( --clr-secondary-100 ); + color-scheme: dark; + } + + &-container { + display: flex; + flex-direction: column; + gap: 0.5rem; + + > .switch-pill-container { + width: fit-content; + } + } + + &-body { + display: grid; + grid-column: 1 / -1; + grid-template-columns: subgrid; + + > .prize-pool-toggle { + display: inline-block; + grid-column: 1 / -1; + padding: 0.5rem 0.75rem; + place-self: center; + width: 100%; + cursor: pointer; + font-weight: bold; + + > div { + position: sticky; + left: 0.75rem; + } + + .general-collapsible-default-toggle { + min-width: unset; + + .general-collapsible-expand-button, + .general-collapsible-expand-button:visited, + .general-collapsible-collapse-button, + .general-collapsible-collapse-button:visited { + outline: unset; + color: var( --clr-secondary-7 ); + + .theme--dark & { + color: var( --clr-secondary-90 ); + } + } + } + } + + &:not( .collapsed ) > .should-collapse { + display: contents; + } + } + + &-row { + display: grid; + grid-column: 1 / -1; + grid-template-columns: subgrid; + grid-template-rows: repeat( var( --prize-pool-row-height, 1 ), auto ); + background-color: var( --prize-pool-placement-background-color ); + + &.prize-pool-table-header-row { + font-weight: bold; + background-color: var( --clr-on-surface-light-primary-4 ); + color: var( --clr-secondary-25 ); + + .theme--dark & { + background-color: var( --clr-on-surface-dark-primary-4 ); + color: var( --clr-secondary-80 ); + } + } + + &:nth-of-type( even ) { + --prize-pool-placement-background-color: var( --clr-on-surface-light-primary-4 ); + --prize-pool-placement-label-color: var( --clr-secondary-25 ); + + .theme--dark & { + --prize-pool-placement-background-color: var( --clr-on-surface-dark-primary-4 ); + --prize-pool-placement-label-color: var( --clr-secondary-100 ); + } + } + + &:hover { + --prize-pool-placement-background-color: var( --clr-on-surface-light-primary-8 ); + + .theme--dark & { + --prize-pool-placement-background-color: var( --clr-on-surface-dark-primary-8 ); + } + } + + &[ data-placement ] { + border-left: 0.25rem solid var( --prize-pool-placement-color, transparent ); + + .generic-label.label--prize-pool-placement { + background-color: var( --prize-pool-placement-color, transparent ); + color: var( --prize-pool-placement-label-color ); + font-weight: bold; + } + } + + &[ data-placement="1" ] { + --prize-pool-placement-color: #f0b419; + --prize-pool-placement-background-color: #fff9eb; + --prize-pool-placement-label-color: var( --clr-secondary-100 ); + + .theme--dark & { + --prize-pool-placement-color: #fbcb50; + --prize-pool-placement-background-color: hsl( from var( --prize-pool-placement-color ) h s l / 0.16 ); + --prize-pool-placement-label-color: var( --clr-secondary-16 ); + } + } + + &[ data-placement="2" ] { + --prize-pool-placement-color: #58b5e3; + --prize-pool-placement-background-color: #f0fcff; + --prize-pool-placement-label-color: var( --clr-secondary-100 ); + + .theme--dark & { + --prize-pool-placement-color: #91d7f9; + --prize-pool-placement-background-color: hsl( from var( --prize-pool-placement-color ) h s l / 0.16 ); + --prize-pool-placement-label-color: var( --clr-secondary-16 ); + } + } + + &[ data-placement="3" ] { + --prize-pool-placement-color: #de7d1d; + --prize-pool-placement-background-color: #fff3ec; + --prize-pool-placement-label-color: var( --clr-secondary-100 ); + + .theme--dark & { + --prize-pool-placement-color: #ffb267; + --prize-pool-placement-background-color: hsl( from var( --prize-pool-placement-color ) h s l / 0.16 ); + --prize-pool-placement-label-color: var( --clr-secondary-16 ); + } + } + } + + &-cell { + display: flex; + grid-row: span var( --prize-pool-cell-height, 1 ); + padding: 0.5rem 0.75rem; + align-items: center; + + &.full-height { + grid-row: span var( --prize-pool-row-height, 1 ); + } + + &:has( > & ) { + padding: unset; + } + + &.prize-cell, + &.opponent-cell:has( > :nth-child( 2 ) ) { + display: grid; + grid-row: span var( --prize-pool-row-height, 1 ); + row-gap: 0.5rem; + grid-template-rows: subgrid; + } + } +}