From bbc43155764ef95559b5ab9ca2c55fb814da16b3 Mon Sep 17 00:00:00 2001 From: Khairul Hidayat Date: Tue, 12 Mar 2024 01:56:50 +0000 Subject: [PATCH] feat: add react types to codeeditor --- components/ui/code-editor.tsx | 58 ++++++++++++++++++- .../project/@slug/components/file-viewer.tsx | 18 +++++- 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/components/ui/code-editor.tsx b/components/ui/code-editor.tsx index 66a81fd..1c395d7 100644 --- a/components/ui/code-editor.tsx +++ b/components/ui/code-editor.tsx @@ -11,6 +11,11 @@ import { useDebounce } from "~/hooks/useDebounce"; import useCommandKey from "~/hooks/useCommandKey"; import { getFileExt, toast } from "~/lib/utils"; +type InitFileData = { + path: string; + value: string; +}; + type Props = { filename?: string; path?: string; @@ -19,17 +24,28 @@ type Props = { onChange: (val: string) => void; formatOnSave?: boolean; isReadOnly?: boolean; + initialFiles?: InitFileData[]; }; const CodeEditor = (props: Props) => { - const { filename, path, value, formatOnSave, isReadOnly, onChange } = props; + const { + filename, + path, + value, + formatOnSave, + isReadOnly, + onChange, + initialFiles, + } = props; const editorRef = useRef(null); + const monacoRef = useRef(null); const [isMounted, setMounted] = useState(false); const [debounceChange, resetDebounceChange] = useDebounce(onChange, 3000); const language = useMemo(() => getLanguage(filename), [filename]); const onMount = useCallback((editor: any, monaco: any) => { editorRef.current = editor; + monacoRef.current = monaco; monaco.languages.typescript.typescriptDefaults.setCompilerOptions({ target: monaco.languages.typescript.ScriptTarget.Latest, @@ -44,15 +60,33 @@ const CodeEditor = (props: Props) => { allowJs: true, typeRoots: ["node_modules/@types"], }); - + monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true); monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({ noSemanticValidation: false, noSyntaxValidation: false, }); setMounted(true); + loadExternalLibs(monaco); }, []); + useEffect(() => { + const monaco = monacoRef.current; + if (!isMounted || !monaco || !initialFiles?.length) { + return; + } + + initialFiles.forEach((i) => { + const uri = monaco.Uri.parse(i.path); + const existingModel = monaco.editor.getModel(uri); + + if (!existingModel) { + const lang = getLanguage(i.path); + monaco.editor.createModel(i.value, lang?.name || "", uri); + } + }); + }, [initialFiles, isMounted]); + const onSave = useCallback(async () => { if (isReadOnly) { return; @@ -239,4 +273,24 @@ function getLanguage(filename?: string | null) { return { name, formatter }; } +const loadExternalLibs = async (monaco: any) => { + const libs = [ + { + name: "react", + url: "https://unpkg.com/@types/react@18.2.65/index.d.ts", + uri: "file:///node_modules/@types/react/index.d.ts", + }, + ]; + + libs.forEach(async (lib) => { + try { + const res = await fetch(lib.url); + const data = await res.text(); + monaco.languages.typescript.typescriptDefaults.addExtraLib(data, lib.uri); + } catch (err) { + console.error(`Cannot load ${lib.name} lib!`, err); + } + }); +}; + export default CodeEditor; diff --git a/pages/project/@slug/components/file-viewer.tsx b/pages/project/@slug/components/file-viewer.tsx index 95bd435..081a2f8 100644 --- a/pages/project/@slug/components/file-viewer.tsx +++ b/pages/project/@slug/components/file-viewer.tsx @@ -4,8 +4,9 @@ import { Data } from "../+data"; import Spinner from "~/components/ui/spinner"; import { previewStore } from "../stores/web-preview"; import { useProjectContext } from "../context/project"; -import { Suspense, lazy } from "react"; +import { Suspense, lazy, useMemo } from "react"; import CodeEditor from "~/components/ui/code-editor"; +import { getFileExt } from "~/lib/utils"; // const CodeEditor = lazy(() => import("~/components/ui/code-editor")); type Props = { @@ -14,7 +15,7 @@ type Props = { const FileViewer = ({ id }: Props) => { const { project } = useProjectContext(); - const { initialFiles } = useData(); + const { initialFiles, files } = useData(); const initialData = initialFiles.find((i) => i.id === id) as any; const { data, isLoading } = trpc.file.getById.useQuery(id, { @@ -32,6 +33,18 @@ const FileViewer = ({ id }: Props) => { }, }); + const projectJsFiles = useMemo(() => { + return files + .filter((i) => { + const ext = getFileExt(i.filename); + return ["js", "jsx", "ts", "tsx"].includes(ext) && !i.isFile; + }) + .map((i) => ({ + path: i.path, + value: i.content || "", + })); + }, [files]); + if (isLoading) { return ; } @@ -49,6 +62,7 @@ const FileViewer = ({ id }: Props) => { value={data.content || ""} isReadOnly={!project.isMutable} formatOnSave + initialFiles={projectJsFiles} onChange={(val) => { if (!project.isMutable) { return;