diff --git a/cmd/pm/main.go b/cmd/pm/main.go index 5ad70e1..d067ef4 100644 --- a/cmd/pm/main.go +++ b/cmd/pm/main.go @@ -14,6 +14,8 @@ func main() { StatisticsStoragePath: "./.pm/stats.json", } + cli := cli.NewCli() + if err := cli.Run(context.Background(), config); err != nil { return } diff --git a/cmd/survey.go b/cmd/survey.go deleted file mode 100644 index 5bd99c5..0000000 --- a/cmd/survey.go +++ /dev/null @@ -1,44 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - - "github.com/LazyBachelor/LazyPM/pkg" - "github.com/LazyBachelor/LazyPM/pkg/cli" - "github.com/LazyBachelor/LazyPM/pkg/cli/repl" - "github.com/LazyBachelor/LazyPM/pkg/tui" - "github.com/LazyBachelor/LazyPM/pkg/web" -) - -func main() { - config := pkg.SurveyConfig{ - RootCmd: "pm", - WebAddress: "localhost:8080", - IssuePrefix: "pm", - BeadsDBPath: "./.pm/db.db", - StatisticsStoragePath: "./.pm/stats.json", - } - - ctx := context.Background() - var err error - - switch os.Args[1] { - case "tui": - _, err = tui.Run(ctx, config) - case "cli": - err = cli.RunWithArgs(ctx, config, os.Args[2:]) - case "repl": - err = repl.RunREPL(ctx, config) - case "web": - err = web.Run(ctx, config) - default: - err = fmt.Errorf("unknown command: %s", os.Args[1]) - } - - if err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } -} diff --git a/cmd/survey/init.go b/cmd/survey/init.go new file mode 100644 index 0000000..533976b --- /dev/null +++ b/cmd/survey/init.go @@ -0,0 +1,32 @@ +package main + +import ( + "context" + + "github.com/LazyBachelor/LazyPM/cmd/survey/tasks" + "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/pkg/cli/repl" + "github.com/LazyBachelor/LazyPM/pkg/task" + "github.com/LazyBachelor/LazyPM/pkg/tui" + "github.com/LazyBachelor/LazyPM/pkg/web" +) + +func initializeServices(ctx context.Context) (*service.Services, func(), error) { + config := service.Config{ + IssuePrefix: "pm", + BeadsDBPath: "./.pm/db.db", + StatisticsStoragePath: "./.pm/stats.json", + WebAddress: "localhost:8080", + } + return service.NewServices(ctx, config) +} + +func initTasks() []*task.Task { + return []*task.Task{ + tasks.NewCreateIssueTask(), + } +} + +func initInterfaces() []task.Interface { + return []task.Interface{repl.NewRepl(), tui.NewTui(), web.NewWeb()} +} diff --git a/cmd/survey/survey.go b/cmd/survey/survey.go new file mode 100644 index 0000000..64e36e2 --- /dev/null +++ b/cmd/survey/survey.go @@ -0,0 +1,67 @@ +package main + +import ( + "context" + "fmt" + "log" + "math/rand" + + "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/pkg/task" +) + +func main() { + ctx := context.Background() + + svc, close, err := initializeServices(ctx) + if err != nil { + log.Fatalf("Failed to initialize services: %v\n", err) + } + defer close() + + surveyTasks := initTasks() + interfaces := initInterfaces() + + if err := taskLoop(ctx, svc, surveyTasks, interfaces); err != nil { + log.Fatalf("Task loop failed: %v\n", err) + } +} + +func taskLoop(ctx context.Context, svc *service.Services, surveyTasks []*task.Task, interfaces []task.Interface) error { + interfaceIndex := rand.Int() % len(interfaces) + + for _, task := range surveyTasks { + + task.SetInterface(interfaces[interfaceIndex]) + + if err := task.Initialize(ctx, svc); err != nil { + return fmt.Errorf("failed to initialize task: %w", err) + } + + if err := task.IntroduceTask(); err != nil { + return fmt.Errorf("failed to display task introduction screen: %w", err) + } + + if err := task.StartInterface(ctx, task.Config); err != nil { + return fmt.Errorf("failed to start task interface: %w", err) + } + + ok, err := task.Validate(ctx, svc) + if err != nil { + return fmt.Errorf("validation error: %w", err) + } + if !ok { + return fmt.Errorf("task validation failed: task did not meet requirements") + } + + if err := task.StartQuestionnaire(); err != nil { + return fmt.Errorf("failed to start questionnaire: %w", err) + } + + interfaceIndex++ + if interfaceIndex >= len(interfaces) { + interfaceIndex = 0 + } + } + return nil +} diff --git a/cmd/survey/tasks/createIssue.go b/cmd/survey/tasks/createIssue.go new file mode 100644 index 0000000..9a9a2d4 --- /dev/null +++ b/cmd/survey/tasks/createIssue.go @@ -0,0 +1,84 @@ +package tasks + +import ( + "context" + "errors" + + "github.com/LazyBachelor/LazyPM/internal/models" + "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/pkg/task" + ui "github.com/LazyBachelor/LazyPM/pkg/task/ui" + "github.com/charmbracelet/huh" +) + +func NewCreateIssueTask() *task.Task { + aboutScreen := ui.NewTaskModel(createIssueDetails()) + questionnaire := ui.NewQuestionnaireModel(createIssueQuestionnaire()) + + task := task.NewTask(aboutScreen, questionnaire) + task.SetConfigFunc(createIssueConfig) + task.SetDbStateFunc(createIssueDbState) + task.SetValidateFunc(createIssueValidate) + return task +} + +func createIssueConfig() task.TaskConfig { + return task.TaskConfig{ + IssuePrefix: "pm", + BeadsDBPath: "./.pm/db.db", + StatisticsStoragePath: "./.pm/task-1-stats.json", + WebAddress: "localhost:8080", + } +} + +func createIssueDetails() ui.TaskDetails { + return ui.TaskDetails{ + Title: "Create Issue Task", + Description: "Create a new issue in the project management system to test the issue creation workflow.", + TimeToComplete: "15m", + Difficulty: "Hard", + } +} + +func createIssueQuestionnaire() ui.Questions { + return ui.Questions{ + huh.NewGroup( + huh.NewConfirm().Title("Was this good"), + ), + huh.NewGroup( + huh.NewSelect[int]().Options( + huh.NewOption("Very good", 1), + huh.NewOption("Very Bad", 2), + ).Title("How good was it?"), + ), + } +} + +func createIssueDbState(ctx context.Context, svc *service.Services) error { + if err := svc.DeleteIssues(); err != nil { + return err + } + + issues := []*models.Issue{ + {Title: "Test Issue", Description: "Long Description", IssueType: models.TypeBug, Status: models.StatusBlocked}, + } + + if err := svc.Beads.CreateIssues(ctx, issues, "actor"); err != nil { + return err + } + + return nil +} + +func createIssueValidate(ctx context.Context, svc *service.Services) (ok bool, errorMsg error) { + issues, err := svc.Beads.SearchIssues(ctx, "", models.IssueFilter{}) + if err != nil { + return false, err + } + + if len(issues) == 0 { + return false, errors.New("no issues found. Please create an issue to proceed.") + } + + return true, nil +} diff --git a/cmd/tui/main.go b/cmd/tui/main.go index f53021f..824e6aa 100644 --- a/cmd/tui/main.go +++ b/cmd/tui/main.go @@ -13,7 +13,9 @@ func main() { IssuePrefix: "pm", } - if _, err := tui.Run(context.Background(), config); err != nil { + tui := tui.NewTui() + + if err := tui.Run(context.Background(), config); err != nil { panic(err) } } diff --git a/cmd/web/main.go b/cmd/web/main.go index bf8542b..1cb7518 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -1,14 +1,17 @@ package main import ( - "github.com/LazyBachelor/LazyPM/internal/service" - "github.com/LazyBachelor/LazyPM/pkg/web" "context" "fmt" "os" + + "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/pkg/web" ) func main() { + web := web.NewWeb() + config := service.Config{ WebAddress: "localhost:8080", BeadsDBPath: "./.pm/db.db", diff --git a/go.mod b/go.mod index 5419fc8..521c31d 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/LazyBachelor/LazyPM go 1.25.6 require ( + charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106193318-19329a3e8410 github.com/google/uuid v1.6.0 github.com/muesli/reflow v0.3.0 github.com/steveyegge/beads v0.49.6 @@ -28,7 +29,6 @@ require ( ) require ( - charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106193318-19329a3e8410 // indirect github.com/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect github.com/andybalholm/brotli v1.2.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect diff --git a/internal/service/service.go b/internal/service/service.go index f36730e..8210ae7 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -2,6 +2,7 @@ package service import ( "context" + "database/sql" "fmt" "os" "time" @@ -24,6 +25,7 @@ type Config struct { type Services struct { Config Config + DB *sql.DB Beads *BeadsService Statistics *StatisticsService } @@ -36,6 +38,12 @@ func NewServices(ctx context.Context, config Config) (*Services, func(), error) os.Exit(0) } + db, err := sql.Open("sqlite3", config.BeadsDBPath) + if err != nil { + return nil, nil, err + } + cleanupFuncs = append(cleanupFuncs, func() { db.Close() }) + store, err := beads.NewSQLiteStorage(ctx, config.BeadsDBPath) if err != nil { return nil, nil, err @@ -59,6 +67,7 @@ func NewServices(ctx context.Context, config Config) (*Services, func(), error) } return &Services{ + DB: db, Beads: beadsSvc, Statistics: statSvc, Config: config, @@ -92,3 +101,13 @@ func initialized(beadsPath string) bool { } return true } + +func (s *Services) DeleteIssues() error { + + var deleteIssues = "DELETE FROM issues;" + + if _, err := s.DB.Exec(deleteIssues); err != nil { + return err + } + return nil +} diff --git a/internal/style/styles.go b/internal/style/styles.go new file mode 100644 index 0000000..7dd47c8 --- /dev/null +++ b/internal/style/styles.go @@ -0,0 +1,28 @@ +package style + +import "github.com/charmbracelet/lipgloss" + +// Color palette +var ( + PrimaryColor = lipgloss.AdaptiveColor{Light: "#007acc", Dark: "#1e90ff"} + SecondaryColor = lipgloss.AdaptiveColor{Light: "#ff6f61", Dark: "#ff6347"} + AccentColor = lipgloss.AdaptiveColor{Light: "#6a5acd", Dark: "#9370db"} + Background = lipgloss.AdaptiveColor{Light: "#ffffff", Dark: "#1e1e1e"} + TextColor = lipgloss.AdaptiveColor{Light: "#000000", Dark: "#ffffff"} +) + +var ( + AppStyle = lipgloss.NewStyle().Padding(1, 2).Background(Background).Foreground(TextColor) +) + +var ( + DefaultBorder = lipgloss.NormalBorder() + BorderStyle = lipgloss.NewStyle().Border(DefaultBorder).BorderForeground(PrimaryColor) +) + +var ( + TitleStyle = lipgloss.NewStyle().Foreground(PrimaryColor).Bold(true) + DescriptionStyle = lipgloss.NewStyle().Foreground(TextColor).Italic(true) + DetailStyle = lipgloss.NewStyle().Foreground(SecondaryColor) + HelpStyle = lipgloss.NewStyle().Foreground(AccentColor) +) diff --git a/internal/style/themes.go b/internal/style/themes.go new file mode 100644 index 0000000..c6861bd --- /dev/null +++ b/internal/style/themes.go @@ -0,0 +1,14 @@ +package style + +import ( + "github.com/charmbracelet/huh" + "github.com/charmbracelet/lipgloss" +) + +func HuhCenterTheme() *huh.Theme { + theme := huh.ThemeBase16() + + theme.Focused.Base = lipgloss.NewStyle().Align(lipgloss.Center) + + return theme +} diff --git a/main.go b/main.go deleted file mode 100644 index 25d2bc4..0000000 --- a/main.go +++ /dev/null @@ -1,53 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - - "github.com/LazyBachelor/LazyPM/internal/models" - "github.com/LazyBachelor/LazyPM/internal/service" -) - -func main() { - ctx := context.Background() - - config := service.Config{ - IssuePrefix: "pm", - BeadsDBPath: "./.pm/db.db", - StatisticsStoragePath: "./.pm/stats.json", - } - svc, cleanup, err := service.NewServices(ctx, config) - checkErr(err) - - defer cleanup() - - issue := &models.Issue{ - IssueType: models.TypeTask, - Title: "Sample Issue", - Description: "This is a sample issue created for testing.", - Status: models.StatusOpen, - } - - err = svc.Beads.CreateIssue(ctx, issue, "") - checkErr(err) - - fetchedIssues, err := svc.Beads.SearchIssues(ctx, "", models.IssueFilter{}) - checkErr(err) - - for _, iss := range fetchedIssues { - fmt.Printf("Issue ID: %s, Title: %s, Status: %s\n", iss.ID, iss.Title, iss.Status) - } - - stats, err := svc.Statistics.GetStatistics() - checkErr(err) - - fmt.Printf("\nStatistics: %v\n", stats) -} - -func checkErr(err error) { - if err != nil { - fmt.Println("Error:", err) - os.Exit(1) - } -} diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index c3f515c..27dd22a 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -11,8 +11,14 @@ import ( // CLIConfig is an alias for service.Config, used to configure the CLI. type CLIConfig = service.Config +type CLI struct{} + +func NewCli() *CLI { + return &CLI{} +} + // Run initializes the services and executes the CLI commands. -func Run(ctx context.Context, config CLIConfig) error { +func (c *CLI) Run(ctx context.Context, config CLIConfig) error { svc, cleanup, err := service.NewServices(ctx, config) if err != nil { return err @@ -30,7 +36,7 @@ func Run(ctx context.Context, config CLIConfig) error { } // RunWithArgs initializes the services and executes the CLI commands with the provided arguments. -func RunWithArgs(ctx context.Context, config CLIConfig, args []string) error { +func (c *CLI) RunWithArgs(ctx context.Context, config CLIConfig, args []string) error { svc, cleanup, err := service.NewServices(ctx, config) if err != nil { return err diff --git a/pkg/cli/repl/repl.go b/pkg/cli/repl/repl.go index cba9569..5f0faa8 100644 --- a/pkg/cli/repl/repl.go +++ b/pkg/cli/repl/repl.go @@ -22,8 +22,14 @@ You can also run shell commands directly. Type 'exit' or 'quit' to leave.` ReplTitle = "Welcome to Project Management CLI! " + ReplHelp ) -// RunREPL starts the interactive Read-Eval-Print Loop for the PM CLI. -func RunREPL(ctx context.Context, config cli.CLIConfig) error { +type REPL struct{} + +func NewRepl() *REPL { + return &REPL{} +} + +// Run starts the interactive Read-Eval-Print Loop for the PM CLI. +func (r *REPL) Run(ctx context.Context, config cli.CLIConfig) error { // Set terminal to raw mode to capture input properly in the REPL. // This allows us to handle input character by character and provide a better user experience. // We also ensure that the terminal state is restored when the REPL exits, even if an error occurs. diff --git a/pkg/survey.go b/pkg/survey.go deleted file mode 100644 index 49636e1..0000000 --- a/pkg/survey.go +++ /dev/null @@ -1,19 +0,0 @@ -package pkg - -import ( - "context" - - "github.com/LazyBachelor/LazyPM/internal/service" -) - -type SurveyConfig = service.Config - -func Run(ctx context.Context, config SurveyConfig) error { - _, cleanup, err := service.NewServices(ctx, config) - if err != nil { - return err - } - defer cleanup() - - return nil -} diff --git a/pkg/task/runner.go b/pkg/task/runner.go new file mode 100644 index 0000000..9ad8b0a --- /dev/null +++ b/pkg/task/runner.go @@ -0,0 +1,32 @@ +package task + +import ( + "context" + "fmt" + + tea "github.com/charmbracelet/bubbletea" +) + +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 +} + +func (t *Task) StartInterface(ctx context.Context, cfg TaskConfig) error { + if t.interfaceType == nil { + return fmt.Errorf("interfaceType is not set") + } + + return t.interfaceType.Run(ctx, cfg) +} + +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 +} diff --git a/pkg/task/task.go b/pkg/task/task.go new file mode 100644 index 0000000..2a9e548 --- /dev/null +++ b/pkg/task/task.go @@ -0,0 +1,56 @@ +package task + +import ( + "context" + "fmt" + + "github.com/LazyBachelor/LazyPM/internal/service" + tea "github.com/charmbracelet/bubbletea" +) + +type Task struct { + Config TaskConfig + interfaceType Interface + aboutScreen tea.Model + questionnaire tea.Model + + validateFunc ValidateFunc + dbStateFunc DbStateFunc +} + +func NewTask(aboutScreen tea.Model, questionnaire tea.Model) *Task { + return &Task{ + aboutScreen: aboutScreen, + questionnaire: questionnaire, + } +} + +func (t *Task) SetConfigFunc(fn ConfigFunc) { + t.Config = fn() +} + +func (t *Task) SetInterface(interfaceType Interface) { + t.interfaceType = interfaceType +} + +func (t *Task) SetDbStateFunc(fn DbStateFunc) { + t.dbStateFunc = fn +} + +func (t *Task) SetValidateFunc(fn ValidateFunc) { + t.validateFunc = fn +} + +func (t *Task) Initialize(ctx context.Context, svc *service.Services) error { + if t.dbStateFunc == nil { + return fmt.Errorf("dbStateFunc is not set") + } + return t.dbStateFunc(ctx, svc) +} + +func (t *Task) Validate(ctx context.Context, svc *service.Services) (bool, error) { + if t.validateFunc == nil { + return false, fmt.Errorf("validateFunc is not set") + } + return t.validateFunc(ctx, svc) +} diff --git a/pkg/task/types.go b/pkg/task/types.go new file mode 100644 index 0000000..90427d5 --- /dev/null +++ b/pkg/task/types.go @@ -0,0 +1,17 @@ +package task + +import ( + "context" + + "github.com/LazyBachelor/LazyPM/internal/service" +) + +type TaskConfig = service.Config + +type Interface interface { + Run(context.Context, TaskConfig) error +} + +type ConfigFunc func() TaskConfig +type ValidateFunc func(context.Context, *service.Services) (ok bool, err error) +type DbStateFunc func(context.Context, *service.Services) error diff --git a/pkg/task/ui/help.go b/pkg/task/ui/help.go new file mode 100644 index 0000000..ebc31e4 --- /dev/null +++ b/pkg/task/ui/help.go @@ -0,0 +1,27 @@ +package taskui + +import "github.com/charmbracelet/bubbles/key" + +type TaskHelpKeys struct { + Quit key.Binding + Continue key.Binding +} + +var DefaultTaskKeys = TaskHelpKeys{ + Quit: key.NewBinding( + key.WithKeys("q", "ctrl+c"), + key.WithHelp("q", "Quit"), + ), + Continue: key.NewBinding( + key.WithKeys(" "), + key.WithHelp("space", "Continue"), + ), +} + +func (h TaskHelpKeys) ShortHelp() []key.Binding { + return []key.Binding{h.Continue, h.Quit} +} + +func (h TaskHelpKeys) FullHelp() [][]key.Binding { + return [][]key.Binding{{h.Continue, h.Quit}} +} diff --git a/pkg/task/ui/questionnaire.go b/pkg/task/ui/questionnaire.go new file mode 100644 index 0000000..e7dc01d --- /dev/null +++ b/pkg/task/ui/questionnaire.go @@ -0,0 +1,54 @@ +package taskui + +import ( + "charm.land/lipgloss/v2" + "github.com/LazyBachelor/LazyPM/internal/style" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/huh" +) + +func NewQuestionnaireModel(questions Questions) *QuestionnaireModel { + form := huh.NewForm(questions...). + WithTheme(style.HuhCenterTheme()).WithLayout(huh.LayoutGrid(1, 1)) + + return &QuestionnaireModel{ + Questions: questions, + form: form, + } +} + +func (q *QuestionnaireModel) Init() tea.Cmd { + return q.form.Init() +} + +func (q *QuestionnaireModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + q.SetSize(msg.Width, msg.Height) + case tea.KeyMsg: + switch msg.String() { + case "q", "ctrl+c": + return q, tea.Quit + } + } + + form, cmd := q.form.Update(msg) + if f, ok := form.(*huh.Form); ok { + q.form = f + } + return q, cmd +} + +func (q *QuestionnaireModel) View() string { + form := lipgloss.NewStyle(). + Width(q.width).Align(lipgloss.Center). + Render(q.form.View()) + + return lipgloss.Place( + q.width, q.height, lipgloss.Center, lipgloss.Center, form, + ) +} + +func (q *QuestionnaireModel) SetSize(width, height int) { + q.width, q.height = width, height +} diff --git a/pkg/task/ui/task.go b/pkg/task/ui/task.go new file mode 100644 index 0000000..c416ad3 --- /dev/null +++ b/pkg/task/ui/task.go @@ -0,0 +1,72 @@ +package taskui + +import ( + "fmt" + + "charm.land/lipgloss/v2" + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" +) + +func NewTaskModel(details TaskDetails) TaskModel { + return TaskModel{ + TaskDetails: details, + keys: DefaultTaskKeys, + help: help.New(), + } +} + +func (m TaskModel) Init() tea.Cmd { + return nil +} + +func (m TaskModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.SetSize(msg.Width, msg.Height) + case tea.KeyMsg: + switch { + case key.Matches(msg, m.keys.Quit): + return m, tea.Quit + case key.Matches(msg, m.keys.Continue): + return m, tea.Quit + } + } + return m, nil +} + +func (m TaskModel) View() string { + padding := 3 + + header := lipgloss.NewStyle(). + PaddingTop(padding).Width(m.width).Align(lipgloss.Center). + Bold(true).Render(m.Title) + + headerHeight := lipgloss.Height(header) + + helpView := lipgloss.NewStyle(). + PaddingBottom(padding). + Width(m.width).Align(lipgloss.Center). + Render(m.help.View(m.keys)) + + helpHeight := lipgloss.Height(helpView) + + detailsText := fmt.Sprintf("Time to complete: %s | Difficulty: %s", m.TimeToComplete, m.Difficulty) + details := lipgloss.NewStyle().Align(lipgloss.Center). + Width(m.width).PaddingBottom(1).Render(detailsText) + + detailsHeight := lipgloss.Height(details) + + content := lipgloss.NewStyle(). + Width(m.width).Height(m.height-headerHeight-helpHeight-detailsHeight). + Align(lipgloss.Center, lipgloss.Center). + Render(m.Description) + + return lipgloss.JoinVertical(lipgloss.Top, header, content, details, helpView) + +} + +func (m *TaskModel) SetSize(width, height int) { + m.width, m.height = width, height +} diff --git a/pkg/task/ui/types.go b/pkg/task/ui/types.go new file mode 100644 index 0000000..dc6a47e --- /dev/null +++ b/pkg/task/ui/types.go @@ -0,0 +1,28 @@ +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 +} diff --git a/pkg/tui/tui.go b/pkg/tui/tui.go index b0feda5..f9a704d 100644 --- a/pkg/tui/tui.go +++ b/pkg/tui/tui.go @@ -10,16 +10,24 @@ import ( type TUIConfig = service.Config -func Run(ctx context.Context, config TUIConfig) (tea.Model, error) { +type Tui struct{} + +func NewTui() *Tui { + return &Tui{} +} + +func (t *Tui) Run(ctx context.Context, config TUIConfig) error { svc, cleanup, err := service.NewServices(ctx, config) if err != nil { - return nil, err + return err } defer cleanup() - app := tea.NewProgram(views.NewDashboardView(svc), - tea.WithAltScreen(), tea.WithMouseAllMotion()) + if _, err := tea.NewProgram(views.NewDashboardView(svc), + tea.WithAltScreen(), tea.WithMouseAllMotion()).Run(); err != nil { + return err + } - return app.Run() + return nil } diff --git a/pkg/web/web.go b/pkg/web/web.go index 0670da8..8c85f36 100644 --- a/pkg/web/web.go +++ b/pkg/web/web.go @@ -11,10 +11,16 @@ import ( type WebConfig = service.Config +type Web struct{} + +func NewWeb() *Web { + return &Web{} +} + //go:embed assets/* var assets embed.FS -func Run(ctx context.Context, config WebConfig) error { +func (w *Web) Run(ctx context.Context, config WebConfig) error { svc, cleanup, err := service.NewServices(ctx, config) if err != nil { return err