Skip to content

Commit e672499

Browse files
authored
Merge branch 'ekzhang:main' into main
2 parents 279e7c1 + 54e4a93 commit e672499

File tree

14 files changed

+1649
-2195
lines changed

14 files changed

+1649
-2195
lines changed

.prettierrc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
{
2-
"proseWrap": "always"
2+
"plugins": ["@trivago/prettier-plugin-sort-imports"],
3+
"proseWrap": "always",
4+
"importOrder": ["^[./]"],
5+
"importOrderSeparation": true,
6+
"importOrderSortSpecifiers": true
37
}

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
[workspace]
22
resolver = "2"
33
members = ["rustpad-server", "rustpad-wasm"]
4+
5+
[profile.release]
6+
lto = true

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ RUN npm ci
2020
COPY . .
2121
ARG GITHUB_SHA
2222
ENV VITE_SHA=${GITHUB_SHA}
23+
RUN npm run check
2324
RUN npm run build
2425

2526
FROM scratch

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!DOCTYPE html>
1+
<!doctype html>
22
<html lang="en">
33
<head>
44
<meta charset="utf-8" />

package-lock.json

Lines changed: 1302 additions & 1976 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,35 @@
55
"private": true,
66
"scripts": {
77
"dev": "vite",
8+
"check": "tsc",
89
"build": "vite build",
910
"serve": "vite preview",
1011
"format": "prettier --write ."
1112
},
1213
"dependencies": {
13-
"@chakra-ui/react": "^1.7.3",
14-
"@emotion/react": "^11.7.1",
15-
"@emotion/styled": "^11.6.0",
16-
"@monaco-editor/react": "^4.3.1",
17-
"framer-motion": "^4.1.17",
14+
"@chakra-ui/react": "^2.10.4",
15+
"@emotion/react": "^11.14.0",
16+
"@emotion/styled": "^11.14.0",
17+
"@monaco-editor/react": "^4.6.0",
18+
"framer-motion": "^11.15.0",
1819
"lodash.debounce": "^4.0.8",
19-
"react": "^17.0.2",
20-
"react-dom": "^17.0.2",
21-
"react-icons": "^4.3.1",
20+
"react": "^18.3.1",
21+
"react-dom": "^18.3.1",
22+
"react-icons": "^5.4.0",
2223
"rustpad-wasm": "file:./rustpad-wasm/pkg",
23-
"use-local-storage-state": "^13.0.0"
24+
"use-local-storage-state": "^19.5.0"
2425
},
2526
"devDependencies": {
26-
"@types/lodash.debounce": "^4.0.6",
27-
"@types/react": "^17.0.38",
28-
"@types/react-dom": "^17.0.11",
29-
"@vitejs/plugin-react": "^1.1.3",
30-
"monaco-editor": "^0.31.1",
31-
"prettier": "2.5.1",
32-
"typescript": "~5.5.3",
33-
"vite": "^5.3.4",
34-
"vite-plugin-top-level-await": "^1.4.1",
35-
"vite-plugin-wasm": "^3.3.0"
36-
},
37-
"overrides": {
38-
"@swc/core": "~1.6.13"
27+
"@trivago/prettier-plugin-sort-imports": "^5.2.0",
28+
"@types/lodash.debounce": "^4.0.9",
29+
"@types/react": "^18.3.18",
30+
"@types/react-dom": "^18.3.5",
31+
"@vitejs/plugin-react": "^4.3.4",
32+
"monaco-editor": "^0.52.2",
33+
"prettier": "3.4.2",
34+
"typescript": "~5.7.2",
35+
"vite": "^6.0.6",
36+
"vite-plugin-top-level-await": "^1.4.4",
37+
"vite-plugin-wasm": "^3.4.1"
3938
}
4039
}

rustpad-server/src/rustpad.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,9 +316,9 @@ impl Rustpad {
316316
for history_op in &state.operations[revision..] {
317317
operation = operation.transform(&history_op.operation)?.0;
318318
}
319-
if operation.target_len() > 100000 {
319+
if operation.target_len() > 256 * 1024 {
320320
bail!(
321-
"target length {} is greater than 100 KB maximum",
321+
"target length {} is greater than 256 KiB maximum",
322322
operation.target_len()
323323
);
324324
}

src/App.tsx

Lines changed: 56 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,22 @@
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";
272
import Editor from "@monaco-editor/react";
283
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+
298
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";
3112
import animals from "./animals.json";
13+
import languages from "./languages.json";
3214
import Rustpad, { UserInfo } from "./rustpad";
3315
import useHash from "./useHash";
34-
import ConnectionStatus from "./ConnectionStatus";
35-
import Footer from "./Footer";
36-
import User from "./User";
3716

3817
function getWsUri(id: string) {
3918
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:";
4120
return url.href;
4221
}
4322

@@ -56,13 +35,21 @@ function App() {
5635
"connected" | "disconnected" | "desynchronized"
5736
>("disconnected");
5837
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+
});
6144
const [editor, setEditor] = useState<editor.IStandaloneCodeEditor>();
62-
const [darkMode, setDarkMode] = useStorage("darkMode", () => false);
45+
const [darkMode, setDarkMode] = useLocalStorageState("darkMode", {
46+
defaultValue: false,
47+
});
6348
const rustpad = useRef<Rustpad>();
6449
const id = useHash();
6550

51+
const [readCodeConfirmOpen, setReadCodeConfirmOpen] = useState(false);
52+
6653
useEffect(() => {
6754
if (editor?.getModel()) {
6855
const model = editor.getModel()!;
@@ -102,7 +89,7 @@ function App() {
10289
}
10390
}, [connection, name, hue]);
10491

105-
function handleChangeLanguage(language: string) {
92+
function handleLanguageChange(language: string) {
10693
setLanguage(language);
10794
if (rustpad.current?.setLanguage(language)) {
10895
toast({
@@ -123,38 +110,30 @@ function App() {
123110
}
124111
}
125112

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) {
138114
if (editor?.getModel()) {
139115
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+
140124
model.pushEditOperations(
141125
editor.getSelections(),
142-
[
143-
{
144-
range: model.getFullModelRange(),
145-
text: rustpadRaw,
146-
},
147-
],
148-
() => null
126+
[{ range, text: rustpadRaw }],
127+
() => null,
149128
);
150129
editor.setPosition({ column: 0, lineNumber: 0 });
151130
if (language !== "rust") {
152-
handleChangeLanguage("rust");
131+
handleLanguageChange("rust");
153132
}
154133
}
155134
}
156135

157-
function handleDarkMode() {
136+
function handleDarkModeChange() {
158137
setDarkMode(!darkMode);
159138
}
160139

@@ -177,116 +156,28 @@ function App() {
177156
Rustpad
178157
</Box>
179158
<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+
/>
194180

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>
290181
<Flex flex={1} minW={0} h="100%" direction="column" overflow="hidden">
291182
<HStack
292183
h={6}

0 commit comments

Comments
 (0)