This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { join, sep } from 'node:path'
|
||||
import { app, dialog, shell, BrowserWindow, ipcMain } from 'electron'
|
||||
import { app, dialog, shell, BrowserWindow, ipcMain, Menu } from 'electron'
|
||||
import { watch, type FSWatcher } from 'chokidar'
|
||||
import { getName, getRoot, openDialog } from './project'
|
||||
import { readAll, readProjectFile, readTree, writeProjectFile } from './fs-service'
|
||||
@@ -34,6 +34,42 @@ function startConfigWatcher(): void {
|
||||
configWatcher.on('add', reload).on('change', reload).on('unlink', reload)
|
||||
}
|
||||
|
||||
/** Show the folder picker; if a new folder is chosen, switch the project
|
||||
* (config + watchers). Returns whether the project changed. */
|
||||
async function openFolderFlow(win: BrowserWindow | null): Promise<boolean> {
|
||||
const next = await openDialog(win)
|
||||
if (!next) return false
|
||||
await resolveConfig(getRoot())
|
||||
startWatcher()
|
||||
startConfigWatcher()
|
||||
return true
|
||||
}
|
||||
|
||||
function buildAppMenu(): Menu {
|
||||
const template: Electron.MenuItemConstructorOptions[] = [
|
||||
...(isMac ? [{ role: 'appMenu' as const }] : []),
|
||||
{
|
||||
label: 'File',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Open Folder…',
|
||||
accelerator: 'CmdOrCtrl+O',
|
||||
click: (_m, win) => {
|
||||
const bw = win instanceof BrowserWindow ? win : BrowserWindow.getFocusedWindow()
|
||||
openFolderFlow(bw).then((changed) => { if (changed) broadcast('project:changed') })
|
||||
},
|
||||
},
|
||||
{ type: 'separator' },
|
||||
isMac ? { role: 'close' } : { role: 'quit' },
|
||||
],
|
||||
},
|
||||
{ role: 'editMenu' },
|
||||
{ role: 'viewMenu' },
|
||||
{ role: 'windowMenu' },
|
||||
]
|
||||
return Menu.buildFromTemplate(template)
|
||||
}
|
||||
|
||||
function startWatcher(): void {
|
||||
if (watcher) { watcher.close(); watcher = null }
|
||||
const root = getRoot()
|
||||
@@ -52,13 +88,7 @@ function startWatcher(): void {
|
||||
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()
|
||||
}
|
||||
await openFolderFlow(BrowserWindow.fromWebContents(e.sender))
|
||||
return { root: getRoot(), name: getName() }
|
||||
})
|
||||
|
||||
@@ -134,6 +164,7 @@ function createWindow(): void {
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
app.setName('Helder')
|
||||
Menu.setApplicationMenu(buildAppMenu())
|
||||
registerIpc()
|
||||
await resolveConfig(getRoot())
|
||||
startWatcher()
|
||||
|
||||
@@ -375,7 +375,8 @@ export function App(): React.ReactElement {
|
||||
</div>
|
||||
<Splitter onDelta={(dx) => setRightW((w) => clamp(w - dx, 280, 780))} />
|
||||
|
||||
<RightColumn width={rightW} />
|
||||
{/* keyed by root so the PTYs respawn in the new cwd when the project switches */}
|
||||
{proj.ready && <RightColumn key={proj.root ?? 'none'} width={rightW} />}
|
||||
</div>
|
||||
|
||||
{/* status bar */}
|
||||
|
||||
@@ -278,6 +278,7 @@ export function Editor({ tabs, active, mode, setMode, onActivate, onClose, onCon
|
||||
<div style={{ opacity: 0.5 }}>{Icon.file({ width: 30, height: 30 })}</div>
|
||||
<div className="big">No file open</div>
|
||||
<div className="klist">
|
||||
<div><span>Open folder</span><kbd>⌘ O</kbd></div>
|
||||
<div><span>Search files & content</span><kbd>⌘ F</kbd></div>
|
||||
<div><span>Copy reference</span><kbd>right-click</kbd></div>
|
||||
<div><span>Pass on to Agent</span><kbd>right-click</kbd></div>
|
||||
|
||||
Reference in New Issue
Block a user