ui improvements
This commit is contained in:
parent
159e7b62e2
commit
5ae5c226ba
@ -52,7 +52,7 @@ export default async function CodePage({ params }: { params: { id: string } }) {
|
|||||||
<Room id={sandboxId}>
|
<Room id={sandboxId}>
|
||||||
<Navbar userData={userData} sandboxData={sandboxData} shared={shared} />
|
<Navbar userData={userData} sandboxData={sandboxData} shared={shared} />
|
||||||
<div className="w-screen flex grow">
|
<div className="w-screen flex grow">
|
||||||
<CodeEditor userData={userData} sandboxId={sandboxId} />
|
<CodeEditor userData={userData} sandboxData={sandboxData} />
|
||||||
</div>
|
</div>
|
||||||
</Room>
|
</Room>
|
||||||
</div>
|
</div>
|
||||||
|
39
frontend/components/dashboard/about.tsx
Normal file
39
frontend/components/dashboard/about.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
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,
|
||||||
|
setOpen,
|
||||||
|
}: {
|
||||||
|
open: boolean
|
||||||
|
setOpen: (open: boolean) => void
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>About this project</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
Sandbox is an open-source cloud-based code editing environment with
|
||||||
|
custom AI code autocompletion and real-time collaboration. The
|
||||||
|
infrastructure runs on Docker and Kubernetes to scale automatically
|
||||||
|
based on resource usage.
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
@ -17,6 +17,7 @@ import DashboardSharedWithMe from "./shared"
|
|||||||
import NewProjectModal from "./newProject"
|
import NewProjectModal from "./newProject"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { useSearchParams } from "next/navigation"
|
import { useSearchParams } from "next/navigation"
|
||||||
|
import AboutModal from "./about"
|
||||||
|
|
||||||
type TScreen = "projects" | "shared" | "settings" | "search"
|
type TScreen = "projects" | "shared" | "settings" | "search"
|
||||||
|
|
||||||
@ -36,6 +37,7 @@ export default function Dashboard({
|
|||||||
const [screen, setScreen] = useState<TScreen>("projects")
|
const [screen, setScreen] = useState<TScreen>("projects")
|
||||||
|
|
||||||
const [newProjectModalOpen, setNewProjectModalOpen] = useState(false)
|
const [newProjectModalOpen, setNewProjectModalOpen] = useState(false)
|
||||||
|
const [aboutModalOpen, setAboutModalOpen] = useState(false)
|
||||||
|
|
||||||
const activeScreen = (s: TScreen) => {
|
const activeScreen = (s: TScreen) => {
|
||||||
if (screen === s) return "justify-start"
|
if (screen === s) return "justify-start"
|
||||||
@ -51,6 +53,7 @@ export default function Dashboard({
|
|||||||
open={newProjectModalOpen}
|
open={newProjectModalOpen}
|
||||||
setOpen={setNewProjectModalOpen}
|
setOpen={setNewProjectModalOpen}
|
||||||
/>
|
/>
|
||||||
|
<AboutModal open={aboutModalOpen} setOpen={setAboutModalOpen} />
|
||||||
<div className="flex grow w-full">
|
<div className="flex grow w-full">
|
||||||
<div className="w-56 shrink-0 border-r border-border p-4 justify-between flex flex-col">
|
<div className="w-56 shrink-0 border-r border-border p-4 justify-between flex flex-col">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
@ -97,6 +100,7 @@ export default function Dashboard({
|
|||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Button
|
<Button
|
||||||
|
onClick={() => setAboutModalOpen(true)}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className="justify-start font-normal text-muted-foreground"
|
className="justify-start font-normal text-muted-foreground"
|
||||||
>
|
>
|
||||||
|
@ -12,6 +12,7 @@ import Image from "next/image"
|
|||||||
import Button from "../ui/customButton"
|
import Button from "../ui/customButton"
|
||||||
import { ChevronRight } from "lucide-react"
|
import { ChevronRight } from "lucide-react"
|
||||||
import Avatar from "../ui/avatar"
|
import Avatar from "../ui/avatar"
|
||||||
|
import Link from "next/link"
|
||||||
|
|
||||||
export default function DashboardSharedWithMe({
|
export default function DashboardSharedWithMe({
|
||||||
shared,
|
shared,
|
||||||
@ -66,9 +67,11 @@ export default function DashboardSharedWithMe({
|
|||||||
{new Date(sandbox.sharedOn).toLocaleDateString()}
|
{new Date(sandbox.sharedOn).toLocaleDateString()}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
|
<Link href={`/code/${sandbox.id}`}>
|
||||||
<Button>
|
<Button>
|
||||||
Open <ChevronRight className="w-4 h-4 ml-2" />
|
Open <ChevronRight className="w-4 h-4 ml-2" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Link>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
|
@ -39,16 +39,16 @@ import EditorTerminal from "./terminal"
|
|||||||
import { Button } from "../ui/button"
|
import { Button } from "../ui/button"
|
||||||
import GenerateInput from "./generate"
|
import GenerateInput from "./generate"
|
||||||
import { TFile, TFileData, TFolder, TTab } from "./sidebar/types"
|
import { TFile, TFileData, TFolder, TTab } from "./sidebar/types"
|
||||||
import { User } from "@/lib/types"
|
import { Sandbox, User } from "@/lib/types"
|
||||||
import { processFileType, validateName } from "@/lib/utils"
|
import { processFileType, validateName } from "@/lib/utils"
|
||||||
import { Cursors } from "./live/cursors"
|
import { Cursors } from "./live/cursors"
|
||||||
|
|
||||||
export default function CodeEditor({
|
export default function CodeEditor({
|
||||||
userData,
|
userData,
|
||||||
sandboxId,
|
sandboxData,
|
||||||
}: {
|
}: {
|
||||||
userData: User
|
userData: User
|
||||||
sandboxId: string
|
sandboxData: Sandbox
|
||||||
}) {
|
}) {
|
||||||
const [files, setFiles] = useState<(TFolder | TFile)[]>([])
|
const [files, setFiles] = useState<(TFolder | TFile)[]>([])
|
||||||
const [tabs, setTabs] = useState<TTab[]>([])
|
const [tabs, setTabs] = useState<TTab[]>([])
|
||||||
@ -72,6 +72,7 @@ export default function CodeEditor({
|
|||||||
const [provider, setProvider] = useState<TypedLiveblocksProvider>()
|
const [provider, setProvider] = useState<TypedLiveblocksProvider>()
|
||||||
const [ai, setAi] = useState(false)
|
const [ai, setAi] = useState(false)
|
||||||
|
|
||||||
|
const isOwner = sandboxData.userId === userData.id
|
||||||
const clerk = useClerk()
|
const clerk = useClerk()
|
||||||
const room = useRoom()
|
const room = useRoom()
|
||||||
|
|
||||||
@ -247,7 +248,7 @@ export default function CodeEditor({
|
|||||||
}, [decorations.options])
|
}, [decorations.options])
|
||||||
|
|
||||||
const socket = io(
|
const socket = io(
|
||||||
`http://localhost:4000?userId=${userData.id}&sandboxId=${sandboxId}`
|
`http://localhost:4000?userId=${userData.id}&sandboxId=${sandboxData.id}`
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -506,7 +507,7 @@ export default function CodeEditor({
|
|||||||
if (type === "file") {
|
if (type === "file") {
|
||||||
setFiles((prev) => [
|
setFiles((prev) => [
|
||||||
...prev,
|
...prev,
|
||||||
{ id: `projects/${sandboxId}/${name}`, name, type: "file" },
|
{ id: `projects/${sandboxData.id}/${name}`, name, type: "file" },
|
||||||
])
|
])
|
||||||
} else {
|
} else {
|
||||||
console.log("adding folder")
|
console.log("adding folder")
|
||||||
|
@ -27,6 +27,8 @@ export default function Navbar({
|
|||||||
const [isEditOpen, setIsEditOpen] = useState(false)
|
const [isEditOpen, setIsEditOpen] = useState(false)
|
||||||
const [isShareOpen, setIsShareOpen] = useState(false)
|
const [isShareOpen, setIsShareOpen] = useState(false)
|
||||||
|
|
||||||
|
const isOwner = sandboxData.userId === userData.id
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<EditSandboxModal
|
<EditSandboxModal
|
||||||
@ -50,21 +52,25 @@ export default function Navbar({
|
|||||||
</Link>
|
</Link>
|
||||||
<div className="text-sm font-medium flex items-center">
|
<div className="text-sm font-medium flex items-center">
|
||||||
{sandboxData.name}
|
{sandboxData.name}
|
||||||
|
{isOwner ? (
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsEditOpen(true)}
|
onClick={() => setIsEditOpen(true)}
|
||||||
className="h-7 w-7 ml-2 flex items-center justify-center bg-transparent hover:bg-muted-foreground/25 cursor-pointer rounded-md ring-offset-2 transition-all ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
className="h-7 w-7 ml-2 flex items-center justify-center bg-transparent hover:bg-muted-foreground/25 cursor-pointer rounded-md ring-offset-2 transition-all ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
>
|
>
|
||||||
<Pencil className="w-4 h-4" />
|
<Pencil className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<Avatars />
|
<Avatars />
|
||||||
|
|
||||||
|
{isOwner ? (
|
||||||
<Button variant="outline" onClick={() => setIsShareOpen(true)}>
|
<Button variant="outline" onClick={() => setIsShareOpen(true)}>
|
||||||
<Users className="w-4 h-4 mr-2" />
|
<Users className="w-4 h-4 mr-2" />
|
||||||
Share
|
Share
|
||||||
</Button>
|
</Button>
|
||||||
|
) : null}
|
||||||
<UserButton userData={userData} />
|
<UserButton userData={userData} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,6 +5,13 @@ import { useEffect, useRef, useState } from "react"
|
|||||||
import { getIconForFolder, getIconForOpenFolder } from "vscode-icons-js"
|
import { getIconForFolder, getIconForOpenFolder } from "vscode-icons-js"
|
||||||
import { TFile, TFolder, TTab } from "./types"
|
import { TFile, TFolder, TTab } from "./types"
|
||||||
import SidebarFile from "./file"
|
import SidebarFile from "./file"
|
||||||
|
import {
|
||||||
|
ContextMenu,
|
||||||
|
ContextMenuContent,
|
||||||
|
ContextMenuItem,
|
||||||
|
ContextMenuTrigger,
|
||||||
|
} from "@/components/ui/context-menu"
|
||||||
|
import { Pencil, Trash2 } from "lucide-react"
|
||||||
|
|
||||||
export default function SidebarFolder({
|
export default function SidebarFolder({
|
||||||
data,
|
data,
|
||||||
@ -39,12 +46,9 @@ export default function SidebarFolder({
|
|||||||
}, [editing])
|
}, [editing])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<ContextMenu>
|
||||||
<div
|
<ContextMenuTrigger
|
||||||
onClick={() => setIsOpen((prev) => !prev)}
|
onClick={() => setIsOpen((prev) => !prev)}
|
||||||
onDoubleClick={() => {
|
|
||||||
setEditing(true)
|
|
||||||
}}
|
|
||||||
className="w-full flex items-center h-7 px-1 transition-colors hover:bg-secondary rounded-sm cursor-pointer"
|
className="w-full flex items-center h-7 px-1 transition-colors hover:bg-secondary rounded-sm cursor-pointer"
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
@ -74,7 +78,29 @@ export default function SidebarFolder({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</ContextMenuTrigger>
|
||||||
|
<ContextMenuContent>
|
||||||
|
<ContextMenuItem
|
||||||
|
onClick={() => {
|
||||||
|
console.log("rename")
|
||||||
|
setEditing(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Pencil className="w-4 h-4 mr-2" />
|
||||||
|
Rename
|
||||||
|
</ContextMenuItem>
|
||||||
|
<ContextMenuItem
|
||||||
|
// disabled={pendingDelete}
|
||||||
|
onClick={() => {
|
||||||
|
console.log("delete")
|
||||||
|
// setPendingDelete(true)
|
||||||
|
// handleDeleteFile(data)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trash2 className="w-4 h-4 mr-2" />
|
||||||
|
Delete
|
||||||
|
</ContextMenuItem>
|
||||||
|
</ContextMenuContent>
|
||||||
{isOpen ? (
|
{isOpen ? (
|
||||||
<div className="flex w-full items-stretch">
|
<div className="flex w-full items-stretch">
|
||||||
<div className="w-[1px] bg-border mx-2 h-full"></div>
|
<div className="w-[1px] bg-border mx-2 h-full"></div>
|
||||||
@ -102,6 +128,6 @@ export default function SidebarFolder({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</ContextMenu>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user