From 6fb1364d6f8f8ab6f5ac6dc40c4d93a6ee1da668 Mon Sep 17 00:00:00 2001 From: Akhilesh Rangani Date: Mon, 21 Oct 2024 13:57:45 -0600 Subject: [PATCH] chore: format frontend code --- frontend/app/(app)/code/[id]/page.tsx | 36 +- frontend/app/(app)/dashboard/page.tsx | 6 +- frontend/app/layout.tsx | 22 +- frontend/app/page.tsx | 12 +- frontend/components/dashboard/about.tsx | 7 - frontend/components/dashboard/index.tsx | 27 +- .../components/dashboard/navbar/index.tsx | 6 +- .../components/dashboard/navbar/search.tsx | 19 +- frontend/components/dashboard/newProject.tsx | 14 +- .../dashboard/projectCard/dropdown.tsx | 28 +- .../dashboard/projectCard/index.tsx | 10 +- .../dashboard/projectCard/revealEffect.tsx | 152 ++++---- frontend/components/dashboard/projects.tsx | 55 ++- frontend/components/dashboard/shared.tsx | 28 +- .../components/editor/AIChat/ChatInput.tsx | 43 ++- .../components/editor/AIChat/ChatMessage.tsx | 4 +- .../editor/AIChat/ContextDisplay.tsx | 3 +- frontend/components/editor/AIChat/index.tsx | 96 ++++-- .../components/editor/AIChat/lib/chatUtils.ts | 212 ++++++------ frontend/components/editor/generate.tsx | 12 +- frontend/components/editor/index.tsx | 326 ++++++++++-------- frontend/components/editor/live/avatars.tsx | 12 +- frontend/components/editor/live/cursors.tsx | 5 +- .../components/editor/live/disableModal.tsx | 36 +- frontend/components/editor/live/room.tsx | 11 +- frontend/components/editor/loading/index.tsx | 6 +- frontend/components/editor/navbar/deploy.tsx | 73 ++-- frontend/components/editor/navbar/edit.tsx | 65 ++-- frontend/components/editor/navbar/index.tsx | 59 ++-- frontend/components/editor/navbar/run.tsx | 95 ++--- frontend/components/editor/navbar/share.tsx | 12 +- frontend/components/editor/preview/index.tsx | 94 ++--- frontend/components/editor/sidebar/file.tsx | 84 ++--- frontend/components/editor/sidebar/folder.tsx | 16 +- frontend/components/editor/sidebar/index.tsx | 112 +++--- frontend/components/editor/sidebar/new.tsx | 44 +-- .../components/editor/terminals/index.tsx | 43 ++- .../components/editor/terminals/terminal.tsx | 86 ++--- .../components/editor/terminals/xterm.css | 62 ++-- frontend/components/landing/index.tsx | 2 +- frontend/components/layout/themeProvider.tsx | 1 - frontend/components/ui/LoadingDots.tsx | 35 +- frontend/components/ui/alert-dialog.tsx | 20 +- frontend/components/ui/button.tsx | 2 +- frontend/components/ui/card.tsx | 2 +- frontend/components/ui/context-menu.tsx | 16 +- frontend/components/ui/customButton.tsx | 3 +- frontend/components/ui/dialog.tsx | 60 ++-- frontend/components/ui/dropdown-menu.tsx | 16 +- frontend/components/ui/form.tsx | 12 +- frontend/components/ui/label.tsx | 2 +- frontend/components/ui/popover.tsx | 4 +- frontend/components/ui/resizable.tsx | 2 +- frontend/components/ui/select.tsx | 14 +- frontend/components/ui/switch.tsx | 2 +- frontend/components/ui/tab.tsx | 30 +- frontend/components/ui/table.tsx | 6 +- frontend/components/ui/userButton.tsx | 21 +- frontend/context/PreviewContext.tsx | 48 ++- frontend/context/SocketContext.tsx | 76 ++-- frontend/context/TerminalContext.tsx | 95 ++--- frontend/lib/terminal.ts | 97 +++--- frontend/lib/types.ts | 92 ++--- frontend/lib/utils.ts | 2 +- 64 files changed, 1421 insertions(+), 1272 deletions(-) diff --git a/frontend/app/(app)/code/[id]/page.tsx b/frontend/app/(app)/code/[id]/page.tsx index 9681a5b..d193d39 100644 --- a/frontend/app/(app)/code/[id]/page.tsx +++ b/frontend/app/(app)/code/[id]/page.tsx @@ -1,12 +1,11 @@ -import Navbar from "@/components/editor/navbar" import { Room } from "@/components/editor/live/room" +import Loading from "@/components/editor/loading" +import Navbar from "@/components/editor/navbar" +import { TerminalProvider } from "@/context/TerminalContext" import { Sandbox, User, UsersToSandboxes } from "@/lib/types" import { currentUser } from "@clerk/nextjs" -import { notFound, redirect } from "next/navigation" -import Loading from "@/components/editor/loading" import dynamic from "next/dynamic" -import fs from "fs" -import { TerminalProvider } from "@/context/TerminalContext" +import { notFound, redirect } from "next/navigation" export const revalidate = 0 @@ -89,19 +88,20 @@ export default async function CodePage({ params }: { params: { id: string } }) { return ( <> -
- - - -
- -
-
-
-
+
+ + + +
+ +
+
+
+
) } diff --git a/frontend/app/(app)/dashboard/page.tsx b/frontend/app/(app)/dashboard/page.tsx index 1f29f96..52f8c3f 100644 --- a/frontend/app/(app)/dashboard/page.tsx +++ b/frontend/app/(app)/dashboard/page.tsx @@ -1,8 +1,8 @@ -import { UserButton, currentUser } from "@clerk/nextjs" -import { redirect } from "next/navigation" import Dashboard from "@/components/dashboard" import Navbar from "@/components/dashboard/navbar" -import { Sandbox, User } from "@/lib/types" +import { User } from "@/lib/types" +import { currentUser } from "@clerk/nextjs" +import { redirect } from "next/navigation" export default async function DashboardPage() { const user = await currentUser() diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx index 70f5791..9448c07 100644 --- a/frontend/app/layout.tsx +++ b/frontend/app/layout.tsx @@ -1,13 +1,13 @@ -import type { Metadata } from "next" -import { GeistSans } from "geist/font/sans" -import { GeistMono } from "geist/font/mono" -import "./globals.css" import { ThemeProvider } from "@/components/layout/themeProvider" -import { ClerkProvider } from "@clerk/nextjs" import { Toaster } from "@/components/ui/sonner" +import { PreviewProvider } from "@/context/PreviewContext" +import { SocketProvider } from "@/context/SocketContext" +import { ClerkProvider } from "@clerk/nextjs" import { Analytics } from "@vercel/analytics/react" -import { PreviewProvider } from "@/context/PreviewContext"; -import { SocketProvider } from '@/context/SocketContext' +import { GeistMono } from "geist/font/mono" +import { GeistSans } from "geist/font/sans" +import type { Metadata } from "next" +import "./globals.css" export const metadata: Metadata = { title: "Sandbox", @@ -15,7 +15,7 @@ export const metadata: Metadata = { } export default function RootLayout({ - children + children, }: Readonly<{ children: React.ReactNode }>) { @@ -30,9 +30,7 @@ export default function RootLayout({ disableTransitionOnChange > - - {children} - + {children} @@ -41,4 +39,4 @@ export default function RootLayout({ ) -} \ No newline at end of file +} diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index 3f99044..8041367 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -1,13 +1,13 @@ -import { currentUser } from "@clerk/nextjs"; -import { redirect } from "next/navigation"; -import Landing from "@/components/landing"; +import Landing from "@/components/landing" +import { currentUser } from "@clerk/nextjs" +import { redirect } from "next/navigation" export default async function Home() { - const user = await currentUser(); + const user = await currentUser() if (user) { - redirect("/dashboard"); + redirect("/dashboard") } - return ; + return } diff --git a/frontend/components/dashboard/about.tsx b/frontend/components/dashboard/about.tsx index 33b0daa..7accef0 100644 --- a/frontend/components/dashboard/about.tsx +++ b/frontend/components/dashboard/about.tsx @@ -3,16 +3,9 @@ import { Dialog, DialogContent, - DialogDescription, DialogHeader, DialogTitle, - DialogTrigger, } from "@/components/ui/dialog" -import Image from "next/image" -import { useState } from "react" - -import { Button } from "../ui/button" -import { ChevronRight } from "lucide-react" export default function AboutModal({ open, diff --git a/frontend/components/dashboard/index.tsx b/frontend/components/dashboard/index.tsx index a7094ec..a1599c7 100644 --- a/frontend/components/dashboard/index.tsx +++ b/frontend/components/dashboard/index.tsx @@ -1,24 +1,16 @@ "use client" -import CustomButton from "@/components/ui/customButton" import { Button } from "@/components/ui/button" -import { - Code2, - FolderDot, - HelpCircle, - Plus, - Settings, - Users, -} from "lucide-react" -import { useEffect, useState } from "react" +import CustomButton from "@/components/ui/customButton" import { Sandbox } from "@/lib/types" +import { Code2, FolderDot, HelpCircle, Plus, Users } from "lucide-react" +import { useRouter, useSearchParams } from "next/navigation" +import { useEffect, useState } from "react" +import { toast } from "sonner" +import AboutModal from "./about" +import NewProjectModal from "./newProject" import DashboardProjects from "./projects" import DashboardSharedWithMe from "./shared" -import NewProjectModal from "./newProject" -import Link from "next/link" -import { useRouter, useSearchParams } from "next/navigation" -import AboutModal from "./about" -import { toast } from "sonner" type TScreen = "projects" | "shared" | "settings" | "search" @@ -49,8 +41,9 @@ export default function Dashboard({ const q = searchParams.get("q") const router = useRouter() - useEffect(() => { // update the dashboard to show a new project - router.refresh() + useEffect(() => { + // update the dashboard to show a new project + router.refresh() }, []) return ( diff --git a/frontend/components/dashboard/navbar/index.tsx b/frontend/components/dashboard/navbar/index.tsx index 8b1f02e..2f983af 100644 --- a/frontend/components/dashboard/navbar/index.tsx +++ b/frontend/components/dashboard/navbar/index.tsx @@ -1,9 +1,9 @@ +import Logo from "@/assets/logo.svg" +import { User } from "@/lib/types" import Image from "next/image" import Link from "next/link" -import Logo from "@/assets/logo.svg" -import DashboardNavbarSearch from "./search" import UserButton from "../../ui/userButton" -import { User } from "@/lib/types" +import DashboardNavbarSearch from "./search" export default function DashboardNavbar({ userData }: { userData: User }) { return ( diff --git a/frontend/components/dashboard/navbar/search.tsx b/frontend/components/dashboard/navbar/search.tsx index f254efe..75f314e 100644 --- a/frontend/components/dashboard/navbar/search.tsx +++ b/frontend/components/dashboard/navbar/search.tsx @@ -1,13 +1,12 @@ -"use client"; +"use client" -import { Input } from "../../ui/input"; -import { Search } from "lucide-react"; -import { useEffect, useState } from "react"; -import { useRouter } from "next/navigation"; +import { Search } from "lucide-react" +import { useRouter } from "next/navigation" +import { Input } from "../../ui/input" export default function DashboardNavbarSearch() { // const [search, setSearch] = useState(""); - const router = useRouter(); + const router = useRouter() // useEffect(() => { // const delayDebounceFn = setTimeout(() => { @@ -29,14 +28,14 @@ export default function DashboardNavbarSearch() { // onChange={(e) => setSearch(e.target.value)} onChange={(e) => { if (e.target.value === "") { - router.push(`/dashboard`); - return; + router.push(`/dashboard`) + return } - router.push(`/dashboard?q=${e.target.value}`); + router.push(`/dashboard?q=${e.target.value}`) }} placeholder="Search projects..." className="pl-8" /> - ); + ) } diff --git a/frontend/components/dashboard/newProject.tsx b/frontend/components/dashboard/newProject.tsx index 334d232..0248058 100644 --- a/frontend/components/dashboard/newProject.tsx +++ b/frontend/components/dashboard/newProject.tsx @@ -3,16 +3,14 @@ import { Dialog, DialogContent, - DialogDescription, DialogHeader, DialogTitle, - DialogTrigger, } from "@/components/ui/dialog" +import { zodResolver } from "@hookform/resolvers/zod" import Image from "next/image" import { useState } from "react" -import { set, z } from "zod" -import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" +import { z } from "zod" import { Form, @@ -31,12 +29,12 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select" -import { useUser } from "@clerk/nextjs" import { createSandbox } from "@/lib/actions" -import { useRouter } from "next/navigation" -import { Loader2 } from "lucide-react" -import { Button } from "../ui/button" import { projectTemplates } from "@/lib/data" +import { useUser } from "@clerk/nextjs" +import { Loader2 } from "lucide-react" +import { useRouter } from "next/navigation" +import { Button } from "../ui/button" const formSchema = z.object({ name: z diff --git a/frontend/components/dashboard/projectCard/dropdown.tsx b/frontend/components/dashboard/projectCard/dropdown.tsx index 24a93f8..522d5bc 100644 --- a/frontend/components/dashboard/projectCard/dropdown.tsx +++ b/frontend/components/dashboard/projectCard/dropdown.tsx @@ -1,30 +1,30 @@ -"use client"; +"use client" -import { Sandbox } from "@/lib/types"; -import { Ellipsis, Globe, Lock, Trash2 } from "lucide-react"; +import { Sandbox } from "@/lib/types" +import { Ellipsis, Globe, Lock, Trash2 } from "lucide-react" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; +} from "@/components/ui/dropdown-menu" export default function ProjectCardDropdown({ sandbox, onVisibilityChange, onDelete, }: { - sandbox: Sandbox; - onVisibilityChange: (sandbox: Sandbox) => void; - onDelete: (sandbox: Sandbox) => void; + sandbox: Sandbox + onVisibilityChange: (sandbox: Sandbox) => void + onDelete: (sandbox: Sandbox) => void }) { return ( { - e.preventDefault(); - e.stopPropagation(); + e.preventDefault() + e.stopPropagation() }} className="h-6 w-6 flex items-center justify-center transition-colors bg-transparent hover:bg-muted-foreground/25 rounded-sm outline-foreground" > @@ -33,8 +33,8 @@ export default function ProjectCardDropdown({ { - e.stopPropagation(); - onVisibilityChange(sandbox); + e.stopPropagation() + onVisibilityChange(sandbox) }} className="cursor-pointer" > @@ -52,8 +52,8 @@ export default function ProjectCardDropdown({ { - e.stopPropagation(); - onDelete(sandbox); + e.stopPropagation() + onDelete(sandbox) }} className="!text-destructive cursor-pointer" > @@ -62,5 +62,5 @@ export default function ProjectCardDropdown({ - ); + ) } diff --git a/frontend/components/dashboard/projectCard/index.tsx b/frontend/components/dashboard/projectCard/index.tsx index 9cc3729..a463e84 100644 --- a/frontend/components/dashboard/projectCard/index.tsx +++ b/frontend/components/dashboard/projectCard/index.tsx @@ -1,14 +1,14 @@ "use client" +import { Card } from "@/components/ui/card" +import { projectTemplates } from "@/lib/data" +import { Sandbox } from "@/lib/types" import { AnimatePresence, motion } from "framer-motion" +import { Clock, Globe, Lock } from "lucide-react" import Image from "next/image" +import { useRouter } from "next/navigation" import { useEffect, useState } from "react" import ProjectCardDropdown from "./dropdown" -import { Clock, Globe, Lock } from "lucide-react" -import { Sandbox } from "@/lib/types" -import { Card } from "@/components/ui/card" -import { useRouter } from "next/navigation" -import { projectTemplates } from "@/lib/data" export default function ProjectCard({ children, diff --git a/frontend/components/dashboard/projectCard/revealEffect.tsx b/frontend/components/dashboard/projectCard/revealEffect.tsx index 3786394..fcda16c 100644 --- a/frontend/components/dashboard/projectCard/revealEffect.tsx +++ b/frontend/components/dashboard/projectCard/revealEffect.tsx @@ -1,8 +1,8 @@ -"use client"; -import { cn } from "@/lib/utils"; -import { Canvas, useFrame, useThree } from "@react-three/fiber"; -import React, { useMemo, useRef } from "react"; -import * as THREE from "three"; +"use client" +import { cn } from "@/lib/utils" +import { Canvas, useFrame, useThree } from "@react-three/fiber" +import React, { useMemo, useRef } from "react" +import * as THREE from "three" export const CanvasRevealEffect = ({ animationSpeed = 0.4, @@ -12,12 +12,12 @@ export const CanvasRevealEffect = ({ dotSize, showGradient = true, }: { - animationSpeed?: number; - opacities?: number[]; - colors?: number[][]; - containerClassName?: string; - dotSize?: number; - showGradient?: boolean; + animationSpeed?: number + opacities?: number[] + colors?: number[][] + containerClassName?: string + dotSize?: number + showGradient?: boolean }) => { return (
@@ -41,16 +41,16 @@ export const CanvasRevealEffect = ({
)}
- ); -}; + ) +} interface DotMatrixProps { - colors?: number[][]; - opacities?: number[]; - totalSize?: number; - dotSize?: number; - shader?: string; - center?: ("x" | "y")[]; + colors?: number[][] + opacities?: number[] + totalSize?: number + dotSize?: number + shader?: string + center?: ("x" | "y")[] } const DotMatrix: React.FC = ({ @@ -69,7 +69,7 @@ const DotMatrix: React.FC = ({ colors[0], colors[0], colors[0], - ]; + ] if (colors.length === 2) { colorsArray = [ colors[0], @@ -78,7 +78,7 @@ const DotMatrix: React.FC = ({ colors[1], colors[1], colors[1], - ]; + ] } else if (colors.length === 3) { colorsArray = [ colors[0], @@ -87,7 +87,7 @@ const DotMatrix: React.FC = ({ colors[1], colors[2], colors[2], - ]; + ] } return { @@ -111,8 +111,8 @@ const DotMatrix: React.FC = ({ value: dotSize, type: "uniform1f", }, - }; - }, [colors, opacities, totalSize, dotSize]); + } + }, [colors, opacities, totalSize, dotSize]) return ( = ({ uniforms={uniforms} maxFps={60} /> - ); -}; + ) +} type Uniforms = { [key: string]: { - value: number[] | number[][] | number; - type: string; - }; -}; + value: number[] | number[][] | number + type: string + } +} const ShaderMaterial = ({ source, uniforms, maxFps = 60, }: { - source: string; - hovered?: boolean; - maxFps?: number; - uniforms: Uniforms; + source: string + hovered?: boolean + maxFps?: number + uniforms: Uniforms }) => { - const { size } = useThree(); - const ref = useRef(); - let lastFrameTime = 0; + const { size } = useThree() + const ref = useRef() + let lastFrameTime = 0 useFrame(({ clock }) => { - if (!ref.current) return; - const timestamp = clock.getElapsedTime(); + if (!ref.current) return + const timestamp = clock.getElapsedTime() if (timestamp - lastFrameTime < 1 / maxFps) { - return; + return } - lastFrameTime = timestamp; + lastFrameTime = timestamp - const material: any = ref.current.material; - const timeLocation = material.uniforms.u_time; - timeLocation.value = timestamp; - }); + const material: any = ref.current.material + const timeLocation = material.uniforms.u_time + timeLocation.value = timestamp + }) const getUniforms = () => { - const preparedUniforms: any = {}; + const preparedUniforms: any = {} for (const uniformName in uniforms) { - const uniform: any = uniforms[uniformName]; + const uniform: any = uniforms[uniformName] switch (uniform.type) { case "uniform1f": - preparedUniforms[uniformName] = { value: uniform.value, type: "1f" }; - break; + preparedUniforms[uniformName] = { value: uniform.value, type: "1f" } + break case "uniform3f": preparedUniforms[uniformName] = { value: new THREE.Vector3().fromArray(uniform.value), type: "3f", - }; - break; + } + break case "uniform1fv": - preparedUniforms[uniformName] = { value: uniform.value, type: "1fv" }; - break; + preparedUniforms[uniformName] = { value: uniform.value, type: "1fv" } + break case "uniform3fv": preparedUniforms[uniformName] = { value: uniform.value.map((v: number[]) => new THREE.Vector3().fromArray(v) ), type: "3fv", - }; - break; + } + break case "uniform2f": preparedUniforms[uniformName] = { value: new THREE.Vector2().fromArray(uniform.value), type: "2f", - }; - break; + } + break default: - console.error(`Invalid uniform type for '${uniformName}'.`); - break; + console.error(`Invalid uniform type for '${uniformName}'.`) + break } } - preparedUniforms["u_time"] = { value: 0, type: "1f" }; + preparedUniforms["u_time"] = { value: 0, type: "1f" } preparedUniforms["u_resolution"] = { value: new THREE.Vector2(size.width * 2, size.height * 2), - }; // Initialize u_resolution - return preparedUniforms; - }; + } // Initialize u_resolution + return preparedUniforms + } // Shader material const material = useMemo(() => { @@ -272,33 +272,33 @@ const ShaderMaterial = ({ blending: THREE.CustomBlending, blendSrc: THREE.SrcAlphaFactor, blendDst: THREE.OneFactor, - }); + }) - return materialObject; - }, [size.width, size.height, source]); + return materialObject + }, [size.width, size.height, source]) return ( - ); -}; + ) +} const Shader: React.FC = ({ source, uniforms, maxFps = 60 }) => { return ( - ); -}; + ) +} interface ShaderProps { - source: string; + source: string uniforms: { [key: string]: { - value: number[] | number[][] | number; - type: string; - }; - }; - maxFps?: number; + value: number[] | number[][] | number + type: string + } + } + maxFps?: number } diff --git a/frontend/components/dashboard/projects.tsx b/frontend/components/dashboard/projects.tsx index 9953e19..7c9705d 100644 --- a/frontend/components/dashboard/projects.tsx +++ b/frontend/components/dashboard/projects.tsx @@ -1,16 +1,12 @@ -"use client"; +"use client" -import { Sandbox } from "@/lib/types"; -import ProjectCard from "./projectCard"; -import Image from "next/image"; -import ProjectCardDropdown from "./projectCard/dropdown"; -import { Clock, Globe, Lock } from "lucide-react"; -import Link from "next/link"; -import { Card } from "../ui/card"; -import { deleteSandbox, updateSandbox } from "@/lib/actions"; -import { toast } from "sonner"; -import { useEffect, useState } from "react"; -import { CanvasRevealEffect } from "./projectCard/revealEffect"; +import { deleteSandbox, updateSandbox } from "@/lib/actions" +import { Sandbox } from "@/lib/types" +import Link from "next/link" +import { useEffect, useState } from "react" +import { toast } from "sonner" +import ProjectCard from "./projectCard" +import { CanvasRevealEffect } from "./projectCard/revealEffect" const colors: { [key: string]: number[][] } = { react: [ @@ -21,38 +17,37 @@ const colors: { [key: string]: number[][] } = { [86, 184, 72], [59, 112, 52], ], -}; +} export default function DashboardProjects({ sandboxes, q, }: { - sandboxes: Sandbox[]; - q: string | null; + sandboxes: Sandbox[] + q: string | null }) { - const [deletingId, setDeletingId] = useState(""); + const [deletingId, setDeletingId] = useState("") const onDelete = async (sandbox: Sandbox) => { - setDeletingId(sandbox.id); - toast(`Project ${sandbox.name} deleted.`); - await deleteSandbox(sandbox.id); - }; + setDeletingId(sandbox.id) + toast(`Project ${sandbox.name} deleted.`) + await deleteSandbox(sandbox.id) + } useEffect(() => { if (deletingId) { - setDeletingId(""); + setDeletingId("") } - }, [sandboxes]); + }, [sandboxes]) const onVisibilityChange = async (sandbox: Sandbox) => { - const newVisibility = - sandbox.visibility === "public" ? "private" : "public"; - toast(`Project ${sandbox.name} is now ${newVisibility}.`); + const newVisibility = sandbox.visibility === "public" ? "private" : "public" + toast(`Project ${sandbox.name} is now ${newVisibility}.`) await updateSandbox({ id: sandbox.id, visibility: newVisibility, - }); - }; + }) + } return (
@@ -65,7 +60,7 @@ export default function DashboardProjects({ {sandboxes.map((sandbox) => { if (q && q.length > 0) { if (!sandbox.name.toLowerCase().includes(q.toLowerCase())) { - return null; + return null } } return ( @@ -93,7 +88,7 @@ export default function DashboardProjects({
- ); + ) })}
) : ( @@ -103,5 +98,5 @@ export default function DashboardProjects({ )}
- ); + ) } diff --git a/frontend/components/dashboard/shared.tsx b/frontend/components/dashboard/shared.tsx index 9e5eb03..d9d4d39 100644 --- a/frontend/components/dashboard/shared.tsx +++ b/frontend/components/dashboard/shared.tsx @@ -1,29 +1,27 @@ -import { Sandbox } from "@/lib/types"; import { Table, TableBody, - TableCaption, TableCell, TableHead, TableHeader, TableRow, -} from "@/components/ui/table"; -import Image from "next/image"; -import Button from "../ui/customButton"; -import { ChevronRight } from "lucide-react"; -import Avatar from "../ui/avatar"; -import Link from "next/link"; +} from "@/components/ui/table" +import { ChevronRight } from "lucide-react" +import Image from "next/image" +import Link from "next/link" +import Avatar from "../ui/avatar" +import Button from "../ui/customButton" export default function DashboardSharedWithMe({ shared, }: { shared: { - id: string; - name: string; - type: "react" | "node"; - author: string; - sharedOn: Date; - }[]; + id: string + name: string + type: "react" | "node" + author: string + sharedOn: Date + }[] }) { return (
@@ -86,5 +84,5 @@ export default function DashboardSharedWithMe({
)} - ); + ) } diff --git a/frontend/components/editor/AIChat/ChatInput.tsx b/frontend/components/editor/AIChat/ChatInput.tsx index a40e0b5..380b6a4 100644 --- a/frontend/components/editor/AIChat/ChatInput.tsx +++ b/frontend/components/editor/AIChat/ChatInput.tsx @@ -1,36 +1,51 @@ -import React from 'react'; -import { Button } from '../../ui/button'; -import { Send, StopCircle } from 'lucide-react'; +import { Send, StopCircle } from "lucide-react" +import { Button } from "../../ui/button" interface ChatInputProps { - input: string; - setInput: (input: string) => void; - isGenerating: boolean; - handleSend: () => void; - handleStopGeneration: () => void; + input: string + setInput: (input: string) => void + isGenerating: boolean + handleSend: () => void + handleStopGeneration: () => void } -export default function ChatInput({ input, setInput, isGenerating, handleSend, handleStopGeneration }: ChatInputProps) { +export default function ChatInput({ + input, + setInput, + isGenerating, + handleSend, + handleStopGeneration, +}: ChatInputProps) { return (
- setInput(e.target.value)} - onKeyPress={(e) => e.key === 'Enter' && !isGenerating && handleSend()} + onKeyPress={(e) => e.key === "Enter" && !isGenerating && handleSend()} className="flex-grow p-2 border rounded-lg min-w-0 bg-input" placeholder="Type your message..." disabled={isGenerating} /> {isGenerating ? ( - ) : ( - )}
- ); + ) } diff --git a/frontend/components/editor/AIChat/ChatMessage.tsx b/frontend/components/editor/AIChat/ChatMessage.tsx index 7eac365..7fd665f 100644 --- a/frontend/components/editor/AIChat/ChatMessage.tsx +++ b/frontend/components/editor/AIChat/ChatMessage.tsx @@ -1,10 +1,10 @@ +import { Check, ChevronDown, ChevronUp, Copy, CornerUpLeft } from 'lucide-react'; import React, { useState } from 'react'; -import { Button } from '../../ui/button'; -import { ChevronUp, ChevronDown, Copy, Check, CornerUpLeft } from 'lucide-react'; import ReactMarkdown from 'react-markdown'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; import remarkGfm from 'remark-gfm'; +import { Button } from '../../ui/button'; import { copyToClipboard, stringifyContent } from './lib/chatUtils'; interface MessageProps { diff --git a/frontend/components/editor/AIChat/ContextDisplay.tsx b/frontend/components/editor/AIChat/ContextDisplay.tsx index a0a9fa0..666d9ba 100644 --- a/frontend/components/editor/AIChat/ContextDisplay.tsx +++ b/frontend/components/editor/AIChat/ContextDisplay.tsx @@ -1,5 +1,4 @@ -import React from 'react'; -import { ChevronUp, ChevronDown, X } from 'lucide-react'; +import { ChevronDown, ChevronUp, X } from 'lucide-react'; interface ContextDisplayProps { context: string | null; diff --git a/frontend/components/editor/AIChat/index.tsx b/frontend/components/editor/AIChat/index.tsx index 316fa07..d3759c5 100644 --- a/frontend/components/editor/AIChat/index.tsx +++ b/frontend/components/editor/AIChat/index.tsx @@ -1,48 +1,58 @@ -import React, { useState, useEffect, useRef } from 'react'; -import LoadingDots from '../../ui/LoadingDots'; -import ChatMessage from './ChatMessage'; -import ChatInput from './ChatInput'; -import ContextDisplay from './ContextDisplay'; -import { handleSend, handleStopGeneration } from './lib/chatUtils'; -import { X } from 'lucide-react'; +import { X } from "lucide-react" +import { useEffect, useRef, useState } from "react" +import LoadingDots from "../../ui/LoadingDots" +import ChatInput from "./ChatInput" +import ChatMessage from "./ChatMessage" +import ContextDisplay from "./ContextDisplay" +import { handleSend, handleStopGeneration } from "./lib/chatUtils" interface Message { - role: 'user' | 'assistant'; - content: string; - context?: string; + role: "user" | "assistant" + content: string + context?: string } -export default function AIChat({ activeFileContent, activeFileName, onClose }: { activeFileContent: string, activeFileName: string, onClose: () => void }) { - const [messages, setMessages] = useState([]); - const [input, setInput] = useState(''); - const [isGenerating, setIsGenerating] = useState(false); - const chatContainerRef = useRef(null); - const abortControllerRef = useRef(null); - const [context, setContext] = useState(null); - const [isContextExpanded, setIsContextExpanded] = useState(false); - const [isLoading, setIsLoading] = useState(false); +export default function AIChat({ + activeFileContent, + activeFileName, + onClose, +}: { + activeFileContent: string + activeFileName: string + onClose: () => void +}) { + const [messages, setMessages] = useState([]) + const [input, setInput] = useState("") + const [isGenerating, setIsGenerating] = useState(false) + const chatContainerRef = useRef(null) + const abortControllerRef = useRef(null) + const [context, setContext] = useState(null) + const [isContextExpanded, setIsContextExpanded] = useState(false) + const [isLoading, setIsLoading] = useState(false) useEffect(() => { - scrollToBottom(); - }, [messages]); + scrollToBottom() + }, [messages]) const scrollToBottom = () => { if (chatContainerRef.current) { setTimeout(() => { chatContainerRef.current?.scrollTo({ top: chatContainerRef.current.scrollHeight, - behavior: 'smooth' - }); - }, 100); + behavior: "smooth", + }) + }, 100) } - }; + } return (
CHAT
- {activeFileName} + + {activeFileName} +
-
+
{messages.map((message, messageIndex) => ( - @@ -65,20 +78,33 @@ export default function AIChat({ activeFileContent, activeFileName, onClose }: { {isLoading && }
- - handleSend(input, context, messages, setMessages, setInput, setIsContextExpanded, setIsGenerating, setIsLoading, abortControllerRef, activeFileContent)} + handleSend={() => + handleSend( + input, + context, + messages, + setMessages, + setInput, + setIsContextExpanded, + setIsGenerating, + setIsLoading, + abortControllerRef, + activeFileContent + ) + } handleStopGeneration={() => handleStopGeneration(abortControllerRef)} />
- ); + ) } diff --git a/frontend/components/editor/AIChat/lib/chatUtils.ts b/frontend/components/editor/AIChat/lib/chatUtils.ts index 9a375c4..a1e0fda 100644 --- a/frontend/components/editor/AIChat/lib/chatUtils.ts +++ b/frontend/components/editor/AIChat/lib/chatUtils.ts @@ -1,58 +1,68 @@ -import React from 'react'; +import React from "react" -export const stringifyContent = (content: any, seen = new WeakSet()): string => { - if (typeof content === 'string') { - return content; +export const stringifyContent = ( + content: any, + seen = new WeakSet() +): string => { + if (typeof content === "string") { + return content } if (content === null) { - return 'null'; + return "null" } if (content === undefined) { - return 'undefined'; + return "undefined" } - if (typeof content === 'number' || typeof content === 'boolean') { - return content.toString(); + if (typeof content === "number" || typeof content === "boolean") { + return content.toString() } - if (typeof content === 'function') { - return content.toString(); + if (typeof content === "function") { + return content.toString() } - if (typeof content === 'symbol') { - return content.toString(); + if (typeof content === "symbol") { + return content.toString() } - if (typeof content === 'bigint') { - return content.toString() + 'n'; + if (typeof content === "bigint") { + return content.toString() + "n" } if (React.isValidElement(content)) { - return React.Children.toArray((content as React.ReactElement).props.children) - .map(child => stringifyContent(child, seen)) - .join(''); + return React.Children.toArray( + (content as React.ReactElement).props.children + ) + .map((child) => stringifyContent(child, seen)) + .join("") } if (Array.isArray(content)) { - return '[' + content.map(item => stringifyContent(item, seen)).join(', ') + ']'; + return ( + "[" + content.map((item) => stringifyContent(item, seen)).join(", ") + "]" + ) } - if (typeof content === 'object') { + if (typeof content === "object") { if (seen.has(content)) { - return '[Circular]'; + return "[Circular]" } - seen.add(content); + seen.add(content) try { const pairs = Object.entries(content).map( ([key, value]) => `${key}: ${stringifyContent(value, seen)}` - ); - return '{' + pairs.join(', ') + '}'; + ) + return "{" + pairs.join(", ") + "}" } catch (error) { - return Object.prototype.toString.call(content); + return Object.prototype.toString.call(content) } } - return String(content); -}; + return String(content) +} -export const copyToClipboard = (text: string, setCopiedText: (text: string | null) => void) => { +export const copyToClipboard = ( + text: string, + setCopiedText: (text: string | null) => void +) => { navigator.clipboard.writeText(text).then(() => { - setCopiedText(text); - setTimeout(() => setCopiedText(null), 2000); - }); -}; + setCopiedText(text) + setTimeout(() => setCopiedText(null), 2000) + }) +} export const handleSend = async ( input: string, @@ -66,97 +76,105 @@ export const handleSend = async ( abortControllerRef: React.MutableRefObject, activeFileContent: string ) => { - if (input.trim() === '' && !context) return; + if (input.trim() === "" && !context) return - const newMessage = { - role: 'user' as const, + const newMessage = { + role: "user" as const, content: input, - context: context || undefined - }; - const updatedMessages = [...messages, newMessage]; - setMessages(updatedMessages); - setInput(''); - setIsContextExpanded(false); - setIsGenerating(true); - setIsLoading(true); + context: context || undefined, + } + const updatedMessages = [...messages, newMessage] + setMessages(updatedMessages) + setInput("") + setIsContextExpanded(false) + setIsGenerating(true) + setIsLoading(true) - abortControllerRef.current = new AbortController(); + abortControllerRef.current = new AbortController() try { - const anthropicMessages = updatedMessages.map(msg => ({ - role: msg.role === 'user' ? 'human' : 'assistant', - content: msg.content - })); + const anthropicMessages = updatedMessages.map((msg) => ({ + role: msg.role === "user" ? "human" : "assistant", + content: msg.content, + })) - const response = await fetch(`${process.env.NEXT_PUBLIC_AI_WORKER_URL}/api`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - messages: anthropicMessages, - context: context || undefined, - activeFileContent: activeFileContent, - }), - signal: abortControllerRef.current.signal, - }); + const response = await fetch( + `${process.env.NEXT_PUBLIC_AI_WORKER_URL}/api`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + messages: anthropicMessages, + context: context || undefined, + activeFileContent: activeFileContent, + }), + signal: abortControllerRef.current.signal, + } + ) if (!response.ok) { - throw new Error('Failed to get AI response'); + throw new Error("Failed to get AI response") } - const reader = response.body?.getReader(); - const decoder = new TextDecoder(); - const assistantMessage = { role: 'assistant' as const, content: '' }; - setMessages([...updatedMessages, assistantMessage]); - setIsLoading(false); + const reader = response.body?.getReader() + const decoder = new TextDecoder() + const assistantMessage = { role: "assistant" as const, content: "" } + setMessages([...updatedMessages, assistantMessage]) + setIsLoading(false) - let buffer = ''; - const updateInterval = 100; - let lastUpdateTime = Date.now(); + let buffer = "" + const updateInterval = 100 + let lastUpdateTime = Date.now() if (reader) { while (true) { - const { done, value } = await reader.read(); - if (done) break; - buffer += decoder.decode(value, { stream: true }); + const { done, value } = await reader.read() + if (done) break + buffer += decoder.decode(value, { stream: true }) - const currentTime = Date.now(); + const currentTime = Date.now() if (currentTime - lastUpdateTime > updateInterval) { - setMessages(prev => { - const updatedMessages = [...prev]; - const lastMessage = updatedMessages[updatedMessages.length - 1]; - lastMessage.content = buffer; - return updatedMessages; - }); - lastUpdateTime = currentTime; + setMessages((prev) => { + const updatedMessages = [...prev] + const lastMessage = updatedMessages[updatedMessages.length - 1] + lastMessage.content = buffer + return updatedMessages + }) + lastUpdateTime = currentTime } } - setMessages(prev => { - const updatedMessages = [...prev]; - const lastMessage = updatedMessages[updatedMessages.length - 1]; - lastMessage.content = buffer; - return updatedMessages; - }); + setMessages((prev) => { + const updatedMessages = [...prev] + const lastMessage = updatedMessages[updatedMessages.length - 1] + lastMessage.content = buffer + return updatedMessages + }) } } catch (error: any) { - if (error.name === 'AbortError') { - console.log('Generation aborted'); + if (error.name === "AbortError") { + console.log("Generation aborted") } else { - console.error('Error fetching AI response:', error); - const errorMessage = { role: 'assistant' as const, content: 'Sorry, I encountered an error. Please try again.' }; - setMessages(prev => [...prev, errorMessage]); + console.error("Error fetching AI response:", error) + const errorMessage = { + role: "assistant" as const, + content: "Sorry, I encountered an error. Please try again.", + } + setMessages((prev) => [...prev, errorMessage]) } } finally { - setIsGenerating(false); - setIsLoading(false); - abortControllerRef.current = null; + setIsGenerating(false) + setIsLoading(false) + abortControllerRef.current = null } -}; +} -export const handleStopGeneration = (abortControllerRef: React.MutableRefObject) => { +export const handleStopGeneration = ( + abortControllerRef: React.MutableRefObject +) => { if (abortControllerRef.current) { - abortControllerRef.current.abort(); + abortControllerRef.current.abort() } -}; +} diff --git a/frontend/components/editor/generate.tsx b/frontend/components/editor/generate.tsx index a77f2bd..9e4bd09 100644 --- a/frontend/components/editor/generate.tsx +++ b/frontend/components/editor/generate.tsx @@ -1,13 +1,13 @@ "use client" -import { useCallback, useEffect, useRef, useState } from "react" -import { Button } from "../ui/button" -import { Check, Loader2, RotateCw, Sparkles, X } from "lucide-react" -import { Socket } from "socket.io-client" -import { Editor } from "@monaco-editor/react" import { User } from "@/lib/types" -import { toast } from "sonner" +import { Editor } from "@monaco-editor/react" +import { Check, Loader2, RotateCw, Sparkles, X } from "lucide-react" import { usePathname, useRouter } from "next/navigation" +import { useCallback, useEffect, useRef, useState } from "react" +import { Socket } from "socket.io-client" +import { toast } from "sonner" +import { Button } from "../ui/button" // import monaco from "monaco-editor" export default function GenerateInput({ diff --git a/frontend/components/editor/index.tsx b/frontend/components/editor/index.tsx index 68a3ed5..9a95fcb 100644 --- a/frontend/components/editor/index.tsx +++ b/frontend/components/editor/index.tsx @@ -1,43 +1,55 @@ "use client" -import { SetStateAction, useCallback, useEffect, useRef, useState } from "react" -import * as monaco from "monaco-editor" -import Editor, { BeforeMount, OnMount } from "@monaco-editor/react" -import { toast } from "sonner" import { useClerk } from "@clerk/nextjs" +import Editor, { BeforeMount, OnMount } from "@monaco-editor/react" import { AnimatePresence, motion } from "framer-motion" +import * as monaco from "monaco-editor" +import { useCallback, useEffect, useRef, useState } from "react" +import { toast } from "sonner" -import * as Y from "yjs" +import { TypedLiveblocksProvider, useRoom, useSelf } from "@/liveblocks.config" import LiveblocksProvider from "@liveblocks/yjs" import { MonacoBinding } from "y-monaco" import { Awareness } from "y-protocols/awareness" -import { TypedLiveblocksProvider, useRoom, useSelf } from "@/liveblocks.config" +import * as Y from "yjs" import { ResizableHandle, ResizablePanel, ResizablePanelGroup, } from "@/components/ui/resizable" -import { FileJson, Loader2, Sparkles, TerminalSquare, ArrowDownToLine, ArrowRightToLine } from "lucide-react" -import Tab from "../ui/tab" -import Sidebar from "./sidebar" -import GenerateInput from "./generate" -import { Sandbox, User, TFile, TFolder, TTab } from "@/lib/types" -import { addNew, processFileType, validateName, debounce } from "@/lib/utils" -import { Cursors } from "./live/cursors" +import { PreviewProvider, usePreview } from "@/context/PreviewContext" +import { useSocket } from "@/context/SocketContext" +import { parseTSConfigToMonacoOptions } from "@/lib/tsconfig" +import { Sandbox, TFile, TFolder, TTab, User } from "@/lib/types" +import { + addNew, + debounce, + deepMerge, + processFileType, + validateName, +} from "@/lib/utils" import { Terminal } from "@xterm/xterm" +import { + ArrowDownToLine, + ArrowRightToLine, + FileJson, + Loader2, + Sparkles, + TerminalSquare, +} from "lucide-react" +import React from "react" +import { ImperativePanelHandle } from "react-resizable-panels" +import { Button } from "../ui/button" +import Tab from "../ui/tab" +import AIChat from "./AIChat" +import GenerateInput from "./generate" +import { Cursors } from "./live/cursors" import DisableAccessModal from "./live/disableModal" import Loading from "./loading" import PreviewWindow from "./preview" +import Sidebar from "./sidebar" import Terminals from "./terminals" -import { ImperativePanelHandle } from "react-resizable-panels" -import { PreviewProvider, usePreview } from "@/context/PreviewContext" -import { useSocket } from "@/context/SocketContext" -import { Button } from "../ui/button" -import React from "react" -import { parseTSConfigToMonacoOptions } from "@/lib/tsconfig" -import { deepMerge } from "@/lib/utils" -import AIChat from "./AIChat" export default function CodeEditor({ userData, @@ -63,9 +75,9 @@ export default function CodeEditor({ // This heartbeat is critical to preventing the E2B sandbox from timing out useEffect(() => { // 10000 ms = 10 seconds - const interval = setInterval(() => socket?.emit("heartbeat"), 10000); - return () => clearInterval(interval); - }, [socket]); + const interval = setInterval(() => socket?.emit("heartbeat"), 10000) + return () => clearInterval(interval) + }, [socket]) //Preview Button state const [isPreviewCollapsed, setIsPreviewCollapsed] = useState(true) @@ -75,11 +87,11 @@ export default function CodeEditor({ }) // Layout state - const [isHorizontalLayout, setIsHorizontalLayout] = useState(false); - const [previousLayout, setPreviousLayout] = useState(false); + const [isHorizontalLayout, setIsHorizontalLayout] = useState(false) + const [previousLayout, setPreviousLayout] = useState(false) // AI Chat state - const [isAIChatOpen, setIsAIChatOpen] = useState(false); + const [isAIChatOpen, setIsAIChatOpen] = useState(false) // File state const [files, setFiles] = useState<(TFolder | TFile)[]>([]) @@ -88,7 +100,7 @@ export default function CodeEditor({ const [activeFileContent, setActiveFileContent] = useState("") const [deletingFolderId, setDeletingFolderId] = useState("") // Added this state to track the most recent content for each file - const [fileContents, setFileContents] = useState>({}); + const [fileContents, setFileContents] = useState>({}) // Editor state const [editorLanguage, setEditorLanguage] = useState("plaintext") @@ -153,7 +165,7 @@ export default function CodeEditor({ const generateRef = useRef(null) const suggestionRef = useRef(null) const generateWidgetRef = useRef(null) - const { previewPanelRef } = usePreview(); + const { previewPanelRef } = usePreview() const editorPanelRef = useRef(null) const previewWindowRef = useRef<{ refreshIframe: () => void }>(null) @@ -470,16 +482,17 @@ export default function CodeEditor({ const model = editorRef?.getModel() // added this because it was giving client side exception - Illegal value for lineNumber when opening an empty file if (model) { - const totalLines = model.getLineCount(); + const totalLines = model.getLineCount() // Check if the cursorLine is a valid number, If cursorLine is out of bounds, we fall back to 1 (the first line) as a default safe value. - const lineNumber = cursorLine > 0 && cursorLine <= totalLines ? cursorLine : 1; // fallback to a valid line number + const lineNumber = + cursorLine > 0 && cursorLine <= totalLines ? cursorLine : 1 // fallback to a valid line number // If for some reason the content doesn't exist, we use an empty string as a fallback. - const line = model.getLineContent(lineNumber) ?? ""; + const line = model.getLineContent(lineNumber) ?? "" // Check if the line is not empty or only whitespace (i.e., `.trim()` removes spaces). // If the line has content, we clear any decorations using the instance of the `decorations` object. // Decorations refer to editor highlights, underlines, or markers, so this clears those if conditions are met. if (line.trim() !== "") { - decorations.instance?.clear(); + decorations.instance?.clear() return } } @@ -505,40 +518,40 @@ export default function CodeEditor({ debounce((activeFileId: string | undefined) => { if (activeFileId) { // Get the current content of the file - const content = fileContents[activeFileId]; + const content = fileContents[activeFileId] // Mark the file as saved in the tabs setTabs((prev) => prev.map((tab) => tab.id === activeFileId ? { ...tab, saved: true } : tab ) - ); - console.log(`Saving file...${activeFileId}`); - console.log(`Saving file...${content}`); - socket?.emit("saveFile", activeFileId, content); + ) + console.log(`Saving file...${activeFileId}`) + console.log(`Saving file...${content}`) + socket?.emit("saveFile", activeFileId, content) } }, Number(process.env.FILE_SAVE_DEBOUNCE_DELAY) || 1000), [socket, fileContents] - ); + ) // Keydown event listener to trigger file save on Ctrl+S or Cmd+S, and toggle AI chat on Ctrl+L or Cmd+L useEffect(() => { const down = (e: KeyboardEvent) => { if (e.key === "s" && (e.metaKey || e.ctrlKey)) { e.preventDefault() - debouncedSaveData(activeFileId); + debouncedSaveData(activeFileId) } else if (e.key === "l" && (e.metaKey || e.ctrlKey)) { e.preventDefault() - setIsAIChatOpen(prev => !prev); + setIsAIChatOpen((prev) => !prev) } - }; + } - document.addEventListener("keydown", down); + document.addEventListener("keydown", down) // Added this line to prevent Monaco editor from handling Cmd/Ctrl+L editorRef?.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyL, () => { - setIsAIChatOpen(prev => !prev); - }); + setIsAIChatOpen((prev) => !prev) + }) return () => { document.removeEventListener("keydown", down) @@ -632,7 +645,7 @@ export default function CodeEditor({ // Socket event listener effect useEffect(() => { - const onConnect = () => { } + const onConnect = () => {} const onDisconnect = () => { setTerminals([]) @@ -702,46 +715,49 @@ export default function CodeEditor({ } // 300ms debounce delay, adjust as needed const selectFile = (tab: TTab) => { - if (tab.id === activeFileId) return; + if (tab.id === activeFileId) return - setGenerate((prev) => ({ ...prev, show: false })); + setGenerate((prev) => ({ ...prev, show: false })) // Check if the tab already exists in the list of open tabs - const exists = tabs.find((t) => t.id === tab.id); + const exists = tabs.find((t) => t.id === tab.id) setTabs((prev) => { if (exists) { // If the tab exists, make it the active tab - setActiveFileId(exists.id); - return prev; + setActiveFileId(exists.id) + return prev } // If the tab doesn't exist, add it to the list of tabs and make it active - return [...prev, tab]; - }); + return [...prev, tab] + }) // If the file's content is already cached, set it as the active content if (fileContents[tab.id]) { - setActiveFileContent(fileContents[tab.id]); + setActiveFileContent(fileContents[tab.id]) } else { // Otherwise, fetch the content of the file and cache it debouncedGetFile(tab.id, (response: string) => { - setFileContents(prev => ({ ...prev, [tab.id]: response })); - setActiveFileContent(response); - }); + setFileContents((prev) => ({ ...prev, [tab.id]: response })) + setActiveFileContent(response) + }) } // Set the editor language based on the file type - setEditorLanguage(processFileType(tab.name)); + setEditorLanguage(processFileType(tab.name)) // Set the active file ID to the new tab - setActiveFileId(tab.id); - }; + setActiveFileId(tab.id) + } // Added this effect to update fileContents when the editor content changes useEffect(() => { if (activeFileId) { // Cache the current active file content using the file ID as the key - setFileContents(prev => ({ ...prev, [activeFileId]: activeFileContent })); + setFileContents((prev) => ({ + ...prev, + [activeFileId]: activeFileContent, + })) } - }, [activeFileContent, activeFileId]); + }, [activeFileContent, activeFileId]) // Close tab and remove from tabs const closeTab = (id: string) => { @@ -757,8 +773,8 @@ export default function CodeEditor({ ? numTabs === 1 ? null : index < numTabs - 1 - ? tabs[index + 1].id - : tabs[index - 1].id + ? tabs[index + 1].id + : tabs[index - 1].id : activeFileId setTabs((prev) => prev.filter((t) => t.id !== id)) @@ -846,34 +862,34 @@ export default function CodeEditor({ const togglePreviewPanel = () => { if (isPreviewCollapsed) { - previewPanelRef.current?.expand(); - setIsPreviewCollapsed(false); + previewPanelRef.current?.expand() + setIsPreviewCollapsed(false) } else { - previewPanelRef.current?.collapse(); - setIsPreviewCollapsed(true); + previewPanelRef.current?.collapse() + setIsPreviewCollapsed(true) } - }; + } const toggleLayout = () => { if (!isAIChatOpen) { - setIsHorizontalLayout(prev => !prev); + setIsHorizontalLayout((prev) => !prev) } - }; + } // Add an effect to handle layout changes when AI chat is opened/closed useEffect(() => { if (isAIChatOpen) { - setPreviousLayout(isHorizontalLayout); - setIsHorizontalLayout(true); + setPreviousLayout(isHorizontalLayout) + setIsHorizontalLayout(true) } else { - setIsHorizontalLayout(previousLayout); + setIsHorizontalLayout(previousLayout) } - }, [isAIChatOpen]); + }, [isAIChatOpen]) // Modify the toggleAIChat function const toggleAIChat = () => { - setIsAIChatOpen(prev => !prev); - }; + setIsAIChatOpen((prev) => !prev) + } // On disabled access for shared users, show un-interactable loading placeholder + info modal if (disableAccess.isDisabled) @@ -882,7 +898,7 @@ export default function CodeEditor({ { }} + setOpen={() => {}} /> @@ -921,8 +937,8 @@ export default function CodeEditor({ code: (isSelected && editorRef?.getSelection() ? editorRef - ?.getModel() - ?.getValueInRange(editorRef?.getSelection()!) + ?.getModel() + ?.getValueInRange(editorRef?.getSelection()!) : editorRef?.getValue()) ?? "", line: generate.line, }} @@ -1008,10 +1024,14 @@ export default function CodeEditor({ deletingFolderId={deletingFolderId} /> {/* Outer ResizablePanelGroup for main layout */} - + {/* Left side: Editor and Preview/Terminal */} - + ) : // Note clerk.loaded is required here due to a bug: https://github.com/clerk/javascript/issues/1643 - clerk.loaded ? ( - <> - {provider && userInfo ? ( - - ) : null} - { - // If the new content is different from the cached content, update it - if (value !== fileContents[activeFileId]) { - setActiveFileContent(value ?? ""); // Update the active file content - // Mark the file as unsaved by setting 'saved' to false - setTabs((prev) => - prev.map((tab) => - tab.id === activeFileId - ? { ...tab, saved: false } - : tab - ) + clerk.loaded ? ( + <> + {provider && userInfo ? ( + + ) : null} + { + // If the new content is different from the cached content, update it + if (value !== fileContents[activeFileId]) { + setActiveFileContent(value ?? "") // Update the active file content + // Mark the file as unsaved by setting 'saved' to false + setTabs((prev) => + prev.map((tab) => + tab.id === activeFileId + ? { ...tab, saved: false } + : tab ) - } else { - // If the content matches the cached content, mark the file as saved - setTabs((prev) => - prev.map((tab) => - tab.id === activeFileId - ? { ...tab, saved: true } - : tab - ) + ) + } else { + // If the content matches the cached content, mark the file as saved + setTabs((prev) => + prev.map((tab) => + tab.id === activeFileId + ? { ...tab, saved: true } + : tab ) - } - }} - options={{ - tabSize: 2, - minimap: { - enabled: false, - }, - padding: { - bottom: 4, - top: 4, - }, - scrollBeyondLastLine: false, - fixedOverflowWidgets: true, - fontFamily: "var(--font-geist-mono)", - }} - theme="vs-dark" - value={activeFileContent} - /> - - ) : ( -
- - Waiting for Clerk to load... -
- )} + ) + } + }} + options={{ + tabSize: 2, + minimap: { + enabled: false, + }, + padding: { + bottom: 4, + top: 4, + }, + scrollBeyondLastLine: false, + fixedOverflowWidgets: true, + fontFamily: "var(--font-geist-mono)", + }} + theme="vs-dark" + value={activeFileContent} + /> + + ) : ( +
+ + Waiting for Clerk to load... +
+ )}
- + - {isHorizontalLayout ? : } + > + {isHorizontalLayout ? ( + + ) : ( + + )} - tab.id === activeFileId)?.name || 'No file selected'} + tab.id === activeFileId)?.name || + "No file selected" + } onClose={toggleAIChat} /> diff --git a/frontend/components/editor/live/avatars.tsx b/frontend/components/editor/live/avatars.tsx index 4b7550d..15c78de 100644 --- a/frontend/components/editor/live/avatars.tsx +++ b/frontend/components/editor/live/avatars.tsx @@ -1,6 +1,6 @@ -"use client"; +"use client" -import { useOthers } from "@/liveblocks.config"; +import { useOthers } from "@/liveblocks.config" const classNames = { red: "w-8 h-8 leading-none font-mono rounded-full ring-1 ring-red-700 ring-offset-2 ring-offset-background overflow-hidden bg-gradient-to-tr from-red-950 to-red-600 flex items-center justify-center text-xs font-medium", @@ -14,10 +14,10 @@ const classNames = { purple: "w-8 h-8 leading-none font-mono rounded-full ring-1 ring-purple-700 ring-offset-2 ring-offset-background overflow-hidden bg-gradient-to-tr from-purple-950 to-purple-600 flex items-center justify-center text-xs font-medium", pink: "w-8 h-8 leading-none font-mono rounded-full ring-1 ring-pink-700 ring-offset-2 ring-offset-background overflow-hidden bg-gradient-to-tr from-pink-950 to-pink-600 flex items-center justify-center text-xs font-medium", -}; +} export function Avatars() { - const users = useOthers(); + const users = useOthers() return ( <> @@ -30,12 +30,12 @@ export function Avatars() { .slice(0, 2) .map((letter) => letter[0].toUpperCase())} - ); + ) })} {users.length > 0 ? (
) : null} - ); + ) } diff --git a/frontend/components/editor/live/cursors.tsx b/frontend/components/editor/live/cursors.tsx index 5a1fcad..0331f23 100644 --- a/frontend/components/editor/live/cursors.tsx +++ b/frontend/components/editor/live/cursors.tsx @@ -1,11 +1,10 @@ -import { useEffect, useMemo, useState } from "react" +import { colors } from "@/lib/colors" import { AwarenessList, TypedLiveblocksProvider, UserAwareness, - useSelf, } from "@/liveblocks.config" -import { colors } from "@/lib/colors" +import { useEffect, useMemo, useState } from "react" export function Cursors({ yProvider, diff --git a/frontend/components/editor/live/disableModal.tsx b/frontend/components/editor/live/disableModal.tsx index 1056b42..976690d 100644 --- a/frontend/components/editor/live/disableModal.tsx +++ b/frontend/components/editor/live/disableModal.tsx @@ -1,43 +1,35 @@ -"use client"; +"use client" import { Dialog, DialogContent, - DialogDescription, DialogHeader, DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; +} from "@/components/ui/dialog" -import { - ChevronRight, - FileStack, - Globe, - Loader2, - TextCursor, -} from "lucide-react"; -import { useRouter } from "next/navigation"; -import { useEffect } from "react"; +import { Loader2 } from "lucide-react" +import { useRouter } from "next/navigation" +import { useEffect } from "react" export default function DisableAccessModal({ open, setOpen, message, }: { - open: boolean; - setOpen: (open: boolean) => void; - message: string; + open: boolean + setOpen: (open: boolean) => void + message: string }) { - const router = useRouter(); + const router = useRouter() useEffect(() => { if (open) { const timeout = setTimeout(() => { - router.push("/dashboard"); - }, 5000); - return () => clearTimeout(timeout); + router.push("/dashboard") + }, 5000) + return () => clearTimeout(timeout) } - }, []); + }, []) return ( @@ -54,5 +46,5 @@ export default function DisableAccessModal({
- ); + ) } diff --git a/frontend/components/editor/live/room.tsx b/frontend/components/editor/live/room.tsx index 8c29005..a83a330 100644 --- a/frontend/components/editor/live/room.tsx +++ b/frontend/components/editor/live/room.tsx @@ -1,14 +1,13 @@ -"use client"; +"use client" -import { RoomProvider } from "@/liveblocks.config"; -import { ClientSideSuspense } from "@liveblocks/react"; +import { RoomProvider } from "@/liveblocks.config" export function Room({ id, children, }: { - id: string; - children: React.ReactNode; + id: string + children: React.ReactNode }) { return ( */} - ); + ) } diff --git a/frontend/components/editor/loading/index.tsx b/frontend/components/editor/loading/index.tsx index eebabc2..3c2021b 100644 --- a/frontend/components/editor/loading/index.tsx +++ b/frontend/components/editor/loading/index.tsx @@ -1,9 +1,6 @@ "use client" -import Image from "next/image" import Logo from "@/assets/logo.svg" -import { Skeleton } from "@/components/ui/skeleton" -import { Loader2, X } from "lucide-react" import { Dialog, DialogContent, @@ -11,6 +8,9 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog" +import { Skeleton } from "@/components/ui/skeleton" +import { Loader2, X } from "lucide-react" +import Image from "next/image" import { useEffect, useState } from "react" export default function Loading({ diff --git a/frontend/components/editor/navbar/deploy.tsx b/frontend/components/editor/navbar/deploy.tsx index a8874db..5e42b75 100644 --- a/frontend/components/editor/navbar/deploy.tsx +++ b/frontend/components/editor/navbar/deploy.tsx @@ -1,34 +1,38 @@ -"use client"; +"use client" -import { useState } from "react"; -import { Button } from "@/components/ui/button"; -import { useTerminal } from "@/context/TerminalContext"; -import { Play, Pause, Globe, Globe2 } from "lucide-react"; -import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; -import { Sandbox, User } from "@/lib/types"; +import { Button } from "@/components/ui/button" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" +import { useTerminal } from "@/context/TerminalContext" +import { Sandbox, User } from "@/lib/types" +import { Globe } from "lucide-react" +import { useState } from "react" export default function DeployButtonModal({ userData, data, }: { - userData: User; - data: Sandbox; + userData: User + data: Sandbox }) { - const { deploy } = useTerminal(); - const [isDeploying, setIsDeploying] = useState(false); + const { deploy } = useTerminal() + const [isDeploying, setIsDeploying] = useState(false) const handleDeploy = () => { if (isDeploying) { - console.log("Stopping deployment..."); - setIsDeploying(false); + console.log("Stopping deployment...") + setIsDeploying(false) } else { - console.log("Starting deployment..."); - setIsDeploying(true); + console.log("Starting deployment...") + setIsDeploying(true) deploy(() => { - setIsDeploying(false); - }); + setIsDeploying(false) + }) } - }; + } return ( <> @@ -39,7 +43,10 @@ export default function DeployButtonModal({ Deploy - +

Domains

-
- ); + ) } -function DeploymentOption({ icon, domain, timestamp, user }: { icon: React.ReactNode; domain: string; timestamp: string; user: string }) { +function DeploymentOption({ + icon, + domain, + timestamp, + user, +}: { + icon: React.ReactNode + domain: string + timestamp: string + user: string +}) { return (
@@ -72,7 +93,9 @@ function DeploymentOption({ icon, domain, timestamp, user }: { icon: React.React {domain}
-

{timestamp} • {user}

+

+ {timestamp} • {user} +

- ); + ) } diff --git a/frontend/components/editor/navbar/edit.tsx b/frontend/components/editor/navbar/edit.tsx index 17575d7..9013dc2 100644 --- a/frontend/components/editor/navbar/edit.tsx +++ b/frontend/components/editor/navbar/edit.tsx @@ -1,60 +1,57 @@ -"use client"; +"use client" import { Dialog, DialogContent, - DialogDescription, DialogHeader, DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { z } from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { useForm } from "react-hook-form"; +} from "@/components/ui/dialog" +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import { z } from "zod" +import { Button } from "@/components/ui/button" import { Form, FormControl, - FormDescription, FormField, FormItem, FormLabel, FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; +} from "@/components/ui/form" +import { Input } from "@/components/ui/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@/components/ui/select"; -import { Loader2 } from "lucide-react"; -import { useState } from "react"; -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"; +} from "@/components/ui/select" +import { deleteSandbox, updateSandbox } from "@/lib/actions" +import { Sandbox } from "@/lib/types" +import { Loader2 } from "lucide-react" +import { useRouter } from "next/navigation" +import { useState } from "react" +import { toast } from "sonner" const formSchema = z.object({ name: z.string().min(1).max(16), visibility: z.enum(["public", "private"]), -}); +}) export default function EditSandboxModal({ open, setOpen, data, }: { - open: boolean; - setOpen: (open: boolean) => void; - data: Sandbox; + open: boolean + setOpen: (open: boolean) => void + data: Sandbox }) { - const [loading, setLoading] = useState(false); - const [loadingDelete, setLoadingDelete] = useState(false); + const [loading, setLoading] = useState(false) + const [loadingDelete, setLoadingDelete] = useState(false) - const router = useRouter(); + const router = useRouter() const form = useForm>({ resolver: zodResolver(formSchema), @@ -62,22 +59,22 @@ export default function EditSandboxModal({ name: data.name, visibility: data.visibility, }, - }); + }) async function onSubmit(values: z.infer) { - setLoading(true); - await updateSandbox({ id: data.id, ...values }); + setLoading(true) + await updateSandbox({ id: data.id, ...values }) - toast.success("Sandbox updated successfully"); + toast.success("Sandbox updated successfully") - setLoading(false); + setLoading(false) } async function onDelete() { - setLoadingDelete(true); - await deleteSandbox(data.id); + setLoadingDelete(true) + await deleteSandbox(data.id) - router.push("/dashboard"); + router.push("/dashboard") } return ( @@ -153,5 +150,5 @@ export default function EditSandboxModal({ - ); + ) } diff --git a/frontend/components/editor/navbar/index.tsx b/frontend/components/editor/navbar/index.tsx index 0408f1a..0451634 100644 --- a/frontend/components/editor/navbar/index.tsx +++ b/frontend/components/editor/navbar/index.tsx @@ -1,33 +1,33 @@ -"use client"; +"use client" -import Image from "next/image"; -import Logo from "@/assets/logo.svg"; -import { Pencil, Users } from "lucide-react"; -import Link from "next/link"; -import { Sandbox, User } from "@/lib/types"; -import UserButton from "@/components/ui/userButton"; -import { Button } from "@/components/ui/button"; -import { useState } from "react"; -import EditSandboxModal from "./edit"; -import ShareSandboxModal from "./share"; -import { Avatars } from "../live/avatars"; -import RunButtonModal from "./run"; -import DeployButtonModal from "./deploy"; +import Logo from "@/assets/logo.svg" +import { Button } from "@/components/ui/button" +import UserButton from "@/components/ui/userButton" +import { Sandbox, User } from "@/lib/types" +import { Pencil, Users } from "lucide-react" +import Image from "next/image" +import Link from "next/link" +import { useState } from "react" +import { Avatars } from "../live/avatars" +import DeployButtonModal from "./deploy" +import EditSandboxModal from "./edit" +import RunButtonModal from "./run" +import ShareSandboxModal from "./share" export default function Navbar({ userData, sandboxData, shared, }: { - userData: User; - sandboxData: Sandbox; - shared: { id: string; name: string }[]; + userData: User + sandboxData: Sandbox + shared: { id: string; name: string }[] }) { - const [isEditOpen, setIsEditOpen] = useState(false); - const [isShareOpen, setIsShareOpen] = useState(false); - const [isRunning, setIsRunning] = useState(false); + const [isEditOpen, setIsEditOpen] = useState(false) + const [isShareOpen, setIsShareOpen] = useState(false) + const [isRunning, setIsRunning] = useState(false) - const isOwner = sandboxData.userId === userData.id;; + const isOwner = sandboxData.userId === userData.id return ( <> @@ -72,19 +72,16 @@ export default function Navbar({ {isOwner ? ( <> - - + + ) : null} - ); -} \ No newline at end of file + ) +} diff --git a/frontend/components/editor/navbar/run.tsx b/frontend/components/editor/navbar/run.tsx index 26e8bbd..2d11008 100644 --- a/frontend/components/editor/navbar/run.tsx +++ b/frontend/components/editor/navbar/run.tsx @@ -1,73 +1,78 @@ -"use client"; +"use client" -import React, { useEffect, useRef } from 'react'; -import { Play, StopCircle } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { useTerminal } from "@/context/TerminalContext"; -import { usePreview } from "@/context/PreviewContext"; -import { toast } from "sonner"; -import { Sandbox } from "@/lib/types"; +import { Button } from "@/components/ui/button" +import { usePreview } from "@/context/PreviewContext" +import { useTerminal } from "@/context/TerminalContext" +import { Sandbox } from "@/lib/types" +import { Play, StopCircle } from "lucide-react" +import { useEffect, useRef } from "react" +import { toast } from "sonner" export default function RunButtonModal({ isRunning, setIsRunning, sandboxData, }: { - isRunning: boolean; - setIsRunning: (running: boolean) => void; - sandboxData: Sandbox; + isRunning: boolean + setIsRunning: (running: boolean) => void + sandboxData: Sandbox }) { - const { createNewTerminal, closeTerminal, terminals } = useTerminal(); - const { setIsPreviewCollapsed, previewPanelRef } = usePreview(); + const { createNewTerminal, closeTerminal, terminals } = useTerminal() + const { setIsPreviewCollapsed, previewPanelRef } = usePreview() // Ref to keep track of the last created terminal's ID - const lastCreatedTerminalRef = useRef(null); + const lastCreatedTerminalRef = useRef(null) // Effect to update the lastCreatedTerminalRef when a new terminal is added useEffect(() => { if (terminals.length > 0 && !isRunning) { - const latestTerminal = terminals[terminals.length - 1]; - if (latestTerminal && latestTerminal.id !== lastCreatedTerminalRef.current) { - lastCreatedTerminalRef.current = latestTerminal.id; + const latestTerminal = terminals[terminals.length - 1] + if ( + latestTerminal && + latestTerminal.id !== lastCreatedTerminalRef.current + ) { + lastCreatedTerminalRef.current = latestTerminal.id } } - }, [terminals, isRunning]); + }, [terminals, isRunning]) const handleRun = async () => { - if (isRunning && lastCreatedTerminalRef.current) - { - await closeTerminal(lastCreatedTerminalRef.current); - lastCreatedTerminalRef.current = null; - setIsPreviewCollapsed(true); - previewPanelRef.current?.collapse(); - } - else if (!isRunning && terminals.length < 4) - { - const command = sandboxData.type === "streamlit" - ? "pip install -r requirements.txt && streamlit run main.py --server.runOnSave true" - : "yarn install && yarn dev"; - + if (isRunning && lastCreatedTerminalRef.current) { + await closeTerminal(lastCreatedTerminalRef.current) + lastCreatedTerminalRef.current = null + setIsPreviewCollapsed(true) + previewPanelRef.current?.collapse() + } else if (!isRunning && terminals.length < 4) { + const command = + sandboxData.type === "streamlit" + ? "pip install -r requirements.txt && streamlit run main.py --server.runOnSave true" + : "yarn install && yarn dev" + try { // Create a new terminal with the appropriate command - await createNewTerminal(command); - setIsPreviewCollapsed(false); - previewPanelRef.current?.expand(); + await createNewTerminal(command) + setIsPreviewCollapsed(false) + previewPanelRef.current?.expand() } catch (error) { - toast.error("Failed to create new terminal."); - console.error("Error creating new terminal:", error); - return; + toast.error("Failed to create new terminal.") + console.error("Error creating new terminal:", error) + return } } else if (!isRunning) { - toast.error("You've reached the maximum number of terminals."); - return; + toast.error("You've reached the maximum number of terminals.") + return } - setIsRunning(!isRunning); - }; + setIsRunning(!isRunning) + } return ( - ); -} \ No newline at end of file + ) +} diff --git a/frontend/components/editor/navbar/share.tsx b/frontend/components/editor/navbar/share.tsx index 1821c9b..3d5991d 100644 --- a/frontend/components/editor/navbar/share.tsx +++ b/frontend/components/editor/navbar/share.tsx @@ -6,10 +6,11 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog" -import { z } from "zod" import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" +import { z } from "zod" +import { Button } from "@/components/ui/button" import { Form, FormControl, @@ -18,14 +19,13 @@ import { FormMessage, } from "@/components/ui/form" import { Input } from "@/components/ui/input" -import { Link, Loader2, UserPlus, X } from "lucide-react" -import { useState } from "react" -import { Sandbox } from "@/lib/types" -import { Button } from "@/components/ui/button" import { shareSandbox } from "@/lib/actions" +import { Sandbox } from "@/lib/types" +import { DialogDescription } from "@radix-ui/react-dialog" +import { Link, Loader2, UserPlus } from "lucide-react" +import { useState } from "react" import { toast } from "sonner" import SharedUser from "./sharedUser" -import { DialogDescription } from "@radix-ui/react-dialog" const formSchema = z.object({ email: z.string().email(), diff --git a/frontend/components/editor/preview/index.tsx b/frontend/components/editor/preview/index.tsx index 407bc7f..099c636 100644 --- a/frontend/components/editor/preview/index.tsx +++ b/frontend/components/editor/preview/index.tsx @@ -1,66 +1,69 @@ "use client" +import { Link, RotateCw, UnfoldVertical } from "lucide-react" import { - Link, - RotateCw, - TerminalSquare, - UnfoldVertical, -} from "lucide-react" -import { useEffect, useRef, useState, useImperativeHandle, forwardRef } from "react" + forwardRef, + useEffect, + useImperativeHandle, + useRef, + useState, +} from "react" import { toast } from "sonner" -export default forwardRef(function PreviewWindow({ - collapsed, - open, - src -}: { - collapsed: boolean - open: () => void - src: string -}, -ref: React.Ref<{ - refreshIframe: () => void -}>) { +export default forwardRef(function PreviewWindow( + { + collapsed, + open, + src, + }: { + collapsed: boolean + open: () => void + src: string + }, + ref: React.Ref<{ + refreshIframe: () => void + }> +) { const frameRef = useRef(null) const [iframeKey, setIframeKey] = useState(0) const refreshIframe = () => { - setIframeKey(prev => prev + 1) + setIframeKey((prev) => prev + 1) } - // Refresh the preview when the URL changes. + // Refresh the preview when the URL changes. useEffect(refreshIframe, [src]) // Expose refreshIframe method to the parent. useImperativeHandle(ref, () => ({ refreshIframe })) return ( <> -
-
Preview
-
- {collapsed ? ( +
+
Preview
+
+ {collapsed ? ( + + + + ) : ( + <> - ) : ( - <> - - - - { - navigator.clipboard.writeText(src) - toast.info("Copied preview link to clipboard") - }} - > - - - - - - - )} -
+ { + navigator.clipboard.writeText(src) + toast.info("Copied preview link to clipboard") + }} + > + + + + + + + )}
+
) }) @@ -76,8 +79,9 @@ function PreviewButton({ }) { return (
{children} diff --git a/frontend/components/editor/sidebar/file.tsx b/frontend/components/editor/sidebar/file.tsx index b3f2e54..b01f666 100644 --- a/frontend/components/editor/sidebar/file.tsx +++ b/frontend/components/editor/sidebar/file.tsx @@ -1,18 +1,18 @@ -"use client"; +"use client" -import Image from "next/image"; -import { getIconForFile } from "vscode-icons-js"; -import { TFile, TTab } from "@/lib/types"; -import { useEffect, useRef, useState } from "react"; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger, -} from "@/components/ui/context-menu"; -import { Loader2, Pencil, Trash2 } from "lucide-react"; +} from "@/components/ui/context-menu" +import { TFile, TTab } from "@/lib/types" +import { Loader2, Pencil, Trash2 } from "lucide-react" +import Image from "next/image" +import { useEffect, useRef, useState } from "react" +import { getIconForFile } from "vscode-icons-js" -import { draggable } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; +import { draggable } from "@atlaskit/pragmatic-drag-and-drop/element/adapter" export default function SidebarFile({ data, @@ -22,36 +22,36 @@ export default function SidebarFile({ movingId, deletingFolderId, }: { - data: TFile; - selectFile: (file: TTab) => void; + data: TFile + selectFile: (file: TTab) => void handleRename: ( id: string, newName: string, oldName: string, type: "file" | "folder" - ) => boolean; - handleDeleteFile: (file: TFile) => void; - movingId: string; - deletingFolderId: string; + ) => boolean + handleDeleteFile: (file: TFile) => void + movingId: string + deletingFolderId: string }) { - const isMoving = movingId === data.id; + const isMoving = movingId === data.id const isDeleting = - deletingFolderId.length > 0 && data.id.startsWith(deletingFolderId); + deletingFolderId.length > 0 && data.id.startsWith(deletingFolderId) - const ref = useRef(null); // for draggable - const [dragging, setDragging] = useState(false); + const ref = useRef(null) // for draggable + const [dragging, setDragging] = useState(false) - const inputRef = useRef(null); - const [imgSrc, setImgSrc] = useState(`/icons/${getIconForFile(data.name)}`); - const [editing, setEditing] = useState(false); - const [pendingDelete, setPendingDelete] = useState(isDeleting); + const inputRef = useRef(null) + const [imgSrc, setImgSrc] = useState(`/icons/${getIconForFile(data.name)}`) + const [editing, setEditing] = useState(false) + const [pendingDelete, setPendingDelete] = useState(isDeleting) useEffect(() => { - setPendingDelete(isDeleting); - }, [isDeleting]); + setPendingDelete(isDeleting) + }, [isDeleting]) useEffect(() => { - const el = ref.current; + const el = ref.current if (el) return draggable({ @@ -59,14 +59,14 @@ export default function SidebarFile({ onDragStart: () => setDragging(true), onDrop: () => setDragging(false), getInitialData: () => ({ id: data.id }), - }); - }, []); + }) + }, []) useEffect(() => { if (editing) { - setTimeout(() => inputRef.current?.focus(), 0); + setTimeout(() => inputRef.current?.focus(), 0) } - }, [editing, inputRef.current]); + }, [editing, inputRef.current]) const renameFile = () => { const renamed = handleRename( @@ -74,12 +74,12 @@ export default function SidebarFile({ inputRef.current?.value ?? data.name, data.name, "file" - ); + ) if (!renamed && inputRef.current) { - inputRef.current.value = data.name; + inputRef.current.value = data.name } - setEditing(false); - }; + setEditing(false) + } return ( @@ -88,7 +88,7 @@ export default function SidebarFile({ disabled={pendingDelete || dragging || isMoving} onClick={() => { if (!editing && !pendingDelete && !isMoving) - selectFile({ ...data, saved: true }); + selectFile({ ...data, saved: true }) }} onDoubleClick={() => { setEditing(true) @@ -119,8 +119,8 @@ export default function SidebarFile({ ) : (
{ - e.preventDefault(); - renameFile(); + e.preventDefault() + renameFile() }} > { - console.log("rename"); - setEditing(true); + console.log("rename") + setEditing(true) }} > @@ -148,9 +148,9 @@ export default function SidebarFile({ { - console.log("delete"); - setPendingDelete(true); - handleDeleteFile(data); + console.log("delete") + setPendingDelete(true) + handleDeleteFile(data) }} > @@ -158,5 +158,5 @@ export default function SidebarFile({ - ); + ) } diff --git a/frontend/components/editor/sidebar/folder.tsx b/frontend/components/editor/sidebar/folder.tsx index 94582d7..19ec699 100644 --- a/frontend/components/editor/sidebar/folder.tsx +++ b/frontend/components/editor/sidebar/folder.tsx @@ -1,20 +1,20 @@ "use client" -import Image from "next/image" -import { useEffect, useRef, useState } from "react" -import { getIconForFolder, getIconForOpenFolder } from "vscode-icons-js" -import { TFile, TFolder, TTab } from "@/lib/types" -import SidebarFile from "./file" import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger, } from "@/components/ui/context-menu" -import { ChevronRight, Loader2, Pencil, Trash2 } from "lucide-react" -import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter" +import { TFile, TFolder, TTab } from "@/lib/types" import { cn } from "@/lib/utils" -import { motion, AnimatePresence } from "framer-motion" +import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter" +import { AnimatePresence, motion } from "framer-motion" +import { ChevronRight, Pencil, Trash2 } from "lucide-react" +import Image from "next/image" +import { useEffect, useRef, useState } from "react" +import { getIconForFolder, getIconForOpenFolder } from "vscode-icons-js" +import SidebarFile from "./file" // Note: Renaming has not been implemented in the backend yet, so UI relating to renaming is commented out diff --git a/frontend/components/editor/sidebar/index.tsx b/frontend/components/editor/sidebar/index.tsx index 8fc706f..a3aac57 100644 --- a/frontend/components/editor/sidebar/index.tsx +++ b/frontend/components/editor/sidebar/index.tsx @@ -1,25 +1,25 @@ -"use client"; +"use client" +import { Button } from "@/components/ui/button" +import { Sandbox, TFile, TFolder, TTab } from "@/lib/types" import { FilePlus, FolderPlus, Loader2, - Sparkles, MessageSquareMore, -} from "lucide-react"; -import SidebarFile from "./file"; -import SidebarFolder from "./folder"; -import { Sandbox, TFile, TFolder, TTab } from "@/lib/types"; -import { useEffect, useRef, useState } from "react"; -import New from "./new"; -import { Socket } from "socket.io-client"; -import { Button } from "@/components/ui/button"; + Sparkles, +} from "lucide-react" +import { useEffect, useRef, useState } from "react" +import { Socket } from "socket.io-client" +import SidebarFile from "./file" +import SidebarFolder from "./folder" +import New from "./new" import { dropTargetForElements, monitorForElements, -} from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; - +} from "@atlaskit/pragmatic-drag-and-drop/element/adapter" + export default function Sidebar({ sandboxData, files, @@ -32,75 +32,73 @@ export default function Sidebar({ addNew, deletingFolderId, }: { - sandboxData: Sandbox; - files: (TFile | TFolder)[]; - selectFile: (tab: TTab) => void; + sandboxData: Sandbox + files: (TFile | TFolder)[] + selectFile: (tab: TTab) => void handleRename: ( id: string, newName: string, oldName: string, type: "file" | "folder" - ) => boolean; - handleDeleteFile: (file: TFile) => void; - handleDeleteFolder: (folder: TFolder) => void; - socket: Socket; - setFiles: (files: (TFile | TFolder)[]) => void; - addNew: (name: string, type: "file" | "folder") => void; - deletingFolderId: string; + ) => boolean + handleDeleteFile: (file: TFile) => void + handleDeleteFolder: (folder: TFolder) => void + socket: Socket + setFiles: (files: (TFile | TFolder)[]) => void + addNew: (name: string, type: "file" | "folder") => void + deletingFolderId: string }) { - const ref = useRef(null); // drop target + const ref = useRef(null) // drop target - const [creatingNew, setCreatingNew] = useState<"file" | "folder" | null>( - null - ); - const [movingId, setMovingId] = useState(""); + const [creatingNew, setCreatingNew] = useState<"file" | "folder" | null>(null) + const [movingId, setMovingId] = useState("") useEffect(() => { - const el = ref.current; + const el = ref.current if (el) { return dropTargetForElements({ element: el, getData: () => ({ id: `projects/${sandboxData.id}` }), canDrop: ({ source }) => { - const file = files.find((child) => child.id === source.data.id); - return !file; + const file = files.find((child) => child.id === source.data.id) + return !file }, - }); + }) } - }, [files]); + }, [files]) useEffect(() => { return monitorForElements({ onDrop({ source, location }) { - const destination = location.current.dropTargets[0]; + const destination = location.current.dropTargets[0] if (!destination) { - return; + return } - const fileId = source.data.id as string; - const folderId = destination.data.id as string; + const fileId = source.data.id as string + const folderId = destination.data.id as string - const fileFolder = fileId.split("/").slice(0, -1).join("/"); + const fileFolder = fileId.split("/").slice(0, -1).join("/") if (fileFolder === folderId) { - return; + return } - console.log("move file", fileId, "to folder", folderId); + console.log("move file", fileId, "to folder", folderId) - setMovingId(fileId); + setMovingId(fileId) socket.emit( "moveFile", fileId, folderId, (response: (TFolder | TFile)[]) => { - setFiles(response); - setMovingId(""); + setFiles(response) + setMovingId("") } - ); + ) }, - }); - }, []); + }) + }, []) return (
@@ -170,7 +168,7 @@ export default function Sidebar({ socket={socket} type={creatingNew} stopEditing={() => { - setCreatingNew(null); + setCreatingNew(null) }} addNew={addNew} /> @@ -180,7 +178,13 @@ export default function Sidebar({
- -
- ); + ) } diff --git a/frontend/components/editor/sidebar/new.tsx b/frontend/components/editor/sidebar/new.tsx index 6997586..7fec344 100644 --- a/frontend/components/editor/sidebar/new.tsx +++ b/frontend/components/editor/sidebar/new.tsx @@ -1,9 +1,9 @@ -"use client"; +"use client" -import { validateName } from "@/lib/utils"; -import Image from "next/image"; -import { useEffect, useRef } from "react"; -import { Socket } from "socket.io-client"; +import { validateName } from "@/lib/utils" +import Image from "next/image" +import { useEffect, useRef } from "react" +import { Socket } from "socket.io-client" export default function New({ socket, @@ -11,18 +11,18 @@ export default function New({ stopEditing, addNew, }: { - socket: Socket; - type: "file" | "folder"; - stopEditing: () => void; - addNew: (name: string, type: "file" | "folder") => void; + socket: Socket + type: "file" | "folder" + stopEditing: () => void + addNew: (name: string, type: "file" | "folder") => void }) { - const inputRef = useRef(null); + const inputRef = useRef(null) function createNew() { - const name = inputRef.current?.value; + const name = inputRef.current?.value if (name) { - const valid = validateName(name, "", type); + const valid = validateName(name, "", type) if (valid.status) { if (type === "file") { socket.emit( @@ -30,23 +30,23 @@ export default function New({ name, ({ success }: { success: boolean }) => { if (success) { - addNew(name, type); + addNew(name, type) } } - ); + ) } else { socket.emit("createFolder", name, () => { - addNew(name, type); - }); + addNew(name, type) + }) } } } - stopEditing(); + stopEditing() } useEffect(() => { - inputRef.current?.focus(); - }, []); + inputRef.current?.focus() + }, []) return (
@@ -63,8 +63,8 @@ export default function New({ /> { - e.preventDefault(); - createNew(); + e.preventDefault() + createNew() }} >
- ); + ) } diff --git a/frontend/components/editor/terminals/index.tsx b/frontend/components/editor/terminals/index.tsx index 6a5fe75..ef148d4 100644 --- a/frontend/components/editor/terminals/index.tsx +++ b/frontend/components/editor/terminals/index.tsx @@ -1,18 +1,17 @@ -"use client"; +"use client" -import { Button } from "@/components/ui/button"; -import Tab from "@/components/ui/tab"; -import { Terminal } from "@xterm/xterm"; -import { Loader2, Plus, SquareTerminal, TerminalSquare } from "lucide-react"; -import { toast } from "sonner"; -import EditorTerminal from "./terminal"; -import { useTerminal } from "@/context/TerminalContext"; -import { useEffect } from "react"; +import { Button } from "@/components/ui/button" +import Tab from "@/components/ui/tab" import { useSocket } from "@/context/SocketContext" +import { useTerminal } from "@/context/TerminalContext" +import { Terminal } from "@xterm/xterm" +import { Loader2, Plus, SquareTerminal, TerminalSquare } from "lucide-react" +import { useEffect } from "react" +import { toast } from "sonner" +import EditorTerminal from "./terminal" export default function Terminals() { - - const { socket } = useSocket(); + const { socket } = useSocket() const { terminals, @@ -22,24 +21,24 @@ export default function Terminals() { activeTerminalId, setActiveTerminalId, creatingTerminal, - } = useTerminal(); + } = useTerminal() - const activeTerminal = terminals.find((t) => t.id === activeTerminalId); + const activeTerminal = terminals.find((t) => t.id === activeTerminalId) // Effect to set the active terminal when a new one is created useEffect(() => { if (terminals.length > 0 && !activeTerminalId) { - setActiveTerminalId(terminals[terminals.length - 1].id); + setActiveTerminalId(terminals[terminals.length - 1].id) } - }, [terminals, activeTerminalId, setActiveTerminalId]); + }, [terminals, activeTerminalId, setActiveTerminalId]) const handleCreateTerminal = () => { if (terminals.length >= 4) { - toast.error("You reached the maximum # of terminals."); - return; + toast.error("You reached the maximum # of terminals.") + return } - createNewTerminal(); - }; + createNewTerminal() + } return ( <> @@ -85,7 +84,7 @@ export default function Terminals() { ? { ...term, terminal: t } : term ) - ); + ) }} visible={activeTerminalId === term.id} /> @@ -98,5 +97,5 @@ export default function Terminals() { )} - ); -} \ No newline at end of file + ) +} diff --git a/frontend/components/editor/terminals/terminal.tsx b/frontend/components/editor/terminals/terminal.tsx index 1187acb..b4e0749 100644 --- a/frontend/components/editor/terminals/terminal.tsx +++ b/frontend/components/editor/terminals/terminal.tsx @@ -1,12 +1,12 @@ -"use client"; +"use client" -import { Terminal } from "@xterm/xterm"; -import { FitAddon } from "@xterm/addon-fit"; -import "./xterm.css"; +import { FitAddon } from "@xterm/addon-fit" +import { Terminal } from "@xterm/xterm" +import "./xterm.css" -import { useEffect, useRef, useState } from "react"; -import { Socket } from "socket.io-client"; -import { Loader2 } from "lucide-react"; +import { Loader2 } from "lucide-react" +import { useEffect, useRef } from "react" +import { Socket } from "socket.io-client" export default function EditorTerminal({ socket, @@ -15,16 +15,16 @@ export default function EditorTerminal({ setTerm, visible, }: { - socket: Socket; - id: string; - term: Terminal | null; - setTerm: (term: Terminal) => void; - visible: boolean; + socket: Socket + id: string + term: Terminal | null + setTerm: (term: Terminal) => void + visible: boolean }) { - const terminalRef = useRef(null); + const terminalRef = useRef(null) useEffect(() => { - if (!terminalRef.current) return; + if (!terminalRef.current) return // console.log("new terminal", id, term ? "reusing" : "creating"); const terminal = new Terminal({ @@ -36,56 +36,56 @@ export default function EditorTerminal({ fontSize: 14, lineHeight: 1.5, letterSpacing: 0, - }); + }) - setTerm(terminal); + setTerm(terminal) return () => { - if (terminal) terminal.dispose(); - }; - }, []); + if (terminal) terminal.dispose() + } + }, []) useEffect(() => { - if (!term) return; + if (!term) return - if (!terminalRef.current) return; - const fitAddon = new FitAddon(); - term.loadAddon(fitAddon); - term.open(terminalRef.current); - fitAddon.fit(); + if (!terminalRef.current) return + const fitAddon = new FitAddon() + term.loadAddon(fitAddon) + term.open(terminalRef.current) + fitAddon.fit() const disposableOnData = term.onData((data) => { - socket.emit("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); - }); + fitAddon.fit() + socket.emit("terminalResize", dimensions) + }) return () => { - disposableOnData.dispose(); - disposableOnResize.dispose(); - }; - }, [term, terminalRef.current]); + disposableOnData.dispose() + disposableOnResize.dispose() + } + }, [term, terminalRef.current]) useEffect(() => { - if (!term) return; + if (!term) return const handleTerminalResponse = (response: { id: string; data: string }) => { if (response.id === id) { - term.write(response.data); + term.write(response.data) } - }; - socket.on("terminalResponse", handleTerminalResponse); - + } + socket.on("terminalResponse", handleTerminalResponse) + return () => { - socket.off("terminalResponse", handleTerminalResponse); - }; - }, [term, id, socket]); + socket.off("terminalResponse", handleTerminalResponse) + } + }, [term, id, socket]) return ( <> @@ -102,5 +102,5 @@ export default function EditorTerminal({ ) : null} - ); + ) } diff --git a/frontend/components/editor/terminals/xterm.css b/frontend/components/editor/terminals/xterm.css index 386a214..58b5693 100644 --- a/frontend/components/editor/terminals/xterm.css +++ b/frontend/components/editor/terminals/xterm.css @@ -35,7 +35,7 @@ * Default styles for xterm.js */ - .xterm { +.xterm { cursor: text; position: relative; user-select: none; @@ -80,7 +80,7 @@ .xterm .composition-view { /* TODO: Composition position got messed up somewhere */ background: transparent; - color: #FFF; + color: #fff; display: none; position: absolute; white-space: nowrap; @@ -154,12 +154,12 @@ } .xterm .xterm-accessibility-tree:not(.debug) *::selection { -color: transparent; + color: transparent; } .xterm .xterm-accessibility-tree { -user-select: text; -white-space: pre; + user-select: text; + white-space: pre; } .xterm .live-region { @@ -176,33 +176,55 @@ white-space: pre; opacity: 1 !important; } -.xterm-underline-1 { text-decoration: underline; } -.xterm-underline-2 { text-decoration: double underline; } -.xterm-underline-3 { text-decoration: wavy underline; } -.xterm-underline-4 { text-decoration: dotted underline; } -.xterm-underline-5 { text-decoration: dashed underline; } +.xterm-underline-1 { + text-decoration: underline; +} +.xterm-underline-2 { + text-decoration: double underline; +} +.xterm-underline-3 { + text-decoration: wavy underline; +} +.xterm-underline-4 { + text-decoration: dotted underline; +} +.xterm-underline-5 { + text-decoration: dashed underline; +} .xterm-overline { text-decoration: overline; } -.xterm-overline.xterm-underline-1 { text-decoration: overline underline; } -.xterm-overline.xterm-underline-2 { text-decoration: overline double underline; } -.xterm-overline.xterm-underline-3 { text-decoration: overline wavy underline; } -.xterm-overline.xterm-underline-4 { text-decoration: overline dotted underline; } -.xterm-overline.xterm-underline-5 { text-decoration: overline dashed underline; } +.xterm-overline.xterm-underline-1 { + text-decoration: overline underline; +} +.xterm-overline.xterm-underline-2 { + text-decoration: overline double underline; +} +.xterm-overline.xterm-underline-3 { + text-decoration: overline wavy underline; +} +.xterm-overline.xterm-underline-4 { + text-decoration: overline dotted underline; +} +.xterm-overline.xterm-underline-5 { + text-decoration: overline dashed underline; +} .xterm-strikethrough { text-decoration: line-through; } .xterm-screen .xterm-decoration-container .xterm-decoration { -z-index: 6; -position: absolute; + z-index: 6; + position: absolute; } -.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer { -z-index: 7; +.xterm-screen + .xterm-decoration-container + .xterm-decoration.xterm-decoration-top-layer { + z-index: 7; } .xterm-decoration-overview-ruler { @@ -216,4 +238,4 @@ z-index: 7; .xterm-decoration-top { z-index: 2; position: relative; -} \ No newline at end of file +} diff --git a/frontend/components/landing/index.tsx b/frontend/components/landing/index.tsx index e8bb993..06e2cb6 100644 --- a/frontend/components/landing/index.tsx +++ b/frontend/components/landing/index.tsx @@ -1,8 +1,8 @@ -import Image from "next/image" import Logo from "@/assets/logo.svg" import XLogo from "@/assets/x.svg" import Button from "@/components/ui/customButton" import { ChevronRight } from "lucide-react" +import Image from "next/image" import Link from "next/link" export default function Landing() { diff --git a/frontend/components/layout/themeProvider.tsx b/frontend/components/layout/themeProvider.tsx index 8c90fbc..19dfaa9 100644 --- a/frontend/components/layout/themeProvider.tsx +++ b/frontend/components/layout/themeProvider.tsx @@ -1,6 +1,5 @@ "use client" -import * as React from "react" import { ThemeProvider as NextThemesProvider } from "next-themes" import { type ThemeProviderProps } from "next-themes/dist/types" diff --git a/frontend/components/ui/LoadingDots.tsx b/frontend/components/ui/LoadingDots.tsx index 330507f..8474eb2 100644 --- a/frontend/components/ui/LoadingDots.tsx +++ b/frontend/components/ui/LoadingDots.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React from "react" const LoadingDots: React.FC = () => { return ( @@ -9,24 +9,35 @@ const LoadingDots: React.FC = () => { - ); -}; - -export default LoadingDots; + ) +} +export default LoadingDots diff --git a/frontend/components/ui/alert-dialog.tsx b/frontend/components/ui/alert-dialog.tsx index 2f30afa..79c4186 100644 --- a/frontend/components/ui/alert-dialog.tsx +++ b/frontend/components/ui/alert-dialog.tsx @@ -1,10 +1,10 @@ "use client" -import * as React from "react" import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" +import * as React from "react" -import { cn } from "@/lib/utils" import { buttonVariants } from "@/components/ui/button" +import { cn } from "@/lib/utils" const AlertDialog = AlertDialogPrimitive.Root @@ -128,14 +128,14 @@ AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName export { AlertDialog, - AlertDialogPortal, - AlertDialogOverlay, - AlertDialogTrigger, - AlertDialogContent, - AlertDialogHeader, - AlertDialogFooter, - AlertDialogTitle, - AlertDialogDescription, AlertDialogAction, AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogOverlay, + AlertDialogPortal, + AlertDialogTitle, + AlertDialogTrigger, } diff --git a/frontend/components/ui/button.tsx b/frontend/components/ui/button.tsx index e0720b0..e3b682e 100644 --- a/frontend/components/ui/button.tsx +++ b/frontend/components/ui/button.tsx @@ -1,6 +1,6 @@ -import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/frontend/components/ui/card.tsx b/frontend/components/ui/card.tsx index 77e9fb7..a36f130 100644 --- a/frontend/components/ui/card.tsx +++ b/frontend/components/ui/card.tsx @@ -73,4 +73,4 @@ const CardFooter = React.forwardRef< )) CardFooter.displayName = "CardFooter" -export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } +export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } diff --git a/frontend/components/ui/context-menu.tsx b/frontend/components/ui/context-menu.tsx index 654810a..f1a8298 100644 --- a/frontend/components/ui/context-menu.tsx +++ b/frontend/components/ui/context-menu.tsx @@ -1,12 +1,12 @@ "use client" -import * as React from "react" import * as ContextMenuPrimitive from "@radix-ui/react-context-menu" import { CheckIcon, ChevronRightIcon, DotFilledIcon, } from "@radix-ui/react-icons" +import * as React from "react" import { cn } from "@/lib/utils" @@ -187,18 +187,18 @@ ContextMenuShortcut.displayName = "ContextMenuShortcut" export { ContextMenu, - ContextMenuTrigger, - ContextMenuContent, - ContextMenuItem, ContextMenuCheckboxItem, - ContextMenuRadioItem, + ContextMenuContent, + ContextMenuGroup, + ContextMenuItem, ContextMenuLabel, + ContextMenuPortal, + ContextMenuRadioGroup, + ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, - ContextMenuGroup, - ContextMenuPortal, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, - ContextMenuRadioGroup, + ContextMenuTrigger, } diff --git a/frontend/components/ui/customButton.tsx b/frontend/components/ui/customButton.tsx index cd1e888..f0527f7 100644 --- a/frontend/components/ui/customButton.tsx +++ b/frontend/components/ui/customButton.tsx @@ -1,6 +1,5 @@ -import * as React from "react" -import { Plus } from "lucide-react" import { cn } from "@/lib/utils" +import * as React from "react" const Button = ({ children, diff --git a/frontend/components/ui/dialog.tsx b/frontend/components/ui/dialog.tsx index 02008c2..94fb4a3 100644 --- a/frontend/components/ui/dialog.tsx +++ b/frontend/components/ui/dialog.tsx @@ -1,18 +1,18 @@ -"use client"; +"use client" -import * as React from "react"; -import * as DialogPrimitive from "@radix-ui/react-dialog"; -import { Cross2Icon } from "@radix-ui/react-icons"; +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { Cross2Icon } from "@radix-ui/react-icons" +import * as React from "react" -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils" -const Dialog = DialogPrimitive.Root; +const Dialog = DialogPrimitive.Root -const DialogTrigger = DialogPrimitive.Trigger; +const DialogTrigger = DialogPrimitive.Trigger -const DialogPortal = DialogPrimitive.Portal; +const DialogPortal = DialogPrimitive.Portal -const DialogClose = DialogPrimitive.Close; +const DialogClose = DialogPrimitive.Close const DialogOverlay = React.forwardRef< React.ElementRef, @@ -26,8 +26,8 @@ const DialogOverlay = React.forwardRef< )} {...props} /> -)); -DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName const DialogContent = React.forwardRef< React.ElementRef, @@ -50,8 +50,8 @@ const DialogContent = React.forwardRef< -)); -DialogContent.displayName = DialogPrimitive.Content.displayName; +)) +DialogContent.displayName = DialogPrimitive.Content.displayName const DialogContentNoClose = React.forwardRef< React.ElementRef, @@ -70,9 +70,9 @@ const DialogContentNoClose = React.forwardRef< {children} -)); +)) DialogContentNoClose.displayName = - DialogPrimitive.Content.displayName + "NoClose"; + DialogPrimitive.Content.displayName + "NoClose" const DialogHeader = ({ className, @@ -85,8 +85,8 @@ const DialogHeader = ({ )} {...props} /> -); -DialogHeader.displayName = "DialogHeader"; +) +DialogHeader.displayName = "DialogHeader" const DialogFooter = ({ className, @@ -99,8 +99,8 @@ const DialogFooter = ({ )} {...props} /> -); -DialogFooter.displayName = "DialogFooter"; +) +DialogFooter.displayName = "DialogFooter" const DialogTitle = React.forwardRef< React.ElementRef, @@ -114,8 +114,8 @@ const DialogTitle = React.forwardRef< )} {...props} /> -)); -DialogTitle.displayName = DialogPrimitive.Title.displayName; +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName const DialogDescription = React.forwardRef< React.ElementRef, @@ -126,19 +126,19 @@ const DialogDescription = React.forwardRef< className={cn("text-sm text-muted-foreground", className)} {...props} /> -)); -DialogDescription.displayName = DialogPrimitive.Description.displayName; +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName export { Dialog, - DialogPortal, - DialogOverlay, - DialogTrigger, DialogClose, DialogContent, DialogContentNoClose, - DialogHeader, - DialogFooter, - DialogTitle, DialogDescription, -}; + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +} diff --git a/frontend/components/ui/dropdown-menu.tsx b/frontend/components/ui/dropdown-menu.tsx index 242b07a..e0ff4bd 100644 --- a/frontend/components/ui/dropdown-menu.tsx +++ b/frontend/components/ui/dropdown-menu.tsx @@ -1,12 +1,12 @@ "use client" -import * as React from "react" import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" import { CheckIcon, ChevronRightIcon, DotFilledIcon, } from "@radix-ui/react-icons" +import * as React from "react" import { cn } from "@/lib/utils" @@ -188,18 +188,18 @@ DropdownMenuShortcut.displayName = "DropdownMenuShortcut" export { DropdownMenu, - DropdownMenuTrigger, - DropdownMenuContent, - DropdownMenuItem, DropdownMenuCheckboxItem, - DropdownMenuRadioItem, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, DropdownMenuLabel, + DropdownMenuPortal, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, - DropdownMenuGroup, - DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, - DropdownMenuRadioGroup, + DropdownMenuTrigger, } diff --git a/frontend/components/ui/form.tsx b/frontend/components/ui/form.tsx index adeb1b6..cd173f8 100644 --- a/frontend/components/ui/form.tsx +++ b/frontend/components/ui/form.tsx @@ -1,6 +1,6 @@ -import * as React from "react" import * as LabelPrimitive from "@radix-ui/react-label" import { Slot } from "@radix-ui/react-slot" +import * as React from "react" import { Controller, ControllerProps, @@ -10,8 +10,8 @@ import { useFormContext, } from "react-hook-form" -import { cn } from "@/lib/utils" import { Label } from "@/components/ui/label" +import { cn } from "@/lib/utils" const Form = FormProvider @@ -165,12 +165,12 @@ const FormMessage = React.forwardRef< FormMessage.displayName = "FormMessage" export { - useFormField, Form, - FormItem, - FormLabel, FormControl, FormDescription, - FormMessage, FormField, + FormItem, + FormLabel, + FormMessage, + useFormField, } diff --git a/frontend/components/ui/label.tsx b/frontend/components/ui/label.tsx index 5341821..173797a 100644 --- a/frontend/components/ui/label.tsx +++ b/frontend/components/ui/label.tsx @@ -1,8 +1,8 @@ "use client" -import * as React from "react" import * as LabelPrimitive from "@radix-ui/react-label" import { cva, type VariantProps } from "class-variance-authority" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/frontend/components/ui/popover.tsx b/frontend/components/ui/popover.tsx index 29c7bd2..ec6e0a0 100644 --- a/frontend/components/ui/popover.tsx +++ b/frontend/components/ui/popover.tsx @@ -1,7 +1,7 @@ "use client" -import * as React from "react" import * as PopoverPrimitive from "@radix-ui/react-popover" +import * as React from "react" import { cn } from "@/lib/utils" @@ -30,4 +30,4 @@ const PopoverContent = React.forwardRef< )) PopoverContent.displayName = PopoverPrimitive.Content.displayName -export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } +export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } diff --git a/frontend/components/ui/resizable.tsx b/frontend/components/ui/resizable.tsx index 5942eb0..a42a132 100644 --- a/frontend/components/ui/resizable.tsx +++ b/frontend/components/ui/resizable.tsx @@ -42,4 +42,4 @@ const ResizableHandle = ({ ) -export { ResizablePanelGroup, ResizablePanel, ResizableHandle } +export { ResizableHandle, ResizablePanel, ResizablePanelGroup } diff --git a/frontend/components/ui/select.tsx b/frontend/components/ui/select.tsx index ac2a8f2..7a02bda 100644 --- a/frontend/components/ui/select.tsx +++ b/frontend/components/ui/select.tsx @@ -1,6 +1,5 @@ "use client" -import * as React from "react" import { CaretSortIcon, CheckIcon, @@ -8,6 +7,7 @@ import { ChevronUpIcon, } from "@radix-ui/react-icons" import * as SelectPrimitive from "@radix-ui/react-select" +import * as React from "react" import { cn } from "@/lib/utils" @@ -152,13 +152,13 @@ SelectSeparator.displayName = SelectPrimitive.Separator.displayName export { Select, - SelectGroup, - SelectValue, - SelectTrigger, SelectContent, - SelectLabel, + SelectGroup, SelectItem, - SelectSeparator, - SelectScrollUpButton, + SelectLabel, SelectScrollDownButton, + SelectScrollUpButton, + SelectSeparator, + SelectTrigger, + SelectValue, } diff --git a/frontend/components/ui/switch.tsx b/frontend/components/ui/switch.tsx index 5f4117f..cf107d3 100644 --- a/frontend/components/ui/switch.tsx +++ b/frontend/components/ui/switch.tsx @@ -1,7 +1,7 @@ "use client" -import * as React from "react" import * as SwitchPrimitives from "@radix-ui/react-switch" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/frontend/components/ui/tab.tsx b/frontend/components/ui/tab.tsx index a47bd41..0ef38e0 100644 --- a/frontend/components/ui/tab.tsx +++ b/frontend/components/ui/tab.tsx @@ -1,8 +1,8 @@ -"use client"; +"use client" -import { Loader2, X } from "lucide-react"; -import { Button } from "./button"; -import { MouseEvent, MouseEventHandler, useEffect } from "react"; +import { Loader2, X } from "lucide-react" +import { MouseEventHandler } from "react" +import { Button } from "./button" export default function Tab({ children, @@ -13,13 +13,13 @@ export default function Tab({ onClose, closing = false, }: { - children: React.ReactNode; - creating?: boolean; - saved?: boolean; - selected?: boolean; - onClick?: MouseEventHandler; - onClose?: () => void; - closing?: boolean; + children: React.ReactNode + creating?: boolean + saved?: boolean + selected?: boolean + onClick?: MouseEventHandler + onClose?: () => void + closing?: boolean }) { return ( - ); + ) } diff --git a/frontend/components/ui/table.tsx b/frontend/components/ui/table.tsx index c0df655..740dc7c 100644 --- a/frontend/components/ui/table.tsx +++ b/frontend/components/ui/table.tsx @@ -110,11 +110,11 @@ TableCaption.displayName = "TableCaption" export { Table, - TableHeader, TableBody, + TableCaption, + TableCell, TableFooter, TableHead, + TableHeader, TableRow, - TableCell, - TableCaption, } diff --git a/frontend/components/ui/userButton.tsx b/frontend/components/ui/userButton.tsx index 3391dc9..ba4eadf 100644 --- a/frontend/components/ui/userButton.tsx +++ b/frontend/components/ui/userButton.tsx @@ -1,23 +1,22 @@ -"use client"; +"use client" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, - DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { User } from "@/lib/types"; -import { useClerk } from "@clerk/nextjs"; -import { LogOut, Pencil, Sparkles } from "lucide-react"; -import { useRouter } from "next/navigation"; +} from "@/components/ui/dropdown-menu" +import { User } from "@/lib/types" +import { useClerk } from "@clerk/nextjs" +import { LogOut, Sparkles } from "lucide-react" +import { useRouter } from "next/navigation" export default function UserButton({ userData }: { userData: User }) { - if (!userData) return null; + if (!userData) return null - const { signOut } = useClerk(); - const router = useRouter(); + const { signOut } = useClerk() + const router = useRouter() return ( @@ -68,5 +67,5 @@ export default function UserButton({ userData }: { userData: User }) { - ); + ) } diff --git a/frontend/context/PreviewContext.tsx b/frontend/context/PreviewContext.tsx index 038fdd5..447dd90 100644 --- a/frontend/context/PreviewContext.tsx +++ b/frontend/context/PreviewContext.tsx @@ -1,34 +1,44 @@ "use client" -import React, { createContext, useContext, useState, useRef } from 'react'; -import { ImperativePanelHandle } from "react-resizable-panels"; +import React, { createContext, useContext, useRef, useState } from "react" +import { ImperativePanelHandle } from "react-resizable-panels" interface PreviewContextType { - isPreviewCollapsed: boolean; - setIsPreviewCollapsed: React.Dispatch>; - previewURL: string; - setPreviewURL: React.Dispatch>; - previewPanelRef: React.RefObject; + isPreviewCollapsed: boolean + setIsPreviewCollapsed: React.Dispatch> + previewURL: string + setPreviewURL: React.Dispatch> + previewPanelRef: React.RefObject } -const PreviewContext = createContext(undefined); +const PreviewContext = createContext(undefined) -export const PreviewProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const [isPreviewCollapsed, setIsPreviewCollapsed] = useState(true); - const [previewURL, setPreviewURL] = useState(""); - const previewPanelRef = useRef(null); +export const PreviewProvider: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { + const [isPreviewCollapsed, setIsPreviewCollapsed] = useState(true) + const [previewURL, setPreviewURL] = useState("") + const previewPanelRef = useRef(null) return ( - + {children} - ); -}; + ) +} export const usePreview = () => { - const context = useContext(PreviewContext); + const context = useContext(PreviewContext) if (context === undefined) { - throw new Error('usePreview must be used within a PreviewProvider'); + throw new Error("usePreview must be used within a PreviewProvider") } - return context; -}; \ No newline at end of file + return context +} diff --git a/frontend/context/SocketContext.tsx b/frontend/context/SocketContext.tsx index bc75834..fe87983 100644 --- a/frontend/context/SocketContext.tsx +++ b/frontend/context/SocketContext.tsx @@ -1,63 +1,65 @@ -"use client"; +"use client" -import React, { createContext, useContext, useEffect, useState } from 'react'; -import { io, Socket } from 'socket.io-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; + socket: Socket | null + setUserAndSandboxId: (userId: string, sandboxId: string) => void } -const SocketContext = createContext(undefined); +const SocketContext = createContext(undefined) -export const SocketProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const [socket, setSocket] = useState(null); - const [userId, setUserId] = useState(null); - const [sandboxId, setSandboxId] = useState(null); +export const SocketProvider: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { + const [socket, setSocket] = useState(null) + const [userId, setUserId] = useState(null) + const [sandboxId, setSandboxId] = useState(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); + 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("connect", () => { + console.log("Socket connected:", newSocket.id) + }) - newSocket.on('disconnect', () => { - console.log("Socket disconnected"); - }); + newSocket.on("disconnect", () => { + console.log("Socket disconnected") + }) return () => { - console.log("Disconnecting socket..."); - newSocket.disconnect(); - }; + console.log("Disconnecting socket...") + newSocket.disconnect() + } } - }, [userId, sandboxId]); + }, [userId, sandboxId]) const setUserAndSandboxId = (newUserId: string, newSandboxId: string) => { - setUserId(newUserId); - setSandboxId(newSandboxId); - }; + setUserId(newUserId) + setSandboxId(newSandboxId) + } const value = { socket, setUserAndSandboxId, - }; + } return ( - - {children} - - ); -}; + {children} + ) +} export const useSocket = (): SocketContextType => { - const context = useContext(SocketContext); + const context = useContext(SocketContext) if (!context) { - throw new Error('useSocket must be used within a SocketProvider'); + throw new Error("useSocket must be used within a SocketProvider") } - return context; -}; + return context +} diff --git a/frontend/context/TerminalContext.tsx b/frontend/context/TerminalContext.tsx index cb1cc47..a7f131a 100644 --- a/frontend/context/TerminalContext.tsx +++ b/frontend/context/TerminalContext.tsx @@ -1,33 +1,44 @@ -"use client"; +"use client" -import React, { createContext, useContext, useState } from 'react'; -import { Terminal } from '@xterm/xterm'; -import { createTerminal as createTerminalHelper, closeTerminal as closeTerminalHelper } from '@/lib/terminal'; -import { useSocket } from '@/context/SocketContext'; +import { useSocket } from "@/context/SocketContext" +import { + closeTerminal as closeTerminalHelper, + createTerminal as createTerminalHelper, +} from "@/lib/terminal" +import { Terminal } from "@xterm/xterm" +import React, { createContext, useContext, useState } from "react" interface TerminalContextType { - terminals: { id: string; terminal: Terminal | null }[]; - setTerminals: React.Dispatch>; - activeTerminalId: string; - setActiveTerminalId: React.Dispatch>; - creatingTerminal: boolean; - setCreatingTerminal: React.Dispatch>; - createNewTerminal: (command?: string) => Promise; - closeTerminal: (id: string) => void; - deploy: (callback: () => void) => void; + terminals: { id: string; terminal: Terminal | null }[] + setTerminals: React.Dispatch< + React.SetStateAction<{ id: string; terminal: Terminal | null }[]> + > + activeTerminalId: string + setActiveTerminalId: React.Dispatch> + creatingTerminal: boolean + setCreatingTerminal: React.Dispatch> + createNewTerminal: (command?: string) => Promise + closeTerminal: (id: string) => void + deploy: (callback: () => void) => void } -const TerminalContext = createContext(undefined); +const TerminalContext = createContext( + undefined +) -export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const { socket } = useSocket(); - const [terminals, setTerminals] = useState<{ id: string; terminal: Terminal | null }[]>([]); - const [activeTerminalId, setActiveTerminalId] = useState(''); - const [creatingTerminal, setCreatingTerminal] = useState(false); +export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { + const { socket } = useSocket() + const [terminals, setTerminals] = useState< + { id: string; terminal: Terminal | null }[] + >([]) + const [activeTerminalId, setActiveTerminalId] = useState("") + const [creatingTerminal, setCreatingTerminal] = useState(false) const createNewTerminal = async (command?: string): Promise => { - if (!socket) return; - setCreatingTerminal(true); + if (!socket) return + setCreatingTerminal(true) try { createTerminalHelper({ setTerminals, @@ -35,36 +46,36 @@ export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ chil setCreatingTerminal, command, socket, - }); + }) } catch (error) { - console.error("Error creating terminal:", error); + console.error("Error creating terminal:", error) } finally { - setCreatingTerminal(false); + setCreatingTerminal(false) } - }; + } const closeTerminal = (id: string) => { - if (!socket) return; - const terminalToClose = terminals.find(term => term.id === id); + if (!socket) return + const terminalToClose = terminals.find((term) => term.id === id) if (terminalToClose) { closeTerminalHelper({ term: terminalToClose, terminals, setTerminals, setActiveTerminalId, - setClosingTerminal: () => {}, + setClosingTerminal: () => {}, socket, activeTerminalId, - }); + }) } - }; + } 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...") socket?.emit("deploy", () => { - callback(); - }); + callback() + }) } const value = { @@ -76,20 +87,20 @@ export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ chil setCreatingTerminal, createNewTerminal, closeTerminal, - deploy - }; + deploy, + } return ( {children} - ); -}; + ) +} export const useTerminal = (): TerminalContextType => { - const context = useContext(TerminalContext); + const context = useContext(TerminalContext) if (!context) { - throw new Error('useTerminal must be used within a TerminalProvider'); + throw new Error("useTerminal must be used within a TerminalProvider") } - return context; -}; + return context +} diff --git a/frontend/lib/terminal.ts b/frontend/lib/terminal.ts index 6f45ee8..a91db3c 100644 --- a/frontend/lib/terminal.ts +++ b/frontend/lib/terminal.ts @@ -1,8 +1,8 @@ // Helper functions for terminal instances -import { createId } from "@paralleldrive/cuid2"; -import { Terminal } from "@xterm/xterm"; -import { Socket } from "socket.io-client"; +import { createId } from "@paralleldrive/cuid2" +import { Terminal } from "@xterm/xterm" +import { Socket } from "socket.io-client" export const createTerminal = ({ setTerminals, @@ -11,30 +11,33 @@ export const createTerminal = ({ command, socket, }: { - setTerminals: React.Dispatch>; - setActiveTerminalId: React.Dispatch>; - setCreatingTerminal: React.Dispatch>; - command?: string; - socket: Socket; - + setTerminals: React.Dispatch< + React.SetStateAction< + { + id: string + terminal: Terminal | null + }[] + > + > + setActiveTerminalId: React.Dispatch> + setCreatingTerminal: React.Dispatch> + command?: string + socket: Socket }) => { - setCreatingTerminal(true); - const id = createId(); - console.log("creating terminal, id:", id); + setCreatingTerminal(true) + const id = createId() + console.log("creating terminal, id:", id) - setTerminals((prev) => [...prev, { id, terminal: null }]); - setActiveTerminalId(id); + setTerminals((prev) => [...prev, { id, terminal: null }]) + setActiveTerminalId(id) setTimeout(() => { socket.emit("createTerminal", id, () => { - setCreatingTerminal(false); - if (command) socket.emit("terminalData", id, command + "\n"); - }); - }, 1000); -}; + setCreatingTerminal(false) + if (command) socket.emit("terminalData", id, command + "\n") + }) + }, 1000) +} export const closeTerminal = ({ term, @@ -44,32 +47,36 @@ export const closeTerminal = ({ setClosingTerminal, socket, activeTerminalId, -} : { - term: { - id: string; - terminal: Terminal | null +}: { + term: { + id: string + terminal: Terminal | null } - terminals: { - id: string; - terminal: Terminal | null + terminals: { + id: string + terminal: Terminal | null }[] - setTerminals: React.Dispatch> + setTerminals: React.Dispatch< + React.SetStateAction< + { + id: string + terminal: Terminal | null + }[] + > + > setActiveTerminalId: React.Dispatch> setClosingTerminal: React.Dispatch> socket: Socket activeTerminalId: string }) => { - const numTerminals = terminals.length; - const index = terminals.findIndex((t) => t.id === term.id); - if (index === -1) return; + const numTerminals = terminals.length + const index = terminals.findIndex((t) => t.id === term.id) + if (index === -1) return - setClosingTerminal(term.id); + setClosingTerminal(term.id) socket.emit("closeTerminal", term.id, () => { - setClosingTerminal(""); + setClosingTerminal("") const nextId = activeTerminalId === term.id @@ -78,17 +85,17 @@ export const closeTerminal = ({ : index < numTerminals - 1 ? terminals[index + 1].id : terminals[index - 1].id - : activeTerminalId; + : activeTerminalId - setTerminals((prev) => prev.filter((t) => t.id !== term.id)); + setTerminals((prev) => prev.filter((t) => t.id !== term.id)) if (!nextId) { - setActiveTerminalId(""); + setActiveTerminalId("") } else { - const nextTerminal = terminals.find((t) => t.id === nextId); + const nextTerminal = terminals.find((t) => t.id === nextId) if (nextTerminal) { - setActiveTerminalId(nextTerminal.id); + setActiveTerminalId(nextTerminal.id) } } - }); -}; \ No newline at end of file + }) +} diff --git a/frontend/lib/types.ts b/frontend/lib/types.ts index c6d144b..87850aa 100644 --- a/frontend/lib/types.ts +++ b/frontend/lib/types.ts @@ -1,65 +1,65 @@ // DB Types export type User = { - id: string; - name: string; - email: string; - generations: number; - sandbox: Sandbox[]; - usersToSandboxes: UsersToSandboxes[]; -}; + id: string + name: string + email: string + generations: number + sandbox: Sandbox[] + usersToSandboxes: UsersToSandboxes[] +} export type Sandbox = { - id: string; - name: string; - type: string; - visibility: "public" | "private"; - createdAt: Date; - userId: string; - usersToSandboxes: UsersToSandboxes[]; -}; + id: string + name: string + type: string + visibility: "public" | "private" + createdAt: Date + userId: string + usersToSandboxes: UsersToSandboxes[] +} export type UsersToSandboxes = { - userId: string; - sandboxId: string; - sharedOn: Date; -}; + userId: string + sandboxId: string + sharedOn: Date +} export type R2Files = { - objects: R2FileData[]; - truncated: boolean; - delimitedPrefixes: any[]; -}; + objects: R2FileData[] + truncated: boolean + delimitedPrefixes: any[] +} export type R2FileData = { - storageClass: string; - uploaded: string; - checksums: any; - httpEtag: string; - etag: string; - size: number; - version: string; - key: string; -}; + storageClass: string + uploaded: string + checksums: any + httpEtag: string + etag: string + size: number + version: string + key: string +} export type TFolder = { - id: string; - type: "folder"; - name: string; - children: (TFile | TFolder)[]; -}; + id: string + type: "folder" + name: string + children: (TFile | TFolder)[] +} export type TFile = { - id: string; - type: "file"; - name: string; -}; + id: string + type: "file" + name: string +} export type TTab = TFile & { - saved: boolean; -}; + saved: boolean +} export type TFileData = { - id: string; - data: string; -}; + id: string + data: string +} diff --git a/frontend/lib/utils.ts b/frontend/lib/utils.ts index 8fd506f..c5fa95e 100644 --- a/frontend/lib/utils.ts +++ b/frontend/lib/utils.ts @@ -1,8 +1,8 @@ import { type ClassValue, clsx } from "clsx" // import { toast } from "sonner" import { twMerge } from "tailwind-merge" -import { Sandbox, TFile, TFolder } from "./types" import fileExtToLang from "./file-extension-to-language.json" +import { Sandbox, TFile, TFolder } from "./types" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs))