Skip to content

Commit

Permalink
added websocket support
Browse files Browse the repository at this point in the history
  • Loading branch information
Mond1c committed Jul 18, 2024
1 parent d77d440 commit c429c0c
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 29 deletions.
27 changes: 27 additions & 0 deletions src/backend/src/main/kotlin/org/icpclive/overlay/Routing.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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() }
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}
65 changes: 36 additions & 29 deletions src/frontend/overlay/src/components/organisms/holder/TimeLine.jsx
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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`
Expand All @@ -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 (
<ProblemWrap leftMargin={leftMargin}>
<Circle pending={!verdict?.isAccepted && !verdict?.isAddingPenalty}
solved={problemResult.result.type === ProblemResult.Type.ICPC ? verdict?.isAccepted : problemResult.score > 0}>
<Label>
{letter}
<ProblemWrap left={left}>
<Circle pending={problemResult.type === "IN_PROGRESS"}
solved={problemResult.type === "ICPC" ? problemResult.isAccepted : problemResult.score > 0}>
<Label darkText={darkText}>
{problemResult.problemId}
</Label>
</Circle>
</ProblemWrap>
Expand All @@ -77,26 +72,38 @@ const Problem = ({ problemResult, letter, color, contestLengthMs }) => {
export const TimeLine = ({ className, teamId }) => {
const contestLengthMs = useAppSelector(state => state.contestInfo.info?.contestLengthMs);
const [runsResults, setRunsResults] = useState([]);

const getResultRuns = (runs) => {
return runs.filter(obj => obj.teamId === teamId);
};

const getRuns = () => {
fetch(c.RUNS_URL).then(response => response.json())
.then(response => setRunsResults(getResultRuns(response)));
};

useEffect(() => {
getRuns();
}, []);

const socket = new WebSocket(c.BASE_URL_WS + "/teamRuns");
socket.onopen = function () {
console.log("WebSocket is open");
socket.send(teamId);
};

socket.onmessage = function (event) {
setRunsResults(JSON.parse(event.data));
console.log(event.data);
};

socket.onclose = function() {
console.log("WebSocket is closed");
};

socket.onerror = function(error) {
console.log("WebSocket error: " + error);
};

return () => {
socket.close();
};
}, [teamId]);


return (
<TimeLineContainer className={className}>
<Line>
{runsResults?.map((problemResult, index) => (
<Problem problemResult={problemResult} letter={problemResult.problemId}
contestLengthMs={contestLengthMs} key={index} />
<Problem problemResult={problemResult} contestLengthMs={contestLengthMs} key={index} />
))}
</Line>
</TimeLineContainer>
Expand Down

0 comments on commit c429c0c

Please sign in to comment.