start multi terminal logic
This commit is contained in:
parent
4e42555887
commit
91feeffc5a
@ -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,7 +327,6 @@ 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]]
|
||||
|
@ -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>
|
||||
{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`}
|
||||
>
|
||||
{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>
|
||||
</>
|
||||
) : (
|
||||
|
@ -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,6 +63,7 @@ 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">
|
||||
@ -76,5 +72,6 @@ export default function EditorTerminal({
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
20
frontend/package-lock.json
generated
20
frontend/package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user