Skip to content

Commit 31a6bd1

Browse files
authored
Merge pull request #71 from CS3219-AY2425S1/titus/fix-local-storage-bug
fix(frontend): 🐛 mount component before accessing local storage
2 parents 7d931ee + 3fd5752 commit 31a6bd1

File tree

3 files changed

+92
-34
lines changed

3 files changed

+92
-34
lines changed

apps/frontend/src/app/collaboration/[id]/page.tsx

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,12 @@ import {
3434
ExecuteVisibleAndHiddenTestsAndSubmit,
3535
ExecutionResults,
3636
GetVisibleTests,
37+
isTestResult,
3738
SubmissionHiddenTestResultsAndStatus,
3839
SubmissionResults,
3940
Test,
41+
TestData,
42+
TestResult,
4043
} from "@/app/services/execute";
4144
import { QuestionDetailFull } from "@/components/question/QuestionDetailFull/QuestionDetailFull";
4245
import VideoPanel from "@/components/VideoPanel/VideoPanel";
@@ -75,15 +78,17 @@ export default function CollaborationPage(props: CollaborationProps) {
7578
);
7679
const [currentUser, setCurrentUser] = useState<string | undefined>(undefined);
7780
const [matchedUser, setMatchedUser] = useState<string>("Loading...");
78-
const [sessionDuration, setSessionDuration] = useState<number>(() => {
79-
const storedTime = localStorage.getItem("session-duration");
80-
return storedTime ? parseInt(storedTime) : 0;
81-
}); // State for count-up timer (TODO: currently using localstorage to store time, change to db stored time in the future)
81+
const [sessionDuration, setSessionDuration] = useState<number>(0); // State for count-up timer (TODO: currently using localstorage to store time, change to db stored time in the future)
8282
const stopwatchRef = useRef<NodeJS.Timeout | null>(null);
8383
const [matchedTopics, setMatchedTopics] = useState<string[] | undefined>(
8484
undefined
8585
);
8686

87+
useEffect(() => {
88+
const storedTime = localStorage.getItem("session-duration");
89+
setSessionDuration(storedTime ? parseInt(storedTime) : 0);
90+
}, []);
91+
8792
// Chat states
8893
const [messageToSend, setMessageToSend] = useState<string | undefined>(
8994
undefined
@@ -314,6 +319,52 @@ export default function CollaborationPage(props: CollaborationProps) {
314319
}
315320
}, [isSessionEndModalOpen, countDown]);
316321

322+
// Tabs component items for visibleTestCases
323+
var items: TabsProps["items"] = visibleTestCases.map((item, index) => {
324+
return {
325+
key: index.toString(),
326+
label: (
327+
<span
328+
style={{
329+
color: !isTestResult(item) ? "" : item.passed ? "green" : "red",
330+
}}
331+
>
332+
Case {index + 1}
333+
</span>
334+
),
335+
children: (
336+
<div>
337+
<Input.TextArea
338+
disabled
339+
placeholder={`Stdin: ${item.input}\nStdout: ${item.expected}`}
340+
rows={4}
341+
/>
342+
{isTestResult(item) && (
343+
<div className="test-result-container">
344+
<InfoCircleFilled className="hidden-test-icon" />
345+
<Typography.Text
346+
strong
347+
style={{ color: item.passed ? "green" : "red" }}
348+
>
349+
{item.passed ? "Passed" : "Failed"}
350+
</Typography.Text>
351+
<br />
352+
<Typography.Text strong>Actual Output:</Typography.Text>{" "}
353+
{item.actual}
354+
<br />
355+
{item.error && (
356+
<>
357+
<Typography.Text strong>Error:</Typography.Text>
358+
<div className="error-message">{item.error}</div>
359+
</>
360+
)}
361+
</div>
362+
)}
363+
</div>
364+
),
365+
};
366+
});
367+
317368
// Handles the cleaning of localstorage variables, stopping the timer & signalling collab user on webrtc
318369
// type: "initiator" | "peer"
319370
const handleCloseCollaboration = (type: string) => {

apps/frontend/src/components/CollaborativeEditor/CollaborativeEditor.tsx

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import * as Y from "yjs";
1515
import { yCollab } from "y-codemirror.next";
1616
import { WebrtcProvider } from "y-webrtc";
1717
import { EditorView, basicSetup } from "codemirror";
18-
import { keymap } from "@codemirror/view"
19-
import { indentWithTab } from "@codemirror/commands"
18+
import { keymap } from "@codemirror/view";
19+
import { indentWithTab } from "@codemirror/commands";
2020
import { EditorState, Compartment } from "@codemirror/state";
2121
import { javascript, javascriptLanguage } from "@codemirror/lang-javascript";
2222
import { python, pythonLanguage } from "@codemirror/lang-python";
@@ -68,15 +68,15 @@ interface Awareness {
6868
executionResultsState: {
6969
executionResults: ExecutionResults;
7070
id: number;
71-
}
71+
};
7272
executingState: {
7373
executing: boolean;
7474
id: number;
75-
}
75+
};
7676
submittingState: {
7777
submitting: boolean;
7878
id: number;
79-
}
79+
};
8080
}
8181

8282
export const usercolors = [
@@ -111,8 +111,7 @@ const CollaborativeEditor = forwardRef(
111111
props.onCodeChange(update.state.doc.toString());
112112
}
113113
});
114-
115-
114+
116115
// Referenced: https://codemirror.net/examples/config/#dynamic-configuration
117116
// const autoLanguage = EditorState.transactionExtender.of((tr) => {
118117
// if (!tr.docChanged) return null;
@@ -196,10 +195,10 @@ const CollaborativeEditor = forwardRef(
196195
});
197196
};
198197

199-
let latestExecutionId: number = (new Date(0)).getTime();
200-
let latestSubmissionId: number = (new Date(0)).getTime();
201-
let latestExecutingId: number = (new Date(0)).getTime();
202-
let latestSubmittingId: number = (new Date(0)).getTime();
198+
let latestExecutionId: number = new Date(0).getTime();
199+
let latestSubmissionId: number = new Date(0).getTime();
200+
let latestExecutingId: number = new Date(0).getTime();
201+
let latestSubmittingId: number = new Date(0).getTime();
203202

204203
useImperativeHandle(ref, () => ({
205204
endSession: () => {
@@ -311,12 +310,14 @@ const CollaborativeEditor = forwardRef(
311310
.get(clientID) as Awareness;
312311

313312
if (
314-
state &&
313+
state &&
315314
state.submissionResultsState &&
316315
state.submissionResultsState.id !== latestSubmissionId
317316
) {
318317
latestSubmissionId = state.submissionResultsState.id;
319-
props.updateSubmissionResults(state.submissionResultsState.submissionResults);
318+
props.updateSubmissionResults(
319+
state.submissionResultsState.submissionResults
320+
);
320321
messageApi.open({
321322
type: "success",
322323
content: `${
@@ -326,12 +327,14 @@ const CollaborativeEditor = forwardRef(
326327
}
327328

328329
if (
329-
state &&
330-
state.executionResultsState &&
330+
state &&
331+
state.executionResultsState &&
331332
state.executionResultsState.id !== latestExecutionId
332333
) {
333334
latestExecutionId = state.executionResultsState.id;
334-
props.updateExecutionResults(state.executionResultsState.executionResults);
335+
props.updateExecutionResults(
336+
state.executionResultsState.executionResults
337+
);
335338
messageApi.open({
336339
type: "success",
337340
content: `${
@@ -341,8 +344,8 @@ const CollaborativeEditor = forwardRef(
341344
}
342345

343346
if (
344-
state &&
345-
state.executingState &&
347+
state &&
348+
state.executingState &&
346349
state.executingState.id !== latestExecutingId
347350
) {
348351
latestExecutingId = state.executingState.id;
@@ -358,18 +361,16 @@ const CollaborativeEditor = forwardRef(
358361
}
359362

360363
if (
361-
state &&
362-
state.submittingState &&
364+
state &&
365+
state.submittingState &&
363366
state.submittingState.id !== latestSubmittingId
364367
) {
365368
latestSubmittingId = state.submittingState.id;
366369
props.updateSubmitting(state.submittingState.submitting);
367370
if (state.submittingState.submitting) {
368371
messageApi.open({
369372
type: "info",
370-
content: `${
371-
props.matchedUser ?? "Peer"
372-
} is saving code...`,
373+
content: `${props.matchedUser ?? "Peer"} is saving code...`,
373374
});
374375
}
375376
}

apps/frontend/src/components/VideoPanel/VideoPanel.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,8 @@ import {
1111
} from "@ant-design/icons";
1212

1313
const VideoPanel = () => {
14-
const matchId = localStorage.getItem("collabId")?.toString() ?? "";
15-
const currentUsername = localStorage.getItem("user")?.toString();
16-
const matchedUsername = localStorage.getItem("matchedUser")?.toString();
17-
const currentId = currentUsername + "-" + matchId ?? "";
18-
const partnerId = matchedUsername + "-" + matchId ?? "";
14+
const [currentId, setCurrentId] = useState<string | undefined>();
15+
const [partnerId, setPartnerId] = useState<string | undefined>();
1916

2017
const remoteVideoRef = useRef<HTMLVideoElement>(null);
2118
const currentUserVideoRef = useRef<HTMLVideoElement>(null);
@@ -30,14 +27,23 @@ const VideoPanel = () => {
3027
const [muteOn, setMuteOn] = useState<boolean>(false);
3128
const [isCalling, setIsCalling] = useState<boolean>(false);
3229

30+
useEffect(() => {
31+
const matchId = localStorage.getItem("collabId")?.toString() ?? "";
32+
const currentUsername = localStorage.getItem("user")?.toString();
33+
const matchedUsername = localStorage.getItem("matchedUser")?.toString();
34+
35+
setCurrentId(currentUsername + "-" + (matchId ?? ""));
36+
setPartnerId(matchedUsername + "-" + (matchId ?? ""));
37+
}, []);
38+
3339
const handleCall = () => {
3440
navigator.mediaDevices
3541
.getUserMedia({
3642
video: true,
3743
audio: true,
3844
})
3945
.then((stream) => {
40-
if (peerInstance) {
46+
if (peerInstance && partnerId) {
4147
const call = peerInstance?.call(partnerId, stream);
4248
setCallInstance(call);
4349
setIsCalling(true); // Set isCalling as true since it is the initiator
@@ -120,7 +126,7 @@ const VideoPanel = () => {
120126
}
121127
};
122128
}
123-
}, []);
129+
}, [currentId]);
124130

125131
// When remote peer initiates end call, we set isCalling to false
126132
useEffect(() => {

0 commit comments

Comments
 (0)