mirror of
https://github.com/supabase/supabase.git
synced 2026-10-13 21:29:08 +03:00
feat(sql-editor): consolidate controls into a single top bar (#47792)
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Refactor / UI improvement ## What is the current behavior? SQL editor controls are split across two rows: a bar between the editor and results (tabs, download, run/save/format/db-selector/role/favorite/intellisense) and a separate footer at the very bottom (row count + limit dropdown). ## What is the new behavior? All controls are consolidated into a single top bar above the editor (⋮ overflow menu, database selector, role impersonation, limit, save, run). The bar between the editor and results now holds only result-relevant information: the Results/Explain/Chart tabs, the row-count summary, and the Export button. The limit dropdown is now always visible as a persistent query setting.
This commit is contained in:
1 parent
93ebd8adf1
commit
3461d60aba
3 files changed
+82
-100
No files matched your search
@@ -9,32 +9,17 @@ import {
|
||||
import { wrapWithRollback } from '@supabase/pg-meta/src/query'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { IS_PLATFORM, LOCAL_STORAGE_KEYS, useFlag, useParams } from 'common'
|
||||
import { ChevronUp, Loader2 } from 'lucide-react'
|
||||
import { Loader2 } from 'lucide-react'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useCallback, useEffect, useEffectEvent, useMemo, useRef, useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import {
|
||||
Button,
|
||||
cn,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from 'ui'
|
||||
import { cn, ResizableHandle, ResizablePanel, ResizablePanelGroup } from 'ui'
|
||||
|
||||
import { useSqlEditorDiff, useSqlEditorPrompt } from './hooks'
|
||||
import { RunQueryWarningModal } from './RunQueryWarningModal'
|
||||
import {
|
||||
generateSnippetTitle,
|
||||
ROWS_PER_PAGE_OPTIONS,
|
||||
sqlAiDisclaimerComment,
|
||||
untitledSnippetTitle,
|
||||
} from './SQLEditor.constants'
|
||||
@@ -57,6 +42,7 @@ import {
|
||||
suffixWithLimit,
|
||||
} from './SQLEditor.utils'
|
||||
import { useAddDefinitions } from './useAddDefinitions'
|
||||
import { UtilityActions } from './UtilityPanel/UtilityActions'
|
||||
import { UtilityPanel } from './UtilityPanel/UtilityPanel'
|
||||
import {
|
||||
isExplainQuery,
|
||||
@@ -65,7 +51,6 @@ import {
|
||||
} from '@/components/interfaces/ExplainVisualizer/ExplainVisualizer.utils'
|
||||
import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
|
||||
import ResizableAIWidget from '@/components/ui/AIEditor/ResizableAIWidget'
|
||||
import { GridFooter } from '@/components/ui/GridFooter'
|
||||
import { useSqlTitleGenerateMutation } from '@/data/ai/sql-title-mutation'
|
||||
import { useDatabaseEventTriggersQuery } from '@/data/database-event-triggers/database-event-triggers-query'
|
||||
import { constructHeaders, isValidConnString } from '@/data/fetchers'
|
||||
@@ -207,7 +192,6 @@ export const SQLEditor = () => {
|
||||
const id = !urlId || urlId === 'new' ? generatedId : urlId
|
||||
|
||||
const limit = sessionSnap.limit
|
||||
const results = sessionSnap.results[id]?.[0]
|
||||
const snippetIsLoading = !(
|
||||
id in snapV2.snippets && snapV2.snippets[id].snippet.content !== undefined
|
||||
)
|
||||
@@ -916,9 +900,18 @@ export const SQLEditor = () => {
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="flex h-full">
|
||||
<div className="flex flex-col h-full">
|
||||
<UtilityActions
|
||||
id={id}
|
||||
isExecuting={isExecuting}
|
||||
isDisabled={isDiffOpen}
|
||||
hasSelection={hasSelection}
|
||||
prettifyQuery={prettifyQuery}
|
||||
executeQuery={executeQueryFromButton}
|
||||
className="px-4 min-h-[42px] border-b shrink-0"
|
||||
/>
|
||||
<ResizablePanelGroup
|
||||
className="relative"
|
||||
className="relative flex-1 min-h-0"
|
||||
orientation="vertical"
|
||||
autoSaveId={LOCAL_STORAGE_KEYS.SQL_EDITOR_SPLIT_SIZE}
|
||||
>
|
||||
@@ -1047,9 +1040,6 @@ export const SQLEditor = () => {
|
||||
isExecuting={isExecuting}
|
||||
isExplainExecuting={isExplainExecuting}
|
||||
isDisabled={isDiffOpen}
|
||||
hasSelection={hasSelection}
|
||||
prettifyQuery={prettifyQuery}
|
||||
executeQuery={executeQueryFromButton}
|
||||
executeExplainQuery={executeExplainQuery}
|
||||
showExplainTab={!disablePrettyExplain}
|
||||
onDebug={onDebug}
|
||||
@@ -1059,63 +1049,6 @@ export const SQLEditor = () => {
|
||||
/>
|
||||
)}
|
||||
</ResizablePanel>
|
||||
|
||||
<div className="h-9">
|
||||
{results?.rows !== undefined && !isExecuting && (
|
||||
<GridFooter className="flex items-center justify-between gap-2">
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<p className="text-xs">
|
||||
<span className="text-foreground">
|
||||
{results.rows.length} row{results.rows.length > 1 ? 's' : ''}
|
||||
</span>
|
||||
<span className="text-foreground-lighter ml-1">
|
||||
{results.autoLimit !== undefined &&
|
||||
` (Limited to only ${results.autoLimit} rows)`}
|
||||
</span>
|
||||
</p>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs">
|
||||
<p className="flex flex-col gap-y-1">
|
||||
<span>
|
||||
Results are automatically limited to preserve browser performance, in
|
||||
particular if your query returns an exceptionally large number of rows.
|
||||
</span>
|
||||
|
||||
<span className="text-foreground-light">
|
||||
You may change or remove this limit from the dropdown on the right
|
||||
</span>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
{results.autoLimit !== undefined && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="default" iconRight={<ChevronUp size={14} />}>
|
||||
Limit results to:{' '}
|
||||
{
|
||||
ROWS_PER_PAGE_OPTIONS.find((opt) => opt.value === sessionSnap.limit)
|
||||
?.label
|
||||
}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-40" align="end">
|
||||
<DropdownMenuRadioGroup
|
||||
value={sessionSnap.limit.toString()}
|
||||
onValueChange={(val) => sessionSnap.setLimit(Number(val))}
|
||||
>
|
||||
{ROWS_PER_PAGE_OPTIONS.map((option) => (
|
||||
<DropdownMenuRadioItem key={option.label} value={option.value.toString()}>
|
||||
{option.label}
|
||||
</DropdownMenuRadioItem>
|
||||
))}
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</GridFooter>
|
||||
)}
|
||||
</div>
|
||||
</ResizablePanelGroup>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Hotkey } from '@tanstack/react-hotkeys'
|
||||
import { LOCAL_STORAGE_KEYS, useParams } from 'common'
|
||||
import { AlignLeft, Check, Heart, Keyboard, MoreVertical } from 'lucide-react'
|
||||
import { AlignLeft, Check, ChevronDown, Heart, Keyboard, MoreVertical } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import {
|
||||
Button,
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
KeyboardShortcut,
|
||||
@@ -16,6 +18,7 @@ import {
|
||||
TooltipTrigger,
|
||||
} from 'ui'
|
||||
|
||||
import { ROWS_PER_PAGE_OPTIONS } from '../SQLEditor.constants'
|
||||
import { AutosaveStatus } from './AutosaveStatus'
|
||||
import { SqlRunButton } from './RunButton'
|
||||
import { SqlSaveButton } from './SaveButton'
|
||||
@@ -37,6 +40,7 @@ export type UtilityActionsProps = {
|
||||
hasSelection?: boolean
|
||||
prettifyQuery: () => void
|
||||
executeQuery: () => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const UtilityActions = ({
|
||||
@@ -46,6 +50,7 @@ export const UtilityActions = ({
|
||||
hasSelection = false,
|
||||
prettifyQuery,
|
||||
executeQuery,
|
||||
className,
|
||||
}: UtilityActionsProps) => {
|
||||
const { ref } = useParams()
|
||||
const snapV2 = useSqlEditorV2StateSnapshot()
|
||||
@@ -86,7 +91,7 @@ export const UtilityActions = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="inline-flex items-center justify-end gap-x-2">
|
||||
<div className={cn('flex items-center justify-end gap-x-2', className)}>
|
||||
<AutosaveStatus id={id} />
|
||||
{/* SavingIndicator reports auto-save progress (spinner/checkmark). In manual
|
||||
mode AutosaveStatus + the Save button own the status, so hide it there. */}
|
||||
@@ -233,6 +238,30 @@ export const UtilityActions = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="default"
|
||||
iconRight={<ChevronDown size={14} className="text-foreground-light" />}
|
||||
>
|
||||
<span className="text-foreground-light">Limit</span>{' '}
|
||||
{ROWS_PER_PAGE_OPTIONS.find((opt) => opt.value === sessionSnap.limit)?.label}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-40" align="end">
|
||||
<DropdownMenuRadioGroup
|
||||
value={sessionSnap.limit.toString()}
|
||||
onValueChange={(val) => sessionSnap.setLimit(Number(val))}
|
||||
>
|
||||
{ROWS_PER_PAGE_OPTIONS.map((option) => (
|
||||
<DropdownMenuRadioItem key={option.label} value={option.value.toString()}>
|
||||
{option.label}
|
||||
</DropdownMenuRadioItem>
|
||||
))}
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<div className="flex items-center">
|
||||
{isManualSaveEnabled && <SqlSaveButton id={id} className="rounded-r-none" />}
|
||||
<SqlRunButton
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { useParams } from 'common'
|
||||
import { toast } from 'sonner'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from 'ui'
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from 'ui'
|
||||
|
||||
import { ChartConfig } from './ChartConfig'
|
||||
import { UtilityActions } from './UtilityActions'
|
||||
import { UtilityTabExplain } from './UtilityTabExplain'
|
||||
import { UtilityTabResults } from './UtilityTabResults'
|
||||
import { DownloadResultsButton } from '@/components/ui/DownloadResultsButton'
|
||||
@@ -19,9 +26,6 @@ export type UtilityPanelProps = {
|
||||
isExplainExecuting?: boolean
|
||||
isDebugging?: boolean
|
||||
isDisabled?: boolean
|
||||
hasSelection: boolean
|
||||
prettifyQuery: () => void
|
||||
executeQuery: () => void
|
||||
executeExplainQuery: () => void
|
||||
showExplainTab?: boolean
|
||||
onDebug: () => void
|
||||
@@ -45,9 +49,6 @@ export const UtilityPanel = ({
|
||||
isExplainExecuting,
|
||||
isDebugging,
|
||||
isDisabled,
|
||||
hasSelection,
|
||||
prettifyQuery,
|
||||
executeQuery,
|
||||
executeExplainQuery,
|
||||
showExplainTab = true,
|
||||
onDebug,
|
||||
@@ -144,6 +145,34 @@ export const UtilityPanel = ({
|
||||
<TabsTrigger className="py-3 text-xs" value="chart">
|
||||
<span className="translate-y-px">Chart</span>
|
||||
</TabsTrigger>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
{result?.rows !== undefined && !isExecuting && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<p className="text-xs">
|
||||
<span className="text-foreground">
|
||||
{result.rows.length} row{result.rows.length === 1 ? '' : 's'}
|
||||
</span>
|
||||
<span className="text-foreground-lighter ml-1">
|
||||
{result.autoLimit !== undefined && `(Limited to only ${result.autoLimit} rows)`}
|
||||
</span>
|
||||
</p>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs">
|
||||
<p className="flex flex-col gap-y-1">
|
||||
<span>
|
||||
Results are automatically limited to preserve browser performance, in particular
|
||||
if your query returns an exceptionally large number of rows.
|
||||
</span>
|
||||
<span className="text-foreground-light">
|
||||
You may change or remove this limit from the toolbar above.
|
||||
</span>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{result?.rows && (
|
||||
<DownloadResultsButton
|
||||
@@ -157,15 +186,6 @@ export const UtilityPanel = ({
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<UtilityActions
|
||||
id={id}
|
||||
isExecuting={isExecuting}
|
||||
isDisabled={isDisabled}
|
||||
hasSelection={hasSelection}
|
||||
prettifyQuery={prettifyQuery}
|
||||
executeQuery={executeQuery}
|
||||
/>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent asChild value="results" className="mt-0 grow">
|
||||
|
||||
Reference in new issue
Block a user