Skip to content

Commit 91412c9

Browse files
committed
PEER-234: Try add collab server
Signed-off-by: SeeuSim <[email protected]>
1 parent 887b282 commit 91412c9

File tree

6 files changed

+166
-48
lines changed

6 files changed

+166
-48
lines changed

frontend/.env.docker

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ FRONTEND_ENV=local
22

33
VITE_USER_SERVICE=http://host.docker.internal:9001
44
VITE_QUESTION_SERVICE=http://host.docker.internal:9002
5+
VITE_COLLAB_SERVICE=http://host.docker.internal:9003
56
FRONTEND_PORT=3000

frontend/.env.local

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ FRONTEND_ENV=local
22

33
VITE_USER_SERVICE=http://localhost:9001
44
VITE_QUESTION_SERVICE=http://localhost:9002
5+
VITE_COLLAB_SERVICE=http://localhost:9003

frontend/package.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@
99
"preview": "vite preview --port 5173 --host 0.0.0.0"
1010
},
1111
"dependencies": {
12-
"@codemirror/state": "^6.4.1",
13-
"@codemirror/theme-one-dark": "^6.1.2",
14-
"@codemirror/view": "^6.34.1",
1512
"@hookform/resolvers": "^3.9.0",
1613
"@radix-ui/react-alert-dialog": "^1.1.1",
1714
"@radix-ui/react-checkbox": "^1.1.1",
@@ -36,7 +33,6 @@
3633
"axios": "^1.7.7",
3734
"class-variance-authority": "^0.7.0",
3835
"clsx": "^2.1.1",
39-
"codemirror": "^5.65.18",
4036
"env-cmd": "^10.1.0",
4137
"lucide-react": "^0.441.0",
4238
"mobx": "^6.13.2",
@@ -53,6 +49,8 @@
5349
"tailwind-merge": "^2.5.2",
5450
"tailwindcss-animate": "^1.0.7",
5551
"y-codemirror": "^3.0.1",
52+
"y-codemirror.next": "^0.3.5",
53+
"y-socket.io": "^1.1.3",
5654
"y-websocket": "^2.0.4",
5755
"yjs": "^13.6.20",
5856
"zod": "^3.23.8"

frontend/src/components/blocks/interview/editor.tsx

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,18 @@
1+
import { getTheme, type IEditorTheme, languages, themeOptions } from '@/lib/editor/extensions';
2+
import { useWindowSize } from '@uidotdev/usehooks';
3+
import type { LanguageName } from '@uiw/codemirror-extensions-langs';
14
import CodeMirror from '@uiw/react-codemirror';
25
import { useState } from 'react';
3-
import { useWindowSize } from '@uidotdev/usehooks';
4-
import {
5-
extensions,
6-
getTheme,
7-
IEditorTheme,
8-
languages,
9-
themeOptions,
10-
} from '@/lib/editor/extensions';
11-
import { LanguageName } from '@uiw/codemirror-extensions-langs';
6+
7+
import { Label } from '@/components/ui/label';
128
import {
139
Select,
1410
SelectContent,
1511
SelectItem,
1612
SelectTrigger,
1713
SelectValue,
1814
} from '@/components/ui/select';
19-
import { Label } from '@/components/ui/label';
15+
import { useCollab } from '@/lib/hooks/use-collab';
2016

2117
const EXTENSION_HEIGHT = 250;
2218
const MIN_EDITOR_HEIGHT = 350;
@@ -30,6 +26,7 @@ export const Editor = ({ room }: EditorProps) => {
3026
const [code, setCode] = useState('');
3127
const [language, setLanguage] = useState<LanguageName>('python');
3228
const [theme, setTheme] = useState<IEditorTheme>('darcula');
29+
const { editorRef, extensions } = useCollab(room);
3330
return (
3431
<div className='flex flex-col gap-4 p-4'>
3532
<div className='flex gap-4'>
@@ -66,6 +63,7 @@ export const Editor = ({ room }: EditorProps) => {
6663
</div>
6764
<div className='border-border w-full text-clip rounded-lg border shadow-sm'>
6865
<CodeMirror
66+
ref={editorRef}
6967
style={{
7068
fontSize: '14px',
7169
}}
@@ -75,12 +73,12 @@ export const Editor = ({ room }: EditorProps) => {
7573
setCode(value);
7674
}}
7775
theme={getTheme(theme)}
78-
extensions={extensions}
7976
lang={language}
8077
basicSetup={{
8178
tabSize: language === 'python' ? 4 : 2,
8279
indentOnInput: true,
8380
}}
81+
extensions={extensions}
8482
/>
8583
</div>
8684
</div>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { EditorState, Extension, type ReactCodeMirrorRef } from '@uiw/react-codemirror';
2+
import { useEffect, useRef, useState } from 'react';
3+
import { yCollab } from 'y-codemirror.next';
4+
import * as Y from 'yjs';
5+
import { SocketIOProvider } from 'y-socket.io';
6+
7+
import { COLLAB_SERVICE } from '@/services/api-clients';
8+
import { extensions as baseExtensions } from '@/lib/editor/extensions';
9+
10+
// credit: https://github.com/yjs/y-websocket
11+
const usercolors = [
12+
{ color: '#30bced', light: '#30bced33' },
13+
{ color: '#6eeb83', light: '#6eeb8333' },
14+
{ color: '#ffbc42', light: '#ffbc4233' },
15+
{ color: '#ecd444', light: '#ecd44433' },
16+
{ color: '#ee6352', light: '#ee635233' },
17+
{ color: '#9ac2c9', light: '#9ac2c933' },
18+
{ color: '#8acb88', light: '#8acb8833' },
19+
{ color: '#1be7ff', light: '#1be7ff33' },
20+
];
21+
22+
const getRandomColor = () => {
23+
return usercolors[Math.floor(Math.random() * usercolors.length)];
24+
};
25+
26+
// TODO: Test if collab logic works
27+
export const useCollab = (roomId: string) => {
28+
const editorRef = useRef<ReactCodeMirrorRef>(null);
29+
const [extensions, setExtensions] = useState<Array<Extension>>([baseExtensions]);
30+
useEffect(() => {
31+
if (editorRef.current) {
32+
const doc = new Y.Doc();
33+
let provider = null;
34+
try {
35+
provider = new SocketIOProvider(COLLAB_SERVICE, roomId, doc, {});
36+
} catch (err) {
37+
const { name, message } = err as Error;
38+
console.log(
39+
`An error occurred connecting to the Collab Server: ${JSON.stringify({ name, message })}`
40+
);
41+
return;
42+
}
43+
if (!provider.socket.connected) {
44+
console.log('Failed to connect');
45+
return;
46+
}
47+
const ytext = doc.getText('codemirror');
48+
const undoManager = new Y.UndoManager(ytext);
49+
const awareness = provider.awareness;
50+
const { color, light } = getRandomColor();
51+
awareness.setLocalStateField('user', {
52+
name: `Anon`,
53+
color: color,
54+
colorLight: light,
55+
});
56+
const collabExt = yCollab(ytext, awareness, { undoManager });
57+
editorRef.current.state = EditorState.create({
58+
doc: ytext.toJSON(),
59+
});
60+
setExtensions([...extensions, collabExt]);
61+
return () => {
62+
doc.destroy();
63+
provider.disconnect();
64+
};
65+
}
66+
}, [editorRef]);
67+
return {
68+
editorRef,
69+
extensions,
70+
};
71+
};

0 commit comments

Comments
 (0)