mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
+8








e273d2b818
## What
- Moves the temporary "Switch to SQL Editor" button out of the Explorer
sidebar header into a footer section ("Looking for snippets?") with a
short explanation and an **Open SQL Editor** button.
- Replaces the header slot with a menu for the Explorer startup
preference (**Start page** / **SQL query**), instead of linking out to
account preferences.
## How to test
1. Enable the Explorer feature preview and open
`/project/<ref>/explorer`.
2. **Header menu:** click the ⋮ button next to the Explorer title. Pick
**SQL query**, then check that **Explorer startup** on `/account/me`
shows the same value (and vice versa).
3. **Footer:** click **Open SQL Editor**. You should land in the SQL
Editor with the **Back to Explorer** button in its title bar.
4. Open **Notebooks** or **Chats** in the sidebar and check that the
menu and footer are hidden there, like the old button was.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Choose whether the Explorer opens to the Start page or SQL query from
the Explorer preferences menu. Your selection is saved and retained when
you reopen the menu.
* Access the SQL Editor from the Explorer’s sidebar footer.
* Explorer preferences are available from the Explorer navigation
header.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Tyler <dshukertjr@gmail.com>
Co-authored-by: Nik Richers <nrichers@gmail.com>
Co-authored-by: Danny White <3104761+dnywh@users.noreply.github.com>
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
Co-authored-by: Jordi Enric <37541088+jordienr@users.noreply.github.com>
Co-authored-by: Gildas Garcia <1122076+djhi@users.noreply.github.com>
Co-authored-by: Katerina Skroumpelou <mandarini@users.noreply.github.com>
Co-authored-by: Franek <franek@ferly.co.uk>
Co-authored-by: Franek Richardson <franek@supabase.io>
Co-authored-by: Michał Olszewski <35968924+charconstpointer@users.noreply.github.com>
Co-authored-by: Steven Eubank <47563310+smeubank@users.noreply.github.com>
Co-authored-by: Anthony Lio <lionnet.ant@gmail.com>
Co-authored-by: Joey Lei <6957385+leizerbeam@users.noreply.github.com>
Co-authored-by: Ali Waseem <waseema393@gmail.com>
Co-authored-by: Samir Ketema <6003000+samirketema@users.noreply.github.com>
Co-authored-by: K-Dog (Kevin) <k.grueneberg1994@gmail.com>
188 lines
6.7 KiB
TypeScript
188 lines
6.7 KiB
TypeScript
import { AnimatePresence, motion } from 'framer-motion'
|
|
import { Home, MessageCirclePlus, NotebookText, Plus, SquareCode } from 'lucide-react'
|
|
import { ComponentProps, ReactNode, useEffect, useEffectEvent, useState } from 'react'
|
|
import {
|
|
cn,
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
TabsTrigger,
|
|
} from 'ui'
|
|
|
|
import { ProjectLayoutWithAuth } from '../ProjectLayout'
|
|
import { EditorTabs } from '../Tabs/Tabs'
|
|
import { type ExplorerResourceType } from './ExplorerLayout.constants'
|
|
import { ExplorerNavChats } from './ExplorerNavChats'
|
|
import { ExplorerNavHeader } from './ExplorerNavHeader'
|
|
import { ExplorerNavHome } from './ExplorerNavHome'
|
|
import { ExplorerNavNotebooks } from './ExplorerNavNotebooks'
|
|
import { ExplorerPreferencesDropdown } from './ExplorerPreferencesDropdown'
|
|
import { ExplorerProvider } from './ExplorerProvider'
|
|
import { useExplorerPreferences } from '@/components/interfaces/Account/Preferences/useExplorerPreferences'
|
|
import { ExplorerNotebookTabCoordinator } from '@/components/interfaces/Explorer/ExplorerNotebookTabCoordinator'
|
|
import { ExplorerQueryTabCoordinator } from '@/components/interfaces/Explorer/ExplorerQueryTabCoordinator'
|
|
import {
|
|
useCreateChat,
|
|
useCreateNotebook,
|
|
useCreateQuery,
|
|
} from '@/components/interfaces/Explorer/hooks'
|
|
import { useDashboardHistory } from '@/hooks/misc/useDashboardHistory'
|
|
import {
|
|
editorEntityTypes,
|
|
EXPLORER_HOME_TAB,
|
|
EXPLORER_HOME_TAB_ID,
|
|
useTabsStateSnapshot,
|
|
type Tab,
|
|
} from '@/state/tabs'
|
|
|
|
export interface ExplorerLayoutProps extends ComponentProps<typeof ProjectLayoutWithAuth> {
|
|
children: ReactNode
|
|
title?: string
|
|
}
|
|
|
|
export const ExplorerLayout = ({ browserTitle, children, title }: ExplorerLayoutProps) => {
|
|
const tabs = useTabsStateSnapshot()
|
|
const { setLastVisitedExplorerTab } = useDashboardHistory()
|
|
const { home, hasCompletedOnboarding, isReady } = useExplorerPreferences()
|
|
const shouldShowHomeTab = isReady && (!hasCompletedOnboarding || home === 'home')
|
|
|
|
const [section, setSection] = useState<ExplorerResourceType>()
|
|
|
|
const activeTab = tabs.activeTab ? tabs.tabsMap[tabs.activeTab] : undefined
|
|
const isActiveExplorerTab =
|
|
activeTab !== undefined && editorEntityTypes.explorer.includes(activeTab.type)
|
|
const activeTabLabel = isActiveExplorerTab ? activeTab.label || 'Untitled' : 'Explorer'
|
|
|
|
const mergedBrowserTitle = {
|
|
...browserTitle,
|
|
section: title ?? browserTitle?.section,
|
|
entity: browserTitle?.entity ?? activeTabLabel,
|
|
}
|
|
|
|
const handleTabChange = (id: string) => {
|
|
if (id === EXPLORER_HOME_TAB_ID) setLastVisitedExplorerTab(undefined)
|
|
}
|
|
|
|
return (
|
|
<ExplorerProvider>
|
|
<ProjectLayoutWithAuth
|
|
product="Explorer"
|
|
browserTitle={mergedBrowserTitle}
|
|
productMenuHeader={
|
|
<ExplorerNavHeader
|
|
section={section}
|
|
onBack={() => setSection(undefined)}
|
|
rootAction={<ExplorerPreferencesDropdown />}
|
|
/>
|
|
}
|
|
productMenu={
|
|
<div className="relative h-full overflow-hidden">
|
|
<AnimatePresence mode="wait">
|
|
{section === undefined && <ExplorerNavHome key="home" onSelectSection={setSection} />}
|
|
{section === 'notebook' && <ExplorerNavNotebooks key="notebooks" />}
|
|
{section === 'chat' && <ExplorerNavChats key="chats" />}
|
|
</AnimatePresence>
|
|
</div>
|
|
}
|
|
>
|
|
<ExplorerQueryTabCoordinator />
|
|
|
|
<ExplorerNotebookTabCoordinator />
|
|
|
|
<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={shouldShowHomeTab ? <HomeTabButton /> : undefined}
|
|
newTabButton={<NewTabButton />}
|
|
onTabChange={handleTabChange}
|
|
/>
|
|
</div>
|
|
<div className="flex-grow min-h-0">{children}</div>
|
|
</div>
|
|
</ProjectLayoutWithAuth>
|
|
</ExplorerProvider>
|
|
)
|
|
}
|
|
|
|
const TabClassName =
|
|
'flex items-center justify-center min-w-(--header-height) min-h-(--header-height) hover:bg-surface-100 shrink-0'
|
|
|
|
const HomeTabButton = () => {
|
|
const tabs = useTabsStateSnapshot()
|
|
|
|
const openTabs = tabs.openTabs
|
|
.map((id) => tabs.tabsMap[id])
|
|
.filter((tab) => tab !== undefined) as Tab[]
|
|
const explorerTabs = openTabs.filter((tab) => editorEntityTypes['explorer']?.includes(tab.type))
|
|
|
|
const ensureHomeTab = useEffectEvent(() => {
|
|
tabs.ensurePinnedTab(EXPLORER_HOME_TAB)
|
|
})
|
|
|
|
useEffect(() => ensureHomeTab(), [])
|
|
|
|
return (
|
|
<TabsTrigger
|
|
value={EXPLORER_HOME_TAB_ID}
|
|
className={cn(
|
|
TabClassName,
|
|
'relative group border-b border-default shadow-none!',
|
|
explorerTabs.length === 0 && 'border-r border-r-default!',
|
|
'bg-dash-sidebar/50 dark:bg-surface-100/50',
|
|
'data-[state=active]:bg-dash-sidebar dark:data-[state=active]:bg-surface-100',
|
|
'data-[state=active]:border-b-background-dash-sidebar dark:data-[state=active]:border-b-background-surface-100'
|
|
)}
|
|
>
|
|
<Home
|
|
size={14}
|
|
strokeWidth={1.5}
|
|
className="text-foreground-lighter transition-colors group-hover:text-foreground-light group-data-[state=active]:text-foreground"
|
|
/>
|
|
<span className="sr-only">Open Explorer home</span>
|
|
<div className="absolute w-full top-0 left-0 right-0 h-px bg-foreground opacity-0 group-data-[state=active]:opacity-100" />
|
|
</TabsTrigger>
|
|
)
|
|
}
|
|
|
|
const NewTabButton = () => {
|
|
const { createNotebook } = useCreateNotebook()
|
|
const { createQuery } = useCreateQuery()
|
|
const { createChat } = useCreateChat()
|
|
|
|
return (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<motion.button
|
|
className={cn(TabClassName, 'border-b')}
|
|
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={() => createQuery()}>
|
|
<SquareCode size={14} />
|
|
<span>Run SQL</span>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem className="gap-x-2" onClick={() => createNotebook()}>
|
|
<NotebookText size={14} />
|
|
<span>New notebook</span>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem className="gap-x-2" onClick={() => createChat()}>
|
|
<MessageCirclePlus size={14} />
|
|
<span>New chat</span>
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
)
|
|
}
|