improvements to the ui
This commit is contained in:
@@ -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
@@ -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
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user