Merge branch 'refs/heads/fix-files-loading'

# Conflicts:
#	frontend/components/editor/navbar/run.tsx
This commit is contained in:
James Murdza 2024-09-01 18:31:15 -07:00
commit 2065814aaa
6 changed files with 117 additions and 89 deletions

View File

@ -7,7 +7,8 @@ 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'; import { TerminalProvider } from '@/context/TerminalContext';
import { PreviewProvider } from "@/context/PreviewContext" import { PreviewProvider } from "@/context/PreviewContext";
import { SocketProvider } from '@/context/SocketContext'
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Sandbox", title: "Sandbox",
@ -15,7 +16,7 @@ export const metadata: Metadata = {
} }
export default function RootLayout({ export default function RootLayout({
children, children
}: Readonly<{ }: Readonly<{
children: React.ReactNode children: React.ReactNode
}>) { }>) {
@ -29,11 +30,13 @@ export default function RootLayout({
forcedTheme="dark" forcedTheme="dark"
disableTransitionOnChange disableTransitionOnChange
> >
<SocketProvider>
<PreviewProvider> <PreviewProvider>
<TerminalProvider> <TerminalProvider>
{children} {children}
</TerminalProvider> </TerminalProvider>
</PreviewProvider> </PreviewProvider>
</SocketProvider>
<Analytics /> <Analytics />
<Toaster position="bottom-left" richColors /> <Toaster position="bottom-left" richColors />
</ThemeProvider> </ThemeProvider>

View File

@ -49,10 +49,8 @@ export default function Dashboard({
const q = searchParams.get("q") const q = searchParams.get("q")
const router = useRouter() const router = useRouter()
useEffect(() => { useEffect(() => { // update the dashboard to show a new project
if (!sandboxes) { router.refresh()
router.refresh()
}
}, [sandboxes]) }, [sandboxes])
return ( return (

View File

@ -3,7 +3,6 @@
import { SetStateAction, useCallback, useEffect, useRef, useState } from "react" import { SetStateAction, useCallback, useEffect, useRef, useState } from "react"
import monaco from "monaco-editor" import monaco from "monaco-editor"
import Editor, { BeforeMount, OnMount } from "@monaco-editor/react" import Editor, { BeforeMount, OnMount } from "@monaco-editor/react"
import { Socket, io } from "socket.io-client"
import { toast } from "sonner" import { toast } from "sonner"
import { useClerk } from "@clerk/nextjs" import { useClerk } from "@clerk/nextjs"
@ -32,7 +31,7 @@ import PreviewWindow from "./preview"
import Terminals from "./terminals" import Terminals from "./terminals"
import { ImperativePanelHandle } from "react-resizable-panels" import { ImperativePanelHandle } from "react-resizable-panels"
import { PreviewProvider, usePreview } from '@/context/PreviewContext'; import { PreviewProvider, usePreview } from '@/context/PreviewContext';
import { useTerminal } from '@/context/TerminalContext'; import { useSocket } from "@/context/SocketContext"
export default function CodeEditor({ export default function CodeEditor({
userData, userData,
@ -41,24 +40,20 @@ export default function CodeEditor({
userData: User userData: User
sandboxData: Sandbox sandboxData: Sandbox
}) { }) {
const socketRef = useRef<Socket | null>(null);
// Initialize socket connection if it doesn't exist //SocketContext functions and effects
if (!socketRef.current) { const { socket, setUserAndSandboxId } = useSocket();
socketRef.current = io(
`${process.env.NEXT_PUBLIC_SERVER_URL}?userId=${userData.id}&sandboxId=${sandboxData.id}`,
{
timeout: 2000,
}
);
}
//Terminalcontext functionsand effects
const { setUserAndSandboxId } = useTerminal();
useEffect(() => { useEffect(() => {
setUserAndSandboxId(userData.id, sandboxData.id); // Ensure userData.id and sandboxData.id are available before attempting to connect
}, [userData.id, sandboxData.id, setUserAndSandboxId]); if (userData.id && sandboxData.id) {
// Check if the socket is not initialized or not connected
if (!socket || (socket && !socket.connected)) {
// Initialize socket connection
setUserAndSandboxId(userData.id, sandboxData.id);
}
}
}, [socket, userData.id, sandboxData.id, setUserAndSandboxId]);
//Preview Button state //Preview Button state
const [isPreviewCollapsed, setIsPreviewCollapsed] = useState(true) const [isPreviewCollapsed, setIsPreviewCollapsed] = useState(true)
@ -120,7 +115,7 @@ export default function CodeEditor({
const room = useRoom() const room = useRoom()
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 // Liveblocks providers map to prevent reinitializing providers
type ProviderData = { type ProviderData = {
provider: LiveblocksProvider<never, never, never, never>; provider: LiveblocksProvider<never, never, never, never>;
@ -333,9 +328,9 @@ export default function CodeEditor({
); );
console.log(`Saving file...${activeFileId}`); console.log(`Saving file...${activeFileId}`);
console.log(`Saving file...${value}`); console.log(`Saving file...${value}`);
socketRef.current?.emit("saveFile", activeFileId, value); socket?.emit("saveFile", activeFileId, value);
}, Number(process.env.FILE_SAVE_DEBOUNCE_DELAY) || 1000), }, Number(process.env.FILE_SAVE_DEBOUNCE_DELAY) || 1000),
[socketRef] [socket]
); );
useEffect(() => { useEffect(() => {
@ -432,9 +427,9 @@ export default function CodeEditor({
// Connection/disconnection effect // Connection/disconnection effect
useEffect(() => { useEffect(() => {
socketRef.current?.connect() socket?.connect()
return () => { return () => {
socketRef.current?.disconnect() socket?.disconnect()
} }
}, []) }, [])
@ -469,25 +464,24 @@ export default function CodeEditor({
}) })
} }
socketRef.current?.on("connect", onConnect) socket?.on("connect", onConnect)
socketRef.current?.on("disconnect", onDisconnect) socket?.on("disconnect", onDisconnect)
socketRef.current?.on("loaded", onLoadedEvent) socket?.on("loaded", onLoadedEvent)
socketRef.current?.on("error", onError) socket?.on("error", onError)
socketRef.current?.on("terminalResponse", onTerminalResponse) socket?.on("terminalResponse", onTerminalResponse)
socketRef.current?.on("disableAccess", onDisableAccess) socket?.on("disableAccess", onDisableAccess)
socketRef.current?.on("previewURL", loadPreviewURL) socket?.on("previewURL", loadPreviewURL)
return () => { return () => {
socketRef.current?.off("connect", onConnect) socket?.off("connect", onConnect)
socketRef.current?.off("disconnect", onDisconnect) socket?.off("disconnect", onDisconnect)
socketRef.current?.off("loaded", onLoadedEvent) socket?.off("loaded", onLoadedEvent)
socketRef.current?.off("error", onError) socket?.off("error", onError)
socketRef.current?.off("terminalResponse", onTerminalResponse) socket?.off("terminalResponse", onTerminalResponse)
socketRef.current?.off("disableAccess", onDisableAccess) socket?.off("disableAccess", onDisableAccess)
socketRef.current?.off("previewURL", loadPreviewURL) socket?.off("previewURL", loadPreviewURL)
} }
// }, []); }, [socket, terminals, setTerminals, setFiles, toast, setDisableAccess, isOwner, loadPreviewURL]);
}, [terminals])
// Helper functions for tabs: // Helper functions for tabs:
@ -499,7 +493,7 @@ export default function CodeEditor({
// Debounced function to get file content // Debounced function to get file content
const debouncedGetFile = useCallback( const debouncedGetFile = useCallback(
debounce((tabId, callback) => { debounce((tabId, callback) => {
socketRef.current?.emit('getFile', tabId, callback); socket?.emit('getFile', tabId, callback);
}, 300), // 300ms debounce delay, adjust as needed }, 300), // 300ms debounce delay, adjust as needed
[] []
); );
@ -603,7 +597,7 @@ export default function CodeEditor({
return false return false
} }
socketRef.current?.emit("renameFile", id, newName) socket?.emit("renameFile", id, newName)
setTabs((prev) => setTabs((prev) =>
prev.map((tab) => (tab.id === id ? { ...tab, name: newName } : tab)) prev.map((tab) => (tab.id === id ? { ...tab, name: newName } : tab))
) )
@ -612,7 +606,7 @@ export default function CodeEditor({
} }
const handleDeleteFile = (file: TFile) => { const handleDeleteFile = (file: TFile) => {
socketRef.current?.emit("deleteFile", file.id, (response: (TFolder | TFile)[]) => { socket?.emit("deleteFile", file.id, (response: (TFolder | TFile)[]) => {
setFiles(response) setFiles(response)
}) })
closeTab(file.id) closeTab(file.id)
@ -622,11 +616,11 @@ export default function CodeEditor({
setDeletingFolderId(folder.id) setDeletingFolderId(folder.id)
console.log("deleting folder", folder.id) console.log("deleting folder", folder.id)
socketRef.current?.emit("getFolder", folder.id, (response: string[]) => socket?.emit("getFolder", folder.id, (response: string[]) =>
closeTabs(response) closeTabs(response)
) )
socketRef.current?.emit("deleteFolder", folder.id, (response: (TFolder | TFile)[]) => { socket?.emit("deleteFolder", folder.id, (response: (TFolder | TFile)[]) => {
setFiles(response) setFiles(response)
setDeletingFolderId("") setDeletingFolderId("")
}) })
@ -654,7 +648,7 @@ export default function CodeEditor({
{generate.show && ai ? ( {generate.show && ai ? (
<GenerateInput <GenerateInput
user={userData} user={userData}
socket={socketRef.current} socket={socket!}
width={generate.width - 90} width={generate.width - 90}
data={{ data={{
fileName: tabs.find((t) => t.id === activeFileId)?.name ?? "", fileName: tabs.find((t) => t.id === activeFileId)?.name ?? "",
@ -714,7 +708,7 @@ export default function CodeEditor({
handleRename={handleRename} handleRename={handleRename}
handleDeleteFile={handleDeleteFile} handleDeleteFile={handleDeleteFile}
handleDeleteFolder={handleDeleteFolder} handleDeleteFolder={handleDeleteFolder}
socket={socketRef.current} socket={socket!}
setFiles={setFiles} setFiles={setFiles}
addNew={(name, type) => addNew(name, type, setFiles, sandboxData)} addNew={(name, type) => addNew(name, type, setFiles, sandboxData)}
deletingFolderId={deletingFolderId} deletingFolderId={deletingFolderId}
@ -832,7 +826,7 @@ export default function CodeEditor({
open={() => { open={() => {
usePreview().previewPanelRef.current?.expand() usePreview().previewPanelRef.current?.expand()
setIsPreviewCollapsed(false) setIsPreviewCollapsed(false)
} } collapsed={isPreviewCollapsed} src={previewURL} ref={previewWindowRef} /> }} collapsed={isPreviewCollapsed} src={previewURL} ref={previewWindowRef} />
</ResizablePanel> </ResizablePanel>
<ResizableHandle /> <ResizableHandle />
<ResizablePanel <ResizablePanel

View File

@ -8,12 +8,15 @@ import { toast } from "sonner";
import EditorTerminal from "./terminal"; import EditorTerminal from "./terminal";
import { useTerminal } from "@/context/TerminalContext"; import { useTerminal } from "@/context/TerminalContext";
import { useEffect } from "react"; import { useEffect } from "react";
import { useSocket } from "@/context/SocketContext"
export default function Terminals() { export default function Terminals() {
const { socket } = useSocket();
const { const {
terminals, terminals,
setTerminals, setTerminals,
socket,
createNewTerminal, createNewTerminal,
closeTerminal, closeTerminal,
activeTerminalId, activeTerminalId,

View File

@ -0,0 +1,63 @@
"use client";
import React, { createContext, useContext, useEffect, useState } from 'react';
import { io, Socket } from 'socket.io-client';
interface SocketContextType {
socket: Socket | null;
setUserAndSandboxId: (userId: string, sandboxId: string) => void;
}
const SocketContext = createContext<SocketContextType | undefined>(undefined);
export const SocketProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [socket, setSocket] = useState<Socket | null>(null);
const [userId, setUserId] = useState<string | null>(null);
const [sandboxId, setSandboxId] = useState<string | null>(null);
useEffect(() => {
if (userId && sandboxId) {
console.log("Initializing socket connection...");
const newSocket = io(`${process.env.NEXT_PUBLIC_SERVER_URL}?userId=${userId}&sandboxId=${sandboxId}`);
console.log("Socket instance:", newSocket);
setSocket(newSocket);
newSocket.on('connect', () => {
console.log("Socket connected:", newSocket.id);
});
newSocket.on('disconnect', () => {
console.log("Socket disconnected");
});
return () => {
console.log("Disconnecting socket...");
newSocket.disconnect();
};
}
}, [userId, sandboxId]);
const setUserAndSandboxId = (newUserId: string, newSandboxId: string) => {
setUserId(newUserId);
setSandboxId(newSandboxId);
};
const value = {
socket,
setUserAndSandboxId,
};
return (
<SocketContext.Provider value={ value }>
{children}
</SocketContext.Provider>
);
};
export const useSocket = (): SocketContextType => {
const context = useContext(SocketContext);
if (!context) {
throw new Error('useSocket must be used within a SocketProvider');
}
return context;
};

View File

@ -1,12 +1,11 @@
"use client"; "use client";
import React, { createContext, useContext, useState, useEffect } from 'react'; import React, { createContext, useContext, useState } from 'react';
import { io, Socket } from 'socket.io-client';
import { Terminal } from '@xterm/xterm'; import { Terminal } from '@xterm/xterm';
import { createTerminal as createTerminalHelper, closeTerminal as closeTerminalHelper } from '@/lib/terminal'; import { createTerminal as createTerminalHelper, closeTerminal as closeTerminalHelper } from '@/lib/terminal';
import { useSocket } from '@/context/SocketContext';
interface TerminalContextType { interface TerminalContextType {
socket: Socket | null;
terminals: { id: string; terminal: Terminal | null }[]; terminals: { id: string; terminal: Terminal | null }[];
setTerminals: React.Dispatch<React.SetStateAction<{ id: string; terminal: Terminal | null }[]>>; setTerminals: React.Dispatch<React.SetStateAction<{ id: string; terminal: Terminal | null }[]>>;
activeTerminalId: string; activeTerminalId: string;
@ -15,41 +14,16 @@ interface TerminalContextType {
setCreatingTerminal: React.Dispatch<React.SetStateAction<boolean>>; setCreatingTerminal: React.Dispatch<React.SetStateAction<boolean>>;
createNewTerminal: (command?: string) => Promise<void>; createNewTerminal: (command?: string) => Promise<void>;
closeTerminal: (id: string) => void; closeTerminal: (id: string) => void;
setUserAndSandboxId: (userId: string, sandboxId: string) => void;
deploy: (callback: () => void) => void; deploy: (callback: () => void) => void;
} }
const TerminalContext = createContext<TerminalContextType | undefined>(undefined); const TerminalContext = createContext<TerminalContextType | undefined>(undefined);
export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [socket, setSocket] = useState<Socket | null>(null); const { socket } = useSocket();
const [terminals, setTerminals] = useState<{ id: string; terminal: Terminal | null }[]>([]); const [terminals, setTerminals] = useState<{ id: string; terminal: Terminal | null }[]>([]);
const [activeTerminalId, setActiveTerminalId] = useState<string>(''); const [activeTerminalId, setActiveTerminalId] = useState<string>('');
const [creatingTerminal, setCreatingTerminal] = useState<boolean>(false); const [creatingTerminal, setCreatingTerminal] = useState<boolean>(false);
const [userId, setUserId] = useState<string | null>(null);
const [sandboxId, setSandboxId] = useState<string | null>(null);
useEffect(() => {
if (userId && sandboxId) {
console.log("Initializing socket connection...");
const newSocket = io(`${process.env.NEXT_PUBLIC_SERVER_URL}?userId=${userId}&sandboxId=${sandboxId}`);
console.log("Socket instance:", newSocket);
setSocket(newSocket);
newSocket.on('connect', () => {
console.log("Socket connected:", newSocket.id);
});
newSocket.on('disconnect', () => {
console.log("Socket disconnected");
});
return () => {
console.log("Disconnecting socket...");
newSocket.disconnect();
};
}
}, [userId, sandboxId]);
const createNewTerminal = async (command?: string): Promise<void> => { const createNewTerminal = async (command?: string): Promise<void> => {
if (!socket) return; if (!socket) return;
@ -85,11 +59,6 @@ export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ chil
} }
}; };
const setUserAndSandboxId = (newUserId: string, newSandboxId: string) => {
setUserId(newUserId);
setSandboxId(newSandboxId);
};
const deploy = (callback: () => void) => { const deploy = (callback: () => void) => {
if (!socket) console.error("Couldn't deploy: No socket"); if (!socket) console.error("Couldn't deploy: No socket");
console.log("Deploying...") console.log("Deploying...")
@ -99,7 +68,6 @@ export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ chil
} }
const value = { const value = {
socket,
terminals, terminals,
setTerminals, setTerminals,
activeTerminalId, activeTerminalId,
@ -108,7 +76,6 @@ export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ chil
setCreatingTerminal, setCreatingTerminal,
createNewTerminal, createNewTerminal,
closeTerminal, closeTerminal,
setUserAndSandboxId,
deploy deploy
}; };