diff --git a/frontend/components/editor/index.tsx b/frontend/components/editor/index.tsx index 415b552..eabbe4d 100644 --- a/frontend/components/editor/index.tsx +++ b/frontend/components/editor/index.tsx @@ -36,7 +36,7 @@ import { useSocket } from "@/context/SocketContext" import { Button } from "../ui/button" import React from "react" import { parseTSConfigToMonacoOptions } from "@/lib/tsconfig" -import { deepMerge } from "@/lib/utils" +import { cn, deepMerge } from "@/lib/utils" export default function CodeEditor({ userData, @@ -62,9 +62,9 @@ export default function CodeEditor({ // This heartbeat is critical to preventing the E2B sandbox from timing out useEffect(() => { // 10000 ms = 10 seconds - const interval = setInterval(() => socket?.emit("heartbeat"), 10000); - return () => clearInterval(interval); - }, [socket]); + const interval = setInterval(() => socket?.emit("heartbeat"), 10000) + return () => clearInterval(interval) + }, [socket]) //Preview Button state const [isPreviewCollapsed, setIsPreviewCollapsed] = useState(true) @@ -80,7 +80,7 @@ export default function CodeEditor({ const [activeFileContent, setActiveFileContent] = useState("") const [deletingFolderId, setDeletingFolderId] = useState("") // Added this state to track the most recent content for each file - const [fileContents, setFileContents] = useState>({}); + const [fileContents, setFileContents] = useState>({}) // Editor state const [editorLanguage, setEditorLanguage] = useState("plaintext") @@ -416,7 +416,7 @@ export default function CodeEditor({ }) } }, [generate.show]) - + // Suggestion widget effect useEffect(() => { if (!suggestionRef.current || !editorRef) return @@ -462,16 +462,17 @@ export default function CodeEditor({ const model = editorRef?.getModel() // added this because it was giving client side exception - Illegal value for lineNumber when opening an empty file if (model) { - const totalLines = model.getLineCount(); + const totalLines = model.getLineCount() // Check if the cursorLine is a valid number, If cursorLine is out of bounds, we fall back to 1 (the first line) as a default safe value. - const lineNumber = cursorLine > 0 && cursorLine <= totalLines ? cursorLine : 1; // fallback to a valid line number + const lineNumber = + cursorLine > 0 && cursorLine <= totalLines ? cursorLine : 1 // fallback to a valid line number // If for some reason the content doesn't exist, we use an empty string as a fallback. - const line = model.getLineContent(lineNumber) ?? ""; + const line = model.getLineContent(lineNumber) ?? "" // Check if the line is not empty or only whitespace (i.e., `.trim()` removes spaces). // If the line has content, we clear any decorations using the instance of the `decorations` object. // Decorations refer to editor highlights, underlines, or markers, so this clears those if conditions are met. if (line.trim() !== "") { - decorations.instance?.clear(); + decorations.instance?.clear() return } } @@ -497,28 +498,28 @@ export default function CodeEditor({ debounce((activeFileId: string | undefined) => { if (activeFileId) { // Get the current content of the file - const content = fileContents[activeFileId]; + const content = fileContents[activeFileId] // Mark the file as saved in the tabs setTabs((prev) => prev.map((tab) => tab.id === activeFileId ? { ...tab, saved: true } : tab ) - ); - console.log(`Saving file...${activeFileId}`); - console.log(`Saving file...${content}`); - socket?.emit("saveFile", activeFileId, content); + ) + console.log(`Saving file...${activeFileId}`) + console.log(`Saving file...${content}`) + socket?.emit("saveFile", activeFileId, content) } }, Number(process.env.FILE_SAVE_DEBOUNCE_DELAY) || 1000), [socket, fileContents] - ); + ) // Keydown event listener to trigger file save on Ctrl+S or Cmd+S useEffect(() => { const down = (e: KeyboardEvent) => { if (e.key === "s" && (e.metaKey || e.ctrlKey)) { e.preventDefault() - debouncedSaveData(activeFileId); + debouncedSaveData(activeFileId) } } document.addEventListener("keydown", down) @@ -615,7 +616,7 @@ export default function CodeEditor({ // Socket event listener effect useEffect(() => { - const onConnect = () => { } + const onConnect = () => {} const onDisconnect = () => { setTerminals([]) @@ -685,46 +686,49 @@ export default function CodeEditor({ } // 300ms debounce delay, adjust as needed const selectFile = (tab: TTab) => { - if (tab.id === activeFileId) return; - - setGenerate((prev) => ({ ...prev, show: false })); - + if (tab.id === activeFileId) return + + setGenerate((prev) => ({ ...prev, show: false })) + // Check if the tab already exists in the list of open tabs - const exists = tabs.find((t) => t.id === tab.id); + const exists = tabs.find((t) => t.id === tab.id) setTabs((prev) => { if (exists) { // If the tab exists, make it the active tab - setActiveFileId(exists.id); - return prev; + setActiveFileId(exists.id) + return prev } // If the tab doesn't exist, add it to the list of tabs and make it active - return [...prev, tab]; - }); - + return [...prev, tab] + }) + // If the file's content is already cached, set it as the active content if (fileContents[tab.id]) { - setActiveFileContent(fileContents[tab.id]); + setActiveFileContent(fileContents[tab.id]) } else { // Otherwise, fetch the content of the file and cache it debouncedGetFile(tab.id, (response: string) => { - setFileContents(prev => ({ ...prev, [tab.id]: response })); - setActiveFileContent(response); - }); + setFileContents((prev) => ({ ...prev, [tab.id]: response })) + setActiveFileContent(response) + }) } - + // Set the editor language based on the file type - setEditorLanguage(processFileType(tab.name)); + setEditorLanguage(processFileType(tab.name)) // Set the active file ID to the new tab - setActiveFileId(tab.id); - }; + setActiveFileId(tab.id) + } // Added this effect to update fileContents when the editor content changes useEffect(() => { if (activeFileId) { // Cache the current active file content using the file ID as the key - setFileContents(prev => ({ ...prev, [activeFileId]: activeFileContent })); + setFileContents((prev) => ({ + ...prev, + [activeFileId]: activeFileContent, + })) } - }, [activeFileContent, activeFileId]); + }, [activeFileContent, activeFileId]) // Close tab and remove from tabs const closeTab = (id: string) => { @@ -740,8 +744,8 @@ export default function CodeEditor({ ? numTabs === 1 ? null : index < numTabs - 1 - ? tabs[index + 1].id - : tabs[index - 1].id + ? tabs[index + 1].id + : tabs[index - 1].id : activeFileId setTabs((prev) => prev.filter((t) => t.id !== id)) @@ -834,7 +838,7 @@ export default function CodeEditor({ { }} + setOpen={() => {}} /> @@ -862,7 +866,10 @@ export default function CodeEditor({ )} -
+
{generate.show ? ( ) : // Note clerk.loaded is required here due to a bug: https://github.com/clerk/javascript/issues/1643 - clerk.loaded ? ( - <> - {provider && userInfo ? ( - - ) : null} - { - // If the new content is different from the cached content, update it - if (value !== fileContents[activeFileId]) { - setActiveFileContent(value ?? ""); // Update the active file content - // Mark the file as unsaved by setting 'saved' to false - setTabs((prev) => - prev.map((tab) => - tab.id === activeFileId - ? { ...tab, saved: false } - : tab - ) + clerk.loaded ? ( + <> + {provider && userInfo ? ( + + ) : null} + { + // If the new content is different from the cached content, update it + if (value !== fileContents[activeFileId]) { + setActiveFileContent(value ?? "") // Update the active file content + // Mark the file as unsaved by setting 'saved' to false + setTabs((prev) => + prev.map((tab) => + tab.id === activeFileId + ? { ...tab, saved: false } + : tab ) - } else { - // If the content matches the cached content, mark the file as saved - setTabs((prev) => - prev.map((tab) => - tab.id === activeFileId - ? { ...tab, saved: true } - : tab - ) + ) + } else { + // If the content matches the cached content, mark the file as saved + setTabs((prev) => + prev.map((tab) => + tab.id === activeFileId + ? { ...tab, saved: true } + : tab ) - } - }} - options={{ - tabSize: 2, - minimap: { - enabled: false, - }, - padding: { - bottom: 4, - top: 4, - }, - scrollBeyondLastLine: false, - fixedOverflowWidgets: true, - fontFamily: "var(--font-geist-mono)", - }} - theme="vs-dark" - value={activeFileContent} - /> - - ) : ( -
- - Waiting for Clerk to load... -
- )} + ) + } + }} + options={{ + tabSize: 2, + minimap: { + enabled: false, + }, + padding: { + bottom: 4, + top: 4, + }, + scrollBeyondLastLine: false, + fixedOverflowWidgets: true, + fontFamily: "var(--font-geist-mono)", + }} + theme="vs-dark" + value={activeFileContent} + /> + + ) : ( +
+ + Waiting for Clerk to load... +
+ )}
diff --git a/frontend/components/editor/loading/index.tsx b/frontend/components/editor/loading/index.tsx index eebabc2..34f8378 100644 --- a/frontend/components/editor/loading/index.tsx +++ b/frontend/components/editor/loading/index.tsx @@ -84,8 +84,10 @@ export default function Loading({
-
- +
+ {new Array(6).fill(0).map((_, i) => ( + + ))}
diff --git a/frontend/components/editor/sidebar/index.tsx b/frontend/components/editor/sidebar/index.tsx index 45fa643..a5dca5f 100644 --- a/frontend/components/editor/sidebar/index.tsx +++ b/frontend/components/editor/sidebar/index.tsx @@ -1,4 +1,4 @@ -"use client"; +"use client" import { FilePlus, @@ -7,20 +7,21 @@ import { MonitorPlay, Search, Sparkles, -} from "lucide-react"; -import SidebarFile from "./file"; -import SidebarFolder from "./folder"; -import { Sandbox, TFile, TFolder, TTab } from "@/lib/types"; -import { useEffect, useRef, useState } from "react"; -import New from "./new"; -import { Socket } from "socket.io-client"; -import { Switch } from "@/components/ui/switch"; +} from "lucide-react" +import SidebarFile from "./file" +import SidebarFolder from "./folder" +import { Sandbox, TFile, TFolder, TTab } from "@/lib/types" +import { useEffect, useRef, useState } from "react" +import New from "./new" +import { Socket } from "socket.io-client" +import { Switch } from "@/components/ui/switch" import { dropTargetForElements, monitorForElements, -} from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; -import Button from "@/components/ui/customButton"; +} from "@atlaskit/pragmatic-drag-and-drop/element/adapter" +import Button from "@/components/ui/customButton" +import { Skeleton } from "@/components/ui/skeleton" export default function Sidebar({ sandboxData, @@ -34,75 +35,73 @@ export default function Sidebar({ addNew, deletingFolderId, }: { - sandboxData: Sandbox; - files: (TFile | TFolder)[]; - selectFile: (tab: TTab) => void; + sandboxData: Sandbox + files: (TFile | TFolder)[] + selectFile: (tab: TTab) => void handleRename: ( id: string, newName: string, oldName: string, type: "file" | "folder" - ) => boolean; - handleDeleteFile: (file: TFile) => void; - handleDeleteFolder: (folder: TFolder) => void; - socket: Socket; - setFiles: (files: (TFile | TFolder)[]) => void; - addNew: (name: string, type: "file" | "folder") => void; - deletingFolderId: string; + ) => boolean + handleDeleteFile: (file: TFile) => void + handleDeleteFolder: (folder: TFolder) => void + socket: Socket + setFiles: (files: (TFile | TFolder)[]) => void + addNew: (name: string, type: "file" | "folder") => void + deletingFolderId: string }) { - const ref = useRef(null); // drop target - - const [creatingNew, setCreatingNew] = useState<"file" | "folder" | null>( - null - ); - const [movingId, setMovingId] = useState(""); + const ref = useRef(null) // drop target + const [creatingNew, setCreatingNew] = useState<"file" | "folder" | null>(null) + const [movingId, setMovingId] = useState("") + console.log(files) useEffect(() => { - const el = ref.current; + const el = ref.current if (el) { return dropTargetForElements({ element: el, getData: () => ({ id: `projects/${sandboxData.id}` }), canDrop: ({ source }) => { - const file = files.find((child) => child.id === source.data.id); - return !file; + const file = files.find((child) => child.id === source.data.id) + return !file }, - }); + }) } - }, [files]); + }, [files]) useEffect(() => { return monitorForElements({ onDrop({ source, location }) { - const destination = location.current.dropTargets[0]; + const destination = location.current.dropTargets[0] if (!destination) { - return; + return } - const fileId = source.data.id as string; - const folderId = destination.data.id as string; + const fileId = source.data.id as string + const folderId = destination.data.id as string - const fileFolder = fileId.split("/").slice(0, -1).join("/"); + const fileFolder = fileId.split("/").slice(0, -1).join("/") if (fileFolder === folderId) { - return; + return } - console.log("move file", fileId, "to folder", folderId); + console.log("move file", fileId, "to folder", folderId) - setMovingId(fileId); + setMovingId(fileId) socket.emit( "moveFile", fileId, folderId, (response: (TFolder | TFile)[]) => { - setFiles(response); - setMovingId(""); + setFiles(response) + setMovingId("") } - ); + ) }, - }); - }, []); + }) + }, []) return (
@@ -138,8 +137,10 @@ export default function Sidebar({ } rounded-sm w-full mt-1 flex flex-col`} > */} {files.length === 0 ? ( -
- +
+ {new Array(6).fill(0).map((_, i) => ( + + ))}
) : ( <> @@ -172,7 +173,7 @@ export default function Sidebar({ socket={socket} type={creatingNew} stopEditing={() => { - setCreatingNew(null); + setCreatingNew(null) }} addNew={addNew} /> @@ -187,5 +188,5 @@ export default function Sidebar({ */}
- ); + ) }