mirror of
https://github.com/supabase/supabase.git
synced 2026-10-09 03:15:06 +03:00
## Context More groundwork for the Explorer - this one's focused on initializing the valtio store for managing notebooks Store architecture will follow closely with the existing sql-editor-store No data persistence yet, but can test creating a new notebook <img width="195" height="143" alt="image" src="https://github.com/user-attachments/assets/8656fb5b-3a8e-4f71-b2ce-d2f34ca9b552" /> Which should open a placeholder page <img width="1387" height="527" alt="image" src="https://github.com/user-attachments/assets/4a81b1ae-a740-40e6-9d33-29fa4f83b541" /> Closing the notebook brings you back to the explorer home page <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added support for creating and opening project notebooks from the Explorer. * Added notebook tabs alongside existing entity and SQL tabs. * Added notebook management, including loading, renaming, removing, editing cells, and tracking unsaved changes. * Added support for SQL, logs, and Markdown notebook cells. * Added dedicated notebook routes and an initial notebook editor view. * Added notebook icons throughout the Explorer interface. * **Documentation** * Documented session-scoped notebook state for query results and row limits. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
121 lines
4.0 KiB
TypeScript
121 lines
4.0 KiB
TypeScript
import { useParams } from 'common'
|
|
import { AnimatePresence, motion } from 'framer-motion'
|
|
import { Home, MessageCirclePlus, NotebookText, Plus } from 'lucide-react'
|
|
import Link from 'next/link'
|
|
import { useRouter } from 'next/router'
|
|
import { ComponentProps, ReactNode, useState } from 'react'
|
|
import { cn, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from 'ui'
|
|
|
|
import { ProjectLayoutWithAuth } from '../ProjectLayout'
|
|
import { EditorTabs } from '../Tabs/Tabs'
|
|
import { type ExplorerResourceType } from './ExplorerLayout.constants'
|
|
import { ExplorerNavChats } from './ExplorerNavChats'
|
|
import { ExplorerNavHome } from './ExplorerNavHome'
|
|
import { ExplorerNavNotebooks } from './ExplorerNavNotebooks'
|
|
import { useCreateNotebook } from '@/components/interfaces/Explorer/hooks'
|
|
|
|
export interface ExplorerLayoutProps extends ComponentProps<typeof ProjectLayoutWithAuth> {
|
|
children: ReactNode
|
|
title?: string
|
|
}
|
|
|
|
export const ExplorerLayout = ({ browserTitle, children, title }: ExplorerLayoutProps) => {
|
|
const [section, setSection] = useState<ExplorerResourceType>()
|
|
|
|
// [Joshen] Temporary, to hook up with tabs store
|
|
const activeTabLabel = 'Active Tab Label'
|
|
|
|
const mergedBrowserTitle = {
|
|
...browserTitle,
|
|
section: title ?? browserTitle?.section,
|
|
entity: browserTitle?.entity ?? activeTabLabel,
|
|
}
|
|
|
|
return (
|
|
<ProjectLayoutWithAuth
|
|
product="Explorer"
|
|
browserTitle={mergedBrowserTitle}
|
|
productMenu={
|
|
<div className="relative h-full overflow-hidden">
|
|
<AnimatePresence mode="wait">
|
|
{section === undefined && <ExplorerNavHome key="home" onSelectSection={setSection} />}
|
|
{section === 'notebook' && (
|
|
<ExplorerNavNotebooks key="notebooks" onBack={() => setSection(undefined)} />
|
|
)}
|
|
{section === 'chat' && (
|
|
<ExplorerNavChats key="chats" onBack={() => setSection(undefined)} />
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
}
|
|
>
|
|
<div className="flex flex-col h-full">
|
|
<div className={cn('h-10 md:min-h-(--header-height) flex items-center bg-surface-100')}>
|
|
<EditorTabs
|
|
isCollapseButtonHidden
|
|
customTabs={<HomeTabButton />}
|
|
newTabButton={<NewTabButton />}
|
|
/>
|
|
</div>
|
|
<div className="h-full">{children}</div>
|
|
</div>
|
|
</ProjectLayoutWithAuth>
|
|
)
|
|
}
|
|
|
|
const TabClassName =
|
|
'flex items-center justify-center min-w-(--header-height) min-h-(--header-height) hover:bg-surface-100 shrink-0 border-b'
|
|
|
|
const HomeTabButton = () => {
|
|
const router = useRouter()
|
|
const { ref } = useParams()
|
|
const isActive = router.pathname.endsWith('/explorer')
|
|
|
|
return (
|
|
<Link href={`/project/${ref}/explorer`} className={cn(TabClassName, 'border-r')}>
|
|
<Home
|
|
size={14}
|
|
strokeWidth={1.5}
|
|
className={cn(
|
|
isActive ? 'text-foreground' : 'text-foreground-lighter hover:text-foreground-light'
|
|
)}
|
|
/>
|
|
<span className="sr-only">Open Explorer home</span>
|
|
</Link>
|
|
)
|
|
}
|
|
|
|
const NewTabButton = () => {
|
|
const { createNotebook } = useCreateNotebook()
|
|
|
|
return (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<motion.button
|
|
className={TabClassName}
|
|
onClick={() => {}}
|
|
initial={{ opacity: 0, scale: 0.8, x: -10 }}
|
|
animate={{ opacity: 1, scale: 1, x: 0 }}
|
|
transition={{ duration: 0.2 }}
|
|
>
|
|
<Plus
|
|
size={16}
|
|
strokeWidth={1.5}
|
|
className="text-foreground-lighter hover:text-foreground-light"
|
|
/>
|
|
</motion.button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent className="w-40" align="end">
|
|
<DropdownMenuItem className="gap-x-2" onClick={() => createNotebook()}>
|
|
<NotebookText size={14} />
|
|
<span>New notebook</span>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem disabled className="gap-x-2">
|
|
<MessageCirclePlus size={14} />
|
|
<span>New chat</span>
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
)
|
|
}
|