v3.42.15: TUI - always allow Esc/Cancel during spinner operations
This commit is contained in:
@@ -4,8 +4,8 @@ This directory contains pre-compiled binaries for the DB Backup Tool across mult
|
|||||||
|
|
||||||
## Build Information
|
## Build Information
|
||||||
- **Version**: 3.42.10
|
- **Version**: 3.42.10
|
||||||
- **Build Time**: 2026-01-08_09:19:02_UTC
|
- **Build Time**: 2026-01-08_09:40:57_UTC
|
||||||
- **Git Commit**: 1831bd7
|
- **Git Commit**: 55d34be
|
||||||
|
|
||||||
## Recent Updates (v1.1.0)
|
## Recent Updates (v1.1.0)
|
||||||
- ✅ Fixed TUI progress display with line-by-line output
|
- ✅ Fixed TUI progress display with line-by-line output
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
"github.com/mattn/go-runewidth"
|
||||||
|
|
||||||
"dbbackup/internal/config"
|
"dbbackup/internal/config"
|
||||||
"dbbackup/internal/logger"
|
"dbbackup/internal/logger"
|
||||||
@@ -130,15 +131,24 @@ func (m BackupManagerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
|
|
||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
// Block input during operations
|
// Allow escape/cancel even during operations
|
||||||
|
if msg.String() == "ctrl+c" || msg.String() == "esc" || msg.String() == "q" {
|
||||||
|
if m.opState != OpIdle {
|
||||||
|
// Cancel current operation
|
||||||
|
m.opState = OpIdle
|
||||||
|
m.opTarget = ""
|
||||||
|
m.message = "Operation cancelled"
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
return m.parent, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block other input during operations
|
||||||
if m.opState != OpIdle {
|
if m.opState != OpIdle {
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
switch msg.String() {
|
switch msg.String() {
|
||||||
case "ctrl+c", "q", "esc":
|
|
||||||
return m.parent, nil
|
|
||||||
|
|
||||||
case "up", "k":
|
case "up", "k":
|
||||||
if m.cursor > 0 {
|
if m.cursor > 0 {
|
||||||
m.cursor--
|
m.cursor--
|
||||||
@@ -219,39 +229,45 @@ func (m BackupManagerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
|
|
||||||
func (m BackupManagerModel) View() string {
|
func (m BackupManagerModel) View() string {
|
||||||
var s strings.Builder
|
var s strings.Builder
|
||||||
|
const boxWidth = 60
|
||||||
|
|
||||||
|
// Helper to pad string to box width (handles UTF-8)
|
||||||
|
padToWidth := func(text string, width int) string {
|
||||||
|
textWidth := runewidth.StringWidth(text)
|
||||||
|
if textWidth >= width {
|
||||||
|
return runewidth.Truncate(text, width-3, "...")
|
||||||
|
}
|
||||||
|
return text + strings.Repeat(" ", width-textWidth)
|
||||||
|
}
|
||||||
|
|
||||||
// Title
|
// Title
|
||||||
s.WriteString(titleStyle.Render("[DB] Backup Archive Manager"))
|
s.WriteString(titleStyle.Render("[DB] Backup Archive Manager"))
|
||||||
s.WriteString("\n\n")
|
s.WriteString("\n\n")
|
||||||
|
|
||||||
// Operation Status Box (always visible)
|
// Operation Status Box (always visible)
|
||||||
s.WriteString("+--[ STATUS ]" + strings.Repeat("-", 47) + "+\n")
|
s.WriteString("+--[ STATUS ]" + strings.Repeat("-", boxWidth-13) + "+\n")
|
||||||
switch m.opState {
|
switch m.opState {
|
||||||
case OpVerifying:
|
case OpVerifying:
|
||||||
spinner := spinnerFrames[m.spinnerFrame]
|
spinner := spinnerFrames[m.spinnerFrame]
|
||||||
statusText := fmt.Sprintf(" %s Verifying: %s", spinner, m.opTarget)
|
statusText := fmt.Sprintf(" %s Verifying: %s", spinner, m.opTarget)
|
||||||
s.WriteString("|" + statusText + strings.Repeat(" ", 59-len(statusText)) + "|\n")
|
s.WriteString("|" + padToWidth(statusText, boxWidth) + "|\n")
|
||||||
case OpDeleting:
|
case OpDeleting:
|
||||||
spinner := spinnerFrames[m.spinnerFrame]
|
spinner := spinnerFrames[m.spinnerFrame]
|
||||||
statusText := fmt.Sprintf(" %s Deleting: %s", spinner, m.opTarget)
|
statusText := fmt.Sprintf(" %s Deleting: %s", spinner, m.opTarget)
|
||||||
s.WriteString("|" + statusText + strings.Repeat(" ", 59-len(statusText)) + "|\n")
|
s.WriteString("|" + padToWidth(statusText, boxWidth) + "|\n")
|
||||||
default:
|
default:
|
||||||
if m.loading {
|
if m.loading {
|
||||||
spinner := spinnerFrames[m.spinnerFrame]
|
spinner := spinnerFrames[m.spinnerFrame]
|
||||||
statusText := fmt.Sprintf(" %s Loading archives...", spinner)
|
statusText := fmt.Sprintf(" %s Loading archives...", spinner)
|
||||||
s.WriteString("|" + statusText + strings.Repeat(" ", 59-len(statusText)) + "|\n")
|
s.WriteString("|" + padToWidth(statusText, boxWidth) + "|\n")
|
||||||
} else if m.message != "" {
|
} else if m.message != "" {
|
||||||
msgText := " " + m.message
|
msgText := " " + m.message
|
||||||
if len(msgText) > 58 {
|
s.WriteString("|" + padToWidth(msgText, boxWidth) + "|\n")
|
||||||
msgText = msgText[:55] + "..."
|
|
||||||
}
|
|
||||||
s.WriteString("|" + msgText + strings.Repeat(" ", 59-len(msgText)) + "|\n")
|
|
||||||
} else {
|
} else {
|
||||||
statusText := " Ready"
|
s.WriteString("|" + padToWidth(" Ready", boxWidth) + "|\n")
|
||||||
s.WriteString("|" + statusText + strings.Repeat(" ", 59-len(statusText)) + "|\n")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.WriteString("+" + strings.Repeat("-", 60) + "+\n\n")
|
s.WriteString("+" + strings.Repeat("-", boxWidth) + "+\n\n")
|
||||||
|
|
||||||
if m.loading {
|
if m.loading {
|
||||||
return s.String()
|
return s.String()
|
||||||
@@ -334,14 +350,8 @@ func (m BackupManagerModel) View() string {
|
|||||||
s.WriteString(infoStyle.Render(fmt.Sprintf("Selected: %d/%d", m.cursor+1, len(m.archives))))
|
s.WriteString(infoStyle.Render(fmt.Sprintf("Selected: %d/%d", m.cursor+1, len(m.archives))))
|
||||||
s.WriteString("\n\n")
|
s.WriteString("\n\n")
|
||||||
|
|
||||||
// Grouped keyboard shortcuts for better readability
|
// Grouped keyboard shortcuts - simple aligned format
|
||||||
s.WriteString("+--[ SHORTCUTS ]" + strings.Repeat("-", 44) + "+\n")
|
s.WriteString("SHORTCUTS: Up/Down=Move | r=Restore | v=Verify | d=Delete | i=Info | R=Refresh | Esc=Back | q=Quit")
|
||||||
s.WriteString("| NAVIGATE ACTIONS OTHER |\n")
|
|
||||||
s.WriteString("| Up/Down: Move r: Restore R: Refresh |\n")
|
|
||||||
s.WriteString("| v: Verify Esc: Back |\n")
|
|
||||||
s.WriteString("| d: Delete q: Quit |\n")
|
|
||||||
s.WriteString("| i: Info |\n")
|
|
||||||
s.WriteString("+" + strings.Repeat("-", 60) + "+")
|
|
||||||
|
|
||||||
return s.String()
|
return s.String()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,13 +146,12 @@ func (m StatusViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
|
|
||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
if !m.loading {
|
// Always allow escape, even during loading
|
||||||
switch msg.String() {
|
switch msg.String() {
|
||||||
case "ctrl+c", "q", "esc", "enter":
|
case "ctrl+c", "q", "esc", "enter":
|
||||||
return m.parent, nil
|
return m.parent, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user