init helder
This commit is contained in:
141
src/main/index.ts
Normal file
141
src/main/index.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { join, sep } from 'node:path'
|
||||
import { app, shell, BrowserWindow, ipcMain } from 'electron'
|
||||
import { watch, type FSWatcher } from 'chokidar'
|
||||
import { getName, getRoot, openDialog } from './project'
|
||||
import { readAll, readProjectFile, readTree, writeProjectFile } from './fs-service'
|
||||
import { commit, discard, load, stage, unstage } from './git-service'
|
||||
import { createPty, killAllPtys, killPty, ptyAvailable, resizePty, writePty } from './pty-service'
|
||||
import { getConfig, getThemeCss, resolveConfig } from './config'
|
||||
import { listFiles, searchContent } from './search-service'
|
||||
|
||||
const isDev = !!process.env['ELECTRON_RENDERER_URL']
|
||||
const isMac = process.platform === 'darwin'
|
||||
|
||||
const WATCH_IGNORE = new Set([
|
||||
'node_modules', '.git', 'out', 'dist', 'build', '.next', '.nuxt',
|
||||
'.cache', 'coverage', 'vendor', '.idea', '.vscode', '.helder', '.turbo',
|
||||
])
|
||||
|
||||
let watcher: FSWatcher | null = null
|
||||
let configWatcher: FSWatcher | null = null
|
||||
let watchTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
function broadcast(channel: string): void {
|
||||
for (const w of BrowserWindow.getAllWindows()) w.webContents.send(channel)
|
||||
}
|
||||
|
||||
function startConfigWatcher(): void {
|
||||
if (configWatcher) { configWatcher.close(); configWatcher = null }
|
||||
const root = getRoot()
|
||||
if (!root) return
|
||||
const dir = join(root, '.helder')
|
||||
configWatcher = watch([join(dir, 'config.json'), join(dir, 'theme.css')], { ignoreInitial: true })
|
||||
const reload = (): void => { resolveConfig(root).then(() => broadcast('config:changed')).catch(() => {}) }
|
||||
configWatcher.on('add', reload).on('change', reload).on('unlink', reload)
|
||||
}
|
||||
|
||||
function startWatcher(): void {
|
||||
if (watcher) { watcher.close(); watcher = null }
|
||||
const root = getRoot()
|
||||
if (!root) return
|
||||
watcher = watch(root, {
|
||||
ignoreInitial: true,
|
||||
ignored: (p: string) => p.split(sep).some((seg) => WATCH_IGNORE.has(seg)),
|
||||
})
|
||||
const ping = (): void => {
|
||||
if (watchTimer) clearTimeout(watchTimer)
|
||||
watchTimer = setTimeout(() => broadcast('project:changed'), 250)
|
||||
}
|
||||
watcher.on('add', ping).on('change', ping).on('unlink', ping).on('addDir', ping).on('unlinkDir', ping)
|
||||
}
|
||||
|
||||
function registerIpc(): void {
|
||||
ipcMain.handle('project:current', () => ({ root: getRoot(), name: getName() }))
|
||||
ipcMain.handle('project:open', async (e) => {
|
||||
const win = BrowserWindow.fromWebContents(e.sender)
|
||||
const next = await openDialog(win)
|
||||
if (next) {
|
||||
await resolveConfig(getRoot())
|
||||
startWatcher()
|
||||
startConfigWatcher()
|
||||
}
|
||||
return { root: getRoot(), name: getName() }
|
||||
})
|
||||
|
||||
ipcMain.handle('fs:tree', () => readTree(getRoot()))
|
||||
ipcMain.handle('fs:files', () => readAll(getRoot()))
|
||||
ipcMain.handle('fs:read', (_e, rel: string) => readProjectFile(getRoot(), rel))
|
||||
ipcMain.handle('fs:write', (_e, rel: string, content: string) => writeProjectFile(getRoot(), rel, content))
|
||||
|
||||
ipcMain.handle('git:load', () => load(getRoot()))
|
||||
ipcMain.handle('git:stage', (_e, paths: string[]) => stage(getRoot(), paths))
|
||||
ipcMain.handle('git:unstage', (_e, paths: string[]) => unstage(getRoot(), paths))
|
||||
ipcMain.handle('git:commit', (_e, message: string) => commit(getRoot(), message))
|
||||
ipcMain.handle('git:discard', (_e, paths: string[]) => discard(getRoot(), paths))
|
||||
|
||||
ipcMain.handle('pty:available', () => ptyAvailable())
|
||||
ipcMain.handle('pty:create', (e, kind: 'agent' | 'shell', cols: number, rows: number) => createPty(e.sender, kind, cols, rows))
|
||||
ipcMain.on('pty:write', (_e, id: number, data: string) => writePty(id, data))
|
||||
ipcMain.on('pty:resize', (_e, id: number, cols: number, rows: number) => resizePty(id, cols, rows))
|
||||
ipcMain.on('pty:kill', (_e, id: number) => killPty(id))
|
||||
|
||||
ipcMain.handle('config:get', () => getConfig())
|
||||
ipcMain.handle('config:theme', () => getThemeCss())
|
||||
|
||||
ipcMain.handle('search:content', (_e, query: string) => searchContent(getRoot(), query))
|
||||
ipcMain.handle('search:files', () => listFiles(getRoot()))
|
||||
}
|
||||
|
||||
function createWindow(): void {
|
||||
const win = new BrowserWindow({
|
||||
width: 1680,
|
||||
height: 1040,
|
||||
minWidth: 1100,
|
||||
minHeight: 680,
|
||||
show: false,
|
||||
backgroundColor: '#16171a',
|
||||
titleBarStyle: isMac ? 'hiddenInset' : 'default',
|
||||
trafficLightPosition: isMac ? { x: 14, y: 12 } : undefined,
|
||||
webPreferences: {
|
||||
preload: join(__dirname, '../preload/index.js'),
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
sandbox: false,
|
||||
},
|
||||
})
|
||||
|
||||
win.on('ready-to-show', () => win.show())
|
||||
|
||||
win.webContents.setWindowOpenHandler(({ url }) => {
|
||||
shell.openExternal(url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
|
||||
if (isDev) {
|
||||
win.loadURL(process.env['ELECTRON_RENDERER_URL'] as string)
|
||||
} else {
|
||||
win.loadFile(join(__dirname, '../renderer/index.html'))
|
||||
}
|
||||
}
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
app.setName('Helder')
|
||||
registerIpc()
|
||||
await resolveConfig(getRoot())
|
||||
startWatcher()
|
||||
startConfigWatcher()
|
||||
createWindow()
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||
})
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (watcher) { watcher.close(); watcher = null }
|
||||
if (configWatcher) { configWatcher.close(); configWatcher = null }
|
||||
killAllPtys()
|
||||
if (!isMac) app.quit()
|
||||
})
|
||||
|
||||
app.on('before-quit', () => killAllPtys())
|
||||
Reference in New Issue
Block a user