feat: v0.1.6 - auto-refresh file list, separate reset key

- Auto-refresh: file list now checks directories every 2 seconds for new files
- r key: resets done/error/deleted file back to idle for reconversion
- f key: manually refresh the file list
- d key: removes file from list only (unchanged)
- Auto-refresh works for directories that files were loaded from
- Updated help menu and bottom bar with new keybindings
This commit is contained in:
noah
2026-03-11 10:46:31 +01:00
parent 82a4123f84
commit 31cfd03e42
6 changed files with 99 additions and 41 deletions
+67 -15
View File
@@ -54,6 +54,8 @@ type conversionStartMsg struct {
type tickMsg time.Time
type refreshFilesMsg struct{}
// ─── Model ───────────────────────────────────────────────────
type Model struct {
@@ -72,6 +74,10 @@ type Model struct {
converted int
totalToConv int
startTime time.Time
// File watching
watchDirs map[string]int64 // dir path -> last known mod time
lastRefresh time.Time
}
// New creates a new TUI model from a list of file paths and an output directory.
@@ -168,7 +174,10 @@ func makeFileEntry(path string, info os.FileInfo) *fileEntry {
// ─── Init ────────────────────────────────────────────────────
func (m Model) Init() tea.Cmd {
return nil
// Start a ticker to watch for file changes every 2 seconds
return tea.Tick(2*time.Second, func(t time.Time) tea.Msg {
return refreshFilesMsg{}
})
}
// ─── Update ──────────────────────────────────────────────────
@@ -198,6 +207,14 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.KeyMsg:
return m.handleKey(msg)
case refreshFilesMsg:
// Check directories for new files
m = m.checkForNewFiles()
// Continue watching
return m, tea.Tick(2*time.Second, func(t time.Time) tea.Msg {
return refreshFilesMsg{}
})
}
return m, nil
@@ -291,20 +308,8 @@ func (m Model) handleFileListKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
openFile(path)
}
case key.Matches(msg, m.keys.DeleteOutput):
// Delete the converted output file from disk
if len(m.files) > 0 && !isConverting {
f := &m.files[m.cursor]
if f.status == "done" && f.outputPath != "" {
if err := os.Remove(f.outputPath); err == nil {
f.status = "idle"
f.outputPath = ""
}
}
}
case key.Matches(msg, m.keys.Back):
// Reset done/error files to idle for reconversion
case key.Matches(msg, m.keys.Reset):
// Reset done/error/deleted file back to idle for reconversion
if len(m.files) > 0 && !isConverting {
f := &m.files[m.cursor]
if f.status == "done" || f.status == "error" || f.status == "deleted" {
@@ -313,6 +318,10 @@ func (m Model) handleFileListKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
f.outputPath = ""
}
}
case key.Matches(msg, m.keys.Refresh):
// Manually refresh the file list
m = m.checkForNewFiles()
}
return m, nil
@@ -414,3 +423,46 @@ func formatSize(bytes int64) string {
return fmt.Sprintf("%d B", bytes)
}
}
// checkForNewFiles scans directories for new files and adds them to the list.
func (m Model) checkForNewFiles() Model {
dirs := make(map[string]bool)
for _, f := range m.files {
dir := filepath.Dir(f.path)
dirs[dir] = true
}
for dir := range dirs {
entries, err := os.ReadDir(dir)
if err != nil {
continue
}
for _, e := range entries {
if e.IsDir() || strings.HasPrefix(e.Name(), ".") {
continue
}
path := filepath.Join(dir, e.Name())
// Check if already in list
exists := false
for _, f := range m.files {
if f.path == path {
exists = true
break
}
}
if !exists {
info, err := e.Info()
if err != nil {
continue
}
entry := makeFileEntry(path, info)
if entry != nil {
m.files = append(m.files, *entry)
}
}
}
}
return m
}