diff --git a/cmd/pm/intro.go b/cmd/pm/intro.go index 9d23844..9353ebb 100644 --- a/cmd/pm/intro.go +++ b/cmd/pm/intro.go @@ -50,7 +50,7 @@ func newIntroModel() introModel { key.WithHelp("enter", "start survey"), ), Continue: key.NewBinding( - key.WithKeys(" ", "j", "l", "down", "right"), + key.WithKeys("space", "j", "l", "down", "right"), key.WithHelp("space", "continue"), ), Back: key.NewBinding( @@ -69,11 +69,11 @@ func newIntroModel() introModel { } func (m introModel) Run() error { - model, err := tea.NewProgram(m, tea.WithAltScreen()).Run() + model, err := tea.NewProgram(m).Run() if err != nil { return err } - if m, ok := model.(introModel); ok && m.userQuit { + if im, ok := model.(introModel); ok && im.userQuit { return models.ErrUserQuit } return nil @@ -87,7 +87,7 @@ func (m introModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: m.SetSize(msg.Width, msg.Height) - case tea.KeyMsg: + case tea.KeyPressMsg: switch { case key.Matches(msg, m.keys.Start) && m.stage == stages: return m, tea.Quit @@ -109,10 +109,13 @@ func (m introModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } -func (m introModel) View() string { +func (m introModel) View() tea.View { if m.width < 55 || m.height < 16 { - return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, + content := lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, style.TextStyle.Render("Terminal too small.")) + v := tea.NewView(content) + v.AltScreen = true + return v } var content string @@ -122,7 +125,7 @@ func (m introModel) View() string { case 2: content = Disclaimer default: - return "" + return tea.NewView("") } boxWidth := min(m.width-10, 120) @@ -149,7 +152,9 @@ func (m introModel) View() string { final := lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, b.String()) - return final + v := tea.NewView(final) + v.AltScreen = true + return v } func (m *introModel) SetSize(width, height int) { diff --git a/cmd/pm/intro_questionnare.go b/cmd/pm/intro_questionnare.go index d9486a9..c5bbdd2 100644 --- a/cmd/pm/intro_questionnare.go +++ b/cmd/pm/intro_questionnare.go @@ -14,7 +14,7 @@ func newIntroQuestionnaire() *IntroQuestionnaire { func (iq *IntroQuestionnaire) Run() (map[string]any, error) { model := task.NewQuestionnaireModel(iq.Questions(), iq.Keys()) - app := tea.NewProgram(model, tea.WithAltScreen()) + app := tea.NewProgram(model) m, err := app.Run() if err != nil { diff --git a/cmd/pm/runner.go b/cmd/pm/runner.go index d524ed8..0872e30 100644 --- a/cmd/pm/runner.go +++ b/cmd/pm/runner.go @@ -11,6 +11,7 @@ import ( "github.com/LazyBachelor/LazyPM/cmd/pm/tasks" "github.com/LazyBachelor/LazyPM/internal/commands/survey" "github.com/LazyBachelor/LazyPM/internal/storage" + "github.com/LazyBachelor/LazyPM/internal/style" "github.com/LazyBachelor/LazyPM/pkg/task" "github.com/spf13/cobra" ) @@ -54,7 +55,7 @@ func runStartCmd(cmd *cobra.Command, args []string) error { Title("Do you want to continue without submitting your responses?"). Description("You can fix your database connection and submit your responses later with the submit command."). Value(&continueWithoutSubmitting). - WithTheme(huh.ThemeBase16(true)). + WithTheme(style.Base16Theme{}). RunAccessible(cmd.OutOrStdout(), cmd.InOrStdin()); err != nil { return fmt.Errorf("failed to read user input: %w", err) } diff --git a/internal/app/initializer.go b/internal/app/initializer.go index 79eb8fb..a6966e2 100644 --- a/internal/app/initializer.go +++ b/internal/app/initializer.go @@ -5,6 +5,7 @@ import ( "os" "charm.land/huh/v2" + "github.com/LazyBachelor/LazyPM/internal/style" ) type Initializer interface { @@ -30,7 +31,7 @@ func (i InteractiveInitializer) Init(path string) error { Title("PM is not initialized in this directory!"). Description("Do you want to initialize it here?"). Value(&initialize), - )).WithTheme(huh.ThemeBase16()).WithAccessible(true).Run() + )).WithTheme(style.Base16Theme{}).WithAccessible(true).Run() if err != nil { return err diff --git a/internal/commands/issues/close.go b/internal/commands/issues/close.go index 3dcd1f2..59d3b95 100644 --- a/internal/commands/issues/close.go +++ b/internal/commands/issues/close.go @@ -4,6 +4,7 @@ import ( "fmt" "charm.land/huh/v2" + "github.com/LazyBachelor/LazyPM/internal/style" "github.com/spf13/cobra" ) @@ -47,13 +48,13 @@ func runCloseCmd(cmd *cobra.Command, args []string) error { huh.NewOption("Won't fix", "Won't fix"), huh.NewOption("Obsolete", "Obsolete"), huh.NewOption("Other", "Other"), - ).WithTheme(huh.ThemeBase()).Run(); err != nil { + ).WithTheme(style.BaseTheme{}).Run(); err != nil { return fmt.Errorf("error getting close reason: %w", err) } if closeReason == "Other" { if err = huh.NewInput().Value(&closeReason). - Title("Enter closing reason:").WithTheme(huh.ThemeBase()).Run(); err != nil { + Title("Enter closing reason:").WithTheme(style.BaseTheme{}).Run(); err != nil { return fmt.Errorf("error getting close reason: %w", err) } if closeReason == "" { diff --git a/internal/commands/issues/delete.go b/internal/commands/issues/delete.go index c8ca2b2..e3e9aa7 100644 --- a/internal/commands/issues/delete.go +++ b/internal/commands/issues/delete.go @@ -7,6 +7,7 @@ import ( "charm.land/huh/v2" "github.com/LazyBachelor/LazyPM/internal/models" + "github.com/LazyBachelor/LazyPM/internal/style" "github.com/spf13/cobra" ) @@ -62,7 +63,7 @@ func runDeleteCmd(cmd *cobra.Command, args []string) error { if !cmd.Flags().Changed("yes") { huh.NewConfirm().Value(&confirmDelete). Title("You want to delete this issue?"). - Inline(true).WithTheme(huh.ThemeBase()).Run() + Inline(true).WithTheme(style.BaseTheme{}).Run() } // If user did not confirm, cancel deletion. @@ -102,7 +103,7 @@ func runDeleteInteractive(ctx context.Context) error { huh.NewGroup( huh.NewMultiSelect[string](). Options(options...).Value(&deleteIDs). - Title("Select issues to delete"))).WithTheme(huh.ThemeBase()) + Title("Select issues to delete"))).WithTheme(style.BaseTheme{}) if err := form.Run(); err != nil { return fmt.Errorf("error running interactive form: %w", err) diff --git a/internal/storage/mongo.go b/internal/storage/mongo.go index b00e06a..e408386 100644 --- a/internal/storage/mongo.go +++ b/internal/storage/mongo.go @@ -9,6 +9,7 @@ import ( "charm.land/huh/v2" "github.com/LazyBachelor/LazyPM/internal/models" + "github.com/LazyBachelor/LazyPM/internal/style" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" @@ -24,7 +25,7 @@ func NewMongoStorage(ctx context.Context, uri, username, password string) (*Mong Password: password, } - client, err := mongo.Connect(ctx, + client, err := mongo.Connect( options.Client().ApplyURI(uri).SetAuth(credentials)) if err != nil { @@ -44,7 +45,7 @@ func NewMongoStorageInteractive(ctx context.Context, uri string) (*MongoStorage, if err := huh.NewInput(). Title("Enter the Database Username"). Value(&username). - WithTheme(huh.ThemeBase16()).Run(); err != nil { + WithTheme(style.Base16Theme{}).Run(); err != nil { return nil, fmt.Errorf("failed to read username: %w", err) } } else { @@ -60,7 +61,7 @@ func NewMongoStorageInteractive(ctx context.Context, uri string) (*MongoStorage, Title("Enter the Survey Password"). EchoMode(huh.EchoModePassword). Value(&password). - WithTheme(huh.ThemeBase16()).Run(); err != nil { + WithTheme(style.Base16Theme{}).Run(); err != nil { return nil, fmt.Errorf("failed to read password: %w", err) } } else { @@ -110,7 +111,7 @@ func (s *MongoStorage) SubmitSurveyResponsesCmd(ctx context.Context, dir string) _, err = userStatscollection.UpdateOne(ctx, bson.M{"_id": stats.ID}, bson.M{"$set": stats}, - options.Update().SetUpsert(true)) + options.UpdateOne().SetUpsert(true)) if err != nil { return fmt.Errorf("Failed to insert stats into database: %v", err) @@ -140,7 +141,7 @@ func (s *MongoStorage) SubmitSurveyResponsesCmd(ctx context.Context, dir string) } _, err = taskMetricsCollection.UpdateOne(ctx, bson.M{"_id": metrics.ID}, bson.M{"$set": metrics}, - options.Update().SetUpsert(true)) + options.UpdateOne().SetUpsert(true)) if err != nil { fmt.Printf("failed to insert metrics from %s: %v", file, err) diff --git a/internal/style/themes.go b/internal/style/themes.go index d677340..c0e4d5f 100644 --- a/internal/style/themes.go +++ b/internal/style/themes.go @@ -5,10 +5,44 @@ import ( "charm.land/lipgloss/v2" ) -func HuhCenterTheme() *huh.Styles { - theme := huh.ThemeBase16(true) +// BaseTheme wraps huh.ThemeBase for use as a huh.Theme +type BaseTheme struct{} - theme.Focused.Base = lipgloss.NewStyle().Align(lipgloss.Center) +func (t BaseTheme) Theme(isDark bool) *huh.Styles { + return huh.ThemeBase(isDark) +} + +// Base16Theme wraps huh.ThemeBase16 for use as a huh.Theme +type Base16Theme struct{} + +func (t Base16Theme) Theme(isDark bool) *huh.Styles { + return huh.ThemeBase16(isDark) +} + +// CenterTheme is a theme that centers form content while preserving ThemeBase16 styling +type CenterTheme struct{} + +func (t CenterTheme) Theme(isDark bool) *huh.Styles { + theme := huh.ThemeBase16(isDark) + // Center the field content + theme.Focused.Base = theme.Focused.Base.Align(lipgloss.Center) + theme.Focused.Card = lipgloss.NewStyle() // Remove card border + theme.Group.Base = lipgloss.NewStyle() // Remove group padding return theme } + +// HuhCenterTheme returns a center-aligned theme for questionnaires +func HuhCenterTheme() huh.Theme { + return CenterTheme{} +} + +// HuhBaseTheme returns a base theme +func HuhBaseTheme() huh.Theme { + return BaseTheme{} +} + +// HuhBase16Theme returns a base16 theme +func HuhBase16Theme() huh.Theme { + return Base16Theme{} +} diff --git a/pkg/task/questionnaire.go b/pkg/task/questionnaire.go index b94e318..ec0e21b 100644 --- a/pkg/task/questionnaire.go +++ b/pkg/task/questionnaire.go @@ -20,7 +20,9 @@ type QuestionnaireModel struct { func NewQuestionnaireModel(questions Questions, keys []string) *QuestionnaireModel { form := huh.NewForm(questions...). - WithTheme(style.HuhCenterTheme()).WithLayout(huh.LayoutGrid(1, 1)) + WithTheme(style.HuhCenterTheme()). + WithLayout(huh.LayoutGrid(1, 1)). + WithWidth(80) return &QuestionnaireModel{ Questions: questions, @@ -37,7 +39,9 @@ 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: + // Update form width to match window + q.form.WithWidth(msg.Width) + case tea.KeyPressMsg: switch msg.String() { case "q", "ctrl+c": q.userQuit = true @@ -57,14 +61,18 @@ func (q *QuestionnaireModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return q, cmd } -func (q *QuestionnaireModel) View() string { +func (q *QuestionnaireModel) View() tea.View { form := lipgloss.NewStyle(). Width(q.width).Align(lipgloss.Center). Render(q.form.View()) - return lipgloss.Place( + content := lipgloss.Place( q.width, q.height, lipgloss.Center, lipgloss.Center, form, ) + + v := tea.NewView(content) + v.AltScreen = true + return v } func (q *QuestionnaireModel) SetSize(width, height int) { diff --git a/pkg/task/runner.go b/pkg/task/runner.go index a87ca7b..5aeae15 100644 --- a/pkg/task/runner.go +++ b/pkg/task/runner.go @@ -157,7 +157,7 @@ func (r *RunLifecycle) Finish(ctx context.Context, runErr error) error { } func runIntro(details models.TaskDetails) error { - model, err := tea.NewProgram(NewTaskModel(details), tea.WithAltScreen()).Run() + model, err := tea.NewProgram(NewTaskModel(details)).Run() if err != nil { return err } @@ -179,7 +179,7 @@ func runQuestionnaire(t Tasker, iType InterfaceType, collector *taskRunCollector keys = provider.QuestionnaireKeys(iType) } - model, err := tea.NewProgram(NewQuestionnaireModel(questions, keys), tea.WithAltScreen()).Run() + model, err := tea.NewProgram(NewQuestionnaireModel(questions, keys)).Run() if err != nil { return err } diff --git a/pkg/task/taskui.go b/pkg/task/taskui.go index 0a5fbff..d5fb921 100644 --- a/pkg/task/taskui.go +++ b/pkg/task/taskui.go @@ -57,7 +57,7 @@ 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: + case tea.KeyPressMsg: switch { case key.Matches(msg, m.keys.Quit): m.userQuit = true @@ -73,10 +73,13 @@ func (m TaskModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } -func (m TaskModel) View() string { +func (m TaskModel) View() tea.View { if m.width < 55 || m.height < 16 { - return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, + content := lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, style.TextStyle.Render("Terminal too small.")) + v := tea.NewView(content) + v.AltScreen = true + return v } boxWidth := min(m.width-10, 120) @@ -111,7 +114,9 @@ func (m TaskModel) View() string { final := lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, b.String()) - return final + v := tea.NewView(final) + v.AltScreen = true + return v } func (m *TaskModel) SetSize(width, height int) { diff --git a/pkg/tui/components/header.go b/pkg/tui/components/header.go index 62bde83..c5d625b 100644 --- a/pkg/tui/components/header.go +++ b/pkg/tui/components/header.go @@ -21,7 +21,6 @@ func (h Header) View(width int) string { lipgloss.Left, title, lipgloss.WithWhitespaceChars("─"), - lipgloss.WithWhitespaceForeground(styles.Primary), ) } diff --git a/pkg/tui/components/issue_detail.go b/pkg/tui/components/issue_detail.go index 5a3430d..11def7a 100644 --- a/pkg/tui/components/issue_detail.go +++ b/pkg/tui/components/issue_detail.go @@ -17,7 +17,7 @@ type IssueDetail struct { } func NewIssueDetail() IssueDetail { - vp := viewport.New(0, 0) + vp := viewport.New(viewport.WithWidth(0), viewport.WithHeight(0)) return IssueDetail{ viewport: vp, } @@ -35,8 +35,7 @@ func (i *IssueDetail) SetComments(comments []*models.Comment) { } func (i *IssueDetail) SetSize(width, height int) { - i.viewport.Height = height - i.viewport.Width = width + i.viewport = viewport.New(viewport.WithWidth(width), viewport.WithHeight(height)) i.refreshContent() } @@ -102,19 +101,21 @@ func formatCommentTime(t time.Time) string { func (i IssueDetail) View() string { content := i.viewport.View() + vpWidth := i.viewport.Width() + vpHeight := i.viewport.Height() if i.focused { return styles.DetailsContainerStyle. BorderForeground(styles.PrimaryBorder). - Width(i.viewport.Width). - Height(i.viewport.Height). - MaxHeight(i.viewport.Height). + Width(vpWidth). + Height(vpHeight). + MaxHeight(vpHeight). Render(content) } return styles.DetailsContainerStyle. - Width(i.viewport.Width). - Height(i.viewport.Height). - MaxHeight(i.viewport.Height). + Width(vpWidth). + Height(vpHeight). + MaxHeight(vpHeight). Render(content) } diff --git a/pkg/tui/components/issue_list.go b/pkg/tui/components/issue_list.go index 47d63b7..087a5c3 100644 --- a/pkg/tui/components/issue_list.go +++ b/pkg/tui/components/issue_list.go @@ -177,10 +177,6 @@ func NewIssueList(app *app.App, width, height int) IssueList { l.SetShowHelp(false) l.SetShowStatusBar(false) l.SetFilteringEnabled(true) - l.FilterInput.PromptStyle = styles.FilterPromptStyle - l.FilterInput.Cursor.Style = styles.FilterStyle - l.FilterInput.TextStyle = styles.FilterInputStyle - l.FilterInput.Prompt = "🔍 " return IssueList{ list: l, @@ -208,10 +204,6 @@ func NewIssueListFromIssues(app *app.App, issues []*models.Issue, width, height l.SetShowHelp(false) l.SetShowStatusBar(false) l.SetFilteringEnabled(true) - l.FilterInput.PromptStyle = styles.FilterPromptStyle - l.FilterInput.Cursor.Style = styles.FilterStyle - l.FilterInput.TextStyle = styles.FilterInputStyle - l.FilterInput.Prompt = "🔍 " return IssueList{ list: l, app: app, diff --git a/pkg/tui/styles/styles.go b/pkg/tui/styles/styles.go index 5e6e70f..4f2af0f 100644 --- a/pkg/tui/styles/styles.go +++ b/pkg/tui/styles/styles.go @@ -1,23 +1,26 @@ package styles -import "charm.land/lipgloss/v2" +import ( + "charm.land/lipgloss/v2" + "charm.land/lipgloss/v2/compat" +) var ( - Primary = lipgloss.AdaptiveColor{Light: "#5A56E0", Dark: "#7571F9"} - Secondary = lipgloss.AdaptiveColor{Light: "#02BA84", Dark: "#02BF87"} + Primary = compat.AdaptiveColor{Light: lipgloss.Color("#5A56E0"), Dark: lipgloss.Color("#7571F9")} + Secondary = compat.AdaptiveColor{Light: lipgloss.Color("#02BA84"), Dark: lipgloss.Color("#02BF87")} - Success = lipgloss.AdaptiveColor{Light: "#02BA84", Dark: "#02BF87"} - Warning = lipgloss.AdaptiveColor{Light: "#F59E0B", Dark: "#F59E0B"} - Error = lipgloss.AdaptiveColor{Light: "#FE5F86", Dark: "#FE5F86"} + Success = compat.AdaptiveColor{Light: lipgloss.Color("#02BA84"), Dark: lipgloss.Color("#02BF87")} + Warning = compat.AdaptiveColor{Light: lipgloss.Color("#F59E0B"), Dark: lipgloss.Color("#F59E0B")} + Error = compat.AdaptiveColor{Light: lipgloss.Color("#FE5F86"), Dark: lipgloss.Color("#FE5F86")} - PrimaryText = lipgloss.AdaptiveColor{Light: "#1A1A1A", Dark: "#E0E0E0"} - SecondaryText = lipgloss.AdaptiveColor{Light: "#666666", Dark: "#999999"} - FaintText = lipgloss.AdaptiveColor{Light: "#999999", Dark: "#666666"} + PrimaryText = compat.AdaptiveColor{Light: lipgloss.Color("#1A1A1A"), Dark: lipgloss.Color("#E0E0E0")} + SecondaryText = compat.AdaptiveColor{Light: lipgloss.Color("#666666"), Dark: lipgloss.Color("#999999")} + FaintText = compat.AdaptiveColor{Light: lipgloss.Color("#999999"), Dark: lipgloss.Color("#666666")} - PrimaryBorder = lipgloss.AdaptiveColor{Light: "#5A56E0", Dark: "#7571F9"} - SecondaryBorder = lipgloss.AdaptiveColor{Light: "#CCCCCC", Dark: "#444444"} + PrimaryBorder = compat.AdaptiveColor{Light: lipgloss.Color("#5A56E0"), Dark: lipgloss.Color("#7571F9")} + SecondaryBorder = compat.AdaptiveColor{Light: lipgloss.Color("#CCCCCC"), Dark: lipgloss.Color("#444444")} - SelectedBackground = lipgloss.AdaptiveColor{Light: "#E8E8E8", Dark: "#333333"} + SelectedBackground = compat.AdaptiveColor{Light: lipgloss.Color("#E8E8E8"), Dark: lipgloss.Color("#333333")} ) const ( diff --git a/pkg/tui/tui.go b/pkg/tui/tui.go index 34d4377..faafc0a 100644 --- a/pkg/tui/tui.go +++ b/pkg/tui/tui.go @@ -30,8 +30,7 @@ func (t *Tui) Run(ctx context.Context, config Config) error { defer cleanup() - p := tea.NewProgram(views.NewRootView(app, t.feedbackChan, t.quitChan, t.submitChan), - tea.WithAltScreen(), tea.WithMouseAllMotion()) + p := tea.NewProgram(views.NewRootView(app, t.feedbackChan, t.quitChan, t.submitChan)) if t.quitChan != nil { go func() { diff --git a/pkg/tui/views/dashboard/keys.go b/pkg/tui/views/dashboard/keys.go index 9c7c1b3..0d159e3 100644 --- a/pkg/tui/views/dashboard/keys.go +++ b/pkg/tui/views/dashboard/keys.go @@ -79,7 +79,7 @@ var defaultDashboardKeyMap = DashboardKeyMap{ ), } -func (d *Model) handleKeyMsg(msg tea.KeyMsg) tea.Cmd { +func (d *Model) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd { var cmd tea.Cmd switch { diff --git a/pkg/tui/views/dashboard/operations.go b/pkg/tui/views/dashboard/operations.go index 64fc0e6..2db5467 100644 --- a/pkg/tui/views/dashboard/operations.go +++ b/pkg/tui/views/dashboard/operations.go @@ -322,7 +322,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return issues.SelectIssueMsg{IssueID: selectedIssue.ID} }) - case tea.KeyMsg: + case tea.KeyPressMsg: if m.confirmingDelete { switch msg.String() { case "y", "Y": @@ -619,7 +619,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } - cmd := m.handleKeyMsg(msg) + cmd := m.handleKeyPressMsg(msg) if cmd != nil { return m, cmd } diff --git a/pkg/tui/views/dashboard/view.go b/pkg/tui/views/dashboard/view.go index b26b33a..569cff7 100644 --- a/pkg/tui/views/dashboard/view.go +++ b/pkg/tui/views/dashboard/view.go @@ -1,15 +1,16 @@ package dashboard import ( + "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" "github.com/LazyBachelor/LazyPM/pkg/tui/components" "github.com/LazyBachelor/LazyPM/pkg/tui/styles" ) -func (m *Model) View() string { +func (m *Model) View() tea.View { if m.width == 0 || m.height == 0 { // if there is no space just print a loading message - return "Loading..." + return tea.NewView("Loading...") } m.helpBar.SetWidth(m.width) @@ -63,7 +64,7 @@ func (m *Model) View() string { if m.editingAssignee { editBoxWidth := min(60, m.width-4) - m.assigneeInput.Width = editBoxWidth - 2 + m.assigneeInput.SetWidth(editBoxWidth - 2) editContent := lipgloss.JoinVertical(lipgloss.Left, styles.LabelStyle.Render("Edit assignee (Enter to save, Esc to cancel):"), m.assigneeInput.View(), @@ -72,12 +73,12 @@ func (m *Model) View() string { Width(editBoxWidth). BorderForeground(styles.PrimaryBorder). Render(editContent) - return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, editBox) + return tea.NewView(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, editBox)) } if m.editingTitle { editBoxWidth := min(60, m.width-4) - m.titleInput.Width = editBoxWidth - 2 + m.titleInput.SetWidth(editBoxWidth - 2) editContent := lipgloss.JoinVertical(lipgloss.Left, styles.LabelStyle.Render("Edit title (Enter to save, Esc to cancel):"), m.titleInput.View(), @@ -86,7 +87,7 @@ func (m *Model) View() string { Width(editBoxWidth). BorderForeground(styles.PrimaryBorder). Render(editContent) - return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, editBox) + return tea.NewView(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, editBox)) } if m.addingComment { @@ -101,7 +102,7 @@ func (m *Model) View() string { Width(editBoxWidth). BorderForeground(styles.PrimaryBorder). Render(editContent) - return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, editBox) + return tea.NewView(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, editBox)) } if m.editingDescription { @@ -116,12 +117,12 @@ func (m *Model) View() string { Width(editBoxWidth). BorderForeground(styles.PrimaryBorder). Render(editContent) - return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, editBox) + return tea.NewView(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, editBox)) } if m.creatingIssue { createBoxWidth := min(60, m.width-4) - m.createTitleInput.Width = createBoxWidth - 2 + m.createTitleInput.SetWidth(createBoxWidth - 2) createContent := lipgloss.JoinVertical(lipgloss.Left, styles.LabelStyle.Render("New issue (Enter to create, Esc to cancel):"), m.createTitleInput.View(), @@ -130,7 +131,7 @@ func (m *Model) View() string { Width(createBoxWidth). BorderForeground(styles.PrimaryBorder). Render(createContent) - return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, createBox) + return tea.NewView(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, createBox)) } if m.confirmingDelete { @@ -143,7 +144,7 @@ func (m *Model) View() string { Width(confirmBoxWidth). BorderForeground(styles.PrimaryBorder). Render(confirmContent) - return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, confirmBox) + return tea.NewView(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, confirmBox)) } if m.choosingStatus { @@ -157,7 +158,7 @@ func (m *Model) View() string { Width(statusBoxWidth). BorderForeground(styles.PrimaryBorder). Render(statusContent) - return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, statusBox) + return tea.NewView(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, statusBox)) } if m.choosingPriority { @@ -171,7 +172,7 @@ func (m *Model) View() string { Width(priorityBoxWidth). BorderForeground(styles.PrimaryBorder). Render(priorityContent) - return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, priorityBox) + return tea.NewView(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, priorityBox)) } if m.choosingCloseReason { @@ -185,7 +186,7 @@ func (m *Model) View() string { Width(reasonBoxWidth). BorderForeground(styles.PrimaryBorder). Render(reasonContent) - return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, reasonBox) + return tea.NewView(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, reasonBox)) } if m.closingOtherReason { @@ -200,7 +201,7 @@ func (m *Model) View() string { Width(editBoxWidth). BorderForeground(styles.PrimaryBorder). Render(editContent) - return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, editBox) + return tea.NewView(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, editBox)) } if m.choosingType { @@ -214,9 +215,9 @@ func (m *Model) View() string { Width(typeBoxWidth). BorderForeground(styles.PrimaryBorder). Render(typeContent) - return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, typeBox) + return tea.NewView(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, typeBox)) } - return mainView + return tea.NewView(mainView) } diff --git a/pkg/tui/views/kanban/keys.go b/pkg/tui/views/kanban/keys.go index 03ca4e1..bd78dbe 100644 --- a/pkg/tui/views/kanban/keys.go +++ b/pkg/tui/views/kanban/keys.go @@ -45,7 +45,7 @@ var defaultKanbanKeyMap = KanbanKeyMap{ ), } -func (d *Model) handleKeyMsg(msg tea.KeyMsg) tea.Cmd { +func (d *Model) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd { var cmd tea.Cmd switch { diff --git a/pkg/tui/views/kanban/operations.go b/pkg/tui/views/kanban/operations.go index 30971f3..144e144 100644 --- a/pkg/tui/views/kanban/operations.go +++ b/pkg/tui/views/kanban/operations.go @@ -258,7 +258,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return issues.SelectIssueMsg{IssueID: selectedIssue.ID} }) - case tea.KeyMsg: + case tea.KeyPressMsg: if m.confirmingDelete { switch msg.String() { case "y", "Y": @@ -499,7 +499,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } - cmd := m.handleKeyMsg(msg) + cmd := m.handleKeyPressMsg(msg) if cmd != nil { return m, cmd } diff --git a/pkg/tui/views/kanban/view.go b/pkg/tui/views/kanban/view.go index 3f4ec43..4b0d38e 100644 --- a/pkg/tui/views/kanban/view.go +++ b/pkg/tui/views/kanban/view.go @@ -1,15 +1,16 @@ package kanban import ( + "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" "github.com/LazyBachelor/LazyPM/pkg/tui/components" "github.com/LazyBachelor/LazyPM/pkg/tui/styles" ) -func (m *Model) View() string { +func (m *Model) View() tea.View { if m.width == 0 || m.height == 0 { // if there is no space just print a loading message - return "Loading..." + return tea.NewView("Loading...") } m.helpBar.SetWidth(m.width) @@ -93,7 +94,7 @@ func (m *Model) View() string { Width(reasonBoxWidth). BorderForeground(styles.PrimaryBorder). Render(reasonContent) - return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, reasonBox) + return tea.NewView(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, reasonBox)) } if m.closingOtherReason { @@ -108,10 +109,10 @@ func (m *Model) View() string { Width(editBoxWidth). BorderForeground(styles.PrimaryBorder). Render(editContent) - return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, editBox) + return tea.NewView(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, editBox)) } - return components.RenderModals( + return tea.NewView(components.RenderModals( m.width, m.height, m.editingTitle, @@ -131,7 +132,7 @@ func (m *Model) View() string { m.editingAssignee, m.assigneeInput.View(), mainView, - ) + )) } diff --git a/pkg/tui/views/root.go b/pkg/tui/views/root.go index 75b91d6..ea51efa 100644 --- a/pkg/tui/views/root.go +++ b/pkg/tui/views/root.go @@ -81,6 +81,6 @@ func (r *RootModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return r, cmd } -func (r *RootModel) View() string { +func (r *RootModel) View() tea.View { return r.currentView.View() } diff --git a/pkg/web/web.go b/pkg/web/web.go index f49e819..42fcd3a 100644 --- a/pkg/web/web.go +++ b/pkg/web/web.go @@ -54,7 +54,7 @@ func (m tuiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.width = msg.Width m.height = msg.Height return m, nil - case tea.KeyMsg: + case tea.KeyPressMsg: switch msg.String() { case "q", "esc", "ctrl+c": select { @@ -69,11 +69,14 @@ func (m tuiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } -func (m tuiModel) View() string { - return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, fmt.Sprintf( +func (m tuiModel) View() tea.View { + content := lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, fmt.Sprintf( "Web server running at %s\n\nPress q, esc, or Ctrl+C to stop the task and server.\n", m.address, )) + v := tea.NewView(content) + v.AltScreen = true + return v } func (w *Web) Run(ctx context.Context, config Config) error { @@ -106,7 +109,6 @@ func (w *Web) Run(ctx context.Context, config Config) error { address: address, quitChan: uiQuitChan, }, - tea.WithAltScreen(), ) screenDone = make(chan struct{})