Add history entry delete button and fix Convert module crash
Features: - Add "×" delete button to each history entry in sidebar - Click to remove individual entries from history - Automatically saves and refreshes sidebar after deletion Bug Fixes: - Fix nil pointer crash when opening Convert module - Fixed widget initialization order: bitrateContainer now created AFTER bitratePresetSelect is initialized - Prevented "invalid memory address" panic in tabs layout Technical Details: - Added deleteHistoryEntry() method to remove entries by ID - Updated BuildHistorySidebar signature to accept onEntryDelete callback - Moved bitrateContainer creation from line 5742 to 5794 - All Select widgets now properly initialized before container creation The crash was caused by bitrateContainer containing a nil bitratePresetSelect widget, which crashed when Fyne's layout system called .Visible() during tab initialization. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
4616dee10a
commit
5b544b8484
|
|
@ -154,6 +154,7 @@ func sortedKeys(m map[string][]fyne.CanvasObject) []string {
|
|||
func BuildHistorySidebar(
|
||||
entries []HistoryEntry,
|
||||
onEntryClick func(HistoryEntry),
|
||||
onEntryDelete func(HistoryEntry),
|
||||
titleColor, bgColor, textColor color.Color,
|
||||
) fyne.CanvasObject {
|
||||
// Filter by status
|
||||
|
|
@ -167,8 +168,8 @@ func BuildHistorySidebar(
|
|||
}
|
||||
|
||||
// Build lists
|
||||
completedList := buildHistoryList(completedEntries, onEntryClick, bgColor, textColor)
|
||||
failedList := buildHistoryList(failedEntries, onEntryClick, bgColor, textColor)
|
||||
completedList := buildHistoryList(completedEntries, onEntryClick, onEntryDelete, bgColor, textColor)
|
||||
failedList := buildHistoryList(failedEntries, onEntryClick, onEntryDelete, bgColor, textColor)
|
||||
|
||||
// Tabs
|
||||
tabs := container.NewAppTabs(
|
||||
|
|
@ -193,6 +194,7 @@ func BuildHistorySidebar(
|
|||
func buildHistoryList(
|
||||
entries []HistoryEntry,
|
||||
onEntryClick func(HistoryEntry),
|
||||
onEntryDelete func(HistoryEntry),
|
||||
bgColor, textColor color.Color,
|
||||
) *fyne.Container {
|
||||
if len(entries) == 0 {
|
||||
|
|
@ -201,7 +203,7 @@ func buildHistoryList(
|
|||
|
||||
var items []fyne.CanvasObject
|
||||
for _, entry := range entries {
|
||||
items = append(items, buildHistoryItem(entry, onEntryClick, bgColor, textColor))
|
||||
items = append(items, buildHistoryItem(entry, onEntryClick, onEntryDelete, bgColor, textColor))
|
||||
}
|
||||
return container.NewVBox(items...)
|
||||
}
|
||||
|
|
@ -209,11 +211,21 @@ func buildHistoryList(
|
|||
func buildHistoryItem(
|
||||
entry HistoryEntry,
|
||||
onEntryClick func(HistoryEntry),
|
||||
onEntryDelete func(HistoryEntry),
|
||||
bgColor, textColor color.Color,
|
||||
) fyne.CanvasObject {
|
||||
// Badge
|
||||
badge := BuildModuleBadge(entry.Type)
|
||||
|
||||
// Capture entry for closures
|
||||
capturedEntry := entry
|
||||
|
||||
// Delete button - small "×" button
|
||||
deleteBtn := widget.NewButton("×", func() {
|
||||
onEntryDelete(capturedEntry)
|
||||
})
|
||||
deleteBtn.Importance = widget.LowImportance
|
||||
|
||||
// Title
|
||||
titleLabel := widget.NewLabel(utils.ShortenMiddle(entry.Title, 25))
|
||||
titleLabel.TextStyle = fyne.TextStyle{Bold: true}
|
||||
|
|
@ -234,7 +246,7 @@ func buildHistoryItem(
|
|||
content := container.NewBorder(
|
||||
nil, nil, statusRect, nil,
|
||||
container.NewVBox(
|
||||
container.NewHBox(badge, layout.NewSpacer()),
|
||||
container.NewHBox(badge, layout.NewSpacer(), deleteBtn),
|
||||
titleLabel,
|
||||
timeLabel,
|
||||
),
|
||||
|
|
@ -245,7 +257,5 @@ func buildHistoryItem(
|
|||
|
||||
item := container.NewPadded(container.NewMax(card, content))
|
||||
|
||||
// Capture entry for closure
|
||||
capturedEntry := entry
|
||||
return NewTappable(item, func() { onEntryClick(capturedEntry) })
|
||||
}
|
||||
|
|
|
|||
42
main.go
42
main.go
|
|
@ -910,8 +910,8 @@ Config:
|
|||
|
||||
// Layout: details at top (scrollable), FFmpeg at bottom (fixed)
|
||||
content := container.NewBorder(
|
||||
detailsScroll, // Top: job details (scrollable, takes priority)
|
||||
container.NewVBox( // Bottom: FFmpeg command (fixed)
|
||||
detailsScroll, // Top: job details (scrollable, takes priority)
|
||||
container.NewVBox( // Bottom: FFmpeg command (fixed)
|
||||
ffmpegSection,
|
||||
container.NewHBox(buttons...),
|
||||
),
|
||||
|
|
@ -925,6 +925,26 @@ Config:
|
|||
d.Show()
|
||||
}
|
||||
|
||||
func (s *appState) deleteHistoryEntry(entry ui.HistoryEntry) {
|
||||
// Remove entry from history
|
||||
var updated []ui.HistoryEntry
|
||||
for _, e := range s.historyEntries {
|
||||
if e.ID != entry.ID {
|
||||
updated = append(updated, e)
|
||||
}
|
||||
}
|
||||
s.historyEntries = updated
|
||||
|
||||
// Save updated history
|
||||
cfg := historyConfig{Entries: s.historyEntries}
|
||||
if err := saveHistoryConfig(cfg); err != nil {
|
||||
logging.Debug(logging.CatUI, "failed to save history after delete: %v", err)
|
||||
}
|
||||
|
||||
// Refresh main menu to update sidebar
|
||||
s.showMainMenu()
|
||||
}
|
||||
|
||||
func (s *appState) stopPreview() {
|
||||
if s.anim != nil {
|
||||
s.anim.Stop()
|
||||
|
|
@ -1349,6 +1369,7 @@ func (s *appState) showMainMenu() {
|
|||
sidebar = ui.BuildHistorySidebar(
|
||||
s.historyEntries,
|
||||
s.showHistoryDetails,
|
||||
s.deleteHistoryEntry,
|
||||
titleColor,
|
||||
utils.MustHex("#1A1F2E"),
|
||||
textColor,
|
||||
|
|
@ -5712,18 +5733,13 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
|||
}
|
||||
}
|
||||
|
||||
// Create containers for hideable sections
|
||||
// Create CRF container (crfEntry already initialized)
|
||||
crfContainer = container.NewVBox(
|
||||
widget.NewLabelWithStyle("Manual CRF (overrides Quality preset)", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||
crfEntry,
|
||||
)
|
||||
|
||||
bitrateContainer = container.NewVBox(
|
||||
widget.NewLabelWithStyle("Video Bitrate (for CBR/VBR)", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||
videoBitrateEntry,
|
||||
widget.NewLabelWithStyle("Recommended Bitrate Preset", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||
bitratePresetSelect,
|
||||
)
|
||||
// Note: bitrateContainer creation moved below after bitratePresetSelect is initialized
|
||||
|
||||
type bitratePreset struct {
|
||||
Label string
|
||||
|
|
@ -5775,6 +5791,14 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
|||
})
|
||||
simpleBitrateSelect.SetSelected(state.convert.BitratePreset)
|
||||
|
||||
// Create bitrate container now that bitratePresetSelect is initialized
|
||||
bitrateContainer = container.NewVBox(
|
||||
widget.NewLabelWithStyle("Video Bitrate (for CBR/VBR)", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||
videoBitrateEntry,
|
||||
widget.NewLabelWithStyle("Recommended Bitrate Preset", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||
bitratePresetSelect,
|
||||
)
|
||||
|
||||
// Simple resolution selector (separate widget to avoid double-parent issues)
|
||||
resolutionSelectSimple := widget.NewSelect([]string{
|
||||
"Source", "360p", "480p", "540p", "720p", "1080p", "1440p", "4K", "8K",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user