From 84c66e2f1ec6c6b957475089033eeff9d2fda429 Mon Sep 17 00:00:00 2001 From: Engin Date: Sat, 29 Jun 2024 14:14:34 +0300 Subject: [PATCH] Make the code better --- internal/terminal/handler/error/error.go | 52 +++--- .../handler/ghrepository/ghrepository.go | 148 +++++++++--------- .../terminal/handler/ghtrigger/ghtrigger.go | 72 ++++----- .../terminal/handler/ghworkflow/ghworkflow.go | 20 +-- .../ghworkflowhistory/ghworkflowhistory.go | 70 +++++---- internal/terminal/handler/handler.go | 44 +++--- internal/terminal/handler/header/header.go | 79 ++++------ .../handler/information/information.go | 12 +- .../terminal/handler/taboptions/taboptions.go | 142 ++++++++--------- internal/terminal/handler/types/types.go | 37 +++++ pkg/workflow/workflow.go | 2 - 11 files changed, 342 insertions(+), 336 deletions(-) diff --git a/internal/terminal/handler/error/error.go b/internal/terminal/handler/error/error.go index fdecb07..79a69a5 100644 --- a/internal/terminal/handler/error/error.go +++ b/internal/terminal/handler/error/error.go @@ -43,6 +43,32 @@ func SetupModelError() ModelError { } } +func (m *ModelError) View() string { + var windowStyle = lipgloss.NewStyle().BorderStyle(lipgloss.RoundedBorder()) + + doc := strings.Builder{} + if m.HaveError() { + windowStyle = ts.WindowStyleError.Width(*hdltypes.ScreenWidth) + doc.WriteString(windowStyle.Render(m.ViewError())) + return doc.String() + } + + switch m.messageType { + case MessageTypeDefault: + windowStyle = ts.WindowStyleDefault.Width(*hdltypes.ScreenWidth) + case MessageTypeProgress: + windowStyle = ts.WindowStyleProgress.Width(*hdltypes.ScreenWidth) + case MessageTypeSuccess: + windowStyle = ts.WindowStyleSuccess.Width(*hdltypes.ScreenWidth) + default: + windowStyle = ts.WindowStyleDefault.Width(*hdltypes.ScreenWidth) + } + + doc.WriteString(windowStyle.Render(m.ViewMessage())) + + return doc.String() +} + func (m *ModelError) SetError(err error) { m.err = err } @@ -107,29 +133,3 @@ func (m *ModelError) ViewMessage() string { doc.WriteString(m.message) return doc.String() } - -func (m *ModelError) View() string { - var windowStyle = lipgloss.NewStyle().BorderStyle(lipgloss.RoundedBorder()) - - doc := strings.Builder{} - if m.HaveError() { - windowStyle = ts.WindowStyleError.Width(*hdltypes.ScreenWidth) - doc.WriteString(windowStyle.Render(m.ViewError())) - return doc.String() - } - - switch m.messageType { - case MessageTypeDefault: - windowStyle = ts.WindowStyleDefault.Width(*hdltypes.ScreenWidth) - case MessageTypeProgress: - windowStyle = ts.WindowStyleProgress.Width(*hdltypes.ScreenWidth) - case MessageTypeSuccess: - windowStyle = ts.WindowStyleSuccess.Width(*hdltypes.ScreenWidth) - default: - windowStyle = ts.WindowStyleDefault.Width(*hdltypes.ScreenWidth) - } - - doc.WriteString(windowStyle.Render(m.ViewMessage())) - - return doc.String() -} diff --git a/internal/terminal/handler/ghrepository/ghrepository.go b/internal/terminal/handler/ghrepository/ghrepository.go index 30204b1..1b3c599 100644 --- a/internal/terminal/handler/ghrepository/ghrepository.go +++ b/internal/terminal/handler/ghrepository/ghrepository.go @@ -44,8 +44,7 @@ type ModelGithubRepository struct { searchTableGithubRepository table.Model modelError *hdlerror.ModelError - modelTabOptions tea.Model - actualModelTabOptions *taboptions.Options + modelTabOptions *taboptions.Options textInput textinput.Model } @@ -54,7 +53,7 @@ var baseStyle = lipgloss.NewStyle(). BorderStyle(lipgloss.NormalBorder()). BorderForeground(lipgloss.Color("240")) -func SetupModelGithubRepository(viewport *viewport.Model, githubUseCase gu.UseCase, selectedRepository *hdltypes.SelectedRepository) *ModelGithubRepository { +func SetupModelGithubRepository(githubUseCase gu.UseCase) *ModelGithubRepository { var tableRowsGithubRepository []table.Row tableGithubRepository := table.New( @@ -114,15 +113,14 @@ func SetupModelGithubRepository(viewport *viewport.Model, githubUseCase gu.UseCa tabOptions := taboptions.NewOptions(&modelError) return &ModelGithubRepository{ - Viewport: viewport, + Viewport: hdltypes.NewTerminalViewport(), Help: help.New(), Keys: keys, github: githubUseCase, tableGithubRepository: tableGithubRepository, modelError: &modelError, - SelectedRepository: selectedRepository, + SelectedRepository: hdltypes.NewSelectedRepository(), modelTabOptions: tabOptions, - actualModelTabOptions: tabOptions, textInput: ti, syncRepositoriesContext: context.Background(), cancelSyncRepositories: func() {}, @@ -145,75 +143,9 @@ func (m *ModelGithubRepository) Init() tea.Cmd { m.modelError.SetSuccessMessage("Opened in browser") } - m.actualModelTabOptions.AddOption("Open in browser", openInBrowser) + m.modelTabOptions.AddOption("Open in browser", openInBrowser) - return nil -} - -func (m *ModelGithubRepository) syncRepositories(ctx context.Context) { - m.modelError.ResetError() // reset previous errors - m.actualModelTabOptions.SetStatus(taboptions.OptionWait) - m.modelError.SetProgressMessage("Fetching repositories...") - - // delete all rows - m.tableGithubRepository.SetRows([]table.Row{}) - m.searchTableGithubRepository.SetRows([]table.Row{}) - - repositories, err := m.github.ListRepositories(ctx, gu.ListRepositoriesInput{ - Limit: 100, // limit to 100 repositories - Page: 5, // page 1 to page 5, at summary we fetch 500 repositories - Sort: domain.SortByUpdated, - }) - if errors.Is(err, context.Canceled) { - return - } else if err != nil { - m.modelError.SetError(err) - m.modelError.SetErrorMessage("Repositories cannot be listed") - return - } - - if len(repositories.Repositories) == 0 { - m.actualModelTabOptions.SetStatus(taboptions.OptionNone) - m.modelError.SetDefaultMessage("No repositories found") - m.textInput.Blur() - return - } - - tableRowsGithubRepository := make([]table.Row, 0, len(repositories.Repositories)) - for _, repository := range repositories.Repositories { - tableRowsGithubRepository = append(tableRowsGithubRepository, - table.Row{repository.Name, repository.DefaultBranch, strconv.Itoa(repository.Stars), strconv.Itoa(len(repository.Workflows))}) - } - - m.tableGithubRepository.SetRows(tableRowsGithubRepository) - m.searchTableGithubRepository.SetRows(tableRowsGithubRepository) - - // set cursor to 0 - m.tableGithubRepository.SetCursor(0) - m.searchTableGithubRepository.SetCursor(0) - - m.tableReady = true - //m.updateSearchBarSuggestions() - m.textInput.Focus() - m.modelError.SetSuccessMessage("Repositories fetched") - go m.Update(m) // update model -} - -func (m *ModelGithubRepository) handleTableInputs(_ context.Context) { - if !m.tableReady { - return - } - - // To avoid go routine leak - selectedRow := m.tableGithubRepository.SelectedRow() - - // Synchronize selected repository name with parent model - if len(selectedRow) > 0 && selectedRow[0] != "" { - m.SelectedRepository.RepositoryName = selectedRow[0] - m.SelectedRepository.BranchName = selectedRow[1] - } - - m.actualModelTabOptions.SetStatus(taboptions.OptionIdle) + return tea.Batch(m.modelTabOptions.Init()) } func (m *ModelGithubRepository) Update(msg tea.Msg) (*ModelGithubRepository, tea.Cmd) { @@ -278,7 +210,73 @@ func (m *ModelGithubRepository) View() string { doc := strings.Builder{} doc.WriteString(baseStyle.Render(m.tableGithubRepository.View())) - return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.viewSearchBar(), m.actualModelTabOptions.View()) + return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.viewSearchBar(), m.modelTabOptions.View()) +} + +func (m *ModelGithubRepository) syncRepositories(ctx context.Context) { + m.modelError.ResetError() // reset previous errors + m.modelTabOptions.SetStatus(taboptions.OptionWait) + m.modelError.SetProgressMessage("Fetching repositories...") + + // delete all rows + m.tableGithubRepository.SetRows([]table.Row{}) + m.searchTableGithubRepository.SetRows([]table.Row{}) + + repositories, err := m.github.ListRepositories(ctx, gu.ListRepositoriesInput{ + Limit: 100, // limit to 100 repositories + Page: 5, // page 1 to page 5, at summary we fetch 500 repositories + Sort: domain.SortByUpdated, + }) + if errors.Is(err, context.Canceled) { + return + } else if err != nil { + m.modelError.SetError(err) + m.modelError.SetErrorMessage("Repositories cannot be listed") + return + } + + if len(repositories.Repositories) == 0 { + m.modelTabOptions.SetStatus(taboptions.OptionNone) + m.modelError.SetDefaultMessage("No repositories found") + m.textInput.Blur() + return + } + + tableRowsGithubRepository := make([]table.Row, 0, len(repositories.Repositories)) + for _, repository := range repositories.Repositories { + tableRowsGithubRepository = append(tableRowsGithubRepository, + table.Row{repository.Name, repository.DefaultBranch, strconv.Itoa(repository.Stars), strconv.Itoa(len(repository.Workflows))}) + } + + m.tableGithubRepository.SetRows(tableRowsGithubRepository) + m.searchTableGithubRepository.SetRows(tableRowsGithubRepository) + + // set cursor to 0 + m.tableGithubRepository.SetCursor(0) + m.searchTableGithubRepository.SetCursor(0) + + m.tableReady = true + //m.updateSearchBarSuggestions() + m.textInput.Focus() + m.modelError.SetSuccessMessage("Repositories fetched") + go m.Update(m) // update model +} + +func (m *ModelGithubRepository) handleTableInputs(_ context.Context) { + if !m.tableReady { + return + } + + // To avoid go routine leak + selectedRow := m.tableGithubRepository.SelectedRow() + + // Synchronize selected repository name with parent model + if len(selectedRow) > 0 && selectedRow[0] != "" { + m.SelectedRepository.RepositoryName = selectedRow[0] + m.SelectedRepository.BranchName = selectedRow[1] + } + + m.modelTabOptions.SetStatus(taboptions.OptionIdle) } func (m *ModelGithubRepository) viewSearchBar() string { diff --git a/internal/terminal/handler/ghtrigger/ghtrigger.go b/internal/terminal/handler/ghtrigger/ghtrigger.go index 24e4177..c0d5a5f 100644 --- a/internal/terminal/handler/ghtrigger/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger/ghtrigger.go @@ -4,11 +4,6 @@ import ( "context" "errors" "fmt" - "github.com/termkit/gama/internal/terminal/handler/header" - "slices" - "strings" - "time" - "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/table" "github.com/charmbracelet/bubbles/textinput" @@ -17,25 +12,29 @@ import ( "github.com/charmbracelet/lipgloss" gu "github.com/termkit/gama/internal/github/usecase" hdlerror "github.com/termkit/gama/internal/terminal/handler/error" + "github.com/termkit/gama/internal/terminal/handler/ghworkflowhistory" + "github.com/termkit/gama/internal/terminal/handler/header" hdltypes "github.com/termkit/gama/internal/terminal/handler/types" "github.com/termkit/gama/pkg/workflow" + "slices" + "strings" + "time" ) type ModelGithubTrigger struct { // current handler's properties - syncWorkflowContext context.Context - cancelSyncWorkflow context.CancelFunc - workflowContent *workflow.Pretty - tableReady bool - isTriggerable bool - forceUpdateWorkflowHistory *bool - optionInit bool - optionCursor int - optionValues []string - currentOption string - selectedWorkflow string - selectedRepositoryName string - triggerFocused bool + syncWorkflowContext context.Context + cancelSyncWorkflow context.CancelFunc + workflowContent *workflow.Pretty + tableReady bool + isTriggerable bool + optionInit bool + optionCursor int + optionValues []string + currentOption string + selectedWorkflow string + selectedRepositoryName string + triggerFocused bool // shared properties SelectedRepository *hdltypes.SelectedRepository @@ -56,7 +55,7 @@ type ModelGithubTrigger struct { tableTrigger table.Model } -func SetupModelGithubTrigger(viewport *viewport.Model, githubUseCase gu.UseCase, selectedRepository *hdltypes.SelectedRepository, forceUpdateWorkflowHistory *bool) *ModelGithubTrigger { +func SetupModelGithubTrigger(githubUseCase gu.UseCase) *ModelGithubTrigger { var tableRowsTrigger []table.Row tableTrigger := table.New( @@ -83,24 +82,23 @@ func SetupModelGithubTrigger(viewport *viewport.Model, githubUseCase gu.UseCase, ti.CharLimit = 72 return &ModelGithubTrigger{ - Viewport: viewport, - header: header.NewHeader(viewport), - forceUpdateWorkflowHistory: forceUpdateWorkflowHistory, - Help: help.New(), - Keys: keys, - github: githubUseCase, - SelectedRepository: selectedRepository, - modelError: hdlerror.SetupModelError(), - tableTrigger: tableTrigger, - textInput: ti, - syncWorkflowContext: context.Background(), - cancelSyncWorkflow: func() {}, + Viewport: hdltypes.NewTerminalViewport(), + header: header.NewHeader(), + Help: help.New(), + Keys: keys, + github: githubUseCase, + SelectedRepository: hdltypes.NewSelectedRepository(), + modelError: hdlerror.SetupModelError(), + tableTrigger: tableTrigger, + textInput: ti, + syncWorkflowContext: context.Background(), + cancelSyncWorkflow: func() {}, } } func (m *ModelGithubTrigger) Init() tea.Cmd { m.modelError.SetDefaultMessage("No workflow contents found.") - return textinput.Blink + return tea.Batch(textinput.Blink) } func (m *ModelGithubTrigger) Update(msg tea.Msg) (*ModelGithubTrigger, tea.Cmd) { @@ -172,6 +170,9 @@ func (m *ModelGithubTrigger) Update(msg tea.Msg) (*ModelGithubTrigger, tea.Cmd) case "enter", tea.KeyEnter.String(): if m.triggerFocused && m.isTriggerable { go m.triggerWorkflow() + return m, func() tea.Msg { + return ghworkflowhistory.UpdateWorkflowHistoryMsg{UpdateAfter: time.Second * 5} + } } } } @@ -601,7 +602,7 @@ func (m *ModelGithubTrigger) triggerWorkflow() { time.Sleep(1 * time.Second) m.modelError.SetProgressMessage("Switching to workflow history tab...") - time.Sleep(1 * time.Second) + time.Sleep(1500 * time.Millisecond) // move these operations under new function named "resetTabSettings" m.workflowContent = nil // reset workflow content @@ -610,11 +611,6 @@ func (m *ModelGithubTrigger) triggerWorkflow() { m.optionValues = nil // reset option values m.selectedRepositoryName = "" // reset selected repository name - go func() { - time.Sleep(1 * time.Second) - *m.forceUpdateWorkflowHistory = true // force update workflow history - }() - m.header.SetCurrentTab(2) // switch tab to workflow history } diff --git a/internal/terminal/handler/ghworkflow/ghworkflow.go b/internal/terminal/handler/ghworkflow/ghworkflow.go index 9b351ff..35ce66e 100644 --- a/internal/terminal/handler/ghworkflow/ghworkflow.go +++ b/internal/terminal/handler/ghworkflow/ghworkflow.go @@ -11,7 +11,6 @@ import ( "github.com/charmbracelet/bubbles/table" hdlerror "github.com/termkit/gama/internal/terminal/handler/error" "github.com/termkit/gama/internal/terminal/handler/ghtrigger" - "github.com/termkit/gama/internal/terminal/handler/taboptions" hdltypes "github.com/termkit/gama/internal/terminal/handler/types" "github.com/charmbracelet/bubbles/list" @@ -44,14 +43,10 @@ type ModelGithubWorkflow struct { tableTriggerableWorkflow table.Model modelError *hdlerror.ModelError - modelTabOptions tea.Model - actualModelTabOptions *taboptions.Options - - modelGithubTrigger tea.Model - actualModelGithubTrigger *ghtrigger.ModelGithubTrigger + modelGithubTrigger *ghtrigger.ModelGithubTrigger } -func SetupModelGithubWorkflow(viewport *viewport.Model, githubUseCase gu.UseCase, selectedRepository *hdltypes.SelectedRepository) *ModelGithubWorkflow { +func SetupModelGithubWorkflow(githubUseCase gu.UseCase) *ModelGithubWorkflow { var tableRowsTriggerableWorkflow []table.Row tableTriggerableWorkflow := table.New( @@ -74,18 +69,15 @@ func SetupModelGithubWorkflow(viewport *viewport.Model, githubUseCase gu.UseCase tableTriggerableWorkflow.SetStyles(s) modelError := hdlerror.SetupModelError() - tabOptions := taboptions.NewOptions(&modelError) return &ModelGithubWorkflow{ - Viewport: viewport, + Viewport: hdltypes.NewTerminalViewport(), Help: help.New(), Keys: keys, github: githubUseCase, modelError: &modelError, tableTriggerableWorkflow: tableTriggerableWorkflow, - SelectedRepository: selectedRepository, - modelTabOptions: tabOptions, - actualModelTabOptions: tabOptions, + SelectedRepository: hdltypes.NewSelectedRepository(), syncTriggerableWorkflowsContext: context.Background(), cancelSyncTriggerableWorkflows: func() {}, } @@ -147,7 +139,6 @@ func (m *ModelGithubWorkflow) syncTriggerableWorkflows(ctx context.Context) { m.modelError.Reset() m.modelError.SetProgressMessage( fmt.Sprintf("[%s@%s] Fetching triggerable workflows...", m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName)) - m.actualModelTabOptions.SetStatus(taboptions.OptionWait) // delete all rows m.tableTriggerableWorkflow.SetRows([]table.Row{}) @@ -165,7 +156,6 @@ func (m *ModelGithubWorkflow) syncTriggerableWorkflows(ctx context.Context) { } if len(triggerableWorkflows.TriggerableWorkflows) == 0 { - m.actualModelTabOptions.SetStatus(taboptions.OptionNone) m.modelError.SetDefaultMessage(fmt.Sprintf("[%s@%s] No triggerable workflow found.", m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName)) return } @@ -202,8 +192,6 @@ func (m *ModelGithubWorkflow) handleTableInputs(_ context.Context) { if len(rows) > 0 && len(selectedRow) > 0 { m.SelectedRepository.WorkflowName = selectedRow[1] } - - m.actualModelTabOptions.SetStatus(taboptions.OptionIdle) } func (m *ModelGithubWorkflow) ViewStatus() string { diff --git a/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go index dad05bb..e136e0c 100644 --- a/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "strings" + "time" "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" @@ -26,7 +27,6 @@ type ModelGithubWorkflowHistory struct { selectedWorkflowID int64 isTableFocused bool lastRepository string - forceUpdate *bool syncWorkflowHistoryContext context.Context cancelSyncWorkflowHistory context.CancelFunc Workflows []gu.Workflow @@ -46,15 +46,10 @@ type ModelGithubWorkflowHistory struct { tableWorkflowHistory table.Model modelError *hdlerror.ModelError - modelTabOptions tea.Model - actualModelTabOptions *taboptions.Options + modelTabOptions *taboptions.Options } -var baseStyle = lipgloss.NewStyle(). - BorderStyle(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("240")) - -func SetupModelGithubWorkflowHistory(viewport *viewport.Model, githubUseCase gu.UseCase, selectedRepository *hdltypes.SelectedRepository, forceUpdate *bool) *ModelGithubWorkflowHistory { +func SetupModelGithubWorkflowHistory(githubUseCase gu.UseCase) *ModelGithubWorkflowHistory { var tableRowsWorkflowHistory []table.Row tableWorkflowHistory := table.New( @@ -80,22 +75,34 @@ func SetupModelGithubWorkflowHistory(viewport *viewport.Model, githubUseCase gu. tabOptions := taboptions.NewOptions(&modelError) return &ModelGithubWorkflowHistory{ - Viewport: viewport, + Viewport: hdltypes.NewTerminalViewport(), Help: help.New(), Keys: keys, github: githubUseCase, tableWorkflowHistory: tableWorkflowHistory, modelError: &modelError, - SelectedRepository: selectedRepository, + SelectedRepository: hdltypes.NewSelectedRepository(), modelTabOptions: tabOptions, - actualModelTabOptions: tabOptions, - forceUpdate: forceUpdate, syncWorkflowHistoryContext: context.Background(), cancelSyncWorkflowHistory: func() {}, } } +type UpdateWorkflowHistoryMsg struct { + UpdateAfter time.Duration +} + func (m *ModelGithubWorkflowHistory) Init() tea.Cmd { + m.setupOptions() + return tea.Batch( + m.modelTabOptions.Init(), + func() tea.Msg { + return UpdateWorkflowHistoryMsg{UpdateAfter: time.Second * 1} + }, + ) +} + +func (m *ModelGithubWorkflowHistory) setupOptions() { openInBrowser := func() { m.modelError.SetProgressMessage("Opening in browser...") @@ -160,23 +167,10 @@ func (m *ModelGithubWorkflowHistory) Init() tea.Cmd { m.modelError.SetSuccessMessage("Canceled workflow") } - m.actualModelTabOptions.AddOption("Open in browser", openInBrowser) - m.actualModelTabOptions.AddOption("Rerun failed jobs", reRunFailedJobs) - m.actualModelTabOptions.AddOption("Rerun workflow", reRunWorkflow) - m.actualModelTabOptions.AddOption("Cancel workflow", cancelWorkflow) - - go func() { - // Make it works with to channels - for { - if *m.forceUpdate { - m.tableReady = false - go m.syncWorkflowHistory(m.syncWorkflowHistoryContext) - *m.forceUpdate = false - } - } - }() - - return tea.Batch(m.modelTabOptions.Init()) + m.modelTabOptions.AddOption("Open in browser", openInBrowser) + m.modelTabOptions.AddOption("Rerun failed jobs", reRunFailedJobs) + m.modelTabOptions.AddOption("Rerun workflow", reRunWorkflow) + m.modelTabOptions.AddOption("Cancel workflow", cancelWorkflow) } func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (*ModelGithubWorkflowHistory, tea.Cmd) { @@ -203,6 +197,12 @@ func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (*ModelGithubWorkflowHi m.tableReady = false go m.syncWorkflowHistory(m.syncWorkflowHistoryContext) } + case UpdateWorkflowHistoryMsg: + go func() { + time.Sleep(msg.UpdateAfter) + m.tableReady = false + m.syncWorkflowHistory(m.syncWorkflowHistoryContext) // TODO : may you use go routine here? + }() } m.modelTabOptions, cmd = m.modelTabOptions.Update(msg) @@ -218,7 +218,7 @@ func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { m.modelError.Reset() m.modelError.SetProgressMessage( fmt.Sprintf("[%s@%s] Fetching workflow history...", m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName)) - m.actualModelTabOptions.SetStatus(taboptions.OptionWait) + m.modelTabOptions.SetStatus(taboptions.OptionWait) // delete all rows m.tableWorkflowHistory.SetRows([]table.Row{}) @@ -239,7 +239,7 @@ func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { } if len(workflowHistory.Workflows) == 0 { - m.actualModelTabOptions.SetStatus(taboptions.OptionNone) + m.modelTabOptions.SetStatus(taboptions.OptionNone) m.modelError.SetDefaultMessage(fmt.Sprintf("[%s@%s] No workflows found.", m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName)) return } @@ -261,12 +261,16 @@ func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { m.tableReady = true m.tableWorkflowHistory.SetRows(tableRowsWorkflowHistory) m.tableWorkflowHistory.SetCursor(0) - m.actualModelTabOptions.SetStatus(taboptions.OptionIdle) + m.modelTabOptions.SetStatus(taboptions.OptionIdle) m.modelError.SetSuccessMessage(fmt.Sprintf("[%s@%s] Workflow history fetched.", m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName)) go m.Update(m) // update model } func (m *ModelGithubWorkflowHistory) View() string { + var baseStyle = lipgloss.NewStyle(). + BorderStyle(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("240")) + termWidth := m.Viewport.Width termHeight := m.Viewport.Height @@ -293,7 +297,7 @@ func (m *ModelGithubWorkflowHistory) View() string { doc := strings.Builder{} doc.WriteString(baseStyle.Render(m.tableWorkflowHistory.View())) - return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.actualModelTabOptions.View()) + return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.modelTabOptions.View()) } func (m *ModelGithubWorkflowHistory) ViewStatus() string { diff --git a/internal/terminal/handler/handler.go b/internal/terminal/handler/handler.go index dcd2167..4802aab 100644 --- a/internal/terminal/handler/handler.go +++ b/internal/terminal/handler/handler.go @@ -7,6 +7,7 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" gu "github.com/termkit/gama/internal/github/usecase" + hdlerror "github.com/termkit/gama/internal/terminal/handler/error" hdlgithubrepo "github.com/termkit/gama/internal/terminal/handler/ghrepository" hdltrigger "github.com/termkit/gama/internal/terminal/handler/ghtrigger" hdlWorkflow "github.com/termkit/gama/internal/terminal/handler/ghworkflow" @@ -17,15 +18,14 @@ import ( ts "github.com/termkit/gama/internal/terminal/style" pkgversion "github.com/termkit/gama/pkg/version" "strings" + "time" ) type model struct { - // Shared properties - SelectedRepository *hdltypes.SelectedRepository - // models viewport *viewport.Model + modelError *hdlerror.ModelError modelHeader *header.Header modelInfo *hdlinfo.ModelInfo modelGithubRepository *hdlgithubrepo.ModelGithubRepository @@ -37,36 +37,28 @@ type model struct { keys keyMap } -const ( - minTerminalWidth = 102 - minTerminalHeight = 24 -) - func SetupTerminal(githubUseCase gu.UseCase, version pkgversion.Version) tea.Model { - forceUpdateWorkflowHistory := new(bool) - vp := &viewport.Model{Width: minTerminalWidth, Height: minTerminalHeight} - selectedRepository := &hdltypes.SelectedRepository{} - // setup models - hdlModelHeader := header.NewHeader(vp) - hdlModelInfo := hdlinfo.SetupModelInfo(vp, githubUseCase, version) - hdlModelGithubRepository := hdlgithubrepo.SetupModelGithubRepository(vp, githubUseCase, selectedRepository) - hdlModelWorkflowHistory := hdlworkflowhistory.SetupModelGithubWorkflowHistory(vp, githubUseCase, selectedRepository, forceUpdateWorkflowHistory) - hdlModelWorkflow := hdlWorkflow.SetupModelGithubWorkflow(vp, githubUseCase, selectedRepository) - hdlModelTrigger := hdltrigger.SetupModelGithubTrigger(vp, githubUseCase, selectedRepository, forceUpdateWorkflowHistory) + hdlModelError := hdlerror.SetupModelError() + hdlModelHeader := header.NewHeader() + hdlModelInfo := hdlinfo.SetupModelInfo(githubUseCase, version) + hdlModelGithubRepository := hdlgithubrepo.SetupModelGithubRepository(githubUseCase) + hdlModelWorkflowHistory := hdlworkflowhistory.SetupModelGithubWorkflowHistory(githubUseCase) + hdlModelWorkflow := hdlWorkflow.SetupModelGithubWorkflow(githubUseCase) + hdlModelTrigger := hdltrigger.SetupModelGithubTrigger(githubUseCase) hdlModelHeader.AddCommonHeader("Info", ts.TitleStyleInactive, ts.TitleStyleActive) hdlModelHeader.AddCommonHeader("Repository", ts.TitleStyleInactive, ts.TitleStyleActive) hdlModelHeader.AddCommonHeader("Workflow History", ts.TitleStyleInactive, ts.TitleStyleActive) hdlModelHeader.AddCommonHeader("Workflow", ts.TitleStyleInactive, ts.TitleStyleActive) hdlModelHeader.AddCommonHeader("Trigger", ts.TitleStyleInactive, ts.TitleStyleActive) - hdlModelHeader.SetSpecialHeader("GAMA", ts.TitleStyleLiveModeOn, ts.TitleStyleLiveModeOff) + hdlModelHeader.SetSpecialHeader("GAMA", time.Millisecond*500, ts.TitleStyleLiveModeOn, ts.TitleStyleLiveModeOff, ts.TitleStyleDisabled) m := model{ - viewport: vp, + viewport: hdltypes.NewTerminalViewport(), + modelError: &hdlModelError, modelHeader: hdlModelHeader, modelInfo: hdlModelInfo, - SelectedRepository: selectedRepository, modelGithubRepository: hdlModelGithubRepository, modelWorkflowHistory: hdlModelWorkflowHistory, modelWorkflow: hdlModelWorkflow, @@ -106,22 +98,24 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.modelHeader, cmd = m.modelHeader.Update(msg) cmds = append(cmds, cmd) + cmds = append(cmds, m.handleTabContent(cmd, msg)) case hdlinfo.UpdateSpinnerMsg: m.modelInfo, cmd = m.modelInfo.Update(msg) cmds = append(cmds, cmd) case header.UpdateMsg: m.modelHeader, cmd = m.modelHeader.Update(msg) cmds = append(cmds, cmd) - default: - cmds = append(cmds, m.handleTabContent(cmd, msg)) + case hdlworkflowhistory.UpdateWorkflowHistoryMsg: + m.modelWorkflowHistory, cmd = m.modelWorkflowHistory.Update(msg) + cmds = append(cmds, cmd) } return m, tea.Batch(cmds...) } func (m *model) View() string { - if m.viewport.Width < minTerminalWidth || m.viewport.Height < minTerminalHeight { - return fmt.Sprintf("Terminal window is too small. Please resize to at least %dx%d.", minTerminalWidth, minTerminalHeight) + if m.viewport.Width < hdltypes.MinTerminalWidth || m.viewport.Height < hdltypes.MinTerminalHeight { + return fmt.Sprintf("Terminal window is too small. Please resize to at least %dx%d.", hdltypes.MinTerminalWidth, hdltypes.MinTerminalHeight) } var mainDoc strings.Builder diff --git a/internal/terminal/handler/header/header.go b/internal/terminal/handler/header/header.go index 1f9ca8a..d731bcb 100644 --- a/internal/terminal/handler/header/header.go +++ b/internal/terminal/handler/header/header.go @@ -5,6 +5,7 @@ import ( "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/termkit/gama/internal/terminal/handler/types" ts "github.com/termkit/gama/internal/terminal/style" "strings" "sync" @@ -13,19 +14,17 @@ import ( // Header is a helper for rendering the Header of the terminal. type Header struct { - keys keyMap - Viewport *viewport.Model - tickerInterval time.Duration + keys keyMap currentTab int lockTabs bool - commonHeaders []commonHeader - specialHeaders []specialHeader - - switchSpecialAnimation bool + commonHeaders []commonHeader + specialHeader specialHeader + specialHeaderInterval time.Duration + currentSpecialStyle int } type commonHeader struct { @@ -40,8 +39,7 @@ type specialHeader struct { header string rawHeader string - firstStyle lipgloss.Style - secondStyle lipgloss.Style + styles []lipgloss.Style } // Define sync.Once and NewHeader should return same instance @@ -51,14 +49,14 @@ var ( ) // NewHeader returns a new Header. -func NewHeader(viewport *viewport.Model) *Header { +func NewHeader() *Header { once.Do(func() { h = &Header{ - tickerInterval: time.Millisecond * 250, - Viewport: viewport, - currentTab: 0, - lockTabs: true, - keys: keys, + specialHeaderInterval: time.Millisecond * 100, + Viewport: types.NewTerminalViewport(), + currentTab: 0, + lockTabs: true, + keys: keys, } }) return h @@ -89,13 +87,16 @@ func (h *Header) AddCommonHeader(header string, inactiveStyle, activeStyle lipgl }) } -func (h *Header) SetSpecialHeader(header string, firstStyle, secondStyle lipgloss.Style) { - h.specialHeaders = append(h.specialHeaders, specialHeader{ - header: header, - rawHeader: header, - firstStyle: firstStyle, - secondStyle: secondStyle, - }) +func (h *Header) SetSpecialHeader(header string, interval time.Duration, styles ...lipgloss.Style) { + h.specialHeaderInterval = interval + if len(styles) == 0 { + styles = append(styles, ts.TitleStyleDisabled) + } + h.specialHeader = specialHeader{ + header: header, + rawHeader: header, + styles: styles, + } } type UpdateMsg struct { @@ -108,18 +109,8 @@ func (h *Header) Init() tea.Cmd { } func (h *Header) tick() tea.Cmd { - t := time.NewTimer(h.tickerInterval) + t := time.NewTimer(h.specialHeaderInterval) return func() tea.Msg { - //ts := <-t.C - //t.Stop() - //for len(t.C) > 0 { - // <-t.C - //} - //return UpdateMsg{ - // Msg: "tick", - // UpdatingComponent: "header", - //} - select { case <-t.C: return UpdateMsg{ @@ -131,8 +122,6 @@ func (h *Header) tick() tea.Cmd { } func (h *Header) Update(msg tea.Msg) (*Header, tea.Cmd) { - //var cmds []tea.Cmd - switch msg := msg.(type) { case tea.KeyMsg: switch { @@ -146,7 +135,13 @@ func (h *Header) Update(msg tea.Msg) (*Header, tea.Cmd) { } } case UpdateMsg: - h.switchSpecialAnimation = !h.switchSpecialAnimation + if msg.UpdatingComponent == "header" { + if h.currentSpecialStyle >= len(h.specialHeader.styles)-1 { + h.currentSpecialStyle = 0 + } else { + h.currentSpecialStyle++ + } + } return h, h.Init() } @@ -163,10 +158,7 @@ func (h *Header) View() string { titles += "LLL RRR" } titleLen := len(titles) - specialTitleLen := len(h.specialHeaders[0].header) - - var specialHeader string - specialHeader = h.specialHeaders[0].header + specialTitleLen := len(h.specialHeader.header) var renderedTitles []string for i, title := range h.commonHeaders { @@ -185,11 +177,8 @@ func (h *Header) View() string { } } - if h.switchSpecialAnimation { - specialHeader = h.specialHeaders[0].firstStyle.Render(h.specialHeaders[0].header) - } else { - specialHeader = h.specialHeaders[0].secondStyle.Render(h.specialHeaders[0].header) - } + var specialHeader string + specialHeader = h.specialHeader.styles[h.currentSpecialStyle].Render(h.specialHeader.header) line := strings.Repeat("─", h.Viewport.Width-(titleLen+specialTitleLen)) diff --git a/internal/terminal/handler/information/information.go b/internal/terminal/handler/information/information.go index 56af815..debca30 100644 --- a/internal/terminal/handler/information/information.go +++ b/internal/terminal/handler/information/information.go @@ -11,6 +11,7 @@ import ( gu "github.com/termkit/gama/internal/github/usecase" hdlerror "github.com/termkit/gama/internal/terminal/handler/error" "github.com/termkit/gama/internal/terminal/handler/header" + "github.com/termkit/gama/internal/terminal/handler/types" pkgversion "github.com/termkit/gama/pkg/version" "strings" "time" @@ -29,7 +30,7 @@ type ModelInfo struct { Help help.Model Viewport *viewport.Model - modelError hdlerror.ModelError + modelError *hdlerror.ModelError spinner spinner.Model // keymap @@ -56,22 +57,22 @@ var ( applicationDescription string ) -func SetupModelInfo(viewport *viewport.Model, githubUseCase gu.UseCase, version pkgversion.Version) *ModelInfo { +func SetupModelInfo(githubUseCase gu.UseCase, version pkgversion.Version) *ModelInfo { modelError := hdlerror.SetupModelError() - hdlModelHeader := header.NewHeader(viewport) + hdlModelHeader := header.NewHeader() s := spinner.New() s.Spinner = spinner.Dot s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("120")) return &ModelInfo{ - Viewport: viewport, + Viewport: types.NewTerminalViewport(), modelHeader: hdlModelHeader, github: githubUseCase, version: version, Help: help.New(), Keys: keys, - modelError: modelError, + modelError: &modelError, spinner: s, } } @@ -149,7 +150,6 @@ func (m *ModelInfo) View() string { } func (m *ModelInfo) testConnection(ctx context.Context) { - time.Sleep(time.Second * 5) // TODO : remove, just for testing spinner _, err := m.github.GetAuthUser(ctx) if err != nil { m.modelError.SetError(err) diff --git a/internal/terminal/handler/taboptions/taboptions.go b/internal/terminal/handler/taboptions/taboptions.go index ce9f29d..b37c6e7 100644 --- a/internal/terminal/handler/taboptions/taboptions.go +++ b/internal/terminal/handler/taboptions/taboptions.go @@ -78,72 +78,13 @@ func NewOptions(modelError *hdlerror.ModelError) *Options { } } -func (o *Options) SetStatus(status OptionStatus) { - o.status = status - o.options[0] = status.String() - o.optionsAction[0] = status.String() -} - -func (o *Options) AddOption(option string, action func()) { - var optionWithNumber string - var optionNumber = len(o.options) - optionWithNumber = fmt.Sprintf("%d) %s", optionNumber, option) - o.options = append(o.options, optionWithNumber) - o.optionsAction = append(o.optionsAction, optionWithNumber) - o.optionsWithFunc[optionNumber] = action -} - -func (o *Options) getOptionMessage() string { - option := o.options[o.cursor] - option = strings.TrimPrefix(option, fmt.Sprintf("%d) ", o.cursor)) - return option -} - -func (o *Options) showAreYouSure() { - if !o.modelLock { - o.previousModelError = *o.modelError - o.modelLock = true - } - o.modelError.Reset() - o.modelError.SetProgressMessage(fmt.Sprintf("Are you sure you want to %s?", o.getOptionMessage())) -} - -func (o *Options) switchToPreviousError() { - if o.modelLock { - return - } - *o.modelError = o.previousModelError -} - -func (o *Options) executeOption() { - go o.optionsWithFunc[o.cursor]() - o.cursor = 0 - o.timer = -1 -} - func (o *Options) Init() tea.Cmd { - return nil -} - -func (o *Options) resetOptionsWithOriginal() { - if o.isTabSelected { - return - } - o.isTabSelected = true - o.timer = 3 - for o.timer >= 0 { - o.optionsAction[0] = fmt.Sprintf("> %ds", o.timer) - time.Sleep(1 * time.Second) - o.timer-- + return func() tea.Msg { + return tea.KeyMsg{} } - o.modelLock = false - o.switchToPreviousError() - o.optionsAction[0] = string(OptionIdle) - o.cursor = 0 - o.isTabSelected = false } -func (o *Options) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (o *Options) Update(msg tea.Msg) (*Options, tea.Cmd) { var cmd tea.Cmd if o.status == OptionWait || o.status == OptionNone { @@ -169,14 +110,6 @@ func (o *Options) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return o, cmd } -func (o *Options) updateCursor(cursor int) { - if cursor < len(o.options) { - o.cursor = cursor - o.showAreYouSure() - go o.resetOptionsWithOriginal() - } -} - func (o *Options) View() string { var style = o.Style.Foreground(lipgloss.Color("15")) @@ -202,3 +135,72 @@ func (o *Options) View() string { return lipgloss.JoinHorizontal(lipgloss.Top, opts...) } + +func (o *Options) resetOptionsWithOriginal() { + if o.isTabSelected { + return + } + o.isTabSelected = true + o.timer = 3 + for o.timer >= 0 { + o.optionsAction[0] = fmt.Sprintf("> %ds", o.timer) + time.Sleep(1 * time.Second) + o.timer-- + } + o.modelLock = false + o.switchToPreviousError() + o.optionsAction[0] = string(OptionIdle) + o.cursor = 0 + o.isTabSelected = false +} + +func (o *Options) updateCursor(cursor int) { + if cursor < len(o.options) { + o.cursor = cursor + o.showAreYouSure() + go o.resetOptionsWithOriginal() + } +} + +func (o *Options) SetStatus(status OptionStatus) { + o.status = status + o.options[0] = status.String() + o.optionsAction[0] = status.String() +} + +func (o *Options) AddOption(option string, action func()) { + var optionWithNumber string + var optionNumber = len(o.options) + optionWithNumber = fmt.Sprintf("%d) %s", optionNumber, option) + o.options = append(o.options, optionWithNumber) + o.optionsAction = append(o.optionsAction, optionWithNumber) + o.optionsWithFunc[optionNumber] = action +} + +func (o *Options) getOptionMessage() string { + option := o.options[o.cursor] + option = strings.TrimPrefix(option, fmt.Sprintf("%d) ", o.cursor)) + return option +} + +func (o *Options) showAreYouSure() { + if !o.modelLock { + o.previousModelError = *o.modelError + o.modelLock = true + } + o.modelError.Reset() + o.modelError.SetProgressMessage(fmt.Sprintf("Are you sure you want to %s?", o.getOptionMessage())) +} + +func (o *Options) switchToPreviousError() { + if o.modelLock { + return + } + *o.modelError = o.previousModelError +} + +func (o *Options) executeOption() { + go o.optionsWithFunc[o.cursor]() + o.cursor = 0 + o.timer = -1 +} diff --git a/internal/terminal/handler/types/types.go b/internal/terminal/handler/types/types.go index 8c9fcc3..33f3087 100644 --- a/internal/terminal/handler/types/types.go +++ b/internal/terminal/handler/types/types.go @@ -1,9 +1,46 @@ package types +import ( + "github.com/charmbracelet/bubbles/viewport" + + "sync" +) + type SelectedRepository struct { RepositoryName string // full repository name (owner/name) WorkflowName string // workflow name BranchName string // branch name } +var ( + onceSelectedRepository sync.Once + selectedRepository *SelectedRepository +) + +func NewSelectedRepository() *SelectedRepository { + onceSelectedRepository.Do(func() { + selectedRepository = &SelectedRepository{} + }) + return selectedRepository +} + var ScreenWidth *int + +// ---------------------------------------------- + +const ( + MinTerminalWidth = 102 + MinTerminalHeight = 24 +) + +var ( + onceViewport sync.Once + vp *viewport.Model +) + +func NewTerminalViewport() *viewport.Model { + onceViewport.Do(func() { + vp = &viewport.Model{Width: MinTerminalWidth, Height: MinTerminalHeight} + }) + return vp +} diff --git a/pkg/workflow/workflow.go b/pkg/workflow/workflow.go index bce1a0e..8eed977 100644 --- a/pkg/workflow/workflow.go +++ b/pkg/workflow/workflow.go @@ -49,8 +49,6 @@ type Choice struct { Value string } -// TODO: Add support for boolean - func ParseWorkflow(content py.WorkflowContent) (*Workflow, error) { var w = &Workflow{ Content: make(map[string]Content),