Skip to content

Commit e09b9e4

Browse files
committed
PEER-234: Persist language to state and other editor
Signed-off-by: SeeuSim <[email protected]>
1 parent 3d1aaec commit e09b9e4

File tree

3 files changed

+104
-56
lines changed

3 files changed

+104
-56
lines changed

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

Lines changed: 65 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
SelectTrigger,
1414
SelectValue,
1515
} from '@/components/ui/select';
16+
import { Skeleton } from '@/components/ui/skeleton';
1617
import { getTheme, type IEditorTheme, languages, themeOptions } from '@/lib/editor/extensions';
1718
import { useCollab } from '@/lib/hooks/use-collab';
1819

@@ -26,67 +27,81 @@ type EditorProps = {
2627
export const Editor = ({ room }: EditorProps) => {
2728
const { height } = useWindowSize();
2829
const [theme, setTheme] = useState<IEditorTheme>('vscodeDark');
29-
const { editorRef, extensions, language, setLanguage, code, setCode, members } = useCollab(room);
30+
const { editorRef, extensions, language, setLanguage, code, setCode, members, isLoading } =
31+
useCollab(room);
3032
const themePreset = useMemo(() => {
3133
return getTheme(theme);
3234
}, [theme]);
3335

3436
return (
3537
<div className='flex flex-col gap-4 p-4'>
36-
<div className='flex w-full justify-between gap-4'>
37-
<div className='flex gap-4'>
38-
<div className='flex flex-col gap-2'>
39-
<Label>Language</Label>
40-
<Select value={language} onValueChange={(val) => setLanguage(val as LanguageName)}>
41-
<SelectTrigger className='max-w-[150px]'>
42-
<SelectValue />
43-
</SelectTrigger>
44-
<SelectContent>
45-
{languages.map((lang, idx) => (
46-
<SelectItem value={lang} key={idx}>
47-
{lang}
48-
</SelectItem>
49-
))}
50-
</SelectContent>
51-
</Select>
38+
{isLoading ? (
39+
<div className='flex h-16 w-full flex-row justify-between pt-4'>
40+
<div className='flex h-10 flex-row gap-4'>
41+
<Skeleton className='h-10 w-16' />
42+
<Skeleton className='h-10 w-32' />
5243
</div>
53-
<div className='flex flex-col gap-2'>
54-
<Label>Theme</Label>
55-
<Select value={theme} onValueChange={(val) => setTheme(val as IEditorTheme)}>
56-
<SelectTrigger className='max-w-[150px]'>
57-
<SelectValue />
58-
</SelectTrigger>
59-
<SelectContent>
60-
{themeOptions.map((theme, idx) => (
61-
<SelectItem value={theme} key={idx}>
62-
{theme}
63-
</SelectItem>
64-
))}
65-
</SelectContent>
66-
</Select>
44+
<div className='flex flex-row items-center gap-2'>
45+
<Skeleton className='size-10 rounded-full' />
46+
<Skeleton className='h-8 w-24' />
6747
</div>
6848
</div>
69-
<div className='flex items-center gap-2'>
70-
<div className='flex gap-1 font-mono text-xs'>
71-
{/* TODO: Get user avatar and display */}
72-
{members.map((member, index) => (
73-
<div
74-
className='grid size-8 place-items-center !overflow-clip rounded-full border p-1 text-xs'
75-
style={{
76-
borderColor: member.color,
77-
}}
78-
key={index}
79-
>
80-
<span className='translate-x-[calc(-50%+12px)]'>{member.userId}</span>
81-
</div>
82-
))}
49+
) : (
50+
<div className='flex w-full justify-between gap-4'>
51+
<div className='flex gap-4'>
52+
<div className='flex flex-col gap-2'>
53+
<Label>Language</Label>
54+
<Select value={language} onValueChange={(val) => setLanguage(val as LanguageName)}>
55+
<SelectTrigger className='max-w-[150px]'>
56+
<SelectValue />
57+
</SelectTrigger>
58+
<SelectContent>
59+
{languages.map((lang, idx) => (
60+
<SelectItem value={lang} key={idx}>
61+
{lang}
62+
</SelectItem>
63+
))}
64+
</SelectContent>
65+
</Select>
66+
</div>
67+
<div className='flex flex-col gap-2'>
68+
<Label>Theme</Label>
69+
<Select value={theme} onValueChange={(val) => setTheme(val as IEditorTheme)}>
70+
<SelectTrigger className='max-w-[150px]'>
71+
<SelectValue />
72+
</SelectTrigger>
73+
<SelectContent>
74+
{themeOptions.map((theme, idx) => (
75+
<SelectItem value={theme} key={idx}>
76+
{theme}
77+
</SelectItem>
78+
))}
79+
</SelectContent>
80+
</Select>
81+
</div>
82+
</div>
83+
<div className='flex items-center gap-2'>
84+
<div className='flex gap-1 font-mono text-xs'>
85+
{/* TODO: Get user avatar and display */}
86+
{members.map((member, index) => (
87+
<div
88+
className='grid size-8 place-items-center !overflow-clip rounded-full border p-1 text-xs'
89+
style={{
90+
borderColor: member.color,
91+
}}
92+
key={index}
93+
>
94+
<span className='translate-x-[calc(-50%+12px)]'>{member.userId}</span>
95+
</div>
96+
))}
97+
</div>
98+
<Button variant='destructive' size='sm' className='group flex items-center !px-2 !py-1'>
99+
<ChevronLeftIcon className='transition-transform duration-200 ease-in-out group-hover:-translate-x-px' />
100+
<span>Disconnect</span>
101+
</Button>
83102
</div>
84-
<Button variant='destructive' size='sm' className='group flex items-center !px-2 !py-1'>
85-
<ChevronLeftIcon className='transition-transform duration-200 ease-in-out group-hover:-translate-x-px' />
86-
<span>Disconnect</span>
87-
</Button>
88103
</div>
89-
</div>
104+
)}
90105
<div className='border-border w-full !overflow-clip rounded-lg border shadow-sm'>
91106
<CodeMirror
92107
ref={editorRef}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { cn } from '@/lib/utils';
2+
3+
function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
4+
return <div className={cn('animate-pulse rounded-md bg-primary/10', className)} {...props} />;
5+
}
6+
7+
export { Skeleton };

frontend/src/lib/hooks/use-collab.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,23 @@ type IYjsUserState = { user: { name: string; userId: string; color: string; colo
3030
// TODO: Test if collab logic works
3131
export const useCollab = (roomId: string) => {
3232
const [userId] = useState(getUserId());
33-
const [code, setCode] = useState('');
3433
const editorRef = useRef<ReactCodeMirrorRef>(null);
34+
const [sharedDocRef, setSharedDocRef] = useState<Y.Map<unknown>>();
3535
const [extensions, setExtensions] = useState<Array<Extension>>(baseExtensions);
36-
const [language, setLanguage] = useState<LanguageName>('python');
3736

37+
const [isLoading, setIsLoading] = useState(true);
38+
const [code, setCode] = useState('');
39+
const [language, _setLanguage] = useState<LanguageName>('python');
3840
const [members, _setMembers] = useState<Array<IYjsUserState['user']>>([]);
3941
const setMembers = useCallback(_setMembers, [members]);
42+
const setLanguage = useCallback(
43+
(lang: LanguageName) => {
44+
_setLanguage(lang);
45+
// Get language from room
46+
sharedDocRef?.set('language', lang);
47+
},
48+
[sharedDocRef]
49+
);
4050

4151
const langExtension = useMemo(() => {
4252
return getLanguage(language);
@@ -58,8 +68,8 @@ export const useCollab = (roomId: string) => {
5868
return;
5969
}
6070

61-
const ytext = doc.getText('codemirror');
62-
const undoManager = new Y.UndoManager(ytext);
71+
const yText = doc.getText('codemirror');
72+
const undoManager = new Y.UndoManager(yText);
6373
const awareness = provider.awareness;
6474

6575
provider.on('status', (event: unknown) => {
@@ -82,13 +92,28 @@ export const useCollab = (roomId: string) => {
8292
};
8393
awareness.setLocalStateField('user', userState);
8494

85-
const collabExt = yCollab(ytext, awareness, { undoManager });
86-
setCode(ytext.toString());
95+
const collabExt = yCollab(yText, awareness, { undoManager });
96+
setCode(yText.toString());
8797
setExtensions([...extensions, collabExt]);
8898

99+
// Initialise room preferences
100+
const yState = doc.getMap('state');
101+
yState.observe((event, _transaction) => {
102+
if (event.keysChanged.has('language')) {
103+
// TODO: Add toast notif for toast language updates!
104+
const lang = yState.get('language') as LanguageName;
105+
_setLanguage(lang);
106+
console.log('Language changed to: ' + lang);
107+
}
108+
});
109+
_setLanguage((yState.get('language') as LanguageName) ?? 'python');
110+
setSharedDocRef(yState);
111+
setTimeout(() => setIsLoading(false), 100); // Flush to next render
112+
89113
return () => {
90114
doc.destroy();
91115
provider.destroy();
116+
setSharedDocRef(undefined);
92117
};
93118
}
94119
}, [editorRef, roomId]);
@@ -101,5 +126,6 @@ export const useCollab = (roomId: string) => {
101126
code,
102127
setCode,
103128
members,
129+
isLoading,
104130
};
105131
};

0 commit comments

Comments
 (0)