forked from Leak_Technologies/VideoTools
Simplified the approach by removing complex callback logic and using a
simple 500ms timer-based update for the stats bar instead. This eliminates
threading errors completely while keeping the code straightforward.
Changes:
1. Removed queue change callback entirely
2. Added background timer that updates stats bar every 500ms
3. Removed initComplete flag (no longer needed)
4. Simplified setContent() to direct calls
5. Added onClearAll parameter to BuildQueueView()
6. Added ClearAll() method to Queue (removes all jobs)
7. Added Clear All button with DangerImportance styling in queue view
8. Clear Completed button now has LowImportance styling
This approach is much simpler: the UI just polls the queue state
periodically instead of trying to handle callbacks from goroutines.
No more threading errors, less code, easier to understand.
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
214 lines
5.6 KiB
Go
214 lines
5.6 KiB
Go
package ui
|
|
|
|
import (
|
|
"fmt"
|
|
"image/color"
|
|
"time"
|
|
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/canvas"
|
|
"fyne.io/fyne/v2/container"
|
|
"fyne.io/fyne/v2/widget"
|
|
"git.leaktechnologies.dev/stu/VideoTools/internal/queue"
|
|
)
|
|
|
|
// BuildQueueView creates the queue viewer UI
|
|
func BuildQueueView(
|
|
jobs []*queue.Job,
|
|
onBack func(),
|
|
onPause func(string),
|
|
onResume func(string),
|
|
onCancel func(string),
|
|
onRemove func(string),
|
|
onClear func(),
|
|
onClearAll func(),
|
|
titleColor, bgColor, textColor color.Color,
|
|
) fyne.CanvasObject {
|
|
// Header
|
|
title := canvas.NewText("JOB QUEUE", titleColor)
|
|
title.TextStyle = fyne.TextStyle{Monospace: true, Bold: true}
|
|
title.TextSize = 24
|
|
|
|
backBtn := widget.NewButton("← Back", onBack)
|
|
backBtn.Importance = widget.LowImportance
|
|
|
|
clearBtn := widget.NewButton("Clear Completed", onClear)
|
|
clearBtn.Importance = widget.LowImportance
|
|
|
|
clearAllBtn := widget.NewButton("Clear All", onClearAll)
|
|
clearAllBtn.Importance = widget.DangerImportance
|
|
|
|
header := container.NewBorder(
|
|
nil, nil,
|
|
backBtn,
|
|
container.NewHBox(clearBtn, clearAllBtn),
|
|
container.NewCenter(title),
|
|
)
|
|
|
|
// Job list
|
|
var jobItems []fyne.CanvasObject
|
|
|
|
if len(jobs) == 0 {
|
|
emptyMsg := widget.NewLabel("No jobs in queue")
|
|
emptyMsg.Alignment = fyne.TextAlignCenter
|
|
jobItems = append(jobItems, container.NewCenter(emptyMsg))
|
|
} else {
|
|
for _, job := range jobs {
|
|
jobItems = append(jobItems, buildJobItem(job, onPause, onResume, onCancel, onRemove, bgColor, textColor))
|
|
}
|
|
}
|
|
|
|
jobList := container.NewVBox(jobItems...)
|
|
scrollable := container.NewVScroll(jobList)
|
|
|
|
body := container.NewBorder(
|
|
header,
|
|
nil, nil, nil,
|
|
scrollable,
|
|
)
|
|
|
|
return container.NewPadded(body)
|
|
}
|
|
|
|
// buildJobItem creates a single job item in the queue list
|
|
func buildJobItem(
|
|
job *queue.Job,
|
|
onPause func(string),
|
|
onResume func(string),
|
|
onCancel func(string),
|
|
onRemove func(string),
|
|
bgColor, textColor color.Color,
|
|
) fyne.CanvasObject {
|
|
// Status color
|
|
statusColor := getStatusColor(job.Status)
|
|
|
|
// Status indicator
|
|
statusRect := canvas.NewRectangle(statusColor)
|
|
statusRect.SetMinSize(fyne.NewSize(6, 0))
|
|
|
|
// Title and description
|
|
titleLabel := widget.NewLabel(job.Title)
|
|
titleLabel.TextStyle = fyne.TextStyle{Bold: true}
|
|
|
|
descLabel := widget.NewLabel(job.Description)
|
|
descLabel.TextStyle = fyne.TextStyle{Italic: true}
|
|
|
|
// Progress bar (for running jobs)
|
|
var progressWidget fyne.CanvasObject
|
|
if job.Status == queue.JobStatusRunning {
|
|
progress := widget.NewProgressBar()
|
|
progress.SetValue(job.Progress / 100.0)
|
|
progressWidget = progress
|
|
} else if job.Status == queue.JobStatusCompleted {
|
|
progress := widget.NewProgressBar()
|
|
progress.SetValue(1.0)
|
|
progressWidget = progress
|
|
} else {
|
|
progressWidget = widget.NewLabel("")
|
|
}
|
|
|
|
// Status text
|
|
statusText := getStatusText(job)
|
|
statusLabel := widget.NewLabel(statusText)
|
|
statusLabel.TextStyle = fyne.TextStyle{Monospace: true}
|
|
|
|
// Control buttons
|
|
var buttons []fyne.CanvasObject
|
|
switch job.Status {
|
|
case queue.JobStatusRunning:
|
|
buttons = append(buttons,
|
|
widget.NewButton("Pause", func() { onPause(job.ID) }),
|
|
widget.NewButton("Cancel", func() { onCancel(job.ID) }),
|
|
)
|
|
case queue.JobStatusPaused:
|
|
buttons = append(buttons,
|
|
widget.NewButton("Resume", func() { onResume(job.ID) }),
|
|
widget.NewButton("Cancel", func() { onCancel(job.ID) }),
|
|
)
|
|
case queue.JobStatusPending:
|
|
buttons = append(buttons,
|
|
widget.NewButton("Cancel", func() { onCancel(job.ID) }),
|
|
)
|
|
case queue.JobStatusCompleted, queue.JobStatusFailed, queue.JobStatusCancelled:
|
|
buttons = append(buttons,
|
|
widget.NewButton("Remove", func() { onRemove(job.ID) }),
|
|
)
|
|
}
|
|
|
|
buttonBox := container.NewHBox(buttons...)
|
|
|
|
// Info section
|
|
infoBox := container.NewVBox(
|
|
titleLabel,
|
|
descLabel,
|
|
progressWidget,
|
|
statusLabel,
|
|
)
|
|
|
|
// Main content
|
|
content := container.NewBorder(
|
|
nil, nil,
|
|
statusRect,
|
|
buttonBox,
|
|
infoBox,
|
|
)
|
|
|
|
// Card background
|
|
card := canvas.NewRectangle(bgColor)
|
|
card.CornerRadius = 4
|
|
|
|
item := container.NewPadded(
|
|
container.NewMax(card, content),
|
|
)
|
|
|
|
return item
|
|
}
|
|
|
|
// getStatusColor returns the color for a job status
|
|
func getStatusColor(status queue.JobStatus) color.Color {
|
|
switch status {
|
|
case queue.JobStatusPending:
|
|
return color.RGBA{R: 150, G: 150, B: 150, A: 255} // Gray
|
|
case queue.JobStatusRunning:
|
|
return color.RGBA{R: 68, G: 136, B: 255, A: 255} // Blue
|
|
case queue.JobStatusPaused:
|
|
return color.RGBA{R: 255, G: 193, B: 7, A: 255} // Yellow
|
|
case queue.JobStatusCompleted:
|
|
return color.RGBA{R: 76, G: 232, B: 112, A: 255} // Green
|
|
case queue.JobStatusFailed:
|
|
return color.RGBA{R: 255, G: 68, B: 68, A: 255} // Red
|
|
case queue.JobStatusCancelled:
|
|
return color.RGBA{R: 255, G: 136, B: 68, A: 255} // Orange
|
|
default:
|
|
return color.Gray{Y: 128}
|
|
}
|
|
}
|
|
|
|
// getStatusText returns a human-readable status string
|
|
func getStatusText(job *queue.Job) string {
|
|
switch job.Status {
|
|
case queue.JobStatusPending:
|
|
return fmt.Sprintf("Status: Pending | Priority: %d", job.Priority)
|
|
case queue.JobStatusRunning:
|
|
elapsed := ""
|
|
if job.StartedAt != nil {
|
|
elapsed = fmt.Sprintf(" | Elapsed: %s", time.Since(*job.StartedAt).Round(time.Second))
|
|
}
|
|
return fmt.Sprintf("Status: Running | Progress: %.1f%%%s", job.Progress, elapsed)
|
|
case queue.JobStatusPaused:
|
|
return "Status: Paused"
|
|
case queue.JobStatusCompleted:
|
|
duration := ""
|
|
if job.StartedAt != nil && job.CompletedAt != nil {
|
|
duration = fmt.Sprintf(" | Duration: %s", job.CompletedAt.Sub(*job.StartedAt).Round(time.Second))
|
|
}
|
|
return fmt.Sprintf("Status: Completed%s", duration)
|
|
case queue.JobStatusFailed:
|
|
return fmt.Sprintf("Status: Failed | Error: %s", job.Error)
|
|
case queue.JobStatusCancelled:
|
|
return "Status: Cancelled"
|
|
default:
|
|
return fmt.Sprintf("Status: %s", job.Status)
|
|
}
|
|
}
|