Skip to content

Commit

Permalink
Display question and swap question in collaboration rooms (#185)
Browse files Browse the repository at this point in the history
Fix #80, fix #149, fix #216
  • Loading branch information
gycgabriel authored Oct 31, 2023
1 parent 79d08bb commit e09d0c5
Show file tree
Hide file tree
Showing 30 changed files with 619 additions and 227 deletions.
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"scripts": {
"lint": "next lint",
"dev:local": "dotenv -e ../.env -- yarn dev",
"dev:local": "dotenv -e ../.env -- yarnpkg dev",
"dev": "next dev",
"build": "next build",
"start": "next start -H 0.0.0.0",
Expand Down
15 changes: 12 additions & 3 deletions frontend/src/components/room/description.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,18 @@ import { TypographyH2, TypographySmall } from "../ui/typography";
type DescriptionProps = {
question: Question;
className?: string;
onSwapQuestionClick?: () => void;
};

export default function Description({
question,
className,
onSwapQuestionClick,
}: DescriptionProps) {
return (
<Card className={`m-2 ml-0 px-6 h-full ${className} overflow-y-auto overflow-x-wrap pb-4`}>
<Card
className={`m-2 ml-0 px-6 h-full ${className} overflow-y-auto overflow-x-wrap pb-4`}
>
<div className="flex flex-row items-center justify-between py-2">
<div className="flex items-center justify-center">
<TypographyH2 className="w-fit">{question.title}</TypographyH2>
Expand All @@ -26,7 +30,9 @@ export default function Description({
</TypographySmall>
</Badge>
</div>
<Button variant="secondary">Swap Question</Button>
<Button variant="secondary" onClick={onSwapQuestionClick}>
Swap Question
</Button>
</div>
<div className="flex gap-2">
{question.topics.map((tag) => (
Expand All @@ -37,7 +43,10 @@ export default function Description({
</div>
<div className="py-6">
<TypographySmall>
<div dangerouslySetInnerHTML={{ __html: question.description }} className="w-[40vw] overflow-x-auto"></div>
<div
dangerouslySetInnerHTML={{ __html: question.description }}
className="w-[40vw] overflow-x-auto"
></div>
</TypographySmall>
</div>
</Card>
Expand Down
18 changes: 13 additions & 5 deletions frontend/src/gateway-address/gateway-address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,18 @@
* - Leave NEXT_PUBLIC_GATEWAY_ADDRESS empty for dev environments
* - For prod, pass in a separate address to NEXT_PUBLIC_GATEWAY_ADDRESS
*/
const httpProxyGatewayAddress = process.env.NEXT_PUBLIC_HTTP_PROXY_GATEWAY_ADDRESS || "http://localhost:4000/";
export const wsMatchProxyGatewayAddress = process.env.NEXT_PUBLIC_WS_MATCH_PROXY_GATEWAY_ADDRESS || "http://localhost:4002";
export const wsCollaborationProxyGatewayAddress = process.env.NEXT_PUBLIC_WS_COLLABORATION_PROXY_GATEWAY_ADDRESS
|| "http://localhost:4003";
const httpProxyGatewayAddress =
process.env.NEXT_PUBLIC_HTTP_PROXY_GATEWAY_ADDRESS ||
"http://localhost:4000/";
export const wsMatchProxyGatewayAddress =
process.env.NEXT_PUBLIC_WS_MATCH_PROXY_GATEWAY_ADDRESS ||
"http://localhost:4002";
export const wsCollaborationProxyGatewayAddress =
process.env.NEXT_PUBLIC_WS_COLLABORATION_PROXY_GATEWAY_ADDRESS ||
"http://localhost:4003";

export const userApiPathAddress = httpProxyGatewayAddress + "api/user-service/";
export const questionApiPathAddress = httpProxyGatewayAddress + "api/question-service/";
export const questionApiPathAddress =
httpProxyGatewayAddress + "api/question-service/";
export const matchApiPathAddress =
httpProxyGatewayAddress + "api/matching-service/";
151 changes: 79 additions & 72 deletions frontend/src/hooks/useCollaboration.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useEffect, useState, useRef, use, useContext} from "react";
import { useEffect, useState, useRef, use, useContext } from "react";
import { io, Socket } from "socket.io-client";
import { debounce } from "lodash";
import {
Expand All @@ -7,8 +7,8 @@ import {
} from "../../../utils/shared-ot";
import { TextOp } from "ot-text-unicode";
import { Room, connect } from "twilio-video";
import {wsCollaborationProxyGatewayAddress} from "@/gateway-address/gateway-address";
import {AuthContext} from "@/contexts/AuthContext";
import { wsCollaborationProxyGatewayAddress } from "@/gateway-address/gateway-address";
import { AuthContext } from "@/contexts/AuthContext";

type UseCollaborationProps = {
roomId: string;
Expand All @@ -26,106 +26,113 @@ enum SocketEvents {

var vers = 0;

const useCollaboration = ({ roomId, userId, disableVideo }: UseCollaborationProps) => {
const useCollaboration = ({
roomId,
userId,
disableVideo,
}: UseCollaborationProps) => {
const [socket, setSocket] = useState<Socket | null>(null);
const [text, setText] = useState<string>("#Write your solution here");
const [cursor, setCursor] = useState<number>(
"#Write your solution here".length
);
const [room, setRoom] = useState<Room | null>(null); // twilio room
const [questionId, setQuestionId] = useState<string>("");
const textRef = useRef<string>(text);
const cursorRef = useRef<number>(cursor);
const prevCursorRef = useRef<number>(cursor);
const prevTextRef = useRef<string>(text);
const awaitingAck = useRef<boolean>(false); // ack from sending update
const awaitingSync = useRef<boolean>(false); // synced with server
const twilioTokenRef = useRef<string>("");
const questionId = "1";
const { user: currentUser, authIsReady } = useContext(AuthContext);

useEffect(() => {
if (currentUser) {
currentUser.getIdToken(true).then(
(token) => {
const socketConnection = io(wsCollaborationProxyGatewayAddress, {
extraHeaders: {
"User-Id-Token": token
},
});
setSocket(socketConnection);

socketConnection.emit(SocketEvents.ROOM_JOIN, roomId, userId);
currentUser.getIdToken(true).then((token) => {
const socketConnection = io(wsCollaborationProxyGatewayAddress, {
extraHeaders: {
"User-Id-Token": token,
},
});
setSocket(socketConnection);

socketConnection.emit(SocketEvents.ROOM_JOIN, roomId, userId);
if (questionId !== "") {
socketConnection.emit(SocketEvents.QUESTION_SET, questionId);
}

socketConnection.on("twilio-token", (token: string) => {
twilioTokenRef.current = token;
if (disableVideo) return;
connect(token, {
name: roomId, audio: true,
video: {width: 640, height: 480, frameRate: 24}
}).then((room) => {
socketConnection.on("twilio-token", (token: string) => {
twilioTokenRef.current = token;
if (disableVideo) return;
connect(token, {
name: roomId,
audio: true,
video: { width: 640, height: 480, frameRate: 24 },
})
.then((room) => {
console.log("Connected to Room");
room.localParticipant.videoTracks.forEach(publication => {
room.localParticipant.videoTracks.forEach((publication) => {
publication.track.disable();
});
room.localParticipant.audioTracks.forEach(publication => {
room.localParticipant.audioTracks.forEach((publication) => {
publication.track.disable();
});
setRoom(room);
}).catch(err => {
})
.catch((err) => {
console.log(err, token, userId, roomId);
});
});

socketConnection.on(
SocketEvents.ROOM_UPDATE,
({
version,
text,
cursor,
}: {
version: number;
text: string;
cursor: number | undefined | null;
}) => {
prevCursorRef.current = cursorRef.current;
console.log("prevCursor: " + prevCursorRef.current);

console.log("cursor: " + cursor);

console.log("Update vers to " + version);
vers = version;

if (awaitingAck.current) return;

textRef.current = text;
prevTextRef.current = text;
setText(text);
if (cursor && cursor > -1) {
console.log("Update cursor to " + cursor);
cursorRef.current = cursor;
setCursor(cursor);
} else {
cursorRef.current = prevCursorRef.current;
cursor = prevCursorRef.current;
console.log("Update cursor to " + prevCursorRef.current);
setCursor(prevCursorRef.current);
}
awaitingSync.current = false;
});

socketConnection.on(
SocketEvents.ROOM_UPDATE,
({
version,
text,
cursor,
}: {
version: number;
text: string;
cursor: number | undefined | null;
}) => {
prevCursorRef.current = cursorRef.current;
console.log("prevCursor: " + prevCursorRef.current);

console.log("cursor: " + cursor);

console.log("Update vers to " + version);
vers = version;

if (awaitingAck.current) return;

textRef.current = text;
prevTextRef.current = text;
setText(text);
if (cursor && cursor > -1) {
console.log("Update cursor to " + cursor);
cursorRef.current = cursor;
setCursor(cursor);
} else {
cursorRef.current = prevCursorRef.current;
cursor = prevCursorRef.current;
console.log("Update cursor to " + prevCursorRef.current);
setCursor(prevCursorRef.current);
}
);
awaitingSync.current = false;
}
);

return () => {
socketConnection.disconnect();
if (room) {
room.disconnect();
}
return () => {
socketConnection.disconnect();
if (room) {
room.disconnect();
}
}
);
};
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [roomId, userId]);
}, [roomId, userId, questionId]);

useEffect(() => {
textRef.current = text;
Expand Down Expand Up @@ -167,7 +174,7 @@ const useCollaboration = ({ roomId, userId, disableVideo }: UseCollaborationProp
});
}, [text, socket]);

return { text, setText, cursor, setCursor, room };
return { text, setText, cursor, setCursor, room, setQuestionId };
};

export default useCollaboration;
29 changes: 29 additions & 0 deletions frontend/src/hooks/useMatch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { AuthContext } from "@/contexts/AuthContext";
import {
getMatchByRoomid as getMatchByRoomidApi,
patchMatchQuestionByRoomid as patchMatchQuestionByRoomidApi,
} from "@/pages/api/matchHandler";
import { User } from "firebase/auth";
import { useContext } from "react";

export const useMatch = () => {
const { user: currentUser, authIsReady } = useContext(AuthContext);

const getMatch = async (roomId: string) => {
if (authIsReady && currentUser) {
const match = await getMatchByRoomidApi(currentUser, roomId);
return match;
}
};

const updateQuestionIdInMatch = async (
roomId: string,
questionId: string
) => {
if (authIsReady && currentUser) {
await patchMatchQuestionByRoomidApi(currentUser, roomId, questionId);
}
};

return { getMatch, updateQuestionIdInMatch };
};
14 changes: 13 additions & 1 deletion frontend/src/hooks/useQuestions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@ import {
fetchQuestions as fetchQuestionsApi,
fetchRandomQuestion as fetchRandomQuestionApi,
postQuestion as postNewQuestionApi,
fetchQuestion as fetchQuestionApi,
} from "./../pages/api/questionHandler";
import { AuthContext } from "@/contexts/AuthContext";
import { Difficulty } from "../types/QuestionTypes";

export const useQuestions = () => {
const { user: currentUser, authIsReady } = useContext(AuthContext);

const fetchQuestion = async (qid: string) => {
if (authIsReady && currentUser) {
return fetchQuestionApi(currentUser, qid);
}
};

const fetchQuestions = async () => {
if (authIsReady) {
return fetchQuestionsApi(currentUser);
Expand All @@ -31,5 +38,10 @@ export const useQuestions = () => {
}
};

return { fetchQuestions, fetchRandomQuestion, postNewQuestion };
return {
fetchQuestion,
fetchQuestions,
fetchRandomQuestion,
postNewQuestion,
};
};
Loading

0 comments on commit e09d0c5

Please sign in to comment.