File size: 3,351 Bytes
9a9d700
 
6a3b521
 
 
dadb124
6a3b521
 
9a9d700
 
 
 
 
 
 
 
 
 
 
 
78397e9
 
 
 
dadb124
78397e9
 
 
dadb124
 
78397e9
 
 
 
 
 
6a3b521
 
 
dadb124
 
 
78397e9
 
 
 
 
 
 
 
6a3b521
78397e9
6a3b521
 
78397e9
6a3b521
78397e9
6a3b521
78397e9
 
 
6a3b521
 
 
 
78397e9
 
 
6a3b521
 
9a9d700
 
 
dadb124
9a9d700
dadb124
9a9d700
 
81529d5
9a9d700
81529d5
 
9a9d700
81529d5
dadb124
9a9d700
dadb124
9a9d700
 
6a3b521
 
 
 
 
 
78397e9
6a3b521
 
 
 
 
 
 
9a9d700
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// Full-page editor for code files.

import Editor, { type Monaco } from "@monaco-editor/react";
import type { editor } from "monaco-editor";
import { useEffect, useRef } from "react";
import { Link, useParams } from "react-router";
import { WebsocketProvider } from "y-websocket";
import * as Y from "yjs";
// @ts-ignore
import Atom from "~icons/tabler/atom.jsx";
// @ts-ignore
import Backspace from "~icons/tabler/backspace.jsx";
// @ts-ignore
import Close from "~icons/tabler/x.jsx";
import favicon from "./assets/favicon.ico";
import theme from "./code-theme.ts";

export default function Code() {
  const { path } = useParams();
  const parentDir = path!.split("/").slice(0, -1).join("/");
  const yDocRef = useRef<any>();
  const wsProviderRef = useRef<any>();
  const monacoBindingRef = useRef<any>();
  const yMonacoRef = useRef<any>();
  const yMonacoLoadingRef = useRef(false);
  const editorRef = useRef<any>();
  useEffect(() => {
    const loadMonaco = async () => {
      if (yMonacoLoadingRef.current) return;
      yMonacoLoadingRef.current = true;
      // y-monaco is gigantic. The other Monaco packages are small.
      yMonacoRef.current = await import("y-monaco");
      initCRDT();
    };
    loadMonaco();
  }, []);
  function beforeMount(monaco: Monaco) {
    monaco.editor.defineTheme("lynxkite", theme);
  }
  function onMount(_editor: editor.IStandaloneCodeEditor, monaco: Monaco) {
    // Do nothing on Ctrl+S. We save after every keypress anyway.
    _editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {});
    editorRef.current = _editor;
    initCRDT();
  }
  function initCRDT() {
    if (!yMonacoRef.current || !editorRef.current) return;
    if (yDocRef.current) return;
    yDocRef.current = new Y.Doc();
    const text = yDocRef.current.getText("text");
    const proto = location.protocol === "https:" ? "wss:" : "ws:";
    wsProviderRef.current = new WebsocketProvider(
      `${proto}//${location.host}/ws/code/crdt`,
      path!,
      yDocRef.current,
    );
    monacoBindingRef.current = new yMonacoRef.current.MonacoBinding(
      text,
      editorRef.current.getModel()!,
      new Set([editorRef.current]),
      wsProviderRef.current.awareness,
    );
  }
  useEffect(() => {
    return () => {
      yDocRef.current?.destroy();
      wsProviderRef.current?.destroy();
      monacoBindingRef.current?.destroy();
    };
  });
  return (
    <div className="workspace">
      <div className="top-bar bg-neutral">
        <Link className="logo" to="">
          <img alt="" src={favicon} />
        </Link>
        <div className="ws-name">{path}</div>
        <div className="tools text-secondary">
          <button className="btn btn-link">
            <Atom />
          </button>
          <button className="btn btn-link">
            <Backspace />
          </button>
          <Link to={`/dir/${parentDir}`} className="btn btn-link">
            <Close />
          </Link>
        </div>
      </div>
      <Editor
        defaultLanguage="python"
        theme="lynxkite"
        path={path}
        beforeMount={beforeMount}
        onMount={onMount}
        loading={null}
        options={{
          cursorStyle: "block",
          cursorBlinking: "solid",
          minimap: { enabled: false },
          renderLineHighlight: "none",
        }}
      />
    </div>
  );
}