diff --git a/backend/server/src/index.ts b/backend/server/src/index.ts index b7a5b82..a2485ae 100644 --- a/backend/server/src/index.ts +++ b/backend/server/src/index.ts @@ -223,6 +223,7 @@ io.on("connection", async (socket) => { }) socket.on("createTerminal", ({ id }: { id: string }) => { + console.log("creating terminal", id) if (terminals[id]) { console.log("Terminal already exists.") return @@ -239,6 +240,7 @@ io.on("connection", async (socket) => { }) const onData = pty.onData((data) => { + console.log("ondata") socket.emit("terminalResponse", { // data: Buffer.from(data, "utf-8").toString("base64"), data, @@ -315,11 +317,11 @@ io.on("connection", async (socket) => { delete terminals[t[0]] }) - console.log("The owner disconnected.") + // console.log("The owner disconnected.") socket.broadcast.emit("ownerDisconnected") } else { - console.log("A shared user disconnected.") + // console.log("A shared user disconnected.") } const sockets = await io.fetchSockets() diff --git a/frontend/app/(app)/code/[id]/page.tsx b/frontend/app/(app)/code/[id]/page.tsx index 0fc0d87..e65b863 100644 --- a/frontend/app/(app)/code/[id]/page.tsx +++ b/frontend/app/(app)/code/[id]/page.tsx @@ -1,64 +1,83 @@ -import Navbar from "@/components/editor/navbar" -import { Room } from "@/components/editor/live/room" -import { Sandbox, User, UsersToSandboxes } from "@/lib/types" -import { currentUser } from "@clerk/nextjs" -import dynamic from "next/dynamic" -import { redirect } from "next/navigation" +import Navbar from "@/components/editor/navbar"; +import { Room } from "@/components/editor/live/room"; +import { Sandbox, User, UsersToSandboxes } from "@/lib/types"; +import { currentUser } from "@clerk/nextjs"; +import dynamic from "next/dynamic"; +import { notFound, redirect } from "next/navigation"; +import Loading from "@/components/editor/loading"; +import { Suspense } from "react"; const CodeEditor = dynamic(() => import("@/components/editor"), { ssr: false, -}) +}); const getUserData = async (id: string) => { const userRes = await fetch( `https://database.ishaan1013.workers.dev/api/user?id=${id}` - ) - const userData: User = await userRes.json() - return userData -} + ); + const userData: User = await userRes.json(); + return userData; +}; const getSandboxData = async (id: string) => { const sandboxRes = await fetch( `https://database.ishaan1013.workers.dev/api/sandbox?id=${id}` - ) - const sandboxData: Sandbox = await sandboxRes.json() - return sandboxData -} + ); + const sandboxData: Sandbox = await sandboxRes.json(); + return sandboxData; +}; const getSharedUsers = async (usersToSandboxes: UsersToSandboxes[]) => { const shared = await Promise.all( usersToSandboxes.map(async (user) => { const userRes = await fetch( `https://database.ishaan1013.workers.dev/api/user?id=${user.userId}` - ) - const userData: User = await userRes.json() - return { id: userData.id, name: userData.name } + ); + const userData: User = await userRes.json(); + return { id: userData.id, name: userData.name }; }) - ) + ); - return shared -} + return shared; +}; export default async function CodePage({ params }: { params: { id: string } }) { - const user = await currentUser() - const sandboxId = params.id + const user = await currentUser(); + const sandboxId = params.id; if (!user) { - redirect("/") + redirect("/"); } - const userData = await getUserData(user.id) - const sandboxData = await getSandboxData(sandboxId) - const shared = await getSharedUsers(sandboxData.usersToSandboxes) + const userData = await getUserData(user.id); + const sandboxData = await getSandboxData(sandboxId); + const shared = await getSharedUsers(sandboxData.usersToSandboxes); + + const isOwner = sandboxData.userId === user.id; + const isSharedUser = shared.some((uts) => uts.id === user.id); + + if (!isOwner && !isSharedUser) { + return notFound(); + } return (
- - -
- -
-
+ }> + + +
+ +
+
+
- ) + ); } diff --git a/frontend/components/editor/index.tsx b/frontend/components/editor/index.tsx index 20556a3..b8c30a7 100644 --- a/frontend/components/editor/index.tsx +++ b/frontend/components/editor/index.tsx @@ -1,23 +1,23 @@ -"use client" +"use client"; -import { useEffect, useRef, useState } from "react" -import monaco from "monaco-editor" -import Editor, { BeforeMount, OnMount } from "@monaco-editor/react" -import { io } from "socket.io-client" -import { toast } from "sonner" -import { useClerk } from "@clerk/nextjs" +import { useEffect, useRef, useState } from "react"; +import monaco from "monaco-editor"; +import Editor, { BeforeMount, OnMount } from "@monaco-editor/react"; +import { io } from "socket.io-client"; +import { toast } from "sonner"; +import { useClerk } from "@clerk/nextjs"; -import * as Y from "yjs" -import LiveblocksProvider from "@liveblocks/yjs" -import { MonacoBinding } from "y-monaco" -import { Awareness } from "y-protocols/awareness" -import { TypedLiveblocksProvider, useRoom } from "@/liveblocks.config" +import * as Y from "yjs"; +import LiveblocksProvider from "@liveblocks/yjs"; +import { MonacoBinding } from "y-monaco"; +import { Awareness } from "y-protocols/awareness"; +import { TypedLiveblocksProvider, useRoom } from "@/liveblocks.config"; import { ResizableHandle, ResizablePanel, ResizablePanelGroup, -} from "@/components/ui/resizable" +} from "@/components/ui/resizable"; import { ChevronLeft, ChevronRight, @@ -28,56 +28,66 @@ import { Shell, SquareTerminal, TerminalSquare, -} from "lucide-react" -import Tab from "../ui/tab" -import Sidebar from "./sidebar" -import EditorTerminal from "./terminal" -import { Button } from "../ui/button" -import GenerateInput from "./generate" -import { Sandbox, User, TFile, TFileData, TFolder, TTab } from "@/lib/types" -import { processFileType, validateName } from "@/lib/utils" -import { Cursors } from "./live/cursors" +} from "lucide-react"; +import Tab from "../ui/tab"; +import Sidebar from "./sidebar"; +import EditorTerminal from "./terminal"; +import { Button } from "../ui/button"; +import GenerateInput from "./generate"; +import { Sandbox, User, TFile, TFileData, TFolder, TTab } from "@/lib/types"; +import { processFileType, validateName } from "@/lib/utils"; +import { Cursors } from "./live/cursors"; +import { Terminal } from "@xterm/xterm"; export default function CodeEditor({ userData, sandboxData, + isSharedUser, }: { - userData: User - sandboxData: Sandbox + userData: User; + sandboxData: Sandbox; + isSharedUser: boolean; }) { - const [files, setFiles] = useState<(TFolder | TFile)[]>([]) - const [tabs, setTabs] = useState([]) - const [editorLanguage, setEditorLanguage] = useState("plaintext") - const [activeId, setActiveId] = useState("") - const [activeFile, setActiveFile] = useState(null) - const [cursorLine, setCursorLine] = useState(0) + const [files, setFiles] = useState<(TFolder | TFile)[]>([]); + const [tabs, setTabs] = useState([]); + const [editorLanguage, setEditorLanguage] = useState("plaintext"); + const [activeFileId, setActiveFileId] = useState(""); + const [activeFileContent, setActiveFileContent] = useState( + null + ); + const [cursorLine, setCursorLine] = useState(0); const [generate, setGenerate] = useState<{ - show: boolean - id: string - line: number - widget: monaco.editor.IContentWidget | undefined - pref: monaco.editor.ContentWidgetPositionPreference[] - width: number - }>({ show: false, line: 0, id: "", widget: undefined, pref: [], width: 0 }) + show: boolean; + id: string; + line: number; + widget: monaco.editor.IContentWidget | undefined; + pref: monaco.editor.ContentWidgetPositionPreference[]; + width: number; + }>({ show: false, line: 0, id: "", widget: undefined, pref: [], width: 0 }); const [decorations, setDecorations] = useState<{ - options: monaco.editor.IModelDeltaDecoration[] - instance: monaco.editor.IEditorDecorationsCollection | undefined - }>({ options: [], instance: undefined }) - const [terminals, setTerminals] = useState([]) - const [provider, setProvider] = useState() - const [ai, setAi] = useState(false) + options: monaco.editor.IModelDeltaDecoration[]; + instance: monaco.editor.IEditorDecorationsCollection | undefined; + }>({ options: [], instance: undefined }); + const [terminals, setTerminals] = useState< + { + id: string; + terminal: Terminal | null; + }[] + >([]); + const [provider, setProvider] = useState(); + const [ai, setAi] = useState(false); - const isOwner = sandboxData.userId === userData.id - const clerk = useClerk() - const room = useRoom() + const isOwner = sandboxData.userId === userData.id; + const clerk = useClerk(); + const room = useRoom(); // const editorRef = useRef(null) const [editorRef, setEditorRef] = - useState() - const editorContainerRef = useRef(null) - const monacoRef = useRef(null) - const generateRef = useRef(null) - const generateWidgetRef = useRef(null) + useState(); + const editorContainerRef = useRef(null); + const monacoRef = useRef(null); + const generateRef = useRef(null); + const generateWidgetRef = useRef(null); const handleEditorWillMount: BeforeMount = (monaco) => { monaco.editor.addKeybindingRules([ @@ -86,20 +96,20 @@ export default function CodeEditor({ command: "null", // when: "textInputFocus", }, - ]) - } + ]); + }; const handleEditorMount: OnMount = (editor, monaco) => { - setEditorRef(editor) - monacoRef.current = monaco + setEditorRef(editor); + monacoRef.current = monaco; editor.onDidChangeCursorPosition((e) => { - const { column, lineNumber } = e.position - if (lineNumber === cursorLine) return - setCursorLine(lineNumber) + const { column, lineNumber } = e.position; + if (lineNumber === cursorLine) return; + setCursorLine(lineNumber); - const model = editor.getModel() - const endColumn = model?.getLineContent(lineNumber).length || 0 + const model = editor.getModel(); + const endColumn = model?.getLineContent(lineNumber).length || 0; setDecorations((prev) => { return { @@ -117,18 +127,18 @@ export default function CodeEditor({ }, }, ], - } - }) - }) + }; + }); + }); editor.onDidBlurEditorText((e) => { setDecorations((prev) => { return { ...prev, options: [], - } - }) - }) + }; + }); + }); editor.addAction({ id: "generate", @@ -142,11 +152,11 @@ export default function CodeEditor({ ...prev, show: !prev.show, pref: [monaco.editor.ContentWidgetPositionPreference.BELOW], - } - }) + }; + }); }, - }) - } + }); + }; useEffect(() => { if (!ai) { @@ -154,32 +164,32 @@ export default function CodeEditor({ return { ...prev, show: false, - } - }) - return + }; + }); + return; } if (generate.show) { editorRef?.changeViewZones(function (changeAccessor) { - if (!generateRef.current) return + if (!generateRef.current) return; const id = changeAccessor.addZone({ afterLineNumber: cursorLine, heightInLines: 3, domNode: generateRef.current, - }) + }); setGenerate((prev) => { - return { ...prev, id, line: cursorLine } - }) - }) + return { ...prev, id, line: cursorLine }; + }); + }); - if (!generateWidgetRef.current) return - const widgetElement = generateWidgetRef.current + if (!generateWidgetRef.current) return; + const widgetElement = generateWidgetRef.current; const contentWidget = { getDomNode: () => { - return widgetElement + return widgetElement; }, getId: () => { - return "generate.widget" + return "generate.widget"; }, getPosition: () => { return { @@ -188,232 +198,253 @@ export default function CodeEditor({ column: 1, }, preference: generate.pref, - } + }; }, - } + }; setGenerate((prev) => { - return { ...prev, widget: contentWidget } - }) - editorRef?.addContentWidget(contentWidget) + return { ...prev, widget: contentWidget }; + }); + editorRef?.addContentWidget(contentWidget); if (generateRef.current && generateWidgetRef.current) { - editorRef?.applyFontInfo(generateRef.current) - editorRef?.applyFontInfo(generateWidgetRef.current) + editorRef?.applyFontInfo(generateRef.current); + editorRef?.applyFontInfo(generateWidgetRef.current); } } else { editorRef?.changeViewZones(function (changeAccessor) { - changeAccessor.removeZone(generate.id) + changeAccessor.removeZone(generate.id); setGenerate((prev) => { - return { ...prev, id: "" } - }) - }) + return { ...prev, id: "" }; + }); + }); - if (!generate.widget) return - editorRef?.removeContentWidget(generate.widget) + if (!generate.widget) return; + editorRef?.removeContentWidget(generate.widget); setGenerate((prev) => { return { ...prev, widget: undefined, - } - }) + }; + }); } - }, [generate.show]) + }, [generate.show]); useEffect(() => { if (decorations.options.length === 0) { - decorations.instance?.clear() + decorations.instance?.clear(); } - if (!ai) return + if (!ai) return; if (decorations.instance) { - decorations.instance.set(decorations.options) + decorations.instance.set(decorations.options); } else { - const instance = editorRef?.createDecorationsCollection() - instance?.set(decorations.options) + const instance = editorRef?.createDecorationsCollection(); + instance?.set(decorations.options); setDecorations((prev) => { return { ...prev, instance, - } - }) + }; + }); } - }, [decorations.options]) + }, [decorations.options]); const socket = io( `http://localhost:4000?userId=${userData.id}&sandboxId=${sandboxData.id}` - ) + ); useEffect(() => { const down = (e: KeyboardEvent) => { if (e.key === "s" && (e.metaKey || e.ctrlKey)) { - e.preventDefault() + e.preventDefault(); - // const activeTab = tabs.find((t) => t.id === activeId) + // const activeTab = tabs.find((t) => t.id === activeFileId) // console.log("saving:", activeTab?.name, editorRef?.getValue()) setTabs((prev) => prev.map((tab) => - tab.id === activeId ? { ...tab, saved: true } : tab + tab.id === activeFileId ? { ...tab, saved: true } : tab ) - ) + ); - socket.emit("saveFile", activeId, editorRef?.getValue()) + socket.emit("saveFile", activeFileId, editorRef?.getValue()); } - } - document.addEventListener("keydown", down) + }; + document.addEventListener("keydown", down); return () => { - document.removeEventListener("keydown", down) - } - }, [tabs, activeId]) + document.removeEventListener("keydown", down); + }; + }, [tabs, activeFileId]); const resizeObserver = new ResizeObserver((entries) => { for (const entry of entries) { - const { width } = entry.contentRect + const { width } = entry.contentRect; setGenerate((prev) => { - return { ...prev, width } - }) + return { ...prev, width }; + }); } - }) + }); useEffect(() => { - const tab = tabs.find((t) => t.id === activeId) - const model = editorRef?.getModel() + const tab = tabs.find((t) => t.id === activeFileId); + const model = editorRef?.getModel(); - if (!editorRef || !tab || !model) return + if (!editorRef || !tab || !model) return; - const yDoc = new Y.Doc() - const yText = yDoc.getText(tab.id) - const yProvider: any = new LiveblocksProvider(room, yDoc) + const yDoc = new Y.Doc(); + const yText = yDoc.getText(tab.id); + const yProvider: any = new LiveblocksProvider(room, yDoc); const onSync = (isSynced: boolean) => { if (isSynced) { - const text = yText.toString() + const text = yText.toString(); if (text === "") { - if (activeFile) { - yText.insert(0, activeFile) + if (activeFileContent) { + yText.insert(0, activeFileContent); } else { setTimeout(() => { - yText.insert(0, editorRef.getValue()) - }, 0) + yText.insert(0, editorRef.getValue()); + }, 0); } } } else { // Yjs content is not synchronized } - } + }; - yProvider.on("sync", onSync) + yProvider.on("sync", onSync); - setProvider(yProvider) + setProvider(yProvider); const binding = new MonacoBinding( yText, model, new Set([editorRef]), yProvider.awareness as Awareness - ) + ); return () => { - yDoc.destroy() - yProvider.destroy() - binding.destroy() - yProvider.off("sync", onSync) - } - }, [editorRef, room, activeFile]) + yDoc.destroy(); + yProvider.destroy(); + binding.destroy(); + yProvider.off("sync", onSync); + }; + }, [editorRef, room, activeFileContent]); // connection/disconnection effect + resizeobserver useEffect(() => { - socket.connect() + socket.connect(); if (editorContainerRef.current) { - resizeObserver.observe(editorContainerRef.current) + resizeObserver.observe(editorContainerRef.current); } return () => { - socket.disconnect() + socket.disconnect(); - resizeObserver.disconnect() - } - }, []) + resizeObserver.disconnect(); + }; + }, []); // event listener effect useEffect(() => { - const onConnect = () => {} + const onConnect = () => { + console.log("connected"); + setTimeout(() => { + socket.emit("createTerminal", { id: "testId" }); + }, 1000); + }; - const onDisconnect = () => {} + const onDisconnect = () => {}; const onLoadedEvent = (files: (TFolder | TFile)[]) => { - setFiles(files) - } + setFiles(files); + }; const onRateLimit = (message: string) => { - toast.error(message) - } + toast.error(message); + }; - socket.on("connect", onConnect) - socket.on("disconnect", onDisconnect) - socket.on("loaded", onLoadedEvent) - socket.on("rateLimit", onRateLimit) + const onTerminalResponse = (response: { id: string; data: string }) => { + const res = response.data; + console.log("terminal response:", res); + + const term = terminals.find((t) => t.id === response.id); + if (term && term.terminal) term.terminal.write(res); + }; + + socket.on("connect", onConnect); + socket.on("disconnect", onDisconnect); + socket.on("loaded", onLoadedEvent); + socket.on("rateLimit", onRateLimit); + socket.on("terminalResponse", onTerminalResponse); return () => { - socket.off("connect", onConnect) - socket.off("disconnect", onDisconnect) - socket.off("loaded", onLoadedEvent) - socket.off("rateLimit", onRateLimit) - } - }, []) + socket.off("connect", onConnect); + socket.off("disconnect", onDisconnect); + socket.off("loaded", onLoadedEvent); + socket.off("rateLimit", onRateLimit); + socket.off("terminalResponse", onTerminalResponse); + }; + }, []); // Helper functions: + const createTerminal = () => { + const id = "testId"; + + socket.emit("createTerminal", { id }); + }; + const selectFile = (tab: TTab) => { - if (tab.id === activeId) return - const exists = tabs.find((t) => t.id === tab.id) + if (tab.id === activeFileId) return; + const exists = tabs.find((t) => t.id === tab.id); setTabs((prev) => { if (exists) { - setActiveId(exists.id) - return prev + setActiveFileId(exists.id); + return prev; } - return [...prev, tab] - }) + return [...prev, tab]; + }); socket.emit("getFile", tab.id, (response: string) => { - setActiveFile(response) - }) - setEditorLanguage(processFileType(tab.name)) - setActiveId(tab.id) - } + setActiveFileContent(response); + }); + setEditorLanguage(processFileType(tab.name)); + setActiveFileId(tab.id); + }; const closeTab = (tab: TFile) => { - const numTabs = tabs.length - const index = tabs.findIndex((t) => t.id === tab.id) + const numTabs = tabs.length; + const index = tabs.findIndex((t) => t.id === tab.id); - if (index === -1) return + if (index === -1) return; const nextId = - activeId === tab.id + activeFileId === tab.id ? numTabs === 1 ? null : index < numTabs - 1 ? tabs[index + 1].id : tabs[index - 1].id - : activeId + : activeFileId; - setTabs((prev) => prev.filter((t) => t.id !== tab.id)) + setTabs((prev) => prev.filter((t) => t.id !== tab.id)); if (!nextId) { - setActiveId("") + setActiveFileId(""); } else { - const nextTab = tabs.find((t) => t.id === nextId) + const nextTab = tabs.find((t) => t.id === nextId); if (nextTab) { - selectFile(nextTab) + selectFile(nextTab); } } - } + }; const handleRename = ( id: string, @@ -421,32 +452,32 @@ export default function CodeEditor({ oldName: string, type: "file" | "folder" ) => { - const valid = validateName(newName, oldName, type) + const valid = validateName(newName, oldName, type); if (!valid.status) { - if (valid.message) toast.error("Invalid file name.") - return false + if (valid.message) toast.error("Invalid file name."); + return false; } - socket.emit("renameFile", id, newName) + socket.emit("renameFile", id, newName); setTabs((prev) => prev.map((tab) => (tab.id === id ? { ...tab, name: newName } : tab)) - ) + ); - return true - } + return true; + }; const handleDeleteFile = (file: TFile) => { socket.emit("deleteFile", file.id, (response: (TFolder | TFile)[]) => { - setFiles(response) - }) - closeTab(file) - } + setFiles(response); + }); + closeTab(file); + }; const handleDeleteFolder = (folder: TFolder) => { // socket.emit("deleteFolder", folder.id, (response: (TFolder | TFile)[]) => { // setFiles(response) // }) - } + }; return ( <> @@ -458,7 +489,7 @@ export default function CodeEditor({ socket={socket} width={generate.width - 90} data={{ - fileName: tabs.find((t) => t.id === activeId)?.name ?? "", + fileName: tabs.find((t) => t.id === activeFileId)?.name ?? "", code: editorRef?.getValue() ?? "", line: generate.line, }} @@ -467,33 +498,33 @@ export default function CodeEditor({ }} onExpand={() => { editorRef?.changeViewZones(function (changeAccessor) { - changeAccessor.removeZone(generate.id) + changeAccessor.removeZone(generate.id); - if (!generateRef.current) return + if (!generateRef.current) return; const id = changeAccessor.addZone({ afterLineNumber: cursorLine, heightInLines: 12, domNode: generateRef.current, - }) + }); setGenerate((prev) => { - return { ...prev, id } - }) - }) + return { ...prev, id }; + }); + }); }} onAccept={(code: string) => { - const line = generate.line + const line = generate.line; setGenerate((prev) => { return { ...prev, show: !prev.show, - } - }) - const file = editorRef?.getValue() + }; + }); + const file = editorRef?.getValue(); - const lines = file?.split("\n") || [] - lines.splice(line - 1, 0, code) - const updatedFile = lines.join("\n") - editorRef?.setValue(updatedFile) + const lines = file?.split("\n") || []; + lines.splice(line - 1, 0, code); + const updatedFile = lines.join("\n"); + editorRef?.setValue(updatedFile); }} /> ) : null} @@ -511,9 +542,9 @@ export default function CodeEditor({ setFiles((prev) => [ ...prev, { id: `projects/${sandboxData.id}/${name}`, name, type: "file" }, - ]) + ]); } else { - console.log("adding folder") + console.log("adding folder"); // setFiles(prev => [...prev, { id, name, type: "folder", children: [] }]) } }} @@ -532,9 +563,9 @@ export default function CodeEditor({ { - selectFile(tab) + selectFile(tab); }} onClose={() => closeTab(tab)} > @@ -546,7 +577,7 @@ export default function CodeEditor({ ref={editorContainerRef} className="grow w-full overflow-hidden rounded-md relative" > - {!activeId ? ( + {!activeFileId ? ( <>
@@ -562,18 +593,22 @@ export default function CodeEditor({ beforeMount={handleEditorWillMount} onMount={handleEditorMount} onChange={(value) => { - if (value === activeFile) { + if (value === activeFileContent) { setTabs((prev) => prev.map((tab) => - tab.id === activeId ? { ...tab, saved: true } : tab + tab.id === activeFileId + ? { ...tab, saved: true } + : tab ) - ) + ); } else { setTabs((prev) => prev.map((tab) => - tab.id === activeId ? { ...tab, saved: false } : tab + tab.id === activeFileId + ? { ...tab, saved: false } + : tab ) - ) + ); } }} options={{ @@ -589,7 +624,7 @@ export default function CodeEditor({ fontFamily: "var(--font-geist-mono)", }} theme="vs-dark" - value={activeFile ?? ""} + value={activeFileContent ?? ""} /> ) : ( @@ -645,7 +680,9 @@ export default function CodeEditor({
- {socket ? : null} + {/* {socket ? : null} */}
) : ( @@ -670,5 +709,5 @@ export default function CodeEditor({ - ) + ); } diff --git a/frontend/components/editor/loading.tsx b/frontend/components/editor/loading.tsx index 6780701..482383a 100644 --- a/frontend/components/editor/loading.tsx +++ b/frontend/components/editor/loading.tsx @@ -1,7 +1,7 @@ -import Image from "next/image" -import Logo from "@/assets/logo.svg" -import { Skeleton } from "../ui/skeleton" -import { Loader, Loader2 } from "lucide-react" +import Image from "next/image"; +import Logo from "@/assets/logo.svg"; +import { Skeleton } from "../ui/skeleton"; +import { Loader, Loader2 } from "lucide-react"; export default function Loading() { return ( @@ -31,11 +31,15 @@ export default function Loading() { -
- - Loading... -
{" "} +
+
+ + Loading... +
+ + +
- ) + ); } diff --git a/frontend/components/editor/navbar/edit.tsx b/frontend/components/editor/navbar/edit.tsx index f95dbe1..c5316fb 100644 --- a/frontend/components/editor/navbar/edit.tsx +++ b/frontend/components/editor/navbar/edit.tsx @@ -35,6 +35,7 @@ import { Sandbox } from "@/lib/types" import { Button } from "@/components/ui/button" import { deleteSandbox, updateSandbox } from "@/lib/actions" import { useRouter } from "next/navigation" +import { toast } from "sonner" const formSchema = z.object({ name: z.string().min(1).max(16), @@ -69,6 +70,8 @@ export default function EditSandboxModal({ setLoading(true) await updateSandbox({ id: data.id, ...values }) + toast.success("Sandbox updated successfully") + setLoading(false) } diff --git a/frontend/components/editor/navbar/share.tsx b/frontend/components/editor/navbar/share.tsx index d2ad727..9e31525 100644 --- a/frontend/components/editor/navbar/share.tsx +++ b/frontend/components/editor/navbar/share.tsx @@ -18,7 +18,7 @@ import { FormMessage, } from "@/components/ui/form" import { Input } from "@/components/ui/input" -import { Loader2, UserPlus, X } from "lucide-react" +import { Link, Loader2, UserPlus, X } from "lucide-react" import { useState } from "react" import { Sandbox } from "@/lib/types" import { Button } from "@/components/ui/button" @@ -75,41 +75,49 @@ export default function ShareSandboxModal({ {data.visibility === "private" ? ( This sandbox is private. Making it public will allow shared - users to view and collaborate. + users to view and collaborate. You can still share & manage access below. ) : null} -
- - ( - - - - - - - )} - /> - - - +
+
+ + ( + + + + + + + )} + /> + + + + +
{shared.length > 0 ? ( <> diff --git a/frontend/components/editor/terminal/index.tsx b/frontend/components/editor/terminal/index.tsx index d2aa421..6bf157d 100644 --- a/frontend/components/editor/terminal/index.tsx +++ b/frontend/components/editor/terminal/index.tsx @@ -1,19 +1,26 @@ -"use client" +"use client"; -import { Terminal } from "@xterm/xterm" -import { FitAddon } from "@xterm/addon-fit" -import "./xterm.css" +import { Terminal } from "@xterm/xterm"; +import { FitAddon } from "@xterm/addon-fit"; +import "./xterm.css"; -import { useEffect, useRef, useState } from "react" -import { Socket } from "socket.io-client" -import { Loader2 } from "lucide-react" +import { useEffect, useRef, useState } from "react"; +import { Socket } from "socket.io-client"; +import { Loader2 } from "lucide-react"; -export default function EditorTerminal({ socket }: { socket: Socket }) { - const terminalRef = useRef(null) - const [term, setTerm] = useState(null) +export default function EditorTerminal({ + socket, + term, + setTerm, +}: { + socket: Socket; + term: Terminal | null; + setTerm: (term: Terminal) => void; +}) { + const terminalRef = useRef(null); useEffect(() => { - if (!terminalRef.current) return + if (!terminalRef.current) return; const terminal = new Terminal({ cursorBlink: true, @@ -22,55 +29,43 @@ export default function EditorTerminal({ socket }: { socket: Socket }) { }, fontFamily: "var(--font-geist-mono)", fontSize: 14, - }) + }); - setTerm(terminal) + setTerm(terminal); return () => { - if (terminal) terminal.dispose() - } - }, []) + if (terminal) terminal.dispose(); + }; + }, []); useEffect(() => { - if (!term) return + if (!term) return; - const onConnect = () => { - console.log("Connected to server", socket.connected) - setTimeout(() => { - socket.emit("createTerminal", { id: "testId" }) - }, 2000) - } - - const onTerminalResponse = (response: { data: string }) => { - // const res = Buffer.from(response.data, "base64").toString("utf-8") - const res = response.data - term.write(res) - } - - socket.on("connect", onConnect) + // const onTerminalResponse = (response: { data: string }) => { + // const res = response.data; + // term.write(res); + // }; if (terminalRef.current) { - socket.on("terminalResponse", onTerminalResponse) + // socket.on("terminalResponse", onTerminalResponse); - const fitAddon = new FitAddon() - term.loadAddon(fitAddon) - term.open(terminalRef.current) - fitAddon.fit() - setTerm(term) + const fitAddon = new FitAddon(); + term.loadAddon(fitAddon); + term.open(terminalRef.current); + fitAddon.fit(); + setTerm(term); } const disposable = term.onData((data) => { - console.log("sending data", data) - socket.emit("terminalData", "testId", data) - }) + console.log("sending data", data); + socket.emit("terminalData", "testId", data); + }); - socket.emit("terminalData", "\n") + // socket.emit("terminalData", "\n"); return () => { - socket.off("connect", onConnect) - socket.off("terminalResponse", onTerminalResponse) - disposable.dispose() - } - }, [term, terminalRef.current]) + disposable.dispose(); + }; + }, [term, terminalRef.current]); return (
@@ -81,5 +76,5 @@ export default function EditorTerminal({ socket }: { socket: Socket }) {
) : null} - ) + ); }