diff --git a/src/backend/src/main/kotlin/org/icpclive/overlay/Routing.kt b/src/backend/src/main/kotlin/org/icpclive/overlay/Routing.kt index bb147591f..7166cc9ac 100644 --- a/src/backend/src/main/kotlin/org/icpclive/overlay/Routing.kt +++ b/src/backend/src/main/kotlin/org/icpclive/overlay/Routing.kt @@ -5,6 +5,7 @@ import io.ktor.server.application.* import io.ktor.server.response.* import io.ktor.server.routing.* import io.ktor.server.websocket.* +import io.ktor.websocket.* import kotlinx.coroutines.flow.* import org.icpclive.Config import org.icpclive.admin.getExternalRun @@ -28,6 +29,32 @@ fun Route.configureOverlayRouting() { flowEndpoint("/mainScreen") { DataBus.mainScreenFlow.await() } flowEndpoint("/contestInfo") { DataBus.currentContestInfoFlow() } flowEndpoint("/runs") { DataBus.contestStateFlow.await().map { it.runsAfterEvent.values.sortedBy { it.time } } } + webSocket("/teamRuns") { + val teamIdStr = (incoming.receive() as? Frame.Text)?.readText() + if (teamIdStr.isNullOrBlank()) { + close(CloseReason(CloseReason.Codes.CANNOT_ACCEPT, "Invalid team id")) + return@webSocket + } + val teamId = teamIdStr.toTeamId() + sendJsonFlow(DataBus.contestStateFlow.await().map { state -> state.runsAfterEvent.values.filter { it.teamId == teamId }.sortedBy { it.time } }.distinctUntilChanged() + .map { runs -> + runs.map { info -> + when (info.result) { + is RunResult.ICPC -> { + val icpcResult = info.result as RunResult.ICPC + TimeLineRunInfo.ICPC(info.time, info.problemId, icpcResult.verdict.isAccepted) + } + is RunResult.IOI -> { + val ioiResult = info.result as RunResult.IOI + TimeLineRunInfo.IOI(info.time, info.problemId, ioiResult.scoreAfter) + } + else -> { + TimeLineRunInfo.InProgress(info.time, info.problemId) + } + } + } + }) + } flowEndpoint("/queue") { DataBus.queueFlow.await() } flowEndpoint("/statistics") { DataBus.statisticFlow.await() } flowEndpoint("/ticker") { DataBus.tickerFlow.await() } diff --git a/src/cds/core/src/main/kotlin/org/icpclive/cds/api/TimeLineRunInfo.kt b/src/cds/core/src/main/kotlin/org/icpclive/cds/api/TimeLineRunInfo.kt new file mode 100644 index 000000000..e6247a4af --- /dev/null +++ b/src/cds/core/src/main/kotlin/org/icpclive/cds/api/TimeLineRunInfo.kt @@ -0,0 +1,21 @@ +package org.icpclive.cds.api + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import org.icpclive.cds.util.serializers.DurationInMillisecondsSerializer +import kotlin.time.Duration + +@Serializable +public sealed class TimeLineRunInfo { + @Serializable + @SerialName("ICPC") + public data class ICPC(@Serializable(with = DurationInMillisecondsSerializer::class) val time: Duration, val problemId: ProblemId, val isAccepted: Boolean) : TimeLineRunInfo() + + @Serializable + @SerialName("IOI") + public data class IOI(@Serializable(with = DurationInMillisecondsSerializer::class) val time: Duration, val problemId: ProblemId, val score: Double) : TimeLineRunInfo() + + @Serializable + @SerialName("IN_PROGRESS") + public data class InProgress(@Serializable(with = DurationInMillisecondsSerializer::class) val time: Duration, val problemId: ProblemId) : TimeLineRunInfo() +} diff --git a/src/frontend/overlay/src/components/organisms/holder/TimeLine.jsx b/src/frontend/overlay/src/components/organisms/holder/TimeLine.jsx index b5db21f66..11857a574 100644 --- a/src/frontend/overlay/src/components/organisms/holder/TimeLine.jsx +++ b/src/frontend/overlay/src/components/organisms/holder/TimeLine.jsx @@ -1,8 +1,6 @@ import React, { useEffect, useState } from "react"; import styled from "styled-components"; import { useAppSelector } from "../../../redux/hooks"; -import { SCOREBOARD_TYPES } from "../../../consts"; -import { ProblemResult } from "../../../../../generated/api"; import c from "../../../config"; import { isShouldUseDarkColor } from "../../../utils/colors"; @@ -43,6 +41,7 @@ const Label = styled.div` justify-content: center; display: flex; text-align: center; + color: ${({ darkText }) => darkText ? "#000" : "#FFF"}; `; const ProblemWrap = styled.div` @@ -51,22 +50,18 @@ const ProblemWrap = styled.div` align-items: center; justify-content: center; position: absolute; - left: ${props => props.leftMargin}; + left: ${({ left }) => left};; `; -const Problem = ({ problemResult, letter, color, contestLengthMs }) => { - const leftMargin = (100 * problemResult.time / contestLengthMs) * 0.99 + "%"; - console.log(letter, color); - const dark = isShouldUseDarkColor(color); - console.log(problemResult); - const verdict = problemResult?.result?.verdict; - console.log(problemResult.result.type === ProblemResult.Type.ICPC ? verdict?.isAccepted : problemResult.score > 0); +const Problem = ({ problemResult, color, contestLengthMs }) => { + const left = (100 * problemResult.time / contestLengthMs) * 0.99 + "%"; + const darkText = isShouldUseDarkColor(color); return ( - - 0}> -