Module:TeamMembers

local util_args = require('Module:ArgsUtil') local util_cargo = require("Module:CargoUtil") local util_esports = require("Module:EsportsUtil") local util_data = require("Module:DataUtil") local util_form = require("Module:FormUtil") local util_html = require("Module:HtmlUtil") local util_map = require("Module:MapUtil") local util_news = require("Module:NewsUtil") local util_sort = require("Module:SortUtil") local util_source = require("Module:SourceUtil") local util_table = require("Module:TableUtil") local util_text = require("Module:TextUtil") local util_time = require("Module:TimeUtil") local util_title = require("Module:TitleUtil") local util_toggle = require("Module:ToggleUtil") local util_vars = require("Module:VarsUtil") local i18n = require('Module:i18nUtil')

local m_team = require('Module:Team') local Region = require('Module:Region') local RoleList = require('Module:RoleList')

local DEBUG = false

local ARGS = { 'Player', 'Country', 'Residency', 'IrlName', 'Role', 'DateJoin', 'DateLeave', 'ContractEnd', 'SortKey' }

local SETTINGS = require('Module:TeamMembers/Settings')

local TOGGLES_DATE = { order = { 'approx', 'exact' }, key = 'date' }

local h = {}

local p = {} function p.fromCargo(frame) i18n.init('TeamMembers', 'NewsUtil') local args = util_args.merge h.castArgs(args) h.setConstants(args) h.setColumns util_toggle.oflInit(TOGGLES_DATE) local listOfChanges = h.queryForChanges(args.team or mw.title.getCurrentTitle.text, args) h.processChangesRows(listOfChanges) local players = h.computePlayers(listOfChanges) return h.getPlayerInfoAndMakeOutput(players, args) end

function p.fromArgs(frame, args) i18n.init('TeamMembers') local args = util_args.merge h.castArgs(args) h.setConstants(args) h.setColumns util_toggle.oflInit(TOGGLES_DATE) local players = h.getPlayersFromArgs(args) return h.getPlayerInfoAndMakeOutput(players) end

-- private function h.castArgs(args) args.team = args.team and m_team.teamlinkname(args.team) args.debug = util_args.castAsBool(args.debug) if not args.when then args.when = 'current' end end

function h.setConstants(args) DEBUG = args.debug if not SETTINGS[args.when] then error(i18n.print('error_InvalidWhen')) end SETTINGS = SETTINGS[args.when] end

function h.setColumns if DEBUG then SETTINGS.columns[#SETTINGS.columns+1] = '_pageName' end end

-- from cargo function h.queryForChanges(team, args) return util_cargo.queryAndCast(h.getChangesQuery(team, args)) end

function h.getChangesQuery(team, args) local query = { tables = { 'TeamRedirects=TR', 'TenuresUnbroken=Tenures', 'TenuresUnbroken__RosterChangeIds=RCID', 'RosterChanges=RC', 'NewsItems=News', -- this PR is for a field, not a join 'PlayerRedirects=PR', },		join = { 'TR.AllName=Tenures.Team', 'Tenures._ID=RCID._rowID', 'RCID._value=RC.RosterChangeId', 'RC.NewsId=News.NewsId', 'Tenures.Player=PR.AllName', },		where = h.getChangesWhere(team, args), fields = h.getChangesFields, oneToMany = h.getChangesOneToMany, -- object_types = { -- 	Role = { -- 		'RoleList', -- 		modifier = 'RoleModifier', -- 	}		-- }	}	return query end

function h.getChangesWhere(team, args) local tbl = { ('TR._pageName="%s"'):format(team), SETTINGS.where, }	return util_cargo.concatWhere(tbl) end

function h.getChangesFields local ret = { -- PlayerKey is needed here so we have a standardized way of matching up with -- PR.AllName in the playerExtraInfo field later on		-- this is due to Cargo treating modified unicode letters the same as		-- their non-unicode varieties 'PR.AllName=PlayerKey', 'Tenures.Player', 'Tenures.NameLeave=Name', 'Tenures._ID=TenuresPrimaryKey', 'Tenures.DateJoin=DateJoin', 'Tenures.DateLeave=DateLeave', 'Tenures.ContractEnd', 'Tenures.ResidencyLeave=Residency [region]', 'Tenures.NameLeave=Name', 'Tenures.NextTeam', 'Tenures.NextIsRetired [boolean]', 'Tenures.IsCurrent [boolean]', }	return ret end

function h.getChangesOneToMany local oneToMany = { groupBy = { 'TenuresPrimaryKey' }, fields = { RosterChanges = { 'RC.Role', 'RC.RoleModifier', 'RC.RolesIngame__full=RolesIngame', 'RC.Roles__full=Roles', 'RC.Status', 'RC.Direction', 'News.Date_Display', 'News.Date_Sort', 'News.IsApproxDate [boolean]', 'News.Source', 'News.Sentence', }		}	}	return oneToMany end

function h.processChangesRows(listOfChanges) util_map.rowsInPlace(listOfChanges, h.processOneChangeRow) end

function h.processOneChangeRow(row) h.moveRosterChangesToExpectedNames(row) -- only completely unambiguous constants should be handled here -- anything that can change before/after needs to be gotten from the LAST roster change -- in the function below this row.SortKeyName = row.Name:lower row.SortKeyLeave = row.DateLeave row.DateDisplayJoin = h.getDateDisplay(row, 'Join') row.DateDisplayLeave = h.getDateDisplay(row, 'Leave') end

function h.moveRosterChangesToExpectedNames(row) if not row.RosterChanges or not row.RosterChanges[1] then return end if row.RosterChanges[1].Direction == 'Join' then row.Date_DisplayJoin = row.RosterChanges[1].Date_Display row.Date_SortJoin = row.RosterChanges[1].Date_Sort row.IsApproxDateJoin = row.RosterChanges[1].IsApproxDate row.SourceJoin = row.RosterChanges[1].Source row.SentenceJoin = row.RosterChanges[1].Sentence end local last = row.RosterChanges[#row.RosterChanges] row.RoleModifier = last.RoleModifier row.Role = last.Role row.Status = last.Status -- we could change this to be a RoleList, or we can just listen to the ingame/all -- params that we got from db	-- either way is fine, and i don't see any inherent advantage to one over the other row.RolesIngame = RoleList(last.RolesIngame, { modifier = row.RoleModifier }) row.Roles = RoleList(last.Roles, { modifier = row.RoleModifier }) if row.Roles:hasIngame then row.SortKeyRole = row.RolesIngame:sortnumber else row.SortKeyRole = row.Roles:sortnumber end if last.Direction == 'Leave' then row.Date_DisplayLeave = last.Date_Display row.Date_SortLeave = last.Date_Sort row.IsApproxDateLeave = last.IsApproxDate row.SourceLeave = last.Source row.SentenceLeave = last.Sentence end end

function h.getDateDisplay(row, when) return ('%s %s '):format(		util_news.getDateDisplayForTable(row, when) or '',		util_news.getSentenceAndRefDisplay(row, when)	) end

function h.getRefSentencePopup(row, when) local popup = util_toggle.popupButton popup.inner:wikitext(row['Sentence' .. when]) :addClass('team-members-sentence') popup.wrapper:addClass('team-members-sentence-wrapper') popup.span:addClass('team-members-sentence-span') return tostring(popup.span) end

function h.computePlayers(listOfChanges) local players = { keys = {} } for _, row in ipairs(listOfChanges) do		if h.isAPlayer(row) then util_table.push(players.keys, row.Player) row.PlayerKey = mw.ustring.lower(row.PlayerKey) players[#players+1] = row end end return players end

function h.isAPlayer(row) -- this could end up more complex later if row.Status == 'official_sub' or row.RolesIngame:exists then return true end end

-- from args function h.getPlayersFromArgs(args) local arr = util_args.splitArgsArray(args.members, ARGS) local players = { keys = {} } for _, row in ipairs(arr) do		local key = util_title.escape(mw.ustring.lower(row.Player)) util_table.push(players.keys, key) row.PlayerKey = key players[#players+1] = row end return players end

-- merge from cargo and from args function h.getPlayerInfoAndMakeOutput(players, args) if not next(players.keys) then return h.makeNoResultsOutput end local playerExtraInfo = h.queryForPlayerExtraInfo(players) util_map.rowBlobInPlace(playerExtraInfo, h.formatOnePlayerExtraInfo) util_map.rowsInPlace(players, h.formatOnePlayer) h.addInfoToPlayers(players, playerExtraInfo) util_map.rowsInPlace(players, h.formatOneFinalPlayer) util_sort.tablesByKeys(players, SETTINGS.sort_fields, SETTINGS.sort_ascending) util_data.removeUnusedColumns(SETTINGS.columns, players) return h.makeNotice(args), h.makeOutput(players) end

function h.makeNoResultsOutput if util_vars.getBool('isdisbanded') then return '' end return i18n.print('noResultsText') end

-- Output

function h.queryForPlayerExtraInfo(players) local dict = util_cargo.getRowDict(h.getPlayerExtraInfoQuery(players), 'PlayerKey') return h.normalizePlayerExtraInfo(dict) end

function h.getPlayerExtraInfoQuery(players) local query = { tables = { 'PlayerRedirects=PR', 'Players=P', 'Alphabets=A', },		join = { 'PR._pageName=P._pageName', 'P.NameAlphabet=A.Alphabet', },		fields = h.getPlayerExtraInfoFields, where = h.getPlayerExtraInfoWhere(players), }	return query end

function h.getPlayerExtraInfoFields local ret = { -- matches the PlayerKey from earlier 'PR.AllName=PlayerKey', 'COALESCE(P.NationalityPrimary,P.Country)=Country [country]', 'P.Name=IrlName', 'P.NativeName', 'P.Residency [region]', 'P.Team=CurrentTeam', 'P._pageName=PlayerPage', 'A.IsTransliterated[boolean]' }	return ret end

function h.getPlayerExtraInfoWhere(players) return util_cargo.concatWhereOr(		util_map.format( players.keys, 'PR.AllName="%s"' )	) end

function h.formatOnePlayerExtraInfo(row) row.Contract = row.Contract or '-' end

function h.formatOnePlayer(row) row.PlayerDisplay = util_esports.playerLinked(row.Name) end

function h.normalizePlayerExtraInfo(dict) local normalized = {} for k, v in pairs(dict) do		normalized[mw.ustring.lower(k)] = v	end return normalized end

-- add info to players function h.addInfoToPlayers(players, playerExtraInfo) -- PlayerKey in both cases is from PlayerRedirects -- so we'll guarantee in both cases get the PR version of the name -- we also lowercased it earlier, but that shouldn't actually matter for _, playerData in ipairs(players) do		-- if not playerExtraInfo[playerData.PlayerKey] then -- util_vars.log(playerData.PlayerKey) -- end util_table.mergeDontOverwrite(playerData, playerExtraInfo[playerData.PlayerKey]) end end

function h.formatOneFinalPlayer(row) row.classes = {} row.Country = row.Country:image row.Residency = row.Residency:image row.RoleDisplay = h.getRoleDisplay(row) row.IrlNameDisplay = h.getIrlNameDisplay(row) h.addNextTeamDisplay(row) local teamHistPopup = h.getTeamHistPopup(row.Player) row.NextTeamDisplay = row.NextTeamDisplay .. teamHistPopup row.DateDisplayJoinWithPopup = h.concatDateDisplayAndPopup(row.DateDisplayJoin, teamHistPopup) if row.ContractEnd and not util_time.dateIsInFuture(row.ContractEnd) then row.classes.ContractEnd = 'team-members-expired' end row.attrs = { RoleDisplay = { ['data-sort-value'] = row.SortKeyRole, },		DateDisplayJoinWithPopup = { ['data-sort-value'] = util_time.unix(row.Date_SortJoin) },		DateDisplayJoin = { ['data-sort-value'] = util_time.unix(row.Date_SortJoin) },		DateDisplayLeave = { ['data-sort-value'] = util_time.unix(row.Date_SortLeave) },	}	row.classes.DateDisplayJoinWithPopup = 'team-members-date-with-popup' end

function h.getRoleDisplay(row) local role = row.RolesIngame:flairs{len='role'} or row.Roles:flairs{len='role'} if not row.Status then return role end local toggle = util_toggle.allToggleAll(nil, 'member-statuses') toggle.button:wikitext('(+) ') toggle.button2:wikitext('(–) ') toggle.content:wikitext((' %s'):format(i18n.print(row.Status))) return role .. ' ' .. tostring(toggle.tbl) end

function h.getIrlNameDisplay(row) if not row.NativeName then return row.IrlName end if not row.IrlName then return nil end if not row.IsTransliterated then return row.IrlName end return ('%s (%s)'):format(row.IrlName, row.NativeName) end

function h.addNextTeamDisplay(row) if row.NextIsRetired then row.NextTeamDisplay = m_team.rightshort('retired') return end row.NextTeam = row.NextTeam or row.CurrentTeam if row.NextTeam then row.NextTeamDisplay = m_team.rightshortlinked(row.NextTeam) return end row.classes.NextTeamDisplay = 'newteam-none' row.NextTeamDisplay = 'None' end

function h.getTeamHistPopup(player) local button = util_toggle.popupButtonLazy(		nil,		'tmtimeline',		('PlayerTeamHistoryPopup|%s'):format(player)	) return tostring(button.span) end

-- function h.concatDateDisplayAndPopup(date, popup) local output = mw.html.create output:tag('div') :addClass('team-members-date-container') :wikitext(date) output:wikitext(popup) return tostring(output) end

-- output function h.makeNotice(args) if args.when ~= 'current' then return '' end return mw.getCurrentFrame:expandTemplate{title = 'ContractExpirationNotice' } end

function h.makeOutput(players) local output = mw.html.create h.printToggler(output) local tbl = output:tag('table') :addClass('wikitable') :addClass('sortable') :addClass('team-members') :addClass('hoverable-rows') :addClass(SETTINGS.parent_class) util_html.printHeaderFromI18n(tbl, SETTINGS.columns) h.printRows(tbl, players) return output end

function h.printToggler(output) local div = output:tag('div') :addClass('toggle-button') div:wikitext(i18n.print('toggleDatesIntro')) util_toggle.printOptionFromListTogglers(div, TOGGLES_DATE) end

function h.printRows(tbl, players) for _, row in ipairs(players) do		local tr = util_html.printRowByList(tbl, row, SETTINGS.columns) h.printRefreshButton(util_html.lastChild(tr), row.PlayerPage) end end

function h.printRefreshButton(td, player) td:addClass('lastcell') local div = td:tag('div') :addClass('lastcell-action-pretty') :addClass('team-members-refresh') :attr('data-player', player) end

return p