diff --git a/src/drivers/MySQLConnection.ts b/src/drivers/MySQLConnection.ts index 9d68737..1652e0c 100644 --- a/src/drivers/MySQLConnection.ts +++ b/src/drivers/MySQLConnection.ts @@ -68,10 +68,15 @@ function mapHeaderType(column: ColumnDefinition): QueryResultHeader { export default class MySQLConnection extends SQLLikeConnection { protected connectionConfig: DatabaseConnectionConfig; protected connection: Connection | undefined; + protected onStateChangedCallback: (state: string) => void; - constructor(connectionConfig: DatabaseConnectionConfig) { + constructor( + connectionConfig: DatabaseConnectionConfig, + statusChanged: (state: string) => void + ) { super(); this.connectionConfig = connectionConfig; + this.onStateChangedCallback = statusChanged; } protected async getConnection() { @@ -81,7 +86,20 @@ export default class MySQLConnection extends SQLLikeConnection { dateStrings: true, namedPlaceholders: true, }); + + console.log('connected'); + this.onStateChangedCallback('Connected'); + + this.connection.on('error', () => { + if (this.connection) { + this.connection.removeAllListeners(); + this.connection.destroy(); + this.connection = undefined; + this.onStateChangedCallback('Disconnected'); + } + }); } + return this.connection; } @@ -131,7 +149,9 @@ export default class MySQLConnection extends SQLLikeConnection { } async close() { - const conn = await this.getConnection(); - conn.destroy(); + if (this.connection) { + const conn = await this.getConnection(); + conn.destroy(); + } } } diff --git a/src/drivers/common/MySQLCommonInterface.ts b/src/drivers/common/MySQLCommonInterface.ts index 9d9ec21..0cfd81f 100644 --- a/src/drivers/common/MySQLCommonInterface.ts +++ b/src/drivers/common/MySQLCommonInterface.ts @@ -200,6 +200,13 @@ export default class MySQLCommonInterface extends SQLCommonInterface { return true; } + async getVersion(): Promise { + const response = await this.singleExecute<{ 'VERSION()': string }>( + 'SELECT VERSION();' + ); + return response.rows[0]['VERSION()']; + } + async getSchema(): Promise { const databaseListResponse = await this.singleExecute( qb().table('information_schema.SCHEMATA').select('SCHEMA_NAME').toRawSQL() diff --git a/src/drivers/common/NotImplementCommonInterface.ts b/src/drivers/common/NotImplementCommonInterface.ts index daaa0e7..83ae742 100644 --- a/src/drivers/common/NotImplementCommonInterface.ts +++ b/src/drivers/common/NotImplementCommonInterface.ts @@ -13,4 +13,8 @@ export default class NotImplementCommonInterface extends SQLCommonInterface { async switchDatabase(): Promise { throw 'Not implemented'; } + + async getVersion(): Promise { + throw 'Not implemented'; + } } diff --git a/src/drivers/common/SQLCommonInterface.ts b/src/drivers/common/SQLCommonInterface.ts index a28b5b5..9a8af0b 100644 --- a/src/drivers/common/SQLCommonInterface.ts +++ b/src/drivers/common/SQLCommonInterface.ts @@ -1,6 +1,7 @@ import { DatabaseSchemas, TableDefinitionSchema } from 'types/SqlSchema'; export default abstract class SQLCommonInterface { + abstract getVersion(): Promise; abstract getSchema(): Promise; abstract getTableSchema( database: string, diff --git a/src/main/ipc/handleConnection.ts b/src/main/ipc/handleConnection.ts index 4c28557..1078756 100644 --- a/src/main/ipc/handleConnection.ts +++ b/src/main/ipc/handleConnection.ts @@ -3,17 +3,26 @@ import SQLLikeConnection, { ConnectionStoreItem, DatabaseConnectionConfig, } from './../../drivers/SQLLikeConnection'; -import { ipcMain } from 'electron'; +import { BrowserWindow, ipcMain } from 'electron'; export default class ConnectionIpcHandler { protected connection: SQLLikeConnection | undefined; + protected window?: BrowserWindow; + + attachWindow(window: BrowserWindow) { + this.window = window; + } register() { ipcMain.handle('connect', async (_, [store]: [ConnectionStoreItem]) => { if (store.type === 'mysql') { - console.log('create mysql connection'); this.connection = new MySQLConnection( - store.config as unknown as DatabaseConnectionConfig + store.config as unknown as DatabaseConnectionConfig, + (status) => { + if (this.window) { + this.window.webContents.send('connection-status-change', status); + } + } ); } return true; diff --git a/src/main/main.ts b/src/main/main.ts index 38fb228..10fb0f9 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -68,6 +68,7 @@ const createWindow = async () => { mainWindow.setMenu(null); otherIpcHandler.attachWindow(mainWindow); + connectionIpcHandler.attachWindow(mainWindow); mainWindow.loadURL(resolveHtmlPath('index.html')); diff --git a/src/main/preload.ts b/src/main/preload.ts index d86d660..1c747ac 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -50,6 +50,13 @@ const electronHandler = { ipcRenderer.invoke('close'); }, + listenConnectionStatusChanged: ( + callback: (event: IpcRendererEvent, status: string) => void + ) => { + ipcRenderer.removeAllListeners('connection-status-change'); + return ipcRenderer.on('connection-status-change', callback); + }, + showMessageBox: (options: MessageBoxSyncOptions): Promise => ipcRenderer.invoke('show-message-box', [options]), @@ -121,7 +128,6 @@ const electronHandler = { }, openExternal: (url: string) => ipcRenderer.invoke('open-external', [url]), - }; contextBridge.exposeInMainWorld('electron', electronHandler); diff --git a/src/renderer/components/StatusBar/hook.ts b/src/renderer/components/StatusBar/hook.ts new file mode 100644 index 0000000..58c2456 --- /dev/null +++ b/src/renderer/components/StatusBar/hook.ts @@ -0,0 +1,46 @@ +import { useCallback, useState, useEffect } from 'react'; + +interface StatusBarConnection { + version?: string; + status?: string; +} + +const MESSAGE_CHANNEL = 'status-bar-connection'; + +export function useStatusBar() { + const setStatusBarConnectionStatus = useCallback( + (data?: StatusBarConnection) => { + window.postMessage({ + type: MESSAGE_CHANNEL, + data, + }); + }, + [] + ); + + return { setStatusBarConnectionStatus }; +} + +export function useStatusBarData() { + const [connectionStatus, setConnectionStatus] = useState< + StatusBarConnection | undefined + >(); + + useEffect(() => { + const receiveMessage = ( + e: MessageEvent<{ type: string; data?: StatusBarConnection }> + ) => { + if (e.data?.type === MESSAGE_CHANNEL) { + setConnectionStatus((prev) => { + if (e.data?.data) return { ...prev, ...e.data?.data }; + return undefined; + }); + } + }; + + window.addEventListener('message', receiveMessage); + return () => window.removeEventListener('message', receiveMessage); + }, [setConnectionStatus]); + + return { connectionStatus }; +} diff --git a/src/renderer/components/StatusBar/index.tsx b/src/renderer/components/StatusBar/index.tsx index 5f5b82d..f8a8cf1 100644 --- a/src/renderer/components/StatusBar/index.tsx +++ b/src/renderer/components/StatusBar/index.tsx @@ -4,6 +4,7 @@ import pkg from './../../../../package.json'; import Button from '../Button'; import Stack from '../Stack'; import ButtonGroup from '../ButtonGroup'; +import { useStatusBarData } from './hook'; function StatusBarAutoUpdate() { const [autoUpdateMessage, setAutoUpdateMessage] = useState(''); @@ -93,10 +94,30 @@ function StatusBarAutoUpdate() { } export default function StatusBar() { + const { connectionStatus } = useStatusBarData(); + return (
  • QueryMaster v{pkg.version}
  • + {!!connectionStatus?.version && ( +
  • MySQL {connectionStatus?.version}
  • + )} + {!!connectionStatus?.status && ( +
  • + + ⬤ + +   {connectionStatus.status} +
  • + )}
diff --git a/src/renderer/components/StatusBar/styles.module.css b/src/renderer/components/StatusBar/styles.module.css index 74fead4..feb905d 100644 --- a/src/renderer/components/StatusBar/styles.module.css +++ b/src/renderer/components/StatusBar/styles.module.css @@ -10,6 +10,14 @@ display: flex; } +.statusBarContainer ul li { + margin-right: 20px; +} + +.statusBarContainer ul li:last-child { + margin-right: 0px; +} + .popup { font-size: 1rem; width: 300px; diff --git a/src/renderer/screens/DatabaseScreen/UpdateConnectionStatus.tsx b/src/renderer/screens/DatabaseScreen/UpdateConnectionStatus.tsx new file mode 100644 index 0000000..8135f00 --- /dev/null +++ b/src/renderer/screens/DatabaseScreen/UpdateConnectionStatus.tsx @@ -0,0 +1,25 @@ +import { useEffect } from 'react'; +import { useStatusBar } from 'renderer/components/StatusBar/hook'; +import { useSqlExecute } from 'renderer/contexts/SqlExecuteProvider'; + +export default function UpdateConnectionStatus() { + const { common } = useSqlExecute(); + const { setStatusBarConnectionStatus } = useStatusBar(); + + useEffect(() => { + common.getVersion().then((version) => { + setStatusBarConnectionStatus({ version, status: 'Connected' }); + }); + }, [common, setStatusBarConnectionStatus]); + + useEffect(() => { + window.electron.listenConnectionStatusChanged((_, status) => { + setStatusBarConnectionStatus({ status }); + }); + return () => { + setStatusBarConnectionStatus(undefined); + }; + }, [setStatusBarConnectionStatus]); + + return <>; +} diff --git a/src/renderer/screens/DatabaseScreen/index.tsx b/src/renderer/screens/DatabaseScreen/index.tsx index 1601c92..7eb0c87 100644 --- a/src/renderer/screens/DatabaseScreen/index.tsx +++ b/src/renderer/screens/DatabaseScreen/index.tsx @@ -19,6 +19,7 @@ import Button from 'renderer/components/Button'; import ButtonGroup from 'renderer/components/ButtonGroup'; import { useConnection } from 'renderer/App'; import SwitchDatabaseProvider from 'renderer/contexts/SwitchDatabaseProvider'; +import UpdateConnectionStatus from './UpdateConnectionStatus'; function DatabaseScreenBody() { const { common } = useSqlExecute(); @@ -92,6 +93,7 @@ function DatabaseScreenBody() { return ( +