diff --git a/lua/spec/team_participants_tbd_spec.lua b/lua/spec/team_participants_tbd_spec.lua index 8608e4a0414..1a1ac1a2ba8 100644 --- a/lua/spec/team_participants_tbd_spec.lua +++ b/lua/spec/team_participants_tbd_spec.lua @@ -89,10 +89,10 @@ describe('Team Participants TBD Functionality', function() TeamParticipantsWikiParser.fillIncompleteRoster(opponent, 5) assert.are_equal(7, #opponent.players) - local actualPlayers = Array.filter(opponent.players, function(p) - return p.extradata.type == 'player' + local activePlayers = Array.filter(opponent.players, function(p) + return p.extradata.type == 'player' and not p.extradata.status end) - assert.are_equal(5, #actualPlayers) + assert.are_equal(5, #activePlayers) end) it('handles missing data gracefully', function() @@ -184,14 +184,14 @@ describe('Team Participants TBD Functionality', function() TeamParticipantsController.fillIncompleteRosters(parsedData) local opponent = parsedData.participants[1].opponent - local actualPlayers = Array.filter(opponent.players, function(p) - return p.extradata.type == 'player' + local activePlayers = Array.filter(opponent.players, function(p) + return p.extradata.type == 'player' and not p.extradata.status end) - assert.are_equal(5, #actualPlayers) - assert.are_equal('alexis', actualPlayers[1].displayName) - assert.are_equal('TBD', actualPlayers[4].displayName) - assert.are_equal('TBD', actualPlayers[5].displayName) + assert.are_equal(5, #activePlayers) + assert.are_equal('alexis', activePlayers[1].displayName) + assert.are_equal('TBD', activePlayers[4].displayName) + assert.are_equal('TBD', activePlayers[5].displayName) LpdbQuery:revert() TeamTemplateMock.tearDown() diff --git a/lua/wikis/commons/TeamParticipants/Controller.lua b/lua/wikis/commons/TeamParticipants/Controller.lua index 45960bb575f..ff03ef69e71 100644 --- a/lua/wikis/commons/TeamParticipants/Controller.lua +++ b/lua/wikis/commons/TeamParticipants/Controller.lua @@ -105,11 +105,11 @@ function TeamParticipantsController.importSquadMembersFromDatabase(participant) end) return Array.map(membersToImport, function (member) - local memberType = member.type + local status if member.hasLeft then - memberType = 'former' + status = 'former' elseif member.role and member.role:lower() == 'substitute' then - memberType = 'sub' + status = 'sub' end return TeamParticipantsWikiParser.parsePlayer{ member.displayName, @@ -117,7 +117,8 @@ function TeamParticipantsController.importSquadMembersFromDatabase(participant) flag = member.nationality, faction = member.faction, role = member.role, - type = memberType, + type = member.type, + status = status, } end) end diff --git a/lua/wikis/commons/TeamParticipants/Parse/Wiki.lua b/lua/wikis/commons/TeamParticipants/Parse/Wiki.lua index 9d2d7f94859..ae2c8e00b5e 100644 --- a/lua/wikis/commons/TeamParticipants/Parse/Wiki.lua +++ b/lua/wikis/commons/TeamParticipants/Parse/Wiki.lua @@ -211,18 +211,30 @@ function TeamParticipantsWikiParser.parsePlayer(playerInput) local playedInput = Logic.readBoolOrNil(playerInput.played) local resultsInput = Logic.readBoolOrNil(playerInput.results) local roles = RoleUtil.readRoleArgs(playerInput.role) - local playerType = playerInput.type or 'player' - - local hasNoStaffRoles = Array.all(roles, function(role) return role.type ~= RoleUtil.ROLE_TYPE.STAFF end) + local inputType = playerInput.type or 'player' + local hasStaffRoles = Array.any(roles, function(role) return role.type == RoleUtil.ROLE_TYPE.STAFF end) + + local status = playerInput.status + if not status then + if inputType == 'former' then + status = 'former' + elseif inputType == 'sub' then + status = 'sub' + end + end - if playerType ~= 'staff' and not hasNoStaffRoles then + local playerType + if inputType == 'staff' or hasStaffRoles then playerType = 'staff' + else + playerType = 'player' end player.extradata = { roles = roles, trophies = tonumber(playerInput.trophies), type = playerType, + status = status, played = Logic.nilOr(playedInput, true), results = Logic.nilOr(resultsInput, playedInput, true), } @@ -239,16 +251,16 @@ function TeamParticipantsWikiParser.fillIncompleteRoster(opponent, minimumPlayer return end - local actualPlayers = Array.filter(opponent.players, function(player) - return player.extradata.type == 'player' + local activePlayers = Array.filter(opponent.players, function(player) + return player.extradata.type == 'player' and not player.extradata.status end) - local actualPlayerCount = #actualPlayers - if actualPlayerCount >= expectedPlayerCount then + local activePlayerCount = #activePlayers + if activePlayerCount >= expectedPlayerCount then return end - local tbdPlayers = TeamParticipantsWikiParser.createTBDPlayers(expectedPlayerCount - actualPlayerCount) + local tbdPlayers = TeamParticipantsWikiParser.createTBDPlayers(expectedPlayerCount - activePlayerCount) Array.extendWith(opponent.players, tbdPlayers) end diff --git a/lua/wikis/commons/Widget/Participants/Team/PotentialQualifiers.lua b/lua/wikis/commons/Widget/Participants/Team/PotentialQualifiers.lua index 0945255a2ae..e5e5dcea239 100644 --- a/lua/wikis/commons/Widget/Participants/Team/PotentialQualifiers.lua +++ b/lua/wikis/commons/Widget/Participants/Team/PotentialQualifiers.lua @@ -30,7 +30,7 @@ function PotentialQualifiers:render() local children = { Div{ - classes = {'team-participant-card__potential-qualifiers-title'}, + classes = {'team-participant-card__subheader'}, children = 'Potential qualifiers' }, Div{ diff --git a/lua/wikis/commons/Widget/Participants/Team/Roster.lua b/lua/wikis/commons/Widget/Participants/Team/Roster.lua index 60966fab459..442608350bd 100644 --- a/lua/wikis/commons/Widget/Participants/Team/Roster.lua +++ b/lua/wikis/commons/Widget/Participants/Team/Roster.lua @@ -38,13 +38,15 @@ local TAB_DATA = { [TAB_ENUM.STAFF] = {title = 'Staff', order = 4}, } ----@type table -local PERSON_TYPE_TO_TAB = { - player = TAB_ENUM.MAIN, - sub = TAB_ENUM.SUB, - former = TAB_ENUM.FORMER, - staff = TAB_ENUM.STAFF, -} +---@param player table +---@return ParticipantsTeamCardTabs +local function getPlayerTab(player) + local status = player.extradata.status + if status == 'former' then return TAB_ENUM.FORMER end + if status == 'sub' then return TAB_ENUM.SUB end + if player.extradata.type == 'staff' then return TAB_ENUM.STAFF end + return TAB_ENUM.MAIN +end -- The biz logic behind the role display is somewhat complicated. -- There's 2 areas we show the role, left-role and right-role @@ -59,7 +61,6 @@ local PERSON_TYPE_TO_TAB = { ---@return string?, string[]? local function getRoleDisplays(player) local roles = player.extradata.roles or {} - local playerType = player.extradata.type local played = player.extradata.played local function roleLeftDisplay() @@ -76,7 +77,7 @@ local function getRoleDisplays(player) local function roleRightDisplay() local rightRoles = {} -- Add status label first (Left or DNP) - if playerType == 'former' then + if player.extradata.status == 'former' then table.insert(rightRoles, 'Left') elseif not played then table.insert(rightRoles, 'DNP') @@ -100,11 +101,11 @@ local ParticipantsTeamRoster = Class.new(Widget) ---@return Widget function ParticipantsTeamRoster:render() local participant = self.props.participant - local makeRostersDisplay = function(players) - -- Used for making the sorting stable + + -- Used for making the sorting stable + local sortPlayers = function(players) local playerToIndex = Table.map(players, function(index, player) return player, index end) - -- Sort the players based on their roles first, then by their original order - players = Array.sortBy(players, FnUtil.identity, function (a, b) + return Array.sortBy(players, FnUtil.identity, function(a, b) local function getPlayerSortOrder(player) local roles = player.extradata.roles or {} return roles[1] and roles[1].sortOrder or math.huge @@ -116,38 +117,76 @@ function ParticipantsTeamRoster:render() end return orderA < orderB end) + end + + local makePlayerWidget = function(player, index) + local playerTeam = participant.opponent.template ~= player.team and player.team or nil + local playerTeamAsOpponent = playerTeam and Opponent.readOpponentArgs{ + type = Opponent.team, + template = playerTeam, + } or nil + local roleLeft, roleRight = getRoleDisplays(player) + return ParticipantsTeamMember{ + player = player, + team = playerTeamAsOpponent, + even = index % 2 == 0, + roleLeft = roleLeft, + roleRight = roleRight, + trophies = player.extradata.trophies or 0, + } + end + + ---@param groups {label: string?, players: table[]}[] + local makeRostersDisplay = function(groups) + local children = {} + for _, group in ipairs(groups) do + if group.label then + table.insert(children, Div{ + classes = {'team-participant-card__subheader'}, + children = group.label, + }) + end + table.insert(children, Div{ + classes = { 'team-participant-roster' }, + children = Array.map(group.players, makePlayerWidget), + }) + end return Div{ classes = { 'team-participant-roster' }, - children = Array.map(players, function(player, index) - local playerTeam = participant.opponent.template ~= player.team and player.team or nil - local playerTeamAsOpponent = playerTeam and Opponent.readOpponentArgs{ - type = Opponent.team, - template = playerTeam, - } or nil - local roleLeft, roleRight = getRoleDisplays(player) - return ParticipantsTeamMember{ - player = player, - team = playerTeamAsOpponent, - even = index % 2 == 0, - roleLeft = roleLeft, - roleRight = roleRight, - trophies = player.extradata.trophies or 0, - strikethrough = player.extradata.type == 'former', - } - end) + children = children, } end local tabs = Array.map(Table.entries(TAB_DATA), function(tabTuple) local tabTypeEnum, tabData = tabTuple[1], tabTuple[2] - local tabPlayers = Array.filter(participant.opponent.players or {}, function(player) - local personType = player.extradata.type - return PERSON_TYPE_TO_TAB[personType] == tabTypeEnum - end) + local tabPlayers = sortPlayers(Array.filter(participant.opponent.players or {}, function(player) + return getPlayerTab(player) == tabTypeEnum + end)) + + local groups + if tabTypeEnum == TAB_ENUM.FORMER then + local formerPlayers = Array.filter(tabPlayers, function(player) + return player.extradata.type ~= 'staff' + end) + local formerStaff = Array.filter(tabPlayers, function(player) + return player.extradata.type == 'staff' + end) + if #formerPlayers > 0 and #formerStaff > 0 then + groups = { + { label = 'Players', players = formerPlayers }, + { label = 'Staff', players = formerStaff }, + } + end + end + if not groups then + groups = { { players = tabPlayers } } + end + return { order = tabData.order, title = tabData.title, type = tabTypeEnum, + groups = groups, players = tabPlayers, } end) @@ -162,7 +201,8 @@ function ParticipantsTeamRoster:render() and #tabs[2].players == 1 then -- If we only have main and staff, and exactly one staff, just show both rosters without a switch - return makeRostersDisplay(Array.extend(tabs[1].players, tabs[2].players)) + local mergedPlayers = sortPlayers(Array.extend(tabs[1].players, tabs[2].players)) + return makeRostersDisplay({ { players = mergedPlayers } }) end tabs = Array.sortBy(tabs, Operator.property('order')) @@ -177,7 +217,7 @@ function ParticipantsTeamRoster:render() tabs = Array.map(tabs, function(tab) return { label = tab.title, - content = makeRostersDisplay(tab.players), + content = makeRostersDisplay(tab.groups), } end), } diff --git a/stylesheets/commons/TeamParticipantCard.scss b/stylesheets/commons/TeamParticipantCard.scss index c202ec485e0..7f0fa6364e9 100644 --- a/stylesheets/commons/TeamParticipantCard.scss +++ b/stylesheets/commons/TeamParticipantCard.scss @@ -427,20 +427,6 @@ $compact-selector: '[data-switch-group="team-cards-compact"]'; flex-direction: column; gap: 0.25rem; - &-title { - padding: 0 0.5rem; - font-size: 0.875rem; - font-weight: bold; - - .theme--light & { - color: var( --clr-secondary-25 ); - } - - .theme--dark & { - color: var( --clr-secondary-90 ); - } - } - &-list { display: flex; flex-direction: column; @@ -467,6 +453,20 @@ $compact-selector: '[data-switch-group="team-cards-compact"]'; } } } + + &__subheader { + padding: 0 0.5rem; + font-size: 0.875rem; + font-weight: bold; + + .theme--light & { + color: var( --clr-secondary-25 ); + } + + .theme--dark & { + color: var( --clr-secondary-90 ); + } + } } body:has( .switch-toggle-active#{ $compact-selector } ) {