11import React , { useState , useCallback , useEffect } from "react" ;
22import type * as Monaco from "monaco-editor" ;
33import { useKeyPressEvent } from "react-use" ;
4- import { Database , Play , Save , Settings , ChevronRight , ChevronDown , Server , Table , Plus , X , CheckCircle2 , Clock } from "lucide-react" ;
4+ import { Database , Play , Save , Settings , ChevronRight , ChevronDown , Server , Table , Plus , X , CheckCircle2 , Clock , Moon , Sun } from "lucide-react" ;
55import Editor from "@monaco-editor/react" ;
66import { Button } from "@/components/ui/button" ;
77import { ResizeHandle } from "@/components/resize-handle" ;
@@ -13,15 +13,11 @@ import {
1313 getProjects ,
1414 insertProject ,
1515 insertQuery ,
16- pgsqlConnector ,
17- pgsqlLoadColumns ,
18- pgsqlLoadSchemas ,
19- pgsqlLoadTables ,
20- pgsqlRunQuery ,
2116 ProjectConnectionStatus ,
2217 ProjectMap ,
2318 TableInfo ,
2419} from "@/tauri" ;
20+ import { DriverFactory , type DriverType } from "@/lib/database-driver" ;
2521
2622type Tab = {
2723 id : number ;
@@ -216,6 +212,7 @@ export default function App() {
216212 const [ connectionModalOpen , setConnectionModalOpen ] = useState ( false ) ;
217213 const [ viewMode , setViewMode ] = useState < "grid" | "record" > ( "grid" ) ;
218214 const [ selectedRow , setSelectedRow ] = useState < number > ( 0 ) ;
215+ const [ theme , setTheme ] = useState < "light" | "dark" > ( "light" ) ;
219216
220217 const [ projects , setProjects ] = useState < ProjectMap > ( { } ) ;
221218 const [ status , setStatus ] = useState < Record < string , ProjectConnectionStatus > > ( { } ) ;
@@ -243,6 +240,14 @@ export default function App() {
243240 useEffect ( ( ) => { columnsRef . current = columns ; } , [ columns ] ) ;
244241 useEffect ( ( ) => { activeProjectRef . current = activeProject ; } , [ activeProject ] ) ;
245242
243+ // Helper to get driver for a project
244+ const getProjectDriver = useCallback ( ( projectId : string ) => {
245+ const d = projectsRef . current [ projectId ] ;
246+ if ( ! d ) return null ;
247+ const driverType = d [ 0 ] as DriverType ;
248+ return DriverFactory . getDriver ( driverType ) ;
249+ } , [ ] ) ;
250+
246251 function registerContextAwareCompletions ( monaco : typeof Monaco ) {
247252 type TableRef = { schema ?: string ; table : string } ;
248253 function stripQuotes ( s : string ) { return s . replaceAll ( '"' , "" ) ; }
@@ -261,12 +266,15 @@ export default function App() {
261266 async function resolveTableRef ( projectId : string , ref : TableRef ) : Promise < { schema : string ; table : string } | null > {
262267 if ( ref . schema ) return { schema : ref . schema , table : ref . table } ;
263268 const projSchemas = schemasRef . current [ projectId ] || [ ] ;
269+ const driver = getProjectDriver ( projectId ) ;
270+ if ( ! driver ) return { schema : "public" , table : ref . table } ;
271+
264272 for ( const schema of projSchemas ) {
265273 const key = `${ projectId } ::${ schema } ` ;
266274 let t = tablesRef . current [ key ] ;
267275 if ( ! t ) {
268276 try {
269- t = await pgsqlLoadTables ( projectId , schema ) ;
277+ t = await driver . loadTables ( projectId , schema ) ;
270278 tablesRef . current [ key ] = t ;
271279 setTables ( ( prev ) => ( { ...prev , [ key ] : t ! } ) ) ;
272280 } catch { }
@@ -280,6 +288,8 @@ export default function App() {
280288 provideCompletionItems : async ( model , position ) => {
281289 const projectId = activeProjectRef . current ;
282290 if ( ! projectId ) return { suggestions : [ ] } ;
291+ const driver = getProjectDriver ( projectId ) ;
292+ if ( ! driver ) return { suggestions : [ ] } ;
283293 const textUntilPosition = model . getValueInRange ( {
284294 startLineNumber : 1 , startColumn : 1 ,
285295 endLineNumber : position . lineNumber , endColumn : position . column ,
@@ -315,7 +325,7 @@ export default function App() {
315325 let cols = columnsRef . current [ colKey ] ;
316326 if ( ! cols ) {
317327 try {
318- cols = await pgsqlLoadColumns ( projectId , resolved . schema , resolved . table ) ;
328+ cols = await driver . loadColumns ( projectId , resolved . schema , resolved . table ) ;
319329 columnsRef . current [ colKey ] = cols ;
320330 setColumns ( ( prev ) => ( { ...prev , [ colKey ] : cols ! } ) ) ;
321331 } catch { }
@@ -330,7 +340,7 @@ export default function App() {
330340 let t = tablesRef . current [ key ] ;
331341 if ( ! t ) {
332342 try {
333- t = await pgsqlLoadTables ( projectId , schema ) ;
343+ t = await driver . loadTables ( projectId , schema ) ;
334344 tablesRef . current [ key ] = t ;
335345 setTables ( ( prev ) => ( { ...prev , [ key ] : t ! } ) ) ;
336346 } catch { }
@@ -347,7 +357,7 @@ export default function App() {
347357 let cols = columnsRef . current [ colKey ] ;
348358 if ( ! cols ) {
349359 try {
350- cols = await pgsqlLoadColumns ( projectId , left , right ) ;
360+ cols = await driver . loadColumns ( projectId , left , right ) ;
351361 columnsRef . current [ colKey ] = cols ;
352362 setColumns ( ( prev ) => ( { ...prev , [ colKey ] : cols ! } ) ) ;
353363 } catch { }
@@ -364,7 +374,7 @@ export default function App() {
364374 let t = tablesRef . current [ key ] ;
365375 if ( ! t ) {
366376 try {
367- t = await pgsqlLoadTables ( projectId , schema ) ;
377+ t = await driver . loadTables ( projectId , schema ) ;
368378 tablesRef . current [ key ] = t ;
369379 setTables ( ( prev ) => ( { ...prev , [ key ] : t ! } ) ) ;
370380 } catch { }
@@ -415,12 +425,15 @@ export default function App() {
415425 const d = projects [ project_id ] ;
416426 if ( ! d ) return ;
417427 setStatus ( ( s ) => ( { ...s , [ project_id ] : ProjectConnectionStatus . Connecting } ) ) ;
418- const key : [ string , string , string , string , string ] = [ d [ 1 ] , d [ 2 ] , d [ 3 ] , d [ 4 ] , d [ 5 ] ] ;
428+ const driverType = d [ 0 ] as DriverType ;
429+ const useSsl = d [ 6 ] === "true" ; // 7th element is SSL flag
430+ const key : [ string , string , string , string , string , string ] = [ d [ 1 ] , d [ 2 ] , d [ 3 ] , d [ 4 ] , d [ 5 ] , useSsl ? "true" : "false" ] ;
419431 try {
420- const st = await pgsqlConnector ( project_id , key ) ;
432+ const driver = DriverFactory . getDriver ( driverType ) ;
433+ const st = await driver . connect ( project_id , key ) ;
421434 setStatus ( ( s ) => ( { ...s , [ project_id ] : st } ) ) ;
422435 if ( st === ProjectConnectionStatus . Connected ) {
423- const sc = await pgsqlLoadSchemas ( project_id ) ;
436+ const sc = await driver . loadSchemas ( project_id ) ;
424437 setSchemas ( ( prev ) => ( { ...prev , [ project_id ] : sc } ) ) ;
425438 }
426439 } catch {
@@ -436,9 +449,13 @@ export default function App() {
436449 const onLoadTables = useCallback ( async ( project_id : string , schema : string ) => {
437450 const key = `${ project_id } ::${ schema } ` ;
438451 if ( tables [ key ] ) return ;
439- const rows = await pgsqlLoadTables ( project_id , schema ) ;
452+ const d = projects [ project_id ] ;
453+ if ( ! d ) return ;
454+ const driverType = d [ 0 ] as DriverType ;
455+ const driver = DriverFactory . getDriver ( driverType ) ;
456+ const rows = await driver . loadTables ( project_id , schema ) ;
440457 setTables ( ( t ) => ( { ...t , [ key ] : rows } ) ) ;
441- } , [ tables ] ) ;
458+ } , [ tables , projects ] ) ;
442459
443460 const onOpenDefaultTableQuery = useCallback ( ( project_id : string , schema : string , table : string ) => {
444461 const sql = `SELECT * FROM "${ schema } "."${ table } " LIMIT 100;` ;
@@ -448,14 +465,18 @@ export default function App() {
448465
449466 const runQuery = useCallback ( async ( ) => {
450467 if ( ! activeProject || ! activeTab ) return ;
451- const [ cols , rows , time ] = await pgsqlRunQuery ( activeProject , activeTab . editorValue ) ;
468+ const d = projects [ activeProject ] ;
469+ if ( ! d ) return ;
470+ const driverType = d [ 0 ] as DriverType ;
471+ const driver = DriverFactory . getDriver ( driverType ) ;
472+ const [ cols , rows , time ] = await driver . runQuery ( activeProject , activeTab . editorValue ) ;
452473 setTabs ( ( ts ) => {
453474 const copy = ts . slice ( ) ;
454475 copy [ selectedTab ] = { ...copy [ selectedTab ] , result : { columns : cols , rows, time } } ;
455476 return copy ;
456477 } ) ;
457478 setSelectedRow ( 0 ) ;
458- } , [ activeProject , activeTab , selectedTab ] ) ;
479+ } , [ activeProject , activeTab , selectedTab , projects ] ) ;
459480
460481 const saveQuery = useCallback ( async ( title : string ) => {
461482 if ( ! activeProject ) return ;
@@ -478,19 +499,31 @@ export default function App() {
478499 } ;
479500
480501 const handleSaveConnection = useCallback ( async ( connection : ConnectionConfig ) => {
481- const details = [ "PGSQL" , connection . username , connection . password , connection . database , connection . host , connection . port ] ;
502+ const details = [ connection . driver , connection . username , connection . password , connection . database , connection . host , connection . port , connection . ssl ? "true" : "false" ] ;
482503 await insertProject ( connection . name , details ) ;
483504 await reloadProjects ( ) ;
484505 } , [ reloadProjects ] ) ;
485506
507+ const toggleTheme = ( ) => {
508+ setTheme ( prev => prev === "light" ? "dark" : "light" ) ;
509+ } ;
510+
511+ useEffect ( ( ) => {
512+ if ( theme === "dark" ) {
513+ document . documentElement . classList . add ( "dark" ) ;
514+ } else {
515+ document . documentElement . classList . remove ( "dark" ) ;
516+ }
517+ } , [ theme ] ) ;
518+
486519 return (
487520 < div className = "flex h-screen flex-col bg-background text-foreground" >
488521 { /* Top Bar */ }
489522 < div className = "flex h-12 items-center justify-between border-b border-border bg-card px-4" >
490523 < div className = "flex items-center gap-3" >
491524 < div className = "flex items-center gap-2" >
492- < Database className = "h-5 w-5 text-white " />
493- < span className = "font-mono text-sm font-semibold text-white " > PostgresGUI</ span >
525+ < Database className = "h-5 w-5" />
526+ < span className = "font-mono text-sm font-semibold" > PostgresGUI</ span >
494527 </ div >
495528 { activeProject && activeProjectDetails ? (
496529 < >
@@ -503,9 +536,9 @@ export default function App() {
503536 status [ activeProject ] === ProjectConnectionStatus . Failed && "bg-destructive" ,
504537 ! status [ activeProject ] && "bg-destructive"
505538 ) } />
506- < span className = "font-mono text-white " > { activeProject } </ span >
539+ < span className = "font-mono" > { activeProject } </ span >
507540 < span className = "text-muted-foreground/50" > •</ span >
508- < span className = "text-white" > { activeProjectDetails [ 4 ] } :{ activeProjectDetails [ 5 ] } </ span >
541+ < span > { activeProjectDetails [ 4 ] } :{ activeProjectDetails [ 5 ] } </ span >
509542 </ div >
510543 </ >
511544 ) : null }
@@ -522,8 +555,8 @@ export default function App() {
522555 } }
523556 disabled = { ! activeProject }
524557 >
525- < Save className = "h-4 w-4 text-white " />
526- < span className = "text-xs text-white " > Save</ span >
558+ < Save className = "h-4 w-4" />
559+ < span className = "text-xs" > Save</ span >
527560 </ Button >
528561 < Button
529562 variant = "default"
@@ -532,12 +565,12 @@ export default function App() {
532565 onClick = { ( ) => void runQuery ( ) }
533566 disabled = { ! activeProject }
534567 >
535- < Play className = "h-4 w-4 text-white " />
536- < span className = "text-xs text-white " > Execute (⌘+Enter)</ span >
568+ < Play className = "h-4 w-4" />
569+ < span className = "text-xs" > Execute (⌘+Enter)</ span >
537570 </ Button >
538571 < div className = "h-4 w-px bg-border" />
539- < Button variant = "ghost" size = "icon" className = "h-8 w-8" >
540- < Settings className = "h-4 w-4 text-white " />
572+ < Button variant = "ghost" size = "icon" className = "h-8 w-8" onClick = { toggleTheme } >
573+ { theme === "light" ? < Moon className = "h-4 w-4" /> : < Sun className = "h-4 w-4 " /> }
541574 </ Button >
542575 </ div >
543576 </ div >
@@ -578,7 +611,7 @@ export default function App() {
578611 : "bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
579612 ) }
580613 >
581- < button onClick = { ( ) => setSelectedTab ( idx ) } className = "font-mono text-xs text-white " >
614+ < button onClick = { ( ) => setSelectedTab ( idx ) } className = "font-mono text-xs" >
582615 Query { idx + 1 }
583616 </ button >
584617 { tabs . length > 1 && (
@@ -592,26 +625,26 @@ export default function App() {
592625 } }
593626 className = "opacity-0 transition-opacity hover:text-foreground group-hover:opacity-100"
594627 >
595- < X className = "h-3 w-3 text-white " />
628+ < X className = "h-3 w-3" />
596629 </ button >
597630 ) }
598631 </ div >
599632 ) ) }
600633 </ div >
601634 < Button variant = "ghost" size = "icon" onClick = { ( ) => setTabs ( ( ts ) => [ ...ts , { id : ts . length + 1 , editorValue : "" } ] ) } className = "h-9 w-9 shrink-0" >
602- < Plus className = "h-4 w-4 text-white " />
635+ < Plus className = "h-4 w-4" />
603636 </ Button >
604637 </ div >
605638 { /* SQL Editor */ }
606639 < div className = "relative flex-1 overflow-hidden bg-[var(--color-editor-bg)]" suppressHydrationWarning >
607- < div className = "absolute inset-0 overflow-auto bg-[#1e1e1e] " >
640+ < div className = "absolute inset-0 overflow-auto bg-editor-bg " >
608641 < Editor
609642 height = "100%"
610643 defaultLanguage = "pgsql"
611644 language = "pgsql"
612- theme = "vs -dark"
645+ theme = { theme === "light" ? "vs" : "vs -dark"}
613646 loading = {
614- < div className = "flex h-full w-full items-center justify-center bg-[#1e1e1e] " >
647+ < div className = "flex h-full w-full items-center justify-center bg-editor-bg " >
615648 < span className = "text-muted-foreground text-sm" > Loading editor...</ span >
616649 </ div >
617650 }
@@ -624,7 +657,6 @@ export default function App() {
624657 lineNumbers : "on" ,
625658 quickSuggestions : { other : true , comments : false , strings : true } ,
626659 suggestOnTriggerCharacters : true ,
627- theme : "vs-dark" ,
628660 } }
629661 value = { activeTab ?. editorValue }
630662 onChange = { ( v ) => {
0 commit comments