diff --git a/internal/ui/mainmenu.go b/internal/ui/mainmenu.go index 019560c..221f5f3 100644 --- a/internal/ui/mainmenu.go +++ b/internal/ui/mainmenu.go @@ -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) }) } diff --git a/main.go b/main.go index ddbc47e..b505080 100644 --- a/main.go +++ b/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",