From de935556cc971c53c79dfb074d462123e068f6ed Mon Sep 17 00:00:00 2001 From: "@roth-dev" Date: Mon, 23 Dec 2024 16:36:27 +0700 Subject: [PATCH 01/10] feat: custom desktop menu --- electron/menu/index.ts | 175 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 electron/menu/index.ts diff --git a/electron/menu/index.ts b/electron/menu/index.ts new file mode 100644 index 0000000..2afe83c --- /dev/null +++ b/electron/menu/index.ts @@ -0,0 +1,175 @@ +import { ConnectionStoreItem } from "@/lib/conn-manager-store"; +import { app, Menu, MenuItemConstructorOptions, shell } from "electron"; +import { isMac } from "../utils"; +import { createDatabaseWindow, windowMap } from "../window/create-database"; +import { OuterbaseApplication } from "../type"; +import { createWindow } from "../main"; +import { createConnectionWindow } from "../window/create-connection-window"; +import { OUTERBASE_GITHUB, OUTERBASE_WEBSITE } from "../constants"; + +export function createMenu( + win: OuterbaseApplication["win"], + connections: ConnectionStoreItem[], +) { + function handleClick() { + createWindow(); + } + + function onOpenConnectionWindow(type: ConnectionStoreItem["type"]) { + createConnectionWindow(win, type); + win?.hide(); + } + + function generateSubMenu() { + const connMenu: MenuItemConstructorOptions["submenu"] = connections.map( + (conn) => { + return { + label: conn.name, + click: () => { + const existingWindow = windowMap.get(conn.id); + if (existingWindow && !existingWindow.isDestroyed()) { + existingWindow.focus(); + } else { + createDatabaseWindow({ win, conn }); + win?.hide(); + } + }, + }; + }, + ); + + return connMenu; + } + + const connSubMenu = generateSubMenu(); + + const customTemplate = [ + ...(isMac + ? [ + { + label: app.name, + submenu: [ + { role: "about" }, + { type: "separator" }, + { role: "services" }, + { type: "separator" }, + { role: "hide" }, + { role: "hideOthers" }, + { role: "unhide" }, + { type: "separator" }, + { role: "quit" }, + ], + }, + ] + : []), + { + label: "File", + submenu: [ + { + label: "New Connection", + submenu: [ + { + label: "MySQL", + click: () => onOpenConnectionWindow("mysql"), + }, + { + label: "PostgreSQL", + click: () => onOpenConnectionWindow("postgres"), + }, + { + label: "SQLite", + click: () => onOpenConnectionWindow("sqlite"), + }, + { + label: "Turso", + click: () => onOpenConnectionWindow("turso"), + }, + { + label: "Cloudflare", + click: () => onOpenConnectionWindow("cloudflare"), + }, + { + label: "Starbase", + click: () => onOpenConnectionWindow("starbase"), + }, + ], + }, + { + label: "New Window", + click: handleClick, + }, + { + type: "separator", + }, + { + label: "Open Recent", + enabled: connSubMenu.length > 0, + submenu: connSubMenu, + click: handleClick, + }, + { + type: "separator", + }, + { role: "close" }, + ...(isMac ? [] : [{ label: "Exit", role: "quit" }]), + ], + }, + { + label: "Edit", + submenu: [ + { role: "undo" }, + { role: "redo" }, + { type: "separator" }, + { role: "cut" }, + { role: "copy" }, + { role: "paste" }, + { role: "delete" }, + { role: "selectAll" }, + ], + }, + { + label: "View", + submenu: [ + { role: "reload" }, + { role: "forceReload" }, + { role: "toggleDevTools" }, + { type: "separator" }, + { role: "resetZoom" }, + { role: "zoomIn" }, + { role: "zoomOut" }, + { type: "separator" }, + { role: "togglefullscreen" }, + ], + }, + { + label: "Window", + submenu: [ + { role: "minimize" }, + { role: "zoom" }, + ...(isMac + ? [{ type: "separator" }, { role: "front" }] + : [{ role: "close" }]), + ], + }, + { + label: "Help", + submenu: [ + { + label: "About Us", + click: async () => { + await shell.openExternal(OUTERBASE_WEBSITE); + }, + }, + { + label: "Report issues", + click: async () => { + await shell.openExternal(OUTERBASE_GITHUB); + }, + }, + ], + }, + ] as MenuItemConstructorOptions[]; + + const menu = Menu.buildFromTemplate(customTemplate); + Menu.setApplicationMenu(menu); +} From f3ca7c83cee018c3c825f42d31c90ee41f602e95 Mon Sep 17 00:00:00 2001 From: "@roth-dev" Date: Mon, 23 Dec 2024 16:37:46 +0700 Subject: [PATCH 02/10] send connections from renderer window to main process --- electron/main.ts | 82 +++++++++------------------------- electron/preload.ts | 12 +++-- src/lib/conn-manager-store.tsx | 5 ++- 3 files changed, 33 insertions(+), 66 deletions(-) diff --git a/electron/main.ts b/electron/main.ts index f1ab273..a2faff0 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -5,8 +5,6 @@ import { ipcMain, type OpenDialogOptions, } from "electron"; -// import { createRequire } from "node:module"; -import { fileURLToPath } from "node:url"; import path from "node:path"; import { ConnectionPool } from "./connection-pool"; import electronUpdater, { type AppUpdater } from "electron-updater"; @@ -14,8 +12,10 @@ import log from "electron-log"; import { type ConnectionStoreItem } from "@/lib/conn-manager-store"; import { bindDockerIpc } from "./ipc/docker"; import { Setting } from "./setting"; -import { ThemeType } from "@/context/theme-provider"; import { OuterbaseApplication } from "./type"; +import { createMenu } from "./menu"; +import { createDatabaseWindow } from "./window/create-database"; +import { getOuterbaseDir, isDev, isMac } from "./utils"; // eslint-disable-next-line @typescript-eslint/no-unused-vars // const require = createRequire(import.meta.url); @@ -31,10 +31,11 @@ log.transports.file.level = "info"; autoUpdater.logger = log; autoUpdater.autoDownload = false; -const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const __dirname = getOuterbaseDir(); // The built directory structure // + // ├─┬─┬ dist // │ │ └── index.html // │ │ @@ -48,7 +49,6 @@ process.env.APP_ROOT = path.join(__dirname, ".."); export const VITE_DEV_SERVER_URL = process.env["VITE_DEV_SERVER_URL"]; export const MAIN_DIST = path.join(process.env.APP_ROOT, "dist-electron"); export const RENDERER_DIST = path.join(process.env.APP_ROOT, "dist"); - process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL ? path.join(process.env.APP_ROOT, "public") : RENDERER_DIST; @@ -59,59 +59,7 @@ const application: OuterbaseApplication = { win: undefined, }; -const STUDIO_ENDPOINT = "https://studio.outerbase.com/embed"; -// const STUDIO_ENDPOINT = "http://localhost:3008/embed"; - -function createDatabaseWindow( - conn: ConnectionStoreItem, - enableDebug?: boolean, -) { - const dbWindow = new BrowserWindow({ - icon: path.join(process.env.VITE_PUBLIC, "electron-vite.svg"), - show: false, - width: 1024, - height: 768, - autoHideMenuBar: true, - webPreferences: { - devTools: true, - additionalArguments: ["--database=" + conn.id], - preload: path.join(__dirname, "preload.mjs"), - }, - }); - - const theme = settings.get("theme") || "light"; - - ConnectionPool.create(conn); - - const queryString = new URLSearchParams({ - name: conn.name, - theme, - }).toString(); - - dbWindow.on("closed", () => { - application.win?.show(); - ConnectionPool.close(conn.id); - }); - - if (conn.type === "mysql") { - dbWindow.loadURL(`${STUDIO_ENDPOINT}/mysql?${queryString}`); - } else if (conn.type === "postgres") { - dbWindow.loadURL(`${STUDIO_ENDPOINT}/postgres?${queryString}`); - } else if (conn.type === "starbase" || conn.type === "cloudflare") { - dbWindow.loadURL(`${STUDIO_ENDPOINT}/starbase?${queryString}`); - } else { - dbWindow.loadURL(`${STUDIO_ENDPOINT}/sqlite?${queryString}`); - } - - if (process.env.NODE_ENV === "development" || enableDebug) { - dbWindow.webContents.openDevTools(); - dbWindow.maximize(); - } - - dbWindow.show(); -} - -function createWindow() { +export function createWindow() { application.win = new BrowserWindow({ icon: path.join(process.env.VITE_PUBLIC, "electron-vite.svg"), title: "Outerbase Studio", @@ -123,7 +71,7 @@ function createWindow() { }, }); - if (process.env.NODE_ENV === "development") { + if (isDev) { application.win.webContents.openDevTools({ mode: "detach" }); } @@ -189,7 +137,7 @@ function createWindow() { // for applications and their menu bar to stay active until the user quits // explicitly with Cmd + Q. app.on("window-all-closed", () => { - if (process.platform !== "darwin") { + if (!isMac) { app.quit(); application.win = undefined; } @@ -210,6 +158,10 @@ app bindDockerIpc(application); }); +ipcMain.on("connections", (_, connections: ConnectionStoreItem[]) => { + createMenu(application.win, connections); +}); + ipcMain.handle("query", async (_, connectionId, query) => { const r = await ConnectionPool.query(connectionId, query); return r; @@ -226,10 +178,18 @@ ipcMain.handle("close", async (sender) => { sender.sender.close({ waitForBeforeUnload: true, }); + + if (BrowserWindow.getAllWindows().length > 1) { + application.win?.destroy(); + } }); ipcMain.handle("connect", (_, conn: ConnectionStoreItem, enableDebug) => { - createDatabaseWindow(conn, enableDebug); + createDatabaseWindow({ + win: application.win, + conn, + enableDebug, + }); if (application.win) application.win.hide(); }); diff --git a/electron/preload.ts b/electron/preload.ts index 62ed0d3..abf286f 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -54,6 +54,10 @@ const outerbaseIpc = { return ipcRenderer.invoke("connect", conn, enableDebug); }, + getConnection() { + return ipcRenderer.invoke("get-connection"); + }, + downloadUpdate() { return ipcRenderer.invoke("download-update"); }, @@ -105,11 +109,11 @@ const outerbaseIpc = { }, setting: { - get: (key: string): Promise => ipcRenderer.invoke("get-setting", key), + get: (key: string): Promise => + ipcRenderer.invoke("get-setting", key), set: (key: string, value: T): Promise => - ipcRenderer.invoke("set-setting", key, value) - } - + ipcRenderer.invoke("set-setting", key, value), + }, }; export type OuterbaseIpc = typeof outerbaseIpc; diff --git a/src/lib/conn-manager-store.tsx b/src/lib/conn-manager-store.tsx index b1da672..105ce45 100644 --- a/src/lib/conn-manager-store.tsx +++ b/src/lib/conn-manager-store.tsx @@ -218,7 +218,10 @@ export class ConnectionStoreManager { try { const data = localStorage.getItem("connections"); if (!data) return []; - return JSON.parse(data) as ConnectionStoreItem[]; + const parsedJson = JSON.parse(data); + // send to main process + window.outerbaseIpc.send("connections", parsedJson); + return parsedJson as ConnectionStoreItem[]; } catch { return []; } From 39fc8736cea49cefd5a3f7d3aa44de809e145b66 Mon Sep 17 00:00:00 2001 From: "@roth-dev" Date: Mon, 23 Dec 2024 16:38:48 +0700 Subject: [PATCH 03/10] focus window when connection already exist --- electron/connection-pool.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/electron/connection-pool.ts b/electron/connection-pool.ts index e80f67c..74e13ae 100644 --- a/electron/connection-pool.ts +++ b/electron/connection-pool.ts @@ -5,12 +5,17 @@ import TursoDriver from "./drivers/sqlite"; import PostgresDriver from "./drivers/postgres"; import StarbaseDriver from "./drivers/starbase"; import CloudflareDriver from "./drivers/cloudflare"; +import { windowMap } from "./window/create-database"; export class ConnectionPool { static connections: Record = {}; static create(conn: ConnectionStoreItem) { if (ConnectionPool.connections[conn.id]) { + const focusWindow = windowMap.get(conn.id); + if (focusWindow && !focusWindow.isDestroyed()) { + focusWindow.focus(); + } throw new Error(`Connection already exists: ${conn.id}`); } From 38dfba0e629db88d025536846dfd844ca053bff5 Mon Sep 17 00:00:00 2001 From: "@roth-dev" Date: Mon, 23 Dec 2024 16:39:27 +0700 Subject: [PATCH 04/10] create connection window --- electron/window/create-connection-window.ts | 34 +++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 electron/window/create-connection-window.ts diff --git a/electron/window/create-connection-window.ts b/electron/window/create-connection-window.ts new file mode 100644 index 0000000..e88d424 --- /dev/null +++ b/electron/window/create-connection-window.ts @@ -0,0 +1,34 @@ +import { BrowserWindow } from "electron"; +import { getWindowConfig } from "../utils"; +import path from "node:path"; +import { RENDERER_DIST, VITE_DEV_SERVER_URL } from "../main"; +import { ConnectionStoreItem } from "@/lib/conn-manager-store"; +import { OuterbaseApplication } from "electron/type"; + +export function createConnectionWindow( + win: OuterbaseApplication["win"], + type: ConnectionStoreItem["type"], +) { + const newWin = new BrowserWindow(getWindowConfig()); + const route = "connection/create/" + type; + + if (VITE_DEV_SERVER_URL) { + const url = `${VITE_DEV_SERVER_URL}${route}`; + newWin.loadURL(url); + } else { + // win.loadFile('dist/index.html') + const filePath = path.join(RENDERER_DIST, "index.html"); + newWin.loadFile(filePath, { + hash: `#/${route}`, + }); + } + + newWin.show(); + + newWin.on("closed", () => { + if (BrowserWindow.getAllWindows().length === 1) { + win?.show(); + } + newWin.destroy(); + }); +} From 2340b2804f7e6b27bd8a50787335ce49b18122dc Mon Sep 17 00:00:00 2001 From: "@roth-dev" Date: Mon, 23 Dec 2024 16:40:09 +0700 Subject: [PATCH 05/10] moved create database window to separete file --- electron/window/create-database.ts | 57 ++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 electron/window/create-database.ts diff --git a/electron/window/create-database.ts b/electron/window/create-database.ts new file mode 100644 index 0000000..5c8c5c3 --- /dev/null +++ b/electron/window/create-database.ts @@ -0,0 +1,57 @@ +import { ThemeType } from "@/context/theme-provider"; +import { ConnectionStoreItem } from "@/lib/conn-manager-store"; +import { BrowserWindow } from "electron"; +import { ConnectionPool } from "../connection-pool"; +import { STUDIO_ENDPOINT } from "../constants"; +import { Setting } from "../setting"; +import { OuterbaseApplication } from "../type"; +import { getWindowConfig, isDev } from "../utils"; + +export const windowMap = new Map(); + +export function createDatabaseWindow(ctx: { + win: OuterbaseApplication["win"]; + conn: ConnectionStoreItem; + enableDebug?: boolean; +}) { + const dbWindow = new BrowserWindow(getWindowConfig(ctx.conn.id)); + + const settings = new Setting(); + settings.load(); + const theme = settings.get("theme") || "light"; + + ConnectionPool.create(ctx.conn); + + const queryString = new URLSearchParams({ + name: ctx.conn.name, + theme, + }).toString(); + + windowMap.set(ctx.conn.id, dbWindow); + + dbWindow.on("closed", () => { + if (windowMap.size === 1) { + ctx.win?.show(); + } + windowMap.delete(ctx.conn.id); + ConnectionPool.close(ctx.conn.id); + dbWindow.destroy(); + }); + + if (ctx.conn.type === "mysql") { + dbWindow.loadURL(`${STUDIO_ENDPOINT}/mysql?${queryString}`); + } else if (ctx.conn.type === "postgres") { + dbWindow.loadURL(`${STUDIO_ENDPOINT}/postgres?${queryString}`); + } else if (ctx.conn.type === "starbase" || ctx.conn.type === "cloudflare") { + dbWindow.loadURL(`${STUDIO_ENDPOINT}/starbase?${queryString}`); + } else { + dbWindow.loadURL(`${STUDIO_ENDPOINT}/sqlite?${queryString}`); + } + + if (isDev || ctx.enableDebug) { + dbWindow.webContents.openDevTools(); + dbWindow.maximize(); + } + + dbWindow.show(); +} From 89ca407c315d84660b68d6265f09f0aa5ec13360 Mon Sep 17 00:00:00 2001 From: "@roth-dev" Date: Mon, 23 Dec 2024 16:40:49 +0700 Subject: [PATCH 06/10] add utility function --- electron/utils/index.ts | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 electron/utils/index.ts diff --git a/electron/utils/index.ts b/electron/utils/index.ts new file mode 100644 index 0000000..a19bbf0 --- /dev/null +++ b/electron/utils/index.ts @@ -0,0 +1,34 @@ +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const isMac = process.platform === "darwin"; +const isWindow = process.platform === "win32"; +const isLinux = process.platform === "linux"; + +function getOuterbaseDir() { + return path.dirname(fileURLToPath(import.meta.url)); +} + +const isDev = process.env.NODE_ENV === "development"; + +/** + * + * @param connId connection id string (optional) + * @returns window configuration object + */ +function getWindowConfig(connId?: string) { + return { + icon: path.join(process.env.VITE_PUBLIC, "electron-vite.svg"), + show: false, + width: 1024, + height: 768, + autoHideMenuBar: true, + webPreferences: { + devTools: true, + additionalArguments: ["--database=" + connId], + preload: path.join(getOuterbaseDir(), "preload.mjs"), + }, + }; +} + +export { isMac, isLinux, isWindow, isDev, getWindowConfig, getOuterbaseDir }; From cf67e8fdc4d0715550eb275ba784de3df224472d Mon Sep 17 00:00:00 2001 From: "@roth-dev" Date: Mon, 23 Dec 2024 16:42:49 +0700 Subject: [PATCH 07/10] set initial route when new connection window --- electron/constants/index.ts | 6 ++++++ src/components/toolbar.tsx | 6 +++--- src/database/editor-route.tsx | 11 +++++------ src/database/index.tsx | 10 +++++++++- src/hooks/useGoback.ts | 17 +++++++++++++++++ 5 files changed, 40 insertions(+), 10 deletions(-) create mode 100644 electron/constants/index.ts create mode 100644 src/hooks/useGoback.ts diff --git a/electron/constants/index.ts b/electron/constants/index.ts new file mode 100644 index 0000000..bcd71dd --- /dev/null +++ b/electron/constants/index.ts @@ -0,0 +1,6 @@ +export const STUDIO_ENDPOINT = "https://studio.outerbase.com/embed"; +// const STUDIO_ENDPOINT = "http://localhost:3008/embed"; + +export const OUTERBASE_WEBSITE = "https://outerbase.com"; +export const OUTERBASE_GITHUB = + "https://github.com/outerbase/studio-desktop/issues"; diff --git a/src/components/toolbar.tsx b/src/components/toolbar.tsx index d7f820a..3966c5e 100644 --- a/src/components/toolbar.tsx +++ b/src/components/toolbar.tsx @@ -6,7 +6,7 @@ import { DropdownMenuTrigger, } from "./ui/dropdown-menu"; import { LucideChevronLeft } from "lucide-react"; -import { useNavigate } from "react-router-dom"; +import useGoback from "@/hooks/useGoback"; export function Toolbar({ children }: PropsWithChildren) { return
{children}
; @@ -38,10 +38,10 @@ export function ToolbarDropdown({ } export function ToolbarBackButton() { - const navigate = useNavigate(); + const goBack = useGoback(); return ( - ); diff --git a/src/database/editor-route.tsx b/src/database/editor-route.tsx index 4955136..6cb6f3f 100644 --- a/src/database/editor-route.tsx +++ b/src/database/editor-route.tsx @@ -1,6 +1,6 @@ import { Toolbar, ToolbarBackButton, ToolbarTitle } from "@/components/toolbar"; import ConnectionEditor from "./editor"; -import { useLocation, useNavigate, useParams } from "react-router-dom"; +import { useLocation, useParams } from "react-router-dom"; import { ConnectionStoreItem, ConnectionStoreManager, @@ -8,15 +8,14 @@ import { } from "@/lib/conn-manager-store"; import { useCallback, useState } from "react"; import { Button } from "@/components/ui/button"; +import useGoback from "@/hooks/useGoback"; export function ConnectionCreateUpdateRoute() { const { type, connectionId } = useParams<{ type: string; connectionId?: string; }>(); - const { state: locationState } = useLocation(); - - const navigate = useNavigate(); + const goBack = useGoback(); const template = connectionTypeTemplates[type as string]; const [value, setValue] = useState(() => { @@ -35,8 +34,8 @@ export function ConnectionCreateUpdateRoute() { const onSaveClicked = useCallback(() => { ConnectionStoreManager.save(value); - navigate(-1); - }, [value, navigate]); + goBack(); + }, [value, goBack]); return (
diff --git a/src/database/index.tsx b/src/database/index.tsx index 9f771c6..3c68ab7 100644 --- a/src/database/index.tsx +++ b/src/database/index.tsx @@ -376,5 +376,13 @@ const ROUTE_LIST = [ ]; export default function DatabaseTab() { - return ; + const [initialRoute] = useState(() => { + const location = window.location; + if (!location || location.pathname === "/") { + return "/connection"; + } + return window.location.pathname; + }); + + return ; } diff --git a/src/hooks/useGoback.ts b/src/hooks/useGoback.ts new file mode 100644 index 0000000..a899f78 --- /dev/null +++ b/src/hooks/useGoback.ts @@ -0,0 +1,17 @@ +import { useCallback } from "react"; +import { useNavigate } from "react-router-dom"; + +export default function useGoback() { + const navigate = useNavigate(); + + const goBack = useCallback(() => { + const canGoBack = window.history.length > 1; + if (canGoBack) { + navigate(-1); + } else { + navigate("/connection"); + } + }, [navigate]); + + return goBack; +} From 5ca0d4d9fa7c72e9f4eea2d0da6f3b2111bea2cc Mon Sep 17 00:00:00 2001 From: "@roth-dev" Date: Tue, 24 Dec 2024 21:55:41 +0700 Subject: [PATCH 08/10] refactor code --- electron/ipc/docker.ts | 11 +- electron/ipc/index.ts | 2 + electron/{menu/index.ts => ipc/menu.ts} | 50 ++++--- electron/main.ts | 124 ++++------------- electron/utils/index.ts | 2 +- electron/window/create-connection-window.ts | 34 ----- electron/window/create-database.ts | 12 +- electron/window/main-window.ts | 145 ++++++++++++++++++++ src/database/index.tsx | 13 +- src/hooks/useNavigateToRoute.ts | 18 +++ 10 files changed, 236 insertions(+), 175 deletions(-) create mode 100644 electron/ipc/index.ts rename electron/{menu/index.ts => ipc/menu.ts} (82%) delete mode 100644 electron/window/create-connection-window.ts create mode 100644 electron/window/main-window.ts create mode 100644 src/hooks/useNavigateToRoute.ts diff --git a/electron/ipc/docker.ts b/electron/ipc/docker.ts index c4e23b5..90107bb 100644 --- a/electron/ipc/docker.ts +++ b/electron/ipc/docker.ts @@ -3,7 +3,7 @@ import { ipcMain, shell } from "electron"; import Docker, { ContainerInspectInfo } from "dockerode"; import { getUserDataPath } from "./../file-helper"; import { type DatabaseInstanceStoreItem } from "@/lib/db-manager-store"; -import { OuterbaseApplication } from "../type"; +import { MainWindow } from "../window/main-window"; export interface PullImageProgress { status: string; @@ -12,7 +12,8 @@ export interface PullImageProgress { id: string; } -export function bindDockerIpc(app: OuterbaseApplication) { +export function bindDockerIpc(main: MainWindow) { + const win = main.getWindow(); const docker = new Docker(); let eventStream: NodeJS.ReadableStream | undefined; let dockerIniting = false; @@ -90,11 +91,11 @@ export function bindDockerIpc(app: OuterbaseApplication) { eventStream.on("data", (data) => { try { - if (app.win) { - app.win?.webContents.send("docker-event", data.toString()); + if (win) { + win?.webContents.send("docker-event", data.toString()); } } catch (e) { - console.error(e) + console.error(e); } }); diff --git a/electron/ipc/index.ts b/electron/ipc/index.ts new file mode 100644 index 0000000..8c82d9f --- /dev/null +++ b/electron/ipc/index.ts @@ -0,0 +1,2 @@ +export { bindMenuIpc } from "./menu"; +export { bindDockerIpc } from "./docker"; diff --git a/electron/menu/index.ts b/electron/ipc/menu.ts similarity index 82% rename from electron/menu/index.ts rename to electron/ipc/menu.ts index 2afe83c..35d89d7 100644 --- a/electron/menu/index.ts +++ b/electron/ipc/menu.ts @@ -1,27 +1,29 @@ +import { isMac } from "../utils"; +import { Setting } from "../setting"; +import { MainWindow } from "../window/main-window"; +import { OUTERBASE_GITHUB, OUTERBASE_WEBSITE } from "../constants"; import { ConnectionStoreItem } from "@/lib/conn-manager-store"; import { app, Menu, MenuItemConstructorOptions, shell } from "electron"; -import { isMac } from "../utils"; import { createDatabaseWindow, windowMap } from "../window/create-database"; -import { OuterbaseApplication } from "../type"; -import { createWindow } from "../main"; -import { createConnectionWindow } from "../window/create-connection-window"; -import { OUTERBASE_GITHUB, OUTERBASE_WEBSITE } from "../constants"; -export function createMenu( - win: OuterbaseApplication["win"], +export function bindMenuIpc( + main: MainWindow, + settings: Setting, connections: ConnectionStoreItem[], ) { - function handleClick() { - createWindow(); - } + const mainWindow = main.getWindow(); function onOpenConnectionWindow(type: ConnectionStoreItem["type"]) { - createConnectionWindow(win, type); - win?.hide(); + main.navigate(`/connection/create/${type}`); } function generateSubMenu() { - const connMenu: MenuItemConstructorOptions["submenu"] = connections.map( + const MAX_VISIBLE_CONNECTIONS = 10; + + const visibleConn = connections.slice(-MAX_VISIBLE_CONNECTIONS); + const remainConn = connections.slice(MAX_VISIBLE_CONNECTIONS); + + const connMenu: MenuItemConstructorOptions["submenu"] = visibleConn.map( (conn) => { return { label: conn.name, @@ -30,14 +32,27 @@ export function createMenu( if (existingWindow && !existingWindow.isDestroyed()) { existingWindow.focus(); } else { - createDatabaseWindow({ win, conn }); - win?.hide(); + createDatabaseWindow({ main, conn, settings }); + mainWindow?.hide(); } }, }; }, ); + if (remainConn.length > 0) { + connMenu.push({ + type: "separator", + }); + connMenu.push({ + label: "See more connection...", + click: () => { + main.show(); + main.navigate("/connection"); + }, + }); + } + return connMenu; } @@ -94,10 +109,6 @@ export function createMenu( }, ], }, - { - label: "New Window", - click: handleClick, - }, { type: "separator", }, @@ -105,7 +116,6 @@ export function createMenu( label: "Open Recent", enabled: connSubMenu.length > 0, submenu: connSubMenu, - click: handleClick, }, { type: "separator", diff --git a/electron/main.ts b/electron/main.ts index f8bfd74..9f543b6 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -6,19 +6,15 @@ import { type OpenDialogOptions, } from "electron"; import path from "node:path"; +import log from "electron-log"; +import { Setting } from "./setting"; +import { getOuterbaseDir, isMac } from "./utils"; import { ConnectionPool } from "./connection-pool"; +import { MainWindow } from "./window/main-window"; import electronUpdater, { type AppUpdater } from "electron-updater"; -import log from "electron-log"; import { type ConnectionStoreItem } from "@/lib/conn-manager-store"; -import { bindDockerIpc } from "./ipc/docker"; -import { Setting } from "./setting"; -import { OuterbaseApplication } from "./type"; -import { createMenu } from "./menu"; import { createDatabaseWindow } from "./window/create-database"; -import { getOuterbaseDir, isDev, isMac } from "./utils"; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -// const require = createRequire(import.meta.url); +import { bindMenuIpc, bindDockerIpc } from "./ipc"; export function getAutoUpdater(): AppUpdater { // Using destructuring to access autoUpdater due to the CommonJS module of 'electron-updater'. @@ -31,7 +27,7 @@ log.transports.file.level = "info"; autoUpdater.logger = log; autoUpdater.autoDownload = false; -const __dirname = getOuterbaseDir(); +const dirname = getOuterbaseDir(); // The built directory structure // @@ -43,7 +39,7 @@ const __dirname = getOuterbaseDir(); // │ │ ├── main.js // │ │ └── preload.mjs // │ -process.env.APP_ROOT = path.join(__dirname, ".."); +process.env.APP_ROOT = path.join(dirname, ".."); // 🚧 Use ['ENV_NAME'] avoid vite:define plugin - Vite@2.x export const VITE_DEV_SERVER_URL = process.env["VITE_DEV_SERVER_URL"]; @@ -55,83 +51,8 @@ process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL const settings = new Setting(); settings.load(); -const application: OuterbaseApplication = { - win: undefined, -}; - -export function createWindow() { - application.win = new BrowserWindow({ - icon: path.join(process.env.VITE_PUBLIC, "electron-vite.svg"), - title: "Outerbase Studio", - - autoHideMenuBar: true, - webPreferences: { - devTools: true, - preload: path.join(__dirname, "preload.mjs"), - }, - }); - - if (isDev) { - application.win.webContents.openDevTools({ mode: "detach" }); - } - - // Test active push message to Renderer-process. - application.win.webContents.on("did-finish-load", () => { - application.win?.webContents.send( - "main-process-message", - new Date().toLocaleString(), - ); - }); - - application.win.webContents.on("will-navigate", (event, url) => { - console.log("trying to navigate", url); - event.preventDefault(); - }); - - application.win.webContents.on("will-redirect", (event, url) => { - log.info("trying to redirect", url); - event.preventDefault(); - }); - - autoUpdater.checkForUpdatesAndNotify(); - - autoUpdater.on("checking-for-update", () => { - application.win?.webContents.send("checking-for-update"); - log.info("checking-for-update"); - }); - autoUpdater.on("update-available", (info) => { - application.win?.webContents.send("update-available", info); - log.info("update-available", info); - }); - - autoUpdater.on("update-not-available", (info) => { - application.win?.webContents.send("update-not-available", info); - log.info("update-not-available", info); - }); - - autoUpdater.on("error", (info) => { - application.win?.webContents.send("update-error", info); - log.info("error", info); - }); - - autoUpdater.on("download-progress", (progress) => { - application.win?.webContents.send("update-download-progress", progress); - log.info("download-progress", progress); - }); - - autoUpdater.on("update-downloaded", (info) => { - application.win?.webContents.send("update-downloaded", info); - log.info("update-downloaded", info); - }); - - if (VITE_DEV_SERVER_URL) { - application.win.loadURL(VITE_DEV_SERVER_URL); - } else { - // win.loadFile('dist/index.html') - application.win.loadFile(path.join(RENDERER_DIST, "index.html")); - } -} +const mainWindow = new MainWindow(); // Quit when all windows are closed, except on macOS. There, it's common // for applications and their menu bar to stay active until the user quits @@ -139,7 +60,7 @@ export function createWindow() { app.on("window-all-closed", () => { if (!isMac) { app.quit(); - application.win = undefined; + mainWindow.remove(); } }); @@ -147,19 +68,19 @@ app.on("activate", () => { // On OS X it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (BrowserWindow.getAllWindows().length === 0) { - createWindow(); + mainWindow.init(); } }); app .whenReady() - .then(createWindow) + .then(() => mainWindow.init()) .finally(() => { - bindDockerIpc(application); + bindDockerIpc(mainWindow); }); ipcMain.on("connections", (_, connections: ConnectionStoreItem[]) => { - createMenu(application.win, connections); + bindMenuIpc(mainWindow, settings, connections); }); ipcMain.handle("query", async (_, connectionId, query) => { @@ -174,23 +95,22 @@ ipcMain.handle( }, ); -ipcMain.handle("close", async (sender) => { - sender.sender.close({ +ipcMain.handle("close", async (event) => { + event.sender.close({ waitForBeforeUnload: true, }); - - if (BrowserWindow.getAllWindows().length > 1) { - application.win?.destroy(); - } }); ipcMain.handle("connect", (_, conn: ConnectionStoreItem, enableDebug) => { createDatabaseWindow({ - win: application.win, conn, + settings, + main: mainWindow, enableDebug, }); - if (application.win) application.win.hide(); + if (mainWindow.getWindow()) { + mainWindow.hide(); + } }); ipcMain.handle("test-connection", async (_, conn: ConnectionStoreItem) => { @@ -216,3 +136,7 @@ ipcMain.handle("get-setting", (_, key) => { ipcMain.handle("set-setting", (_, key, value) => { settings.set(key, value); }); + +ipcMain.on("navigate", (event, route: string) => { + event.sender.send("navigate-to", route); +}); diff --git a/electron/utils/index.ts b/electron/utils/index.ts index a19bbf0..14e7819 100644 --- a/electron/utils/index.ts +++ b/electron/utils/index.ts @@ -22,7 +22,7 @@ function getWindowConfig(connId?: string) { show: false, width: 1024, height: 768, - autoHideMenuBar: true, + autoHideMenuBar: false, webPreferences: { devTools: true, additionalArguments: ["--database=" + connId], diff --git a/electron/window/create-connection-window.ts b/electron/window/create-connection-window.ts deleted file mode 100644 index e88d424..0000000 --- a/electron/window/create-connection-window.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { BrowserWindow } from "electron"; -import { getWindowConfig } from "../utils"; -import path from "node:path"; -import { RENDERER_DIST, VITE_DEV_SERVER_URL } from "../main"; -import { ConnectionStoreItem } from "@/lib/conn-manager-store"; -import { OuterbaseApplication } from "electron/type"; - -export function createConnectionWindow( - win: OuterbaseApplication["win"], - type: ConnectionStoreItem["type"], -) { - const newWin = new BrowserWindow(getWindowConfig()); - const route = "connection/create/" + type; - - if (VITE_DEV_SERVER_URL) { - const url = `${VITE_DEV_SERVER_URL}${route}`; - newWin.loadURL(url); - } else { - // win.loadFile('dist/index.html') - const filePath = path.join(RENDERER_DIST, "index.html"); - newWin.loadFile(filePath, { - hash: `#/${route}`, - }); - } - - newWin.show(); - - newWin.on("closed", () => { - if (BrowserWindow.getAllWindows().length === 1) { - win?.show(); - } - newWin.destroy(); - }); -} diff --git a/electron/window/create-database.ts b/electron/window/create-database.ts index 5c8c5c3..5717820 100644 --- a/electron/window/create-database.ts +++ b/electron/window/create-database.ts @@ -4,21 +4,21 @@ import { BrowserWindow } from "electron"; import { ConnectionPool } from "../connection-pool"; import { STUDIO_ENDPOINT } from "../constants"; import { Setting } from "../setting"; -import { OuterbaseApplication } from "../type"; import { getWindowConfig, isDev } from "../utils"; +import { MainWindow } from "./main-window"; export const windowMap = new Map(); export function createDatabaseWindow(ctx: { - win: OuterbaseApplication["win"]; + main: MainWindow; + settings: Setting; conn: ConnectionStoreItem; enableDebug?: boolean; }) { + const win = ctx.main.getWindow(); const dbWindow = new BrowserWindow(getWindowConfig(ctx.conn.id)); - const settings = new Setting(); - settings.load(); - const theme = settings.get("theme") || "light"; + const theme = ctx.settings.get("theme") || "light"; ConnectionPool.create(ctx.conn); @@ -31,7 +31,7 @@ export function createDatabaseWindow(ctx: { dbWindow.on("closed", () => { if (windowMap.size === 1) { - ctx.win?.show(); + win?.show(); } windowMap.delete(ctx.conn.id); ConnectionPool.close(ctx.conn.id); diff --git a/electron/window/main-window.ts b/electron/window/main-window.ts new file mode 100644 index 0000000..5e7de3d --- /dev/null +++ b/electron/window/main-window.ts @@ -0,0 +1,145 @@ +import path from "node:path"; +import log from "electron-log"; +import { BrowserWindow } from "electron"; +import { OuterbaseApplication } from "../type"; +import { getOuterbaseDir, isDev } from "../utils"; +import { VITE_DEV_SERVER_URL, RENDERER_DIST, getAutoUpdater } from "../main"; + +const autoUpdater = getAutoUpdater(); + +/** + * WARNING: Avoid using this main window as multiple window + */ +export class MainWindow { + private application: OuterbaseApplication = {}; + + constructor() { + this.application.win = undefined; + } + + /** + * Initialize the main window + */ + public init() { + const dirname = getOuterbaseDir(); + + this.application.win = new BrowserWindow({ + icon: path.join(process.env.VITE_PUBLIC, "electron-vite.svg"), + title: "Outerbase Studio", + autoHideMenuBar: true, + webPreferences: { + devTools: true, + preload: path.join(dirname, "preload.mjs"), + }, + }); + if (isDev) { + this.application.win.webContents.openDevTools({ mode: "detach" }); + } + + this.application.win.on("close", (event) => { + if (BrowserWindow.getAllWindows().length === 1) { + this.application.win?.destroy(); + } else { + this.navigate("/connection"); + this.hide(); + } + event.preventDefault(); + }); + + // Test active push message to Renderer-process. + this.application.win.webContents.on("did-finish-load", () => { + this.application.win?.webContents.send( + "main-process-message", + new Date().toLocaleString(), + ); + }); + + this.application.win.webContents.on("will-navigate", (event, url) => { + console.log("trying to navigate", url); + event.preventDefault(); + }); + + this.application.win.webContents.on("will-redirect", (event, url) => { + log.info("trying to redirect", url); + event.preventDefault(); + }); + + autoUpdater.checkForUpdatesAndNotify(); + + autoUpdater.on("checking-for-update", () => { + this.application.win?.webContents.send("checking-for-update"); + log.info("checking-for-update"); + }); + + autoUpdater.on("update-available", (info) => { + this.application.win?.webContents.send("update-available", info); + log.info("update-available", info); + }); + + autoUpdater.on("update-not-available", (info) => { + this.application.win?.webContents.send("update-not-available", info); + log.info("update-not-available", info); + }); + + autoUpdater.on("error", (info) => { + this.application.win?.webContents.send("update-error", info); + log.info("error", info); + }); + + autoUpdater.on("download-progress", (progress) => { + this.application.win?.webContents.send( + "update-download-progress", + progress, + ); + log.info("download-progress", progress); + }); + + autoUpdater.on("update-downloaded", (info) => { + this.application.win?.webContents.send("update-downloaded", info); + log.info("update-downloaded", info); + }); + + if (VITE_DEV_SERVER_URL) { + this.application.win.loadURL(VITE_DEV_SERVER_URL); + } else { + // win.loadFile('dist/index.html') + this.application.win.loadFile(path.join(RENDERER_DIST, "index.html")); + } + } + + public navigate(routeName: string): void { + if (this.application.win) { + if (!this.application.win.isVisible()) { + this.show(); + } + if (this.application.win.isDestroyed()) { + this.init(); + } + this.application.win.webContents.send("navigate-to", routeName); + } + } + + public hide(): void { + this.application.win?.hide(); + } + + public show(): void { + if (!this.application.win || this.application.win.isDestroyed()) { + this.init; + } else { + this.application.win?.show(); + } + } + + /** + * + * @returns Browswer window + */ + public getWindow(): OuterbaseApplication["win"] { + return this.application.win; + } + + public remove() { + this.application.win = undefined; + } +} diff --git a/src/database/index.tsx b/src/database/index.tsx index 3c68ab7..75c4925 100644 --- a/src/database/index.tsx +++ b/src/database/index.tsx @@ -57,6 +57,7 @@ import { AnimatePresence, motion } from "framer-motion"; import { useToast } from "@/hooks/use-toast"; import { cn } from "@/lib/utils"; import ImportConnectionStringRoute from "./import-connection-string"; +import useNavigateToRoute from "@/hooks/useNavigateToRoute"; const connectionTypeList = [ "mysql", @@ -253,6 +254,8 @@ function ConnectionItem({ } function ConnectionListRoute() { + useNavigateToRoute(); + const [connectionList, setConnectionList] = useState(() => { return ConnectionStoreManager.list(); }); @@ -376,13 +379,5 @@ const ROUTE_LIST = [ ]; export default function DatabaseTab() { - const [initialRoute] = useState(() => { - const location = window.location; - if (!location || location.pathname === "/") { - return "/connection"; - } - return window.location.pathname; - }); - - return ; + return ; } diff --git a/src/hooks/useNavigateToRoute.ts b/src/hooks/useNavigateToRoute.ts new file mode 100644 index 0000000..1e1f150 --- /dev/null +++ b/src/hooks/useNavigateToRoute.ts @@ -0,0 +1,18 @@ +import { useEffect } from "react"; +import { useNavigate } from "react-router-dom"; + +export default function useNavigateToRoute() { + const navigate = useNavigate(); + + useEffect(() => { + const navigateToHandler = (_: Electron.IpcRendererEvent, route: string) => { + navigate(route); + }; + + window.outerbaseIpc.on("navigate-to", navigateToHandler); + + return () => { + window.outerbaseIpc.off("navigate-to", navigateToHandler); + }; + }, [navigate]); +} From 9d88700e650588c2d76ea25194ad5e32f23dcc8d Mon Sep 17 00:00:00 2001 From: "@roth-dev" Date: Tue, 24 Dec 2024 22:23:41 +0700 Subject: [PATCH 09/10] prevent destroyed main window --- electron/ipc/menu.ts | 8 ++++++-- electron/window/main-window.ts | 7 +++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/electron/ipc/menu.ts b/electron/ipc/menu.ts index 35d89d7..d30ab99 100644 --- a/electron/ipc/menu.ts +++ b/electron/ipc/menu.ts @@ -32,8 +32,12 @@ export function bindMenuIpc( if (existingWindow && !existingWindow.isDestroyed()) { existingWindow.focus(); } else { - createDatabaseWindow({ main, conn, settings }); - mainWindow?.hide(); + if (!mainWindow?.isDestroyed()) { + if (mainWindow?.isVisible()) { + main.hide(); + } + createDatabaseWindow({ main, conn, settings }); + } } }, }; diff --git a/electron/window/main-window.ts b/electron/window/main-window.ts index 5e7de3d..b2ac692 100644 --- a/electron/window/main-window.ts +++ b/electron/window/main-window.ts @@ -109,11 +109,10 @@ export class MainWindow { public navigate(routeName: string): void { if (this.application.win) { - if (!this.application.win.isVisible()) { - this.show(); - } if (this.application.win.isDestroyed()) { this.init(); + } else { + this.show(); } this.application.win.webContents.send("navigate-to", routeName); } @@ -125,7 +124,7 @@ export class MainWindow { public show(): void { if (!this.application.win || this.application.win.isDestroyed()) { - this.init; + this.init(); } else { this.application.win?.show(); } From d22d5d6268c1cab997c28300b0511aa3a0737933 Mon Sep 17 00:00:00 2001 From: "@roth-dev" Date: Tue, 24 Dec 2024 22:39:21 +0700 Subject: [PATCH 10/10] bind menu ipc --- electron/ipc/menu.ts | 331 ++++++++++++++++++++++--------------------- electron/main.ts | 5 +- 2 files changed, 170 insertions(+), 166 deletions(-) diff --git a/electron/ipc/menu.ts b/electron/ipc/menu.ts index d30ab99..bd063f6 100644 --- a/electron/ipc/menu.ts +++ b/electron/ipc/menu.ts @@ -3,187 +3,194 @@ import { Setting } from "../setting"; import { MainWindow } from "../window/main-window"; import { OUTERBASE_GITHUB, OUTERBASE_WEBSITE } from "../constants"; import { ConnectionStoreItem } from "@/lib/conn-manager-store"; -import { app, Menu, MenuItemConstructorOptions, shell } from "electron"; +import { + app, + ipcMain, + Menu, + MenuItemConstructorOptions, + shell, +} from "electron"; import { createDatabaseWindow, windowMap } from "../window/create-database"; -export function bindMenuIpc( - main: MainWindow, - settings: Setting, - connections: ConnectionStoreItem[], -) { +export function bindMenuIpc(main: MainWindow, settings: Setting) { const mainWindow = main.getWindow(); - function onOpenConnectionWindow(type: ConnectionStoreItem["type"]) { - main.navigate(`/connection/create/${type}`); - } + function createMenu(connections: ConnectionStoreItem[]) { + function onOpenConnectionWindow(type: ConnectionStoreItem["type"]) { + main.navigate(`/connection/create/${type}`); + } - function generateSubMenu() { - const MAX_VISIBLE_CONNECTIONS = 10; + function generateSubMenu() { + const MAX_VISIBLE_CONNECTIONS = 10; - const visibleConn = connections.slice(-MAX_VISIBLE_CONNECTIONS); - const remainConn = connections.slice(MAX_VISIBLE_CONNECTIONS); + const visibleConn = connections.slice(-MAX_VISIBLE_CONNECTIONS); + const remainConn = connections.slice(MAX_VISIBLE_CONNECTIONS); - const connMenu: MenuItemConstructorOptions["submenu"] = visibleConn.map( - (conn) => { - return { - label: conn.name, - click: () => { - const existingWindow = windowMap.get(conn.id); - if (existingWindow && !existingWindow.isDestroyed()) { - existingWindow.focus(); - } else { - if (!mainWindow?.isDestroyed()) { - if (mainWindow?.isVisible()) { - main.hide(); + const connMenu: MenuItemConstructorOptions["submenu"] = visibleConn.map( + (conn) => { + return { + label: conn.name, + click: () => { + const existingWindow = windowMap.get(conn.id); + if (existingWindow && !existingWindow.isDestroyed()) { + existingWindow.focus(); + } else { + if (!mainWindow?.isDestroyed()) { + if (mainWindow?.isVisible()) { + main.hide(); + } + createDatabaseWindow({ main, conn, settings }); } - createDatabaseWindow({ main, conn, settings }); } - } - }, - }; - }, - ); - - if (remainConn.length > 0) { - connMenu.push({ - type: "separator", - }); - connMenu.push({ - label: "See more connection...", - click: () => { - main.show(); - main.navigate("/connection"); + }, + }; }, - }); - } + ); - return connMenu; - } + if (remainConn.length > 0) { + connMenu.push({ + type: "separator", + }); + connMenu.push({ + label: "See more connection...", + click: () => { + main.show(); + main.navigate("/connection"); + }, + }); + } - const connSubMenu = generateSubMenu(); + return connMenu; + } - const customTemplate = [ - ...(isMac - ? [ + const connSubMenu = generateSubMenu(); + const customTemplate = [ + ...(isMac + ? [ + { + label: app.name, + submenu: [ + { role: "about" }, + { type: "separator" }, + { role: "services" }, + { type: "separator" }, + { role: "hide" }, + { role: "hideOthers" }, + { role: "unhide" }, + { type: "separator" }, + { role: "quit" }, + ], + }, + ] + : []), + { + label: "File", + submenu: [ { - label: app.name, + label: "New Connection", submenu: [ - { role: "about" }, - { type: "separator" }, - { role: "services" }, - { type: "separator" }, - { role: "hide" }, - { role: "hideOthers" }, - { role: "unhide" }, - { type: "separator" }, - { role: "quit" }, + { + label: "MySQL", + click: () => onOpenConnectionWindow("mysql"), + }, + { + label: "PostgreSQL", + click: () => onOpenConnectionWindow("postgres"), + }, + { + label: "SQLite", + click: () => onOpenConnectionWindow("sqlite"), + }, + { + label: "Turso", + click: () => onOpenConnectionWindow("turso"), + }, + { + label: "Cloudflare", + click: () => onOpenConnectionWindow("cloudflare"), + }, + { + label: "Starbase", + click: () => onOpenConnectionWindow("starbase"), + }, ], }, - ] - : []), - { - label: "File", - submenu: [ - { - label: "New Connection", - submenu: [ - { - label: "MySQL", - click: () => onOpenConnectionWindow("mysql"), - }, - { - label: "PostgreSQL", - click: () => onOpenConnectionWindow("postgres"), - }, - { - label: "SQLite", - click: () => onOpenConnectionWindow("sqlite"), - }, - { - label: "Turso", - click: () => onOpenConnectionWindow("turso"), - }, - { - label: "Cloudflare", - click: () => onOpenConnectionWindow("cloudflare"), - }, - { - label: "Starbase", - click: () => onOpenConnectionWindow("starbase"), + { + type: "separator", + }, + { + label: "Open Recent", + enabled: connSubMenu.length > 0, + submenu: connSubMenu, + }, + { + type: "separator", + }, + { role: "close" }, + ...(isMac ? [] : [{ label: "Exit", role: "quit" }]), + ], + }, + { + label: "Edit", + submenu: [ + { role: "undo" }, + { role: "redo" }, + { type: "separator" }, + { role: "cut" }, + { role: "copy" }, + { role: "paste" }, + { role: "delete" }, + { role: "selectAll" }, + ], + }, + { + label: "View", + submenu: [ + { role: "reload" }, + { role: "forceReload" }, + { role: "toggleDevTools" }, + { type: "separator" }, + { role: "resetZoom" }, + { role: "zoomIn" }, + { role: "zoomOut" }, + { type: "separator" }, + { role: "togglefullscreen" }, + ], + }, + { + label: "Window", + submenu: [ + { role: "minimize" }, + { role: "zoom" }, + ...(isMac + ? [{ type: "separator" }, { role: "front" }] + : [{ role: "close" }]), + ], + }, + { + label: "Help", + submenu: [ + { + label: "About Us", + click: async () => { + await shell.openExternal(OUTERBASE_WEBSITE); }, - ], - }, - { - type: "separator", - }, - { - label: "Open Recent", - enabled: connSubMenu.length > 0, - submenu: connSubMenu, - }, - { - type: "separator", - }, - { role: "close" }, - ...(isMac ? [] : [{ label: "Exit", role: "quit" }]), - ], - }, - { - label: "Edit", - submenu: [ - { role: "undo" }, - { role: "redo" }, - { type: "separator" }, - { role: "cut" }, - { role: "copy" }, - { role: "paste" }, - { role: "delete" }, - { role: "selectAll" }, - ], - }, - { - label: "View", - submenu: [ - { role: "reload" }, - { role: "forceReload" }, - { role: "toggleDevTools" }, - { type: "separator" }, - { role: "resetZoom" }, - { role: "zoomIn" }, - { role: "zoomOut" }, - { type: "separator" }, - { role: "togglefullscreen" }, - ], - }, - { - label: "Window", - submenu: [ - { role: "minimize" }, - { role: "zoom" }, - ...(isMac - ? [{ type: "separator" }, { role: "front" }] - : [{ role: "close" }]), - ], - }, - { - label: "Help", - submenu: [ - { - label: "About Us", - click: async () => { - await shell.openExternal(OUTERBASE_WEBSITE); }, - }, - { - label: "Report issues", - click: async () => { - await shell.openExternal(OUTERBASE_GITHUB); + { + label: "Report issues", + click: async () => { + await shell.openExternal(OUTERBASE_GITHUB); + }, }, - }, - ], - }, - ] as MenuItemConstructorOptions[]; + ], + }, + ] as MenuItemConstructorOptions[]; + + const menu = Menu.buildFromTemplate(customTemplate); + Menu.setApplicationMenu(menu); + } - const menu = Menu.buildFromTemplate(customTemplate); - Menu.setApplicationMenu(menu); + ipcMain.on("connections", (_, connections: ConnectionStoreItem[]) => { + createMenu(connections); + }); } diff --git a/electron/main.ts b/electron/main.ts index 9f543b6..8b84ae8 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -77,12 +77,9 @@ app .then(() => mainWindow.init()) .finally(() => { bindDockerIpc(mainWindow); + bindMenuIpc(mainWindow, settings); }); -ipcMain.on("connections", (_, connections: ConnectionStoreItem[]) => { - bindMenuIpc(mainWindow, settings, connections); -}); - ipcMain.handle("query", async (_, connectionId, query) => { const r = await ConnectionPool.query(connectionId, query); return r;