Skip to content

Commit

Permalink
Frontend add cursor support
Browse files Browse the repository at this point in the history
  • Loading branch information
gycgabriel committed Oct 12, 2023
1 parent 1ce8685 commit 0d783c6
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 8 deletions.
41 changes: 39 additions & 2 deletions frontend/src/components/room/code-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
Settings,
Play,
} from "lucide-react";
import Editor from "@monaco-editor/react";
import Editor, { OnMount } from "@monaco-editor/react";

import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
Expand All @@ -25,6 +25,7 @@ import {
} from "@/components/ui/popover";
import { Card } from "../ui/card";
import { TypographyBody, TypographyBodyHeavy } from "../ui/typography";
import { editor } from "monaco-editor";

type CodeEditorProps = {
theme?: string;
Expand All @@ -33,7 +34,9 @@ type CodeEditorProps = {
defaultValue?: string;
className?: string;
text: string;
cursor: number;
onChange: React.Dispatch<React.SetStateAction<string>>;
onCursorChange: React.Dispatch<React.SetStateAction<number>>;
};

const frameworks = [
Expand Down Expand Up @@ -66,17 +69,50 @@ export default function CodeEditor({
defaultValue = "#Write your solution here",
className,
text,
cursor,
onChange,
onCursorChange,
}: CodeEditorProps) {
const [open, setOpen] = React.useState(false);
const [value, setValue] = React.useState("");

const [monacoInstance, setMonacoInstance] =
React.useState<editor.IStandaloneCodeEditor | null>(null);

const editorMount: OnMount = (editorL: editor.IStandaloneCodeEditor) => {
setMonacoInstance(editorL);
};

const setCursorPosition = React.useCallback(
(cursor: number) => {
if (!monacoInstance) return;

const position = monacoInstance.getModel()!.getPositionAt(cursor);
monacoInstance.setPosition(position);
},
[monacoInstance]
);

React.useEffect(() => {
if (cursor !== undefined) {
setCursorPosition(cursor);
}
}, [cursor, setCursorPosition]);

const editorOnChange = React.useCallback(
(value: string | undefined) => {
if (!monacoInstance) return;
if (value === undefined) return;

if (monacoInstance.getPosition()) {
const cursor = monacoInstance
.getModel()!
.getOffsetAt(monacoInstance.getPosition()!);
onCursorChange(cursor);
}
onChange(value);
},
[onChange]
[onChange, onCursorChange, monacoInstance]
);

return (
Expand Down Expand Up @@ -142,6 +178,7 @@ export default function CodeEditor({
value={text}
theme={theme}
onChange={(e) => editorOnChange(e)}
onMount={editorMount}
/>
<Card className="flex-1 p-2 mt-2">
<div className="h-[9vh] p-2">
Expand Down
30 changes: 26 additions & 4 deletions frontend/src/hooks/useCollaboration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useEffect, useState, useRef } from "react";
import { io, Socket } from "socket.io-client";
import { debounce } from "lodash";
import {
TextOperationSet,
TextOperationSetWithCursor,
createTextOpFromTexts,
} from "../../../utils/shared-ot";
import { TextOp } from "ot-text-unicode";
Expand All @@ -24,7 +24,11 @@ var vers = 0;
const useCollaboration = ({ roomId, userId }: 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 textRef = useRef<string>(text);
const cursorRef = 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
Expand All @@ -37,7 +41,15 @@ const useCollaboration = ({ roomId, userId }: UseCollaborationProps) => {

socketConnection.on(
SocketEvents.ROOM_UPDATE,
({ version, text }: { version: number; text: string }) => {
({
version,
text,
cursor,
}: {
version: number;
text: string;
cursor: number | undefined | null;
}) => {
console.log("Update vers to " + version);
vers = version;

Expand All @@ -46,6 +58,11 @@ const useCollaboration = ({ roomId, userId }: UseCollaborationProps) => {
textRef.current = text;
prevTextRef.current = text;
setText(text);
if (cursor && cursor > -1) {
console.log("Update cursor to " + cursor);
cursorRef.current = cursor;
setCursor(cursor);
}
awaitingSync.current = false;
}
);
Expand All @@ -59,6 +76,10 @@ const useCollaboration = ({ roomId, userId }: UseCollaborationProps) => {
textRef.current = text;
}, [text]);

useEffect(() => {
cursorRef.current = cursor;
}, [cursor]);

useEffect(() => {
if (!socket) return;

Expand All @@ -80,17 +101,18 @@ const useCollaboration = ({ roomId, userId }: UseCollaborationProps) => {

console.log(textOp);

const textOperationSet: TextOperationSet = {
const textOperationSet: TextOperationSetWithCursor = {
version: vers,
operations: textOp,
cursor: cursorRef.current,
};

socket.emit(SocketEvents.ROOM_UPDATE, textOperationSet, () => {
awaitingAck.current = false;
});
}, [text, socket]);

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

export default useCollaboration;
9 changes: 7 additions & 2 deletions frontend/src/pages/room/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default function Room() {
const roomId = router.query.id as string;
const userId = "user1";

const { text, setText } = useCollaboration({
const { text, setText, cursor, setCursor } = useCollaboration({
roomId: roomId as string,
userId,
});
Expand Down Expand Up @@ -52,7 +52,12 @@ export default function Room() {
<TabsContent value="solution">{question.solution}</TabsContent>
</Tabs>
<div className="flex-1">
<CodeEditor text={text} onChange={setText} />
<CodeEditor
text={text}
cursor={cursor}
onChange={setText}
onCursorChange={setCursor}
/>
</div>
</div>
</div>
Expand Down
4 changes: 4 additions & 0 deletions utils/shared-ot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ export interface TextOperationSet {
operations: TextOp;
}

export interface TextOperationSetWithCursor extends TextOperationSet {
cursor?: number;
}

export function createTextOpFromTexts(
prevText: string,
currentText: string
Expand Down

0 comments on commit 0d783c6

Please sign in to comment.