Skip to content

Commit 7e14cd4

Browse files
committed
Merge branch 'staging'
2 parents a5c6fa7 + 53eeb38 commit 7e14cd4

6 files changed

Lines changed: 102 additions & 132 deletions

File tree

src/components/Carousel.css

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
.slider {
2-
width: 100%;
3-
overflow: hidden;
4-
height: 100%;
5-
}
6-
71
.slides {
82
display: flex;
93
overflow-x: auto;
@@ -38,3 +32,8 @@ body {
3832
body {
3933
display: flex;
4034
}
35+
36+
.monaco-editor {
37+
position: absolute !important;
38+
inset: 0;
39+
}

src/components/Hooks.tsx

Lines changed: 17 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { useMonaco } from "@monaco-editor/react"
2-
import { useKeycloak } from "@react-keycloak/web"
32
import { useMutation, useQuery, UseQueryOptions } from "@tanstack/react-query"
43
import axios, { AxiosError } from "axios"
5-
import { EventSource } from "extended-eventsource"
64
import { compact, concat, flatten } from "lodash"
75
import { Uri } from "monaco-editor"
86
import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react"
@@ -306,66 +304,39 @@ export const useCountdown = (start: number | null, end: number | null) => {
306304
}
307305

308306
export const useTimeframeFromSSE = () => {
309-
const { keycloak } = useKeycloak()
310-
const { courseSlug } = useParams()
311-
const token = keycloak.token
312-
313307
const [timeFrameFromEvent, setTimeFrameFromEvent] = useState<
314308
[number, number] | null
315309
>(null)
316-
const [error, setError] = useState<Error | null>(null)
317-
318-
useEffect(() => {
319-
if (!token || !courseSlug) return
320-
const eventSource = new EventSource(
321-
`/api/courses/${courseSlug}/subscribe`,
322-
{
323-
headers: { Authorization: `Bearer ${token}` },
324-
retry: 3000,
325-
},
326-
)
327-
328-
const onTimeEvent = (e: MessageEvent) => {
329-
const [startTimeString, endTimeString] = (e.data as string).split("/")
330-
setTimeFrameFromEvent([
331-
Date.parse(startTimeString),
332-
Date.parse(endTimeString),
333-
])
334-
}
335-
336-
eventSource.addEventListener("timer-update", onTimeEvent)
337-
eventSource.onerror = (error) => {
338-
console.error("SSE error", error)
339-
setError(new Error("SSE connection error"))
340-
}
341310

342-
const cleanup = () => {
343-
eventSource.removeEventListener("timer-update", onTimeEvent)
344-
eventSource.close()
345-
}
346-
window.addEventListener("beforeunload", cleanup)
347-
return () => {
348-
window.removeEventListener("beforeunload", cleanup)
349-
cleanup()
350-
}
351-
}, [courseSlug, token])
311+
useSSE<string>("timer-update", (data) => {
312+
const [startTimeString, endTimeString] = data.split("/")
313+
setTimeFrameFromEvent([
314+
Date.parse(startTimeString),
315+
Date.parse(endTimeString),
316+
])
317+
})
352318

353-
return { timeFrameFromEvent, error }
319+
return { timeFrameFromEvent }
354320
}
355321

356-
// properly define eventType
357322
export const useSSE = <T,>(eventType: string, handler: (data: T) => void) => {
358323
const { eventSource } = useEventSource()
359324

325+
const handlerRef = useRef(handler)
326+
327+
useEffect(() => {
328+
handlerRef.current = handler
329+
}, [handler])
330+
360331
useEffect(() => {
361332
if (!eventSource) return
362333

363334
const listener = (event: MessageEvent) => {
364335
try {
365336
const parsed = JSON.parse(event.data)
366-
handler(parsed as T)
337+
handlerRef.current(parsed as T)
367338
} catch {
368-
handler(event.data as unknown as T)
339+
handlerRef.current(event.data as unknown as T)
369340
}
370341
}
371342

@@ -374,7 +345,7 @@ export const useSSE = <T,>(eventType: string, handler: (data: T) => void) => {
374345
return () => {
375346
eventSource.removeEventListener(eventType, listener)
376347
}
377-
}, [eventSource, eventType, handler])
348+
}, [eventSource, eventType])
378349
}
379350

380351
export const useInspect = () => {

src/components/SubmissionsCarousel.tsx

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ const Slide: React.FC<{
134134
}, [selectedFileName])
135135

136136
return (
137-
<Flex direction={"column"} borderRadius={"lg"}>
137+
<Flex direction={"column"} borderRadius={"lg"} flexGrow={0}>
138138
<HStack
139139
justify={"space-between"}
140140
px={3}
@@ -168,16 +168,27 @@ const Slide: React.FC<{
168168
></BookmarkToggle>
169169
</HStack>
170170
<Divider />
171-
<Flex h={"full"} marginTop={4} direction={"column"} p={2}>
172-
<Editor
173-
value={submission.content[selectedFileName]}
174-
options={{
175-
readOnly: true,
176-
minimap: { enabled: false },
177-
scrollBeyondLastLine: false,
178-
}}
179-
language={derivedProgrammingLanguage}
180-
></Editor>
171+
<Flex
172+
h={"full"}
173+
marginTop={4}
174+
direction={"column"}
175+
p={2}
176+
minH={0}
177+
flex={1}
178+
>
179+
<Box flex={1} minH={0} position={"relative"}>
180+
<Editor
181+
value={submission.content[selectedFileName]}
182+
options={{
183+
readOnly: true,
184+
minimap: { enabled: false },
185+
scrollBeyondLastLine: false,
186+
automaticLayout: true,
187+
}}
188+
height={"100%"}
189+
language={derivedProgrammingLanguage}
190+
></Editor>
191+
</Box>
181192
<Flex direction={"row-reverse"}>
182193
<Button
183194
position={"relative"}
@@ -326,10 +337,15 @@ export const SubmissionsCarousel: React.FC<{
326337
display={"flex"}
327338
p={0}
328339
background={"transparent"}
329-
height={"full"}
340+
flex={1}
330341
>
331-
<Flex className="slider" width={"full"} borderRadius={"2xl"}>
332-
<Flex className="slides" ref={sliderRef} gap={SLIDES_GAP}>
342+
<Flex width={"full"} borderRadius={"2xl"} flex={1} flexGrow={0}>
343+
<Flex
344+
className="slides"
345+
ref={sliderRef}
346+
gap={SLIDES_GAP}
347+
height={"auto"}
348+
>
333349
{submissions.map((submission, i) => (
334350
<Slide
335351
submission={submission}

src/context/EventSourceContext.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,26 @@ export const EventSourceProvider: React.FC<{ children: React.ReactNode }> = ({
3232
`/api/courses/${courseSlug}/subscribe`,
3333
{
3434
headers: {
35-
Authorization: `Bearer ${keycloak.token}`,
35+
Authorization: `Bearer ${token}`,
3636
},
3737
retry: 3000,
3838
},
3939
)
40-
eventSource.addEventListener("emitter-id", (event: MessageEvent) => {
40+
41+
const emitterListener = (event: MessageEvent) => {
4142
setEmitterId(event.data)
42-
})
43+
}
4344

45+
eventSource.addEventListener("emitter-id", emitterListener)
4446
eventSourceRef.current = eventSource
4547
setTick((t) => t + 1) // force re-render so context provider updates value
4648

4749
return () => {
50+
eventSource.removeEventListener("emitter-id", emitterListener)
4851
eventSource.close()
52+
eventSourceRef.current = null
4953
}
50-
}, [courseSlug, keycloak.token, token])
54+
}, [courseSlug, eventSourceRef, token])
5155

5256
useEffect(() => {
5357
if (!emitterId || !courseSlug) return
@@ -63,11 +67,11 @@ export const EventSourceProvider: React.FC<{ children: React.ReactNode }> = ({
6367
})
6468
}
6569
},
66-
60000, // 1 minute heartbeat
70+
15000, // 15 sec heartbeat
6771
)
6872

6973
return () => clearInterval(interval)
70-
}, [emitterId, courseSlug, keycloak.token, sendHeartbeat, toast])
74+
}, [courseSlug, emitterId, sendHeartbeat, toast])
7175

7276
return (
7377
<EventSourceContext.Provider

src/pages/Layout.tsx

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
} from "@chakra-ui/react"
1818
import { useKeycloak } from "@react-keycloak/web"
1919
import { compact, join } from "lodash"
20-
import { useEffect, useState } from "react"
20+
import { useEffect } from "react"
2121
import { useTranslation } from "react-i18next"
2222
import { AiOutlineLogout } from "react-icons/ai"
2323
import {
@@ -46,18 +46,10 @@ export default function Layout() {
4646
const { keycloak } = useKeycloak()
4747
const { courseSlug } = useParams()
4848
const { data: examples } = useExamples()
49-
const [ongoingExamplePath, setOngoingExamplePath] = useState<string | null>(
50-
null,
51-
)
49+
5250
const isSupervisor =
5351
!!courseSlug && keycloak.hasRealmRole(courseSlug + "-supervisor")
5452

55-
useEffect(() => {
56-
if (ongoingExamplePath) {
57-
navigate(ongoingExamplePath)
58-
}
59-
}, [navigate, ongoingExamplePath])
60-
6153
useEffect(() => {
6254
if (!courseSlug || !examples || isSupervisor) return
6355

@@ -77,7 +69,7 @@ export default function Layout() {
7769
}, [examples, courseSlug, navigate, isSupervisor, location.pathname])
7870

7971
useSSE<string>("redirect", (data) => {
80-
setOngoingExamplePath(data)
72+
navigate(data)
8173
toast({
8274
title: t("redirect_toast"),
8375
status: "info",

src/pages/PrivateDashboard.tsx

Lines changed: 37 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
Divider,
1212
Flex,
1313
Grid,
14-
GridItem,
1514
Heading,
1615
HStack,
1716
Icon,
@@ -350,7 +349,7 @@ const SubmissionInspector: React.FC<{
350349
)
351350

352351
return (
353-
<Flex direction={"column"} h={"full"} gap={2}>
352+
<Flex direction={"column"} h={"full"} gap={2} flex={1}>
354353
<Flex layerStyle={"segment"} direction="row" p={3}>
355354
{Object.entries(categories).map(([categoryKey, category], i) => {
356355
const bgColor = bgColors[i]
@@ -923,21 +922,14 @@ export function PrivateDashboard() {
923922
example?.information[currentLanguage]?.title ||
924923
example?.information["en"]?.title
925924
return (
926-
<Grid
927-
layerStyle={"container"}
928-
templateColumns="1fr 1fr 1fr"
929-
templateRows={"5fr 1fr"}
930-
gap={2}
931-
height={"full"}
932-
>
933-
<GridItem
925+
<Flex layerStyle={"container"} gap={2} height={"full"}>
926+
<Flex
927+
direction={"column"}
934928
layerStyle={"segment"}
935929
gap={4}
936-
rowStart={1}
937-
rowEnd={5}
938-
colStart={1}
939-
colEnd={2}
940930
p={1}
931+
minWidth={0}
932+
flex={1}
941933
>
942934
<Tabs
943935
variant={"line"}
@@ -974,9 +966,9 @@ export function PrivateDashboard() {
974966
</TabPanel>
975967
</TabPanels>
976968
</Tabs>
977-
</GridItem>
978-
<GridItem gap={4} colStart={2} colEnd={4} rowStart={1} rowEnd={4}>
979-
<Flex direction={"column"} h={"full"}>
969+
</Flex>
970+
<Flex gap={2} flexDirection={"column"} minWidth={0} flex={2}>
971+
<Flex direction={"column"} flex={1}>
980972
{!submissions || submissions.length < 1 ? (
981973
<TaskDescription
982974
instructionContent={instructionsContent}
@@ -1001,37 +993,33 @@ export function PrivateDashboard() {
1001993
/>
1002994
)}
1003995
</Flex>
1004-
</GridItem>
1005-
<GridItem
1006-
rowStart={4}
1007-
rowEnd={5}
1008-
colStart={2}
1009-
colEnd={-1}
1010-
display={"flex"}
1011-
flexDirection={"row"}
1012-
gap={3}
1013-
layerStyle={"segment"}
1014-
alignContent={"space-between"}
1015-
p={2}
1016-
>
1017-
<GeneralInformation
1018-
exampleState={exampleState}
1019-
generalInformation={exampleInformation}
1020-
></GeneralInformation>
1021-
1022-
<ExampleTimeController
1023-
handleTimeAdjustment={handleTimeAdjustment}
1024-
durationAsString={durationAsString} // will be some derived state once implemented properly
1025-
exampleState={exampleState}
1026-
handleStart={handleStart}
1027-
handleTermination={handleTermination}
1028-
handleReset={handleReset}
1029-
setDurationInSeconds={setDurationInSeconds}
1030-
startTime={derivedStartDate}
1031-
endTime={derivedEndDate}
1032-
setExampleState={setExampleState}
1033-
></ExampleTimeController>
1034-
</GridItem>
1035-
</Grid>
996+
<Flex
997+
display={"flex"}
998+
flexDirection={"row"}
999+
gap={3}
1000+
layerStyle={"segment"}
1001+
alignContent={"space-between"}
1002+
p={2}
1003+
>
1004+
<GeneralInformation
1005+
exampleState={exampleState}
1006+
generalInformation={exampleInformation}
1007+
></GeneralInformation>
1008+
1009+
<ExampleTimeController
1010+
handleTimeAdjustment={handleTimeAdjustment}
1011+
durationAsString={durationAsString}
1012+
exampleState={exampleState}
1013+
handleStart={handleStart}
1014+
handleTermination={handleTermination}
1015+
handleReset={handleReset}
1016+
setDurationInSeconds={setDurationInSeconds}
1017+
startTime={derivedStartDate}
1018+
endTime={derivedEndDate}
1019+
setExampleState={setExampleState}
1020+
></ExampleTimeController>
1021+
</Flex>
1022+
</Flex>
1023+
</Flex>
10361024
)
10371025
}

0 commit comments

Comments
 (0)