mirror of
https://github.com/Rohithgilla12/data-peek
synced 2026-04-21 12:57:16 +00:00
refactor(sidebar): consolidate search to omnibar, remove redundancy
Remove duplicate search inputs from Schema Explorer and Query History since the omnibar already covers both. Fix omnibar spring bounce easing to smooth expo ease-out under 200ms. Consolidate Quick Query's identical dual buttons into one, remove its Recent section that duplicated History.
This commit is contained in:
parent
e9787e7218
commit
0054dce5a7
4 changed files with 24 additions and 195 deletions
|
|
@ -1,12 +1,11 @@
|
|||
import { useState, useMemo } from 'react'
|
||||
import { ChevronRight, Clock, Copy, MoreHorizontal, Play, Trash2, Search, X } from 'lucide-react'
|
||||
import { ChevronRight, Clock, Copy, MoreHorizontal, Play, Trash2 } from 'lucide-react'
|
||||
|
||||
import { Badge, Input, Collapsible, CollapsibleContent, CollapsibleTrigger, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, useSidebar } from '@data-peek/ui'
|
||||
import { Badge, Collapsible, CollapsibleContent, CollapsibleTrigger, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, useSidebar } from '@data-peek/ui'
|
||||
|
||||
import { useQueryStore, useConnectionStore, useTabStore } from '@/stores'
|
||||
import { QueryHistoryDialog } from './query-history-dialog'
|
||||
import {
|
||||
filterHistory,
|
||||
formatRelativeTime,
|
||||
truncateQuery,
|
||||
getQueryType,
|
||||
|
|
@ -25,27 +24,14 @@ export function QueryHistory() {
|
|||
const createQueryTab = useTabStore((s) => s.createQueryTab)
|
||||
const [isHistoryDialogOpen, setIsHistoryDialogOpen] = useState(false)
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
|
||||
const filteredHistory = useMemo(() => {
|
||||
const connectionFiltered = activeConnectionId
|
||||
return activeConnectionId
|
||||
? history.filter((h) => h.connectionId === activeConnectionId || !h.connectionId)
|
||||
: history
|
||||
}, [history, activeConnectionId])
|
||||
|
||||
if (!searchQuery.trim()) {
|
||||
return connectionFiltered
|
||||
}
|
||||
|
||||
return filterHistory(connectionFiltered, {
|
||||
searchQuery,
|
||||
filterStatus: 'all',
|
||||
filterType: 'all',
|
||||
connectionId: null
|
||||
})
|
||||
}, [history, activeConnectionId, searchQuery])
|
||||
|
||||
const displayLimit = searchQuery.trim() ? 20 : 10
|
||||
const displayedHistory = filteredHistory.slice(0, displayLimit)
|
||||
const displayedHistory = filteredHistory.slice(0, 10)
|
||||
|
||||
const handleQueryClick = (query: string) => {
|
||||
const activeTab = getActiveTab()
|
||||
|
|
@ -87,35 +73,10 @@ export function QueryHistory() {
|
|||
</SidebarGroupLabel>
|
||||
<CollapsibleContent>
|
||||
<SidebarGroupContent>
|
||||
<div className="px-2 pb-2">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2 top-1/2 -translate-y-1/2 size-3 text-muted-foreground" />
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search history..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="h-7 pl-7 pr-7 text-xs"
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button
|
||||
onClick={() => setSearchQuery('')}
|
||||
className="absolute right-1.5 top-1/2 -translate-y-1/2 size-4 flex items-center justify-center text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<X className="size-3" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SidebarMenu>
|
||||
{displayedHistory.length === 0 ? (
|
||||
<div className="px-2 py-4 text-xs text-muted-foreground text-center">
|
||||
{searchQuery
|
||||
? 'No matching queries'
|
||||
: activeConnectionId
|
||||
? 'No queries yet'
|
||||
: 'Select a connection'}
|
||||
{activeConnectionId ? 'No queries yet' : 'Select a connection'}
|
||||
</div>
|
||||
) : (
|
||||
displayedHistory.map((item) => {
|
||||
|
|
@ -201,7 +162,7 @@ export function QueryHistory() {
|
|||
)
|
||||
})
|
||||
)}
|
||||
{filteredHistory.length > displayLimit && (
|
||||
{filteredHistory.length > 10 && (
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
className="text-sidebar-foreground/70"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
Database as SchemaIcon,
|
||||
Loader2,
|
||||
XCircle,
|
||||
Search,
|
||||
|
||||
X,
|
||||
Network,
|
||||
Plus,
|
||||
|
|
@ -41,7 +41,7 @@ import {
|
|||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
Input,
|
||||
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
|
|
@ -529,8 +529,6 @@ export function SchemaExplorer() {
|
|||
)
|
||||
const [expandedTables, setExpandedTables] = React.useState<Set<string>>(new Set())
|
||||
const [expandedRoutines, setExpandedRoutines] = React.useState<Set<string>>(new Set())
|
||||
const [searchQuery, setSearchQuery] = React.useState('')
|
||||
|
||||
// Only animate stagger on schema data changes, not on every re-render
|
||||
const animatedSchemasRef = React.useRef<typeof schemas>(null)
|
||||
const shouldAnimateStagger = animatedSchemasRef.current !== schemas
|
||||
|
|
@ -550,45 +548,23 @@ export function SchemaExplorer() {
|
|||
|
||||
const createQueryTab = useTabStore((s) => s.createQueryTab)
|
||||
|
||||
// Filter schemas and tables/routines based on search query, filter toggles, and focused schema
|
||||
const filteredSchemas = React.useMemo(() => {
|
||||
const query = searchQuery.toLowerCase().trim()
|
||||
|
||||
return schemas
|
||||
.filter((schema) => {
|
||||
// If a schema is focused, only show that schema
|
||||
if (focusedSchema && schema.name !== focusedSchema) return false
|
||||
return true
|
||||
})
|
||||
.map((schema) => {
|
||||
// Filter tables based on type and search
|
||||
const filteredTables = schema.tables.filter((table) => {
|
||||
// Type filter
|
||||
if (table.type === 'table' && !showTables) return false
|
||||
if (table.type === 'view' && !showViews) return false
|
||||
if (table.type === 'materialized_view' && !showMaterializedViews) return false
|
||||
// Search filter - match table name or column names
|
||||
if (
|
||||
query &&
|
||||
!table.name.toLowerCase().includes(query) &&
|
||||
!table.columns.some((c) => c.name.toLowerCase().includes(query))
|
||||
)
|
||||
return false
|
||||
return true
|
||||
})
|
||||
|
||||
// Filter routines based on type and search
|
||||
const filteredRoutines = schema.routines?.filter((routine) => {
|
||||
// Type filter
|
||||
if (routine.type === 'function' && !showFunctions) return false
|
||||
if (routine.type === 'procedure' && !showProcedures) return false
|
||||
// Search filter - match routine name or parameter names
|
||||
if (
|
||||
query &&
|
||||
!routine.name.toLowerCase().includes(query) &&
|
||||
!routine.parameters.some((p) => p.name.toLowerCase().includes(query))
|
||||
)
|
||||
return false
|
||||
return true
|
||||
})
|
||||
|
||||
|
|
@ -601,7 +577,6 @@ export function SchemaExplorer() {
|
|||
.filter((schema) => schema.tables.length > 0 || (schema.routines?.length ?? 0) > 0)
|
||||
}, [
|
||||
schemas,
|
||||
searchQuery,
|
||||
showTables,
|
||||
showViews,
|
||||
showMaterializedViews,
|
||||
|
|
@ -610,13 +585,6 @@ export function SchemaExplorer() {
|
|||
focusedSchema
|
||||
])
|
||||
|
||||
// Auto-expand schemas when searching
|
||||
React.useEffect(() => {
|
||||
if (searchQuery.trim()) {
|
||||
setExpandedSchemas(new Set(filteredSchemas.map((s) => s.name)))
|
||||
}
|
||||
}, [searchQuery, filteredSchemas])
|
||||
|
||||
// Update expanded schemas when schemas change
|
||||
React.useEffect(() => {
|
||||
setExpandedSchemas(new Set(schemas.map((s) => s.name)))
|
||||
|
|
@ -1081,35 +1049,10 @@ export function SchemaExplorer() {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* Search Input */}
|
||||
<div className="px-2 pb-2">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2 top-1/2 -translate-y-1/2 size-3.5 text-muted-foreground" />
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search tables, columns, routines..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="h-7 pl-7 pr-7 text-xs"
|
||||
/>
|
||||
{searchQuery && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="absolute right-0.5 top-1/2 -translate-y-1/2 size-6 hover:bg-transparent"
|
||||
onClick={() => setSearchQuery('')}
|
||||
>
|
||||
<X className="size-3.5 text-muted-foreground" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<SidebarMenu>
|
||||
{filteredSchemas.length === 0 ? (
|
||||
<div className="px-2 py-4 text-xs text-muted-foreground text-center">
|
||||
{searchQuery
|
||||
? 'No tables, columns, or routines match your search'
|
||||
: 'No schemas found'}
|
||||
No schemas found
|
||||
</div>
|
||||
) : (
|
||||
filteredSchemas.map((schema) => (
|
||||
|
|
|
|||
|
|
@ -339,7 +339,7 @@ export function SidebarOmnibar() {
|
|||
</div>
|
||||
|
||||
<div
|
||||
className="overflow-hidden transition-all duration-300 ease-[cubic-bezier(0.34,1.3,0.64,1)]"
|
||||
className="overflow-hidden transition-all duration-150 ease-[cubic-bezier(0.16,1,0.3,1)]"
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateRows: isActive && query.length > 0 ? '1fr' : '0fr'
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import * as React from 'react'
|
||||
import { Play, Send, ChevronDown, Clock } from 'lucide-react'
|
||||
import { Button, Collapsible, CollapsibleContent, CollapsibleTrigger, cn, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@data-peek/ui'
|
||||
import { Play, ChevronDown } from 'lucide-react'
|
||||
import { Button, Collapsible, CollapsibleContent, CollapsibleTrigger, cn, SidebarGroup, SidebarGroupContent, SidebarGroupLabel } from '@data-peek/ui'
|
||||
|
||||
import { SQLEditor } from '@/components/sql-editor'
|
||||
import { useQueryStore, useConnectionStore, useTabStore, useSettingsStore } from '@/stores'
|
||||
import { useConnectionStore, useTabStore, useSettingsStore } from '@/stores'
|
||||
|
||||
export function SidebarQuickQuery() {
|
||||
const hideQuickQueryPanel = useSettingsStore((s) => s.hideQuickQueryPanel)
|
||||
|
|
@ -13,44 +13,17 @@ export function SidebarQuickQuery() {
|
|||
|
||||
const activeConnection = useConnectionStore((s) => s.getActiveConnection())
|
||||
const schemas = useConnectionStore((s) => s.schemas)
|
||||
const { history } = useQueryStore()
|
||||
|
||||
const createQueryTab = useTabStore((s) => s.createQueryTab)
|
||||
|
||||
// Get recent 3 queries for quick access
|
||||
const recentQueries = React.useMemo(() => {
|
||||
return history
|
||||
.filter((h) => h.status === 'success')
|
||||
.slice(0, 3)
|
||||
.map((h) => ({
|
||||
id: h.id,
|
||||
query: h.query,
|
||||
preview: h.query.replace(/\s+/g, ' ').slice(0, 40) + (h.query.length > 40 ? '...' : '')
|
||||
}))
|
||||
}, [history])
|
||||
|
||||
const handleRunQuickQuery = () => {
|
||||
if (!activeConnection || !quickQuery.trim()) return
|
||||
// Create a new query tab with the query
|
||||
createQueryTab(activeConnection.id, quickQuery)
|
||||
setQuickQuery('')
|
||||
}
|
||||
|
||||
const handleSendToMainEditor = () => {
|
||||
if (!quickQuery.trim() || !activeConnection) return
|
||||
// Create a new query tab with the query
|
||||
createQueryTab(activeConnection.id, quickQuery)
|
||||
setQuickQuery('')
|
||||
}
|
||||
|
||||
const handleUseRecentQuery = (query: string) => {
|
||||
if (!activeConnection) return
|
||||
// Create a new query tab with the recent query
|
||||
createQueryTab(activeConnection.id, query)
|
||||
}
|
||||
|
||||
if (hideQuickQueryPanel) {
|
||||
return <></>
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -84,64 +57,16 @@ export function SidebarQuickQuery() {
|
|||
schemas={schemas}
|
||||
/>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex gap-1.5">
|
||||
<Button
|
||||
size="sm"
|
||||
className="flex-1 h-7 gap-1.5 text-xs"
|
||||
disabled={!activeConnection || !quickQuery.trim()}
|
||||
onClick={handleRunQuickQuery}
|
||||
>
|
||||
<Play className="size-3" />
|
||||
New Tab
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex-1 h-7 gap-1.5 text-xs"
|
||||
disabled={!quickQuery.trim()}
|
||||
onClick={handleSendToMainEditor}
|
||||
>
|
||||
<Send className="size-3" />
|
||||
Send to Editor
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
className="h-7 gap-1.5 text-xs"
|
||||
disabled={!activeConnection || !quickQuery.trim()}
|
||||
onClick={handleRunQuickQuery}
|
||||
>
|
||||
<Play className="size-3" />
|
||||
Run in New Tab
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Recent Queries */}
|
||||
{recentQueries.length > 0 && (
|
||||
<div className="mt-3 pt-3 border-t border-border/40">
|
||||
<div className="flex items-center gap-1.5 mb-2">
|
||||
<Clock className="size-3 text-muted-foreground" />
|
||||
<span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
|
||||
Recent
|
||||
</span>
|
||||
</div>
|
||||
<SidebarMenu>
|
||||
{recentQueries.map((item) => (
|
||||
<SidebarMenuItem key={item.id}>
|
||||
<SidebarMenuButton
|
||||
onClick={() => handleUseRecentQuery(item.query)}
|
||||
className="h-auto py-1.5 px-2"
|
||||
>
|
||||
<code className="text-[10px] font-mono text-muted-foreground truncate">
|
||||
{item.preview}
|
||||
</code>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* No Connection State */}
|
||||
{!activeConnection && (
|
||||
<div className="mt-2 text-center">
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
Select a connection to run queries
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</SidebarGroupContent>
|
||||
</CollapsibleContent>
|
||||
</SidebarGroup>
|
||||
|
|
|
|||
Loading…
Reference in a new issue