feat: add preview/delete-output to TUI, replace how-it-works with tutorial gif
CLI: - p key opens file with system viewer (input in file list, output in results) - x key deletes converted output file from disk in results state - New 'deleted' status shown in red in the status column - Updated help overlay and bottom bar keybindings Web: - Replace three-step timeline in 'How it works' with Tuturial.gif - GIF shown in browser-style window frame matching site design
This commit is contained in:
@@ -3,7 +3,9 @@ package tui
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -35,7 +37,7 @@ type fileEntry struct {
|
||||
targetFormat string
|
||||
formats []string
|
||||
formatIdx int
|
||||
status string // "idle", "converting", "done", "error"
|
||||
status string // "idle", "converting", "done", "error", "deleted"
|
||||
error string
|
||||
outputPath string
|
||||
}
|
||||
@@ -290,6 +292,11 @@ func (m Model) handleFileListKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
if !isConverting {
|
||||
return m.startConversion()
|
||||
}
|
||||
|
||||
case key.Matches(msg, m.keys.Preview):
|
||||
if len(m.files) > 0 {
|
||||
openFile(m.files[m.cursor].path)
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
@@ -303,7 +310,7 @@ func (m Model) handleResultsKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
// Go back to file list to convert more
|
||||
m.state = stateFileList
|
||||
for i := range m.files {
|
||||
if m.files[i].status == "done" || m.files[i].status == "error" {
|
||||
if m.files[i].status == "done" || m.files[i].status == "error" || m.files[i].status == "deleted" {
|
||||
m.files[i].status = "idle"
|
||||
m.files[i].error = ""
|
||||
m.files[i].outputPath = ""
|
||||
@@ -315,10 +322,33 @@ func (m Model) handleResultsKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
case key.Matches(msg, m.keys.Up):
|
||||
if m.cursor > 0 {
|
||||
m.cursor--
|
||||
m.ensureVisible()
|
||||
}
|
||||
case key.Matches(msg, m.keys.Down):
|
||||
if m.cursor < len(m.files)-1 {
|
||||
m.cursor++
|
||||
m.ensureVisible()
|
||||
}
|
||||
case key.Matches(msg, m.keys.Preview):
|
||||
// Preview: open output file if done, otherwise open input file
|
||||
if len(m.files) > 0 {
|
||||
f := m.files[m.cursor]
|
||||
if f.status == "done" && f.outputPath != "" {
|
||||
openFile(f.outputPath)
|
||||
} else {
|
||||
openFile(f.path)
|
||||
}
|
||||
}
|
||||
case key.Matches(msg, m.keys.DeleteOutput):
|
||||
// Delete the converted output file from disk
|
||||
if len(m.files) > 0 {
|
||||
f := &m.files[m.cursor]
|
||||
if f.status == "done" && f.outputPath != "" {
|
||||
if err := os.Remove(f.outputPath); err == nil {
|
||||
f.status = "deleted"
|
||||
f.outputPath = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
@@ -387,6 +417,22 @@ func (m Model) maxVisibleFiles() int {
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────
|
||||
|
||||
// openFile opens a file with the system default viewer.
|
||||
func openFile(path string) {
|
||||
var cmd *exec.Cmd
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
cmd = exec.Command("open", path)
|
||||
case "linux":
|
||||
cmd = exec.Command("xdg-open", path)
|
||||
case "windows":
|
||||
cmd = exec.Command("rundll32", "url.dll,FileProtocolHandler", path)
|
||||
default:
|
||||
return
|
||||
}
|
||||
cmd.Start() //nolint:errcheck
|
||||
}
|
||||
|
||||
func formatSize(bytes int64) string {
|
||||
const (
|
||||
KB = 1024
|
||||
|
||||
Reference in New Issue
Block a user