Skip to content

Commit 42c812c

Browse files
committed
Add connection status of users
1 parent 6013d4e commit 42c812c

File tree

3 files changed

+77
-21
lines changed

3 files changed

+77
-21
lines changed

frontend/src/domain/context/CollaborationContext.tsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import React, { createContext, useState, useContext, ReactNode, useEffect, useRef, useMemo } from "react";
2-
import { Monaco } from "@monaco-editor/react";
1+
import React, { createContext, useState, useContext, ReactNode, useEffect, useMemo } from "react";
32
import * as Y from "yjs";
43
import * as monaco from "monaco-editor";
54
import { MonacoBinding } from "y-monaco";
@@ -20,6 +19,7 @@ interface CollaborationContextType {
2019
isExecuting: boolean;
2120
execResult: CodeExecResult | null;
2221
setRoomId: (roomId: string) => void;
22+
connectedUsers: string[];
2323
}
2424

2525
const CollaborationContext = createContext<CollaborationContextType | undefined>(undefined);
@@ -36,14 +36,17 @@ export const CollaborationProvider: React.FC<{ children: ReactNode }> = ({ child
3636
version: "1.32.3",
3737
alias: "Javascript"
3838
});
39-
const [languages, setLanguages] = useState<Language[]>([]);
40-
const [execResult, setExecResult] = useState<CodeExecResult | null>(null);
41-
const [isExecuting, setIsExecuting] = useState<boolean>(false);
4239

4340
const ydoc = useMemo(() => new Y.Doc(), []);
4441
const ymap: Y.Map<any> = useMemo(() => ydoc.getMap("sharedMap"), [ydoc]);
4542

4643
const [roomId, setRoomId] = useState<string | null>(null);
44+
45+
const [languages, setLanguages] = useState<Language[]>([]);
46+
const [execResult, setExecResult] = useState<CodeExecResult | null>(null);
47+
const [isExecuting, setIsExecuting] = useState<boolean>(false);
48+
const [connectedUsers, setConnectedUsers] = useState<string[]>([]);
49+
4750
const [editor, setEditor] = useState<monaco.editor.IStandaloneCodeEditor | null>(null);
4851
const [provider, setProvider] = useState<WebsocketProvider | null>(null);
4952
const [binding, setBinding] = useState<MonacoBinding | null>(null);
@@ -55,11 +58,14 @@ export const CollaborationProvider: React.FC<{ children: ReactNode }> = ({ child
5558
}
5659
const provider = new WebsocketProvider("ws://localhost:1234", roomId, ydoc);
5760
setProvider(provider);
61+
5862
provider.awareness.setLocalStateField(USERNAME, username);
5963
provider.awareness.on("change", (update: any) => {
60-
const users = provider.awareness.getStates();
64+
const users = Array.from(provider.awareness.getStates().values());
65+
setConnectedUsers(users.map((user) => user[USERNAME]));
6166
// TODO: Some UI feedback about connection status of the other user
6267
});
68+
6369
return () => {
6470
provider?.destroy();
6571
ydoc?.destroy();
@@ -156,7 +162,8 @@ export const CollaborationProvider: React.FC<{ children: ReactNode }> = ({ child
156162
handleChangeLanguage,
157163
handleExecuteCode,
158164
isExecuting,
159-
execResult
165+
execResult,
166+
connectedUsers
160167
}}
161168
>
162169
{children}

frontend/src/presentation/components/CodeEditor/CodeEditor.module.css

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@
1616
align-items: center;
1717
}
1818

19+
.toolbarLeft,
20+
.toolbarRight {
21+
display: flex;
22+
justify-content: start;
23+
align-items: center;
24+
gap: 8px;
25+
}
26+
27+
.toolbarLeft,
1928
.editor {
2029
flex: 1;
2130
}
@@ -36,3 +45,30 @@
3645
.pendingExecution p {
3746
padding: 0;
3847
}
48+
49+
.connectionStatusContainer {
50+
display: flex;
51+
justify-content: flex-end;
52+
align-items: center;
53+
gap: 24px;
54+
}
55+
56+
.connectionStatus {
57+
display: flex;
58+
align-items: center;
59+
gap: 6px;
60+
}
61+
62+
.statusUsername {
63+
line-height: 100%;
64+
max-width: 120px;
65+
overflow: hidden;
66+
text-overflow: ellipsis;
67+
}
68+
69+
.greenCircle {
70+
width: 10px;
71+
height: 10px;
72+
background-color: rgb(18, 221, 18);
73+
border-radius: 50%;
74+
}

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

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ interface CodeEditorProps {
1313
}
1414

1515
const CodeEditor: React.FC<CodeEditorProps> = ({ roomId }) => {
16-
const { onEditorIsMounted, isExecuting, setRoomId } = useCollaboration();
16+
const { onEditorIsMounted, isExecuting, setRoomId, connectedUsers } = useCollaboration();
1717
const [theme, setTheme] = useState("vs-light");
1818

1919
const handleEditorDidMount = (editor: monaco.editor.IStandaloneCodeEditor) => {
@@ -32,21 +32,34 @@ const CodeEditor: React.FC<CodeEditorProps> = ({ roomId }) => {
3232
return (
3333
<div className={styles.container}>
3434
<div className={styles.toolbar}>
35-
<LanguageSelector />
36-
{isExecuting && (
37-
<div className={styles.pendingExecution}>
38-
<Spin />
39-
<p>Running...</p>
35+
<div className={styles.toolbarLeft}>
36+
<LanguageSelector />
37+
{isExecuting && (
38+
<div className={styles.pendingExecution}>
39+
<Spin />
40+
<p>Running...</p>
41+
</div>
42+
)}
43+
</div>
44+
<div className={styles.toolbarRight}>
45+
<div className={styles.connectionStatusContainer}>
46+
{connectedUsers.map((user) => (
47+
<div key={user} className={styles.connectionStatus}>
48+
<div className={styles.greenCircle}></div>
49+
<div className={styles.statusUsername}>{user}</div>
50+
</div>
51+
))}
4052
</div>
41-
)}
42-
<div className={styles.buttonGroup}>
43-
<Button
44-
onClick={handleToggleTheme}
45-
type="text"
46-
icon={theme === "vs-light" ? <SunOutlined /> : <MoonFilled />}
47-
/>
4853

49-
<CodeActionButtons disabled={isExecuting} />
54+
<div className={styles.buttonGroup}>
55+
<Button
56+
onClick={handleToggleTheme}
57+
type="text"
58+
icon={theme === "vs-light" ? <SunOutlined /> : <MoonFilled />}
59+
/>
60+
61+
<CodeActionButtons disabled={isExecuting} />
62+
</div>
5063
</div>
5164
</div>
5265
<div className={styles.editor}>

0 commit comments

Comments
 (0)