improvements to the ui

This commit is contained in:
2026-06-16 09:36:18 +02:00
parent 7bac3fe7c5
commit 8fc6478bb1
12 changed files with 292 additions and 170 deletions
+37
View File
@@ -46,9 +46,44 @@ const THEME_TEMPLATE = `/* Helder theme — custom CSS applied OVER the built-in
}
`
/** Recently-opened files, newest first. Local machine state — git-ignored. */
const RECENT_FILE = 'recent.json'
const MAX_RECENT = 100
const GITIGNORE_BODY = `# Helder — local, machine-specific state (do not commit)\n${RECENT_FILE}\n`
let current: HelderConfig = DEFAULTS
let themeCss = ''
/** Create `.helder/.gitignore` (ignoring recent.json) only when it's missing. */
async function ensureGitignore(dir: string): Promise<void> {
const path = join(dir, '.gitignore')
try {
await readFile(path, 'utf8')
} catch {
await writeFile(path, GITIGNORE_BODY)
}
}
export async function getRecent(root: string): Promise<string[]> {
try {
const arr = JSON.parse(await readFile(join(root, '.helder', RECENT_FILE), 'utf8'))
return Array.isArray(arr) ? arr.filter((p): p is string => typeof p === 'string').slice(0, MAX_RECENT) : []
} catch {
return []
}
}
export async function setRecent(root: string, list: string[]): Promise<void> {
try {
const dir = join(root, '.helder')
await mkdir(dir, { recursive: true })
await ensureGitignore(dir)
await writeFile(join(dir, RECENT_FILE), JSON.stringify(list.slice(0, MAX_RECENT), null, 2) + '\n')
} catch {
/* read-only / inaccessible root — recents just won't persist */
}
}
function isPlainObject(v: unknown): v is Record<string, unknown> {
return !!v && typeof v === 'object' && !Array.isArray(v)
}
@@ -83,6 +118,8 @@ export async function resolveConfig(root: string): Promise<void> {
themeCss = THEME_TEMPLATE
await writeFile(join(dir, 'theme.css'), THEME_TEMPLATE)
}
await ensureGitignore(dir)
} catch {
// Read-only / inaccessible root: fall back to built-in defaults.
current = DEFAULTS
+4 -1
View File
@@ -5,7 +5,7 @@ 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 { getConfig, getRecent, getThemeCss, resolveConfig, setRecent } from './config'
import { listFiles, searchContent } from './search-service'
const isDev = !!process.env['ELECTRON_RENDERER_URL']
@@ -112,6 +112,9 @@ function registerIpc(): void {
ipcMain.handle('config:get', () => getConfig())
ipcMain.handle('config:theme', () => getThemeCss())
ipcMain.handle('recent:get', () => getRecent(getRoot()))
ipcMain.handle('recent:set', (_e, list: string[]) => setRecent(getRoot(), list))
ipcMain.handle('search:content', (_e, query: string) => searchContent(getRoot(), query))
ipcMain.handle('search:files', () => listFiles(getRoot()))
+8 -4
View File
@@ -1,4 +1,4 @@
import { basename } from 'node:path'
import { basename, resolve } from 'node:path'
import { existsSync, statSync } from 'node:fs'
import { dialog, BrowserWindow } from 'electron'
@@ -6,12 +6,16 @@ import { dialog, BrowserWindow } from 'electron'
* One project per window. The root is resolved (in order) from $HELDER_PROJECT,
* a directory passed on argv, or the process working directory — then it can be
* changed at runtime via the Open Folder dialog.
*
* Always store an absolute path: in dev the app is launched as `electron .`, so
* argv carries a bare "." — `basename(".")` is "." (the repo name would show as a
* lone dot). `resolve()` turns it into the real directory before we name it.
*/
function resolveInitialRoot(): string {
const envRoot = process.env.HELDER_PROJECT
if (envRoot && existsSync(envRoot) && statSync(envRoot).isDirectory()) return envRoot
if (envRoot && existsSync(envRoot) && statSync(envRoot).isDirectory()) return resolve(envRoot)
const argDir = process.argv.slice(1).find((a) => !a.startsWith('-') && existsSync(a) && safeIsDir(a))
if (argDir) return argDir
if (argDir) return resolve(argDir)
return process.cwd()
}
@@ -30,7 +34,7 @@ export function getName(): string {
}
export function setRoot(next: string): void {
root = next
root = resolve(next)
}
export async function openDialog(win: BrowserWindow | null): Promise<string | null> {