-
Notifications
You must be signed in to change notification settings - Fork 16
[Keylog] Support new keylog format #262
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
969d187
7ba0303
0cced9c
d5bc899
52c6444
6645ad5
ceab3e1
690e872
690ca2a
f001373
ea9f351
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,8 +3,17 @@ import styled from "styled-components"; | |
| import { useAppSelector } from "@/redux/hooks"; | ||
| import c from "@/config"; | ||
| import { getIOIColor } from "@/utils/statusInfo"; | ||
| import { ContestInfo, TeamId, TeamInfo, TimeLineRunInfo } from "@shared/api"; | ||
| import { calculateContestTime } from "@/components/molecules/Clock"; | ||
| import { | ||
| ContestInfo, | ||
| ContestStatus, | ||
| TeamId, | ||
| TeamInfo, | ||
| TimeLineRunInfo, | ||
| } from "@shared/api"; | ||
| import { | ||
| calculateContestTime, | ||
| getStartTime, | ||
| } from "@/components/molecules/Clock"; | ||
| import { isShouldUseDarkColor } from "@/utils/colors"; | ||
| import { KeylogGraph } from "./KeylogGraph"; | ||
|
|
||
|
|
@@ -425,6 +434,34 @@ export function TimeLineBackground({ | |
| ); | ||
| } | ||
|
|
||
| interface KeyboardEvent { | ||
| timestamp: string; // ISO8601 | ||
| keys: Record<string, KeyStats>; | ||
| } | ||
|
|
||
| interface KeyStats { | ||
| raw?: number; | ||
| bare?: number; | ||
| shift?: number; | ||
| ctrl?: number; | ||
| alt?: number; | ||
| meta?: number; | ||
|
|
||
| "ctrl+shift"?: number; | ||
| "ctrl+alt"?: number; | ||
| "shift+alt"?: number; | ||
| "ctrl+meta"?: number; | ||
| "shift+meta"?: number; | ||
| "alt+meta"?: number; | ||
|
|
||
| "ctrl+shift+alt"?: number; | ||
| "ctrl+shift+meta"?: number; | ||
| "ctrl+alt+meta"?: number; | ||
| "shift+alt+meta"?: number; | ||
|
|
||
| "ctrl+shift+alt+meta"?: number; | ||
| } | ||
|
|
||
| export function TimeLine({ | ||
| teamId, | ||
| className = null, | ||
|
|
@@ -509,20 +546,60 @@ export function TimeLine({ | |
| }, [contestInfo, isPvp]); | ||
|
|
||
| useEffect(() => { | ||
| if (!keylogUrl) return; | ||
| const startTime = getStartTime(contestInfo); | ||
| if (!keylogUrl || !startTime) return; | ||
|
|
||
| async function fetchKeylogData() { | ||
| // TODO: Move all this code to KeylogGraph | ||
| async function fetchNDJSON(): Promise<KeyboardEvent[]> { | ||
| try { | ||
| const response = await fetch(keylogUrl); | ||
| const data: number[] = await response.json(); | ||
| setKeylog(data); | ||
| } catch (error) { | ||
| console.error("Error fetching keylog data:", error); | ||
| if (!response.ok) throw new Error("Failed to fetch keylog"); | ||
|
|
||
| const text = await response.text(); | ||
| return text | ||
| .trim() | ||
| .split("\n") | ||
| .filter((line) => line.trim()) | ||
| .map((line) => JSON.parse(line) as KeyboardEvent); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If a single last line is not fully read/written we won't be showing all the keyboard events. Maybe it's a good idea to add the try/catch somewhere else or extract this whole procedure to a function that would try to read all the events
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This sounds computationally heavy considering that a keylog file might be more than a megabyte. Moving it to a seperate worker that would fetch and cache the keylog data is probably a good idea |
||
| } catch (e) { | ||
| console.error(e); | ||
| return []; | ||
| } | ||
| } | ||
|
|
||
| async function fetchKeylogData() { | ||
| const events = await fetchNDJSON(); | ||
| if (events.length === 0) return; | ||
|
|
||
| const contestStart = new Date(startTime!).getTime(); | ||
| const AGGREGATION_MS = c.KEYLOG_INTERVAL_LENGTH; | ||
|
|
||
| const totalIntervals = Math.ceil( | ||
| contestInfo!.contestLengthMs / c.KEYLOG_INTERVAL_LENGTH, | ||
| ); | ||
| const interval_minutes = c.KEYLOG_INTERVAL_LENGTH / 60000; | ||
| const pressesPerMinuteFactor = 1 / interval_minutes; | ||
| const newKeylog = new Array(totalIntervals).fill(0); | ||
|
|
||
| events.forEach((event) => { | ||
| const eventTime = new Date(event.timestamp).getTime(); | ||
|
|
||
| if (eventTime < contestStart) return; | ||
|
|
||
| const timeDiff = eventTime - contestStart; | ||
| const index = Math.floor(timeDiff / AGGREGATION_MS); | ||
|
|
||
| if (index >= 0 && index < totalIntervals) { | ||
| const pressesCount = Object.values(event.keys).reduce((sum, k) => sum + (k.bare ?? 0) + (k.shift ?? 0), 0); | ||
| newKeylog[index] += pressesCount; | ||
| } | ||
| }); | ||
|
|
||
| setKeylog(newKeylog.map(v => v * pressesPerMinuteFactor)); | ||
| } | ||
|
|
||
| fetchKeylogData(); | ||
| }, [keylogUrl]); | ||
| }, [keylogUrl, contestInfo]); | ||
|
|
||
| if (!contestInfo) return null; | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Name clash with the https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent