Jelajahi Sumber

Frontend updates and refactoring

Eric Zhang 4 tahun lalu
induk
melakukan
8257e92589
5 mengubah file dengan 195 tambahan dan 148 penghapusan
  1. 106 137
      src/App.tsx
  2. 34 0
      src/ConnectionStatus.tsx
  3. 26 0
      src/Footer.tsx
  4. 0 11
      src/index.tsx
  5. 29 0
      src/useHash.ts

+ 106 - 137
src/App.tsx

@@ -17,33 +17,28 @@ import {
   Text,
   Text,
   useToast,
   useToast,
 } from "@chakra-ui/react";
 } from "@chakra-ui/react";
-import {
-  VscChevronRight,
-  VscCircleFilled,
-  VscFolderOpened,
-  VscGist,
-  VscRemote,
-} from "react-icons/vsc";
+import { VscChevronRight, VscFolderOpened, VscGist } from "react-icons/vsc";
 import useStorage from "use-local-storage-state";
 import useStorage from "use-local-storage-state";
 import Editor from "@monaco-editor/react";
 import Editor from "@monaco-editor/react";
 import { editor } from "monaco-editor/esm/vs/editor/editor.api";
 import { editor } from "monaco-editor/esm/vs/editor/editor.api";
 import raw from "raw.macro";
 import raw from "raw.macro";
-import Rustpad, { UserInfo } from "./rustpad";
 import languages from "./languages.json";
 import languages from "./languages.json";
 import animals from "./animals.json";
 import animals from "./animals.json";
+import Rustpad, { UserInfo } from "./rustpad";
+import useHash from "./useHash";
+import ConnectionStatus from "./ConnectionStatus";
+import Footer from "./Footer";
 import User from "./User";
 import User from "./User";
 
 
 set_panic_hook();
 set_panic_hook();
 
 
-const version = process.env.REACT_APP_SHA
-  ? process.env.REACT_APP_SHA.slice(0, 7)
-  : "development";
-
-const id = window.location.hash.slice(1);
-const wsUri =
-  (window.location.origin.startsWith("https") ? "wss://" : "ws://") +
-  window.location.host +
-  `/api/socket/${id}`;
+function getWsUri(id: string) {
+  return (
+    (window.location.origin.startsWith("https") ? "wss://" : "ws://") +
+    window.location.host +
+    `/api/socket/${id}`
+  );
+}
 
 
 function generateName() {
 function generateName() {
   return "Anonymous " + animals[Math.floor(Math.random() * animals.length)];
   return "Anonymous " + animals[Math.floor(Math.random() * animals.length)];
@@ -63,14 +58,15 @@ function App() {
   const [hue, setHue] = useStorage("hue", generateHue);
   const [hue, setHue] = useStorage("hue", generateHue);
   const [editor, setEditor] = useState<editor.IStandaloneCodeEditor>();
   const [editor, setEditor] = useState<editor.IStandaloneCodeEditor>();
   const rustpad = useRef<Rustpad>();
   const rustpad = useRef<Rustpad>();
+  const id = useHash();
 
 
   useEffect(() => {
   useEffect(() => {
-    if (editor) {
+    if (editor?.getModel()) {
       const model = editor.getModel()!;
       const model = editor.getModel()!;
       model.setValue("");
       model.setValue("");
       model.setEOL(0); // LF
       model.setEOL(0); // LF
       rustpad.current = new Rustpad({
       rustpad.current = new Rustpad({
-        uri: wsUri,
+        uri: getWsUri(id),
         editor,
         editor,
         onConnected: () => setConnection("connected"),
         onConnected: () => setConnection("connected"),
         onDisconnected: () => setConnection("disconnected"),
         onDisconnected: () => setConnection("disconnected"),
@@ -95,7 +91,7 @@ function App() {
         rustpad.current = undefined;
         rustpad.current = undefined;
       };
       };
     }
     }
-  }, [editor, toast, setUsers]);
+  }, [id, editor, toast, setUsers]);
 
 
   useEffect(() => {
   useEffect(() => {
     if (connection === "connected") {
     if (connection === "connected") {
@@ -136,7 +132,7 @@ function App() {
   }
   }
 
 
   function handleLoadSample() {
   function handleLoadSample() {
-    if (editor) {
+    if (editor?.getModel()) {
       const model = editor.getModel()!;
       const model = editor.getModel()!;
       model.pushEditOperations(
       model.pushEditOperations(
         editor.getSelections(),
         editor.getSelections(),
@@ -168,114 +164,99 @@ function App() {
         Rustpad
         Rustpad
       </Box>
       </Box>
       <Flex flex="1 0" minH={0}>
       <Flex flex="1 0" minH={0}>
-        <Flex direction="column" bgColor="#f3f3f3" w="xs" overflowY="auto">
-          <Container maxW="full" lineHeight={1.4} py={4}>
-            <HStack spacing={1}>
-              <Icon
-                as={VscCircleFilled}
-                color={
-                  {
-                    connected: "green.500",
-                    disconnected: "orange.500",
-                    desynchronized: "red.500",
-                  }[connection]
-                }
-              />
-              <Text fontSize="sm" fontStyle="italic" color="gray.600">
-                {
-                  {
-                    connected: "You are connected!",
-                    disconnected: "Connecting to the server...",
-                    desynchronized: "Disconnected, please refresh.",
-                  }[connection]
-                }
-              </Text>
-            </HStack>
-
-            <Heading mt={4} mb={1.5} size="sm">
-              Language
-            </Heading>
-            <Select
-              size="sm"
-              bgColor="white"
-              value={language}
-              onChange={(event) => handleChangeLanguage(event.target.value)}
-            >
-              {languages.map((lang) => (
-                <option key={lang} value={lang}>
-                  {lang}
-                </option>
-              ))}
-            </Select>
+        <Container
+          w="xs"
+          bgColor="#f3f3f3"
+          overflowY="auto"
+          maxW="full"
+          lineHeight={1.4}
+          py={4}
+        >
+          <ConnectionStatus connection={connection} />
 
 
-            <Heading mt={4} mb={1.5} size="sm">
-              Share Link
-            </Heading>
-            <InputGroup size="sm">
-              <Input
-                readOnly
-                pr="3.5rem"
-                variant="outline"
-                bgColor="white"
-                value={`${window.location.origin}/#${id}`}
-              />
-              <InputRightElement width="3.5rem">
-                <Button h="1.4rem" size="xs" onClick={handleCopy}>
-                  Copy
-                </Button>
-              </InputRightElement>
-            </InputGroup>
+          <Heading mt={4} mb={1.5} size="sm">
+            Language
+          </Heading>
+          <Select
+            size="sm"
+            bgColor="white"
+            value={language}
+            onChange={(event) => handleChangeLanguage(event.target.value)}
+          >
+            {languages.map((lang) => (
+              <option key={lang} value={lang}>
+                {lang}
+              </option>
+            ))}
+          </Select>
 
 
-            <Heading mt={4} mb={1.5} size="sm">
-              Active Users
-            </Heading>
-            <Stack spacing={0} mb={1.5} fontSize="sm">
-              <User
-                info={{ name, hue }}
-                isMe
-                onChangeName={(name) => name.length > 0 && setName(name)}
-                onChangeColor={() => setHue(generateHue())}
-              />
-              {Object.entries(users).map(([id, info]) => (
-                <User key={id} info={info} />
-              ))}
-            </Stack>
+          <Heading mt={4} mb={1.5} size="sm">
+            Share Link
+          </Heading>
+          <InputGroup size="sm">
+            <Input
+              readOnly
+              pr="3.5rem"
+              variant="outline"
+              bgColor="white"
+              value={`${window.location.origin}/#${id}`}
+            />
+            <InputRightElement width="3.5rem">
+              <Button h="1.4rem" size="xs" onClick={handleCopy}>
+                Copy
+              </Button>
+            </InputRightElement>
+          </InputGroup>
 
 
-            <Heading mt={4} mb={1.5} size="sm">
-              About
-            </Heading>
-            <Text fontSize="sm" mb={1.5}>
-              <strong>Rustpad</strong> is an open-source collaborative text
-              editor based on the <em>operational transformation</em> algorithm.
-            </Text>
-            <Text fontSize="sm" mb={1.5}>
-              Share a link to this pad with others, and they can edit from their
-              browser while seeing your changes in real time.
-            </Text>
-            <Text fontSize="sm" mb={1.5}>
-              Built using Rust and TypeScript. See the{" "}
-              <Link
-                color="blue.600"
-                fontWeight="semibold"
-                href="https://github.com/ekzhang/rustpad"
-                isExternal
-              >
-                GitHub repository
-              </Link>{" "}
-              for details.
-            </Text>
+          <Heading mt={4} mb={1.5} size="sm">
+            Active Users
+          </Heading>
+          <Stack spacing={0} mb={1.5} fontSize="sm">
+            <User
+              info={{ name, hue }}
+              isMe
+              onChangeName={(name) => name.length > 0 && setName(name)}
+              onChangeColor={() => setHue(generateHue())}
+            />
+            {Object.entries(users).map(([id, info]) => (
+              <User key={id} info={info} />
+            ))}
+          </Stack>
 
 
-            <Button
-              size="sm"
-              colorScheme="purple"
-              variant="outline"
-              mt={2}
-              onClick={handleLoadSample}
+          <Heading mt={4} mb={1.5} size="sm">
+            About
+          </Heading>
+          <Text fontSize="sm" mb={1.5}>
+            <strong>Rustpad</strong> is an open-source collaborative text editor
+            based on the <em>operational transformation</em> algorithm.
+          </Text>
+          <Text fontSize="sm" mb={1.5}>
+            Share a link to this pad with others, and they can edit from their
+            browser while seeing your changes in real time.
+          </Text>
+          <Text fontSize="sm" mb={1.5}>
+            Built using Rust and TypeScript. See the{" "}
+            <Link
+              color="blue.600"
+              fontWeight="semibold"
+              href="https://github.com/ekzhang/rustpad"
+              isExternal
             >
             >
-              See the code
-            </Button>
-          </Container>
-        </Flex>
+              GitHub repository
+            </Link>{" "}
+            for details.
+          </Text>
+
+          <Button
+            size="sm"
+            colorScheme="purple"
+            variant="outline"
+            mt={2}
+            onClick={handleLoadSample}
+          >
+            See the code
+          </Button>
+        </Container>
         <Flex flex={1} minW={0} h="100%" direction="column" overflow="hidden">
         <Flex flex={1} minW={0} h="100%" direction="column" overflow="hidden">
           <HStack
           <HStack
             h={6}
             h={6}
@@ -305,19 +286,7 @@ function App() {
           </Box>
           </Box>
         </Flex>
         </Flex>
       </Flex>
       </Flex>
-      <Flex h="22px" bgColor="#0071c3" color="white">
-        <Flex
-          h="100%"
-          bgColor="#09835c"
-          pl={2.5}
-          pr={4}
-          fontSize="sm"
-          align="center"
-        >
-          <Icon as={VscRemote} mb={-0.5} mr={1} />
-          <Text fontSize="xs">Rustpad ({version})</Text>
-        </Flex>
-      </Flex>
+      <Footer />
     </Flex>
     </Flex>
   );
   );
 }
 }

+ 34 - 0
src/ConnectionStatus.tsx

@@ -0,0 +1,34 @@
+import { HStack, Icon, Text } from "@chakra-ui/react";
+import { VscCircleFilled } from "react-icons/vsc";
+
+type ConnectionStatusProps = {
+  connection: "connected" | "disconnected" | "desynchronized";
+};
+
+function ConnectionStatus({ connection }: ConnectionStatusProps) {
+  return (
+    <HStack spacing={1}>
+      <Icon
+        as={VscCircleFilled}
+        color={
+          {
+            connected: "green.500",
+            disconnected: "orange.500",
+            desynchronized: "red.500",
+          }[connection]
+        }
+      />
+      <Text fontSize="sm" fontStyle="italic" color="gray.600">
+        {
+          {
+            connected: "You are connected!",
+            disconnected: "Connecting to the server...",
+            desynchronized: "Disconnected, please refresh.",
+          }[connection]
+        }
+      </Text>
+    </HStack>
+  );
+}
+
+export default ConnectionStatus;

+ 26 - 0
src/Footer.tsx

@@ -0,0 +1,26 @@
+import { Flex, Icon, Text } from "@chakra-ui/react";
+import { VscRemote } from "react-icons/vsc";
+
+const version = process.env.REACT_APP_SHA
+  ? process.env.REACT_APP_SHA.slice(0, 7)
+  : "development";
+
+function Footer() {
+  return (
+    <Flex h="22px" bgColor="#0071c3" color="white">
+      <Flex
+        h="100%"
+        bgColor="#09835c"
+        pl={2.5}
+        pr={4}
+        fontSize="sm"
+        align="center"
+      >
+        <Icon as={VscRemote} mb={-0.5} mr={1} />
+        <Text fontSize="xs">Rustpad ({version})</Text>
+      </Flex>
+    </Flex>
+  );
+}
+
+export default Footer;

+ 0 - 11
src/index.tsx

@@ -3,17 +3,6 @@ import ReactDOM from "react-dom";
 import { ChakraProvider } from "@chakra-ui/react";
 import { ChakraProvider } from "@chakra-ui/react";
 import "./index.css";
 import "./index.css";
 
 
-const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
-const idLen = 6;
-
-if (!window.location.hash) {
-  let id = "";
-  for (let i = 0; i < idLen; i++) {
-    id += chars[Math.floor(Math.random() * chars.length)];
-  }
-  window.location.hash = id;
-}
-
 // An asynchronous entry point is needed to load WebAssembly files.
 // An asynchronous entry point is needed to load WebAssembly files.
 import("./App").then(({ default: App }) => {
 import("./App").then(({ default: App }) => {
   ReactDOM.render(
   ReactDOM.render(

+ 29 - 0
src/useHash.ts

@@ -0,0 +1,29 @@
+import { useEffect, useState } from "react";
+
+const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+const idLen = 6;
+
+function getHash() {
+  if (!window.location.hash) {
+    let id = "";
+    for (let i = 0; i < idLen; i++) {
+      id += chars[Math.floor(Math.random() * chars.length)];
+    }
+    window.location.hash = id;
+  }
+  return window.location.hash.slice(1);
+}
+
+function useHash() {
+  const [hash, setHash] = useState(getHash);
+
+  useEffect(() => {
+    const handler = () => setHash(getHash());
+    window.addEventListener("hashchange", handler);
+    return () => window.removeEventListener("hashchange", handler);
+  }, []);
+
+  return hash;
+}
+
+export default useHash;