From b3b45e67be658aa0e96b1465785a2e2b9f860397 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Mon, 16 Feb 2026 13:08:45 +0100 Subject: [PATCH] add exit mechanicm to each stage remove types and moved struct to appropriate files --- cmd/survey/intro.go | 60 ++++++++++++++++++++---------------- cmd/survey/survey.go | 28 ++++++++++++----- pkg/task/runner.go | 20 +++++++++--- pkg/task/types.go | 3 ++ pkg/task/ui/questionnaire.go | 14 +++++++++ pkg/task/ui/task.go | 23 ++++++++++++++ pkg/task/ui/types.go | 28 ----------------- 7 files changed, 111 insertions(+), 65 deletions(-) delete mode 100644 pkg/task/ui/types.go diff --git a/cmd/survey/intro.go b/cmd/survey/intro.go index 1efa2ae..a734d20 100644 --- a/cmd/survey/intro.go +++ b/cmd/survey/intro.go @@ -1,11 +1,13 @@ package main import ( - "github.com/LazyBachelor/LazyPM/internal/style" + "errors" + tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/huh" ) +var ErrUserQuit = errors.New("user quit") + const ( IntroTitle = "Welcome to the Task Survey!" @@ -21,54 +23,60 @@ By participating, you consent to the collection and use of your data as describe ) type introModel struct { - form *huh.Form + stage int width, height int + userQuit bool } func newIntroModel() introModel { return introModel{ - form: huh.NewForm( - huh.NewGroup( - huh.NewNote().Title(IntroTitle).Description(IntroductionText), - ), - huh.NewGroup( - huh.NewNote().Title("Disclaimer").Description(Disclaimer), - ), - ), + stage: 0, } } func (m introModel) Init() tea.Cmd { - m.form.Init() return nil } func (m introModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { - case tea.KeyMsg: - switch msg.Type { - case tea.KeyEsc, tea.KeyCtrlC: - return m, tea.Quit - } case tea.WindowSizeMsg: m.SetSize(msg.Width, msg.Height) + case tea.KeyMsg: + switch msg.Type { + case tea.KeyEnter, tea.KeySpace: + m.stage++ + if m.stage > 1 { + return m, tea.Quit + } + case tea.KeyEsc, tea.KeyCtrlC: + m.userQuit = true + return m, tea.Quit + } } - form, cmd := m.form.Update(msg) - if f, ok := form.(*huh.Form); ok { - m.form = f - } - - return m, cmd + return m, nil } func (m introModel) View() string { - return m.form.WithTheme(style.HuhCenterTheme()).View() + switch m.stage { + case 0: + return IntroductionText + case 1: + return Disclaimer + } + return "" } func (m introModel) Run() error { - _, err := tea.NewProgram(m, tea.WithAltScreen()).Run() - return err + model, err := tea.NewProgram(m, tea.WithAltScreen()).Run() + if err != nil { + return err + } + if m, ok := model.(introModel); ok && m.userQuit { + return ErrUserQuit + } + return nil } func (m *introModel) SetSize(width, height int) { diff --git a/cmd/survey/survey.go b/cmd/survey/survey.go index 0d9629f..429a559 100644 --- a/cmd/survey/survey.go +++ b/cmd/survey/survey.go @@ -2,9 +2,11 @@ package main import ( "context" + "errors" "fmt" "log" "math/rand" + "os" "github.com/LazyBachelor/LazyPM/internal/service" "github.com/LazyBachelor/LazyPM/pkg/task" @@ -14,6 +16,9 @@ func main() { ctx := context.Background() if err := newIntroModel().Run(); err != nil { + if errors.Is(err, ErrUserQuit) { + os.Exit(0) + } log.Fatalf("Failed to run intro screen: %v\n", err) } @@ -27,6 +32,9 @@ func main() { interfaces := initInterfaces() if err := taskLoop(ctx, svc, surveyTasks, interfaces); err != nil { + if errors.Is(err, task.ErrUserQuit) { + os.Exit(0) + } log.Fatalf("Task loop failed: %v\n", err) } } @@ -34,23 +42,26 @@ func main() { func taskLoop(ctx context.Context, svc *service.Services, surveyTasks []*task.Task, interfaces []task.Interface) error { interfaceIndex := rand.Int() % len(interfaces) - for _, task := range surveyTasks { + for _, t := range surveyTasks { - task.SetInterface(interfaces[interfaceIndex]) + t.SetInterface(interfaces[interfaceIndex]) - if err := task.Initialize(ctx, svc); err != nil { + if err := t.Initialize(ctx, svc); err != nil { return fmt.Errorf("failed to initialize task: %w", err) } - if err := task.IntroduceTask(); err != nil { + if err := t.IntroduceTask(); err != nil { + if errors.Is(err, task.ErrUserQuit) { + return task.ErrUserQuit + } return fmt.Errorf("failed to display task introduction screen: %w", err) } - if err := task.StartInterface(ctx, task.Config); err != nil { + if err := t.StartInterface(ctx, t.Config); err != nil { return fmt.Errorf("failed to start task interface: %w", err) } - ok, err := task.Validate(ctx, svc) + ok, err := t.Validate(ctx, svc) if err != nil { return fmt.Errorf("validation error: %w", err) } @@ -58,7 +69,10 @@ func taskLoop(ctx context.Context, svc *service.Services, surveyTasks []*task.Ta return fmt.Errorf("task validation failed: task did not meet requirements") } - if err := task.StartQuestionnaire(); err != nil { + if err := t.StartQuestionnaire(); err != nil { + if errors.Is(err, task.ErrUserQuit) { + return task.ErrUserQuit + } return fmt.Errorf("failed to start questionnaire: %w", err) } diff --git a/pkg/task/runner.go b/pkg/task/runner.go index 9ad8b0a..5d43706 100644 --- a/pkg/task/runner.go +++ b/pkg/task/runner.go @@ -11,8 +11,14 @@ func (t *Task) IntroduceTask() error { if t.aboutScreen == nil { return fmt.Errorf("aboutScreen is not set") } - _, err := tea.NewProgram(t.aboutScreen, tea.WithAltScreen()).Run() - return err + model, err := tea.NewProgram(t.aboutScreen, tea.WithAltScreen()).Run() + if err != nil { + return err + } + if m, ok := model.(interface{ GetUserQuit() bool }); ok && m.GetUserQuit() { + return ErrUserQuit + } + return nil } func (t *Task) StartInterface(ctx context.Context, cfg TaskConfig) error { @@ -27,6 +33,12 @@ func (t *Task) StartQuestionnaire() error { if t.questionnaire == nil { return fmt.Errorf("questionnaire is not set") } - _, err := tea.NewProgram(t.questionnaire, tea.WithAltScreen()).Run() - return err + model, err := tea.NewProgram(t.questionnaire, tea.WithAltScreen()).Run() + if err != nil { + return err + } + if m, ok := model.(interface{ GetUserQuit() bool }); ok && m.GetUserQuit() { + return ErrUserQuit + } + return nil } diff --git a/pkg/task/types.go b/pkg/task/types.go index 90427d5..524ee2a 100644 --- a/pkg/task/types.go +++ b/pkg/task/types.go @@ -2,10 +2,13 @@ package task import ( "context" + "errors" "github.com/LazyBachelor/LazyPM/internal/service" ) +var ErrUserQuit = errors.New("user quit") + type TaskConfig = service.Config type Interface interface { diff --git a/pkg/task/ui/questionnaire.go b/pkg/task/ui/questionnaire.go index e7dc01d..f70daa2 100644 --- a/pkg/task/ui/questionnaire.go +++ b/pkg/task/ui/questionnaire.go @@ -7,6 +7,15 @@ import ( "github.com/charmbracelet/huh" ) +type Questions []*huh.Group + +type QuestionnaireModel struct { + Questions + form *huh.Form + width, height int + userQuit bool +} + func NewQuestionnaireModel(questions Questions) *QuestionnaireModel { form := huh.NewForm(questions...). WithTheme(style.HuhCenterTheme()).WithLayout(huh.LayoutGrid(1, 1)) @@ -28,6 +37,7 @@ func (q *QuestionnaireModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.KeyMsg: switch msg.String() { case "q", "ctrl+c": + q.userQuit = true return q, tea.Quit } } @@ -52,3 +62,7 @@ func (q *QuestionnaireModel) View() string { func (q *QuestionnaireModel) SetSize(width, height int) { q.width, q.height = width, height } + +func (q QuestionnaireModel) GetUserQuit() bool { + return q.userQuit +} diff --git a/pkg/task/ui/task.go b/pkg/task/ui/task.go index c416ad3..57a5d2f 100644 --- a/pkg/task/ui/task.go +++ b/pkg/task/ui/task.go @@ -1,6 +1,7 @@ package taskui import ( + "errors" "fmt" "charm.land/lipgloss/v2" @@ -9,6 +10,23 @@ import ( tea "github.com/charmbracelet/bubbletea" ) +var ErrUserQuit = errors.New("user quit") + +type TaskDetails struct { + Title string + Description string + TimeToComplete string + Difficulty string +} + +type TaskModel struct { + TaskDetails + keys TaskHelpKeys + help help.Model + width, height int + userQuit bool +} + func NewTaskModel(details TaskDetails) TaskModel { return TaskModel{ TaskDetails: details, @@ -28,6 +46,7 @@ func (m TaskModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.KeyMsg: switch { case key.Matches(msg, m.keys.Quit): + m.userQuit = true return m, tea.Quit case key.Matches(msg, m.keys.Continue): return m, tea.Quit @@ -70,3 +89,7 @@ func (m TaskModel) View() string { func (m *TaskModel) SetSize(width, height int) { m.width, m.height = width, height } + +func (m TaskModel) GetUserQuit() bool { + return m.userQuit +} diff --git a/pkg/task/ui/types.go b/pkg/task/ui/types.go deleted file mode 100644 index dc6a47e..0000000 --- a/pkg/task/ui/types.go +++ /dev/null @@ -1,28 +0,0 @@ -package taskui - -import ( - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/huh" -) - -type TaskDetails struct { - Title string - Description string - TimeToComplete string - Difficulty string -} - -type TaskModel struct { - TaskDetails - keys TaskHelpKeys - help help.Model - width, height int -} - -type Questions []*huh.Group - -type QuestionnaireModel struct { - Questions - form *huh.Form - width, height int -}