add exit mechanicm to each stage

remove types and moved struct to appropriate files
This commit is contained in:
Robin Olsen
2026-02-16 13:08:45 +01:00
parent 1fe92d1972
commit b3b45e67be
7 changed files with 111 additions and 65 deletions

View File

@@ -1,11 +1,13 @@
package main package main
import ( import (
"github.com/LazyBachelor/LazyPM/internal/style" "errors"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/huh"
) )
var ErrUserQuit = errors.New("user quit")
const ( const (
IntroTitle = "Welcome to the Task Survey!" 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 { type introModel struct {
form *huh.Form stage int
width, height int width, height int
userQuit bool
} }
func newIntroModel() introModel { func newIntroModel() introModel {
return introModel{ return introModel{
form: huh.NewForm( stage: 0,
huh.NewGroup(
huh.NewNote().Title(IntroTitle).Description(IntroductionText),
),
huh.NewGroup(
huh.NewNote().Title("Disclaimer").Description(Disclaimer),
),
),
} }
} }
func (m introModel) Init() tea.Cmd { func (m introModel) Init() tea.Cmd {
m.form.Init()
return nil return nil
} }
func (m introModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m introModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.Type {
case tea.KeyEsc, tea.KeyCtrlC:
return m, tea.Quit
}
case tea.WindowSizeMsg: case tea.WindowSizeMsg:
m.SetSize(msg.Width, msg.Height) 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) return m, nil
if f, ok := form.(*huh.Form); ok {
m.form = f
}
return m, cmd
} }
func (m introModel) View() string { 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 { func (m introModel) Run() error {
_, err := tea.NewProgram(m, tea.WithAltScreen()).Run() model, err := tea.NewProgram(m, tea.WithAltScreen()).Run()
return err if err != nil {
return err
}
if m, ok := model.(introModel); ok && m.userQuit {
return ErrUserQuit
}
return nil
} }
func (m *introModel) SetSize(width, height int) { func (m *introModel) SetSize(width, height int) {

View File

@@ -2,9 +2,11 @@ package main
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"log" "log"
"math/rand" "math/rand"
"os"
"github.com/LazyBachelor/LazyPM/internal/service" "github.com/LazyBachelor/LazyPM/internal/service"
"github.com/LazyBachelor/LazyPM/pkg/task" "github.com/LazyBachelor/LazyPM/pkg/task"
@@ -14,6 +16,9 @@ func main() {
ctx := context.Background() ctx := context.Background()
if err := newIntroModel().Run(); err != nil { if err := newIntroModel().Run(); err != nil {
if errors.Is(err, ErrUserQuit) {
os.Exit(0)
}
log.Fatalf("Failed to run intro screen: %v\n", err) log.Fatalf("Failed to run intro screen: %v\n", err)
} }
@@ -27,6 +32,9 @@ func main() {
interfaces := initInterfaces() interfaces := initInterfaces()
if err := taskLoop(ctx, svc, surveyTasks, interfaces); err != nil { 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) 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 { func taskLoop(ctx context.Context, svc *service.Services, surveyTasks []*task.Task, interfaces []task.Interface) error {
interfaceIndex := rand.Int() % len(interfaces) 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) 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) 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) 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 { if err != nil {
return fmt.Errorf("validation error: %w", err) 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") 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) return fmt.Errorf("failed to start questionnaire: %w", err)
} }

View File

@@ -11,8 +11,14 @@ func (t *Task) IntroduceTask() error {
if t.aboutScreen == nil { if t.aboutScreen == nil {
return fmt.Errorf("aboutScreen is not set") return fmt.Errorf("aboutScreen is not set")
} }
_, err := tea.NewProgram(t.aboutScreen, tea.WithAltScreen()).Run() model, err := tea.NewProgram(t.aboutScreen, tea.WithAltScreen()).Run()
return err 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 { func (t *Task) StartInterface(ctx context.Context, cfg TaskConfig) error {
@@ -27,6 +33,12 @@ func (t *Task) StartQuestionnaire() error {
if t.questionnaire == nil { if t.questionnaire == nil {
return fmt.Errorf("questionnaire is not set") return fmt.Errorf("questionnaire is not set")
} }
_, err := tea.NewProgram(t.questionnaire, tea.WithAltScreen()).Run() model, err := tea.NewProgram(t.questionnaire, tea.WithAltScreen()).Run()
return err if err != nil {
return err
}
if m, ok := model.(interface{ GetUserQuit() bool }); ok && m.GetUserQuit() {
return ErrUserQuit
}
return nil
} }

View File

@@ -2,10 +2,13 @@ package task
import ( import (
"context" "context"
"errors"
"github.com/LazyBachelor/LazyPM/internal/service" "github.com/LazyBachelor/LazyPM/internal/service"
) )
var ErrUserQuit = errors.New("user quit")
type TaskConfig = service.Config type TaskConfig = service.Config
type Interface interface { type Interface interface {

View File

@@ -7,6 +7,15 @@ import (
"github.com/charmbracelet/huh" "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 { func NewQuestionnaireModel(questions Questions) *QuestionnaireModel {
form := huh.NewForm(questions...). form := huh.NewForm(questions...).
WithTheme(style.HuhCenterTheme()).WithLayout(huh.LayoutGrid(1, 1)) 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: case tea.KeyMsg:
switch msg.String() { switch msg.String() {
case "q", "ctrl+c": case "q", "ctrl+c":
q.userQuit = true
return q, tea.Quit return q, tea.Quit
} }
} }
@@ -52,3 +62,7 @@ func (q *QuestionnaireModel) View() string {
func (q *QuestionnaireModel) SetSize(width, height int) { func (q *QuestionnaireModel) SetSize(width, height int) {
q.width, q.height = width, height q.width, q.height = width, height
} }
func (q QuestionnaireModel) GetUserQuit() bool {
return q.userQuit
}

View File

@@ -1,6 +1,7 @@
package taskui package taskui
import ( import (
"errors"
"fmt" "fmt"
"charm.land/lipgloss/v2" "charm.land/lipgloss/v2"
@@ -9,6 +10,23 @@ import (
tea "github.com/charmbracelet/bubbletea" 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 { func NewTaskModel(details TaskDetails) TaskModel {
return TaskModel{ return TaskModel{
TaskDetails: details, TaskDetails: details,
@@ -28,6 +46,7 @@ func (m TaskModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.KeyMsg: case tea.KeyMsg:
switch { switch {
case key.Matches(msg, m.keys.Quit): case key.Matches(msg, m.keys.Quit):
m.userQuit = true
return m, tea.Quit return m, tea.Quit
case key.Matches(msg, m.keys.Continue): case key.Matches(msg, m.keys.Continue):
return m, tea.Quit return m, tea.Quit
@@ -70,3 +89,7 @@ func (m TaskModel) View() string {
func (m *TaskModel) SetSize(width, height int) { func (m *TaskModel) SetSize(width, height int) {
m.width, m.height = width, height m.width, m.height = width, height
} }
func (m TaskModel) GetUserQuit() bool {
return m.userQuit
}

View File

@@ -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
}