diff --git a/src/frontend/overlay/src/components/molecules/info/ContestantInfo.tsx b/src/frontend/overlay/src/components/molecules/info/ContestantInfo.tsx index 28c3edd48..d205d7523 100644 --- a/src/frontend/overlay/src/components/molecules/info/ContestantInfo.tsx +++ b/src/frontend/overlay/src/components/molecules/info/ContestantInfo.tsx @@ -6,7 +6,6 @@ import { ShrinkingBox } from "../../atoms/ShrinkingBox"; import { RankLabel } from "../../atoms/ContestLabels"; import { formatScore, useFormatPenalty } from "@/services/displayUtils"; import { useAppSelector } from "@/redux/hooks"; -import { Award } from "@shared/api"; const ContestantInfoLabel = styled(RankLabel)` @@ -49,12 +48,10 @@ const ContestantInfoScoreLabel = styled(ShrinkingBox)` export const ContestantInfo = ({ teamId, roundBR= true, className = null }) => { const contestInfo = useAppSelector((state) => state.contestInfo.info); const scoreboardData = useAppSelector((state) => state.scoreboard[SCOREBOARD_TYPES.normal].ids[teamId]); - const awards = useAppSelector((state) => state.scoreboard[SCOREBOARD_TYPES.normal].idAwards[teamId]); - const medal = awards.find((award) => award.type == Award.Type.medal) as Award.medal; const teamData = useAppSelector((state) => state.contestInfo.info?.teamsId[teamId]); const formatPenalty = useFormatPenalty(); return - + diff --git a/src/frontend/overlay/src/components/molecules/info/ContestantViewLine.tsx b/src/frontend/overlay/src/components/molecules/info/ContestantViewLine.tsx index 18277e508..1f1e4341f 100644 --- a/src/frontend/overlay/src/components/molecules/info/ContestantViewLine.tsx +++ b/src/frontend/overlay/src/components/molecules/info/ContestantViewLine.tsx @@ -66,7 +66,7 @@ interface ContestantViewLineProps { } export const ContestantViewLine = ({ teamId, isSmall, className, isTop }: ContestantViewLineProps) => { - const scoreboardData = useAppSelector((state) => state.scoreboard[SCOREBOARD_TYPES.normal]?.ids[teamId]); + const scoreboardData: LegacyScoreboardRow = useAppSelector((state) => state.scoreboard[SCOREBOARD_TYPES.normal]?.ids[teamId]); // for (let i = 0; i < scoreboardData?.problemResults.length; i++) { // scoreboardData.problemResults[i]["index"] = i; // } diff --git a/src/frontend/overlay/src/components/organisms/tickers/Scoreboard.tsx b/src/frontend/overlay/src/components/organisms/tickers/Scoreboard.tsx index 1cae94ab7..c35dce75d 100644 --- a/src/frontend/overlay/src/components/organisms/tickers/Scoreboard.tsx +++ b/src/frontend/overlay/src/components/organisms/tickers/Scoreboard.tsx @@ -35,10 +35,10 @@ const TickerScoreboardContestantInfo = styled(ContestantInfo)` export const Scoreboard = ({ tickerSettings, state }) => { const { from, to, periodMs } = tickerSettings; const [row, setRow] = useState(0); - const order = useAppSelector((state) => state.scoreboard[SCOREBOARD_TYPES.normal].order.slice(from-1, to)); - const nrows = Math.ceil(order.length / 4); + const rows = useAppSelector((state) => state.scoreboard[SCOREBOARD_TYPES.normal].rows.slice(from-1, to)); + const nrows = Math.ceil(rows.length / 4); useEffect(() => { - if(state !== "entering" && order.length > 0) { + if(state !== "entering" && rows.length > 0) { const interval = setInterval(() => { if (state !== "exiting") { setRow((row) => { @@ -48,13 +48,13 @@ export const Scoreboard = ({ tickerSettings, state }) => { }, (periodMs - c.TICKER_SCROLL_TRANSITION_TIME) / nrows / c.TICKER_SCOREBOARD_REPEATS + 1); return () => clearInterval(interval); } - }, [nrows, periodMs, state, order.length]); + }, [nrows, periodMs, state, rows.length]); // This fugliness is needed to scroll the scoreboard return ( - {order.map((teamId) => ( - + {rows.map((row) => ( + ))} ); diff --git a/src/frontend/overlay/src/components/organisms/widgets/Queue.tsx b/src/frontend/overlay/src/components/organisms/widgets/Queue.tsx index 1a01e5f0d..e9f54ada1 100644 --- a/src/frontend/overlay/src/components/organisms/widgets/Queue.tsx +++ b/src/frontend/overlay/src/components/organisms/widgets/Queue.tsx @@ -13,7 +13,7 @@ import star from "../../../assets/icons/star.svg"; import star_mask from "../../../assets/icons/star_mask.svg"; import { formatScore } from "@/services/displayUtils"; import { useAppSelector } from "@/redux/hooks"; -import { Award, RunInfo, Widget } from "@shared/api"; +import { RunInfo, Widget } from "@shared/api"; import { isFTS } from "@/utils/statusInfo"; // const MAX_QUEUE_ROWS_COUNT = 20; @@ -297,13 +297,12 @@ export const QueueRow = ({ runInfo, const scoreboardData = useAppSelector((state) => state.scoreboard[SCOREBOARD_TYPES.normal].ids[runInfo.teamId]); const teamData = useAppSelector((state) => state.contestInfo.info?.teamsId[runInfo.teamId]); const probData = useAppSelector((state) => state.contestInfo.info?.problemsId[runInfo.problemId]); - const awards = useAppSelector((state) => state.scoreboard[SCOREBOARD_TYPES.normal].idAwards[runInfo.teamId]); - const medal = awards?.find((award) => award.type == Award.Type.medal) as Award.medal; const isFTSRun = runInfo?.result?.type === "ICPC" && runInfo.result.isFirstToSolveRun || runInfo?.result?.type === "IOI" && runInfo.result.isFirstBestRun; + return - + state.scoreboard[optimismLevel].ids[teamId]); const contestData = useAppSelector((state) => state.contestInfo.info); const teamData = useAppSelector((state) => state.contestInfo.info?.teamsId[teamId]); - const awards = useAppSelector((state) => state.scoreboard[SCOREBOARD_TYPES.normal].idAwards[teamId]); - const medal = awards?.find((award) => award.type == Award.Type.medal) as Award.medal; const needPenalty = useNeedPenalty(); const formatPenalty = useFormatPenalty(); return - + @@ -177,16 +174,8 @@ const ScoreboardRowsWrap = styled.div<{maxHeight: number}>` max-height: ${({ maxHeight }) => `${maxHeight}px`}; `; -export const useScoreboardRows = (optimismLevel: OptimismLevel, selectedGroup: string) => { - const order = useAppSelector((state) => state.scoreboard[optimismLevel]?.orderById); - const teamsId = useAppSelector((state) => state.contestInfo.info?.teamsId); - if (teamsId === undefined || order === undefined) { - return []; - } - return Object.entries(order) - .filter(([k]) => - selectedGroup === "all" || (teamsId[k]?.groups ?? []).includes(selectedGroup)); -}; +export const extractScoreboardRows = (data: ScoreboardData, selectedGroup) => + data.rows.filter(t => selectedGroup === "all" || (t?.teamGroups ?? []).includes(selectedGroup)); /** * Scollbar for scoreboard @@ -225,13 +214,21 @@ interface ScoreboardRowsProps { } export const ScoreboardRows = ({ settings, onPage }: ScoreboardRowsProps) => { - const rows = useScoreboardRows(settings.optimismLevel, settings.group); + const rows = extractScoreboardRows( + useAppSelector((state) => state.scoreboard[settings.optimismLevel]), + settings.group); + const teams = _(rows) + .map((el, i): [number, LegacyScoreboardRow] => [i, el]) + .sortBy("[1].teamId") + .value(); const rowHeight = c.SCOREBOARD_ROW_HEIGHT + c.SCOREBOARD_ROW_PADDING; const scrollPos = useScroller(rows.length, onPage, c.SCOREBOARD_SCROLL_INTERVAL, settings.startFromRow - 1, settings.numRows); return - {rows.map(([teamId, position]) => - - + {teams.map(([index, teamData]) => + + )} ; diff --git a/src/frontend/overlay/src/consts.ts b/src/frontend/overlay/src/consts.ts index 4bdf04bf3..a573f52ea 100644 --- a/src/frontend/overlay/src/consts.ts +++ b/src/frontend/overlay/src/consts.ts @@ -9,9 +9,9 @@ export const WEBSOCKETS = { queue: "queue", statistics: "statistics", ticker: "ticker", - scoreboardNormal: "scoreboard/v2/normal", - scoreboardOptimistic: "scoreboard/v2/optimistic", - scoreboardPessimistic: "scoreboard/v2/pessimistic", + scoreboardNormal: "scoreboard/normal", + scoreboardOptimistic: "scoreboard/optimistic", + scoreboardPessimistic: "scoreboard/pessimistic", ...WEBSOCKETS_OVERRIDE }; diff --git a/src/frontend/overlay/src/redux/contest/contestInfo.ts b/src/frontend/overlay/src/redux/contest/contestInfo.ts index 513bb4319..4f0e07f1a 100644 --- a/src/frontend/overlay/src/redux/contest/contestInfo.ts +++ b/src/frontend/overlay/src/redux/contest/contestInfo.ts @@ -10,7 +10,7 @@ const ActionTypes = { type ContestState = { info: (ContestInfo & { teamsId: Record, - problemsId: Record, + problemsId: Record }) | undefined } diff --git a/src/frontend/overlay/src/redux/contest/scoreboard.ts b/src/frontend/overlay/src/redux/contest/scoreboard.ts index 2b7ef50b6..eafa4fd35 100644 --- a/src/frontend/overlay/src/redux/contest/scoreboard.ts +++ b/src/frontend/overlay/src/redux/contest/scoreboard.ts @@ -1,62 +1,57 @@ import _ from "lodash"; -import { Award, OptimismLevel, ScoreboardDiff, ScoreboardRow, TeamId } from "@shared/api"; -import { createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { LegacyScoreboardRow, OptimismLevel } from "@shared/api"; + +const ActionTypes = { + SCOREBOARD_SET: "SCOREBOARD_SET", +}; export type ScoreboardData = { - ids: Record, // maybe stable? - idAwards: Record[]>, - order: TeamId[], - orderById: Record, - ranks: number[], - awards: Award[] + rows: LegacyScoreboardRow[], + ids: Record }; export type ScoreboardState = { [key in OptimismLevel]: ScoreboardData } -const initialState: ScoreboardState = Object.fromEntries( - Object.keys(OptimismLevel).map((key) => [key, { - ids: {}, - idAwards: {}, - order: [], - ranks: [], - awards: [] - }]) -) as ScoreboardState; +const initialState: ScoreboardState = { + [OptimismLevel.normal]: { + rows: [], + ids: {} + }, + [OptimismLevel.optimistic]: { + rows: [], + ids: {} + }, + [OptimismLevel.pessimistic]: { + rows: [], + ids: {} + } +}; -const scoreboardSlice = createSlice({ - name: "scoreboard", - initialState, - reducers: { - handleScoreboardDiff(state, action: PayloadAction<{ optimism: OptimismLevel, diff: ScoreboardDiff }>) { - const { optimism, diff } = action.payload; - state[optimism].awards = diff.awards; - state[optimism].order = diff.order; - state[optimism].ranks = diff.ranks; - const rankMap = Object.fromEntries(_.zip(diff.order, diff.ranks)); - for (const [id, newData] of Object.entries(diff.rows)) { - state[optimism].ids[id] = { - ...newData, - rank: rankMap[id] - }; +export const setScoreboard = (scoreboardType, rows) => { + return async dispatch => { + dispatch({ + type: ActionTypes.SCOREBOARD_SET, + payload: { + type: scoreboardType, + rows: rows } - state[optimism].orderById = Object.fromEntries( - diff.order.map((teamId, index) => [teamId, index]) - ); - state[optimism].idAwards = {}; - for (const award of diff.awards) { - for (const teamId of award.teams) { - if(state[optimism].idAwards[teamId] === undefined) { - state[optimism].idAwards[teamId] = []; - } - state[optimism].idAwards[teamId].push({ ...award, teams: undefined }); - } + }); + }; +}; + +export function scoreboardReducer(state = initialState, action): ScoreboardState { + switch (action.type) { + case ActionTypes.SCOREBOARD_SET: + return { + ...state, + [action.payload.type]: { + rows: action.payload.rows, + ids: _.keyBy(action.payload.rows, "teamId") } - } + }; + default: + return state; } -}); - -export const { handleScoreboardDiff } = scoreboardSlice.actions; - -export default scoreboardSlice.reducer; +} diff --git a/src/frontend/overlay/src/redux/store.ts b/src/frontend/overlay/src/redux/store.ts index 982d84ee1..0157e3908 100644 --- a/src/frontend/overlay/src/redux/store.ts +++ b/src/frontend/overlay/src/redux/store.ts @@ -5,7 +5,7 @@ // import thunkMiddleware from "redux-thunk"; import { contestInfoReducer } from "./contest/contestInfo"; import { queueReducer } from "./contest/queue"; -import scoreboardReducer from "./contest/scoreboard"; +import { scoreboardReducer } from "./contest/scoreboard"; import debugReducer from "./debug"; import statusReducer from "./status"; import { widgetsReducer } from "./widgets"; diff --git a/src/frontend/overlay/src/services/ws/scoreboard.ts b/src/frontend/overlay/src/services/ws/scoreboard.ts index 68188cd30..c07aabed6 100644 --- a/src/frontend/overlay/src/services/ws/scoreboard.ts +++ b/src/frontend/overlay/src/services/ws/scoreboard.ts @@ -1,7 +1,7 @@ -import { handleScoreboardDiff } from "../../redux/contest/scoreboard"; -import { OptimismLevel, ScoreboardDiff } from "@shared/api"; +import { setScoreboard } from "../../redux/contest/scoreboard"; +import { LegacyScoreboard } from "@shared/api"; -export const handleMessage = (optimism: OptimismLevel) => (dispatch, e) => { - const diff = JSON.parse(e.data) as ScoreboardDiff; - dispatch(handleScoreboardDiff({ optimism, diff })); +export const handleMessage = (type) => (dispatch, e) => { + const message = JSON.parse(e.data) as LegacyScoreboard; + dispatch(setScoreboard(type, message.rows)); }; diff --git a/src/frontend/overlay/src/services/ws/ws.ts b/src/frontend/overlay/src/services/ws/ws.ts index efdcb9892..6a7225b33 100644 --- a/src/frontend/overlay/src/services/ws/ws.ts +++ b/src/frontend/overlay/src/services/ws/ws.ts @@ -7,7 +7,6 @@ import { handleMessage as queueHandler } from "./queue"; import { handleMessage as scoreboardHandler } from "./scoreboard"; import { handleMessage as statisticsHandler } from "./statistics"; import { handleMessage as tickerHandler } from "./ticker"; -import { OptimismLevel } from "@shared/api"; const handler = { @@ -33,9 +32,9 @@ const handler = { export const WEBSOCKET_HANDLERS = new Proxy({ mainScreen: mainScreenHandler, queue: queueHandler, - scoreboardOptimistic: scoreboardHandler(OptimismLevel.optimistic), - scoreboardNormal: scoreboardHandler(OptimismLevel.normal), - scoreboardPessimistic: scoreboardHandler(OptimismLevel.pessimistic), + scoreboardOptimistic: scoreboardHandler(SCOREBOARD_TYPES.optimistic), + scoreboardNormal: scoreboardHandler(SCOREBOARD_TYPES.normal), + scoreboardPessimistic: scoreboardHandler(SCOREBOARD_TYPES.pessimistic), contestInfo: contestInfoHandler, ticker: tickerHandler, statistics: statisticsHandler,