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;
+};