diff --git a/internal/ui/mainmenu.go b/internal/ui/mainmenu.go index 0b5187f..019560c 100644 --- a/internal/ui/mainmenu.go +++ b/internal/ui/mainmenu.go @@ -43,13 +43,16 @@ type HistoryEntry struct { } // BuildMainMenu creates the main menu view with module tiles grouped by category -func BuildMainMenu(modules []ModuleInfo, onModuleClick func(string), onModuleDrop func(string, []fyne.URI), onQueueClick func(), onLogsClick func(), onBenchmarkClick func(), onBenchmarkHistoryClick func(), titleColor, queueColor, textColor color.Color, queueCompleted, queueTotal int) fyne.CanvasObject { +func BuildMainMenu(modules []ModuleInfo, onModuleClick func(string), onModuleDrop func(string, []fyne.URI), onQueueClick func(), onLogsClick func(), onBenchmarkClick func(), onBenchmarkHistoryClick func(), onToggleSidebar func(), sidebarVisible bool, sidebar fyne.CanvasObject, titleColor, queueColor, textColor color.Color, queueCompleted, queueTotal int) fyne.CanvasObject { title := canvas.NewText("VIDEOTOOLS", titleColor) title.TextStyle = fyne.TextStyle{Monospace: true, Bold: true} title.TextSize = 28 queueTile := buildQueueTile(queueCompleted, queueTotal, queueColor, textColor, onQueueClick) + sidebarToggleBtn := widget.NewButton("☰ History", onToggleSidebar) + sidebarToggleBtn.Importance = widget.LowImportance + benchmarkBtn := widget.NewButton("Run Benchmark", onBenchmarkClick) benchmarkBtn.Importance = widget.LowImportance @@ -59,7 +62,7 @@ func BuildMainMenu(modules []ModuleInfo, onModuleClick func(string), onModuleDro logsBtn := widget.NewButton("Logs", onLogsClick) logsBtn.Importance = widget.LowImportance - header := container.New(layout.NewHBoxLayout(), title, layout.NewSpacer(), benchmarkBtn, viewResultsBtn, logsBtn, queueTile) + header := container.New(layout.NewHBoxLayout(), title, layout.NewSpacer(), sidebarToggleBtn, benchmarkBtn, viewResultsBtn, logsBtn, queueTile) categorized := map[string][]fyne.CanvasObject{} for i := range modules { @@ -103,6 +106,13 @@ func BuildMainMenu(modules []ModuleInfo, onModuleClick func(string), onModuleDro container.NewVBox(sections...), ) + // Wrap with HSplit if sidebar is visible + if sidebarVisible && sidebar != nil { + split := container.NewHSplit(sidebar, body) + split.Offset = 0.2 + return split + } + return body } diff --git a/main.go b/main.go index cc03dbd..735eba9 100644 --- a/main.go +++ b/main.go @@ -21,6 +21,7 @@ import ( "regexp" "runtime" "slices" + "sort" "strconv" "strings" "sync" @@ -815,6 +816,103 @@ func (s *appState) addToHistory(job *queue.Job) { } } +// showHistoryDetails displays detailed information about a history entry +func (s *appState) showHistoryDetails(entry ui.HistoryEntry) { + // Format config + var configLines []string + for key, value := range entry.Config { + configLines = append(configLines, fmt.Sprintf("%s: %v", key, value)) + } + sort.Strings(configLines) + + // Format timestamps + createdStr := entry.CreatedAt.Format("2006-01-02 15:04:05") + startedStr := "N/A" + if entry.StartedAt != nil { + startedStr = entry.StartedAt.Format("2006-01-02 15:04:05") + } + completedStr := "N/A" + if entry.CompletedAt != nil { + completedStr = entry.CompletedAt.Format("2006-01-02 15:04:05") + } + + details := fmt.Sprintf(`Type: %s +Status: %s +Input: %s +Output: %s + +Created: %s +Started: %s +Completed: %s + +Config: +%s`, entry.Type, entry.Status, entry.InputFile, entry.OutputFile, + createdStr, startedStr, completedStr, strings.Join(configLines, "\n")) + + if entry.Error != "" { + details += fmt.Sprintf("\n\nError:\n%s", entry.Error) + } + + detailsLabel := widget.NewLabel(details) + detailsLabel.Wrapping = fyne.TextWrapWord + + // FFmpeg Command section + var ffmpegSection fyne.CanvasObject + if entry.FFmpegCmd != "" { + cmdWidget := ui.NewFFmpegCommandWidget(entry.FFmpegCmd, s.window) + ffmpegSection = container.NewVBox( + widget.NewSeparator(), + widget.NewLabel("FFmpeg Command:"), + cmdWidget, + ) + } + + // Buttons + var buttons []fyne.CanvasObject + + if entry.OutputFile != "" { + if _, err := os.Stat(entry.OutputFile); err == nil { + buttons = append(buttons, widget.NewButton("Show in Folder", func() { + dir := filepath.Dir(entry.OutputFile) + if err := openFolder(dir); err != nil { + dialog.ShowError(err, s.window) + } + })) + } + } + + if entry.LogPath != "" { + if _, err := os.Stat(entry.LogPath); err == nil { + buttons = append(buttons, widget.NewButton("View Log", func() { + s.openLogViewer(entry.Title, entry.LogPath, false) + })) + } + } + + closeBtn := widget.NewButton("Close", nil) + buttons = append(buttons, layout.NewSpacer(), closeBtn) + + // Layout + contentVBox := container.NewVBox( + container.NewVScroll(detailsLabel), + ) + if ffmpegSection != nil { + contentVBox.Add(ffmpegSection) + } + + content := container.NewBorder( + nil, + container.NewHBox(buttons...), + nil, nil, + contentVBox, + ) + + d := dialog.NewCustom("Job Details", "Close", content, s.window) + d.Resize(fyne.NewSize(700, 600)) + closeBtn.OnTapped = func() { d.Hide() } + d.Show() +} + func (s *appState) stopPreview() { if s.anim != nil { s.anim.Stop() @@ -1233,6 +1331,18 @@ func (s *appState) showMainMenu() { queueTotal = len(s.jobQueue.List()) } + // Build sidebar if visible + var sidebar fyne.CanvasObject + if s.sidebarVisible { + sidebar = ui.BuildHistorySidebar( + s.historyEntries, + s.showHistoryDetails, + titleColor, + utils.MustHex("#1A1F2E"), + textColor, + ) + } + menu := ui.BuildMainMenu(mods, s.showModule, s.handleModuleDrop, s.showQueue, func() { logDir := getLogsDir() _ = os.MkdirAll(logDir, 0o755) @@ -1264,7 +1374,11 @@ func (s *appState) showMainMenu() { viewAppLogBtn, ) dialog.ShowCustom("Logs", "Close", logOptions, s.window) - }, s.showBenchmark, s.showBenchmarkHistory, titleColor, queueColor, textColor, queueCompleted, queueTotal) + }, s.showBenchmark, s.showBenchmarkHistory, func() { + // Toggle sidebar + s.sidebarVisible = !s.sidebarVisible + s.showMainMenu() + }, s.sidebarVisible, sidebar, titleColor, queueColor, textColor, queueCompleted, queueTotal) // Update stats bar s.updateStatsBar()