From 44f803ffaf21c956224d1f92a6ceee5a8aad027e Mon Sep 17 00:00:00 2001 From: Akhilesh Rangani Date: Mon, 15 Jul 2024 15:33:26 -0400 Subject: [PATCH] feat: add run button --- frontend/app/layout.tsx | 3 + frontend/components/editor/index.tsx | 6 +- frontend/components/editor/navbar/index.tsx | 23 +++-- frontend/components/editor/navbar/run.tsx | 60 +++++++++++ .../components/editor/terminals/index.tsx | 31 ++---- frontend/context/TerminalContext.tsx | 99 +++++++++++++++++++ 6 files changed, 186 insertions(+), 36 deletions(-) create mode 100644 frontend/components/editor/navbar/run.tsx create mode 100644 frontend/context/TerminalContext.tsx diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx index 50ee950..c62b454 100644 --- a/frontend/app/layout.tsx +++ b/frontend/app/layout.tsx @@ -6,6 +6,7 @@ import { ThemeProvider } from "@/components/layout/themeProvider" import { ClerkProvider } from "@clerk/nextjs" import { Toaster } from "@/components/ui/sonner" import { Analytics } from "@vercel/analytics/react" +import { TerminalProvider } from '@/context/TerminalContext'; export const metadata: Metadata = { title: "Sandbox", @@ -27,7 +28,9 @@ export default function RootLayout({ forcedTheme="dark" disableTransitionOnChange > + {children} + diff --git a/frontend/components/editor/index.tsx b/frontend/components/editor/index.tsx index 11cffcd..b7e993c 100644 --- a/frontend/components/editor/index.tsx +++ b/frontend/components/editor/index.tsx @@ -829,11 +829,7 @@ type ProviderData = { className="p-2 flex flex-col" > {isOwner ? ( - + ) : (
diff --git a/frontend/components/editor/navbar/index.tsx b/frontend/components/editor/navbar/index.tsx index 413200b..75a9dbe 100644 --- a/frontend/components/editor/navbar/index.tsx +++ b/frontend/components/editor/navbar/index.tsx @@ -2,7 +2,7 @@ import Image from "next/image"; import Logo from "@/assets/logo.svg"; -import { Pencil, Users } from "lucide-react"; +import { Pencil, Users, Play, StopCircle } from "lucide-react"; import Link from "next/link"; import { Sandbox, User } from "@/lib/types"; import UserButton from "@/components/ui/userButton"; @@ -11,23 +11,30 @@ import { useState } from "react"; import EditSandboxModal from "./edit"; import ShareSandboxModal from "./share"; import { Avatars } from "../live/avatars"; +import RunButtonModal from "./run"; +import { Terminal } from "@xterm/xterm"; +import { Socket } from "socket.io-client"; +import Terminals from "../terminals"; export default function Navbar({ userData, sandboxData, shared, + socket, }: { userData: User; sandboxData: Sandbox; - shared: { - id: string; - name: string; - }[]; + shared: { id: string; name: string }[]; + socket: Socket; }) { const [isEditOpen, setIsEditOpen] = useState(false); const [isShareOpen, setIsShareOpen] = useState(false); + const [isRunning, setIsRunning] = useState(false); + const [terminals, setTerminals] = useState<{ id: string; terminal: Terminal | null }[]>([]); + const [activeTerminalId, setActiveTerminalId] = useState(""); + const [creatingTerminal, setCreatingTerminal] = useState(false); - const isOwner = sandboxData.userId === userData.id; + const isOwner = sandboxData.userId === userData.id;; return ( <> @@ -62,6 +69,10 @@ export default function Navbar({ ) : null}
+
diff --git a/frontend/components/editor/navbar/run.tsx b/frontend/components/editor/navbar/run.tsx new file mode 100644 index 0000000..e22eea2 --- /dev/null +++ b/frontend/components/editor/navbar/run.tsx @@ -0,0 +1,60 @@ +"use client"; + +import { Play, StopCircle } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { useTerminal } from "@/context/TerminalContext"; +import { closeTerminal } from "@/lib/terminal"; + +export default function RunButtonModal({ + isRunning, + setIsRunning, +}: { + isRunning: boolean; + setIsRunning: (running: boolean) => void; +}) { + const { createNewTerminal, terminals, setTerminals, socket, setActiveTerminalId } = useTerminal(); + + const handleRun = () => { + if (isRunning) { + console.log('Stopping sandbox...'); + console.log('Closing Terminal'); + console.log('Closing Preview Window'); + + // Close all terminals if needed + terminals.forEach(term => { + if (term.terminal) { + // Assuming you have a closeTerminal function similar to createTerminal + closeTerminal({ + term, + terminals, + setTerminals, + setActiveTerminalId, + setClosingTerminal: () => { }, + socket: socket!, + activeTerminalId: term.id, + }); + } + }); + } else { + console.log('Running sandbox...'); + console.log('Opening Terminal'); + console.log('Opening Preview Window'); + + if (terminals.length < 4) { + createNewTerminal(); + } else { + console.error('Maximum number of terminals reached.'); + } + } + setIsRunning(!isRunning); + }; + + return ( + <> + + + ); +} diff --git a/frontend/components/editor/terminals/index.tsx b/frontend/components/editor/terminals/index.tsx index a777eeb..2ac6c97 100644 --- a/frontend/components/editor/terminals/index.tsx +++ b/frontend/components/editor/terminals/index.tsx @@ -2,30 +2,16 @@ import { Button } from "@/components/ui/button"; import Tab from "@/components/ui/tab"; -import { closeTerminal, createTerminal } from "@/lib/terminal"; +import { closeTerminal } from "@/lib/terminal"; import { Terminal } from "@xterm/xterm"; import { Loader2, Plus, SquareTerminal, TerminalSquare } from "lucide-react"; -import { Socket } from "socket.io-client"; import { toast } from "sonner"; import EditorTerminal from "./terminal"; import { useState } from "react"; +import { useTerminal } from "@/context/TerminalContext"; -export default function Terminals({ - terminals, - setTerminals, - socket, -}: { - terminals: { id: string; terminal: Terminal | null }[]; - setTerminals: React.Dispatch< - React.SetStateAction< - { - id: string; - terminal: Terminal | null; - }[] - > - >; - socket: Socket; -}) { +export default function Terminals() { + const { terminals, setTerminals, socket, createNewTerminal } = useTerminal(); const [activeTerminalId, setActiveTerminalId] = useState(""); const [creatingTerminal, setCreatingTerminal] = useState(false); const [closingTerminal, setClosingTerminal] = useState(""); @@ -46,7 +32,7 @@ export default function Terminals({ setTerminals, setActiveTerminalId, setClosingTerminal, - socket, + socket: socket!, activeTerminalId, }) } @@ -64,12 +50,7 @@ export default function Terminals({ toast.error("You reached the maximum # of terminals."); return; } - createTerminal({ - setTerminals, - setActiveTerminalId, - setCreatingTerminal, - socket, - }); + createNewTerminal(); }} size="smIcon" variant={"secondary"} diff --git a/frontend/context/TerminalContext.tsx b/frontend/context/TerminalContext.tsx new file mode 100644 index 0000000..72a1625 --- /dev/null +++ b/frontend/context/TerminalContext.tsx @@ -0,0 +1,99 @@ +"use client"; + +import React, { createContext, useContext, useState, useEffect } from 'react'; +import { io, Socket } from 'socket.io-client'; +import { Terminal } from '@xterm/xterm'; +import { createId } from '@paralleldrive/cuid2'; +import { createTerminal as createTerminalHelper, closeTerminal as closeTerminalHelper } from '@/lib/terminal'; // Adjust the import path as necessary + +interface TerminalContextType { + socket: Socket | null; + terminals: { id: string; terminal: Terminal | null }[]; + setTerminals: React.Dispatch>; + activeTerminalId: string; + setActiveTerminalId: React.Dispatch>; + creatingTerminal: boolean; + setCreatingTerminal: React.Dispatch>; + createNewTerminal: () => void; + closeTerminal: (id: string) => void; +} + +const TerminalContext = createContext(undefined); + +export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [socket, setSocket] = useState(null); + const [terminals, setTerminals] = useState<{ id: string; terminal: Terminal | null }[]>([]); + const [activeTerminalId, setActiveTerminalId] = useState(''); + const [creatingTerminal, setCreatingTerminal] = useState(false); + + useEffect(() => { + // Replace with your server URL + const socketIo = io(process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'); + setSocket(socketIo); + + // Log socket events + socketIo.on('connect', () => { + console.log('Socket connected:', socketIo.id); + }); + + socketIo.on('disconnect', () => { + console.log('Socket disconnected'); + }); + + return () => { + socketIo.disconnect(); + }; + }, []); + + const createNewTerminal = () => { + if (socket) { + createTerminalHelper({ + setTerminals, + setActiveTerminalId, + setCreatingTerminal, + socket, + }); + } + }; + + const closeTerminal = (id: string) => { + const terminalToClose = terminals.find(term => term.id === id); + if (terminalToClose && socket) { + closeTerminalHelper({ + term: terminalToClose, + terminals, + setTerminals, + setActiveTerminalId, + setClosingTerminal: () => {}, // Implement if needed + socket, + activeTerminalId, + }); + } + }; + + const value = { + socket, + terminals, + setTerminals, + activeTerminalId, + setActiveTerminalId, + creatingTerminal, + setCreatingTerminal, + createNewTerminal, + closeTerminal, + }; + + return ( + + {children} + + ); +}; + +export const useTerminal = (): TerminalContextType => { + const context = useContext(TerminalContext); + if (!context) { + throw new Error('useTerminal must be used within a TerminalProvider'); + } + return context; +};