From e86e86dbe2ef5fd18e988b95baad6b9324af4d91 Mon Sep 17 00:00:00 2001 From: Ishaan Dey Date: Thu, 9 May 2024 22:16:56 -0700 Subject: [PATCH] add resizing logic --- backend/server/src/index.ts | 14 ++-- frontend/components/dashboard/newProject.tsx | 7 +- frontend/components/dashboard/projects.tsx | 14 +++- frontend/components/editor/index.tsx | 64 ++++++++++++----- frontend/components/editor/preview/index.tsx | 68 +++++++++++++++---- .../components/editor/terminals/index.tsx | 3 +- .../components/editor/terminals/terminal.tsx | 26 ++++--- frontend/components/ui/tab.tsx | 4 +- 8 files changed, 150 insertions(+), 50 deletions(-) diff --git a/backend/server/src/index.ts b/backend/server/src/index.ts index bd88709..e2be665 100644 --- a/backend/server/src/index.ts +++ b/backend/server/src/index.ts @@ -263,8 +263,15 @@ io.on("connection", async (socket) => { callback() }) + socket.on("resizeTerminal", (dimensions: { cols: number; rows: number }) => { + console.log("resizeTerminal", dimensions) + Object.values(terminals).forEach((t) => { + t.terminal.resize(dimensions.cols, dimensions.rows) + }) + + }) + socket.on("terminalData", (id: string, data: string) => { - console.log("terminalData", id, data) if (!terminals[id]) { console.log("terminal not found", id) return @@ -282,13 +289,10 @@ io.on("connection", async (socket) => { return } - console.log("closing terminal", id) terminals[id].onData.dispose() terminals[id].onExit.dispose() delete terminals[id] - console.log("terminals:", Object.keys(terminals)) - callback() }) @@ -330,7 +334,7 @@ io.on("connection", async (socket) => { socket.on("disconnect", async () => { if (data.isOwner) { - console.log("deleting all terminals") + // console.log("deleting all terminals") Object.entries(terminals).forEach((t) => { const { terminal, onData, onExit } = t[1] onData.dispose() diff --git a/frontend/components/dashboard/newProject.tsx b/frontend/components/dashboard/newProject.tsx index 7b60f29..8f1f016 100644 --- a/frontend/components/dashboard/newProject.tsx +++ b/frontend/components/dashboard/newProject.tsx @@ -120,7 +120,12 @@ export default function NewProjectModal({ } return ( - + { + if (!loading) setOpen(open); + }} + > Create A Sandbox diff --git a/frontend/components/dashboard/projects.tsx b/frontend/components/dashboard/projects.tsx index 650462d..0d86f20 100644 --- a/frontend/components/dashboard/projects.tsx +++ b/frontend/components/dashboard/projects.tsx @@ -9,6 +9,7 @@ import Link from "next/link"; import { Card } from "../ui/card"; import { deleteSandbox, updateSandbox } from "@/lib/actions"; import { toast } from "sonner"; +import { useState } from "react"; export default function DashboardProjects({ sandboxes, @@ -17,9 +18,16 @@ export default function DashboardProjects({ sandboxes: Sandbox[]; q: string | null; }) { + const [deletingId, setDeletingId] = useState(""); + const onDelete = async (sandbox: Sandbox) => { + setDeletingId(sandbox.id); toast(`Project ${sandbox.name} deleted.`); await deleteSandbox(sandbox.id); + setTimeout(() => { + // timeout to wait for revalidatePath and avoid flashing + setDeletingId(""); + }, 200); }; const onVisibilityChange = async (sandbox: Sandbox) => { @@ -50,7 +58,11 @@ export default function DashboardProjects({ {/* */} diff --git a/frontend/components/editor/index.tsx b/frontend/components/editor/index.tsx index 33f08c1..dbb1328 100644 --- a/frontend/components/editor/index.tsx +++ b/frontend/components/editor/index.tsx @@ -30,6 +30,7 @@ import DisableAccessModal from "./live/disableModal"; import Loading from "./loading"; import PreviewWindow from "./preview"; import Terminals from "./terminals"; +import { ImperativePanelHandle } from "react-resizable-panels"; export default function CodeEditor({ userData, @@ -44,12 +45,28 @@ export default function CodeEditor({ `http://localhost:4000?userId=${userData.id}&sandboxId=${sandboxData.id}` ); + const [isPreviewCollapsed, setIsPreviewCollapsed] = useState( + sandboxData.type !== "react" + ); + const [disableAccess, setDisableAccess] = useState({ + isDisabled: false, + message: "", + }); + + // File state const [files, setFiles] = useState<(TFolder | TFile)[]>([]); const [tabs, setTabs] = useState([]); - const [editorLanguage, setEditorLanguage] = useState("plaintext"); const [activeFileId, setActiveFileId] = useState(""); const [activeFileContent, setActiveFileContent] = useState(""); + + // Editor state + const [editorLanguage, setEditorLanguage] = useState("plaintext"); const [cursorLine, setCursorLine] = useState(0); + const [editorRef, setEditorRef] = + useState(); + + // AI Copilot state + const [ai, setAi] = useState(false); const [generate, setGenerate] = useState<{ show: boolean; id: string; @@ -62,6 +79,8 @@ export default function CodeEditor({ options: monaco.editor.IModelDeltaDecoration[]; instance: monaco.editor.IEditorDecorationsCollection | undefined; }>({ options: [], instance: undefined }); + + // Terminal state const [terminals, setTerminals] = useState< { id: string; @@ -71,24 +90,21 @@ export default function CodeEditor({ const [activeTerminalId, setActiveTerminalId] = useState(""); const [creatingTerminal, setCreatingTerminal] = useState(false); const [closingTerminal, setClosingTerminal] = useState(""); - const [provider, setProvider] = useState(); - const [ai, setAi] = useState(false); - const [disableAccess, setDisableAccess] = useState({ - isDisabled: false, - message: "", - }); + const activeTerminal = terminals.find((t) => t.id === activeTerminalId); const isOwner = sandboxData.userId === userData.id; const clerk = useClerk(); - const room = useRoom(); - const activeTerminal = terminals.find((t) => t.id === activeTerminalId); - const [editorRef, setEditorRef] = - useState(); + // Liveblocks hooks + const room = useRoom(); + const [provider, setProvider] = useState(); + + // Refs for libraries / features const editorContainerRef = useRef(null); const monacoRef = useRef(null); const generateRef = useRef(null); const generateWidgetRef = useRef(null); + const previewPanelRef = useRef(null); // Resize observer tracks editor width for generate widget const resizeObserver = new ResizeObserver((entries) => { @@ -371,10 +387,11 @@ export default function CodeEditor({ }; const onDisableAccess = (message: string) => { - setDisableAccess({ - isDisabled: true, - message, - }); + if (!isOwner) + setDisableAccess({ + isDisabled: true, + message, + }); }; socket.on("connect", onConnect); @@ -571,7 +588,7 @@ export default function CodeEditor({ @@ -659,11 +676,22 @@ export default function CodeEditor({ setIsPreviewCollapsed(true)} + onExpand={() => setIsPreviewCollapsed(false)} > - + { + previewPanelRef.current?.expand(); + setIsPreviewCollapsed(false); + }} + /> void; +}) { return ( <> -
+
Preview
-
- -
-
- -
-
- -
-
- -
+ {collapsed ? ( + + + + ) : ( + <> + console.log("Terminal")}> + + + console.log("Back")}> + + + console.log("Forward")}> + + + console.log("Reload")}> + + + + )}
-
+ {collapsed ? null : ( +
+ )} ); } + +function PreviewButton({ + children, + onClick, +}: { + children: React.ReactNode; + onClick: () => void; +}) { + return ( +
+ {children} +
+ ); +} diff --git a/frontend/components/editor/terminals/index.tsx b/frontend/components/editor/terminals/index.tsx index f28f18c..f96ac84 100644 --- a/frontend/components/editor/terminals/index.tsx +++ b/frontend/components/editor/terminals/index.tsx @@ -50,6 +50,7 @@ export default function Terminals({ {terminals.map((term) => ( setActiveTerminalId(term.id)} onClose={() => closeTerminal({ @@ -85,7 +86,7 @@ export default function Terminals({ }} size="smIcon" variant={"secondary"} - className={`font-normal shrink-0 select-none text-muted-foreground`} + className={`font-normal shrink-0 select-none text-muted-foreground disabled:opacity-50`} > {creatingTerminal ? ( diff --git a/frontend/components/editor/terminals/terminal.tsx b/frontend/components/editor/terminals/terminal.tsx index 8408764..3acbe23 100644 --- a/frontend/components/editor/terminals/terminal.tsx +++ b/frontend/components/editor/terminals/terminal.tsx @@ -48,19 +48,29 @@ export default function EditorTerminal({ useEffect(() => { if (!term) return; - if (terminalRef.current) { - const fitAddon = new FitAddon(); - term.loadAddon(fitAddon); - term.open(terminalRef.current); - fitAddon.fit(); - } - const disposable = term.onData((data) => { + if (!terminalRef.current) return; + const fitAddon = new FitAddon(); + term.loadAddon(fitAddon); + term.open(terminalRef.current); + fitAddon.fit(); + + const disposableOnData = term.onData((data) => { console.log("terminalData", id, data); socket.emit("terminalData", id, data); }); + const disposableOnResize = term.onResize((dimensions) => { + // const terminal_size = { + // width: dimensions.cols, + // height: dimensions.rows, + // }; + fitAddon.fit(); + socket.emit("terminalResize", dimensions); + }); + return () => { - disposable.dispose(); + disposableOnData.dispose(); + disposableOnResize.dispose(); }; }, [term, terminalRef.current]); diff --git a/frontend/components/ui/tab.tsx b/frontend/components/ui/tab.tsx index 0338f53..a47bd41 100644 --- a/frontend/components/ui/tab.tsx +++ b/frontend/components/ui/tab.tsx @@ -6,6 +6,7 @@ import { MouseEvent, MouseEventHandler, useEffect } from "react"; export default function Tab({ children, + creating = false, saved = true, selected = false, onClick, @@ -13,6 +14,7 @@ export default function Tab({ closing = false, }: { children: React.ReactNode; + creating?: boolean; saved?: boolean; selected?: boolean; onClick?: MouseEventHandler; @@ -43,7 +45,7 @@ export default function Tab({ } className="h-5 w-5 ml-0.5 group flex items-center justify-center translate-x-1 transition-colors bg-transparent hover:bg-muted-foreground/25 cursor-pointer rounded-sm" > - {closing ? ( + {closing || creating ? ( ) : saved ? (