Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to connect database multiple window #19

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
5 changes: 5 additions & 0 deletions electron/connection-pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@ 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";
import { ConnectionPoolType } from "./type";

export class ConnectionPool {
static connections: Record<string, BaseDriver> = {};

protected static createDBPool(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}`);
}
let connectionPool: ConnectionPoolType;
Expand Down
6 changes: 6 additions & 0 deletions electron/constants/index.ts
Original file line number Diff line number Diff line change
@@ -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";
82 changes: 21 additions & 61 deletions electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ 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";
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);
Expand All @@ -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
// │ │
Expand All @@ -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;
Expand All @@ -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<ThemeType>("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",
Expand All @@ -123,7 +71,7 @@ function createWindow() {
},
});

if (process.env.NODE_ENV === "development") {
if (isDev) {
application.win.webContents.openDevTools({ mode: "detach" });
}

Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
Expand All @@ -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();
});

Expand Down
175 changes: 175 additions & 0 deletions electron/menu/index.ts
Original file line number Diff line number Diff line change
@@ -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();
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to maintain only one main windows. We can have multiple database windows, but should only have one main window


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;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure you slice the connection let say last 10, if there is more than 10, you can put "See more connections"


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);
}
4 changes: 4 additions & 0 deletions electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ const outerbaseIpc = {
return ipcRenderer.invoke("test-connection", conn);
},

getConnection() {
return ipcRenderer.invoke("get-connection");
},

downloadUpdate() {
return ipcRenderer.invoke("download-update");
},
Expand Down
34 changes: 34 additions & 0 deletions electron/utils/index.ts
Original file line number Diff line number Diff line change
@@ -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,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

autoHideMenuBar: false,

If it is true, Windows will never have menu

webPreferences: {
devTools: true,
additionalArguments: ["--database=" + connId],
preload: path.join(getOuterbaseDir(), "preload.mjs"),
},
};
}

export { isMac, isLinux, isWindow, isDev, getWindowConfig, getOuterbaseDir };
Loading