start multi terminal logic

This commit is contained in:
Ishaan Dey 2024-05-06 23:34:45 -07:00
parent 4e42555887
commit 91feeffc5a
5 changed files with 139 additions and 39 deletions

View File

@ -222,7 +222,7 @@ io.on("connection", async (socket) => {
}
})
socket.on("createTerminal", ({ id }: { id: string }) => {
socket.on("createTerminal", (id: string, callback) => {
console.log("creating terminal", id)
if (terminals[id]) {
console.log("Terminal already exists.")
@ -243,6 +243,7 @@ io.on("connection", async (socket) => {
console.log("ondata")
socket.emit("terminalResponse", {
// data: Buffer.from(data, "utf-8").toString("base64"),
id,
data,
})
})
@ -256,6 +257,8 @@ io.on("connection", async (socket) => {
onData,
onExit,
}
callback(true)
})
socket.on("terminalData", (id: string, data: string) => {
@ -271,6 +274,19 @@ io.on("connection", async (socket) => {
}
})
socket.on("closeTerminal", (id: string, callback) => {
if (!terminals[id]) {
console.log("tried to close, but term does not exist. terminals", terminals)
return
}
terminals[id].onData.dispose()
terminals[id].onExit.dispose()
delete terminals[id]
callback(true)
})
socket.on(
"generateCode",
async (
@ -311,10 +327,9 @@ io.on("connection", async (socket) => {
if (data.isOwner) {
Object.entries(terminals).forEach((t) => {
const { terminal, onData, onExit } = t[1]
if (os.platform() !== "win32") terminal.kill()
onData.dispose()
onExit.dispose()
delete terminals[t[0]]
onData.dispose()
onExit.dispose()
delete terminals[t[0]]
})
// console.log("The owner disconnected.")

View File

@ -6,6 +6,7 @@ import Editor, { BeforeMount, OnMount } from "@monaco-editor/react";
import { io } from "socket.io-client";
import { toast } from "sonner";
import { useClerk } from "@clerk/nextjs";
import { createId } from "@paralleldrive/cuid2";
import * as Y from "yjs";
import LiveblocksProvider from "@liveblocks/yjs";
@ -52,9 +53,7 @@ export default function CodeEditor({
const [tabs, setTabs] = useState<TTab[]>([]);
const [editorLanguage, setEditorLanguage] = useState("plaintext");
const [activeFileId, setActiveFileId] = useState<string>("");
const [activeFileContent, setActiveFileContent] = useState<string | null>(
null
);
const [activeFileContent, setActiveFileContent] = useState("");
const [cursorLine, setCursorLine] = useState(0);
const [generate, setGenerate] = useState<{
show: boolean;
@ -74,12 +73,16 @@ export default function CodeEditor({
terminal: Terminal | null;
}[]
>([]);
const [activeTerminalId, setActiveTerminalId] = useState("");
const [creatingTerminal, setCreatingTerminal] = useState(false);
const [provider, setProvider] = useState<TypedLiveblocksProvider>();
const [ai, setAi] = useState(false);
const isOwner = sandboxData.userId === userData.id;
const clerk = useClerk();
const room = useRoom();
const activeTerminal = terminals.find((t) => t.id === activeTerminalId);
console.log("activeTerminal", activeTerminal ? activeTerminal.id : "none");
// const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null)
const [editorRef, setEditorRef] =
@ -354,9 +357,8 @@ export default function CodeEditor({
useEffect(() => {
const onConnect = () => {
console.log("connected");
setTimeout(() => {
socket.emit("createTerminal", { id: "testId" });
}, 1000);
createTerminal();
};
const onDisconnect = () => {};
@ -395,9 +397,17 @@ export default function CodeEditor({
// Helper functions:
const createTerminal = () => {
const id = "testId";
socket.emit("createTerminal", { id });
setCreatingTerminal(true);
const id = createId();
setActiveTerminalId(id);
setTimeout(() => {
socket.emit("createTerminal", id, (res: boolean) => {
if (res) {
setTerminals((prev) => [...prev, { id, terminal: null }]);
}
});
}, 1000);
setCreatingTerminal(false);
};
const selectFile = (tab: TTab) => {
@ -446,6 +456,36 @@ export default function CodeEditor({
}
};
const closeTerminal = (term: { id: string; terminal: Terminal | null }) => {
const numTerminals = terminals.length;
const index = terminals.findIndex((t) => t.id === term.id);
if (index === -1) return;
socket.emit("closeTerminal", term.id, (res: boolean) => {
if (res) {
const nextId =
activeTerminalId === term.id
? numTerminals === 1
? null
: index < numTerminals - 1
? terminals[index + 1].id
: terminals[index - 1].id
: activeTerminalId;
setTerminals((prev) => prev.filter((t) => t.id !== term.id));
if (!nextId) {
setActiveTerminalId("");
} else {
const nextTerminal = terminals.find((t) => t.id === nextId);
if (nextTerminal) {
setActiveTerminalId(nextTerminal.id);
}
}
}
});
};
const handleRename = (
id: string,
newName: string,
@ -624,7 +664,7 @@ export default function CodeEditor({
fontFamily: "var(--font-geist-mono)",
}}
theme="vs-dark"
value={activeFileContent ?? ""}
value={activeFileContent}
/>
</>
) : (
@ -673,29 +713,56 @@ export default function CodeEditor({
{isOwner ? (
<>
<div className="h-10 w-full flex gap-2 shrink-0">
<Tab selected>
<SquareTerminal className="w-4 h-4 mr-2" />
Shell
</Tab>
{terminals.map((term) => (
<Tab
key={term.id}
onClick={() => setActiveTerminalId(term.id)}
onClose={() => closeTerminal(term)}
selected={activeTerminalId === term.id}
>
<SquareTerminal className="w-4 h-4 mr-2" />
Shell
</Tab>
))}
<Button
disabled={creatingTerminal}
onClick={() => {
if (terminals.length >= 4) {
toast.error(
"You reached the maximum # of terminals."
);
return;
}
createTerminal();
}}
size="smIcon"
variant={"secondary"}
className={`font-normal select-none text-muted-foreground`}
>
<Plus className="w-4 h-4" />
{creatingTerminal ? (
<Loader2 className="animate-spin w-4 h-4" />
) : (
<Plus className="w-4 h-4" />
)}
</Button>
</div>
<div className="w-full relative grow h-full overflow-hidden rounded-md bg-secondary">
{/* {socket ? <EditorTerminal socket={socket} term={
} /> : null} */}
{socket && activeTerminal ? (
<EditorTerminal
socket={socket}
id={activeTerminal.id}
term={activeTerminal.terminal}
setTerm={(t: Terminal) => {
setTerminals((prev) =>
prev.map((term) =>
term.id === activeTerminal.id
? { ...term, terminal: t }
: term
)
);
}}
/>
) : null}
</div>
</>
) : (

View File

@ -10,10 +10,12 @@ import { Loader2 } from "lucide-react";
export default function EditorTerminal({
socket,
id,
term,
setTerm,
}: {
socket: Socket;
id: string;
term: Terminal | null;
setTerm: (term: Terminal) => void;
}) {
@ -41,14 +43,7 @@ export default function EditorTerminal({
useEffect(() => {
if (!term) return;
// const onTerminalResponse = (response: { data: string }) => {
// const res = response.data;
// term.write(res);
// };
if (terminalRef.current) {
// socket.on("terminalResponse", onTerminalResponse);
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
term.open(terminalRef.current);
@ -57,7 +52,7 @@ export default function EditorTerminal({
}
const disposable = term.onData((data) => {
console.log("sending data", data);
socket.emit("terminalData", "testId", data);
socket.emit("terminalData", id, data);
});
// socket.emit("terminalData", "\n");
@ -68,13 +63,15 @@ export default function EditorTerminal({
}, [term, terminalRef.current]);
return (
<div ref={terminalRef} className="w-full h-full text-left">
{term === null ? (
<div className="flex items-center text-muted-foreground p-2">
<Loader2 className="animate-spin mr-2 h-4 w-4" />
<span>Connecting to terminal...</span>
</div>
) : null}
</div>
<>
<div ref={terminalRef} className="w-full h-full text-left">
{term === null ? (
<div className="flex items-center text-muted-foreground p-2">
<Loader2 className="animate-spin mr-2 h-4 w-4" />
<span>Connecting to terminal...</span>
</div>
) : null}
</div>
</>
);
}

View File

@ -16,6 +16,7 @@
"@liveblocks/react": "^1.12.0",
"@liveblocks/yjs": "^1.12.0",
"@monaco-editor/react": "^4.6.0",
"@paralleldrive/cuid2": "^2.2.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-context-menu": "^2.1.5",
"@radix-ui/react-dialog": "^1.0.5",
@ -609,6 +610,17 @@
"node": ">= 10"
}
},
"node_modules/@noble/hashes": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz",
"integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -641,6 +653,14 @@
"node": ">= 8"
}
},
"node_modules/@paralleldrive/cuid2": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz",
"integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==",
"dependencies": {
"@noble/hashes": "^1.1.5"
}
},
"node_modules/@peculiar/asn1-schema": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.8.tgz",

View File

@ -17,6 +17,7 @@
"@liveblocks/react": "^1.12.0",
"@liveblocks/yjs": "^1.12.0",
"@monaco-editor/react": "^4.6.0",
"@paralleldrive/cuid2": "^2.2.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-context-menu": "^2.1.5",
"@radix-ui/react-dialog": "^1.0.5",