File size: 3,489 Bytes
544e2ab
 
ca97c6b
 
 
90b31da
ca97c6b
 
544e2ab
 
 
 
 
 
 
 
90b31da
544e2ab
 
90b31da
544e2ab
f350e4d
 
 
 
d1700e9
f350e4d
 
 
d1700e9
 
f350e4d
 
 
 
 
 
ca97c6b
 
 
d1700e9
 
 
f350e4d
 
 
 
 
 
 
 
ca97c6b
f350e4d
ca97c6b
 
f350e4d
ca97c6b
0ea55ea
f350e4d
ca97c6b
f350e4d
 
 
ca97c6b
 
 
 
f350e4d
 
 
ca97c6b
 
544e2ab
 
 
d1700e9
544e2ab
d1700e9
544e2ab
 
1d6d85b
544e2ab
1d6d85b
 
544e2ab
1d6d85b
d1700e9
544e2ab
d1700e9
544e2ab
 
ca97c6b
 
 
 
 
 
f350e4d
ca97c6b
 
 
 
 
 
 
544e2ab
 
 
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
108
109
// 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 } 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";
import { usePath } from "./common.ts";

export default function Code() {
  const path = usePath().replace(/^[/]code[/]/, "");
  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,
    );
    editorRef.current.getModel()!.setEOL(0); // https://github.com/yjs/y-monaco/issues/6
    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>
  );
}