Skip to content

Commit 73947ea

Browse files
committed
Electron updates
1 parent d00c0a5 commit 73947ea

7 files changed

Lines changed: 249 additions & 39 deletions

File tree

packages/electron/package.json

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,6 @@
1717
"email": "julian@gojani.xyz"
1818
},
1919
"license": "MIT",
20-
"pnpm": {
21-
"onlyBuiltDependencies": [
22-
"electron",
23-
"electron-winstaller"
24-
]
25-
},
2620
"devDependencies": {
2721
"@electron-forge/cli": "^7.8.1",
2822
"@electron-forge/maker-deb": "^7.8.1",
@@ -32,7 +26,9 @@
3226
"@electron-forge/plugin-auto-unpack-natives": "^7.8.1",
3327
"@electron-forge/plugin-fuses": "^7.8.1",
3428
"@electron-forge/plugin-vite": "^7.8.1",
29+
"@tailwindcss/vite": "^4.1.7",
3530
"@electron/fuses": "^1.8.0",
31+
"@vitejs/plugin-vue": "^5.2.3",
3632
"electron": "36.2.1",
3733
"vite": "^5.0.12"
3834
},

packages/electron/src/main.js

Lines changed: 153 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,189 @@
1-
import { app, BrowserWindow } from 'electron';
1+
import { app, BrowserWindow, dialog, ipcMain } from 'electron';
22
import path from 'node:path';
3+
import fs from 'node:fs';
34
import started from 'electron-squirrel-startup';
5+
import { startBackendServer } from '../../backend/src/server.ts';
46

57
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
68
if (started) {
79
app.quit();
810
}
911

12+
const appDataPath = app.getPath('appData');
13+
const stableUserDataPath = path.join(appDataPath, 'StarQuery');
14+
const legacyUserDataPaths = [
15+
path.join(appDataPath, 'star-query'),
16+
path.join(appDataPath, 'Electron'),
17+
];
18+
19+
if (!fs.existsSync(stableUserDataPath)) {
20+
const legacyPath = legacyUserDataPaths.find((candidate) => fs.existsSync(candidate));
21+
if (legacyPath) {
22+
fs.mkdirSync(stableUserDataPath, { recursive: true });
23+
fs.cpSync(legacyPath, stableUserDataPath, { recursive: true, force: false, errorOnExist: false });
24+
}
25+
}
26+
27+
app.setName('StarQuery');
28+
app.setPath('userData', stableUserDataPath);
29+
30+
const getDesktopConfigPath = () => path.join(app.getPath('userData'), 'starquery-desktop.json');
31+
const getLocalMetaStorePath = () => path.join(app.getPath('userData'), 'backend', 'starquery-meta.sqlite');
32+
33+
let localBackend = null;
34+
let isQuitting = false;
35+
36+
const readDesktopConfig = () => {
37+
const filePath = getDesktopConfigPath();
38+
39+
if (!fs.existsSync(filePath)) {
40+
return {};
41+
}
42+
43+
try {
44+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
45+
} catch {
46+
return {};
47+
}
48+
};
49+
50+
const writeDesktopConfig = (config) => {
51+
const filePath = getDesktopConfigPath();
52+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
53+
fs.writeFileSync(filePath, JSON.stringify(config, null, 2));
54+
};
55+
56+
const hasRunningLocalBackend = () => Boolean(localBackend?.server?.listening);
57+
58+
const startLocalBackend = async () => {
59+
if (hasRunningLocalBackend()) {
60+
return localBackend;
61+
}
62+
63+
if (localBackend) {
64+
try {
65+
await localBackend.close();
66+
} catch (error) {
67+
console.error('Failed to clean up stale local StarQuery backend', error);
68+
} finally {
69+
localBackend = null;
70+
}
71+
}
72+
73+
localBackend = await startBackendServer({
74+
host: '127.0.0.1',
75+
port: 0,
76+
serverName: 'Local Computer',
77+
mode: 'local',
78+
metaStore: {
79+
driver: 'sqlite',
80+
sqlitePath: getLocalMetaStorePath(),
81+
},
82+
});
83+
84+
return localBackend;
85+
};
86+
1087
const createWindow = async () => {
1188
// Create the browser window.
1289
const mainWindow = new BrowserWindow({
1390
icon: '../images/128x128.png',
14-
width: 800,
15-
height: 600,
91+
width: 1280,
92+
height: 720,
93+
...('darwin' ? {vibrancy: 'sidebar',
94+
visualEffectState: 'active',
95+
transparent: true} : {}),
96+
1697
webPreferences: {
1798
preload: path.join(__dirname, 'preload.js'),
1899
},
19-
titleBarStyle: 'hidden'
100+
titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'hidden'
101+
});
102+
103+
mainWindow.webContents.on("did-finish-load", () => {
104+
const electronClasses = ['electron'];
105+
if (process.platform === 'darwin') {
106+
electronClasses.push('electron-mac');
107+
}
108+
109+
mainWindow.webContents.executeJavaScript(`
110+
document.documentElement.classList.add(${electronClasses.map((value) => JSON.stringify(value)).join(', ')});
111+
`);
20112
});
21113

22114
// and load the index.html of the app.
23115
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
24-
mainWindow.loadURL(/*MAIN_WINDOW_VITE_DEV_SERVER_URL*/'http://localhost:5173/');
116+
await mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);
25117
} else {
26-
mainWindow.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`));
118+
await mainWindow.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`));
27119
}
28120

29-
mainWindow.webContents.on("did-finish-load", () => {
30-
mainWindow.webContents.executeJavaScript(`
31-
document.documentElement.classList.add("electron");
32-
`);
33-
});
34-
35121
// Open the DevTools.
36122
//mainWindow.webContents.openDevTools();
37123
};
38124

39-
app.whenReady().then(() => {
40-
createWindow();
125+
app.whenReady().then(async () => {
126+
try {
127+
await startLocalBackend();
128+
} catch (error) {
129+
console.error('Failed to start local StarQuery backend', error);
130+
}
131+
132+
ipcMain.handle('starquery:desktop-config:get', () => readDesktopConfig());
133+
ipcMain.handle('starquery:desktop-config:set', (_event, config) => {
134+
writeDesktopConfig(config);
135+
return { ok: true };
136+
});
137+
ipcMain.handle('starquery:local-server:url', async () => {
138+
try {
139+
await startLocalBackend();
140+
} catch (error) {
141+
console.error('Failed to provide local backend URL', error);
142+
return null;
143+
}
144+
145+
return localBackend?.url ?? null;
146+
});
147+
ipcMain.handle('starquery:sqlite-file:pick', async () => {
148+
const result = await dialog.showOpenDialog({
149+
properties: ['openFile'],
150+
filters: [
151+
{ name: 'SQLite Databases', extensions: ['sqlite', 'sqlite3', 'db', 'db3'] },
152+
{ name: 'All Files', extensions: ['*'] },
153+
],
154+
});
155+
156+
return result.canceled ? null : (result.filePaths[0] ?? null);
157+
});
158+
159+
await createWindow();
41160
app.on('activate', () => {
42161
if (BrowserWindow.getAllWindows().length === 0) {
43-
createWindow();
162+
void createWindow();
44163
}
45164
});
46165
});
47166

167+
app.on('before-quit', (event) => {
168+
if (isQuitting) {
169+
return;
170+
}
171+
172+
isQuitting = true;
173+
event.preventDefault();
174+
175+
Promise.resolve(localBackend?.close())
176+
.catch((error) => {
177+
console.error('Failed to stop local StarQuery backend', error);
178+
})
179+
.finally(() => {
180+
localBackend = null;
181+
app.quit();
182+
});
183+
});
184+
48185
app.on('window-all-closed', () => {
49186
if (process.platform !== 'darwin') {
50187
app.quit();
51188
}
52-
});
189+
});

packages/electron/src/preload.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
1-
// See the Electron documentation for details on how to use preload scripts:
2-
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts
1+
import { contextBridge, ipcRenderer } from 'electron';
2+
3+
contextBridge.exposeInMainWorld('starqueryDesktop', {
4+
isElectron: true,
5+
getConfig: () => ipcRenderer.invoke('starquery:desktop-config:get'),
6+
setConfig: (config) => ipcRenderer.invoke('starquery:desktop-config:set', config),
7+
getLocalServerUrl: () => ipcRenderer.invoke('starquery:local-server:url'),
8+
pickSqliteFile: () => ipcRenderer.invoke('starquery:sqlite-file:pick'),
9+
});

packages/electron/src/renderer.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
import './index.css';
2-
1+
import '../../frontend/src/main.ts';
32

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = class PgNativeUnavailable {
2+
constructor() {
3+
throw new Error('pg-native is not available in this Electron build')
4+
}
5+
}
Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,55 @@
1-
import { defineConfig } from 'vite';
1+
import fs from 'node:fs'
2+
import path from 'node:path'
3+
import { fileURLToPath, URL } from 'node:url'
4+
import { defineConfig } from 'vite'
25

3-
// https://vitejs.dev/config
4-
export default defineConfig({});
6+
const pgNativeShim = fileURLToPath(new URL('./src/shims/pg-native.cjs', import.meta.url))
7+
const backendDrizzleMigrationsPath = fileURLToPath(new URL('../backend/src/meta/drizzle', import.meta.url))
8+
9+
function patchNamespaceInteropHelper() {
10+
return {
11+
name: 'patch-namespace-interop-helper',
12+
renderChunk(code) {
13+
if (!code.includes('function _interopNamespaceDefault')) {
14+
return null
15+
}
16+
17+
return {
18+
code: code.replaceAll(
19+
'Object.defineProperty(n, k, d.get ? d : {',
20+
'Object.defineProperty(n, k, d && d.get ? d : {',
21+
),
22+
map: null,
23+
}
24+
},
25+
}
26+
}
27+
28+
function copyMetaMigrationsPlugin() {
29+
return {
30+
name: 'copy-meta-drizzle-migrations',
31+
writeBundle(outputOptions) {
32+
const outputDir = outputOptions.dir
33+
if (!outputDir) {
34+
return
35+
}
36+
37+
const targetPath = path.join(outputDir, 'drizzle')
38+
fs.rmSync(targetPath, { recursive: true, force: true })
39+
fs.mkdirSync(path.dirname(targetPath), { recursive: true })
40+
fs.cpSync(backendDrizzleMigrationsPath, targetPath, {
41+
recursive: true,
42+
force: true,
43+
})
44+
},
45+
}
46+
}
47+
48+
export default defineConfig({
49+
resolve: {
50+
alias: {
51+
'pg-native': pgNativeShim,
52+
},
53+
},
54+
plugins: [patchNamespaceInteropHelper(), copyMetaMigrationsPlugin()],
55+
})

packages/electron/vite.renderer.config.mjs

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,32 @@ import { fileURLToPath, URL } from 'node:url'
22

33
import { defineConfig } from 'vite'
44
import vue from '@vitejs/plugin-vue'
5-
import vueDevTools from 'vite-plugin-vue-devtools'
5+
import tailwindcss from '@tailwindcss/vite'
6+
7+
const frontendRoot = fileURLToPath(new URL('../frontend', import.meta.url))
8+
const frontendSrc = fileURLToPath(new URL('../frontend/src', import.meta.url))
9+
const electronRendererOutDir = fileURLToPath(new URL('./.vite/renderer/main_window', import.meta.url))
10+
611

7-
// https://vite.dev/config/
812
export default defineConfig({
9-
plugins: [
10-
vue(),
11-
vueDevTools(),
12-
],
13-
resolve: {
14-
alias: {
15-
'@': fileURLToPath(new URL('frontend', import.meta.url))
16-
},
13+
root: frontendRoot,
14+
base: './',
15+
plugins: [vue(), tailwindcss()],
16+
optimizeDeps: {
17+
entries: ['index.html'],
18+
},
19+
resolve: {
20+
preserveSymlinks: false,
21+
alias: {
22+
'@': frontendSrc,
23+
},
24+
},
25+
server: {
26+
watch: {
27+
ignored: [`${frontendRoot}/dist/**`],
1728
},
29+
},
30+
build: {
31+
outDir: electronRendererOutDir,
32+
},
1833
})

0 commit comments

Comments
 (0)