1
- import React , { createContext , useState , useContext , ReactNode , useEffect , useMemo , useCallback } from "react" ;
1
+ import React , { createContext , useState , useContext , ReactNode , useEffect , useMemo } from "react" ;
2
2
import * as Y from "yjs" ;
3
3
import * as monaco from "monaco-editor" ;
4
4
import { MonacoBinding } from "y-monaco" ;
@@ -20,7 +20,6 @@ interface CollaborationContextType {
20
20
execResult : CodeExecResult | null ;
21
21
setRoomId : ( roomId : string ) => void ;
22
22
connectedUsers : string [ ] ;
23
- disconnect : ( ) => void ;
24
23
}
25
24
26
25
const CollaborationContext = createContext < CollaborationContextType | undefined > ( undefined ) ;
@@ -42,81 +41,86 @@ export const CollaborationProvider: React.FC<{ children: ReactNode }> = ({ child
42
41
const ymap : Y . Map < any > = useMemo ( ( ) => ydoc . getMap ( "sharedMap" ) , [ ydoc ] ) ;
43
42
44
43
const [ roomId , setRoomId ] = useState < string | null > ( null ) ;
44
+
45
45
const [ languages , setLanguages ] = useState < Language [ ] > ( [ ] ) ;
46
46
const [ execResult , setExecResult ] = useState < CodeExecResult | null > ( null ) ;
47
47
const [ isExecuting , setIsExecuting ] = useState < boolean > ( false ) ;
48
48
const [ connectedUsers , setConnectedUsers ] = useState < string [ ] > ( [ ] ) ;
49
+
49
50
const [ editor , setEditor ] = useState < monaco . editor . IStandaloneCodeEditor | null > ( null ) ;
50
51
const [ provider , setProvider ] = useState < WebsocketProvider | null > ( null ) ;
51
52
const [ binding , setBinding ] = useState < MonacoBinding | null > ( null ) ;
52
53
53
- const disconnect = useCallback ( ( ) => {
54
- binding ?. destroy ( ) ;
55
- provider ?. destroy ( ) ;
56
- ydoc ?. destroy ( ) ;
57
- setConnectedUsers ( [ ] ) ;
58
- setProvider ( null ) ;
59
- setBinding ( null ) ;
60
- setRoomId ( null ) ;
61
- } , [ binding , provider , ydoc ] ) ;
62
-
54
+ // This effect manages the lifetime of the yjs doc and the provider
63
55
useEffect ( ( ) => {
64
- if ( ! roomId ) return ;
65
-
66
- const newProvider = new WebsocketProvider ( "ws://localhost:1234" , roomId , ydoc ) ;
67
- setProvider ( newProvider ) ;
68
-
69
- newProvider . awareness . setLocalStateField ( USERNAME , username ) ;
70
- newProvider . awareness . on ( "change" , ( ) => {
71
- const users = Array . from ( newProvider . awareness . getStates ( ) . values ( ) ) ;
72
- const uniqueUsers = new Set ( users . map ( ( user ) => user [ USERNAME ] ) ) ; // Use Set for uniqueness
73
- setConnectedUsers ( Array . from ( uniqueUsers ) ) ; // Convert Set back to Array
56
+ if ( roomId == null ) {
57
+ return ;
58
+ }
59
+ const provider = new WebsocketProvider ( "ws://localhost:1234" , roomId , ydoc ) ;
60
+ setProvider ( provider ) ;
61
+
62
+ provider . awareness . setLocalStateField ( USERNAME , username ) ;
63
+ provider . awareness . on ( "change" , ( update : any ) => {
64
+ const users = Array . from ( provider . awareness . getStates ( ) . values ( ) ) ;
65
+ setConnectedUsers ( users . map ( ( user ) => user [ USERNAME ] ) ) ;
66
+ // TODO: Some UI feedback about connection status of the other user
74
67
} ) ;
75
68
76
69
return ( ) => {
77
- disconnect ( ) ;
70
+ provider ?. destroy ( ) ;
71
+ ydoc ?. destroy ( ) ;
78
72
} ;
79
- } , [ ydoc , roomId , username , USERNAME , disconnect ] ) ;
73
+ } , [ ydoc , roomId ] ) ;
80
74
75
+ // This effect manages the lifetime of the editor binding
81
76
useEffect ( ( ) => {
82
- if ( ! provider || ! editor ?. getModel ( ) ) return ;
77
+ if ( provider == null || editor == null || editor . getModel ( ) == null ) {
78
+ return ;
79
+ }
83
80
84
- const newBinding = new MonacoBinding (
81
+ const binding = new MonacoBinding (
85
82
ydoc . getText ( "monaco" ) ,
86
83
editor . getModel ( ) ! ,
87
84
new Set ( [ editor ] ) ,
88
- provider . awareness
85
+ provider ? .awareness
89
86
) ;
90
- setBinding ( newBinding ) ;
87
+
88
+ setBinding ( binding ) ;
91
89
92
90
ymap . observe ( ( event ) => {
93
91
event . changes . keys . forEach ( ( change , key ) => {
94
92
if ( key === SELECTED_LANGUAGE ) {
95
93
const language : Language = ymap . get ( SELECTED_LANGUAGE ) ;
96
94
setSelectedLanguage ( language ) ;
97
- monaco . editor . setModelLanguage ( editor . getModel ( ) ! , language . language ) ;
95
+ const model = editor . getModel ( ) ;
96
+ monaco . editor . setModelLanguage ( model ! , language . language ) ;
98
97
}
99
98
} ) ;
100
99
} ) ;
101
100
101
+ // Set the editor's language
102
102
const language : Language = ymap . get ( SELECTED_LANGUAGE ) ;
103
- monaco . editor . setModelLanguage ( editor . getModel ( ) ! , language ?. language ?? "javascript" ) ;
103
+ const model = editor . getModel ( ) ;
104
+ monaco . editor . setModelLanguage ( model ! , language ?. language ?? "javascript" ) ;
104
105
105
- return ( ) => newBinding . destroy ( ) ;
106
- } , [ ydoc , provider , editor , ymap , SELECTED_LANGUAGE ] ) ;
106
+ return ( ) => {
107
+ binding . destroy ( ) ;
108
+ } ;
109
+ } , [ ydoc , provider , editor , ymap ] ) ;
107
110
108
111
useEffect ( ( ) => {
109
112
initialiseLanguages ( ) ;
110
113
} , [ ] ) ;
111
114
112
115
const initialiseLanguages = async ( ) => {
116
+ // Initialise language dropdown
113
117
const allLanguages = monaco . languages . getLanguages ( ) ;
114
118
const pistonLanguageVersions = await PistonClient . getLanguageVersions ( ) ;
115
119
setLanguages (
116
120
allLanguages
117
121
. filter ( ( lang ) => pistonLanguageVersions . some ( ( pistonLang : any ) => pistonLang . language === lang . id ) )
118
122
. map ( ( lang ) => ( {
119
- alias : lang . aliases ?. [ 0 ] || lang . id ,
123
+ alias : lang . aliases && lang . aliases . length > 0 ? lang . aliases [ 0 ] : lang . id ,
120
124
language : lang . id ,
121
125
version : pistonLanguageVersions . find ( ( pistonLang : any ) => pistonLang . language === lang . id ) ?. version
122
126
} ) )
@@ -131,9 +135,11 @@ export const CollaborationProvider: React.FC<{ children: ReactNode }> = ({ child
131
135
try {
132
136
setIsExecuting ( true ) ;
133
137
const sourceCode = editor ?. getValue ( ) ;
134
- if ( ! sourceCode ) return ;
135
-
136
- const output = await PistonClient . executeCode ( selectedLanguage , sourceCode ) ;
138
+ if ( ! sourceCode ) {
139
+ // TODO
140
+ return ;
141
+ }
142
+ const output : CodeExecResult = await PistonClient . executeCode ( selectedLanguage , sourceCode ) ;
137
143
setExecResult ( output ) ;
138
144
} catch ( e ) {
139
145
toast . error ( "There was an issue running the code" ) ;
@@ -157,8 +163,7 @@ export const CollaborationProvider: React.FC<{ children: ReactNode }> = ({ child
157
163
handleExecuteCode,
158
164
isExecuting,
159
165
execResult,
160
- connectedUsers,
161
- disconnect
166
+ connectedUsers
162
167
} }
163
168
>
164
169
{ children }
0 commit comments