Compare commits
6 Commits
fix-editor
...
fix-editor
Author | SHA1 | Date | |
---|---|---|---|
08d562ee54 | |||
db1410f587 | |||
7a80734c25 | |||
0a21cb2637 | |||
2fbabbd403 | |||
9f0b6a8fdc |
@ -192,6 +192,8 @@ io.on("connection", async (socket) => {
|
|||||||
|
|
||||||
// todo: send diffs + debounce for efficiency
|
// todo: send diffs + debounce for efficiency
|
||||||
socket.on("saveFile", async (fileId: string, body: string) => {
|
socket.on("saveFile", async (fileId: string, body: string) => {
|
||||||
|
if (!fileId) return; // handles saving when no file is open
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (Buffer.byteLength(body, "utf-8") > MAX_BODY_SIZE) {
|
if (Buffer.byteLength(body, "utf-8") > MAX_BODY_SIZE) {
|
||||||
socket.emit(
|
socket.emit(
|
||||||
|
@ -63,14 +63,6 @@ const CodeEditor = dynamic(() => import("@/components/editor"), {
|
|||||||
loading: () => <Loading />,
|
loading: () => <Loading />,
|
||||||
})
|
})
|
||||||
|
|
||||||
function getReactDefinitionFile() {
|
|
||||||
const reactDefinitionFile = fs.readFileSync(
|
|
||||||
"node_modules/@types/react/index.d.ts",
|
|
||||||
"utf8"
|
|
||||||
)
|
|
||||||
return reactDefinitionFile
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function CodePage({ params }: { params: { id: string } }) {
|
export default async function CodePage({ params }: { params: { id: string } }) {
|
||||||
const user = await currentUser()
|
const user = await currentUser()
|
||||||
const sandboxId = params.id
|
const sandboxId = params.id
|
||||||
@ -94,8 +86,6 @@ export default async function CodePage({ params }: { params: { id: string } }) {
|
|||||||
return notFound()
|
return notFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
const reactDefinitionFile = getReactDefinitionFile()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overflow-hidden overscroll-none w-screen flex flex-col h-screen bg-background">
|
<div className="overflow-hidden overscroll-none w-screen flex flex-col h-screen bg-background">
|
||||||
<Room id={sandboxId}>
|
<Room id={sandboxId}>
|
||||||
@ -104,7 +94,6 @@ export default async function CodePage({ params }: { params: { id: string } }) {
|
|||||||
<CodeEditor
|
<CodeEditor
|
||||||
userData={userData}
|
userData={userData}
|
||||||
sandboxData={sandboxData}
|
sandboxData={sandboxData}
|
||||||
reactDefinitionFile={reactDefinitionFile}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Room>
|
</Room>
|
||||||
|
@ -6,7 +6,6 @@ import { ThemeProvider } from "@/components/layout/themeProvider"
|
|||||||
import { ClerkProvider } from "@clerk/nextjs"
|
import { ClerkProvider } from "@clerk/nextjs"
|
||||||
import { Toaster } from "@/components/ui/sonner"
|
import { Toaster } from "@/components/ui/sonner"
|
||||||
import { Analytics } from "@vercel/analytics/react"
|
import { Analytics } from "@vercel/analytics/react"
|
||||||
import { TerminalProvider } from '@/context/TerminalContext';
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Sandbox",
|
title: "Sandbox",
|
||||||
@ -28,9 +27,7 @@ export default function RootLayout({
|
|||||||
forcedTheme="dark"
|
forcedTheme="dark"
|
||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
<TerminalProvider>
|
|
||||||
{children}
|
{children}
|
||||||
</TerminalProvider>
|
|
||||||
<Analytics />
|
<Analytics />
|
||||||
<Toaster position="bottom-left" richColors />
|
<Toaster position="bottom-left" richColors />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
@ -35,11 +35,9 @@ import { ImperativePanelHandle } from "react-resizable-panels"
|
|||||||
export default function CodeEditor({
|
export default function CodeEditor({
|
||||||
userData,
|
userData,
|
||||||
sandboxData,
|
sandboxData,
|
||||||
reactDefinitionFile,
|
|
||||||
}: {
|
}: {
|
||||||
userData: User
|
userData: User
|
||||||
sandboxData: Sandbox
|
sandboxData: Sandbox
|
||||||
reactDefinitionFile: string
|
|
||||||
}) {
|
}) {
|
||||||
const socketRef = useRef<Socket | null>(null);
|
const socketRef = useRef<Socket | null>(null);
|
||||||
|
|
||||||
@ -105,6 +103,16 @@ export default function CodeEditor({
|
|||||||
const [provider, setProvider] = useState<TypedLiveblocksProvider>()
|
const [provider, setProvider] = useState<TypedLiveblocksProvider>()
|
||||||
const userInfo = useSelf((me) => me.info)
|
const userInfo = useSelf((me) => me.info)
|
||||||
|
|
||||||
|
// Liveblocks providers map to prevent reinitializing providers
|
||||||
|
type ProviderData = {
|
||||||
|
provider: LiveblocksProvider<never, never, never, never>;
|
||||||
|
yDoc: Y.Doc;
|
||||||
|
yText: Y.Text;
|
||||||
|
binding?: MonacoBinding;
|
||||||
|
onSync: (isSynced: boolean) => void;
|
||||||
|
};
|
||||||
|
const providersMap = useRef(new Map<string, ProviderData>());
|
||||||
|
|
||||||
// Refs for libraries / features
|
// Refs for libraries / features
|
||||||
const editorContainerRef = useRef<HTMLDivElement>(null)
|
const editorContainerRef = useRef<HTMLDivElement>(null)
|
||||||
const monacoRef = useRef<typeof monaco | null>(null)
|
const monacoRef = useRef<typeof monaco | null>(null)
|
||||||
@ -326,30 +334,21 @@ export default function CodeEditor({
|
|||||||
}, [activeFileId, tabs, debouncedSaveData]);
|
}, [activeFileId, tabs, debouncedSaveData]);
|
||||||
|
|
||||||
// Liveblocks live collaboration setup effect
|
// Liveblocks live collaboration setup effect
|
||||||
|
|
||||||
type ProviderData = {
|
|
||||||
provider: LiveblocksProvider<never, never, never, never>;
|
|
||||||
yDoc: Y.Doc;
|
|
||||||
yText: Y.Text;
|
|
||||||
binding?: MonacoBinding;
|
|
||||||
onSync: (isSynced: boolean) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const providersMap = useRef(new Map<string, ProviderData>());
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const tab = tabs.find((t) => t.id === activeFileId);
|
const tab = tabs.find((t) => t.id === activeFileId)
|
||||||
const model = editorRef?.getModel();
|
const model = editorRef?.getModel()
|
||||||
|
|
||||||
if (!editorRef || !tab || !model) return;
|
if (!editorRef || !tab || !model) return
|
||||||
|
|
||||||
let providerData: ProviderData;
|
let providerData: ProviderData;
|
||||||
|
|
||||||
|
// When a file is opened for the first time, create a new provider and store in providersMap.
|
||||||
if (!providersMap.current.has(tab.id)) {
|
if (!providersMap.current.has(tab.id)) {
|
||||||
const yDoc = new Y.Doc();
|
const yDoc = new Y.Doc();
|
||||||
const yText = yDoc.getText(tab.id);
|
const yText = yDoc.getText(tab.id);
|
||||||
const yProvider = new LiveblocksProvider(room, yDoc);
|
const yProvider = new LiveblocksProvider(room, yDoc);
|
||||||
|
|
||||||
|
// Inserts the file content into the editor once when the tab is changed.
|
||||||
const onSync = (isSynced: boolean) => {
|
const onSync = (isSynced: boolean) => {
|
||||||
if (isSynced) {
|
if (isSynced) {
|
||||||
const text = yText.toString()
|
const text = yText.toString()
|
||||||
@ -365,11 +364,14 @@ type ProviderData = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
yProvider.on("sync", onSync);
|
yProvider.on("sync", onSync)
|
||||||
|
|
||||||
|
// Save the provider to the map.
|
||||||
providerData = { provider: yProvider, yDoc, yText, onSync };
|
providerData = { provider: yProvider, yDoc, yText, onSync };
|
||||||
providersMap.current.set(tab.id, providerData);
|
providersMap.current.set(tab.id, providerData);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
// When a tab is opened that has been open before, reuse the existing provider.
|
||||||
providerData = providersMap.current.get(tab.id)!;
|
providerData = providersMap.current.get(tab.id)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,7 +395,7 @@ type ProviderData = {
|
|||||||
providerData.binding = undefined;
|
providerData.binding = undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [editorRef, room, activeFileContent, activeFileId, tabs]);
|
}, [room, activeFileContent]);
|
||||||
|
|
||||||
// Added this effect to clean up when the component unmounts
|
// Added this effect to clean up when the component unmounts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -490,8 +492,6 @@ type ProviderData = {
|
|||||||
|
|
||||||
setGenerate((prev) => ({ ...prev, show: false }));
|
setGenerate((prev) => ({ ...prev, show: false }));
|
||||||
|
|
||||||
//editor windows fix
|
|
||||||
function updateTabs(){
|
|
||||||
const exists = tabs.find((t) => t.id === tab.id);
|
const exists = tabs.find((t) => t.id === tab.id);
|
||||||
setTabs((prev) => {
|
setTabs((prev) => {
|
||||||
if (exists) {
|
if (exists) {
|
||||||
@ -500,16 +500,13 @@ type ProviderData = {
|
|||||||
}
|
}
|
||||||
return [...prev, tab];
|
return [...prev, tab];
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if (fileCache.current.has(tab.id)) {
|
if (fileCache.current.has(tab.id)) {
|
||||||
setActiveFileContent(fileCache.current.get(tab.id));
|
setActiveFileContent(fileCache.current.get(tab.id));
|
||||||
updateTabs();
|
|
||||||
} else {
|
} else {
|
||||||
debouncedGetFile(tab.id, (response: SetStateAction<string>) => {
|
debouncedGetFile(tab.id, (response: SetStateAction<string>) => {
|
||||||
fileCache.current.set(tab.id, response);
|
fileCache.current.set(tab.id, response);
|
||||||
setActiveFileContent(response);
|
setActiveFileContent(response);
|
||||||
updateTabs();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -829,7 +826,11 @@ type ProviderData = {
|
|||||||
className="p-2 flex flex-col"
|
className="p-2 flex flex-col"
|
||||||
>
|
>
|
||||||
{isOwner ? (
|
{isOwner ? (
|
||||||
<Terminals/>
|
<Terminals
|
||||||
|
terminals={terminals}
|
||||||
|
setTerminals={setTerminals}
|
||||||
|
socket={socketRef.current}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full h-full flex items-center justify-center text-lg font-medium text-muted-foreground/50 select-none">
|
<div className="w-full h-full flex items-center justify-center text-lg font-medium text-muted-foreground/50 select-none">
|
||||||
<TerminalSquare className="w-4 h-4 mr-2" />
|
<TerminalSquare className="w-4 h-4 mr-2" />
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Logo from "@/assets/logo.svg";
|
import Logo from "@/assets/logo.svg";
|
||||||
import { Pencil, Users, Play, StopCircle } from "lucide-react";
|
import { Pencil, Users } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Sandbox, User } from "@/lib/types";
|
import { Sandbox, User } from "@/lib/types";
|
||||||
import UserButton from "@/components/ui/userButton";
|
import UserButton from "@/components/ui/userButton";
|
||||||
@ -11,30 +11,23 @@ import { useState } from "react";
|
|||||||
import EditSandboxModal from "./edit";
|
import EditSandboxModal from "./edit";
|
||||||
import ShareSandboxModal from "./share";
|
import ShareSandboxModal from "./share";
|
||||||
import { Avatars } from "../live/avatars";
|
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({
|
export default function Navbar({
|
||||||
userData,
|
userData,
|
||||||
sandboxData,
|
sandboxData,
|
||||||
shared,
|
shared,
|
||||||
socket,
|
|
||||||
}: {
|
}: {
|
||||||
userData: User;
|
userData: User;
|
||||||
sandboxData: Sandbox;
|
sandboxData: Sandbox;
|
||||||
shared: { id: string; name: string }[];
|
shared: {
|
||||||
socket: Socket;
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}[];
|
||||||
}) {
|
}) {
|
||||||
const [isEditOpen, setIsEditOpen] = useState(false);
|
const [isEditOpen, setIsEditOpen] = useState(false);
|
||||||
const [isShareOpen, setIsShareOpen] = 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 (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -69,10 +62,6 @@ export default function Navbar({
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<RunButtonModal
|
|
||||||
isRunning={isRunning}
|
|
||||||
setIsRunning={setIsRunning}
|
|
||||||
/>
|
|
||||||
<div className="flex items-center h-full space-x-4">
|
<div className="flex items-center h-full space-x-4">
|
||||||
<Avatars />
|
<Avatars />
|
||||||
|
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
"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 (
|
|
||||||
<>
|
|
||||||
<Button variant="outline" onClick={handleRun}>
|
|
||||||
{isRunning ? <StopCircle className="w-4 h-4 mr-2" /> : <Play className="w-4 h-4 mr-2" />}
|
|
||||||
{isRunning ? 'Stop' : 'Run'}
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -2,16 +2,30 @@
|
|||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import Tab from "@/components/ui/tab";
|
import Tab from "@/components/ui/tab";
|
||||||
import { closeTerminal } from "@/lib/terminal";
|
import { closeTerminal, createTerminal } from "@/lib/terminal";
|
||||||
import { Terminal } from "@xterm/xterm";
|
import { Terminal } from "@xterm/xterm";
|
||||||
import { Loader2, Plus, SquareTerminal, TerminalSquare } from "lucide-react";
|
import { Loader2, Plus, SquareTerminal, TerminalSquare } from "lucide-react";
|
||||||
|
import { Socket } from "socket.io-client";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import EditorTerminal from "./terminal";
|
import EditorTerminal from "./terminal";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTerminal } from "@/context/TerminalContext";
|
|
||||||
|
|
||||||
export default function Terminals() {
|
export default function Terminals({
|
||||||
const { terminals, setTerminals, socket, createNewTerminal } = useTerminal();
|
terminals,
|
||||||
|
setTerminals,
|
||||||
|
socket,
|
||||||
|
}: {
|
||||||
|
terminals: { id: string; terminal: Terminal | null }[];
|
||||||
|
setTerminals: React.Dispatch<
|
||||||
|
React.SetStateAction<
|
||||||
|
{
|
||||||
|
id: string;
|
||||||
|
terminal: Terminal | null;
|
||||||
|
}[]
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
socket: Socket;
|
||||||
|
}) {
|
||||||
const [activeTerminalId, setActiveTerminalId] = useState("");
|
const [activeTerminalId, setActiveTerminalId] = useState("");
|
||||||
const [creatingTerminal, setCreatingTerminal] = useState(false);
|
const [creatingTerminal, setCreatingTerminal] = useState(false);
|
||||||
const [closingTerminal, setClosingTerminal] = useState("");
|
const [closingTerminal, setClosingTerminal] = useState("");
|
||||||
@ -32,7 +46,7 @@ export default function Terminals() {
|
|||||||
setTerminals,
|
setTerminals,
|
||||||
setActiveTerminalId,
|
setActiveTerminalId,
|
||||||
setClosingTerminal,
|
setClosingTerminal,
|
||||||
socket: socket!,
|
socket,
|
||||||
activeTerminalId,
|
activeTerminalId,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -50,7 +64,12 @@ export default function Terminals() {
|
|||||||
toast.error("You reached the maximum # of terminals.");
|
toast.error("You reached the maximum # of terminals.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
createNewTerminal();
|
createTerminal({
|
||||||
|
setTerminals,
|
||||||
|
setActiveTerminalId,
|
||||||
|
setCreatingTerminal,
|
||||||
|
socket,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
size="smIcon"
|
size="smIcon"
|
||||||
variant={"secondary"}
|
variant={"secondary"}
|
||||||
|
@ -1,99 +0,0 @@
|
|||||||
"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<React.SetStateAction<{ id: string; terminal: Terminal | null }[]>>;
|
|
||||||
activeTerminalId: string;
|
|
||||||
setActiveTerminalId: React.Dispatch<React.SetStateAction<string>>;
|
|
||||||
creatingTerminal: boolean;
|
|
||||||
setCreatingTerminal: React.Dispatch<React.SetStateAction<boolean>>;
|
|
||||||
createNewTerminal: () => void;
|
|
||||||
closeTerminal: (id: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TerminalContext = createContext<TerminalContextType | undefined>(undefined);
|
|
||||||
|
|
||||||
export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
||||||
const [socket, setSocket] = useState<Socket | null>(null);
|
|
||||||
const [terminals, setTerminals] = useState<{ id: string; terminal: Terminal | null }[]>([]);
|
|
||||||
const [activeTerminalId, setActiveTerminalId] = useState<string>('');
|
|
||||||
const [creatingTerminal, setCreatingTerminal] = useState<boolean>(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 (
|
|
||||||
<TerminalContext.Provider value={value}>
|
|
||||||
{children}
|
|
||||||
</TerminalContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useTerminal = (): TerminalContextType => {
|
|
||||||
const context = useContext(TerminalContext);
|
|
||||||
if (!context) {
|
|
||||||
throw new Error('useTerminal must be used within a TerminalProvider');
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
Reference in New Issue
Block a user