File size: 4,305 Bytes
61e6dfe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b0d8ea8
 
 
 
 
 
 
 
 
61e6dfe
 
 
 
 
 
 
 
b0d8ea8
61e6dfe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import { useMemo, useRef, useCallback, useEffect } from "react";
import { useQueryClient } from "@tanstack/react-query";
import { Monaco } from "@monaco-editor/react";
import {
  useActiveCode,
  SandpackStack,
  FileTabs,
  useSandpack,
} from "@codesandbox/sandpack-react";
import { useTheme } from "next-themes";

import NightLight from "@/components/editor/night-light.json";
import Night from "@/components/editor/night.json";
import { File } from "@/lib/type";
import dynamic from "next/dynamic";

const Editor = dynamic(
  () => import("@monaco-editor/react").then((mod) => mod.Editor),
  { ssr: false }
);

const LANGUAGE_MAP = {
  js: "javascript",
  ts: "typescript",
  html: "html",
  css: "css",
  json: "json",
  txt: "text",
};

export function AppEditorMonacoEditor({
  setShowSaveChanges,
}: {
  setShowSaveChanges: (show: boolean) => void;
}) {
  const { theme } = useTheme();
  const { code, updateCode } = useActiveCode();
  const { sandpack } = useSandpack();
  const queryClient = useQueryClient();
  const updateTimeoutRef = useRef<NodeJS.Timeout | null>(null);

  const handleEditorDidMount = (monaco: Monaco) => {
    monaco.editor.defineTheme("NightLight", {
      base: "vs",
      inherit: true,
      ...NightLight,
      rules: [],
    });
    monaco.editor.defineTheme("Night", {
      base: "vs-dark",
      ...Night,
      rules: [],
    });
  };

  const language = useMemo(() => {
    const extension = sandpack.activeFile
      .split(".")
      .pop()
      ?.toLowerCase() as string;
    return LANGUAGE_MAP[extension as keyof typeof LANGUAGE_MAP] ?? "text";
  }, [sandpack.activeFile]);

  const updateFile = useCallback(
    (newValue: string, activeFile: string) => {
      if (updateTimeoutRef.current) {
        clearTimeout(updateTimeoutRef.current);
      }

      updateTimeoutRef.current = setTimeout(() => {
        const manuallyUpdatedFiles =
          queryClient.getQueryData<File[]>(["manuallyUpdatedFiles"]) ?? [];
        const fileIndex = manuallyUpdatedFiles.findIndex(
          (file) => file.path === activeFile
        );
        if (fileIndex !== -1) {
          manuallyUpdatedFiles[fileIndex].content = newValue;
        } else {
          manuallyUpdatedFiles.push({
            path: activeFile,
            content: newValue,
          });
        }
        queryClient.setQueryData<File[]>(
          ["manuallyUpdatedFiles"],
          manuallyUpdatedFiles
        );
      }, 100);
    },
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [queryClient]
  );

  const handleEditorChange = (value: string | undefined) => {
    setShowSaveChanges(true);
    const newValue = value || "";
    updateCode(newValue);
    const activeFile = sandpack.activeFile?.replace(/^\//, "");
    updateFile(newValue, activeFile);
  };

  useEffect(() => {
    return () => {
      if (updateTimeoutRef.current) {
        clearTimeout(updateTimeoutRef.current);
      }
    };
  }, []);

  const themeEditor = useMemo(() => {
    const isSystemDark =
      window.matchMedia &&
      window.matchMedia("(prefers-color-scheme: dark)").matches;
    const effectiveTheme =
      theme === "system" ? (isSystemDark ? "dark" : "light") : theme;
    return effectiveTheme === "dark" ? "Night" : "NightLight";
  }, [theme]);

  return (
    <SandpackStack className="h-full!">
      <FileTabs />
      <div style={{ flex: 1 }}>
        <Editor
          width="100%"
          height="100%"
          language={language}
          theme={themeEditor}
          key={sandpack.activeFile}
          options={{
            fontSize: 14,
            fontFamily: "Jetbrains-Mono",
            fontLigatures: true,
            wordWrap: "on",
            minimap: {
              enabled: false,
            },
            bracketPairColorization: {
              enabled: true,
            },
            cursorBlinking: "smooth",
            formatOnPaste: true,
            suggest: {
              showFields: false,
              showFunctions: false,
            },
            stickyTabStops: false,
            stickyScroll: {
              enabled: false,
            },
          }}
          beforeMount={handleEditorDidMount}
          value={code}
          onChange={handleEditorChange}
        />
      </div>
    </SandpackStack>
  );
}