"use client" import Editor, { OnMount } from "@monaco-editor/react" import monaco from "monaco-editor" import { useEffect, useRef, useState } from "react" // import theme from "./theme.json" import { ResizableHandle, ResizablePanel, ResizablePanelGroup, } from "@/components/ui/resizable" import { ChevronLeft, ChevronRight, FileJson, Plus, RotateCw, Shell, SquareTerminal, TerminalSquare, } from "lucide-react" import Tab from "../ui/tab" import Sidebar from "./sidebar" import { useClerk } from "@clerk/nextjs" import { TFile, TFileData, TFolder, TTab } from "./sidebar/types" import { io } from "socket.io-client" import { processFileType, validateName } from "@/lib/utils" import { toast } from "sonner" import EditorTerminal from "./terminal" import { Button } from "../ui/button" import { User } from "@/lib/types" export default function CodeEditor({ userData, sandboxId, }: { userData: User sandboxId: string }) { const clerk = useClerk() const editorRef = useRef(null) const handleEditorMount: OnMount = (editor, monaco) => { editorRef.current = editor } const [files, setFiles] = useState<(TFolder | TFile)[]>([]) const [editorLanguage, setEditorLanguage] = useState("plaintext") const [activeFile, setActiveFile] = useState(null) const [tabs, setTabs] = useState([]) const [activeId, setActiveId] = useState(null) const [terminals, setTerminals] = useState([]) const socket = io( `http://localhost:4000?userId=${userData.id}&sandboxId=${sandboxId}` ) useEffect(() => { const down = (e: KeyboardEvent) => { if (e.key === "s" && (e.metaKey || e.ctrlKey)) { e.preventDefault() const activeTab = tabs.find((t) => t.id === activeId) console.log("saving:", activeTab?.name, editorRef.current?.getValue()) setTabs((prev) => prev.map((tab) => tab.id === activeId ? { ...tab, saved: true } : tab ) ) socket.emit("saveFile", activeId, editorRef.current?.getValue()) } } document.addEventListener("keydown", down) return () => { document.removeEventListener("keydown", down) } }, [tabs, activeId]) // WS event handlers: // connection/disconnection effect useEffect(() => { socket.connect() return () => { socket.disconnect() } }, []) // event listener effect useEffect(() => { const onConnect = () => {} const onDisconnect = () => {} const onLoadedEvent = (files: (TFolder | TFile)[]) => { console.log("onLoadedEvent") setFiles(files) } socket.on("connect", onConnect) socket.on("disconnect", onDisconnect) socket.on("loaded", onLoadedEvent) return () => { socket.off("connect", onConnect) socket.off("disconnect", onDisconnect) socket.off("loaded", onLoadedEvent) } }, []) // Helper functions: const selectFile = (tab: TTab) => { setTabs((prev) => { const exists = prev.find((t) => t.id === tab.id) if (exists) { // console.log("exists") setActiveId(exists.id) return prev } return [...prev, tab] }) socket.emit("getFile", tab.id, (response: string) => { setActiveFile(response) }) setEditorLanguage(processFileType(tab.name)) setActiveId(tab.id) } const closeTab = (tab: TFile) => { const numTabs = tabs.length const index = tabs.findIndex((t) => t.id === tab.id) if (index === -1) return const nextId = activeId === tab.id ? numTabs === 1 ? null : index < numTabs - 1 ? tabs[index + 1].id : tabs[index - 1].id : activeId const nextTab = tabs.find((t) => t.id === nextId) if (nextTab) selectFile(nextTab) else setActiveId(null) setTabs((prev) => prev.filter((t) => t.id !== tab.id)) } const handleRename = ( id: string, newName: string, oldName: string, type: "file" | "folder" ) => { if (!validateName(newName, oldName, type)) { toast.error("Invalid file name.") console.log("invalid name") return false } socket.emit("renameFile", id, newName) setTabs((prev) => prev.map((tab) => (tab.id === id ? { ...tab, name: newName } : tab)) ) return true } const handleDeleteFile = (file: TFile) => { socket.emit("deleteFile", file.id, (response: (TFolder | TFile)[]) => { setFiles(response) }) closeTab(file) } const handleDeleteFolder = (folder: TFolder) => { // socket.emit("deleteFolder", folder.id, (response: (TFolder | TFile)[]) => { // setFiles(response) // }) } return ( <> { if (type === "file") { console.log("adding file") setFiles((prev) => [ ...prev, { id: `projects/${sandboxId}/${name}`, name, type: "file" }, ]) } else { console.log("adding folder") // setFiles(prev => [...prev, { id, name, type: "folder", children: [] }]) } }} />
{tabs.map((tab) => ( { selectFile(tab) }} onClose={() => closeTab(tab)} > {tab.name} ))}
{activeId === null ? ( <>
No file selected.
) : clerk.loaded ? ( { setTabs((prev) => prev.map((tab) => tab.id === activeId ? { ...tab, saved: false } : tab ) ) }} options={{ minimap: { enabled: false, }, padding: { bottom: 4, top: 4, }, scrollBeyondLastLine: false, fixedOverflowWidgets: true, }} theme="vs-dark" value={activeFile ?? ""} /> ) : null}
Preview
Shell
{socket ? : null}
) }