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...
-
{" "}
+
- )
+ );
}
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}
- )
+ );
}