diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 54d81a0..6dee1ef 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -156,9 +156,7 @@ function App() { return [...prev, connection] } else { // Updated connection - return prev.map((conn) => - conn.id === connection.id ? { ...conn, ...connection } : conn - ) + return prev.map((conn) => (conn.id === connection.id ? { ...conn, ...connection } : conn)) } }) @@ -188,7 +186,13 @@ function App() { void } export function ActiveConnectionLayout({ connectionId, connectionName, + database, onDisconnect }: ActiveConnectionLayoutProps) { const [isReadOnly, setIsReadOnly] = useState(false) + const [showSearchBar, setShowSearchBar] = useState(false) + const [tables, setTables] = useState([]) + const [query, setQuery] = useState('') + const [debouncedQuery, setDebouncedQuery] = useState('') + const [activeTableEntry, setActiveTableEntry] = useState(0) const queryWorkspaceRef = useRef(null) const newTabHandlerRef = useRef<(() => void) | null>(null) + const searchBarRef = useRef(null) useEffect(() => { const checkReadOnly = async () => { @@ -35,6 +44,34 @@ export function ActiveConnectionLayout({ checkReadOnly() }, [connectionId]) + // debounce the search query + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedQuery(query) + }, 300) // 300ms debounce + + return () => clearTimeout(handler) + }, [query]) + + // fetch tables for search + const loadTables = useCallback(async () => { + let tables = [] as string[] + try { + const result = await window.api.database.getTables(connectionId, database) + if (result.success && result.tables) { + tables = result.tables + } + } catch (error) { + console.error('Error loading databases:', error) + } finally { + setTables(tables) + } + }, [connectionId, database]) + + useEffect(() => { + loadTables() + }, [loadTables]) + // Global keyboard shortcut for Cmd+N / Ctrl+N useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { @@ -52,6 +89,30 @@ export function ActiveConnectionLayout({ } }, []) + // Keyboard shorcut for Quick Table Search (Cmd + P / Ctrl + P) + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if ((event.metaKey || event.ctrlKey) && event.key === 'p') { + event.preventDefault() + setShowSearchBar(true) + } + if (event.key === 'Escape') { + setShowSearchBar(false) + setQuery('') + } + } + document.addEventListener('keydown', handleKeyDown) + return () => { + document.removeEventListener('keydown', handleKeyDown) + } + }, []) + + useEffect(() => { + if (showSearchBar && searchBarRef.current) { + searchBarRef.current.focus() + } + }, [showSearchBar]) + const handleOpenTableTab = (database: string, tableName: string) => { if (window.openTableTab) { window.openTableTab(database, tableName) @@ -75,6 +136,32 @@ export function ActiveConnectionLayout({ queryWorkspaceRef.current.executeQueryFromAI(query) } } + const filteredResults = useMemo( + () => + debouncedQuery + ? tables.filter((tableName) => + tableName.toLowerCase().includes(debouncedQuery.toLowerCase()) + ) + : [], + [debouncedQuery, tables] + ) + const handleKeyDownInput = (e: React.KeyboardEvent) => { + if (e.key === 'ArrowDown') { + e.preventDefault() + setActiveTableEntry((prev) => Math.min(prev + 1, filteredResults.length - 1)) + } + if (e.key === 'ArrowUp') { + e.preventDefault() + setActiveTableEntry((prev) => Math.max(prev - 1, 0)) + } + if (e.key === 'Enter') { + e.preventDefault() + setShowSearchBar(false) + handleOpenTableTab(database, filteredResults[activeTableEntry]) + setDebouncedQuery('') + setActiveTableEntry(0) + } + } return ( @@ -91,6 +178,31 @@ export function ActiveConnectionLayout({ )} + + + + setQuery(e.target.value)} + onKeyDown={handleKeyDownInput} + /> + + {filteredResults.map((tableName, idx) => ( + + {tableName} + + ))} + + + +