1
- import { useEffect , useRef , useState } from "react" ;
2
- import {
3
- Box ,
4
- Button ,
5
- Container ,
6
- Flex ,
7
- Heading ,
8
- HStack ,
9
- Icon ,
10
- Input ,
11
- InputGroup ,
12
- InputRightElement ,
13
- Link ,
14
- Select ,
15
- Stack ,
16
- Switch ,
17
- Text ,
18
- useToast ,
19
- } from "@chakra-ui/react" ;
20
- import {
21
- VscChevronRight ,
22
- VscFolderOpened ,
23
- VscGist ,
24
- VscRepoPull ,
25
- } from "react-icons/vsc" ;
26
- import useStorage from "use-local-storage-state" ;
1
+ import { Box , Flex , HStack , Icon , Text , useToast } from "@chakra-ui/react" ;
27
2
import Editor from "@monaco-editor/react" ;
28
3
import { editor } from "monaco-editor/esm/vs/editor/editor.api" ;
4
+ import { useEffect , useRef , useState } from "react" ;
5
+ import { VscChevronRight , VscFolderOpened , VscGist } from "react-icons/vsc" ;
6
+ import useLocalStorageState from "use-local-storage-state" ;
7
+
29
8
import rustpadRaw from "../rustpad-server/src/rustpad.rs?raw" ;
30
- import languages from "./languages.json" ;
9
+ import Footer from "./Footer" ;
10
+ import ReadCodeConfirm from "./ReadCodeConfirm" ;
11
+ import Sidebar from "./Sidebar" ;
31
12
import animals from "./animals.json" ;
13
+ import languages from "./languages.json" ;
32
14
import Rustpad , { UserInfo } from "./rustpad" ;
33
15
import useHash from "./useHash" ;
34
- import ConnectionStatus from "./ConnectionStatus" ;
35
- import Footer from "./Footer" ;
36
- import User from "./User" ;
37
16
38
17
function getWsUri ( id : string ) {
39
18
let url = new URL ( `api/socket/${ id } ` , window . location . href ) ;
40
- url . protocol = ( url . protocol == "https:" ) ? "wss:" : "ws:" ;
19
+ url . protocol = url . protocol == "https:" ? "wss:" : "ws:" ;
41
20
return url . href ;
42
21
}
43
22
@@ -56,13 +35,21 @@ function App() {
56
35
"connected" | "disconnected" | "desynchronized"
57
36
> ( "disconnected" ) ;
58
37
const [ users , setUsers ] = useState < Record < number , UserInfo > > ( { } ) ;
59
- const [ name , setName ] = useStorage ( "name" , generateName ) ;
60
- const [ hue , setHue ] = useStorage ( "hue" , generateHue ) ;
38
+ const [ name , setName ] = useLocalStorageState ( "name" , {
39
+ defaultValue : generateName ,
40
+ } ) ;
41
+ const [ hue , setHue ] = useLocalStorageState ( "hue" , {
42
+ defaultValue : generateHue ,
43
+ } ) ;
61
44
const [ editor , setEditor ] = useState < editor . IStandaloneCodeEditor > ( ) ;
62
- const [ darkMode , setDarkMode ] = useStorage ( "darkMode" , ( ) => false ) ;
45
+ const [ darkMode , setDarkMode ] = useLocalStorageState ( "darkMode" , {
46
+ defaultValue : false ,
47
+ } ) ;
63
48
const rustpad = useRef < Rustpad > ( ) ;
64
49
const id = useHash ( ) ;
65
50
51
+ const [ readCodeConfirmOpen , setReadCodeConfirmOpen ] = useState ( false ) ;
52
+
66
53
useEffect ( ( ) => {
67
54
if ( editor ?. getModel ( ) ) {
68
55
const model = editor . getModel ( ) ! ;
@@ -102,7 +89,7 @@ function App() {
102
89
}
103
90
} , [ connection , name , hue ] ) ;
104
91
105
- function handleChangeLanguage ( language : string ) {
92
+ function handleLanguageChange ( language : string ) {
106
93
setLanguage ( language ) ;
107
94
if ( rustpad . current ?. setLanguage ( language ) ) {
108
95
toast ( {
@@ -123,38 +110,30 @@ function App() {
123
110
}
124
111
}
125
112
126
- async function handleCopy ( ) {
127
- await navigator . clipboard . writeText ( `${ window . location . origin } /#${ id } ` ) ;
128
- toast ( {
129
- title : "Copied!" ,
130
- description : "Link copied to clipboard" ,
131
- status : "success" ,
132
- duration : 2000 ,
133
- isClosable : true ,
134
- } ) ;
135
- }
136
-
137
- function handleLoadSample ( ) {
113
+ function handleLoadSample ( confirmed : boolean ) {
138
114
if ( editor ?. getModel ( ) ) {
139
115
const model = editor . getModel ( ) ! ;
116
+ const range = model . getFullModelRange ( ) ;
117
+
118
+ // If there are at least 10 lines of code, ask for confirmation.
119
+ if ( range . endLineNumber >= 10 && ! confirmed ) {
120
+ setReadCodeConfirmOpen ( true ) ;
121
+ return ;
122
+ }
123
+
140
124
model . pushEditOperations (
141
125
editor . getSelections ( ) ,
142
- [
143
- {
144
- range : model . getFullModelRange ( ) ,
145
- text : rustpadRaw ,
146
- } ,
147
- ] ,
148
- ( ) => null
126
+ [ { range, text : rustpadRaw } ] ,
127
+ ( ) => null ,
149
128
) ;
150
129
editor . setPosition ( { column : 0 , lineNumber : 0 } ) ;
151
130
if ( language !== "rust" ) {
152
- handleChangeLanguage ( "rust" ) ;
131
+ handleLanguageChange ( "rust" ) ;
153
132
}
154
133
}
155
134
}
156
135
157
- function handleDarkMode ( ) {
136
+ function handleDarkModeChange ( ) {
158
137
setDarkMode ( ! darkMode ) ;
159
138
}
160
139
@@ -177,116 +156,28 @@ function App() {
177
156
Rustpad
178
157
</ Box >
179
158
< Flex flex = "1 0" minH = { 0 } >
180
- < Container
181
- w = "xs"
182
- bgColor = { darkMode ? "#252526" : "#f3f3f3" }
183
- overflowY = "auto"
184
- maxW = "full"
185
- lineHeight = { 1.4 }
186
- py = { 4 }
187
- >
188
- < ConnectionStatus darkMode = { darkMode } connection = { connection } />
189
-
190
- < Flex justifyContent = "space-between" mt = { 4 } mb = { 1.5 } w = "full" >
191
- < Heading size = "sm" > Dark Mode</ Heading >
192
- < Switch isChecked = { darkMode } onChange = { handleDarkMode } />
193
- </ Flex >
159
+ < Sidebar
160
+ documentId = { id }
161
+ connection = { connection }
162
+ darkMode = { darkMode }
163
+ language = { language }
164
+ currentUser = { { name, hue } }
165
+ users = { users }
166
+ onDarkModeChange = { handleDarkModeChange }
167
+ onLanguageChange = { handleLanguageChange }
168
+ onLoadSample = { ( ) => handleLoadSample ( false ) }
169
+ onChangeName = { ( name ) => name . length > 0 && setName ( name ) }
170
+ onChangeColor = { ( ) => setHue ( generateHue ( ) ) }
171
+ />
172
+ < ReadCodeConfirm
173
+ isOpen = { readCodeConfirmOpen }
174
+ onClose = { ( ) => setReadCodeConfirmOpen ( false ) }
175
+ onConfirm = { ( ) => {
176
+ handleLoadSample ( true ) ;
177
+ setReadCodeConfirmOpen ( false ) ;
178
+ } }
179
+ />
194
180
195
- < Heading mt = { 4 } mb = { 1.5 } size = "sm" >
196
- Language
197
- </ Heading >
198
- < Select
199
- size = "sm"
200
- bgColor = { darkMode ? "#3c3c3c" : "white" }
201
- borderColor = { darkMode ? "#3c3c3c" : "white" }
202
- value = { language }
203
- onChange = { ( event ) => handleChangeLanguage ( event . target . value ) }
204
- >
205
- { languages . map ( ( lang ) => (
206
- < option key = { lang } value = { lang } style = { { color : "black" } } >
207
- { lang }
208
- </ option >
209
- ) ) }
210
- </ Select >
211
-
212
- < Heading mt = { 4 } mb = { 1.5 } size = "sm" >
213
- Share Link
214
- </ Heading >
215
- < InputGroup size = "sm" >
216
- < Input
217
- readOnly
218
- pr = "3.5rem"
219
- variant = "outline"
220
- bgColor = { darkMode ? "#3c3c3c" : "white" }
221
- borderColor = { darkMode ? "#3c3c3c" : "white" }
222
- value = { `${ window . location . origin } /#${ id } ` }
223
- />
224
- < InputRightElement width = "3.5rem" >
225
- < Button
226
- h = "1.4rem"
227
- size = "xs"
228
- onClick = { handleCopy }
229
- _hover = { { bg : darkMode ? "#575759" : "gray.200" } }
230
- bgColor = { darkMode ? "#575759" : "gray.200" }
231
- >
232
- Copy
233
- </ Button >
234
- </ InputRightElement >
235
- </ InputGroup >
236
-
237
- < Heading mt = { 4 } mb = { 1.5 } size = "sm" >
238
- Active Users
239
- </ Heading >
240
- < Stack spacing = { 0 } mb = { 1.5 } fontSize = "sm" >
241
- < User
242
- info = { { name, hue } }
243
- isMe
244
- onChangeName = { ( name ) => name . length > 0 && setName ( name ) }
245
- onChangeColor = { ( ) => setHue ( generateHue ( ) ) }
246
- darkMode = { darkMode }
247
- />
248
- { Object . entries ( users ) . map ( ( [ id , info ] ) => (
249
- < User key = { id } info = { info } darkMode = { darkMode } />
250
- ) ) }
251
- </ Stack >
252
-
253
- < Heading mt = { 4 } mb = { 1.5 } size = "sm" >
254
- About
255
- </ Heading >
256
- < Text fontSize = "sm" mb = { 1.5 } >
257
- < strong > Rustpad</ strong > is an open-source collaborative text editor
258
- based on the < em > operational transformation</ em > algorithm.
259
- </ Text >
260
- < Text fontSize = "sm" mb = { 1.5 } >
261
- Share a link to this pad with others, and they can edit from their
262
- browser while seeing your changes in real time.
263
- </ Text >
264
- < Text fontSize = "sm" mb = { 1.5 } >
265
- Built using Rust and TypeScript. See the{ " " }
266
- < Link
267
- color = "blue.600"
268
- fontWeight = "semibold"
269
- href = "https://github.com/ekzhang/rustpad"
270
- isExternal
271
- >
272
- GitHub repository
273
- </ Link > { " " }
274
- for details.
275
- </ Text >
276
-
277
- < Button
278
- size = "sm"
279
- colorScheme = { darkMode ? "whiteAlpha" : "blackAlpha" }
280
- borderColor = { darkMode ? "purple.400" : "purple.600" }
281
- color = { darkMode ? "purple.400" : "purple.600" }
282
- variant = "outline"
283
- leftIcon = { < VscRepoPull /> }
284
- mt = { 1 }
285
- onClick = { handleLoadSample }
286
- >
287
- Read the code
288
- </ Button >
289
- </ Container >
290
181
< Flex flex = { 1 } minW = { 0 } h = "100%" direction = "column" overflow = "hidden" >
291
182
< HStack
292
183
h = { 6 }
0 commit comments