Skip to content

Commit 62fd787

Browse files
authored
Merge pull request #95 from CS3219-AY2425S1/add-on-leave-saving-attempt
Add On Unexpected Leave Code Saving
2 parents 8669766 + 75b362f commit 62fd787

File tree

3 files changed

+93
-6
lines changed

3 files changed

+93
-6
lines changed

frontend/src/presentation/components/CodeEditor/CodeEditor.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useEffect, useRef } from "react";
1+
import React, { useState, useEffect, useRef, useImperativeHandle, forwardRef } from "react";
22
import styles from "./CodeEditor.module.css";
33
import Editor from "@monaco-editor/react";
44
import { Button, Spin, Modal } from "antd";
@@ -14,6 +14,10 @@ interface CodeEditorProps {
1414
roomId: string;
1515
attemptStartedAt: Date;
1616
collaboratorId: string;
17+
onUserConfirmedLeave: (shouldSave: boolean) => void; // New prop
18+
}
19+
export interface CodeEditorHandle {
20+
getEditorText: () => string;
1721
}
1822

1923
function usePrevious<T>(value: T): T | undefined {
@@ -24,19 +28,25 @@ function usePrevious<T>(value: T): T | undefined {
2428
return ref.current;
2529
}
2630

27-
const CodeEditor: React.FC<CodeEditorProps> = ({
31+
const CodeEditor = forwardRef<CodeEditorHandle, CodeEditorProps>(({
2832
questionId,
2933
roomId,
3034
attemptStartedAt,
31-
collaboratorId
32-
}) => {
35+
collaboratorId,
36+
onUserConfirmedLeave,
37+
}, ref) => {
3338
const { onEditorIsMounted, isExecuting, setRoomId, connectedUsers } = useCollaboration();
3439
const [theme, setTheme] = useState("vs-light");
3540
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
3641
const containerRef = useRef<HTMLDivElement>(null);
3742
const [isModalVisible, setIsModalVisible] = useState(false);
3843
const [leftUser, setLeftUser] = useState<string | null>(null);
3944

45+
// Expose the getEditorText method using useImperativeHandle
46+
useImperativeHandle(ref, () => ({
47+
getEditorText: () => editorRef.current?.getValue() || "",
48+
}));
49+
4050
const prevConnectedUsers = usePrevious(connectedUsers);
4151

4252
useEffect(() => {
@@ -151,6 +161,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
151161
roomId={roomId}
152162
attemptStartedAt={attemptStartedAt}
153163
collaboratorId={collaboratorId}
164+
onUserConfirmedLeave={onUserConfirmedLeave}
154165
/>
155166
</div>
156167
</div>
@@ -168,6 +179,6 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
168179
</div>
169180
</div>
170181
);
171-
};
182+
});
172183

173184
export default CodeEditor;

frontend/src/presentation/components/CodeEditor/LeaveButton.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ interface LeaveButtonProps {
1111
roomId: string;
1212
attemptStartedAt: Date;
1313
collaboratorId: string;
14+
onUserConfirmedLeave: (shouldSave: boolean) => void;
1415
}
1516

1617
const LeaveButton: React.FC<LeaveButtonProps> = ({
@@ -19,6 +20,7 @@ const LeaveButton: React.FC<LeaveButtonProps> = ({
1920
roomId,
2021
attemptStartedAt,
2122
collaboratorId,
23+
onUserConfirmedLeave,
2224
}) => {
2325
const navigate = useNavigate();
2426
const [isModalVisible, setIsModalVisible] = useState(false);
@@ -34,8 +36,10 @@ const LeaveButton: React.FC<LeaveButtonProps> = ({
3436
setIsModalVisible(false);
3537
};
3638

39+
3740
const handleLeaveWithoutSaving = () => {
3841
setIsModalVisible(false);
42+
onUserConfirmedLeave(false);
3943
navigate('/');
4044
};
4145

@@ -50,6 +54,7 @@ const LeaveButton: React.FC<LeaveButtonProps> = ({
5054
getEditorText(),
5155
);
5256
message.success("Your work has been saved successfully.");
57+
onUserConfirmedLeave(false);
5358
navigate('/');
5459
} catch (error) {
5560
if (error instanceof Error) {

frontend/src/presentation/pages/CollaborationRoomPage.tsx

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { useState, useCallback, useEffect, useRef } from "react";
2+
import { CodeEditorHandle } from "../../presentation/components/CodeEditor/CodeEditor";
23
import styles from "./CollaborationRoomPage.module.css";
34
import CodeEditor from "../../presentation/components/CodeEditor/CodeEditor";
45
import { QuestionDetail } from "../../presentation/components/QuestionDetail";
@@ -15,6 +16,7 @@ import { Room } from "../../domain/entities/Room";
1516
import { useAuth } from "../../domain/context/AuthContext";
1617
import { Spin } from "antd";
1718
import { toast } from "react-toastify";
19+
import { historyUseCases } from "domain/usecases/HistoryUseCases";
1820

1921
const CollaborationRoomPage: React.FC = () => {
2022
const location = useLocation();
@@ -23,12 +25,15 @@ const CollaborationRoomPage: React.FC = () => {
2325

2426
// State Definitions
2527
const { urlRoomId } = useParams<{ urlRoomId: string }>();
26-
const [room, setRoom] = useState<Room | null>(null);
28+
const [hasUserConfirmedLeave, setHasUserConfirmedLeave] = useState(false);
29+
const [shouldSaveOnLeave, setShouldSaveOnLeave] = useState(true);
30+
const [room, setRoom] = useState<Room>();
2731
const [question, setQuestion] = useState<Question | undefined>(undefined);
2832
const [showChat, setShowChat] = useState(false);
2933
const [loading, setLoading] = useState<boolean>(true);
3034
const [error, setError] = useState<string | null>(null);
3135
const resizeTimeoutRef = useRef<NodeJS.Timeout>();
36+
const codeEditorRef = useRef<CodeEditorHandle>(null);
3237

3338
// Extract details from location.state if available
3439
const { roomId, attemptStartedAt, matchUserId, questionId } = locationState || {};
@@ -111,7 +116,68 @@ const CollaborationRoomPage: React.FC = () => {
111116
if (resizeTimeoutRef.current) clearTimeout(resizeTimeoutRef.current);
112117
};
113118
}, [handleResize]);
119+
120+
121+
useEffect(() => {
122+
123+
const saveAttempt = async () => {
124+
if (hasUserConfirmedLeave) {
125+
if (!shouldSaveOnLeave) return; // User chose not to save
126+
}
127+
128+
// Attempt to save
129+
try {
130+
if (!room) return;
131+
await historyUseCases.createOrUpdateUserHistory(
132+
room.questionId,
133+
room.roomId,
134+
new Date(room.attemptStartedAt).getTime().toString(),
135+
Date.now().toString(),
136+
room.userIdTwo?._id,
137+
codeEditorRef.current?.getEditorText() || "",
138+
);
139+
console.log("Attempt saved successfully.");
140+
} catch (error) {
141+
console.error("Failed to save attempt on unmount:", error);
142+
}
143+
};
144+
145+
return () => {
146+
saveAttempt();
147+
};
148+
}, [hasUserConfirmedLeave, room, shouldSaveOnLeave]);
149+
150+
useEffect(() => {
151+
152+
const saveAttemptAsync = async () => {
153+
if (!room) return;
154+
if (hasUserConfirmedLeave) {
155+
if (!shouldSaveOnLeave) return;
156+
}
157+
158+
const editorContent = codeEditorRef.current?.getEditorText() || "";
159+
await historyUseCases.createOrUpdateUserHistory(
160+
room.questionId,
161+
room.roomId,
162+
new Date(room.attemptStartedAt).getTime().toString(),
163+
Date.now().toString(),
164+
room.userIdTwo?._id,
165+
editorContent,
166+
);
167+
};
114168

169+
const handleBeforeUnload = async (event: BeforeUnloadEvent) => {
170+
await saveAttemptAsync();
171+
};
172+
173+
window.addEventListener('beforeunload', handleBeforeUnload);
174+
175+
return () => {
176+
window.removeEventListener('beforeunload', handleBeforeUnload);
177+
};
178+
}, [hasUserConfirmedLeave, room, shouldSaveOnLeave]);
179+
180+
115181
// Resizable Layout Configurations
116182
const { position: questionPosition, separatorProps: verticalSeparatorProps } = useResizable({
117183
axis: "x",
@@ -164,10 +230,15 @@ const CollaborationRoomPage: React.FC = () => {
164230
<div className={styles.editorAndOutputContainer}>
165231
<div className={styles.editorContainer}>
166232
<CodeEditor
233+
ref={codeEditorRef}
167234
questionId={room.questionId}
168235
roomId={room.roomId}
169236
attemptStartedAt={new Date(room.attemptStartedAt)}
170237
collaboratorId={room.userIdTwo?._id}
238+
onUserConfirmedLeave={(shouldSave: boolean) => {
239+
setHasUserConfirmedLeave(true);
240+
setShouldSaveOnLeave(shouldSave);
241+
}}
171242
/>
172243
</div>
173244

0 commit comments

Comments
 (0)